From 3d6851c57a3ecda9cea67e8d9184e20f3c890481 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 23 Jun 2026 16:07:53 -0700 Subject: [PATCH 01/90] rdi_from_Dwarf: count dependency counts correctly when using cached type content hashes --- src/rdi_from_dwarf/rdi_from_dwarf.c | 8 ++++++-- src/rdi_from_dwarf/rdi_from_dwarf.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) 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; From 1901e67f39a115f08628bd34941623503f3fb19c Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 23 Jun 2026 20:00:57 -0600 Subject: [PATCH 02/90] use synchronous reads for call stack computation, but extend artifact cache with the ability to say that a non-retryable failure occurred, so that we can fall back to stale call stacks --- project.4coder | 2 +- project.raddbg | 29 ++----- src/artifact_cache/artifact_cache.c | 38 +++++---- src/artifact_cache/artifact_cache.h | 13 ++- src/dbg_engine/dbg_engine_ctrl.c | 81 ++++++++++++------- src/dbg_engine/dbg_engine_ctrl.h | 9 +-- src/dbg_info/dbg_info.c | 4 +- src/dbg_info/dbg_info.h | 4 +- src/disasm/disasm.c | 4 +- src/disasm/disasm.h | 2 +- src/eval/eval_types.c | 4 +- src/file_stream/file_stream.c | 4 +- src/file_stream/file_stream.h | 2 +- src/raddbg/raddbg_core.c | 6 +- src/raddbg/raddbg_core.h | 6 +- src/raddbg/raddbg_views.c | 4 +- .../tests/rdi_from_dwarf_tests.c | 2 +- src/text/text.c | 2 +- src/text/text.h | 2 +- 19 files changed, 122 insertions(+), 96 deletions(-) diff --git a/project.4coder b/project.4coder index 850ffd88..4c4e9f4c 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 raddbg no_meta debug telemetry", .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..bffd93b9 100644 --- a/project.raddbg +++ b/project.raddbg @@ -9,13 +9,6 @@ target: debug_subprocesses: 0 } target: -{ - executable: "build/raddbg.exe" - working_directory: "../raddebugger" - arguments: "--user:test123" - debug_subprocesses: 0 -} -target: { executable: "build/mule_main.exe" working_directory: build @@ -25,7 +18,6 @@ target: { executable: "build/torture.exe" working_directory: build - arguments: "raddbg/*" } target: { @@ -40,20 +32,13 @@ target: } target: { - executable: "build/release_version/raddbg.exe" - working_directory: "build/release_version" -} -target: -{ - executable: "build/radbin.exe" + executable: "build/ryan_scratch.exe" working_directory: build - arguments: "--breakpad fat.so --capture" -} -target: -{ - executable: "build/radbin.exe" - working_directory: build - arguments: "--rdi raddbg" enabled: 1 } -watch_pin: expression: tag_hash_slots +target: +{ + executable: "build/raddbg.exe" + working_directory: "build/" + arguments: "--bin --dump test123.rdi" +} 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..ff61e9e0 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; diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 943f7ad8..977ca9d6 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -1781,9 +1781,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 +1890,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 +1947,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; @@ -5626,7 +5636,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 +5778,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 +5991,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 +6018,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 +6027,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 +6034,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 +6044,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 +6173,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 +6214,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 +6264,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 +6375,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..c55144e1 100644 --- a/src/dbg_engine/dbg_engine_ctrl.h +++ b/src/dbg_engine/dbg_engine_ctrl.h @@ -640,7 +640,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 +652,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_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..c933c200 100644 --- a/src/dbg_info/dbg_info.h +++ b/src/dbg_info/dbg_info.h @@ -363,14 +363,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_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/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..03ed9777 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); diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 9fec8814..18dc5d4a 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -10088,7 +10088,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 +10105,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 +10122,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}; { diff --git a/src/raddbg/raddbg_core.h b/src/raddbg/raddbg_core.h index b7994c02..c473ee8f 100644 --- a/src/raddbg/raddbg_core.h +++ b/src/raddbg/raddbg_core.h @@ -794,9 +794,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_views.c b/src/raddbg/raddbg_views.c index f7b7f018..8db66e9d 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -4504,7 +4504,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 +5130,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/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/text/text.c b/src/text/text.c index 2e414aeb..ba7d2918 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -2685,7 +2685,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); diff --git a/src/text/text.h b/src/text/text.h index 2f039b9e..6425f848 100644 --- a/src/text/text.h +++ b/src/text/text.h @@ -232,7 +232,7 @@ internal TXT_ScopeNode *txt_scope_node_from_info_pt(TXT_TextInfo *info, 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); From 6b841d9959fd097811411155cc1942956806c0e5 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 24 Jun 2026 07:20:06 -0600 Subject: [PATCH 03/90] rdi_make: allow duplicates in bake name map insertions - they become extremely costly to check for, and we don't produce them anyways... --- src/lib_rdi_make/rdi_make.c | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) 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; } } From 296c851272f7acb885e85642aa808b9d346f43fd Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 24 Jun 2026 16:29:43 -0700 Subject: [PATCH 04/90] eval2 checkpoint: casts, function calls, expression-embedded identifier definitions, ternaries, compile-time ternaries --- project.4coder | 2 +- project.raddbg | 38 +++- src/eval2/eval2.c | 373 +++++++++++++++++++++++++++---- src/eval2/eval2.h | 11 +- src/eval2/eval2.mdesk | 4 +- src/eval2/generated/eval2.meta.c | 4 +- src/eval2/generated/eval2.meta.h | 4 +- src/lib_rdi/rdi.c | 20 +- src/rdi/rdi.mdesk | 20 +- src/scratch/ryan_scratch.c | 80 ++++++- 10 files changed, 470 insertions(+), 86 deletions(-) diff --git a/project.4coder b/project.4coder index 4c4e9f4c..0dfc2fee 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 raddbg 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 ryan_scratch no_meta && pushd build && ryan_scratch && popd", .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 bffd93b9..8c9b43d1 100644 --- a/project.raddbg +++ b/project.raddbg @@ -9,6 +9,13 @@ target: debug_subprocesses: 0 } target: +{ + executable: "build/raddbg.exe" + working_directory: "../raddebugger" + arguments: "--user:test123" + debug_subprocesses: 0 +} +target: { executable: "build/mule_main.exe" working_directory: build @@ -18,6 +25,7 @@ target: { executable: "build/torture.exe" working_directory: build + arguments: "raddbg/*" } target: { @@ -31,14 +39,32 @@ target: arguments: "--rdi raddbg" } target: +{ + executable: "build/release_version/raddbg.exe" + working_directory: "build/release_version" +} +target: +{ + executable: "build/radbin.exe" + working_directory: build + 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" +} +watch_pin: expression: tag_hash_slots +target: { executable: "build/ryan_scratch.exe" working_directory: build enabled: 1 } -target: -{ - executable: "build/raddbg.exe" - working_directory: "build/" - arguments: "--bin --dump test123.rdi" -} diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index 9ee5e7be..c44262d6 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -796,11 +796,16 @@ internal E2_Expr * e2_expr_binary_op(Arena *arena, E2_TypeKey type_key, RDI_EvalOp op, E2_Expr *lhs, E2_Expr *rhs) { E2_Expr *e = e2_expr(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); + 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_expr_push_child(e, lhs); e2_expr_push_child(e, rhs); return e; @@ -900,6 +905,12 @@ e2_expr_convert_if_possible(Arena *arena, E2_Expr *expr, E2_TypeKey dst_type_key e2_expr_push_child(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)) { @@ -909,10 +920,20 @@ e2_expr_convert_if_possible(Arena *arena, E2_Expr *expr, E2_TypeKey dst_type_key return result; } +internal E2_Expr * +e2_expr_type(Arena *arena, E2_TypeKey type_key) +{ + E2_Expr *expr = e2_expr(arena); + expr->type_key = type_key; + expr->mode = E2_Mode_Type; + return expr; +} + internal void e2_expr_push_child(E2_Expr *parent, E2_Expr *expr) { SLLQueuePush_NZ(&e2_expr_nil, parent->first, parent->last, expr, next); + parent->child_count += 1; } //////////////////////////////// @@ -1148,10 +1169,11 @@ 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, E2_ExprMap *expr_map, E2_Expr *access_result, E2_Val compile_time_eval_result, String8 string) { U64 off = state->string_off; E2_Parse parse = {E2_ParseStatus_Error, .expr = &e2_expr_nil, .params_expr = &e2_expr_nil}; + E2_Expr *last_chained_expr = &e2_expr_nil; E2_Expr *next_access_result = access_result; //- rjf: parse & attach to top parsing task - if we don't have a top task @@ -1258,6 +1280,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E { expr = next_access_result; next_access_result = &e2_expr_nil; + state->caller_request_count = 0; } // rjf: if we don't, ask for it - reset the offset to before this token @@ -1272,12 +1295,34 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E } } - //- rjf: standalone identifiers (also could be operator keyword) + //- rjf: standalone identifiers (also could be definition or operator keyword) else if(need_new_expr && e2_try_token(string, E2_TokenKind_Identifier, s(""), &off, &token)) { String8 identifier = str8_substr(string, token.range); B32 identifier_mapped = 0; + // rjf: first try to resolve as a definition + if(!identifier_mapped && e2_try_token(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 = 1; + task->op_kind = E2_OpKind_Define; + task->max_precedence = e2_op_kind_info_table[E2_OpKind_Define].precedence; + task->identifier = identifier; + SLLStackPush(state->top_task, task); + } + // rjf: first try to resolve as an operator name if(!identifier_mapped) { @@ -1324,6 +1369,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E { expr = e2_expr_from_name(expr_map, identifier); identifier_mapped = (expr != &e2_expr_nil); + state->caller_request_count = 0; } // rjf: couldn't map -> ask caller to resolve it @@ -1331,7 +1377,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E { done = 1; parse.status = E2_ParseStatus_MissedIdentifierResolution; - parse.missed_identifier = identifier; + parse.identifier = identifier; off = start_off; } } @@ -1386,6 +1432,30 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E } } + //- rjf: leaf string literals + else if(need_new_expr && e2_try_token(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); + expr->op = RDI_EvalOp_ConstString; + expr->mode = E2_Mode_Value; + expr->string = raw_string; + expr->val.u64 = raw_string.size; + expr->type_key = e2_type_key_cons_array(e2_type_key_basic(E2_TypeKind_UChar8), raw_string.size); + } + + //- rjf: leaf character literals + else if(need_new_expr && e2_try_token(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); + UnicodeDecode decode = utf8_decode(raw_string.str, raw_string.size); + expr = e2_expr_const_u64_or_smaller(arena, decode.codepoint); + } + //- rjf: extend parsed expression tree with trailing operators if(!state->caller_info_completes_task && expr != &e2_expr_nil) { @@ -1452,7 +1522,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E if(state->caller_info_completes_task || expr != &e2_expr_nil) { //- rjf: pop finished expressions - for(;state->top_task != 0 && !done;) + for(B32 task_pushed = 0; state->top_task != 0 && !done && !task_pushed;) { //- rjf: gather finished expression to current task; increase task child count if(!state->caller_info_completes_task) @@ -1482,9 +1552,48 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E closer_found = e2_try_token(string, E2_TokenKind_Symbol, state->top_task->expected_closer, &off, 0); } + //- 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 = (closer_found && state->top_task->child_count == 1 && state->top_task->first_child->v->mode == E2_Mode_Type); + if(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 = 2; + task->op_kind = E2_OpKind_CCast; + task->max_precedence = e2_op_kind_info_table[E2_OpKind_CCast].precedence; + 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; + task_pushed = 1; + } + } + //- 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) + 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; @@ -1494,7 +1603,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E 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; + E2_Expr *mhs = completed_task->child_count == 3 ? completed_task->first_child->next->v : &e2_expr_nil; { RDI_EvalOp op = RDI_EvalOp_Stop; E2_TypeKey dst_type_key = {E2_TypeKeyKind_Null}; @@ -1558,6 +1667,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E { finished_root = next_access_result; next_access_result = &e2_expr_nil; + state->caller_request_count = 0; } else { @@ -1573,7 +1683,30 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E //- rjf: calls case E2_OpKind_Call: { - // TODO(rjf) + // 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_access_result != &e2_expr_nil) + { + finished_root = next_access_result; + next_access_result = &e2_expr_nil; + state->caller_request_count = 0; + } + else + { + done = 1; + parse.status = E2_ParseStatus_Call; + parse.expr = lhs; + parse.params_expr = rhs; + state->caller_info_completes_task = 1; + } + } }break; //- rjf: sizeof @@ -1585,6 +1718,32 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E finished_root->type_key = e2_type_key_basic(E2_TypeKind_U64); }break; + //- rjf: typeof + case E2_OpKind_TypeOf: + { + E2_TypeKey rhs_type_key = e2_type_key_unwrap(rhs->type_key, E2_TypeUnwrapFlag_AllDecorative & ~E2_TypeUnwrapFlag_Enums); + finished_root = e2_expr(arena); + finished_root->mode = E2_Mode_Type; + finished_root->type_key = rhs_type_key; + }break; + + //- rjf: casts + case E2_OpKind_CCast: + cast:; + { + E2_Expr *cast_type_expr = lhs; + E2_TypeKey cast_type_key = cast_type_expr->type_key; + if(e2_type_key_match(e2_type_key_zero(), cast_type_key)) + { + e2_msgf(arena, &parse.msgs, completed_task->src_range, "Cannot cast to this type."); + } + else + { + E2_Expr *castee_expr = rhs; + finished_root = e2_expr_convert_if_possible(arena, castee_expr, cast_type_key); + } + }break; + //- rjf: dereferences case E2_OpKind_Deref: case E2_OpKind_DerefAsm: @@ -1801,16 +1960,21 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E if(!malformed) { E2_TypeKey dst_type_key = lhs_type_key; + E2_Expr *lhs_converted = lhs_value; + E2_Expr *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_expr_convert_if_possible(arena, lhs_value, coerced_type_key); + rhs_converted = e2_expr_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_expr_convert_if_possible(arena, lhs_value, dst_type_key); + rhs_converted = e2_expr_convert_if_possible(arena, rhs_value, dst_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; @@ -1818,36 +1982,149 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E //- rjf: pointer-add arithmetic case ArithKind_PtrAdd: { - + // TODO(rjf) }break; //- rjf: pointer-sub arithmetic case ArithKind_PtrSub: { - + // TODO(rjf) }break; //- rjf: pointer-array comparisons case ArithKind_PtrArrayCompare: { - + // TODO(rjf) }break; //- rjf: type comparisons case ArithKind_TypeCompare: { - + // TODO(rjf) }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: definitions + case E2_OpKind_Define: + { + if(completed_with_caller_info) + { + finished_root = lhs; + } + else + { + done = 1; + parse.status = E2_ParseStatus_NewIdentifierDefinition; + parse.expr = lhs; + parse.identifier = completed_task->identifier; + state->caller_info_completes_task = 1; + } + }break; //- rjf: conditionals case E2_OpKind_Cond: { - // TODO(rjf) + // rjf: unpack operands + E2_Expr *condition_expr = lhs; + E2_Expr *pass_expr = mhs; + E2_Expr *fail_expr = rhs; + E2_TypeKey type_key = e2_coerced_type_key_from_operands(pass_expr->type_key, fail_expr->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_expr->type_key)); + RDI_EvalTypeGroup fail_type_group = e2_type_group_from_kind(e2_type_kind_from_key(fail_expr->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; + } + } + + // rjf: compile-time conditional -> ask caller to evaluate conditional + if(compile_time_cond) + { + if(!completed_with_caller_info) + { + done = 1; + parse.status = E2_ParseStatus_CompileTimeEval; + parse.expr = lhs; + state->caller_info_completes_task = 1; + } + else + { + if(compile_time_eval_result.u64 == 0) + { + finished_root = fail_expr; + } + else + { + finished_root = pass_expr; + } + } + } + + // rjf: runtime conditional -> build opcodes for evaluating condition, jumping, etc. + else + { + // rjf: convert pass/fail to coerced type + pass_expr = e2_expr_convert_if_possible(arena, pass_expr, type_key); + fail_expr = e2_expr_convert_if_possible(arena, fail_expr, type_key); + + // rjf: form conditional tree + E2_Expr *cond_root = e2_expr(arena); + E2_Expr *cjump_expr = e2_expr(arena); + E2_Expr *jump_expr = e2_expr(arena); + e2_expr_push_child(cond_root, condition_expr); + e2_expr_push_child(cond_root, cjump_expr); + e2_expr_push_child(cond_root, fail_expr); + e2_expr_push_child(cond_root, jump_expr); + e2_expr_push_child(cond_root, pass_expr); + + // rjf: compute # of bytes for pass + U64 pass_expr_byte_count = 0; + { + Temp scratch = scratch_begin(&arena, 1); + String8 pass_bytecode = e2_bytecode_from_expr(scratch.arena, pass_expr); + pass_expr_byte_count = pass_bytecode.size; + scratch_end(scratch); + } + + // rjf: configure unconditional jump (fail -> end) + jump_expr->op = RDI_EvalOp_Skip; + jump_expr->val.u64 = pass_expr_byte_count; + + // rjf: compute # of bytes for fail + U64 fail_expr_byte_count = 0; + { + Temp scratch = scratch_begin(&arena, 1); + String8 fail_bytecode = e2_bytecode_from_expr(scratch.arena, fail_expr); + fail_expr_byte_count = fail_bytecode.size; + scratch_end(scratch); + } + + // rjf: compute # of bytes for unconditional jump + U64 jump_expr_byte_count = 0; + { + Temp scratch = scratch_begin(&arena, 1); + String8 jump_bytecode = e2_bytecode_from_expr(scratch.arena, jump_expr); + jump_expr_byte_count = jump_bytecode.size; + scratch_end(scratch); + } + + // rjf: configure conditional jump + cjump_expr->op = RDI_EvalOp_Cond; + cjump_expr->val.u64 = fail_expr_byte_count + jump_expr_byte_count; + + // rjf: fill conditional root type + cond_root->type_key = type_key; + + // rjf: take result + finished_root = cond_root; + } }break; } } @@ -1858,15 +2135,15 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E 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: work on the parent of the finished expression next + expr = finished_root; } //- rjf: if the top task is not done -> break & continue - reset expression, because we need to parse another. @@ -1876,25 +2153,24 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E break; } } - - //- 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: we're always done if there's nothing left to parse, *or* if we made no progress. + if(off >= string.size || (off == start_off && !e2_parse_status_is_caller_request(parse.status))) + { + done = 1; + if(state->top_task == 0) { - parse.expr = expr; + if(parse.expr == &e2_expr_nil && expr != &e2_expr_nil) + { + parse.expr = expr; + } if(parse.status == E2_ParseStatus_Error) { parse.status = E2_ParseStatus_Good; } - break; } } - - //- rjf: we're always done if there's nothing left to parse - if(off >= string.size) - { - done = 1; - } } //- rjf: asking caller for info? -> if we already did this at this offset, @@ -1905,7 +2181,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E { if(parse.status == E2_ParseStatus_MissedIdentifierResolution) { - 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; } @@ -1977,6 +2253,10 @@ e2_bytecode_from_expr(Arena *arena, E2_Expr *expr) //- rjf: unpack this op U16 ctrlbits = rdi_eval_op_ctrlbits_table[e->op]; U64 child_count = RDI_POPN_FROM_CTRLBITS(ctrlbits); + if(e->op == 0) + { + child_count = e->child_count; + } //- rjf: push the next child task if we can if(t->pushed_child_count < child_count) @@ -2003,17 +2283,24 @@ e2_bytecode_from_expr(Arena *arena, E2_Expr *expr) //- 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(e->op != 0) { - ctrlbits = rdi_eval_op_ctrlbits_table[e->op]; + U16 ctrlbits = 0; + if(e->op < RDI_EvalOp_COUNT) + { + ctrlbits = rdi_eval_op_ctrlbits_table[e->op]; + } + 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)); + if(e->string.size != 0) + { + str8_list_push(scratch.arena, &strings, e->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); } diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index f920d69e..61ee043b 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -54,11 +54,13 @@ typedef enum E2_ParseStatus //- rjf: caller-provided info E2_ParseStatus_MissedIdentifierResolution, + E2_ParseStatus_NewIdentifierDefinition, E2_ParseStatus_MemberAccess, E2_ParseStatus_IndexAccess, E2_ParseStatus_Call, + E2_ParseStatus_CompileTimeEval, E2_ParseStatus_FirstCallerRequest = E2_ParseStatus_MissedIdentifierResolution, - E2_ParseStatus_LastCallerRequest = E2_ParseStatus_Call, + E2_ParseStatus_LastCallerRequest = E2_ParseStatus_CompileTimeEval, } E2_ParseStatus; @@ -315,6 +317,7 @@ struct E2_Expr E2_Expr *first; E2_Expr *last; E2_Expr *next; + U64 child_count; Rng1U64 src_range; String8 string; RDI_EvalOp op; @@ -356,6 +359,7 @@ struct E2_ParseTask U64 child_count_target; S64 max_precedence; E2_OpKind op_kind; + String8 identifier; String8 expected_closer; String8 expected_splitter; B32 splitter_is_required; @@ -393,7 +397,7 @@ typedef struct E2_Parse E2_Parse; struct E2_Parse { E2_ParseStatus status; - String8 missed_identifier; + String8 identifier; E2_Expr *expr; String8 member_name; E2_Expr *params_expr; @@ -566,6 +570,7 @@ internal E2_Expr *e2_expr_binary_op(Arena *arena, E2_TypeKey type_key, RDI_EvalO 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 E2_Expr *e2_expr_type(Arena *arena, E2_TypeKey type_key); internal void e2_expr_push_child(E2_Expr *parent, E2_Expr *expr); //////////////////////////////// @@ -574,7 +579,7 @@ internal void e2_expr_push_child(E2_Expr *parent, E2_Expr *expr); 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_Parse e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E2_Expr *access_result, E2_Val compile_time_eval_result, String8 string); //////////////////////////////// //~ rjf: Expression -> Bytecode diff --git a/src/eval2/eval2.mdesk b/src/eval2/eval2.mdesk index 2f07c727..bfba77cd 100644 --- a/src/eval2/eval2.mdesk +++ b/src/eval2/eval2.mdesk @@ -9,6 +9,8 @@ E2_OpTable: {Call Call 1 "" "(" ")" ","} {DerefAsm UnaryPrefix 1 "[" "" "]" "" } {SizeOf UnaryPrefix 1 "sizeof " "" "" "" } + {TypeOf UnaryPrefix 1 "typeof " "" "" "" } + {CCast UnaryPrefix 1 "" "" "" "" } {Deref UnaryPrefix 2 "*" "" "" "" } {Address UnaryPrefix 2 "&" "" "" "" } {Pos UnaryPrefix 2 "+" "" "" "" } @@ -33,7 +35,7 @@ E2_OpTable: {BitOr Binary 10 "" "|" "" "" } {LogAnd Binary 11 "" "&&" "" "" } {LogOr Binary 12 "" "||" "" "" } - {Define Binary 13 "" "=" "" "" } + {Define Null 13 "" "=" "" "" } {Cond Ternary 14 "" "?" "" ":"} } diff --git a/src/eval2/generated/eval2.meta.c b/src/eval2/generated/eval2.meta.c index 0b8b63b1..7fd3f85b 100644 --- a/src/eval2/generated/eval2.meta.c +++ b/src/eval2/generated/eval2.meta.c @@ -4,7 +4,7 @@ //- GENERATED CODE C_LINKAGE_BEGIN -E2_OpInfo e2_op_kind_info_table[32] = +E2_OpInfo e2_op_kind_info_table[34] = { {0}, {E2_OpParseKind_Binary, 1, str8_lit_comp(""), str8_lit_comp("."), str8_lit_comp(""), str8_lit_comp("")}, @@ -12,6 +12,8 @@ E2_OpInfo e2_op_kind_info_table[32] = {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, 1, str8_lit_comp("typeof "), 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, 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("")}, diff --git a/src/eval2/generated/eval2.meta.h b/src/eval2/generated/eval2.meta.h index eedb460f..be6b0ca2 100644 --- a/src/eval2/generated/eval2.meta.h +++ b/src/eval2/generated/eval2.meta.h @@ -14,6 +14,8 @@ E2_OpKind_Index, E2_OpKind_Call, E2_OpKind_DerefAsm, E2_OpKind_SizeOf, +E2_OpKind_TypeOf, +E2_OpKind_CCast, E2_OpKind_Deref, E2_OpKind_Address, E2_OpKind_Pos, @@ -122,7 +124,7 @@ E2_TypeKind_LastMeta = E2_TypeKind_MetaDescription, } E2_TypeKind; C_LINKAGE_BEGIN -extern E2_OpInfo e2_op_kind_info_table[32]; +extern E2_OpInfo e2_op_kind_info_table[34]; extern U8 e2_type_kind_basic_byte_size_table[61]; extern String8 e2_type_kind_basic_string_table[61]; 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/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/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index 8c19eaf3..0721d17a 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -36,6 +36,22 @@ entry_point(CmdLine *cmdline) { String8 strings[] = { + // (A)B + // (A) & B + // (A) + // int & B + // (1 + (int)&B) + s("foo = 123"), + s("1 > 2"), + s("1 ? \"Test\" : 888"), + s("'a'"), + s("'b'"), + s("float32(111)"), + s("float64(111)"), + s("222.f"), + s("222.0"), + s("(float32)222"), + s("(float64)222"), s("[123]"), s("foo"), s("foo.bar"), @@ -55,6 +71,7 @@ entry_point(CmdLine *cmdline) s("-123"), s("sizeof 123"), s("1 ? 123 : 456"), + s("0 ? 123 : 456"), s("123(1, 2, 3)"), }; for EachElement(idx, strings) @@ -68,9 +85,10 @@ entry_point(CmdLine *cmdline) E2_ParseState state = {0}; E2_ExprMap expr_map = {0}; E2_Expr *access_expr = &e2_expr_nil; + E2_Val compile_time_eval_result = {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, &expr_map, access_expr, compile_time_eval_result, strings[idx]); expr = parse.expr; access_expr = &e2_expr_nil; for EachNode(n, E2_Msg, parse.msgs.first) @@ -79,15 +97,52 @@ entry_point(CmdLine *cmdline) } if(parse.status == E2_ParseStatus_MissedIdentifierResolution) { - e2_expr_map_push(scratch.arena, &expr_map, parse.missed_identifier, e2_expr_const_u64_or_smaller(scratch.arena, 123)); + E2_Expr *expr = &e2_expr_nil; + if(str8_match(parse.identifier, s("float32"), 0)) + { + expr = e2_expr_type(scratch.arena, e2_type_key_basic(E2_TypeKind_F32)); + } + else if(str8_match(parse.identifier, s("float64"), 0)) + { + expr = e2_expr_type(scratch.arena, e2_type_key_basic(E2_TypeKind_F64)); + } + else if(str8_match(parse.identifier, s("int32"), 0)) + { + expr = e2_expr_type(scratch.arena, e2_type_key_basic(E2_TypeKind_S32)); + } + else if(str8_match(parse.identifier, s("int64"), 0)) + { + expr = e2_expr_type(scratch.arena, e2_type_key_basic(E2_TypeKind_S64)); + } + else + { + expr = e2_expr_const_u64_or_smaller(scratch.arena, 123); + } + e2_expr_map_push(scratch.arena, &expr_map, parse.identifier, expr); + } + if(parse.status == E2_ParseStatus_NewIdentifierDefinition) + { + e2_expr_map_push(scratch.arena, &expr_map, parse.identifier, expr); + } + if(parse.status == E2_ParseStatus_MemberAccess) + { + access_expr = e2_expr_const_u64_or_smaller(scratch.arena, 456); } if(parse.status == E2_ParseStatus_IndexAccess) { access_expr = e2_expr_const_u64_or_smaller(scratch.arena, 111); } - if(parse.status == E2_ParseStatus_MemberAccess) + if(parse.status == E2_ParseStatus_Call) { - access_expr = e2_expr_const_u64_or_smaller(scratch.arena, 456); + access_expr = e2_expr_const_u64_or_smaller(scratch.arena, 123456); + } + if(parse.status == E2_ParseStatus_CompileTimeEval) + { + String8 bytecode = e2_bytecode_from_expr(scratch.arena, expr); + 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_parse_status_is_terminal(parse.status)) { @@ -124,12 +179,17 @@ 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* %s%S\n", + strings[idx], + val.s64, + val.f32, + val.f64, + expr->mode == E2_Mode_Type ? "type" : + expr->mode == E2_Mode_Value ? "value" : + "address", + msgs_string.size != 0 ? " // " : "", msgs_string); + raddbg_log("%S", log); + printf("%.*s", str8_varg(log)); fflush(stdout); scratch_end(scratch); From c817d1f13629a98301409bf33aea1a4f886a5c4d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 24 Jun 2026 20:39:53 -0700 Subject: [PATCH 05/90] eval2: type keys checkpoint --- src/eval2/eval2.c | 37 +++++++++++-------- src/eval2/eval2.h | 92 +++++++++++++---------------------------------- 2 files changed, 46 insertions(+), 83 deletions(-) diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index c44262d6..2ca4ddee 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -432,7 +432,14 @@ e2_byte_size_from_type_key(E2_TypeKey k) }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; @@ -485,10 +492,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,10 +516,7 @@ 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: - { - // TODO(rjf) - }break; + case E2_TypeKeyKind_Cons:{}break; case E2_TypeKeyKind_Reg:{}break; } } @@ -543,7 +544,7 @@ e2_arch_from_type_key(E2_TypeKey k) }break; case E2_TypeKeyKind_Reg: { - arch = k.u32[0]; + arch = (Arch)k.u32[0]; }break; } return arch; @@ -608,10 +609,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; } } @@ -1700,10 +1698,15 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E } else { + E2_Expr *params_expr = e2_expr(arena); + for(E2_ExprNode *n = completed_task->first_child->next; n != 0; n = n->next) + { + e2_expr_push_child(params_expr, n->v); + } done = 1; parse.status = E2_ParseStatus_Call; parse.expr = lhs; - parse.params_expr = rhs; + parse.params_expr = params_expr; state->caller_info_completes_task = 1; } } @@ -2202,6 +2205,10 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E { e2_msgf(arena, &parse.msgs, t->src_range, "Couldn't index into this type."); } + else if(t->child_count >= 1 && t->op_kind == E2_OpKind_Call) + { + e2_msgf(arena, &parse.msgs, t->src_range, "Couldn't call into this type."); + } else if(t->child_count == 1 && t->child_count_target == 2 && t->op_kind != E2_OpKind_Null) { e2_msgf(arena, &parse.msgs, t->src_range, "Expected expression after binary operator `%S`.", e2_op_kind_info_table[t->op_kind].sep); diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index 61ee043b..71459b1f 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -135,69 +135,37 @@ struct E2_TypeKeyList }; //////////////////////////////// -//~ rjf: Unpacked Type Info +//~ rjf: Constructed Type Cache Types -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; - -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; struct E2_Expr **args; }; +typedef struct E2_ConsTypeNode E2_ConsTypeNode; +struct E2_ConsTypeNode +{ + E2_ConsTypeNode *key_next; + E2_ConsTypeNode *content_next; + E2_TypeKey key; + E2_ConsTypeParams params; + U64 byte_size; +}; + +typedef struct E2_ConsTypeSlot E2_ConsTypeSlot; +struct E2_ConsTypeSlot +{ + E2_ConsTypeNode *first; + E2_ConsTypeNode *last; +}; + //////////////////////////////// //~ rjf: Evaluation Values @@ -502,24 +470,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); From 38c1e2a414cc0eaf387ee9460125310326b9bfc3 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 25 Jun 2026 09:07:34 -0600 Subject: [PATCH 06/90] eval2: ptr arithmetic & comparisons --- project.raddbg | 38 +++---------------- src/eval2/eval2.c | 77 ++++++++++++++++++++++++++++++++++++-- src/eval2/eval2.h | 3 ++ src/scratch/ryan_scratch.c | 1 + 4 files changed, 84 insertions(+), 35 deletions(-) diff --git a/project.raddbg b/project.raddbg index 8c9b43d1..99025277 100644 --- a/project.raddbg +++ b/project.raddbg @@ -9,13 +9,6 @@ target: debug_subprocesses: 0 } target: -{ - executable: "build/raddbg.exe" - working_directory: "../raddebugger" - arguments: "--user:test123" - debug_subprocesses: 0 -} -target: { executable: "build/mule_main.exe" working_directory: build @@ -25,7 +18,6 @@ target: { executable: "build/torture.exe" working_directory: build - arguments: "raddbg/*" } target: { @@ -39,32 +31,14 @@ target: arguments: "--rdi raddbg" } target: -{ - executable: "build/release_version/raddbg.exe" - working_directory: "build/release_version" -} -target: -{ - executable: "build/radbin.exe" - working_directory: build - 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" -} -watch_pin: expression: tag_hash_slots -target: { executable: "build/ryan_scratch.exe" working_directory: build enabled: 1 } +target: +{ + executable: "build/raddbg.exe" + working_directory: build + arguments: "--bin --dump test123.rdi" +} diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index 2ca4ddee..80f1392e 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -550,6 +550,19 @@ e2_arch_from_type_key(E2_TypeKey k) return arch; } +//- 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) + { + // TODO(rjf) + } + return result; +} + //- rjf: type graph traversal primitives internal E2_TypeKey @@ -1985,13 +1998,54 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E //- rjf: pointer-add arithmetic case ArithKind_PtrAdd: { - // TODO(rjf) + // rjf: l/r -> ptr + int + E2_Expr *ptr_expr = lhs; + E2_Expr *int_expr = rhs; + if(e2_type_kind_is_ptr_or_ref(rhs_type_kind)) + { + ptr_expr = rhs; + int_expr = lhs; + } + + // rjf: unpack ptee type + E2_TypeKey ptee_type_key = e2_type_key_direct(e2_type_key_undecorate(ptr_expr->type_key)); + U64 ptee_byte_size = e2_byte_size_from_type_key(ptee_type_key); + + // rjf: form tree + E2_Expr *base_addr_expr = e2_expr_resolve_to_value(arena, ptr_expr); + E2_Expr *index_expr = e2_expr_resolve_to_value(arena, int_expr); + E2_Expr *offset_expr = index_expr; + if(ptee_byte_size > 1) + { + E2_Expr *size_expr = e2_expr_const_u64_or_smaller(arena, ptee_byte_size); + offset_expr = e2_expr_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Mul, index_expr, size_expr); + } + E2_Expr *addr_value_expr = e2_expr_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Add, base_addr_expr, offset_expr); + addr_value_expr->mode = E2_Mode_Address; + addr_value_expr->type_key = ptr_expr->type_key; + finished_root = addr_value_expr; }break; //- rjf: pointer-sub arithmetic case ArithKind_PtrSub: { - // TODO(rjf) + E2_Expr *lhs_addr_value_expr = e2_expr_resolve_to_value(arena, lhs); + E2_Expr *rhs_addr_value_expr = e2_expr_resolve_to_value(arena, rhs); + E2_Expr *sub_expr = e2_expr_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Sub, lhs_addr_value_expr, rhs_addr_value_expr); + E2_Expr *sub_divided_expr = sub_expr; + 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_expr = e2_expr_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Div, sub_expr, e2_expr_const_u64_or_smaller(arena, ptee_byte_size)); + } + } + finished_root = sub_divided_expr; }break; //- rjf: pointer-array comparisons @@ -2003,7 +2057,24 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E //- rjf: type comparisons case ArithKind_TypeCompare: { - // TODO(rjf) + if(completed_task->op_kind == E2_OpKind_EqEq || + completed_task->op_kind == E2_OpKind_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(completed_task->op_kind == E2_OpKind_NtEq) + { + result = !result; + } + finished_root = e2_expr_const_u64_or_smaller(arena, (U64)result); + finished_root->type_key = e2_type_key_basic(E2_TypeKind_Bool); + } + else + { + e2_msgf(arena, &parse.msgs, completed_task->src_range, "Cannot use `%S` on types.", str8_skip_chop_whitespace(e2_op_kind_info_table[completed_task->op_kind].sep)); + } }break; } }break; diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index 71459b1f..a1c7ee21 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -490,6 +490,9 @@ internal U64 e2_shift_from_type_key(E2_TypeKey k); internal U64 e2_mask_count_from_type_key(E2_TypeKey k); internal Arch e2_arch_from_type_key(E2_TypeKey k); +//- 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); internal E2_TypeKey e2_type_key_owner(E2_TypeKey k); diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index 0721d17a..bf85cd64 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -41,6 +41,7 @@ entry_point(CmdLine *cmdline) // (A) // int & B // (1 + (int)&B) + s("int32 == int32"), s("foo = 123"), s("1 > 2"), s("1 ? \"Test\" : 888"), From 9d651f4bd545332a0a1e2d6faee86ee71bab350a Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 25 Jun 2026 09:08:52 -0600 Subject: [PATCH 07/90] EpicGamesExt -> EpicGames --- README.md | 4 ++-- src/base/base_context_cracking.h | 2 +- src/raddbg/raddbg.mdesk | 2 +- src/raddbg/raddbg_core.c | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) 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/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/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index ff696199..cd700574 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -2522,7 +2522,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 18dc5d4a..21e21a9d 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -7197,7 +7197,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 +7655,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")); } } @@ -12841,7 +12841,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); From 6c309038cee4e992c16024e981c87a4d0604c2d4 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 25 Jun 2026 09:51:13 -0600 Subject: [PATCH 08/90] dbg_engine: ctrl thread - be robust to strange duplicate module unload events --- src/dbg_engine/dbg_engine_ctrl.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index 977ca9d6..a90b39f9 100644 --- a/src/dbg_engine/dbg_engine_ctrl.c +++ b/src/dbg_engine/dbg_engine_ctrl.c @@ -2816,7 +2816,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; + } } } From c1b34617095a06305ee07416fce88e73acf21bf7 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 25 Jun 2026 11:36:18 -0600 Subject: [PATCH 09/90] eval2: switch to extrusive lists for expression lists - this lets us reuse expression trees. also solve strange isolated unload module events in win32 demon as well --- src/eval2/eval2.c | 62 +++++++++++++++++++---------------- src/eval2/eval2.h | 35 +++++++++++++------- src/scratch/ryan_scratch.c | 1 + src/win32/demon/win32_demon.c | 5 ++- 4 files changed, 62 insertions(+), 41 deletions(-) diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index 80f1392e..287b98c7 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -799,7 +799,7 @@ e2_expr_unary_op(Arena *arena, E2_TypeKey type_key, RDI_EvalOp op, E2_Expr *oper 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_expr_push_child(arena, e, operand); return e; } @@ -817,8 +817,8 @@ e2_expr_binary_op(Arena *arena, E2_TypeKey type_key, RDI_EvalOp op, E2_Expr *lhs e->mode = E2_Mode_Value; 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_expr_push_child(e, lhs); - e2_expr_push_child(e, rhs); + e2_expr_push_child(arena, e, lhs); + e2_expr_push_child(arena, e, rhs); return e; } @@ -833,7 +833,7 @@ e2_expr_resolve_to_value(Arena *arena, E2_Expr *expr) result = e2_expr(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_expr_push_child(arena, result, expr); } // rjf: address evaluations -> read value from space @@ -846,7 +846,7 @@ e2_expr_resolve_to_value(Arena *arena, E2_Expr *expr) 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_expr_push_child(arena, memread_expr, expr); result = memread_expr; } @@ -885,7 +885,7 @@ e2_expr_truncate(Arena *arena, E2_Expr *expr, E2_TypeKey dst_type_key) 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_expr_push_child(arena, result, expr); } return result; } @@ -913,7 +913,7 @@ e2_expr_convert_if_possible(Arena *arena, E2_Expr *expr, E2_TypeKey dst_type_key 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_expr_push_child(arena, result, expr); } // rjf: no-op from src -> dst @@ -941,12 +941,20 @@ e2_expr_type(Arena *arena, E2_TypeKey type_key) } internal void -e2_expr_push_child(E2_Expr *parent, E2_Expr *expr) +e2_expr_push_child_node(E2_Expr *parent, E2_ExprNode *node) { - SLLQueuePush_NZ(&e2_expr_nil, parent->first, parent->last, expr, next); + 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); +} + //////////////////////////////// //~ rjf: String -> Expression @@ -1626,10 +1634,9 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E 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); - } + finished_root->first_child = completed_task->first_child; + finished_root->last_child = completed_task->last_child; + finished_root->child_count = completed_task->child_count; } else { @@ -1712,10 +1719,9 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E else { E2_Expr *params_expr = e2_expr(arena); - for(E2_ExprNode *n = completed_task->first_child->next; n != 0; n = n->next) - { - e2_expr_push_child(params_expr, n->v); - } + params_expr->first_child = completed_task->first_child->next; + params_expr->last_child = completed_task->last_child; + params_expr->child_count = completed_task->child_count-1; done = 1; parse.status = E2_ParseStatus_Call; parse.expr = lhs; @@ -2152,11 +2158,11 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E E2_Expr *cond_root = e2_expr(arena); E2_Expr *cjump_expr = e2_expr(arena); E2_Expr *jump_expr = e2_expr(arena); - e2_expr_push_child(cond_root, condition_expr); - e2_expr_push_child(cond_root, cjump_expr); - e2_expr_push_child(cond_root, fail_expr); - e2_expr_push_child(cond_root, jump_expr); - e2_expr_push_child(cond_root, pass_expr); + e2_expr_push_child(arena, cond_root, condition_expr); + e2_expr_push_child(arena, cond_root, cjump_expr); + e2_expr_push_child(arena, cond_root, fail_expr); + e2_expr_push_child(arena, cond_root, jump_expr); + e2_expr_push_child(arena, cond_root, pass_expr); // rjf: compute # of bytes for pass U64 pass_expr_byte_count = 0; @@ -2318,10 +2324,10 @@ e2_bytecode_from_expr(Arena *arena, E2_Expr *expr) { Task *next; E2_Expr *e; - E2_Expr *last_pushed_child; + E2_ExprNode *last_pushed_child_node; U64 pushed_child_count; }; - Task start_task = {0, expr, &e2_expr_nil}; + Task start_task = {0, expr}; Task *top_task = &start_task; Task *free_task = 0; for(Task *t = top_task; t != 0; t = top_task) @@ -2340,7 +2346,7 @@ e2_bytecode_from_expr(Arena *arena, E2_Expr *expr) 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_ExprNode *next_child_node = (t->last_pushed_child_node == 0 ? e->first_child : t->last_pushed_child_node->next); Task *child_task = free_task; if(child_task != 0) { @@ -2352,10 +2358,10 @@ 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->e = next_child_node ? next_child_node->v : &e2_expr_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 diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index a1c7ee21..46723de3 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -166,6 +166,16 @@ struct E2_ConsTypeSlot E2_ConsTypeNode *last; }; +typedef struct E2_ConsTypeMap E2_ConsTypeMap; +struct E2_ConsTypeMap +{ + U64 id_gen; + U64 content_slots_count; + E2_ConsTypeSlot *content_slots; + U64 key_slots_count; + E2_ConsTypeSlot *key_slots; +}; + //////////////////////////////// //~ rjf: Evaluation Values @@ -279,12 +289,18 @@ struct E2_Token //////////////////////////////// //~ rjf: Expression Tree Building +typedef struct E2_ExprNode E2_ExprNode; +struct E2_ExprNode +{ + E2_ExprNode *next; + struct E2_Expr *v; +}; + typedef struct E2_Expr E2_Expr; struct E2_Expr { - E2_Expr *first; - E2_Expr *last; - E2_Expr *next; + E2_ExprNode *first_child; + E2_ExprNode *last_child; U64 child_count; Rng1U64 src_range; String8 string; @@ -294,13 +310,6 @@ struct E2_Expr E2_Val val; }; -typedef struct E2_ExprNode E2_ExprNode; -struct E2_ExprNode -{ - E2_ExprNode *next; - E2_Expr *v; -}; - typedef struct E2_ExprMapNode E2_ExprMapNode; struct E2_ExprMapNode { @@ -424,8 +433,9 @@ struct E2_Interp //~ rjf: Globals thread_static E2_Assets *e2_assets = 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}; //////////////////////////////// //~ rjf: Space -> Memory Map Helpers @@ -530,7 +540,8 @@ 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 E2_Expr *e2_expr_type(Arena *arena, E2_TypeKey type_key); -internal void e2_expr_push_child(E2_Expr *parent, E2_Expr *expr); +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); //////////////////////////////// //~ rjf: String -> Expression diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index bf85cd64..00f10e92 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -41,6 +41,7 @@ entry_point(CmdLine *cmdline) // (A) // int & B // (1 + (int)&B) + s("123, 456"), s("int32 == int32"), s("foo = 123"), s("1 > 2"), diff --git a/src/win32/demon/win32_demon.c b/src/win32/demon/win32_demon.c index f8c9f6cf..3144f426 100644 --- a/src/win32/demon/win32_demon.c +++ b/src/win32/demon/win32_demon.c @@ -2359,7 +2359,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; From 6b52d68c6268fd97a950f4d5f97e9d206f4a23ba Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 25 Jun 2026 13:50:16 -0700 Subject: [PATCH 10/90] eval2 checkpoint: type operations, constructed types, checkpoint before splitting back to frontend / compiler split - we need to do things with the parse tree that we can't with an ir tree --- project.raddbg | 38 +- src/eval2/eval2.c | 615 ++++++++++++++++++++++++++++++- src/eval2/eval2.h | 102 ++++- src/eval2/eval2.mdesk | 1 + src/eval2/generated/eval2.meta.c | 5 +- src/eval2/generated/eval2.meta.h | 3 +- src/scratch/ryan_scratch.c | 3 + 7 files changed, 745 insertions(+), 22 deletions(-) diff --git a/project.raddbg b/project.raddbg index 99025277..8c9b43d1 100644 --- a/project.raddbg +++ b/project.raddbg @@ -9,6 +9,13 @@ target: debug_subprocesses: 0 } target: +{ + executable: "build/raddbg.exe" + working_directory: "../raddebugger" + arguments: "--user:test123" + debug_subprocesses: 0 +} +target: { executable: "build/mule_main.exe" working_directory: build @@ -18,6 +25,7 @@ target: { executable: "build/torture.exe" working_directory: build + arguments: "raddbg/*" } target: { @@ -31,14 +39,32 @@ target: arguments: "--rdi raddbg" } target: +{ + executable: "build/release_version/raddbg.exe" + working_directory: "build/release_version" +} +target: +{ + executable: "build/radbin.exe" + working_directory: build + 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" +} +watch_pin: expression: tag_hash_slots +target: { executable: "build/ryan_scratch.exe" working_directory: build enabled: 1 } -target: -{ - executable: "build/raddbg.exe" - working_directory: build - arguments: "--bin --dump test123.rdi" -} diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index 287b98c7..c909cc1e 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -163,6 +163,153 @@ 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)); + } + + // 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); + // 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 +518,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,7 +575,9 @@ 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: { @@ -475,6 +624,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) { @@ -540,7 +700,9 @@ 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: { @@ -558,7 +720,104 @@ e2_type_deep_match(E2_TypeKey l, E2_TypeKey r) B32 result = e2_type_key_match(l, r); if(!result) { - // TODO(rjf) + 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; } @@ -596,7 +855,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; } @@ -955,6 +1216,235 @@ e2_expr_push_child(Arena *arena, E2_Expr *parent, E2_Expr *expr) e2_expr_push_child_node(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; + if(u <= 0x7fffffff) + { + e->op = RDI_EvalOp_ConstU32; + e->type_key = e2_type_key_basic(E2_TypeKind_S32); + } + else if(u <= 0xffffffff) + { + e->op = RDI_EvalOp_ConstU32; + e->type_key = e2_type_key_basic(E2_TypeKind_U32); + } + else if(u <= 0x7fffffffffffffffull) + { + e->op = RDI_EvalOp_ConstU64; + e->type_key = e2_type_key_basic(E2_TypeKind_S64); + } + else if(u <= 0xffffffffffffffffull) + { + e->op = RDI_EvalOp_ConstU64; + e->type_key = e2_type_key_basic(E2_TypeKind_U64); + } + } + return e; +} + +internal E2_IRNode * +e2_irnode_const_f32(Arena *arena, F32 f32) +{ + E2_IRNode *e = e2_irnode(arena); + e->mode = E2_Mode_Value; + e->val.f32 = f32; + e->op = RDI_EvalOp_ConstU32; + e->type_key = e2_type_key_basic(E2_TypeKind_F32); + return e; +} + +internal E2_IRNode * +e2_irnode_const_f64(Arena *arena, F64 f64) +{ + E2_IRNode *e = e2_irnode(arena); + e->mode = E2_Mode_Value; + e->val.f64 = f64; + e->op = RDI_EvalOp_ConstU64; + e->type_key = e2_type_key_basic(E2_TypeKind_F64); + return e; +} + +internal E2_IRNode * +e2_irnode_unary_op(Arena *arena, E2_TypeKey type_key, RDI_EvalOp op, E2_IRNode *operand) +{ + 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_irnode_push_child(arena, e, operand); + return e; +} + +internal E2_IRNode * +e2_irnode_binary_op(Arena *arena, E2_TypeKey type_key, RDI_EvalOp op, E2_IRNode *lhs, E2_IRNode *rhs) +{ + 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(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_IRNode * +e2_irnode_resolve_to_value(Arena *arena, E2_IRNode *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_irnode(arena); + result->type_key = e2_type_key_direct(e2_type_key_undecorate(expr->type_key)); + result->mode = E2_Mode_Value; + e2_irnode_push_child(arena, result, expr); + } + + // rjf: address evaluations -> read value from space + else if(expr->mode == E2_Mode_Address) + { + U64 memread_byte_size = e2_byte_size_from_type_key(expr->type_key); + memread_byte_size = Min(64, memread_byte_size); + 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_irnode_push_child(arena, memread_expr, expr); + result = memread_expr; + } + + // rjf: bitfields -> shift & mask + E2_TypeKey core_type_key = e2_type_key_undecorate(expr->type_key); + if(e2_type_kind_from_key(core_type_key) == E2_TypeKind_Bitfield) + { + // rjf: unpack bitfield params + U64 shift = e2_shift_from_type_key(result->type_key); + U64 mask_count = e2_mask_count_from_type_key(result->type_key); + U64 valid_bits_mask = 0; + for EachIndex(idx, mask_count) + { + valid_bits_mask |= (1ull<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_irnode_push_child(arena, result, expr); + } + return result; +} + +internal E2_IRNode * +e2_irnode_convert_if_possible(Arena *arena, E2_IRNode *expr, E2_TypeKey dst_type_key) +{ + E2_IRNode *result = expr; + { + // rjf: unpack src / dst types + E2_TypeKey src_type_key = expr->type_key; + E2_TypeKind src_type_kind = e2_type_kind_from_key(src_type_key); + E2_TypeKind dst_type_kind = e2_type_kind_from_key(dst_type_key); + RDI_EvalTypeGroup src_type_group = e2_type_group_from_kind(src_type_kind); + RDI_EvalTypeGroup dst_type_group = e2_type_group_from_kind(dst_type_kind); + U64 src_byte_size = e2_byte_size_from_type_key(src_type_key); + U64 dst_byte_size = e2_byte_size_from_type_key(dst_type_key); + + // rjf: convert from src -> dst + RDI_EvalConversionKind conversion_kind = rdi_eval_conversion_kind_from_typegroups(src_type_group, dst_type_group); + if(conversion_kind == RDI_EvalConversionKind_Legal) + { + 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_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_irnode_truncate(arena, expr, dst_type_key); + } + } + return result; +} + +internal E2_IRNode * +e2_irnode_type(Arena *arena, E2_TypeKey type_key) +{ + 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 @@ -1319,9 +1809,29 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E { String8 identifier = str8_substr(string, token.range); B32 identifier_mapped = 0; + B32 definitions_are_allowed = (state->top_task == 0 || state->top_task->op_kind != E2_OpKind_Macro); + + // rjf: if we're currently parsing a macro definition, then we first want to try + // resolving identifiers as macro arguments + if(!identifier_mapped && state->top_task != 0 && state->top_task->op_kind == E2_OpKind_Macro) + { + U64 arg_num = 1; + for(String8Node *n = state->top_task->macro_arg_names.first; n != 0; n = n->next, arg_num += 1) + { + if(str8_match(n->string, identifier, 0)) + { + identifier_mapped = 1; + expr = e2_expr(arena); + expr->op = E2_EvalOp_LeafMacroArgument; + expr->val.u64 = arg_num; + expr->mode = E2_Mode_Value; + break; + } + } + } // rjf: first try to resolve as a definition - if(!identifier_mapped && e2_try_token(string, E2_TokenKind_Symbol, s("="), &off, 0)) + if(definitions_are_allowed && !identifier_mapped && e2_try_token(string, E2_TokenKind_Symbol, s("="), &off, 0)) { identifier_mapped = 1; E2_ParseTask *task = state->free_task; @@ -1342,6 +1852,74 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E SLLStackPush(state->top_task, task); } + // rjf: try to resolve it as a macro definition + if(definitions_are_allowed && !identifier_mapped) + { + U64 macro_off = off; + if(e2_try_token(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(string, E2_TokenKind_Identifier, s(""), ¯o_off, 0)) + { + looks_like_macro_def = 0; + break; + } + macro_arg_count += 1; + e2_try_token(string, E2_TokenKind_Symbol, s(","), ¯o_off, 0); + if(e2_try_token(string, E2_TokenKind_Symbol, s(")"), ¯o_off, 0)) + { + break; + } + } + if(looks_like_macro_def && e2_try_token(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 = 1; + task->op_kind = E2_OpKind_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(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(string, E2_TokenKind_Symbol, s(","), ¯o_off, 0); + if(e2_try_token(string, E2_TokenKind_Symbol, s(")"), ¯o_off, 0)) + { + break; + } + } + e2_try_token(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) { @@ -2102,6 +2680,23 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E } }break; + //- rjf: macros + case E2_OpKind_Macro: + { + if(completed_with_caller_info) + { + finished_root = &e2_expr_nil; + } + else + { + done = 1; + parse.status = E2_ParseStatus_NewIdentifierDefinition; + parse.expr = lhs; + parse.identifier = completed_task->identifier; + state->caller_info_completes_task = 1; + } + }break; + //- rjf: conditionals case E2_OpKind_Cond: { @@ -2459,7 +3054,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 46723de3..dfbff2ac 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -39,6 +39,7 @@ struct E2_OpInfo enum { E2_EvalOp_SetCtxID = RDI_EvalOp_COUNT, + E2_EvalOp_LeafMacroArgument, }; //////////////////////////////// @@ -67,6 +68,32 @@ 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_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 @@ -152,9 +179,9 @@ struct E2_ConsTypeParams typedef struct E2_ConsTypeNode E2_ConsTypeNode; struct E2_ConsTypeNode { - E2_ConsTypeNode *key_next; + E2_ConsTypeNode *id_next; E2_ConsTypeNode *content_next; - E2_TypeKey key; + U64 id; E2_ConsTypeParams params; U64 byte_size; }; @@ -169,11 +196,12 @@ struct E2_ConsTypeSlot typedef struct E2_ConsTypeMap E2_ConsTypeMap; struct E2_ConsTypeMap { + Arena *arena; U64 id_gen; - U64 content_slots_count; + U64 table_start_arena_pos; + U64 slots_count; E2_ConsTypeSlot *content_slots; - U64 key_slots_count; - E2_ConsTypeSlot *key_slots; + E2_ConsTypeSlot *id_slots; }; //////////////////////////////// @@ -340,6 +368,7 @@ struct E2_ParseTask String8 expected_closer; String8 expected_splitter; B32 splitter_is_required; + String8List macro_arg_names; }; typedef struct E2_ParseState E2_ParseState; @@ -381,6 +410,40 @@ struct E2_Parse 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_Compile E2_Compile; +struct E2_Compile +{ + E2_CompileStatus status; + String8 identifier; + E2_Expr *expr; + String8 member_name; + E2_Expr *params_expr; + E2_MsgList msgs; +}; + //////////////////////////////// //~ rjf: Interpretation @@ -433,9 +496,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 = {0}; +read_only global E2_IRNode e2_irnode_nil = {0}; //////////////////////////////// //~ rjf: Space -> Memory Map Helpers @@ -461,6 +526,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 @@ -496,6 +571,7 @@ 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 Arch e2_arch_from_type_key(E2_TypeKey k); @@ -543,6 +619,22 @@ internal E2_Expr *e2_expr_type(Arena *arena, E2_TypeKey type_key); 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); +//////////////////////////////// +//~ 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 diff --git a/src/eval2/eval2.mdesk b/src/eval2/eval2.mdesk index bfba77cd..4848b557 100644 --- a/src/eval2/eval2.mdesk +++ b/src/eval2/eval2.mdesk @@ -36,6 +36,7 @@ E2_OpTable: {LogAnd Binary 11 "" "&&" "" "" } {LogOr Binary 12 "" "||" "" "" } {Define Null 13 "" "=" "" "" } + {Macro Null 14 "" "" "" "" } {Cond Ternary 14 "" "?" "" ":"} } diff --git a/src/eval2/generated/eval2.meta.c b/src/eval2/generated/eval2.meta.c index 7fd3f85b..f65bab75 100644 --- a/src/eval2/generated/eval2.meta.c +++ b/src/eval2/generated/eval2.meta.c @@ -4,7 +4,7 @@ //- GENERATED CODE C_LINKAGE_BEGIN -E2_OpInfo e2_op_kind_info_table[34] = +E2_OpInfo e2_op_kind_info_table[35] = { {0}, {E2_OpParseKind_Binary, 1, str8_lit_comp(""), str8_lit_comp("."), str8_lit_comp(""), str8_lit_comp("")}, @@ -38,7 +38,8 @@ E2_OpInfo e2_op_kind_info_table[34] = {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_Null, 13, str8_lit_comp(""), str8_lit_comp("="), str8_lit_comp(""), str8_lit_comp("")}, +{E2_OpParseKind_Null, 14, 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(":")}, }; diff --git a/src/eval2/generated/eval2.meta.h b/src/eval2/generated/eval2.meta.h index be6b0ca2..438901d0 100644 --- a/src/eval2/generated/eval2.meta.h +++ b/src/eval2/generated/eval2.meta.h @@ -41,6 +41,7 @@ E2_OpKind_BitOr, E2_OpKind_LogAnd, E2_OpKind_LogOr, E2_OpKind_Define, +E2_OpKind_Macro, E2_OpKind_Cond, E2_OpKind_COUNT, } E2_OpKind; @@ -124,7 +125,7 @@ E2_TypeKind_LastMeta = E2_TypeKind_MetaDescription, } E2_TypeKind; C_LINKAGE_BEGIN -extern E2_OpInfo e2_op_kind_info_table[34]; +extern E2_OpInfo e2_op_kind_info_table[35]; extern U8 e2_type_kind_basic_byte_size_table[61]; extern String8 e2_type_kind_basic_string_table[61]; diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index 00f10e92..e80a047e 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -34,6 +34,8 @@ internal void entry_point(CmdLine *cmdline) { + E2_ConsTypeMap *cons_types = e2_cons_type_map_alloc(); + e2_select_cons_type_map(cons_types); String8 strings[] = { // (A)B @@ -44,6 +46,7 @@ entry_point(CmdLine *cmdline) s("123, 456"), s("int32 == int32"), s("foo = 123"), + s("bar(a, b) = a + b"), s("1 > 2"), s("1 ? \"Test\" : 888"), s("'a'"), From c2b74260efe40b4346869213059dc67605170fd8 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 25 Jun 2026 13:58:03 -0700 Subject: [PATCH 11/90] eval2: opkind -> exprkind, prepare for exprs being looser --- src/eval2/eval2.c | 184 +++++++++++++++---------------- src/eval2/eval2.h | 23 ++-- src/eval2/eval2.mdesk | 10 +- src/eval2/generated/eval2.meta.c | 70 ++++++------ src/eval2/generated/eval2.meta.h | 78 ++++++------- 5 files changed, 183 insertions(+), 182 deletions(-) diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index c909cc1e..6cbdb46d 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -1589,9 +1589,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 EachNonZeroEnumVal(E2_ExprKind, k) { - 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 = (e2_expr_kind_info_table[k].pre.size != 0) ? e2_expr_kind_info_table[k].pre : e2_expr_kind_info_table[k].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; @@ -1735,25 +1735,25 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E else if(need_new_expr && e2_try_token(string, E2_TokenKind_Symbol, s(""), &off, &token)) { // rjf: string -> operator kind - E2_OpKind op_kind = E2_OpKind_Null; + E2_ExprKind expr_kind = E2_ExprKind_Null; String8 closer = {0}; { String8 token_string = str8_substr(string, token.range); - for EachNonZeroEnumVal(E2_OpKind, k) + for EachNonZeroEnumVal(E2_ExprKind, k) { - 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(e2_expr_kind_info_table[k].parse_kind == E2_ExprParseKind_UnaryPrefix && + e2_expr_kind_info_table[k].precedence <= max_precedence && + str8_match(token_string, e2_expr_kind_info_table[k].pre, 0)) { - op_kind = k; - closer = e2_op_kind_info_table[k].post; + expr_kind = k; + closer = e2_expr_kind_info_table[k].post; 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) @@ -1767,14 +1767,14 @@ 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->expr_kind = expr_kind; + task->max_precedence = e2_expr_kind_info_table[expr_kind].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) { String8 token_string = str8_substr(string, token.range); e2_msgf(arena, &parse.msgs, token.range, "Unexpected `%S`.", token_string); @@ -1782,7 +1782,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E } //- 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 != 0 && state->top_task->expr_kind == E2_ExprKind_Dot && e2_try_token(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) @@ -1809,11 +1809,11 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E { String8 identifier = str8_substr(string, token.range); B32 identifier_mapped = 0; - B32 definitions_are_allowed = (state->top_task == 0 || state->top_task->op_kind != E2_OpKind_Macro); + B32 definitions_are_allowed = (state->top_task == 0 || state->top_task->expr_kind != E2_ExprKind_Macro); // rjf: if we're currently parsing a macro definition, then we first want to try // resolving identifiers as macro arguments - if(!identifier_mapped && state->top_task != 0 && state->top_task->op_kind == E2_OpKind_Macro) + if(!identifier_mapped && state->top_task != 0 && state->top_task->expr_kind == E2_ExprKind_Macro) { U64 arg_num = 1; for(String8Node *n = state->top_task->macro_arg_names.first; n != 0; n = n->next, arg_num += 1) @@ -1846,8 +1846,8 @@ 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 = E2_OpKind_Define; - task->max_precedence = e2_op_kind_info_table[E2_OpKind_Define].precedence; + task->expr_kind = E2_ExprKind_Define; + task->max_precedence = e2_expr_kind_info_table[E2_ExprKind_Define].precedence; task->identifier = identifier; SLLStackPush(state->top_task, task); } @@ -1891,7 +1891,7 @@ 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 = E2_OpKind_Macro; + task->expr_kind = E2_ExprKind_Macro; task->max_precedence = max_S64; task->identifier = identifier; SLLStackPush(state->top_task, task); @@ -1924,23 +1924,23 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E if(!identifier_mapped) { // rjf: string -> op kind - E2_OpKind op_kind = E2_OpKind_Null; + E2_ExprKind expr_kind = E2_ExprKind_Null; { String8 token_string = str8_substr(string, token.range); - for EachNonZeroEnumVal(E2_OpKind, k) + for EachNonZeroEnumVal(E2_ExprKind, k) { - 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(e2_expr_kind_info_table[k].parse_kind == E2_ExprParseKind_UnaryPrefix && + e2_expr_kind_info_table[k].precedence <= max_precedence && + str8_match(token_string, str8_skip_chop_whitespace(e2_expr_kind_info_table[k].pre), 0)) { - op_kind = k; + expr_kind = k; 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; @@ -1955,8 +1955,8 @@ 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->expr_kind = expr_kind; + task->max_precedence = e2_expr_kind_info_table[expr_kind].precedence; SLLStackPush(state->top_task, task); } } @@ -2060,35 +2060,35 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E if(e2_try_token(string, E2_TokenKind_Symbol, s(""), &trailing_symbol_off, &token)) { // 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; { String8 token_string = str8_substr(string, token.range); - for EachNonZeroEnumVal(E2_OpKind, k) + for EachNonZeroEnumVal(E2_ExprKind, k) { - 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_ExprKindInfo *expr_kind_info = &e2_expr_kind_info_table[k]; + if(expr_kind_info->precedence <= max_precedence && str8_match(token_string, expr_kind_info->sep, 0)) { - switch(op_info->parse_kind) + switch(expr_kind_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; + case E2_ExprParseKind_Binary: {child_count_target = 2;}break; + case E2_ExprParseKind_Ternary: {child_count_target = 3; splitter_is_required = 1;}break; + case E2_ExprParseKind_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 = k; break; } } } // rjf: non-null operator kind -> build binary expression node, push task to fill other child - if(op_kind != E2_OpKind_Null) + if(expr_kind != E2_ExprKind_Null) { E2_ParseTask *task = state->free_task; if(task != 0) @@ -2103,8 +2103,8 @@ 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->max_precedence = e2_expr_kind_info_table[expr_kind].precedence; task->expected_splitter = splitter; task->expected_closer = closer; task->splitter_is_required = splitter_is_required; @@ -2177,8 +2177,8 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E task->src_range = type_expr->src_range; task->child_count = 1; task->child_count_target = 2; - task->op_kind = E2_OpKind_CCast; - task->max_precedence = e2_op_kind_info_table[E2_OpKind_CCast].precedence; + task->expr_kind = E2_ExprKind_CCast; + task->max_precedence = e2_expr_kind_info_table[E2_ExprKind_CCast].precedence; SLLStackPush(state->top_task, task); expr = &e2_expr_nil; E2_ExprNode *child_n = push_array(arena, E2_ExprNode, 1); @@ -2204,7 +2204,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E { RDI_EvalOp op = RDI_EvalOp_Stop; E2_TypeKey dst_type_key = {E2_TypeKeyKind_Null}; - switch(completed_task->op_kind) + switch(completed_task->expr_kind) { //- rjf: no op -> just a sub-expression default: @@ -2226,13 +2226,13 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E // 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: + case E2_ExprKind_Dot: { finished_root = rhs; }break; //- rjf: indexes - case E2_OpKind_Index: + 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); @@ -2277,7 +2277,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E }break; //- rjf: calls - case E2_OpKind_Call: + case E2_ExprKind_Call: { // rjf: calls of types redirect to casts if(lhs->mode == E2_Mode_Type) @@ -2310,7 +2310,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E }break; //- rjf: sizeof - case E2_OpKind_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); @@ -2319,7 +2319,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E }break; //- rjf: typeof - case E2_OpKind_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_expr(arena); @@ -2328,7 +2328,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E }break; //- rjf: casts - case E2_OpKind_CCast: + case E2_ExprKind_CCast: cast:; { E2_Expr *cast_type_expr = lhs; @@ -2345,8 +2345,8 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E }break; //- rjf: dereferences - case E2_OpKind_Deref: - case E2_OpKind_DerefAsm: + 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); @@ -2367,7 +2367,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E 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) + completed_task->expr_kind != E2_ExprKind_DerefAsm) { malformed = 1; e2_msgf(arena, &parse.msgs, completed_task->src_range, "Cannot dereference this type."); @@ -2381,7 +2381,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E // 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) + completed_task->expr_kind == E2_ExprKind_DerefAsm) { dereferenced_type = e2_type_key_basic(E2_TypeKind_U64); } @@ -2405,7 +2405,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E }break; //- rjf: address-of - case E2_OpKind_Address: + case E2_ExprKind_Address: { // rjf: determine if malformed B32 malformed = 0; @@ -2426,15 +2426,15 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E }break; //- rjf: positive (no-op, just take the right-hand-side) - case E2_OpKind_Pos: + case E2_ExprKind_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; + 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 @@ -2473,24 +2473,24 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E }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; + 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 @@ -2641,14 +2641,14 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E //- rjf: type comparisons case ArithKind_TypeCompare: { - if(completed_task->op_kind == E2_OpKind_EqEq || - completed_task->op_kind == E2_OpKind_NtEq) + if(completed_task->expr_kind == E2_ExprKind_EqEq || + completed_task->expr_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(completed_task->op_kind == E2_OpKind_NtEq) + if(completed_task->expr_kind == E2_ExprKind_NtEq) { result = !result; } @@ -2657,14 +2657,14 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E } else { - e2_msgf(arena, &parse.msgs, completed_task->src_range, "Cannot use `%S` on types.", str8_skip_chop_whitespace(e2_op_kind_info_table[completed_task->op_kind].sep)); + e2_msgf(arena, &parse.msgs, completed_task->src_range, "Cannot use `%S` on types.", str8_skip_chop_whitespace(e2_expr_kind_info_table[completed_task->expr_kind].sep)); } }break; } }break; //- rjf: definitions - case E2_OpKind_Define: + case E2_ExprKind_Define: { if(completed_with_caller_info) { @@ -2681,7 +2681,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E }break; //- rjf: macros - case E2_OpKind_Macro: + case E2_ExprKind_Macro: { if(completed_with_caller_info) { @@ -2698,7 +2698,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E }break; //- rjf: conditionals - case E2_OpKind_Cond: + case E2_ExprKind_Cond: { // rjf: unpack operands E2_Expr *condition_expr = lhs; @@ -2869,25 +2869,25 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E { for(E2_ParseTask *t = state->top_task; t != 0; t = t->next) { - if(t->child_count == 1 && t->child_count_target == 2 && t->op_kind == E2_OpKind_Dot) + 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->op_kind == E2_OpKind_Call) + else if(t->child_count >= 1 && t->expr_kind == E2_ExprKind_Call) { e2_msgf(arena, &parse.msgs, t->src_range, "Couldn't call 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->child_count_target == 2 && t->expr_kind != E2_ExprKind_Null) { - 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, "Expected expression after binary operator `%S`.", e2_expr_kind_info_table[t->expr_kind].sep); } - else if(t->child_count == 0 && t->child_count_target == 1 && t->op_kind != E2_OpKind_Null) + else if(t->child_count == 0 && t->child_count_target == 1 && t->expr_kind != E2_ExprKind_Null) { - 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 unary operator `%S`.", e2_expr_kind_info_table[t->expr_kind].pre); } else { diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index dfbff2ac..c200ea6c 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -7,20 +7,20 @@ //////////////////////////////// //~ rjf: Operator Info Tables -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_UnaryPrefix, + E2_ExprParseKind_Binary, + E2_ExprParseKind_Ternary, + E2_ExprParseKind_Call, } -E2_OpParseKind; +E2_ExprParseKind; -typedef struct E2_OpInfo E2_OpInfo; -struct E2_OpInfo +typedef struct E2_ExprKindInfo E2_ExprKindInfo; +struct E2_ExprKindInfo { - E2_OpParseKind parse_kind; + E2_ExprParseKind parse_kind; S64 precedence; String8 pre; String8 sep; @@ -330,6 +330,7 @@ struct E2_Expr E2_ExprNode *first_child; E2_ExprNode *last_child; U64 child_count; + E2_ExprKind kind; Rng1U64 src_range; String8 string; RDI_EvalOp op; @@ -363,7 +364,7 @@ struct E2_ParseTask U64 child_count; U64 child_count_target; S64 max_precedence; - E2_OpKind op_kind; + E2_ExprKind expr_kind; String8 identifier; String8 expected_closer; String8 expected_splitter; diff --git a/src/eval2/eval2.mdesk b/src/eval2/eval2.mdesk index 4848b557..74139819 100644 --- a/src/eval2/eval2.mdesk +++ b/src/eval2/eval2.mdesk @@ -2,7 +2,7 @@ // Licensed under the MIT license (https://opensource.org/license/mit/) @table(name parse_kind precedence pre sep pos chain) -E2_OpTable: +E2_ExprKindTable: { {Dot Binary 1 "" "." "" "" } {Index Binary 1 "" "[" "]" "" } @@ -40,17 +40,17 @@ E2_OpTable: {Cond Ternary 14 "" "?" "" ":"} } -@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(E2_ExprKindInfo) e2_expr_kind_info_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)")}` + @expand(E2_ExprKindTable a) `{E2_ExprParseKind_$(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)")}` } @table(name basic_string basic_byte_size) diff --git a/src/eval2/generated/eval2.meta.c b/src/eval2/generated/eval2.meta.c index f65bab75..ddfd007b 100644 --- a/src/eval2/generated/eval2.meta.c +++ b/src/eval2/generated/eval2.meta.c @@ -4,43 +4,43 @@ //- GENERATED CODE C_LINKAGE_BEGIN -E2_OpInfo e2_op_kind_info_table[35] = +E2_ExprKindInfo e2_expr_kind_info_table[35] = { {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, 1, str8_lit_comp("typeof "), 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, 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_Null, 13, str8_lit_comp(""), str8_lit_comp("="), str8_lit_comp(""), str8_lit_comp("")}, -{E2_OpParseKind_Null, 14, 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(":")}, +{E2_ExprParseKind_Binary, 1, str8_lit_comp(""), str8_lit_comp("."), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 1, str8_lit_comp(""), str8_lit_comp("["), str8_lit_comp("]"), str8_lit_comp("")}, +{E2_ExprParseKind_Call, 1, str8_lit_comp(""), str8_lit_comp("("), str8_lit_comp(")"), str8_lit_comp(",")}, +{E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("["), str8_lit_comp(""), str8_lit_comp("]"), str8_lit_comp("")}, +{E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("sizeof "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("typeof "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("*"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("&"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("+"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("-"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("!"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("~"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("*"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("/"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("%"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 4, str8_lit_comp(""), str8_lit_comp("+"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 4, str8_lit_comp(""), str8_lit_comp("-"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 5, str8_lit_comp(""), str8_lit_comp("<<"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 5, str8_lit_comp(""), str8_lit_comp(">>"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp("<"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp("<="), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp(">"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp(">="), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 7, str8_lit_comp(""), str8_lit_comp("=="), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 7, str8_lit_comp(""), str8_lit_comp("!="), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 8, str8_lit_comp(""), str8_lit_comp("&"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 9, str8_lit_comp(""), str8_lit_comp("^"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 10, str8_lit_comp(""), str8_lit_comp("|"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 11, str8_lit_comp(""), str8_lit_comp("&&"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Binary, 12, str8_lit_comp(""), str8_lit_comp("||"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Null, 13, str8_lit_comp(""), str8_lit_comp("="), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Null, 14, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Ternary, 14, 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 438901d0..2cd5dedb 100644 --- a/src/eval2/generated/eval2.meta.h +++ b/src/eval2/generated/eval2.meta.h @@ -6,45 +6,45 @@ #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_TypeOf, -E2_OpKind_CCast, -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_Macro, -E2_OpKind_Cond, -E2_OpKind_COUNT, -} E2_OpKind; +E2_ExprKind_Null, +E2_ExprKind_Dot, +E2_ExprKind_Index, +E2_ExprKind_Call, +E2_ExprKind_DerefAsm, +E2_ExprKind_SizeOf, +E2_ExprKind_TypeOf, +E2_ExprKind_CCast, +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_TypeKind { @@ -125,7 +125,7 @@ E2_TypeKind_LastMeta = E2_TypeKind_MetaDescription, } E2_TypeKind; C_LINKAGE_BEGIN -extern E2_OpInfo e2_op_kind_info_table[35]; +extern E2_ExprKindInfo e2_expr_kind_info_table[35]; extern U8 e2_type_kind_basic_byte_size_table[61]; extern String8 e2_type_kind_basic_string_table[61]; From d7393b2f65e33c31efa844da4f13fcafb28f4fcf Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 25 Jun 2026 15:48:14 -0700 Subject: [PATCH 12/90] eval2: split string -> expr compilation path into frontend / middle end - string -> expr, where expr is a loose structural syntax tree (where we can get macros and stuff without doing typechecking etc.), and expr -> irtree, where irtree is the usual typed / typechecked / ir structured representation - preserve minimal identifier resolution path for parser, but move most resolution things to the middle-end --- src/eval2/eval2.c | 1682 +++++++++++++++++------------- src/eval2/eval2.h | 80 +- src/eval2/eval2.mdesk | 74 +- src/eval2/generated/eval2.meta.c | 10 +- src/eval2/generated/eval2.meta.h | 8 +- src/scratch/ryan_scratch.c | 130 +-- 6 files changed, 1124 insertions(+), 860 deletions(-) diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index 6cbdb46d..57d4efee 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 @@ -991,13 +1047,30 @@ 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 void +e2_expr_push_child_node(E2_Expr *parent, E2_ExprNode *node) +{ + 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); +} +#if 0 + internal E2_Expr * e2_expr_const_u64_or_smaller(Arena *arena, U64 u) { @@ -1200,21 +1273,7 @@ e2_expr_type(Arena *arena, E2_TypeKey type_key) expr->mode = E2_Mode_Type; return expr; } - -internal void -e2_expr_push_child_node(E2_Expr *parent, E2_ExprNode *node) -{ - 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); -} +#endif //////////////////////////////// //~ rjf: Expression Constructors @@ -1678,12 +1737,10 @@ 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, E2_Val compile_time_eval_result, String8 string) +e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, String8 string) { U64 off = state->string_off; E2_Parse parse = {E2_ParseStatus_Error, .expr = &e2_expr_nil, .params_expr = &e2_expr_nil}; - E2_Expr *last_chained_expr = &e2_expr_nil; - E2_Expr *next_access_result = access_result; //- rjf: parse & attach to top parsing task - if we don't have a top task // then it is just the result @@ -1784,24 +1841,15 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E //- rjf: identifiers with an active dot op kind -> member access else if(need_new_expr && state->top_task != 0 && state->top_task->expr_kind == E2_ExprKind_Dot && e2_try_token(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; - state->caller_request_count = 0; - } - - // 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 definition or operator keyword) @@ -1811,25 +1859,6 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E B32 identifier_mapped = 0; B32 definitions_are_allowed = (state->top_task == 0 || state->top_task->expr_kind != E2_ExprKind_Macro); - // rjf: if we're currently parsing a macro definition, then we first want to try - // resolving identifiers as macro arguments - if(!identifier_mapped && state->top_task != 0 && state->top_task->expr_kind == E2_ExprKind_Macro) - { - U64 arg_num = 1; - for(String8Node *n = state->top_task->macro_arg_names.first; n != 0; n = n->next, arg_num += 1) - { - if(str8_match(n->string, identifier, 0)) - { - identifier_mapped = 1; - expr = e2_expr(arena); - expr->op = E2_EvalOp_LeafMacroArgument; - expr->val.u64 = arg_num; - expr->mode = E2_Mode_Value; - break; - } - } - } - // rjf: first try to resolve as a definition if(definitions_are_allowed && !identifier_mapped && e2_try_token(string, E2_TokenKind_Symbol, s("="), &off, 0)) { @@ -1961,72 +1990,31 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E } } - // rjf: try to resolve via caller-provided named expressions + // rjf: build this as a leaf identifier expression - ask the caller if it's a type if(!identifier_mapped) { - expr = e2_expr_from_name(expr_map, identifier); - identifier_mapped = (expr != &e2_expr_nil); - state->caller_request_count = 0; - } - - // rjf: couldn't map -> ask caller to resolve it - if(!identifier_mapped) - { - done = 1; - parse.status = E2_ParseStatus_MissedIdentifierResolution; - parse.identifier = identifier; - off = start_off; + if(state->caller_request_count == 0) + { + done = 1; + parse.status = E2_ParseStatus_CheckIdentifierIsType; + parse.identifier = identifier; + off = start_off; + } + else + { + identifier_mapped = 1; + expr = e2_expr(arena, identifier_is_type ? E2_ExprKind_TypeIdentifier : E2_ExprKind_Identifier); + expr->string = str8_substr(string, token.range); + state->caller_request_count = 0; + } } } //- rjf: leaf numerics else if(need_new_expr && e2_try_token(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_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) - { - if(u64_idx >= ArrayCount(expr->val.u512.u64)) - { - 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: - { - e2_msgf(arena, &parse.msgs, token.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)) - { - expr = e2_expr_const_u64_or_smaller(arena, u64_val); - } + expr = e2_expr(arena, E2_ExprKind_Numeric); + expr->string = str8_substr(string, token.range); } //- rjf: leaf string literals @@ -2035,12 +2023,8 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E 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); - expr->op = RDI_EvalOp_ConstString; - expr->mode = E2_Mode_Value; + expr = e2_expr(arena, E2_ExprKind_StringLiteral); expr->string = raw_string; - expr->val.u64 = raw_string.size; - expr->type_key = e2_type_key_cons_array(e2_type_key_basic(E2_TypeKind_UChar8), raw_string.size); } //- rjf: leaf character literals @@ -2049,8 +2033,8 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E 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); - UnicodeDecode decode = utf8_decode(raw_string.str, raw_string.size); - expr = e2_expr_const_u64_or_smaller(arena, decode.codepoint); + expr = e2_expr(arena, E2_ExprKind_CharLiteral); + expr->string = raw_string; } //- rjf: extend parsed expression tree with trailing operators @@ -2149,9 +2133,34 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E closer_found = e2_try_token(string, E2_TokenKind_Symbol, state->top_task->expected_closer, &off, 0); } + //- rjf: determine if first child of a parenthesized sub-expression is a type + B32 parenthesized_child_is_type = 0; + if(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) + { + 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; + } + } + } + //- 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 = (closer_found && state->top_task->child_count == 1 && state->top_task->first_child->v->mode == E2_Mode_Type); + B32 is_cast = parenthesized_child_is_type; if(is_cast) { // rjf: pop the sub-task for the type expression @@ -2198,610 +2207,12 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, E2_ExprMap *expr_map, E //- 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 ? completed_task->first_child->next->v : &e2_expr_nil; { - RDI_EvalOp op = RDI_EvalOp_Stop; - E2_TypeKey dst_type_key = {E2_TypeKeyKind_Null}; - switch(completed_task->expr_kind) - { - //- rjf: no op -> just a sub-expression - default: - { - if(lhs != rhs) - { - finished_root = e2_expr(arena); - finished_root->first_child = completed_task->first_child; - finished_root->last_child = completed_task->last_child; - finished_root->child_count = completed_task->child_count; - } - 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_ExprKind_Dot: - { - 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_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; - state->caller_request_count = 0; - } - 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_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_access_result != &e2_expr_nil) - { - finished_root = next_access_result; - next_access_result = &e2_expr_nil; - state->caller_request_count = 0; - } - else - { - E2_Expr *params_expr = e2_expr(arena); - params_expr->first_child = completed_task->first_child->next; - params_expr->last_child = completed_task->last_child; - params_expr->child_count = completed_task->child_count-1; - done = 1; - parse.status = E2_ParseStatus_Call; - parse.expr = lhs; - parse.params_expr = params_expr; - state->caller_info_completes_task = 1; - } - } - }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_expr_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_expr(arena); - finished_root->mode = E2_Mode_Type; - finished_root->type_key = rhs_type_key; - }break; - - //- rjf: casts - case E2_ExprKind_CCast: - cast:; - { - E2_Expr *cast_type_expr = lhs; - E2_TypeKey cast_type_key = cast_type_expr->type_key; - if(e2_type_key_match(e2_type_key_zero(), cast_type_key)) - { - e2_msgf(arena, &parse.msgs, completed_task->src_range, "Cannot cast to this type."); - } - else - { - E2_Expr *castee_expr = rhs; - finished_root = e2_expr_convert_if_possible(arena, castee_expr, 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, &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->expr_kind != E2_ExprKind_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->expr_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_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_ExprKind_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_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, &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_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_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; - E2_Expr *lhs_converted = lhs_value; - E2_Expr *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_expr_convert_if_possible(arena, lhs_value, coerced_type_key); - rhs_converted = e2_expr_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_expr_convert_if_possible(arena, lhs_value, dst_type_key); - 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: - { - // rjf: l/r -> ptr + int - E2_Expr *ptr_expr = lhs; - E2_Expr *int_expr = rhs; - if(e2_type_kind_is_ptr_or_ref(rhs_type_kind)) - { - ptr_expr = rhs; - int_expr = lhs; - } - - // rjf: unpack ptee type - E2_TypeKey ptee_type_key = e2_type_key_direct(e2_type_key_undecorate(ptr_expr->type_key)); - U64 ptee_byte_size = e2_byte_size_from_type_key(ptee_type_key); - - // rjf: form tree - E2_Expr *base_addr_expr = e2_expr_resolve_to_value(arena, ptr_expr); - E2_Expr *index_expr = e2_expr_resolve_to_value(arena, int_expr); - E2_Expr *offset_expr = index_expr; - if(ptee_byte_size > 1) - { - E2_Expr *size_expr = e2_expr_const_u64_or_smaller(arena, ptee_byte_size); - offset_expr = e2_expr_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Mul, index_expr, size_expr); - } - E2_Expr *addr_value_expr = e2_expr_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Add, base_addr_expr, offset_expr); - addr_value_expr->mode = E2_Mode_Address; - addr_value_expr->type_key = ptr_expr->type_key; - finished_root = addr_value_expr; - }break; - - //- rjf: pointer-sub arithmetic - case ArithKind_PtrSub: - { - E2_Expr *lhs_addr_value_expr = e2_expr_resolve_to_value(arena, lhs); - E2_Expr *rhs_addr_value_expr = e2_expr_resolve_to_value(arena, rhs); - E2_Expr *sub_expr = e2_expr_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Sub, lhs_addr_value_expr, rhs_addr_value_expr); - E2_Expr *sub_divided_expr = sub_expr; - 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_expr = e2_expr_binary_op(arena, e2_type_key_basic(E2_TypeKind_U64), RDI_EvalOp_Div, sub_expr, e2_expr_const_u64_or_smaller(arena, ptee_byte_size)); - } - } - finished_root = sub_divided_expr; - }break; - - //- rjf: pointer-array comparisons - case ArithKind_PtrArrayCompare: - { - // TODO(rjf) - }break; - - //- rjf: type comparisons - case ArithKind_TypeCompare: - { - if(completed_task->expr_kind == E2_ExprKind_EqEq || - completed_task->expr_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(completed_task->expr_kind == E2_ExprKind_NtEq) - { - result = !result; - } - finished_root = e2_expr_const_u64_or_smaller(arena, (U64)result); - finished_root->type_key = e2_type_key_basic(E2_TypeKind_Bool); - } - else - { - e2_msgf(arena, &parse.msgs, completed_task->src_range, "Cannot use `%S` on types.", str8_skip_chop_whitespace(e2_expr_kind_info_table[completed_task->expr_kind].sep)); - } - }break; - } - }break; - - //- rjf: definitions - case E2_ExprKind_Define: - { - if(completed_with_caller_info) - { - finished_root = lhs; - } - else - { - done = 1; - parse.status = E2_ParseStatus_NewIdentifierDefinition; - parse.expr = lhs; - parse.identifier = completed_task->identifier; - state->caller_info_completes_task = 1; - } - }break; - - //- rjf: macros - case E2_ExprKind_Macro: - { - if(completed_with_caller_info) - { - finished_root = &e2_expr_nil; - } - else - { - done = 1; - parse.status = E2_ParseStatus_NewIdentifierDefinition; - parse.expr = lhs; - parse.identifier = completed_task->identifier; - state->caller_info_completes_task = 1; - } - }break; - - //- rjf: conditionals - case E2_ExprKind_Cond: - { - // rjf: unpack operands - E2_Expr *condition_expr = lhs; - E2_Expr *pass_expr = mhs; - E2_Expr *fail_expr = rhs; - E2_TypeKey type_key = e2_coerced_type_key_from_operands(pass_expr->type_key, fail_expr->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_expr->type_key)); - RDI_EvalTypeGroup fail_type_group = e2_type_group_from_kind(e2_type_kind_from_key(fail_expr->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; - } - } - - // rjf: compile-time conditional -> ask caller to evaluate conditional - if(compile_time_cond) - { - if(!completed_with_caller_info) - { - done = 1; - parse.status = E2_ParseStatus_CompileTimeEval; - parse.expr = lhs; - state->caller_info_completes_task = 1; - } - else - { - if(compile_time_eval_result.u64 == 0) - { - finished_root = fail_expr; - } - else - { - finished_root = pass_expr; - } - } - } - - // rjf: runtime conditional -> build opcodes for evaluating condition, jumping, etc. - else - { - // rjf: convert pass/fail to coerced type - pass_expr = e2_expr_convert_if_possible(arena, pass_expr, type_key); - fail_expr = e2_expr_convert_if_possible(arena, fail_expr, type_key); - - // rjf: form conditional tree - E2_Expr *cond_root = e2_expr(arena); - E2_Expr *cjump_expr = e2_expr(arena); - E2_Expr *jump_expr = e2_expr(arena); - e2_expr_push_child(arena, cond_root, condition_expr); - e2_expr_push_child(arena, cond_root, cjump_expr); - e2_expr_push_child(arena, cond_root, fail_expr); - e2_expr_push_child(arena, cond_root, jump_expr); - e2_expr_push_child(arena, cond_root, pass_expr); - - // rjf: compute # of bytes for pass - U64 pass_expr_byte_count = 0; - { - Temp scratch = scratch_begin(&arena, 1); - String8 pass_bytecode = e2_bytecode_from_expr(scratch.arena, pass_expr); - pass_expr_byte_count = pass_bytecode.size; - scratch_end(scratch); - } - - // rjf: configure unconditional jump (fail -> end) - jump_expr->op = RDI_EvalOp_Skip; - jump_expr->val.u64 = pass_expr_byte_count; - - // rjf: compute # of bytes for fail - U64 fail_expr_byte_count = 0; - { - Temp scratch = scratch_begin(&arena, 1); - String8 fail_bytecode = e2_bytecode_from_expr(scratch.arena, fail_expr); - fail_expr_byte_count = fail_bytecode.size; - scratch_end(scratch); - } - - // rjf: compute # of bytes for unconditional jump - U64 jump_expr_byte_count = 0; - { - Temp scratch = scratch_begin(&arena, 1); - String8 jump_bytecode = e2_bytecode_from_expr(scratch.arena, jump_expr); - jump_expr_byte_count = jump_bytecode.size; - scratch_end(scratch); - } - - // rjf: configure conditional jump - cjump_expr->op = RDI_EvalOp_Cond; - cjump_expr->val.u64 = fail_expr_byte_count + jump_expr_byte_count; - - // rjf: fill conditional root type - cond_root->type_key = type_key; - - // rjf: take result - finished_root = cond_root; - } - }break; - } + 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 @@ -2854,7 +2265,7 @@ 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.identifier.size), "`%S` couldn't be resolved.", parse.identifier); } @@ -2905,10 +2316,793 @@ 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; + } + }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; + }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_CCast: + 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."); + } + else + { + 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 `%S` on types.", str8_skip_chop_whitespace(e2_expr_kind_info_table[e->kind].sep)); + } + }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; + } + } + + // 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; + } + }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}; { @@ -2918,30 +3112,30 @@ e2_bytecode_from_expr(Arena *arena, E2_Expr *expr) struct Task { Task *next; - E2_Expr *e; - E2_ExprNode *last_pushed_child_node; + E2_IRNode *ir; + E2_IRNodePtrNode *last_pushed_child_node; U64 pushed_child_count; }; - Task start_task = {0, expr}; + 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]; + U16 ctrlbits = rdi_eval_op_ctrlbits_table[ir->op]; U64 child_count = RDI_POPN_FROM_CTRLBITS(ctrlbits); - if(e->op == 0) + if(ir->op == 0) { - child_count = e->child_count; + 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_ExprNode *next_child_node = (t->last_pushed_child_node == 0 ? e->first_child : t->last_pushed_child_node->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) { @@ -2953,7 +3147,7 @@ e2_bytecode_from_expr(Arena *arena, E2_Expr *expr) } MemoryZeroStruct(child_task); SLLStackPush(top_task, child_task); - child_task->e = next_child_node ? next_child_node->v : &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_node = next_child_node; @@ -2962,22 +3156,22 @@ e2_bytecode_from_expr(Arena *arena, E2_Expr *expr) //- rjf: did push of all children -> push this expr's op, pop off stack else { - if(e->op != 0) + if(ir->op != 0) { U16 ctrlbits = 0; - if(e->op < RDI_EvalOp_COUNT) + if(ir->op < RDI_EvalOp_COUNT) { - ctrlbits = rdi_eval_op_ctrlbits_table[e->op]; + ctrlbits = rdi_eval_op_ctrlbits_table[ir->op]; } 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); + 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(e->string.size != 0) + if(ir->string.size != 0) { - str8_list_push(scratch.arena, &strings, e->string); + str8_list_push(scratch.arena, &strings, ir->string); } } SLLStackPop(top_task); diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index c200ea6c..5594c8ec 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -11,6 +11,7 @@ typedef enum E2_ExprParseKind { E2_ExprParseKind_Null, E2_ExprParseKind_UnaryPrefix, + E2_ExprParseKind_UnaryPostfix, E2_ExprParseKind_Binary, E2_ExprParseKind_Ternary, E2_ExprParseKind_Call, @@ -54,14 +55,10 @@ typedef enum E2_ParseStatus E2_ParseStatus_LastTerminal = E2_ParseStatus_Error, //- rjf: caller-provided info - E2_ParseStatus_MissedIdentifierResolution, - E2_ParseStatus_NewIdentifierDefinition, - E2_ParseStatus_MemberAccess, - E2_ParseStatus_IndexAccess, - E2_ParseStatus_Call, - E2_ParseStatus_CompileTimeEval, - E2_ParseStatus_FirstCallerRequest = E2_ParseStatus_MissedIdentifierResolution, - E2_ParseStatus_LastCallerRequest = E2_ParseStatus_CompileTimeEval, + E2_ParseStatus_CheckIdentifierIsType, + E2_ParseStatus_NewMacro, + E2_ParseStatus_FirstCallerRequest = E2_ParseStatus_CheckIdentifierIsType, + E2_ParseStatus_LastCallerRequest = E2_ParseStatus_NewMacro, } E2_ParseStatus; @@ -333,10 +330,6 @@ struct E2_Expr E2_ExprKind kind; Rng1U64 src_range; String8 string; - RDI_EvalOp op; - E2_TypeKey type_key; - E2_Mode mode; - E2_Val val; }; typedef struct E2_ExprMapNode E2_ExprMapNode; @@ -434,14 +427,48 @@ struct E2_IRNode 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; +}; + typedef struct E2_Compile E2_Compile; struct E2_Compile { E2_CompileStatus status; String8 identifier; - E2_Expr *expr; + E2_IRNode *irtree; String8 member_name; - E2_Expr *params_expr; + E2_IRNode *params_irtree; E2_MsgList msgs; }; @@ -515,6 +542,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 @@ -607,7 +640,10 @@ 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(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); +#if 0 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); @@ -617,8 +653,7 @@ 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 E2_Expr *e2_expr_type(Arena *arena, E2_TypeKey type_key); -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); +#endif //////////////////////////////// //~ rjf: IR Tree Constructors @@ -642,12 +677,17 @@ internal void e2_irnode_push_child(Arena *arena, E2_IRNode *parent, E2_IRNode *e 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, E2_Val compile_time_eval_result, String8 string); +internal E2_Parse e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, 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 74139819..ad9c14b7 100644 --- a/src/eval2/eval2.mdesk +++ b/src/eval2/eval2.mdesk @@ -4,40 +4,46 @@ @table(name parse_kind precedence pre sep pos chain) E2_ExprKindTable: { - {Dot Binary 1 "" "." "" "" } - {Index Binary 1 "" "[" "]" "" } - {Call Call 1 "" "(" ")" ","} - {DerefAsm UnaryPrefix 1 "[" "" "]" "" } - {SizeOf UnaryPrefix 1 "sizeof " "" "" "" } - {TypeOf UnaryPrefix 1 "typeof " "" "" "" } - {CCast UnaryPrefix 1 "" "" "" "" } - {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 Null 13 "" "=" "" "" } - {Macro Null 14 "" "" "" "" } - {Cond Ternary 14 "" "?" "" ":"} + {Identifier Null 0} + {TypeIdentifier Null 0} + {Numeric Null 0} + {StringLiteral Null 0} + {CharLiteral Null 0} + {Dot Null 0 "" "." "" "" } + {Index Binary 1 "" "[" "]" "" } + {Call Call 1 "" "(" ")" ","} + {DerefAsm UnaryPrefix 1 "[" "" "]" "" } + {SizeOf UnaryPrefix 1 "sizeof " "" "" "" } + {TypeOf UnaryPrefix 1 "typeof " "" "" "" } + {CCast UnaryPrefix 1 "" "" "" "" } + {Deref UnaryPrefix 2 "*" "" "" "" } + {Address UnaryPrefix 2 "&" "" "" "" } + {Pos UnaryPrefix 2 "+" "" "" "" } + {Neg UnaryPrefix 2 "-" "" "" "" } + {LogNot UnaryPrefix 2 "!" "" "" "" } + {BitNot UnaryPrefix 2 "~" "" "" "" } + {Ptr UnaryPostfix 1 "" "" "*" "" } + {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 Null 13 "" "=" "" "" } + {Macro Null 14 "" "" "" "" } + {Cond Ternary 14 "" "?" "" ":"} } @enum E2_ExprKind: diff --git a/src/eval2/generated/eval2.meta.c b/src/eval2/generated/eval2.meta.c index ddfd007b..a1faf05b 100644 --- a/src/eval2/generated/eval2.meta.c +++ b/src/eval2/generated/eval2.meta.c @@ -4,10 +4,15 @@ //- GENERATED CODE C_LINKAGE_BEGIN -E2_ExprKindInfo e2_expr_kind_info_table[35] = +E2_ExprKindInfo e2_expr_kind_info_table[41] = { {0}, -{E2_ExprParseKind_Binary, 1, str8_lit_comp(""), str8_lit_comp("."), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp("."), str8_lit_comp(""), str8_lit_comp("")}, {E2_ExprParseKind_Binary, 1, str8_lit_comp(""), str8_lit_comp("["), str8_lit_comp("]"), str8_lit_comp("")}, {E2_ExprParseKind_Call, 1, str8_lit_comp(""), str8_lit_comp("("), str8_lit_comp(")"), str8_lit_comp(",")}, {E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("["), str8_lit_comp(""), str8_lit_comp("]"), str8_lit_comp("")}, @@ -20,6 +25,7 @@ E2_ExprKindInfo e2_expr_kind_info_table[35] = {E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("-"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, {E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("!"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, {E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("~"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_UnaryPostfix, 1, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("*"), str8_lit_comp("")}, {E2_ExprParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("*"), str8_lit_comp(""), str8_lit_comp("")}, {E2_ExprParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("/"), str8_lit_comp(""), str8_lit_comp("")}, {E2_ExprParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("%"), str8_lit_comp(""), str8_lit_comp("")}, diff --git a/src/eval2/generated/eval2.meta.h b/src/eval2/generated/eval2.meta.h index 2cd5dedb..72f22fd6 100644 --- a/src/eval2/generated/eval2.meta.h +++ b/src/eval2/generated/eval2.meta.h @@ -9,6 +9,11 @@ typedef enum E2_ExprKind { E2_ExprKind_Null, +E2_ExprKind_Identifier, +E2_ExprKind_TypeIdentifier, +E2_ExprKind_Numeric, +E2_ExprKind_StringLiteral, +E2_ExprKind_CharLiteral, E2_ExprKind_Dot, E2_ExprKind_Index, E2_ExprKind_Call, @@ -22,6 +27,7 @@ E2_ExprKind_Pos, E2_ExprKind_Neg, E2_ExprKind_LogNot, E2_ExprKind_BitNot, +E2_ExprKind_Ptr, E2_ExprKind_Mul, E2_ExprKind_Div, E2_ExprKind_Mod, @@ -125,7 +131,7 @@ E2_TypeKind_LastMeta = E2_TypeKind_MetaDescription, } E2_TypeKind; C_LINKAGE_BEGIN -extern E2_ExprKindInfo e2_expr_kind_info_table[35]; +extern E2_ExprKindInfo e2_expr_kind_info_table[41]; extern U8 e2_type_kind_basic_byte_size_table[61]; extern String8 e2_type_kind_basic_string_table[61]; diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index e80a047e..3c9483a6 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -46,7 +46,7 @@ entry_point(CmdLine *cmdline) s("123, 456"), s("int32 == int32"), s("foo = 123"), - s("bar(a, b) = a + b"), + // s("bar(a, b) = a + b"), s("1 > 2"), s("1 ? \"Test\" : 888"), s("'a'"), @@ -88,67 +88,16 @@ 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; - E2_Val compile_time_eval_result = {0}; + B32 identifier_is_type = 0; for(;;) { - E2_Parse parse = e2_parse_from_string(scratch.arena, &state, &expr_map, access_expr, compile_time_eval_result, strings[idx]); + E2_Parse parse = e2_parse_from_string(scratch.arena, &state, identifier_is_type, 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) - { - E2_Expr *expr = &e2_expr_nil; - if(str8_match(parse.identifier, s("float32"), 0)) - { - expr = e2_expr_type(scratch.arena, e2_type_key_basic(E2_TypeKind_F32)); - } - else if(str8_match(parse.identifier, s("float64"), 0)) - { - expr = e2_expr_type(scratch.arena, e2_type_key_basic(E2_TypeKind_F64)); - } - else if(str8_match(parse.identifier, s("int32"), 0)) - { - expr = e2_expr_type(scratch.arena, e2_type_key_basic(E2_TypeKind_S32)); - } - else if(str8_match(parse.identifier, s("int64"), 0)) - { - expr = e2_expr_type(scratch.arena, e2_type_key_basic(E2_TypeKind_S64)); - } - else - { - expr = e2_expr_const_u64_or_smaller(scratch.arena, 123); - } - e2_expr_map_push(scratch.arena, &expr_map, parse.identifier, expr); - } - if(parse.status == E2_ParseStatus_NewIdentifierDefinition) - { - e2_expr_map_push(scratch.arena, &expr_map, parse.identifier, expr); - } - if(parse.status == E2_ParseStatus_MemberAccess) - { - access_expr = e2_expr_const_u64_or_smaller(scratch.arena, 456); - } - if(parse.status == E2_ParseStatus_IndexAccess) - { - access_expr = e2_expr_const_u64_or_smaller(scratch.arena, 111); - } - if(parse.status == E2_ParseStatus_Call) - { - access_expr = e2_expr_const_u64_or_smaller(scratch.arena, 123456); - } - if(parse.status == E2_ParseStatus_CompileTimeEval) - { - String8 bytecode = e2_bytecode_from_expr(scratch.arena, expr); - 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_parse_status_is_terminal(parse.status)) { break; @@ -156,8 +105,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}; @@ -189,8 +201,8 @@ entry_point(CmdLine *cmdline) val.s64, val.f32, val.f64, - expr->mode == E2_Mode_Type ? "type" : - expr->mode == E2_Mode_Value ? "value" : + irtree->mode == E2_Mode_Type ? "type" : + irtree->mode == E2_Mode_Value ? "value" : "address", msgs_string.size != 0 ? " // " : "", msgs_string); raddbg_log("%S", log); From bcbc5e4a2ab18e451a6ccb3ebc922bd14e019d3d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 26 Jun 2026 10:07:02 -0600 Subject: [PATCH 13/90] eval2: macro argument expression tree parsing --- src/eval2/eval2.c | 24 ++++++++++++++++++++++++ src/eval2/eval2.h | 4 ++-- src/eval2/eval2.mdesk | 1 + src/eval2/generated/eval2.meta.c | 3 ++- src/eval2/generated/eval2.meta.h | 3 ++- src/scratch/ryan_scratch.c | 8 +++++++- 6 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index 57d4efee..1abf8ded 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -1990,6 +1990,30 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } } + // rjf: try to resolve it as a macro argument + if(!identifier_mapped && state->top_task != 0 && state->top_task->expr_kind == E2_ExprKind_Macro) + { + 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: build this as a leaf identifier expression - ask the caller if it's a type if(!identifier_mapped) { diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index 5594c8ec..e71bd30e 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -56,9 +56,8 @@ typedef enum E2_ParseStatus //- rjf: caller-provided info E2_ParseStatus_CheckIdentifierIsType, - E2_ParseStatus_NewMacro, E2_ParseStatus_FirstCallerRequest = E2_ParseStatus_CheckIdentifierIsType, - E2_ParseStatus_LastCallerRequest = E2_ParseStatus_NewMacro, + E2_ParseStatus_LastCallerRequest = E2_ParseStatus_CheckIdentifierIsType, } E2_ParseStatus; @@ -328,6 +327,7 @@ struct E2_Expr E2_ExprNode *last_child; U64 child_count; E2_ExprKind kind; + U32 macro_arg_num; Rng1U64 src_range; String8 string; }; diff --git a/src/eval2/eval2.mdesk b/src/eval2/eval2.mdesk index ad9c14b7..dc77b61c 100644 --- a/src/eval2/eval2.mdesk +++ b/src/eval2/eval2.mdesk @@ -5,6 +5,7 @@ E2_ExprKindTable: { {Identifier Null 0} + {MacroArg Null 0} {TypeIdentifier Null 0} {Numeric Null 0} {StringLiteral Null 0} diff --git a/src/eval2/generated/eval2.meta.c b/src/eval2/generated/eval2.meta.c index a1faf05b..d36bd2f9 100644 --- a/src/eval2/generated/eval2.meta.c +++ b/src/eval2/generated/eval2.meta.c @@ -4,7 +4,7 @@ //- GENERATED CODE C_LINKAGE_BEGIN -E2_ExprKindInfo e2_expr_kind_info_table[41] = +E2_ExprKindInfo e2_expr_kind_info_table[42] = { {0}, {E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, @@ -12,6 +12,7 @@ E2_ExprKindInfo e2_expr_kind_info_table[41] = {E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, {E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, {E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, {E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp("."), str8_lit_comp(""), str8_lit_comp("")}, {E2_ExprParseKind_Binary, 1, str8_lit_comp(""), str8_lit_comp("["), str8_lit_comp("]"), str8_lit_comp("")}, {E2_ExprParseKind_Call, 1, str8_lit_comp(""), str8_lit_comp("("), str8_lit_comp(")"), str8_lit_comp(",")}, diff --git a/src/eval2/generated/eval2.meta.h b/src/eval2/generated/eval2.meta.h index 72f22fd6..8fa788b5 100644 --- a/src/eval2/generated/eval2.meta.h +++ b/src/eval2/generated/eval2.meta.h @@ -10,6 +10,7 @@ typedef enum E2_ExprKind { E2_ExprKind_Null, E2_ExprKind_Identifier, +E2_ExprKind_MacroArg, E2_ExprKind_TypeIdentifier, E2_ExprKind_Numeric, E2_ExprKind_StringLiteral, @@ -131,7 +132,7 @@ E2_TypeKind_LastMeta = E2_TypeKind_MetaDescription, } E2_TypeKind; C_LINKAGE_BEGIN -extern E2_ExprKindInfo e2_expr_kind_info_table[41]; +extern E2_ExprKindInfo e2_expr_kind_info_table[42]; extern U8 e2_type_kind_basic_byte_size_table[61]; extern String8 e2_type_kind_basic_string_table[61]; diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index 3c9483a6..d7875e8a 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -43,10 +43,10 @@ entry_point(CmdLine *cmdline) // (A) // int & B // (1 + (int)&B) + s("bar(a, b) = a + b"), s("123, 456"), s("int32 == int32"), s("foo = 123"), - // s("bar(a, b) = a + b"), s("1 > 2"), s("1 ? \"Test\" : 888"), s("'a'"), @@ -98,6 +98,12 @@ entry_point(CmdLine *cmdline) { 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; From 9491fdf5c240878d0e38d5824f207976e70b8e49 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 26 Jun 2026 11:22:43 -0600 Subject: [PATCH 14/90] eval2: split out expression kinds from parse styles of expressions - lets us separate semantic expression tree from frontend, so that we can easily switch frontend --- src/eval2/eval2.c | 133 +++++++++++++++++++------------ src/eval2/eval2.h | 21 ++--- src/eval2/eval2.mdesk | 109 ++++++++++++++++++++----- src/eval2/generated/eval2.meta.c | 81 +++++++++---------- src/eval2/generated/eval2.meta.h | 27 ++++++- src/scratch/ryan_scratch.c | 2 +- 6 files changed, 239 insertions(+), 134 deletions(-) diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index 1abf8ded..5b528e45 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -1508,8 +1508,9 @@ 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) +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; @@ -1648,9 +1649,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_ExprKind, k) + for EachIndex(idx, lang_info->expr_kind_parse_infos_count) { - String8 op_symbol = (e2_expr_kind_info_table[k].pre.size != 0) ? e2_expr_kind_info_table[k].pre : e2_expr_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; @@ -1695,9 +1696,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; @@ -1707,14 +1708,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; @@ -1737,10 +1738,11 @@ 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, B32 identifier_is_type, 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_LangInfo *lang_info = &e2_lang_kind_info_table[lang]; //- rjf: parse & attach to top parsing task - if we don't have a top task // then it is just the result @@ -1756,7 +1758,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, 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) { @@ -1769,7 +1771,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } //- 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) @@ -1789,21 +1791,23 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } //- 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)) { // rjf: string -> operator kind E2_ExprKind expr_kind = E2_ExprKind_Null; String8 closer = {0}; + S64 precedence = 0; { String8 token_string = str8_substr(string, token.range); - for EachNonZeroEnumVal(E2_ExprKind, k) + for EachIndex(idx, lang_info->expr_kind_parse_infos_count) { - if(e2_expr_kind_info_table[k].parse_kind == E2_ExprParseKind_UnaryPrefix && - e2_expr_kind_info_table[k].precedence <= max_precedence && - str8_match(token_string, e2_expr_kind_info_table[k].pre, 0)) + if(lang_info->expr_kind_parse_infos[idx].parse_kind == E2_ExprParseKind_UnaryPrefix && + lang_info->expr_kind_parse_infos[idx].precedence <= max_precedence && + str8_match(token_string, lang_info->expr_kind_parse_infos[idx].pre, 0)) { - expr_kind = k; - closer = e2_expr_kind_info_table[k].post; + 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; } } @@ -1825,7 +1829,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, task->src_range = token.range; task->child_count_target = 1; task->expr_kind = expr_kind; - task->max_precedence = e2_expr_kind_info_table[expr_kind].precedence; + task->max_precedence = precedence; task->expected_closer = closer; SLLStackPush(state->top_task, task); } @@ -1839,7 +1843,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } //- rjf: identifiers with an active dot op kind -> member access - else if(need_new_expr && state->top_task != 0 && state->top_task->expr_kind == E2_ExprKind_Dot && e2_try_token(string, E2_TokenKind_Identifier, s(""), &off, &token)) + else if(need_new_expr && state->top_task != 0 && state->top_task->expr_kind == E2_ExprKind_Dot && e2_try_token(lang, string, E2_TokenKind_Identifier, s(""), &off, &token)) { expr = e2_expr(arena, E2_ExprKind_Dot); expr->first_child = state->top_task->first_child; @@ -1853,14 +1857,14 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } //- rjf: standalone identifiers (also could be definition or operator keyword) - else if(need_new_expr && e2_try_token(string, E2_TokenKind_Identifier, s(""), &off, &token)) + 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 == 0 || state->top_task->expr_kind != E2_ExprKind_Macro); // rjf: first try to resolve as a definition - if(definitions_are_allowed && !identifier_mapped && e2_try_token(string, E2_TokenKind_Symbol, s("="), &off, 0)) + if(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; @@ -1876,7 +1880,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, task->src_range = token.range; task->child_count_target = 1; task->expr_kind = E2_ExprKind_Define; - task->max_precedence = e2_expr_kind_info_table[E2_ExprKind_Define].precedence; + task->max_precedence = max_S64; task->identifier = identifier; SLLStackPush(state->top_task, task); } @@ -1885,26 +1889,26 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, if(definitions_are_allowed && !identifier_mapped) { U64 macro_off = off; - if(e2_try_token(string, E2_TokenKind_Symbol, s("("), ¯o_off, 0)) + 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(string, E2_TokenKind_Identifier, s(""), ¯o_off, 0)) + 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(string, E2_TokenKind_Symbol, s(","), ¯o_off, 0); - if(e2_try_token(string, E2_TokenKind_Symbol, s(")"), ¯o_off, 0)) + 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(string, E2_TokenKind_Symbol, s("="), ¯o_off, 0)) + 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; @@ -1930,18 +1934,18 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, for(;macro_off < string.size;) { E2_Token next_token = {0}; - if(!e2_try_token(string, E2_TokenKind_Identifier, s(""), ¯o_off, &next_token)) + 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(string, E2_TokenKind_Symbol, s(","), ¯o_off, 0); - if(e2_try_token(string, E2_TokenKind_Symbol, s(")"), ¯o_off, 0)) + 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(string, E2_TokenKind_Symbol, s("="), ¯o_off, 0); + e2_try_token(lang, string, E2_TokenKind_Symbol, s("="), ¯o_off, 0); // rjf: advance offset to past '=' off = macro_off; @@ -1954,15 +1958,17 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, { // rjf: string -> op kind E2_ExprKind expr_kind = E2_ExprKind_Null; + S64 precedence = 0; { String8 token_string = str8_substr(string, token.range); - for EachNonZeroEnumVal(E2_ExprKind, k) + for EachIndex(idx, lang_info->expr_kind_parse_infos_count) { - if(e2_expr_kind_info_table[k].parse_kind == E2_ExprParseKind_UnaryPrefix && - e2_expr_kind_info_table[k].precedence <= max_precedence && - str8_match(token_string, str8_skip_chop_whitespace(e2_expr_kind_info_table[k].pre), 0)) + if(lang_info->expr_kind_parse_infos[idx].parse_kind == E2_ExprParseKind_UnaryPrefix && + 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)) { - expr_kind = k; + expr_kind = lang_info->expr_kind_parse_infos[idx].expr_kind; + precedence = lang_info->expr_kind_parse_infos[idx].precedence; break; } } @@ -1985,7 +1991,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, task->src_range = token.range; task->child_count_target = 1; task->expr_kind = expr_kind; - task->max_precedence = e2_expr_kind_info_table[expr_kind].precedence; + task->max_precedence = precedence; SLLStackPush(state->top_task, task); } } @@ -2035,14 +2041,14 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } //- 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)) { 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(string, E2_TokenKind_StringLiteral, s(""), &off, &token)) + 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); @@ -2052,7 +2058,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } //- rjf: leaf character literals - else if(need_new_expr && e2_try_token(string, E2_TokenKind_CharLiteral, s(""), &off, &token)) + 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); @@ -2065,7 +2071,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, 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)) { // rjf: token string -> operator kind E2_ExprKind expr_kind = E2_ExprKind_Null; @@ -2073,11 +2079,12 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, String8 splitter = {0}; String8 closer = {0}; B32 splitter_is_required = 0; + S64 precedence = 0; { String8 token_string = str8_substr(string, token.range); - for EachNonZeroEnumVal(E2_ExprKind, k) + for EachIndex(idx, lang_info->expr_kind_parse_infos_count) { - E2_ExprKindInfo *expr_kind_info = &e2_expr_kind_info_table[k]; + 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)) { switch(expr_kind_info->parse_kind) @@ -2089,7 +2096,8 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } splitter = expr_kind_info->chain; closer = expr_kind_info->post; - expr_kind = k; + expr_kind = expr_kind_info->expr_kind; + precedence = expr_kind_info->precedence; break; } } @@ -2112,7 +2120,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, task->child_count = 0; task->child_count_target = child_count_target; task->expr_kind = expr_kind; - task->max_precedence = e2_expr_kind_info_table[expr_kind].precedence; + task->max_precedence = precedence; task->expected_splitter = splitter; task->expected_closer = closer; task->splitter_is_required = splitter_is_required; @@ -2144,7 +2152,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, 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) + 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); } @@ -2154,7 +2162,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, 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); + closer_found = e2_try_token(lang, string, E2_TokenKind_Symbol, state->top_task->expected_closer, &off, 0); } //- rjf: determine if first child of a parenthesized sub-expression is a type @@ -2211,7 +2219,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, task->child_count = 1; task->child_count_target = 2; task->expr_kind = E2_ExprKind_CCast; - task->max_precedence = e2_expr_kind_info_table[E2_ExprKind_CCast].precedence; + task->max_precedence = 1; SLLStackPush(state->top_task, task); expr = &e2_expr_nil; E2_ExprNode *child_n = push_array(arena, E2_ExprNode, 1); @@ -2304,6 +2312,25 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, { for(E2_ParseTask *t = state->top_task; t != 0; t = t->next) { + 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) + { + if(lang_info->expr_kind_parse_infos[idx].parse_kind == E2_ExprParseKind_Binary) + { + symbol = lang_info->expr_kind_parse_infos[idx].sep; + } + else if(lang_info->expr_kind_parse_infos[idx].parse_kind == E2_ExprParseKind_UnaryPrefix) + { + 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); @@ -2316,13 +2343,13 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, { e2_msgf(arena, &parse.msgs, t->src_range, "Couldn't call into this type."); } - else if(t->child_count == 1 && t->child_count_target == 2 && t->expr_kind != E2_ExprKind_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 binary operator `%S`.", e2_expr_kind_info_table[t->expr_kind].sep); + 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) + 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`.", e2_expr_kind_info_table[t->expr_kind].pre); + e2_msgf(arena, &parse.msgs, t->src_range, "Expected expression after unary operator `%S`.", symbol); } else { @@ -2954,7 +2981,7 @@ e2_compile_from_expr(Arena *arena, E2_CompileState *state, E2_IRNode *resolve_re } else { - e2_msgf(arena, &compile.msgs, e->src_range, "Cannot use `%S` on types.", str8_skip_chop_whitespace(e2_expr_kind_info_table[e->kind].sep)); + e2_msgf(arena, &compile.msgs, e->src_range, "Cannot use this operator on types."); } }break; } diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index e71bd30e..758a43ac 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -5,7 +5,7 @@ #define EVAL2_H //////////////////////////////// -//~ rjf: Operator Info Tables +//~ rjf: Expression Parse Info Table Types typedef enum E2_ExprParseKind { @@ -18,17 +18,6 @@ typedef enum E2_ExprParseKind } E2_ExprParseKind; -typedef struct E2_ExprKindInfo E2_ExprKindInfo; -struct E2_ExprKindInfo -{ - E2_ExprParseKind parse_kind; - S64 precedence; - String8 pre; - String8 sep; - String8 post; - String8 chain; -}; - //////////////////////////////// //~ rjf: Generated Code @@ -674,10 +663,10 @@ internal void e2_irnode_push_child(Arena *arena, E2_IRNode *parent, E2_IRNode *e //////////////////////////////// //~ 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, B32 identifier_is_type, 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 -> IR Tree diff --git a/src/eval2/eval2.mdesk b/src/eval2/eval2.mdesk index dc77b61c..ef0aaba9 100644 --- a/src/eval2/eval2.mdesk +++ b/src/eval2/eval2.mdesk @@ -1,16 +1,95 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -@table(name parse_kind precedence pre sep pos chain) +@table(name) E2_ExprKindTable: { - {Identifier Null 0} - {MacroArg Null 0} - {TypeIdentifier Null 0} - {Numeric Null 0} - {StringLiteral Null 0} - {CharLiteral Null 0} - {Dot Null 0 "" "." "" "" } + {Identifier} + {MacroArg} + {TypeIdentifier} + {Numeric} + {StringLiteral} + {CharLiteral} + {Dot} + {Index} + {Call} + {DerefAsm} + {SizeOf} + {TypeOf} + {CCast} + {Deref} + {Address} + {Pos} + {Neg} + {LogNot} + {BitNot} + {Ptr} + {Mul} + {Div} + {Mod} + {Add} + {Sub} + {LShift} + {RShift} + {Less} + {LtEq} + {Grtr} + {GrEq} + {EqEq} + {NtEq} + {BitAnd} + {BitXor} + {BitOr} + {LogAnd} + {LogOr} + {Define} + {Macro} + {Cond} +} + +@enum E2_ExprKind: +{ + Null, + @expand(E2_ExprKindTable a) `$(a.name)`, + COUNT, +} + +@struct E2_ExprKindParseInfo: +{ + `E2_ExprKind expr_kind`; + `E2_ExprParseKind parse_kind`; + `S64 precedence`; + `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) +E2_ExprKindParseInfoTable_CLike: +{ {Index Binary 1 "" "[" "]" "" } {Call Call 1 "" "(" ")" ","} {DerefAsm UnaryPrefix 1 "[" "" "]" "" } @@ -42,22 +121,12 @@ E2_ExprKindTable: {BitOr Binary 10 "" "|" "" "" } {LogAnd Binary 11 "" "&&" "" "" } {LogOr Binary 12 "" "||" "" "" } - {Define Null 13 "" "=" "" "" } - {Macro Null 14 "" "" "" "" } {Cond Ternary 14 "" "?" "" ":"} } -@enum E2_ExprKind: +@data(E2_ExprKindParseInfo) e2_expr_kind_parse_info_table__clike: { - Null, - @expand(E2_ExprKindTable a) `$(a.name)`, - COUNT, -} - -@data(E2_ExprKindInfo) e2_expr_kind_info_table: -{ - `{0}`, - @expand(E2_ExprKindTable a) `{E2_ExprParseKind_$(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)")}` + @expand(E2_ExprKindParseInfoTable_CLike a) `{E2_ExprKind_$(a.name), E2_ExprParseKind_$(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)")}` } @table(name basic_string basic_byte_size) diff --git a/src/eval2/generated/eval2.meta.c b/src/eval2/generated/eval2.meta.c index d36bd2f9..8df3c611 100644 --- a/src/eval2/generated/eval2.meta.c +++ b/src/eval2/generated/eval2.meta.c @@ -4,50 +4,45 @@ //- GENERATED CODE C_LINKAGE_BEGIN -E2_ExprKindInfo e2_expr_kind_info_table[42] = +E2_LangInfo e2_lang_kind_info_table[1] = { -{0}, -{E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Null, 0, str8_lit_comp(""), str8_lit_comp("."), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 1, str8_lit_comp(""), str8_lit_comp("["), str8_lit_comp("]"), str8_lit_comp("")}, -{E2_ExprParseKind_Call, 1, str8_lit_comp(""), str8_lit_comp("("), str8_lit_comp(")"), str8_lit_comp(",")}, -{E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("["), str8_lit_comp(""), str8_lit_comp("]"), str8_lit_comp("")}, -{E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("sizeof "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("typeof "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("*"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("&"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("+"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("-"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("!"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("~"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_UnaryPostfix, 1, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("*"), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("*"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("/"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("%"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 4, str8_lit_comp(""), str8_lit_comp("+"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 4, str8_lit_comp(""), str8_lit_comp("-"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 5, str8_lit_comp(""), str8_lit_comp("<<"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 5, str8_lit_comp(""), str8_lit_comp(">>"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp("<"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp("<="), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp(">"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp(">="), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 7, str8_lit_comp(""), str8_lit_comp("=="), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 7, str8_lit_comp(""), str8_lit_comp("!="), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 8, str8_lit_comp(""), str8_lit_comp("&"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 9, str8_lit_comp(""), str8_lit_comp("^"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 10, str8_lit_comp(""), str8_lit_comp("|"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 11, str8_lit_comp(""), str8_lit_comp("&&"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Binary, 12, str8_lit_comp(""), str8_lit_comp("||"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Null, 13, str8_lit_comp(""), str8_lit_comp("="), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Null, 14, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprParseKind_Ternary, 14, str8_lit_comp(""), str8_lit_comp("?"), str8_lit_comp(""), str8_lit_comp(":")}, +{ArrayCount(e2_expr_kind_parse_info_table__clike), (e2_expr_kind_parse_info_table__clike)}, +}; + +E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[32] = +{ +{E2_ExprKind_Index, E2_ExprParseKind_Binary, 1, str8_lit_comp(""), str8_lit_comp("["), str8_lit_comp("]"), str8_lit_comp("")}, +{E2_ExprKind_Call, E2_ExprParseKind_Call, 1, str8_lit_comp(""), str8_lit_comp("("), str8_lit_comp(")"), str8_lit_comp(",")}, +{E2_ExprKind_DerefAsm, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("["), str8_lit_comp(""), str8_lit_comp("]"), str8_lit_comp("")}, +{E2_ExprKind_SizeOf, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("sizeof "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_TypeOf, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("typeof "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_CCast, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Deref, E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("*"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Address, E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("&"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Pos, E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("+"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Neg, E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("-"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_LogNot, E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("!"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_BitNot, E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("~"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Ptr, E2_ExprParseKind_UnaryPostfix, 1, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("*"), str8_lit_comp("")}, +{E2_ExprKind_Mul, E2_ExprParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("*"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Div, E2_ExprParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("/"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Mod, E2_ExprParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("%"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Add, E2_ExprParseKind_Binary, 4, str8_lit_comp(""), str8_lit_comp("+"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Sub, E2_ExprParseKind_Binary, 4, str8_lit_comp(""), str8_lit_comp("-"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_LShift, E2_ExprParseKind_Binary, 5, str8_lit_comp(""), str8_lit_comp("<<"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_RShift, E2_ExprParseKind_Binary, 5, str8_lit_comp(""), str8_lit_comp(">>"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Less, E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp("<"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_LtEq, E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp("<="), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Grtr, E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp(">"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_GrEq, E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp(">="), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_EqEq, E2_ExprParseKind_Binary, 7, str8_lit_comp(""), str8_lit_comp("=="), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_NtEq, E2_ExprParseKind_Binary, 7, str8_lit_comp(""), str8_lit_comp("!="), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_BitAnd, E2_ExprParseKind_Binary, 8, str8_lit_comp(""), str8_lit_comp("&"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_BitXor, E2_ExprParseKind_Binary, 9, str8_lit_comp(""), str8_lit_comp("^"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_BitOr, E2_ExprParseKind_Binary, 10, str8_lit_comp(""), str8_lit_comp("|"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_LogAnd, E2_ExprParseKind_Binary, 11, str8_lit_comp(""), str8_lit_comp("&&"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_LogOr, E2_ExprParseKind_Binary, 12, str8_lit_comp(""), str8_lit_comp("||"), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_Cond, E2_ExprParseKind_Ternary, 14, 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 8fa788b5..60a92d50 100644 --- a/src/eval2/generated/eval2.meta.h +++ b/src/eval2/generated/eval2.meta.h @@ -53,6 +53,11 @@ E2_ExprKind_Cond, E2_ExprKind_COUNT, } E2_ExprKind; +typedef enum E2_LangKind +{ +E2_LangKind_CLike, +} E2_LangKind; + typedef enum E2_TypeKind { E2_TypeKind_Null, @@ -131,8 +136,28 @@ 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; +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_ExprKindInfo e2_expr_kind_info_table[42]; +extern E2_LangInfo e2_lang_kind_info_table[1]; +extern E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[32]; extern U8 e2_type_kind_basic_byte_size_table[61]; extern String8 e2_type_kind_basic_string_table[61]; diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index d7875e8a..3c0994de 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -91,7 +91,7 @@ entry_point(CmdLine *cmdline) B32 identifier_is_type = 0; for(;;) { - E2_Parse parse = e2_parse_from_string(scratch.arena, &state, identifier_is_type, 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; for EachNode(n, E2_Msg, parse.msgs.first) From 60b266a1cc07cd9819da3754718ab736b52bd28d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 26 Jun 2026 11:28:01 -0600 Subject: [PATCH 15/90] support size_of and type_of in clike expressions --- project.raddbg | 2 +- src/eval2/eval2.mdesk | 66 ++++++++++++++++---------------- src/eval2/generated/eval2.meta.c | 4 +- src/eval2/generated/eval2.meta.h | 2 +- src/raddbg/raddbg_core.c | 2 +- 5 files changed, 40 insertions(+), 36 deletions(-) diff --git a/project.raddbg b/project.raddbg index 8c9b43d1..4adbb44b 100644 --- a/project.raddbg +++ b/project.raddbg @@ -7,6 +7,7 @@ target: working_directory: "../raddebugger" arguments: "--user:C:/devel/raddebugger/build/raddbg_test.user" debug_subprocesses: 0 + enabled: 1 } target: { @@ -66,5 +67,4 @@ target: { executable: "build/ryan_scratch.exe" working_directory: build - enabled: 1 } diff --git a/src/eval2/eval2.mdesk b/src/eval2/eval2.mdesk index ef0aaba9..50b986a3 100644 --- a/src/eval2/eval2.mdesk +++ b/src/eval2/eval2.mdesk @@ -90,38 +90,40 @@ E2_LangKindTable: @table(name parse_kind precedence pre sep pos chain) E2_ExprKindParseInfoTable_CLike: { - {Index Binary 1 "" "[" "]" "" } - {Call Call 1 "" "(" ")" ","} - {DerefAsm UnaryPrefix 1 "[" "" "]" "" } - {SizeOf UnaryPrefix 1 "sizeof " "" "" "" } - {TypeOf UnaryPrefix 1 "typeof " "" "" "" } - {CCast UnaryPrefix 1 "" "" "" "" } - {Deref UnaryPrefix 2 "*" "" "" "" } - {Address UnaryPrefix 2 "&" "" "" "" } - {Pos UnaryPrefix 2 "+" "" "" "" } - {Neg UnaryPrefix 2 "-" "" "" "" } - {LogNot UnaryPrefix 2 "!" "" "" "" } - {BitNot UnaryPrefix 2 "~" "" "" "" } - {Ptr UnaryPostfix 1 "" "" "*" "" } - {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 "" "||" "" "" } - {Cond Ternary 14 "" "?" "" ":"} + {Index Binary 1 "" "[" "]" "" } + {Call Call 1 "" "(" ")" ","} + {DerefAsm UnaryPrefix 1 "[" "" "]" "" } + {SizeOf UnaryPrefix 1 "sizeof " "" "" "" } + {TypeOf UnaryPrefix 1 "typeof " "" "" "" } + {SizeOf UnaryPrefix 1 "size_of " "" "" "" } + {TypeOf UnaryPrefix 1 "type_of " "" "" "" } + {CCast UnaryPrefix 1 "" "" "" "" } + {Deref UnaryPrefix 2 "*" "" "" "" } + {Address UnaryPrefix 2 "&" "" "" "" } + {Pos UnaryPrefix 2 "+" "" "" "" } + {Neg UnaryPrefix 2 "-" "" "" "" } + {LogNot UnaryPrefix 2 "!" "" "" "" } + {BitNot UnaryPrefix 2 "~" "" "" "" } + {Ptr UnaryPostfix 1 "" "" "*" "" } + {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 "" "||" "" "" } + {Cond Ternary 14 "" "?" "" ":"} } @data(E2_ExprKindParseInfo) e2_expr_kind_parse_info_table__clike: diff --git a/src/eval2/generated/eval2.meta.c b/src/eval2/generated/eval2.meta.c index 8df3c611..5a9c842c 100644 --- a/src/eval2/generated/eval2.meta.c +++ b/src/eval2/generated/eval2.meta.c @@ -9,13 +9,15 @@ 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[32] = +E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[34] = { {E2_ExprKind_Index, E2_ExprParseKind_Binary, 1, str8_lit_comp(""), str8_lit_comp("["), str8_lit_comp("]"), str8_lit_comp("")}, {E2_ExprKind_Call, E2_ExprParseKind_Call, 1, str8_lit_comp(""), str8_lit_comp("("), str8_lit_comp(")"), str8_lit_comp(",")}, {E2_ExprKind_DerefAsm, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("["), str8_lit_comp(""), str8_lit_comp("]"), str8_lit_comp("")}, {E2_ExprKind_SizeOf, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("sizeof "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, {E2_ExprKind_TypeOf, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("typeof "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_SizeOf, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("size_of "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, +{E2_ExprKind_TypeOf, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("type_of "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, {E2_ExprKind_CCast, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, {E2_ExprKind_Deref, E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("*"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, {E2_ExprKind_Address, E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("&"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, diff --git a/src/eval2/generated/eval2.meta.h b/src/eval2/generated/eval2.meta.h index 60a92d50..bc3638b5 100644 --- a/src/eval2/generated/eval2.meta.h +++ b/src/eval2/generated/eval2.meta.h @@ -157,7 +157,7 @@ E2_ExprKindParseInfo *expr_kind_parse_infos; C_LINKAGE_BEGIN extern E2_LangInfo e2_lang_kind_info_table[1]; -extern E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[32]; +extern E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[34]; extern U8 e2_type_kind_basic_byte_size_table[61]; extern String8 e2_type_kind_basic_string_table[61]; diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 21e21a9d..c994b690 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -4117,7 +4117,7 @@ rd_view_ui(Rng2F32 rect) cells_are_editable && row->eval.expr == &e_expr_nil) { - ghost_text = str8_lit("Expression"); + ghost_text = str8_lit("Add a expression to watch..."); is_non_code = (!cell_selected || !ewv->text_editing); cell_params.flags &= ~(RD_CellFlag_Expander|RD_CellFlag_ExpanderSpace|RD_CellFlag_ExpanderPlaceholder); } From f1f654ecfd97541f513c717db4b44d138271b599 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 26 Jun 2026 13:20:38 -0700 Subject: [PATCH 16/90] eval2: simplify expression parsing info, allow child reversal (for parsing non-canonical expression orders into canonical shapes, e.g. Rust-style casts like 123 as float32 -> cast(float32, 123); ptr/array type operators, etc./ --- project.4coder | 2 +- project.raddbg | 2 +- src/eval2/eval2.c | 353 +++++++++++-------------------- src/eval2/eval2.h | 21 +- src/eval2/eval2.mdesk | 179 +++++++++------- src/eval2/generated/eval2.meta.c | 214 ++++++++++++++++--- src/eval2/generated/eval2.meta.h | 11 +- src/scratch/ryan_scratch.c | 5 + 8 files changed, 421 insertions(+), 366 deletions(-) diff --git a/project.4coder b/project.4coder index 0dfc2fee..6d11dd66 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 ryan_scratch no_meta && pushd build && ryan_scratch && popd", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && build ryan_scratch && pushd build && ryan_scratch && popd", .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 4adbb44b..8c9b43d1 100644 --- a/project.raddbg +++ b/project.raddbg @@ -7,7 +7,6 @@ target: working_directory: "../raddebugger" arguments: "--user:C:/devel/raddebugger/build/raddbg_test.user" debug_subprocesses: 0 - enabled: 1 } target: { @@ -67,4 +66,5 @@ target: { executable: "build/ryan_scratch.exe" working_directory: build + enabled: 1 } diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index 5b528e45..e1aecb5a 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -1069,211 +1069,6 @@ e2_expr_push_child(Arena *arena, E2_Expr *parent, E2_Expr *expr) n->v = expr; e2_expr_push_child_node(parent, n); } -#if 0 - -internal E2_Expr * -e2_expr_const_u64_or_smaller(Arena *arena, U64 u) -{ - E2_Expr *e = e2_expr(arena); - { - e->mode = E2_Mode_Value; - e->val.u64 = u; - if(u <= 0x7fffffff) - { - e->op = RDI_EvalOp_ConstU32; - e->type_key = e2_type_key_basic(E2_TypeKind_S32); - } - else if(u <= 0xffffffff) - { - e->op = RDI_EvalOp_ConstU32; - e->type_key = e2_type_key_basic(E2_TypeKind_U32); - } - else if(u <= 0x7fffffffffffffffull) - { - e->op = RDI_EvalOp_ConstU64; - e->type_key = e2_type_key_basic(E2_TypeKind_S64); - } - else if(u <= 0xffffffffffffffffull) - { - e->op = RDI_EvalOp_ConstU64; - e->type_key = e2_type_key_basic(E2_TypeKind_U64); - } - } - return e; -} - -internal E2_Expr * -e2_expr_const_f32(Arena *arena, F32 f32) -{ - E2_Expr *e = e2_expr(arena); - e->mode = E2_Mode_Value; - e->val.f32 = f32; - e->op = RDI_EvalOp_ConstU32; - e->type_key = e2_type_key_basic(E2_TypeKind_F32); - return e; -} - -internal E2_Expr * -e2_expr_const_f64(Arena *arena, F64 f64) -{ - E2_Expr *e = e2_expr(arena); - e->mode = E2_Mode_Value; - e->val.f64 = f64; - e->op = RDI_EvalOp_ConstU64; - e->type_key = e2_type_key_basic(E2_TypeKind_F64); - return e; -} - -internal E2_Expr * -e2_expr_unary_op(Arena *arena, E2_TypeKey type_key, RDI_EvalOp op, E2_Expr *operand) -{ - E2_Expr *e = e2_expr(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(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) -{ - E2_Expr *e = e2_expr(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(arith_type_key)); - e->val.u512.u8[1] = 8*e2_byte_size_from_type_key(arith_type_key); - e2_expr_push_child(arena, e, lhs); - e2_expr_push_child(arena, e, rhs); - return e; -} - -internal E2_Expr * -e2_expr_resolve_to_value(Arena *arena, E2_Expr *expr) -{ - E2_Expr *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->type_key = e2_type_key_direct(e2_type_key_undecorate(expr->type_key)); - result->mode = E2_Mode_Value; - e2_expr_push_child(arena, result, expr); - } - - // rjf: address evaluations -> read value from space - else if(expr->mode == E2_Mode_Address) - { - 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); - 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(arena, memread_expr, expr); - result = memread_expr; - } - - // rjf: bitfields -> shift & mask - E2_TypeKey core_type_key = e2_type_key_undecorate(expr->type_key); - if(e2_type_kind_from_key(core_type_key) == E2_TypeKind_Bitfield) - { - // rjf: unpack bitfield params - U64 shift = e2_shift_from_type_key(result->type_key); - U64 mask_count = e2_mask_count_from_type_key(result->type_key); - U64 valid_bits_mask = 0; - for EachIndex(idx, mask_count) - { - valid_bits_mask |= (1ull<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(arena, result, expr); - } - return result; -} - -internal E2_Expr * -e2_expr_convert_if_possible(Arena *arena, E2_Expr *expr, E2_TypeKey dst_type_key) -{ - E2_Expr *result = expr; - { - // rjf: unpack src / dst types - E2_TypeKey src_type_key = expr->type_key; - E2_TypeKind src_type_kind = e2_type_kind_from_key(src_type_key); - E2_TypeKind dst_type_kind = e2_type_kind_from_key(dst_type_key); - RDI_EvalTypeGroup src_type_group = e2_type_group_from_kind(src_type_kind); - RDI_EvalTypeGroup dst_type_group = e2_type_group_from_kind(dst_type_kind); - U64 src_byte_size = e2_byte_size_from_type_key(src_type_key); - U64 dst_byte_size = e2_byte_size_from_type_key(dst_type_key); - - // rjf: convert from src -> dst - 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->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(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); - } - } - return result; -} - -internal E2_Expr * -e2_expr_type(Arena *arena, E2_TypeKey type_key) -{ - E2_Expr *expr = e2_expr(arena); - expr->type_key = type_key; - expr->mode = E2_Mode_Type; - return expr; -} -#endif //////////////////////////////// //~ rjf: Expression Constructors @@ -1801,7 +1596,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, String8 token_string = str8_substr(string, token.range); for EachIndex(idx, lang_info->expr_kind_parse_infos_count) { - if(lang_info->expr_kind_parse_infos[idx].parse_kind == E2_ExprParseKind_UnaryPrefix && + 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, lang_info->expr_kind_parse_infos[idx].pre, 0)) { @@ -1826,11 +1621,11 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, task = push_array_no_zero(arena, E2_ParseTask, 1); } MemoryZeroStruct(task); - task->src_range = token.range; - task->child_count_target = 1; - task->expr_kind = expr_kind; - task->max_precedence = 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); } @@ -1878,7 +1673,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } MemoryZeroStruct(task); task->src_range = token.range; - task->child_count_target = 1; + 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; @@ -1923,7 +1718,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } MemoryZeroStruct(task); task->src_range = token.range; - task->child_count_target = 1; + 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; @@ -1963,7 +1758,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, String8 token_string = str8_substr(string, token.range); for EachIndex(idx, lang_info->expr_kind_parse_infos_count) { - if(lang_info->expr_kind_parse_infos[idx].parse_kind == E2_ExprParseKind_UnaryPrefix && + 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)) { @@ -1989,7 +1784,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } MemoryZeroStruct(task); task->src_range = token.range; - task->child_count_target = 1; + 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); @@ -2067,12 +1862,55 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, expr->string = raw_string; } - //- rjf: extend parsed expression tree with trailing operators + //- rjf: extend parsed expression tree with completed trailing type operators (right-to-left) if(!state->caller_info_completes_task && expr != &e2_expr_nil) { U64 trailing_symbol_off = off; if(e2_try_token(lang, string, E2_TokenKind_Symbol, s(""), &trailing_symbol_off, &token)) { + B32 is_expr_type = (expr->kind == E2_ExprKind_TypeIdentifier || + expr->kind == E2_ExprKind_Ptr || + (state->top_task != 0 && state->top_task->expr_kind == E2_ExprKind_Cast)); + if(is_expr_type) + { + // rjf: token string -> operator kind + E2_ExprKind expr_kind = E2_ExprKind_Null; + { + 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] && + str8_match(op_info->post, token_string, 0)) + { + expr_kind = op_info->expr_kind; + break; + } + } + } + + // rjf: got an expression kind -> build new operator node + if(expr_kind != E2_ExprKind_Null) + { + E2_Expr *op_expr = e2_expr(arena, expr_kind); + e2_expr_push_child(arena, op_expr, expr); + expr = op_expr; + off = trailing_symbol_off; + } + } + } + } + + //- rjf: extend parsed expression tree with trailing operators + if(!state->caller_info_completes_task && expr != &e2_expr_nil) + { + U64 trailing_symbol_off = off; + 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_ExprKind expr_kind = E2_ExprKind_Null; U64 child_count_target = 2; @@ -2080,24 +1918,22 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, String8 closer = {0}; B32 splitter_is_required = 0; S64 precedence = 0; + B32 reverse_children = 0; { String8 token_string = str8_substr(string, token.range); for EachIndex(idx, lang_info->expr_kind_parse_infos_count) { 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)) + if(expr_kind_info->precedence <= max_precedence && + str8_match(token_string, expr_kind_info->sep, 0) && + (!!expr_is_type == !!e2_expr_kind_is_type_expr_table[expr_kind_info->expr_kind])) { - switch(expr_kind_info->parse_kind) - { - default:{}break; - case E2_ExprParseKind_Binary: {child_count_target = 2;}break; - case E2_ExprParseKind_Ternary: {child_count_target = 3; splitter_is_required = 1;}break; - case E2_ExprParseKind_Call: {child_count_target = max_U64;}break; - } 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; } } @@ -2120,6 +1956,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, task->child_count = 0; task->child_count_target = child_count_target; task->expr_kind = expr_kind; + task->reverse_children = reverse_children; task->max_precedence = precedence; task->expected_splitter = splitter; task->expected_closer = closer; @@ -2141,7 +1978,14 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, if(!state->caller_info_completes_task) { E2_ExprNode *n = push_array(arena, E2_ExprNode, 1); - SLLQueuePush(state->top_task->first_child, state->top_task->last_child, n); + if(state->top_task->reverse_children) + { + SLLQueuePushFront(state->top_task->first_child, state->top_task->last_child, n); + } + else + { + SLLQueuePush(state->top_task->first_child, state->top_task->last_child, n); + } n->v = expr; state->top_task->child_count += 1; } @@ -2165,9 +2009,19 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, 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->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(closer_found && state->top_task->child_count == 1) + if(!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; @@ -2193,7 +2047,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, //- 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(is_cast) + 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; @@ -2217,8 +2071,8 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, MemoryZeroStruct(task); task->src_range = type_expr->src_range; task->child_count = 1; - task->child_count_target = 2; - task->expr_kind = E2_ExprKind_CCast; + 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; @@ -2319,11 +2173,12 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, { if(lang_info->expr_kind_parse_infos[idx].expr_kind == t->expr_kind) { - if(lang_info->expr_kind_parse_infos[idx].parse_kind == E2_ExprParseKind_Binary) + 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(lang_info->expr_kind_parse_infos[idx].parse_kind == E2_ExprParseKind_UnaryPrefix) + else if(target_operand_count == 1) { symbol = lang_info->expr_kind_parse_infos[idx].pre; } @@ -2468,6 +2323,35 @@ e2_compile_from_expr(Arena *arena, E2_CompileState *state, E2_IRNode *resolve_re } }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: leaf numerics case E2_ExprKind_Numeric: { @@ -2652,7 +2536,7 @@ e2_compile_from_expr(Arena *arena, E2_CompileState *state, E2_IRNode *resolve_re }break; //- rjf: casts - case E2_ExprKind_CCast: + case E2_ExprKind_Cast: cast:; { E2_IRNode *cast_type_ir = lhs; @@ -2661,6 +2545,11 @@ e2_compile_from_expr(Arena *arena, E2_CompileState *state, E2_IRNode *resolve_re { e2_msgf(arena, &compile.msgs, e->src_range, "Cannot cast to this type."); } + else if(e2_type_kind_is_ptr_or_ref(e2_type_kind_from_key(cast_type_key))) + { + finished_root = rhs; + rhs->mode = E2_Mode_Address; + } else { E2_IRNode *castee_ir = rhs; diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index 758a43ac..3b1aa869 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -10,11 +10,8 @@ typedef enum E2_ExprParseKind { E2_ExprParseKind_Null, - E2_ExprParseKind_UnaryPrefix, - E2_ExprParseKind_UnaryPostfix, - E2_ExprParseKind_Binary, - E2_ExprParseKind_Ternary, - E2_ExprParseKind_Call, + E2_ExprParseKind_Prefix, + E2_ExprParseKind_Postfix, } E2_ExprParseKind; @@ -67,6 +64,7 @@ typedef enum E2_CompileStatus //- rjf: caller-provided info E2_CompileStatus_MissedIdentifierResolution, E2_CompileStatus_NewIdentifierDefinition, + E2_CompileStatus_NewCtxID, E2_CompileStatus_MemberAccess, E2_CompileStatus_IndexAccess, E2_CompileStatus_Call, @@ -345,6 +343,7 @@ struct E2_ParseTask E2_ExprNode *last_child; U64 child_count; U64 child_count_target; + B32 reverse_children; S64 max_precedence; E2_ExprKind expr_kind; String8 identifier; @@ -448,6 +447,7 @@ 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; @@ -632,17 +632,6 @@ internal E2_TypeKey e2_coerced_type_key_from_operands(E2_TypeKey lhs, E2_TypeKey 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); -#if 0 -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 E2_Expr *e2_expr_type(Arena *arena, E2_TypeKey type_key); -#endif //////////////////////////////// //~ rjf: IR Tree Constructors diff --git a/src/eval2/eval2.mdesk b/src/eval2/eval2.mdesk index 50b986a3..9392574c 100644 --- a/src/eval2/eval2.mdesk +++ b/src/eval2/eval2.mdesk @@ -1,50 +1,51 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -@table(name) +@table(name is_type_expression is_first_operand_type_maybe target_operand_count) E2_ExprKindTable: { - {Identifier} - {MacroArg} - {TypeIdentifier} - {Numeric} - {StringLiteral} - {CharLiteral} - {Dot} - {Index} - {Call} - {DerefAsm} - {SizeOf} - {TypeOf} - {CCast} - {Deref} - {Address} - {Pos} - {Neg} - {LogNot} - {BitNot} - {Ptr} - {Mul} - {Div} - {Mod} - {Add} - {Sub} - {LShift} - {RShift} - {Less} - {LtEq} - {Grtr} - {GrEq} - {EqEq} - {NtEq} - {BitAnd} - {BitXor} - {BitOr} - {LogAnd} - {LogOr} - {Define} - {Macro} - {Cond} + {Identifier 0 0 0 } + {MacroArg 0 0 0 } + {TypeIdentifier 1 0 0 } + {Ptr 1 1 1 } + {Array 1 1 2 } + {Numeric 0 0 0 } + {StringLiteral 0 0 0 } + {CharLiteral 0 0 0 } + {Dot 0 0 1 } + {Index 0 0 2 } + {Call 0 0 0xffffffffffffffffull} + {DerefAsm 0 0 1 } + {SizeOf 0 1 1 } + {TypeOf 1 1 1 } + {Cast 0 1 2 } + {Deref 0 0 1 } + {Address 0 0 1 } + {Pos 0 0 1 } + {Neg 0 0 1 } + {LogNot 0 0 1 } + {BitNot 0 0 1 } + {Mul 0 0 2 } + {Div 0 0 2 } + {Mod 0 0 2 } + {Add 0 0 2 } + {Sub 0 0 2 } + {LShift 0 0 2 } + {RShift 0 0 2 } + {Less 0 0 2 } + {LtEq 0 0 2 } + {Grtr 0 0 2 } + {GrEq 0 0 2 } + {EqEq 0 0 2 } + {NtEq 0 0 2 } + {BitAnd 0 0 2 } + {BitXor 0 0 2 } + {BitOr 0 0 2 } + {LogAnd 0 0 2 } + {LogOr 0 0 2 } + {Define 0 0 1 } + {Macro 0 0 1 } + {Cond 0 0 3 } } @enum E2_ExprKind: @@ -54,11 +55,30 @@ E2_ExprKindTable: COUNT, } +@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`; @@ -87,48 +107,51 @@ E2_LangKindTable: @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) +@table(name parse_kind precedence pre sep pos chain reverse_children) E2_ExprKindParseInfoTable_CLike: { - {Index Binary 1 "" "[" "]" "" } - {Call Call 1 "" "(" ")" ","} - {DerefAsm UnaryPrefix 1 "[" "" "]" "" } - {SizeOf UnaryPrefix 1 "sizeof " "" "" "" } - {TypeOf UnaryPrefix 1 "typeof " "" "" "" } - {SizeOf UnaryPrefix 1 "size_of " "" "" "" } - {TypeOf UnaryPrefix 1 "type_of " "" "" "" } - {CCast UnaryPrefix 1 "" "" "" "" } - {Deref UnaryPrefix 2 "*" "" "" "" } - {Address UnaryPrefix 2 "&" "" "" "" } - {Pos UnaryPrefix 2 "+" "" "" "" } - {Neg UnaryPrefix 2 "-" "" "" "" } - {LogNot UnaryPrefix 2 "!" "" "" "" } - {BitNot UnaryPrefix 2 "~" "" "" "" } - {Ptr UnaryPostfix 1 "" "" "*" "" } - {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 "" "||" "" "" } - {Cond Ternary 14 "" "?" "" ":"} + {Index Postfix 1 "" "[" "]" "" 0} + {Call Postfix 1 "" "(" ")" "," 0} + {DerefAsm Prefix 1 "[" "" "]" "" 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), str8_lit_comp("$(a.pre)"), str8_lit_comp("$(a.sep)"), str8_lit_comp("$(a.pos)"), str8_lit_comp("$(a.chain)")}` + @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 5a9c842c..7f578104 100644 --- a/src/eval2/generated/eval2.meta.c +++ b/src/eval2/generated/eval2.meta.c @@ -4,47 +4,191 @@ //- GENERATED CODE C_LINKAGE_BEGIN +B8 e2_expr_kind_is_type_expr_table[43] = +{ +0, +0, +0, +1, +1, +1, +0, +0, +0, +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[43] = +{ +0, +0, +0, +0, +1, +1, +0, +0, +0, +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[43] = +{ +0, +0, +0, +0, +1, +2, +0, +0, +0, +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[34] = +E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[37] = { -{E2_ExprKind_Index, E2_ExprParseKind_Binary, 1, str8_lit_comp(""), str8_lit_comp("["), str8_lit_comp("]"), str8_lit_comp("")}, -{E2_ExprKind_Call, E2_ExprParseKind_Call, 1, str8_lit_comp(""), str8_lit_comp("("), str8_lit_comp(")"), str8_lit_comp(",")}, -{E2_ExprKind_DerefAsm, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("["), str8_lit_comp(""), str8_lit_comp("]"), str8_lit_comp("")}, -{E2_ExprKind_SizeOf, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("sizeof "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_TypeOf, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("typeof "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_SizeOf, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("size_of "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_TypeOf, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp("type_of "), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_CCast, E2_ExprParseKind_UnaryPrefix, 1, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_Deref, E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("*"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_Address, E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("&"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_Pos, E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("+"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_Neg, E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("-"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_LogNot, E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("!"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_BitNot, E2_ExprParseKind_UnaryPrefix, 2, str8_lit_comp("~"), str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_Ptr, E2_ExprParseKind_UnaryPostfix, 1, str8_lit_comp(""), str8_lit_comp(""), str8_lit_comp("*"), str8_lit_comp("")}, -{E2_ExprKind_Mul, E2_ExprParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("*"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_Div, E2_ExprParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("/"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_Mod, E2_ExprParseKind_Binary, 3, str8_lit_comp(""), str8_lit_comp("%"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_Add, E2_ExprParseKind_Binary, 4, str8_lit_comp(""), str8_lit_comp("+"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_Sub, E2_ExprParseKind_Binary, 4, str8_lit_comp(""), str8_lit_comp("-"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_LShift, E2_ExprParseKind_Binary, 5, str8_lit_comp(""), str8_lit_comp("<<"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_RShift, E2_ExprParseKind_Binary, 5, str8_lit_comp(""), str8_lit_comp(">>"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_Less, E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp("<"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_LtEq, E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp("<="), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_Grtr, E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp(">"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_GrEq, E2_ExprParseKind_Binary, 6, str8_lit_comp(""), str8_lit_comp(">="), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_EqEq, E2_ExprParseKind_Binary, 7, str8_lit_comp(""), str8_lit_comp("=="), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_NtEq, E2_ExprParseKind_Binary, 7, str8_lit_comp(""), str8_lit_comp("!="), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_BitAnd, E2_ExprParseKind_Binary, 8, str8_lit_comp(""), str8_lit_comp("&"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_BitXor, E2_ExprParseKind_Binary, 9, str8_lit_comp(""), str8_lit_comp("^"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_BitOr, E2_ExprParseKind_Binary, 10, str8_lit_comp(""), str8_lit_comp("|"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_LogAnd, E2_ExprParseKind_Binary, 11, str8_lit_comp(""), str8_lit_comp("&&"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_LogOr, E2_ExprParseKind_Binary, 12, str8_lit_comp(""), str8_lit_comp("||"), str8_lit_comp(""), str8_lit_comp("")}, -{E2_ExprKind_Cond, E2_ExprParseKind_Ternary, 14, 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_DerefAsm, E2_ExprParseKind_Prefix, 1, 0, str8_lit_comp("["), 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 bc3638b5..5995845e 100644 --- a/src/eval2/generated/eval2.meta.h +++ b/src/eval2/generated/eval2.meta.h @@ -12,6 +12,8 @@ E2_ExprKind_Null, E2_ExprKind_Identifier, E2_ExprKind_MacroArg, E2_ExprKind_TypeIdentifier, +E2_ExprKind_Ptr, +E2_ExprKind_Array, E2_ExprKind_Numeric, E2_ExprKind_StringLiteral, E2_ExprKind_CharLiteral, @@ -21,14 +23,13 @@ E2_ExprKind_Call, E2_ExprKind_DerefAsm, E2_ExprKind_SizeOf, E2_ExprKind_TypeOf, -E2_ExprKind_CCast, +E2_ExprKind_Cast, E2_ExprKind_Deref, E2_ExprKind_Address, E2_ExprKind_Pos, E2_ExprKind_Neg, E2_ExprKind_LogNot, E2_ExprKind_BitNot, -E2_ExprKind_Ptr, E2_ExprKind_Mul, E2_ExprKind_Div, E2_ExprKind_Mod, @@ -142,6 +143,7 @@ struct E2_ExprKindParseInfo E2_ExprKind expr_kind; E2_ExprParseKind parse_kind; S64 precedence; +B8 reverse_children; String8 pre; String8 sep; String8 post; @@ -156,8 +158,11 @@ E2_ExprKindParseInfo *expr_kind_parse_infos; }; C_LINKAGE_BEGIN +extern B8 e2_expr_kind_is_type_expr_table[43]; +extern B8 e2_expr_kind_is_first_operand_type_maybe_table[43]; +extern U64 e2_expr_kind_target_operand_count_table[43]; extern E2_LangInfo e2_lang_kind_info_table[1]; -extern E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[34]; +extern E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[37]; extern U8 e2_type_kind_basic_byte_size_table[61]; extern String8 e2_type_kind_basic_string_table[61]; diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index 3c0994de..02673e0f 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -43,6 +43,11 @@ entry_point(CmdLine *cmdline) // (A) // int & B // (1 + (int)&B) + s("int32[100]"), + s("123 as float32"), + s("cast float32 123"), + s("cast (float32) 123"), + s("(int32 *)123"), s("bar(a, b) = a + b"), s("123, 456"), s("int32 == int32"), From fdbcf7ccdcedb5b1917355425d794190a6b046a1 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 26 Jun 2026 16:19:19 -0700 Subject: [PATCH 17/90] eval2: first pass at c recursive type expression parsing --- src/eval2/eval2.c | 201 ++++++++++++++++++++++++------- src/eval2/eval2.h | 7 ++ src/eval2/eval2.mdesk | 93 +++++++------- src/eval2/generated/eval2.meta.c | 50 +++++++- src/eval2/generated/eval2.meta.h | 3 +- src/scratch/ryan_scratch.c | 39 +++--- 6 files changed, 285 insertions(+), 108 deletions(-) diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index e1aecb5a..86807f60 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -1070,6 +1070,21 @@ e2_expr_push_child(Arena *arena, E2_Expr *parent, E2_Expr *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 @@ -1539,14 +1554,26 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, E2_Parse parse = {E2_ParseStatus_Error, .expr = &e2_expr_nil, .params_expr = &e2_expr_nil}; 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 E2_Expr *expr = &e2_expr_nil; 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); + 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 @@ -1638,7 +1665,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } //- rjf: identifiers with an active dot op kind -> member access - else if(need_new_expr && state->top_task != 0 && state->top_task->expr_kind == E2_ExprKind_Dot && e2_try_token(lang, 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)) { expr = e2_expr(arena, E2_ExprKind_Dot); expr->first_child = state->top_task->first_child; @@ -1656,7 +1683,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, { String8 identifier = str8_substr(string, token.range); B32 identifier_mapped = 0; - B32 definitions_are_allowed = (state->top_task == 0 || state->top_task->expr_kind != E2_ExprKind_Macro); + B32 definitions_are_allowed = (state->top_task->expr_kind != E2_ExprKind_Macro); // rjf: first try to resolve as a definition if(definitions_are_allowed && !identifier_mapped && e2_try_token(lang, string, E2_TokenKind_Symbol, s("="), &off, 0)) @@ -1792,7 +1819,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } // rjf: try to resolve it as a macro argument - if(!identifier_mapped && state->top_task != 0 && state->top_task->expr_kind == E2_ExprKind_Macro) + if(!identifier_mapped && state->top_task->expr_kind == E2_ExprKind_Macro) { U64 macro_arg_num = 0; { @@ -1862,16 +1889,17 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, expr->string = raw_string; } - //- rjf: extend parsed expression tree with completed trailing type operators (right-to-left) - if(!state->caller_info_completes_task && expr != &e2_expr_nil) + //- 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)) { - B32 is_expr_type = (expr->kind == E2_ExprKind_TypeIdentifier || - expr->kind == E2_ExprKind_Ptr || - (state->top_task != 0 && state->top_task->expr_kind == E2_ExprKind_Cast)); - if(is_expr_type) + 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) { // rjf: token string -> operator kind E2_ExprKind expr_kind = E2_ExprKind_Null; @@ -1893,7 +1921,10 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, if(expr_kind != E2_ExprKind_Null) { E2_Expr *op_expr = e2_expr(arena, expr_kind); - e2_expr_push_child(arena, op_expr, expr); + if(expr != &e2_expr_nil) + { + e2_expr_push_child(arena, op_expr, expr); + } expr = op_expr; off = trailing_symbol_off; } @@ -1901,6 +1932,38 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } } + //- 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) + { + 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)) + { + 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; + } + } + //- rjf: extend parsed expression tree with trailing operators if(!state->caller_info_completes_task && expr != &e2_expr_nil) { @@ -1926,7 +1989,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, 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_is_type_expr_table[expr_kind_info->expr_kind])) + (!expr_is_type || e2_expr_kind_allow_type_operands_table[expr_kind_info->expr_kind])) { splitter = expr_kind_info->chain; closer = expr_kind_info->post; @@ -1939,7 +2002,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } } - // rjf: non-null operator kind -> build binary expression node, push task to fill other child + // 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; @@ -1959,6 +2022,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, 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); @@ -1974,6 +2038,18 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, //- rjf: pop finished expressions for(B32 task_pushed = 0; state->top_task != 0 && !done && !task_pushed;) { + //- 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) + { + 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) { @@ -1992,7 +2068,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, //- rjf: consume expected splitters if(!state->caller_info_completes_task && - state->top_task->child_count > 1 && + 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) { @@ -2011,7 +2087,8 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, //- rjf: determine if the task above a parenthesized sub-expression is expecting a type B32 parent_looking_for_type = 0; - if(state->top_task->next != 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]) @@ -2021,7 +2098,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, //- rjf: determine if first child of a parenthesized sub-expression is a type B32 parenthesized_child_is_type = 0; - if(!parent_looking_for_type && closer_found && state->top_task->child_count == 1) + 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; @@ -2094,11 +2171,18 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, //- rjf: produced finished expression tree, given all children E2_Expr *finished_root = &e2_expr_nil; { - 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; + 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 @@ -2107,6 +2191,25 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, 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) { @@ -2114,14 +2217,25 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, SLLStackPush(state->free_task, completed_task); } - //- rjf: work on the parent of the finished expression next - expr = finished_root; + //- rjf: if this task still has potential right-hand-sides, then we need to + // break out of the pop-loop and keep parsing at this level. + if(state->top_task->expr_kind == E2_ExprKind_Null || completed_task->do_type_ancestors) + { + break; + } } - //- rjf: if the top task is not done -> break & continue - reset expression, because we need to parse another. + //- rjf: if the top task is not done -> break & continue. + // + // 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 { - expr = &e2_expr_nil; + if(state->top_task->expr_kind != E2_ExprKind_Null) + { + expr = &e2_expr_nil; + } break; } } @@ -2131,16 +2245,13 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, if(off >= string.size || (off == start_off && !e2_parse_status_is_caller_request(parse.status))) { done = 1; - if(state->top_task == 0) + if(expr != &e2_expr_nil) { - if(parse.expr == &e2_expr_nil && expr != &e2_expr_nil) - { - parse.expr = expr; - } - if(parse.status == E2_ParseStatus_Error) - { - parse.status = E2_ParseStatus_Good; - } + parse.expr = expr; + } + if(parse.status == E2_ParseStatus_Error) + { + parse.status = E2_ParseStatus_Good; } } } @@ -2164,7 +2275,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, //- 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) { String8 symbol = {0}; if(t->expr_kind != E2_ExprKind_Null) @@ -2410,6 +2521,7 @@ e2_compile_from_expr(Arena *arena, E2_CompileState *state, E2_IRNode *resolve_re 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 @@ -2545,16 +2657,8 @@ e2_compile_from_expr(Arena *arena, E2_CompileState *state, E2_IRNode *resolve_re { e2_msgf(arena, &compile.msgs, e->src_range, "Cannot cast to this type."); } - else if(e2_type_kind_is_ptr_or_ref(e2_type_kind_from_key(cast_type_key))) - { - finished_root = rhs; - rhs->mode = E2_Mode_Address; - } - else - { - E2_IRNode *castee_ir = rhs; - finished_root = e2_irnode_convert_if_possible(arena, castee_ir, cast_type_key); - } + E2_IRNode *castee_ir = rhs; + finished_root = e2_irnode_convert_if_possible(arena, castee_ir, cast_type_key); }break; //- rjf: dereferences @@ -2912,6 +3016,10 @@ e2_compile_from_expr(Arena *arena, E2_CompileState *state, E2_IRNode *resolve_re { compile_time_cond = 1; } + if(pass_ir->mode != fail_ir->mode) + { + compile_time_cond = 1; + } } // rjf: compile-time conditional -> ask caller to evaluate conditional @@ -2993,6 +3101,7 @@ e2_compile_from_expr(Arena *arena, E2_CompileState *state, E2_IRNode *resolve_re // rjf: take result finished_root = cond_root; + finished_root->mode = pass_ir->mode; } }break; } diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index 3b1aa869..6abdcea4 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -341,6 +341,7 @@ 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; @@ -349,14 +350,18 @@ struct E2_ParseTask 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; @@ -632,6 +637,8 @@ internal E2_TypeKey e2_coerced_type_key_from_operands(E2_TypeKey lhs, E2_TypeKey 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 diff --git a/src/eval2/eval2.mdesk b/src/eval2/eval2.mdesk index 9392574c..359d1f21 100644 --- a/src/eval2/eval2.mdesk +++ b/src/eval2/eval2.mdesk @@ -1,51 +1,51 @@ // Copyright (c) Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) -@table(name is_type_expression is_first_operand_type_maybe target_operand_count) +@table(name allow_type_operands is_type_expression is_first_operand_type_maybe target_operand_count) E2_ExprKindTable: { - {Identifier 0 0 0 } - {MacroArg 0 0 0 } - {TypeIdentifier 1 0 0 } - {Ptr 1 1 1 } - {Array 1 1 2 } - {Numeric 0 0 0 } - {StringLiteral 0 0 0 } - {CharLiteral 0 0 0 } - {Dot 0 0 1 } - {Index 0 0 2 } - {Call 0 0 0xffffffffffffffffull} - {DerefAsm 0 0 1 } - {SizeOf 0 1 1 } - {TypeOf 1 1 1 } - {Cast 0 1 2 } - {Deref 0 0 1 } - {Address 0 0 1 } - {Pos 0 0 1 } - {Neg 0 0 1 } - {LogNot 0 0 1 } - {BitNot 0 0 1 } - {Mul 0 0 2 } - {Div 0 0 2 } - {Mod 0 0 2 } - {Add 0 0 2 } - {Sub 0 0 2 } - {LShift 0 0 2 } - {RShift 0 0 2 } - {Less 0 0 2 } - {LtEq 0 0 2 } - {Grtr 0 0 2 } - {GrEq 0 0 2 } - {EqEq 0 0 2 } - {NtEq 0 0 2 } - {BitAnd 0 0 2 } - {BitXor 0 0 2 } - {BitOr 0 0 2 } - {LogAnd 0 0 2 } - {LogOr 0 0 2 } - {Define 0 0 1 } - {Macro 0 0 1 } - {Cond 0 0 3 } + {Identifier 0 0 0 0 } + {MacroArg 0 0 0 0 } + {TypeIdentifier 0 1 0 0 } + {Ptr 1 1 1 1 } + {Array 1 1 1 2 } + {Numeric 0 0 0 0 } + {StringLiteral 0 0 0 0 } + {CharLiteral 0 0 0 0 } + {Dot 1 0 0 1 } + {Index 0 0 0 2 } + {Call 1 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 1 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_ExprKind: @@ -55,6 +55,12 @@ E2_ExprKindTable: COUNT, } +@data(B8) e2_expr_kind_allow_type_operands_table: +{ + 0, + @expand(E2_ExprKindTable a) `$(a.allow_type_operands)` +} + @data(B8) e2_expr_kind_is_type_expr_table: { 0, @@ -110,6 +116,7 @@ E2_LangKindTable: @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} {DerefAsm Prefix 1 "[" "" "]" "" 0} diff --git a/src/eval2/generated/eval2.meta.c b/src/eval2/generated/eval2.meta.c index 7f578104..ead6d5f3 100644 --- a/src/eval2/generated/eval2.meta.c +++ b/src/eval2/generated/eval2.meta.c @@ -4,6 +4,53 @@ //- GENERATED CODE C_LINKAGE_BEGIN +B8 e2_expr_kind_allow_type_operands_table[43] = +{ +0, +0, +0, +0, +1, +1, +0, +0, +0, +1, +0, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +1, +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[43] = { 0, @@ -150,8 +197,9 @@ 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[37] = +E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[38] = { +{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_DerefAsm, E2_ExprParseKind_Prefix, 1, 0, str8_lit_comp("["), str8_lit_comp(""), str8_lit_comp("]"), str8_lit_comp("")}, diff --git a/src/eval2/generated/eval2.meta.h b/src/eval2/generated/eval2.meta.h index 5995845e..4a974d85 100644 --- a/src/eval2/generated/eval2.meta.h +++ b/src/eval2/generated/eval2.meta.h @@ -158,11 +158,12 @@ E2_ExprKindParseInfo *expr_kind_parse_infos; }; C_LINKAGE_BEGIN +extern B8 e2_expr_kind_allow_type_operands_table[43]; extern B8 e2_expr_kind_is_type_expr_table[43]; extern B8 e2_expr_kind_is_first_operand_type_maybe_table[43]; extern U64 e2_expr_kind_target_operand_count_table[43]; extern E2_LangInfo e2_lang_kind_info_table[1]; -extern E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[37]; +extern E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[38]; extern U8 e2_type_kind_basic_byte_size_table[61]; extern String8 e2_type_kind_basic_string_table[61]; diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index 02673e0f..647b16e3 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -43,46 +43,51 @@ entry_point(CmdLine *cmdline) // (A) // int & B // (1 + (int)&B) - s("int32[100]"), + + // TODO(rjf): this is now busted, `mul` root comes out - related to always having a top task maybe? + s("3 * 4 + 2"), + s("(3 * 4) + 2"), + 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("int32 (*) [100]"), + s("int32[100]"), s("cast float32 123"), s("cast (float32) 123"), s("(int32 *)123"), s("bar(a, b) = a + b"), - s("123, 456"), - s("int32 == int32"), s("foo = 123"), s("1 > 2"), + s("1 ? 123 : 456"), + s("0 ? 123 : 456"), s("1 ? \"Test\" : 888"), s("'a'"), s("'b'"), - s("float32(111)"), - s("float64(111)"), - s("222.f"), - s("222.0"), s("(float32)222"), s("(float64)222"), - s("[123]"), - s("foo"), - s("foo.bar"), - s("123[456]"), + 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("0 ? 123 : 456"), s("123(1, 2, 3)"), +#if 0 + // s("float32(111)"), + // s("float64(111)"), +#endif }; for EachElement(idx, strings) { From 773e6554dcb182ba333baa24f390e342a3cbdecc Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 26 Jun 2026 20:35:50 -0700 Subject: [PATCH 18/90] cycle detection in fully qualified string generation --- src/rdi/rdi_local.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) 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) From b704e797e2227018ecf094287fad7c591bc4254a Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Sat, 27 Jun 2026 03:50:17 -0700 Subject: [PATCH 19/90] notes --- src/raddbg/raddbg_main.c | 9 +++++++++ 1 file changed, 9 insertions(+) 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 From cae975409972700a51b9efb68eac716ec44e521d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Sat, 27 Jun 2026 04:25:05 -0700 Subject: [PATCH 20/90] eval2: fix parsing task popping rules, still give chance to rhs when popping tasks in all cases --- src/eval2/eval2.c | 408 +++++++++++++++---------------- src/eval2/eval2.mdesk | 3 +- src/eval2/generated/eval2.meta.c | 14 +- src/eval2/generated/eval2.meta.h | 9 +- src/scratch/ryan_scratch.c | 7 +- 5 files changed, 217 insertions(+), 224 deletions(-) diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index 86807f60..90fd4cf0 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -1571,6 +1571,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, for(B32 done = 0; !done;) { U64 start_off = off; + 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); @@ -1615,22 +1616,27 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, //- rjf: symbols (possible prefix unaries, *or* unexpected) 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 + 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 EachIndex(idx, lang_info->expr_kind_parse_infos_count) { 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, lang_info->expr_kind_parse_infos[idx].pre, 0)) { - 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; + 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; + } } } } @@ -1657,9 +1663,8 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } // rjf: report unexpected symbols - if(expr_kind == E2_ExprKind_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); } } @@ -1935,7 +1940,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, //- 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) + 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 && @@ -2031,218 +2036,201 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } } - //- 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(B32 task_pushed = 0; state->top_task != 0 && !done && !task_pushed;) + //- 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: 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) + 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_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); + SLLQueuePushFront(state->top_task->first_child, state->top_task->last_child, n); } - - //- 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) - { - SLLQueuePushFront(state->top_task->first_child, state->top_task->last_child, n); - } - else - { - 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: 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(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) - { - 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; - } - } - } - - //- 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; - task_pushed = 1; - } - } - - //- 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 this task still has potential right-hand-sides, then we need to - // break out of the pop-loop and keep parsing at this level. - if(state->top_task->expr_kind == E2_ExprKind_Null || completed_task->do_type_ancestors) - { - break; - } - } - - //- rjf: if the top task is not done -> break & continue. - // - // 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; - } - 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: 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(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) + { + 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; + } + } + } + + //- 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, *or* if we made no progress. - if(off >= string.size || (off == start_off && !e2_parse_status_is_caller_request(parse.status))) + //- 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) diff --git a/src/eval2/eval2.mdesk b/src/eval2/eval2.mdesk index 359d1f21..da5bf16f 100644 --- a/src/eval2/eval2.mdesk +++ b/src/eval2/eval2.mdesk @@ -9,12 +9,13 @@ E2_ExprKindTable: {TypeIdentifier 0 1 0 0 } {Ptr 1 1 1 1 } {Array 1 1 1 2 } + {Function 1 1 1 0xffffffffffffffffull} {Numeric 0 0 0 0 } {StringLiteral 0 0 0 0 } {CharLiteral 0 0 0 0 } {Dot 1 0 0 1 } {Index 0 0 0 2 } - {Call 1 0 0 0xffffffffffffffffull} + {Call 0 0 0 0xffffffffffffffffull} {DerefAsm 1 0 0 1 } {SizeOf 1 0 1 1 } {TypeOf 1 1 1 1 } diff --git a/src/eval2/generated/eval2.meta.c b/src/eval2/generated/eval2.meta.c index ead6d5f3..00740fe6 100644 --- a/src/eval2/generated/eval2.meta.c +++ b/src/eval2/generated/eval2.meta.c @@ -4,7 +4,7 @@ //- GENERATED CODE C_LINKAGE_BEGIN -B8 e2_expr_kind_allow_type_operands_table[43] = +B8 e2_expr_kind_allow_type_operands_table[44] = { 0, 0, @@ -12,12 +12,13 @@ B8 e2_expr_kind_allow_type_operands_table[43] = 0, 1, 1, +1, 0, 0, 0, 1, 0, -1, +0, 1, 1, 1, @@ -51,7 +52,7 @@ B8 e2_expr_kind_allow_type_operands_table[43] = 1, }; -B8 e2_expr_kind_is_type_expr_table[43] = +B8 e2_expr_kind_is_type_expr_table[44] = { 0, 0, @@ -59,6 +60,7 @@ B8 e2_expr_kind_is_type_expr_table[43] = 1, 1, 1, +1, 0, 0, 0, @@ -98,7 +100,7 @@ B8 e2_expr_kind_is_type_expr_table[43] = 0, }; -B8 e2_expr_kind_is_first_operand_type_maybe_table[43] = +B8 e2_expr_kind_is_first_operand_type_maybe_table[44] = { 0, 0, @@ -106,6 +108,7 @@ B8 e2_expr_kind_is_first_operand_type_maybe_table[43] = 0, 1, 1, +1, 0, 0, 0, @@ -145,7 +148,7 @@ B8 e2_expr_kind_is_first_operand_type_maybe_table[43] = 0, }; -U64 e2_expr_kind_target_operand_count_table[43] = +U64 e2_expr_kind_target_operand_count_table[44] = { 0, 0, @@ -153,6 +156,7 @@ U64 e2_expr_kind_target_operand_count_table[43] = 0, 1, 2, +0xffffffffffffffffull, 0, 0, 0, diff --git a/src/eval2/generated/eval2.meta.h b/src/eval2/generated/eval2.meta.h index 4a974d85..355864fb 100644 --- a/src/eval2/generated/eval2.meta.h +++ b/src/eval2/generated/eval2.meta.h @@ -14,6 +14,7 @@ E2_ExprKind_MacroArg, E2_ExprKind_TypeIdentifier, E2_ExprKind_Ptr, E2_ExprKind_Array, +E2_ExprKind_Function, E2_ExprKind_Numeric, E2_ExprKind_StringLiteral, E2_ExprKind_CharLiteral, @@ -158,10 +159,10 @@ E2_ExprKindParseInfo *expr_kind_parse_infos; }; C_LINKAGE_BEGIN -extern B8 e2_expr_kind_allow_type_operands_table[43]; -extern B8 e2_expr_kind_is_type_expr_table[43]; -extern B8 e2_expr_kind_is_first_operand_type_maybe_table[43]; -extern U64 e2_expr_kind_target_operand_count_table[43]; +extern B8 e2_expr_kind_allow_type_operands_table[44]; +extern B8 e2_expr_kind_is_type_expr_table[44]; +extern B8 e2_expr_kind_is_first_operand_type_maybe_table[44]; +extern U64 e2_expr_kind_target_operand_count_table[44]; extern E2_LangInfo e2_lang_kind_info_table[1]; extern E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[38]; extern U8 e2_type_kind_basic_byte_size_table[61]; diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index 647b16e3..f30e50ed 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -43,8 +43,9 @@ entry_point(CmdLine *cmdline) // (A) // int & B // (1 + (int)&B) - - // TODO(rjf): this is now busted, `mul` root comes out - related to always having a top task maybe? + s("int32 *"), + s("int32[100]"), + s("int32 (*) [100]"), s("3 * 4 + 2"), s("(3 * 4) + 2"), s("3 * 4"), @@ -57,8 +58,6 @@ entry_point(CmdLine *cmdline) s("123, 456"), s("222.f"), s("123 as float32"), - s("int32 (*) [100]"), - s("int32[100]"), s("cast float32 123"), s("cast (float32) 123"), s("(int32 *)123"), From 410a8e3afac12437ca10ef5ae272bca7284da97d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Sat, 27 Jun 2026 04:43:58 -0700 Subject: [PATCH 21/90] eval2: function pointer type parsing/compiling --- src/eval2/eval2.c | 35 ++++++++++++++++++++++++++++++++ src/eval2/eval2.h | 1 + src/eval2/eval2.mdesk | 3 ++- src/eval2/generated/eval2.meta.c | 5 +++-- src/eval2/generated/eval2.meta.h | 2 +- src/scratch/ryan_scratch.c | 14 +++++++------ 6 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index 90fd4cf0..2921afc8 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -269,6 +269,13 @@ e2_cons_type_node_from_params(E2_ConsTypeParams *params) 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 @@ -299,6 +306,11 @@ e2_cons_type_node_from_params(E2_ConsTypeParams *params) 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) @@ -1914,6 +1926,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, { 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; @@ -2451,6 +2464,28 @@ e2_compile_from_expr(Arena *arena, E2_CompileState *state, E2_IRNode *resolve_re } }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: leaf numerics case E2_ExprKind_Numeric: { diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index 6abdcea4..af0488ea 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -156,6 +156,7 @@ struct E2_ConsTypeParams E2_TypeKey direct; U64 count; U64 depth; + E2_TypeKey *param_types; struct E2_Expr **args; }; diff --git a/src/eval2/eval2.mdesk b/src/eval2/eval2.mdesk index da5bf16f..932ad155 100644 --- a/src/eval2/eval2.mdesk +++ b/src/eval2/eval2.mdesk @@ -26,7 +26,7 @@ E2_ExprKindTable: {Neg 1 0 0 1 } {LogNot 1 0 0 1 } {BitNot 1 0 0 1 } - {Mul 1 0 0 2 } + {Mul 0 0 0 2 } {Div 1 0 0 2 } {Mod 1 0 0 2 } {Add 1 0 0 2 } @@ -120,6 +120,7 @@ E2_ExprKindParseInfoTable_CLike: {Dot Postfix 1 "" "." "" "" 0} {Index Postfix 1 "" "[" "]" "" 0} {Call Postfix 1 "" "(" ")" "," 0} + {Function Postfix 1 "" "(" ")" "," 0} {DerefAsm Prefix 1 "[" "" "]" "" 0} {SizeOf Prefix 1 "sizeof " "" "" "" 0} {TypeOf Prefix 1 "typeof " "" "" "" 0} diff --git a/src/eval2/generated/eval2.meta.c b/src/eval2/generated/eval2.meta.c index 00740fe6..7612deae 100644 --- a/src/eval2/generated/eval2.meta.c +++ b/src/eval2/generated/eval2.meta.c @@ -29,7 +29,7 @@ B8 e2_expr_kind_allow_type_operands_table[44] = 1, 1, 1, -1, +0, 1, 1, 1, @@ -201,11 +201,12 @@ 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[38] = +E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[39] = { {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_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("")}, diff --git a/src/eval2/generated/eval2.meta.h b/src/eval2/generated/eval2.meta.h index 355864fb..fb22a0db 100644 --- a/src/eval2/generated/eval2.meta.h +++ b/src/eval2/generated/eval2.meta.h @@ -164,7 +164,7 @@ extern B8 e2_expr_kind_is_type_expr_table[44]; extern B8 e2_expr_kind_is_first_operand_type_maybe_table[44]; extern U64 e2_expr_kind_target_operand_count_table[44]; extern E2_LangInfo e2_lang_kind_info_table[1]; -extern E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[38]; +extern E2_ExprKindParseInfo e2_expr_kind_parse_info_table__clike[39]; extern U8 e2_type_kind_basic_byte_size_table[61]; extern String8 e2_type_kind_basic_string_table[61]; diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index f30e50ed..435ca84c 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -43,11 +43,15 @@ entry_point(CmdLine *cmdline) // (A) // int & B // (1 + (int)&B) - s("int32 *"), - s("int32[100]"), + s("int32(*)(int32, int32)"), +#if 0 + s("123(1, 2, 3)"), s("int32 (*) [100]"), - s("3 * 4 + 2"), 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"), @@ -59,7 +63,6 @@ entry_point(CmdLine *cmdline) s("222.f"), s("123 as float32"), s("cast float32 123"), - s("cast (float32) 123"), s("(int32 *)123"), s("bar(a, b) = a + b"), s("foo = 123"), @@ -82,8 +85,7 @@ entry_point(CmdLine *cmdline) s("123 + "), s("-123"), s("sizeof 123"), - 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 From 969851b6f0052f2ce8d85605c72788977cf606f9 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Sat, 27 Jun 2026 04:54:30 -0700 Subject: [PATCH 22/90] flip NO_ASYNC; turn on as needed, if a layer requiring it is included --- src/artifact_cache/artifact_cache.h | 3 +++ src/base/base_entry_point.c | 5 +++-- src/com_shim/com_shim_main.c | 1 - src/content/content.h | 3 +++ src/dbg_info/dbg_info.h | 3 +++ src/file_stream/file_stream.h | 3 +++ src/http/http.h | 3 +++ src/symbol_server/symbol_server.h | 4 ++++ src/torture/torture_main.c | 1 - 9 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/artifact_cache/artifact_cache.h b/src/artifact_cache/artifact_cache.h index ff61e9e0..3acc5182 100644 --- a/src/artifact_cache/artifact_cache.h +++ b/src/artifact_cache/artifact_cache.h @@ -179,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_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/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_info/dbg_info.h b/src/dbg_info/dbg_info.h index c933c200..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); //////////////////////////////// diff --git a/src/file_stream/file_stream.h b/src/file_stream/file_stream.h index 03ed9777..56e09f66 100644 --- a/src/file_stream/file_stream.h +++ b/src/file_stream/file_stream.h @@ -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/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/torture/torture_main.c b/src/torture/torture_main.c index 75cae873..4e03d65b 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 //////////////////////////////// From 0e2b4ac5e7def2c0c5e691d032dbe4b014712664 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Sat, 27 Jun 2026 06:21:31 -0700 Subject: [PATCH 23/90] ui discoverability adjustments --- project.raddbg | 2 +- src/raddbg/generated/raddbg.meta.c | 2 +- src/raddbg/raddbg.mdesk | 2 +- src/raddbg/raddbg_core.c | 4 ++-- src/raddbg/raddbg_widgets.c | 30 +++++++++++++++++++----------- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/project.raddbg b/project.raddbg index 8c9b43d1..4adbb44b 100644 --- a/project.raddbg +++ b/project.raddbg @@ -7,6 +7,7 @@ target: working_directory: "../raddebugger" arguments: "--user:C:/devel/raddebugger/build/raddbg_test.user" debug_subprocesses: 0 + enabled: 1 } target: { @@ -66,5 +67,4 @@ target: { executable: "build/ryan_scratch.exe" working_directory: build - enabled: 1 } diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index a61108aa..b8bcd0cd 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -691,7 +691,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("")}}, diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index cd700574..e5e11731 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -1049,7 +1049,7 @@ RD_CmdTable: // | | | //- 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" "" } + {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" "{tab_commands}" } {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." "" "" } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index c994b690..de46b739 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -4117,7 +4117,7 @@ rd_view_ui(Rng2F32 rect) cells_are_editable && row->eval.expr == &e_expr_nil) { - ghost_text = str8_lit("Add a expression to watch..."); + 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); } @@ -6579,7 +6579,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; diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index 4db60f77..d204e7e1 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)) From 96cfb441fd950e98e4a8ccc21acf29b8b3aa87d2 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Sat, 27 Jun 2026 12:07:24 -0700 Subject: [PATCH 24/90] correctly use command titles in native file explorer --- src/linux/window_manager/linux_window_manager.c | 2 +- src/raddbg/raddbg_core.c | 7 ++++++- src/win32/window_manager/win32_window_manager.c | 3 ++- src/window_manager/window_manager.h | 2 +- src/window_manager/window_manager_stub.c | 2 +- 5 files changed, 11 insertions(+), 5 deletions(-) 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/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index de46b739..575fcb3a 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -13106,7 +13106,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) { diff --git a/src/win32/window_manager/win32_window_manager.c b/src/win32/window_manager/win32_window_manager.c index 341b1b98..bb155677 100644 --- a/src/win32/window_manager/win32_window_manager.c +++ b/src/win32/window_manager/win32_window_manager.c @@ -1629,7 +1629,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 +1638,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.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(); } From 16c447bb79904c8b91342aad433c8f443bbb9b34 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Sun, 28 Jun 2026 07:36:24 -0700 Subject: [PATCH 25/90] eval2: leaf bytecode in ir tree --- src/eval2/eval2.c | 20 +++++++++++++++++--- src/eval2/eval2.h | 2 +- src/scratch/ryan_scratch.c | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index 2921afc8..f5e4a5c3 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -3196,9 +3196,14 @@ e2_bytecode_from_irnode(Arena *arena, E2_IRNode *irnode) E2_IRNode *ir = t->ir; //- rjf: unpack this op - U16 ctrlbits = rdi_eval_op_ctrlbits_table[ir->op]; - U64 child_count = RDI_POPN_FROM_CTRLBITS(ctrlbits); - if(ir->op == 0) + 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; } @@ -3235,6 +3240,15 @@ e2_bytecode_from_irnode(Arena *arena, E2_IRNode *irnode) { 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); diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index af0488ea..ab134099 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -26,7 +26,7 @@ E2_ExprParseKind; enum { E2_EvalOp_SetCtxID = RDI_EvalOp_COUNT, - E2_EvalOp_LeafMacroArgument, + E2_EvalOp_LeafBytecode, }; //////////////////////////////// diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index 435ca84c..1c7618a6 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -44,7 +44,6 @@ entry_point(CmdLine *cmdline) // int & B // (1 + (int)&B) s("int32(*)(int32, int32)"), -#if 0 s("123(1, 2, 3)"), s("int32 (*) [100]"), s("(3 * 4) + 2"), @@ -85,6 +84,7 @@ entry_point(CmdLine *cmdline) s("123 + "), s("-123"), s("sizeof 123"), +#if 0 // TODO(rjf): these are ambiguous with regular C type expressions with a naive parse: // s("float32(111)"), // s("float64(111)"), From b0841ff2da1bc1161077547b11a68fd33a35191e Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 23 Jul 2026 20:22:54 -0700 Subject: [PATCH 26/90] first pass at custom load-module exception (long term for JIT) --- src/lib_raddbg_markup/raddbg_markup.h | 33 +++++++++++++++++++++++++++ src/win32/demon/win32_demon.c | 33 +++++++++++++++++++++++++++ src/win32/demon/win32_demon.h | 1 + 3 files changed, 67 insertions(+) diff --git a/src/lib_raddbg_markup/raddbg_markup.h b/src/lib_raddbg_markup/raddbg_markup.h index a12f64b8..cefa460b 100644 --- a/src/lib_raddbg_markup/raddbg_markup.h +++ b/src/lib_raddbg_markup/raddbg_markup.h @@ -38,6 +38,7 @@ # 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) raddbg_load_module__impl((ptr), (size), (name)) #else # define raddbg_is_attached(...) (0) # define raddbg_thread_id(...) ((void)0) @@ -57,6 +58,7 @@ # 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) ((void)0) #endif //////////////////////////////// @@ -466,6 +468,37 @@ raddbg_annotate_vaddr_range__impl(void *ptr, unsigned __int64 size, char *fmt, . } } +void +raddbg_load_module__impl(void *ptr, unsigned __int64 size, char *name) +{ + 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; + }; +#pragma pack(pop) + RADDBG_LoadModuleInfo info; + info.vaddr = (unsigned __int64)ptr; + info.size = size; + info.name_vaddr= (unsigned __int64)name; +#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/win32/demon/win32_demon.c b/src/win32/demon/win32_demon.c index 3144f426..4006db73 100644 --- a/src/win32/demon/win32_demon.c +++ b/src/win32/demon/win32_demon.c @@ -2773,6 +2773,39 @@ 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]; + + // 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); + + // 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..e81373b8 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 From 78d12eb914378d8552b31c501c12e1c202356024 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 23 Jul 2026 20:28:52 -0700 Subject: [PATCH 27/90] custom module loads -> support manually specifying pdb path --- src/lib_raddbg_markup/raddbg_markup.h | 84 ++++++++++++++------------- src/win32/demon/win32_demon.c | 25 +++++--- src/win32/demon/win32_demon.h | 2 +- 3 files changed, 62 insertions(+), 49 deletions(-) diff --git a/src/lib_raddbg_markup/raddbg_markup.h b/src/lib_raddbg_markup/raddbg_markup.h index cefa460b..715f8754 100644 --- a/src/lib_raddbg_markup/raddbg_markup.h +++ b/src/lib_raddbg_markup/raddbg_markup.h @@ -20,45 +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_load_module(ptr, size, name) raddbg_load_module__impl((ptr), (size), (name)) +# 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_load_module(ptr, size, name) ((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 //////////////////////////////// @@ -469,7 +469,7 @@ raddbg_annotate_vaddr_range__impl(void *ptr, unsigned __int64 size, char *fmt, . } void -raddbg_load_module__impl(void *ptr, unsigned __int64 size, char *name) +raddbg_load_module__impl(void *ptr, unsigned __int64 size, char *name, char *dbg_path) { if(raddbg_is_attached()) { @@ -480,12 +480,14 @@ raddbg_load_module__impl(void *ptr, unsigned __int64 size, char *name) 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.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 diff --git a/src/win32/demon/win32_demon.c b/src/win32/demon/win32_demon.c index 4006db73..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); @@ -2777,13 +2787,14 @@ dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) 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 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); + 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); diff --git a/src/win32/demon/win32_demon.h b/src/win32/demon/win32_demon.h index e81373b8..2f48aaed 100644 --- a/src/win32/demon/win32_demon.h +++ b/src/win32/demon/win32_demon.h @@ -346,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); From 0963f4a6de377dab000b00a77b64732ad38760d7 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 30 Jun 2026 13:35:48 -0700 Subject: [PATCH 28/90] const/volatile/unsigned/signed type operators --- src/eval2/eval2.c | 58 +++++++++++++++++++ src/eval2/eval2.h | 8 +++ src/eval2/eval2.mdesk | 97 ++++++++++++++++++-------------- src/eval2/generated/eval2.meta.c | 54 ++++++++++++------ src/eval2/generated/eval2.meta.h | 20 ++++--- 5 files changed, 170 insertions(+), 67 deletions(-) diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index f5e4a5c3..55435871 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -2486,6 +2486,64 @@ e2_compile_from_expr(Arena *arena, E2_CompileState *state, E2_IRNode *resolve_re 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: { diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index ab134099..ff912ad5 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -147,11 +147,19 @@ struct E2_TypeKeyList //////////////////////////////// //~ rjf: Constructed Type Cache Types +typedef U32 E2_TypeFlags; +enum +{ + E2_TypeFlag_Const = (1<<0), + E2_TypeFlag_Volatile = (1<<1), +}; + typedef struct E2_ConsTypeParams E2_ConsTypeParams; struct E2_ConsTypeParams { Arch arch; E2_TypeKind kind; + E2_TypeFlags flags; String8 name; E2_TypeKey direct; U64 count; diff --git a/src/eval2/eval2.mdesk b/src/eval2/eval2.mdesk index 932ad155..70d13964 100644 --- a/src/eval2/eval2.mdesk +++ b/src/eval2/eval2.mdesk @@ -4,15 +4,24 @@ @table(name allow_type_operands is_type_expression is_first_operand_type_maybe target_operand_count) E2_ExprKindTable: { + //- rjf: leaves {Identifier 0 0 0 0 } {MacroArg 0 0 0 0 } {TypeIdentifier 0 1 0 0 } - {Ptr 1 1 1 1 } - {Array 1 1 1 2 } - {Function 1 1 1 0xffffffffffffffffull} {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} @@ -117,45 +126,49 @@ E2_LangKindTable: @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} - {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} + {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: diff --git a/src/eval2/generated/eval2.meta.c b/src/eval2/generated/eval2.meta.c index 7612deae..7affddae 100644 --- a/src/eval2/generated/eval2.meta.c +++ b/src/eval2/generated/eval2.meta.c @@ -4,19 +4,23 @@ //- GENERATED CODE C_LINKAGE_BEGIN -B8 e2_expr_kind_allow_type_operands_table[44] = +B8 e2_expr_kind_allow_type_operands_table[48] = { 0, 0, 0, 0, -1, -1, -1, 0, 0, 0, 1, +1, +1, +1, +1, +1, +1, +1, 0, 0, 1, @@ -52,18 +56,22 @@ B8 e2_expr_kind_allow_type_operands_table[44] = 1, }; -B8 e2_expr_kind_is_type_expr_table[44] = +B8 e2_expr_kind_is_type_expr_table[48] = { 0, 0, 0, 1, -1, -1, -1, 0, 0, 0, +1, +1, +1, +1, +1, +1, +1, 0, 0, 0, @@ -100,18 +108,22 @@ B8 e2_expr_kind_is_type_expr_table[44] = 0, }; -B8 e2_expr_kind_is_first_operand_type_maybe_table[44] = +B8 e2_expr_kind_is_first_operand_type_maybe_table[48] = { 0, 0, 0, 0, -1, -1, -1, 0, 0, 0, +1, +1, +1, +1, +1, +1, +1, 0, 0, 0, @@ -148,18 +160,22 @@ B8 e2_expr_kind_is_first_operand_type_maybe_table[44] = 0, }; -U64 e2_expr_kind_target_operand_count_table[44] = +U64 e2_expr_kind_target_operand_count_table[48] = { 0, 0, 0, 0, +0, +0, +0, 1, 2, 0xffffffffffffffffull, -0, -0, -0, +1, +1, +1, +1, 1, 2, 0xffffffffffffffffull, @@ -201,13 +217,17 @@ 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[39] = +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("")}, diff --git a/src/eval2/generated/eval2.meta.h b/src/eval2/generated/eval2.meta.h index fb22a0db..2bc7ea5b 100644 --- a/src/eval2/generated/eval2.meta.h +++ b/src/eval2/generated/eval2.meta.h @@ -12,12 +12,16 @@ E2_ExprKind_Null, E2_ExprKind_Identifier, E2_ExprKind_MacroArg, E2_ExprKind_TypeIdentifier, -E2_ExprKind_Ptr, -E2_ExprKind_Array, -E2_ExprKind_Function, 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, @@ -159,12 +163,12 @@ E2_ExprKindParseInfo *expr_kind_parse_infos; }; C_LINKAGE_BEGIN -extern B8 e2_expr_kind_allow_type_operands_table[44]; -extern B8 e2_expr_kind_is_type_expr_table[44]; -extern B8 e2_expr_kind_is_first_operand_type_maybe_table[44]; -extern U64 e2_expr_kind_target_operand_count_table[44]; +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[39]; +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]; From 20cd955cc9d92e85562d633e2165eaaeaf38ed08 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 30 Jun 2026 14:20:15 -0700 Subject: [PATCH 29/90] eval2: first pass at type_key -> string path --- src/eval2/eval2.c | 219 +++++++++++++++++++++++++++++++++++++ src/eval2/eval2.h | 5 + src/scratch/ryan_scratch.c | 3 +- 3 files changed, 226 insertions(+), 1 deletion(-) diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index 55435871..fd15cdd8 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -751,6 +751,35 @@ e2_mask_count_from_type_key(E2_TypeKey k) 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: + { + 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; + } + } + return result; +} + internal Arch e2_arch_from_type_key(E2_TypeKey k) { @@ -780,6 +809,196 @@ e2_arch_from_type_key(E2_TypeKey k) 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 diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index ff912ad5..1479c7ca 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -611,7 +611,12 @@ 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); diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index 1c7618a6..84c2256d 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -213,7 +213,7 @@ entry_point(CmdLine *cmdline) String8 msgs_string = str8_list_join(scratch.arena, &msgs, &join); // rjf: log - String8 log = str8f(scratch.arena, "%S -> %I64d (f32: %f) (f64: %f) *%s* %s%S\n", + String8 log = str8f(scratch.arena, "%S -> %I64d (f32: %f) (f64: %f) *%s* (type: %S) %s%S\n", strings[idx], val.s64, val.f32, @@ -221,6 +221,7 @@ entry_point(CmdLine *cmdline) 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)); From 62328cea3fef477fb266847ddfc4c1082ed62d53 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 6 Jul 2026 09:48:54 -0700 Subject: [PATCH 30/90] checkpoint on text patches --- src/raddbg/raddbg_core.c | 3 +- src/raddbg/raddbg_views.c | 216 +++++++++++++++++++++++++-- src/raddbg/raddbg_views.h | 4 + src/raddbg/raddbg_widgets.c | 176 +--------------------- src/raddbg/raddbg_widgets.h | 3 +- src/scratch/ryan_scratch.c | 2 + src/text/text.c | 287 ++++++++++++++++++++++++++++++++++-- src/text/text.h | 90 ++++++++++- src/ui/ui_core.c | 2 +- 9 files changed, 579 insertions(+), 204 deletions(-) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 575fcb3a..87cc8d0d 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -16583,10 +16583,11 @@ rd_frame(void) 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); + TXT_PatchList patches = {0}; 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)); + expr_off_range = r1u64(txt_off_from_pt(&info, &patches, range.min), txt_off_from_pt(&info, &patches, range.max)); } else { diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 8db66e9d..dd212d48 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); @@ -119,6 +120,201 @@ 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 + // + B32 snap[Axis2_COUNT] = {0}; + UI_Focus(UI_FocusKind_On) if(ui_is_focus_active()) + { + U64 line_count_per_page = ClampBot(num_possible_visible_lines, 10) - 10; + TxtPt *cursor = &rd_regs()->cursor; + TxtPt *mark = &rd_regs()->mark; + S64 *preferred_column = &cv->preferred_column; + B32 change = 0; + 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; + String8 line = txt_string_from_info_data_line_num(text_info, text_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 = text_info->lines_count; + String8 prev_line = txt_string_from_info_data_line_num(text_info, text_data, cursor->line-1); + String8 next_line = txt_string_from_info_data_line_num(text_info, text_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(text_info, text_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(text_info, text_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, text_info->lines_count ? dim_1u64(text_info->lines_ranges[text_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 + { +#if 0 + if(single_line_op.replace.size != 0) + { + Rng1U64 range = r1u64(txt_off_from_pt(text_info, &cv->patches, *cursor), + txt_off_from_pt(text_info, &cv->patches, *mark)); + txt_patch_list_push_new(cv->patch_arena, &cv->patches, range, single_line_op.replace); + } +#endif + *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(text_info, text_data, &cv->patches, txt_rng(*cursor, *mark)); + wm_set_clipboard_text(text); + taken = 1; + } + + //- rjf: consume + if(taken) + { + ui_eat_event(evt); + } + } + } + + ////////////////////////////// + //- rjf: compute current patched text state + // + TXT_Patched text_patched = txt_patched_from_info_data_patches(scratch.arena, text_info, text_data, &cv->patches); + ////////////////////////////// //- rjf: determine visible line range / count // @@ -180,7 +376,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla { 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)); + String8 text = txt_string_from_info_data_txt_rng(text_info, text_data, &cv->patches, txt_rng(rd_regs()->cursor, rd_regs()->mark)); if(text.size < 256) { cfg_node_new_replace(rd_state->cfg, input, text); @@ -243,7 +439,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla visible_line_idx < visible_line_count && line_idx < text_info->lines_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_text[visible_line_idx] = txt_string_from_info_data_line_num(text_info, text_data, line_num); 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]; } @@ -611,18 +807,6 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla 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,14 +846,14 @@ 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 = txt_string_from_info_data_txt_rng(text_info, text_data, &cv->patches, 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 = txt_string_from_info_data_txt_rng(text_info, text_data, &cv->patches, sig.mouse_expr_rng)); } } diff --git a/src/raddbg/raddbg_views.h b/src/raddbg/raddbg_views.h index 530d07b5..7e5b22ab 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; diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index d204e7e1..b4d7c3a0 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -2186,7 +2186,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_pt(params->text_info, params->patches, rd_regs()->cursor); } ////////////////////////////// @@ -2214,7 +2214,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)) @@ -2999,179 +2999,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; diff --git a/src/raddbg/raddbg_widgets.h b/src/raddbg/raddbg_widgets.h index 6811c1f0..62d29b55 100644 --- a/src/raddbg/raddbg_widgets.h +++ b/src/raddbg/raddbg_widgets.h @@ -108,6 +108,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 @@ -174,7 +175,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); internal RD_CodeSliceSignal rd_code_slicef(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *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/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index 84c2256d..e20bafa9 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -44,6 +44,8 @@ entry_point(CmdLine *cmdline) // int & B // (1 + (int)&B) s("int32(*)(int32, int32)"), + s("int32 *(*)(int32, int32)"), + s("int32 **(*)(int32, int32)"), s("123(1, 2, 3)"), s("int32 (*) [100]"), s("(3 * 4) + 2"), diff --git a/src/text/text.c b/src/text/text.c index ba7d2918..1043d970 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 @@ -2281,19 +2294,274 @@ txt_token_array_from_string__disasm_x64_intel(Arena *arena, U64 *bytes_processed //~ rjf: Text Info Extractor Helpers internal U64 -txt_off_from_info_pt(TXT_TextInfo *info, TxtPt pt) +txt_patched_off_from_base_off(TXT_PatchList *patches, U64 base_off) +{ + U64 result = base_off; + for EachNode(n, TXT_PatchNode, patches->first) + { + // rjf: this patch occurred before our result -> shift our offset + if(n->v.range.max < result) + { + S64 delta = (S64)n->v.replace.size - (S64)dim_1u64(n->v.range); + S64 result_adjusted = (S64)result + delta; + result_adjusted = ClampBot(0, result_adjusted); + result = (U64)result_adjusted; + } + + // rjf: if this patch covers our offset -> just reset to the beginning of the patch range + else if(contains_1u64(n->v.range, result)) + { + result = n->v.range.min; + } + + // NOTE(rjf): otherwise, this patch occurs *after* our offset, and so our offset remains + // valid. + } + return result; +} + +internal Rng1U64 +txt_patched_range_from_base_range(TXT_PatchList *patches, Rng1U64 base_range) +{ + Rng1U64 result = r1u64(txt_patched_off_from_base_off(patches, base_range.min), + txt_patched_off_from_base_off(patches, base_range.max)); + return result; +} + +internal void +txt_line_map_push(Arena *arena, TXT_LineMap *map, Rng1U64 idx_range, Rng1U64 *ranges, S64 delta) +{ + TXT_LineMapRangeNode *n = push_array(arena, TXT_LineMapRangeNode, 1); + n->idx_range = idx_range; + n->ranges = ranges; + n->delta = delta; + SLLQueuePush(map->first_range, map->last_range, n); + map->total_line_count += dim_1u64(idx_range); +} + +internal U64 +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) + { + for EachInRange(idx, n->idx_range) + { + Rng1U64 line_range = n->ranges[idx-n->idx_range.min]; + Rng1U64 line_range_shifted = r1u64(line_range.min + n->delta, line_range.max + n->delta); + if(line_range_shifted.min <= off && off <= line_range_shifted.max) + { + result = idx+1; + goto break_all; + } + } + } + break_all:; + return result; +} + +internal Rng1U64 +txt_range_from_line_idx(TXT_LineMap *map, U64 idx) +{ + Rng1U64 result = {0}; + for(TXT_LineMapRangeNode *n = map->first_range; n != 0; n = n->next) + { + if(contains_1u64(n->idx_range, idx)) + { + result = n->ranges[idx - n->idx_range.min]; + result.min = (U64)((S64)result.min + n->delta); + result.max = (U64)((S64)result.max + n->delta); + } + } + 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}; + memory_map_push(scratch.arena, &last_memory_map, r1u64(0, data.size), data.str); + txt_line_map_push(scratch.arena, &last_line_map, r1u64(0, info->lines_count), info->lines_ranges, 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}; + + // 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 portion of 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: 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; + } + } + if(last_line_start_off < n->v.replace.size) + { + 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 = shift_1u64(map_n->idx_range, 1); + 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-1, range_x_pre.max-1), 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-1, range_x_post_shifted.max-1), map_n->ranges + (range_x_post.min - num_range.min), map_n->delta + size_delta); + } + } + } + + // rjf: compute affected line ranges + U64 affected_line_count = dim_1u64(replace_line_num_range)+1; + 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 replace_line_range = replace_line_range_n->v; + Rng1U64 affected_line_range = r1u64(replace_line_range.min + n->v.range.min, replace_line_range.max + n->v.range.min); + + // 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_idx(&last_line_map, replace_line_num_range.min + affected_line_idx - 1); + affected_line_range.min = og_line_range.min; + } + + // rjf: the last line in the range -> take max from original line map, shift + if(affected_line_idx == affected_line_count-1) + { + Rng1U64 og_line_range = txt_range_from_line_idx(&last_line_map, replace_line_num_range.min + affected_line_idx - 1); + affected_line_range.max = og_line_range.max + size_delta; + } + + // rjf: commit + affected_line_ranges[affected_line_idx] = affected_line_range; + + replace_line_range_n = replace_line_range_n->next; + } + } + + // rjf: push affected line ranges + txt_line_map_push(scratch.arena, &next_line_map, r1u64(replace_line_num_range.min-1, (replace_line_num_range.max+1) - 1), affected_line_ranges, 0); + + // rjf: advance to the next memory map + last_memory_map = next_memory_map; + last_size = next_size; + last_line_map = next_line_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->idx_range, n->ranges, 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 +2571,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 +2760,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 +2927,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; } diff --git a/src/text/text.h b/src/text/text.h index 6425f848..efe94ad4 100644 --- a/src/text/text.h +++ b/src/text/text.h @@ -163,6 +163,73 @@ 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 idx_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_Patched TXT_Patched; +struct TXT_Patched +{ + MemoryMap memory_map; + U64 size; + TXT_LineMap line_map; +}; + +typedef struct TXT_Line TXT_Line; +struct TXT_Line +{ + Rng1U64 range; +}; + +typedef struct TXT_LineArray TXT_LineArray; +struct TXT_LineArray +{ + TXT_Line *v; + U64 count; +}; + //////////////////////////////// //~ rjf: Generated Code @@ -202,6 +269,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,17 +289,27 @@ 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 U64 txt_patched_off_from_base_off(TXT_PatchList *patches, U64 base_off); +internal Rng1U64 txt_patched_range_from_base_range(TXT_PatchList *patches, Rng1U64 base_range); +internal void txt_line_map_push(Arena *arena, TXT_LineMap *map, Rng1U64 idx_range, Rng1U64 *ranges, S64 delta); +internal U64 txt_line_num_from_off(TXT_LineMap *map, U64 off); +internal Rng1U64 txt_range_from_line_idx(TXT_LineMap *map, U64 idx); +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 diff --git a/src/ui/ui_core.c b/src/ui/ui_core.c index a632976d..34aa9a66 100644 --- a/src/ui/ui_core.c +++ b/src/ui/ui_core.c @@ -262,7 +262,7 @@ ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, //- 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) + if(next_cursor.column > string.size+replace.size+1 || 1 > next_cursor.column || event->delta_2s32.y != 0) { flags |= UI_TxtOpFlag_Invalid; } From 1ea9fae835f93bd79d4409466fd2032f2e4fa838 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Tue, 23 Jun 2026 10:56:48 -0700 Subject: [PATCH 31/90] debug report live stats after /OPT:REF pass --- src/linker/lnk.c | 73 ++++++++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 2850b08b..58e0ae00 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2740,46 +2740,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(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 EachNode(obj_n, LNK_ObjNode, objs.first) { + LNK_Obj *obj = &obj_n->data; + + 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); } From d34e1a7802ac431501a559cea0fe9cd35eb0627f Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 3 Jul 2026 00:26:27 -0700 Subject: [PATCH 32/90] implement /OPT:ICF Add identical COMDAT folding using a color-refinement over foldable read-only sections. Hash section contents, relocation targets, and target colors until partitions stabilize, then redirect folded COMDAT symlinks and discard duplicate sections. Honor .llvm_addrsig via /LLVM_ADDRSIG so address-significant sections are not folded, keep separate color space for code, unwind info, and MSVC vftables, and update COMDAT symbol/section mapping after folding. Add linker tests covering function folding, relocation-sensitive folds, alignment, color-space separation, symlink chains, .llvm_addrsig, pdata/xdata, and C++ ICF cases. --- src/linker/lnk.c | 763 ++++++++++++++--- src/linker/lnk.h | 14 +- src/linker/lnk_config.c | 8 + src/linker/lnk_config.h | 4 + src/linker/lnk_log.h | 1 + src/linker/lnk_obj.c | 49 +- src/linker/lnk_obj.h | 7 +- src/linker/lnk_symbol_table.c | 4 +- src/linker/tests/linker_tests.c | 1158 +++++++++++++++++++++++++- src/linker/thread_pool/thread_pool.c | 12 +- src/linker/thread_pool/thread_pool.h | 2 +- 11 files changed, 1869 insertions(+), 153 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 58e0ae00..5f4de897 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -171,6 +171,8 @@ 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, + + "/LLVM_ADDRSIG", }; char *push_opts[] = { @@ -234,11 +236,10 @@ lnk_make_default_cmd_line(Arena *arena, LNK_CmdLine user_cmd_line) } // when /FORCE is specified on the command line, do not stop on these errors -#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 @@ -2397,19 +2398,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 +2516,128 @@ lnk_reloc_ref_batch_list_concat_in_place_atomic(LNK_RelocRefsBatchList *list, LN } } +internal U32Array * +lnk_obj_indices_from_section_counts(Arena *arena, U64 worker_count, LNK_Obj **objs, U64 objs_count) +{ + Temp scratch = scratch_begin(&arena, 1); + + U64 *worker_section_counts = push_array(scratch.arena, U64, worker_count); + U64 *worker_obj_counts = push_array(scratch.arena, U64, worker_count); + U32Array *obj_indices = push_array(arena, U32Array, worker_count); + + for EachIndex(obj_idx, objs_count) { + U64 min_worker_idx = 0; + for (U64 worker_idx = 1; worker_idx < worker_count; worker_idx += 1) { + if (worker_section_counts[worker_idx] < worker_section_counts[min_worker_idx]) { + min_worker_idx = worker_idx; + } + } + + worker_section_counts[min_worker_idx] += objs[obj_idx]->header.section_count_no_null; + worker_obj_counts[min_worker_idx] += 1; + } + + for EachIndex(worker_idx, worker_count) { + obj_indices[worker_idx].v = push_array_no_zero(arena, U32, worker_obj_counts[worker_idx]); + } + + MemoryZero(worker_section_counts, sizeof(worker_section_counts[0])*worker_count); + MemoryZero(worker_obj_counts, sizeof(worker_obj_counts[0])*worker_count); + + for EachIndex(obj_idx, objs_count) { + U64 min_worker_idx = 0; + for (U64 worker_idx = 1; worker_idx < worker_count; worker_idx += 1) { + if (worker_section_counts[worker_idx] < worker_section_counts[min_worker_idx]) { + min_worker_idx = worker_idx; + } + } + + U32Array *worker_obj_indices = &obj_indices[min_worker_idx]; + worker_obj_indices->v[worker_obj_counts[min_worker_idx]++] = (U32)obj_idx; + worker_obj_indices->count += 1; + worker_section_counts[min_worker_idx] += objs[obj_idx]->header.section_count_no_null; + } + + scratch_end(scratch); + return obj_indices; +} + +internal B32 +lnk_resolve_reloc_target_symbol(Arena *arena, LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, String8 pass_name, LNK_ObjSymbolRef *resolved_symbol_out) +{ + B32 is_resolved = 1; + + Temp temp = temp_begin(arena); + HashMap seen_hm = {0}; + B32 keep_walking = 1; + LNK_ObjSymbolRef result = symbol; + do { + // detect cyclic chains + U64 symbol_key = ((U64)result.obj->input_idx << 32ull) | (U64)result.symbol_idx; + if (hash_map_search_u64_u64(&seen_hm, symbol_key) == 0) { + hash_map_push_u64_u64(temp.arena, &seen_hm, symbol_key, 1); + } else { + COFF_ParsedSymbol symbol_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symbol.obj, symbol.symbol_idx); + lnk_error_obj(LNK_Warning_CyclicSymbol, symbol.obj, "symbol %S forms a cyclic chain (%S)", symbol_parsed.name, pass_name); + MemoryZeroStruct(&result); + is_resolved = 0; + break; + } + + // unpack symbol + COFF_ParsedSymbol result_parsed = lnk_parsed_symbol_from_coff_symbol_idx(result.obj, result.symbol_idx); + COFF_SymbolValueInterpType result_interp = coff_interp_from_parsed_symbol(result_parsed); + + // resolve symbol + LNK_ObjSymbolRef next_ref = {0}; + if (lnk_resolve_symbol(symtab, result, &next_ref)) { + keep_walking = (result_interp == COFF_SymbolValueInterp_Weak || result_interp == COFF_SymbolValueInterp_Undefined); + result = next_ref; + } else { + keep_walking = 0; + } + } while (keep_walking); + + if (resolved_symbol_out) { + *resolved_symbol_out = result; + } + + temp_end(temp); + return is_resolved; +} + internal -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 +2664,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,37 +2714,7 @@ 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; } @@ -2674,7 +2748,7 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task) 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); @@ -2740,8 +2814,8 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task) if (task_id == 0) { ProfBegin("Remove Unreachable Sections"); - 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_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, section_number); @@ -2756,8 +2830,8 @@ THREAD_POOL_TASK_FUNC(lnk_walk_relocs_and_mark_ref_sections_task) enum { Stat_Null, Stat_Code, Stat_Data, Stat_Debug, Stat_Count }; 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; @@ -2813,14 +2887,503 @@ 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 }); + Temp scratch = scratch_begin(0,0); + U32Array *obj_indices = lnk_obj_indices_from_section_counts(scratch.arena, tp->worker_count, objs, objs_count); + + ProfScope("/OPT:REF") + { + LNK_OptTask task = { .symtab = symtab, .config = config, .objs = objs, .objs_count = objs_count, .obj_indices = obj_indices }; + tp_for_parallel(tp, 0, tp->worker_count, lnk_opt_ref_task, &task); + } + + scratch_end(scratch); +} + +typedef enum LNK_ICF_ColorSpace +{ + LNK_ICF_ColorSpace_Null, + LNK_ICF_ColorSpace_Code, + LNK_ICF_ColorSpace_Unwind, + LNK_ICF_ColorSpace_VFTable, + LNK_ICF_ColorSpace_COUNT, +} LNK_ICF_ColorSpace; + +internal String8 +lnk_string_from_icf_color_space(LNK_ICF_ColorSpace color_space) +{ + String8 result = str8_lit("Unknown"); + switch (color_space) { + case LNK_ICF_ColorSpace_Null: { result = str8_lit("Null"); } break; + case LNK_ICF_ColorSpace_Code: { result = str8_lit("Code"); } break; + case LNK_ICF_ColorSpace_Unwind: { result = str8_lit("Unwind"); } break; + case LNK_ICF_ColorSpace_VFTable: { result = str8_lit("VFTables"); } break; + case LNK_ICF_ColorSpace_COUNT: { result = str8_lit("Unknown"); } break; + } + return result; +} + +internal LNK_ICF_ColorSpace +lnk_icf_color_space_from_section(LNK_Obj *obj, U32 sect_idx) +{ + LNK_ICF_ColorSpace result = LNK_ICF_ColorSpace_Null; + + // fold read-only COMDAT sections + COFF_SectionFlags expected_flags = COFF_SectionFlag_LnkCOMDAT | COFF_SectionFlag_MemRead; + COFF_SectionFlags exclude_flags = COFF_SectionFlag_LnkRemove | COFF_SectionFlag_MemWrite | LNK_SECTION_FLAG_NOICF; + + if ((obj->section_flags[sect_idx] & expected_flags) == expected_flags && (obj->section_flags[sect_idx] & exclude_flags) == 0) { + // fold code + if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntCode) { + result = LNK_ICF_ColorSpace_Code; + } + // fold data + else if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntInitializedData) { + COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, sect_idx + 1); + String8 section_name = str8_cstring_capped(section_header->name, section_header->name + sizeof(section_header->name)); + + // fold unwind info + if (str8_match(section_name, str8_lit(".xdata"), 0) || str8_match(section_name, str8_lit(".pdata"), 0)) { + result = LNK_ICF_ColorSpace_Unwind; + } else { + // fold MSVC vftables separately from other read-only data + LNK_ObjSymbolRef symlink_ref = {0}; + if (lnk_obj_get_comdat_symlink(obj, sect_idx + 1, &symlink_ref)) { + COFF_ParsedSymbol symlink_symbol = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx); + if (str8_starts_with(symlink_symbol.name, str8_lit("??_7"))) { + result = LNK_ICF_ColorSpace_VFTable; + } + } + } + } + } + + return result; +} + +internal +THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(&arena,1); + + // + // OPT uses a color-refinement algorithm for folding duplicate sections. + // If a color group contains multiple distinct hashes, the group is split + // and a new refinement pass is run. By default, the algorithm loops until + // partitions stabilize. Equivalence is established by comparing cryptographic + // 128-bit hashes; in theory, the chance of collisions are near the birthday + // paradox with BLAKE3 and the other downside it is susceptible to adversarial + // inputs. + // + + LNK_OptTask *task = raw_task; + LNK_Obj **objs = task->objs; + + if (task_id == 0) { + lnk_log(LNK_Log_Debug, "/OPT:ICF:"); + } + + if (task->config->llvm_addrsig == LNK_SwitchState_Yes) { + // .llvm_addrsig is an array of ULEB128 symbol indices, which mark sections + // whose addresses are significant + for EachIndex(i, task->obj_indices[task_id].count) { + U64 obj_idx = task->obj_indices[task_id].v[i]; + LNK_Obj *obj = task->objs[obj_idx]; + + if (obj->llvm_addrsig_sect_idx >= obj->header.section_count_no_null) { continue; } + + String8 symbol_table = lnk_coff_symbol_table_from_obj(obj); + String8 string_table = lnk_coff_string_table_from_obj(obj); + COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, obj->llvm_addrsig_sect_idx + 1); + String8 section_data = str8_substr(obj->data, r1u64s(section_header->foff, section_header->fsize)); + + // parse symbol indices and mark selected sections with NOICF flag + for (U64 off = 0; off < section_data.size;) { + U64 symbol_off = off; + U64 symbol_idx = 0; + off += str8_deserial_read_uleb128(section_data, off, &symbol_idx); + if (symbol_off == off) { break; } + + if (symbol_idx < obj->header.symbol_count) { + LNK_ObjSymbolRef target_ref = { .obj = obj, .symbol_idx = symbol_idx }; + B32 is_symbol_found = lnk_resolve_reloc_target_symbol(scratch.arena, task->symtab, target_ref, str8_lit("/OPT:ICF"), &target_ref); + if (is_symbol_found) { + COFF_ParsedSymbol symbol = coff_parse_symbol(target_ref.obj->header, string_table, symbol_table, target_ref.symbol_idx); + if (coff_interp_from_parsed_symbol(symbol) == COFF_SymbolValueInterp_Regular) { + obj->section_flags[symbol.section_number - 1] |= LNK_SECTION_FLAG_NOICF; + } else { + lnk_error_obj(LNK_Error_IllData, obj, ".llvm_addrsig: skip symbol 0x%x at offset 0x%x; symbol index must address a section-based symbol\n", symbol_idx, symbol_off); + } + } + } else { + lnk_error_obj(LNK_Error_IllData, obj, ".llvm_addrsig: contains out of bounds symbol index 0x%x at offset 0x%x\n", symbol_idx, symbol_off); + } + } + } + barrier_wait(tp->barrier); + } + + // + // step 1: fill out color map and contributions + // + + // alloc total section counter + U64 *contrib_counts = 0; + if (task_id == 0) { + contrib_counts = push_array(scratch.arena, U64, task->objs_count); + } + tp_broadcast(&contrib_counts); + + // count contributions + for EachIndex(i, task->obj_indices[task_id].count) { + U64 obj_idx = task->obj_indices[task_id].v[i]; + LNK_Obj *obj = objs[obj_idx]; + for EachIndex(sect_idx, obj->header.section_count_no_null) { + if (lnk_icf_color_space_from_section(obj, sect_idx)) { + contrib_counts[obj_idx] += 1; + } + } + } + barrier_wait(tp->barrier); + + typedef struct { + struct { + U128 hash; + U64 old_color; + } key; + U32 obj_idx; + U32 sect_idx; + } Contrib; + + U64 contrib_count = sum_array_u64(task->objs_count, contrib_counts); + U64 *contrib_offsets = 0; + U64 **color_map = 0; + Contrib *contribs = 0; + U32 *is_part_stable = 0; + U64 *next_color = 0; + if (task_id == 0) { + contrib_offsets = offsets_from_counts_array_u64(scratch.arena, contrib_counts, task->objs_count); + color_map = push_array(scratch.arena, U64 *, task->objs_count); + for EachIndex(obj_idx, task->objs_count) { + LNK_Obj *obj = objs[obj_idx]; + color_map[obj_idx] = push_array(scratch.arena, U64, obj->header.section_count_no_null); + } + contribs = push_array(scratch.arena, Contrib, contrib_count); + is_part_stable = push_array(scratch.arena, U32, 1); + next_color = push_array(scratch.arena, U64, 1); + *next_color = LNK_ICF_ColorSpace_COUNT; + + lnk_log(LNK_Log_Debug, " Contrib count: %S", str8_from_count(scratch.arena, contrib_count)); + } + tp_broadcast(&contrib_offsets); + tp_broadcast(&color_map); + tp_broadcast(&contribs); + tp_broadcast(&is_part_stable); + tp_broadcast(&next_color); + + for EachIndex(i, task->obj_indices[task_id].count) { + U64 obj_idx = task->obj_indices[task_id].v[i]; + LNK_Obj *obj = objs[obj_idx]; + U64 cursor = 0; + for EachIndex(sect_idx, obj->header.section_count_no_null) { + LNK_ICF_ColorSpace color_space = lnk_icf_color_space_from_section(obj, sect_idx); + if (color_space == LNK_ICF_ColorSpace_Null) { + // assign unique color to sections that are not foldable + color_map[obj_idx][sect_idx] = ins_atomic_u64_inc_eval(next_color); + } + else { + // compute contribution index + U64 contrib_idx = contrib_offsets[obj_idx] + cursor++; + + // seed foldable sections with a content-derived color + COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, sect_idx + 1); + String8 section_data = str8_substr(obj->data, r1u64s(section_header->foff, section_header->fsize)); + XXH3_state_t hasher; + XXH3_64bits_reset(&hasher); + XXH3_64bits_update(&hasher, &color_space, sizeof(color_space)); + XXH3_64bits_update(&hasher, section_data.str, section_data.size); + + // fill out contribution + contribs[contrib_idx] = (Contrib){ + .obj_idx = safe_cast_u32(obj->input_idx), + .sect_idx = safe_cast_u32(sect_idx), + }; + + // assign content-derived starting color value + color_map[obj_idx][sect_idx] = XXH3_64bits_digest(&hasher) | (1ull << 63); + } + } + } + barrier_wait(tp->barrier); + + // + // step 2: refine equivalence classes + // + + U64 iter_count = 0; + for (;; iter_count += 1) { + barrier_wait(tp->barrier); + // reset color status tracker + if (task_id == 0) { + *is_part_stable = 1; + } + barrier_wait(tp->barrier); + + // compute colored hashes + for EachIndex(i, task->obj_indices[task_id].count) { + U64 obj_idx = task->obj_indices[task_id].v[i]; + LNK_Obj *obj = objs[obj_idx]; + COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); + U64 contrib_cursor = 0; + for EachIndex(sect_idx, obj->header.section_count_no_null) { + LNK_ICF_ColorSpace color_space = lnk_icf_color_space_from_section(obj, sect_idx); + if (color_space == LNK_ICF_ColorSpace_Null) { continue; } + + // parse relocations + COFF_SectionHeader *section_header = section_table + sect_idx; + COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, section_header); + + U64 contrib_idx = contrib_offsets[obj_idx] + contrib_cursor; + contrib_cursor += 1; + contribs[contrib_idx].key.old_color = color_map[obj_idx][sect_idx]; + + // parse section data + String8 section_data = str8_substr(obj->data, r1u64s(section_header->foff, section_header->fsize)); + + blake3_hasher hasher; blake3_hasher_init(&hasher); + + // mix section non-recursive properties + blake3_hasher_update(&hasher, section_data.str, section_data.size); + blake3_hasher_update(&hasher, &relocs.count, sizeof(relocs.count)); + blake3_hasher_update(&hasher, &color_space, sizeof(color_space)); + + for EachIndex(reloc_idx, relocs.count) { + COFF_Reloc *r = &relocs.v[reloc_idx]; + + // resolve symbol referenced by the relocation + LNK_ObjSymbolRef target_ref = { .obj = obj, .symbol_idx = r->isymbol }; + B32 is_symbol_found = lnk_resolve_reloc_target_symbol(scratch.arena, task->symtab, target_ref, str8_lit("/OPT:ICF"), &target_ref); + + COFF_SymbolValueInterpType target_interp; + U64 target_id; + U32 target_value; + + if (is_symbol_found) { + // parse COFF symbol and interpret the target symbol kind + COFF_ParsedSymbol target_symbol = lnk_parsed_symbol_from_coff_symbol_idx(target_ref.obj, target_ref.symbol_idx); + target_interp = coff_interp_from_parsed_symbol(target_symbol); + + switch (target_interp) { + case COFF_SymbolValueInterp_Regular: { + // color relocation with the referenced section + target_id = color_map[target_ref.obj->input_idx][target_symbol.section_number - 1]; + target_value = target_symbol.value; + } break; + default: { + // color relocation with unique symbol ref + target_id = Compose64Bit(target_ref.obj->input_idx, target_ref.symbol_idx); + target_value = target_symbol.value; + } break; + } + } else { + // color relocation with unique symbol ref + target_interp = max_U32; + target_id = Compose64Bit(obj_idx, r->isymbol); + target_value = 0; + } + + // mix relocation properties + blake3_hasher_update(&hasher, &r->apply_off, sizeof(r->apply_off)); + blake3_hasher_update(&hasher, &r->type, sizeof(r->type)); + blake3_hasher_update(&hasher, &target_interp, sizeof(target_interp)); + blake3_hasher_update(&hasher, &target_id, sizeof(target_id)); + blake3_hasher_update(&hasher, &target_value, sizeof(target_value)); + } + + // finalize section hash + U128 hash; + blake3_hasher_finalize(&hasher, (U8*)&hash, sizeof(hash)); + + // update contribution hash and color + contribs[contrib_idx].key.hash = hash; + } + } + barrier_wait(tp->barrier); + + // group sections by (color, hash) and handle color splits + if (task_id == 0) { + Temp temp = temp_begin(scratch.arena); + + HashMap new_color_hm = {0}; + HashMap old_color_hm = {0}; + U64 start_next_color = *next_color; + + for EachIndex(contrib_idx, contrib_count) { + Contrib *contrib = &contribs[contrib_idx]; + U64 *new_color_ptr = hash_map_search_string_u64(&new_color_hm, str8_struct(&contrib->key)); + if (new_color_ptr == 0) { + U64 color; + if (hash_map_search_u64_u64(&old_color_hm, contrib->key.old_color) == 0) { + color = contrib->key.old_color; + hash_map_push_u64_u64(temp.arena, &old_color_hm, contrib->key.old_color, color); + } else { + // generate a new color for the hash + color = ++*next_color; + *is_part_stable = 0; + } + new_color_ptr = &hash_map_push_string_u64(temp.arena, &new_color_hm, str8_struct(&contrib->key), color)->v.value.value_u64; + } + color_map[contrib->obj_idx][contrib->sect_idx] = *new_color_ptr; + } + + U64 split_count = *next_color - start_next_color; + lnk_log(LNK_Log_Debug, " Pass %llu found %S splits", iter_count, str8_from_count(scratch.arena, split_count)); + + temp_end(temp); + } + barrier_wait(tp->barrier); + + // stop iterating when partitions stabilize + if (*is_part_stable) { break; } + } + barrier_wait(tp->barrier); + + // + // step 3: flag folded sections for removal + // + + if (task_id == 0) { + HashMap leader_hm = { 0 }; + for EachIndex(contrib_idx, contrib_count) { + Contrib *contrib = &contribs[contrib_idx]; + U64 color = color_map[contrib->obj_idx][contrib->sect_idx]; + if (hash_map_search_u64_raw(&leader_hm, color) == 0) { + hash_map_push_u64_raw(scratch.arena, &leader_hm, color, contrib); + } + } + + typedef struct { U64 count; U64 size; } FoldStats; + FoldStats fold_stats[LNK_ICF_ColorSpace_COUNT] = {0}; + + for EachIndex(contrib_idx, contrib_count) { + Contrib *contrib = &contribs[contrib_idx]; + Contrib *leader = hash_map_search_u64_raw(&leader_hm, color_map[contrib->obj_idx][contrib->sect_idx]); + if (leader == 0 || leader == contrib) { continue; } + + LNK_Obj *contrib_obj = objs[contrib->obj_idx]; + LNK_Obj *leader_obj = objs[leader->obj_idx]; + + // update fold stats + if (lnk_get_log_status(LNK_Log_Debug)) { + COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(contrib_obj, contrib->sect_idx + 1); + LNK_ICF_ColorSpace color_space = lnk_icf_color_space_from_section(contrib_obj, contrib->sect_idx); + fold_stats[color_space].count += 1; + fold_stats[color_space].size += section_header->fsize; + } + + // update leader with largest alignment + U64 leader_align = coff_align_size_from_section_flags(leader_obj->section_flags[leader->sect_idx]); + U64 contrib_align = coff_align_size_from_section_flags(contrib_obj->section_flags[contrib->sect_idx]); + if (leader_align < contrib_align) { + leader_obj->section_flags[leader->sect_idx] &= ~(COFF_SectionFlag_AlignMask << COFF_SectionFlag_AlignShift); + leader_obj->section_flags[leader->sect_idx] |= coff_section_flag_from_align_size(contrib_align); + } + + // update COMDAT leader symbol link + Assert(leader_obj->comdats[leader->sect_idx] != max_U32); + LNK_ObjSymbolRef leader_symlink = { leader_obj, leader_obj->comdats[leader->sect_idx] }; + contrib_obj->symlinks[contrib->sect_idx + 1] = leader_symlink; + + // discard folded section + contrib_obj->section_flags[contrib->sect_idx] |= COFF_SectionFlag_LnkRemove; + + #if LNK_PARANOID + String8 section_name = lnk_obj_section_name_from_section_number(contrib_obj, contrib->sect_idx+1); + String8 leader_name = lnk_obj_section_name_from_section_number(leader_obj, leader->sect_idx+1); + lnk_log(LNK_Log_Debug, "fold %.*s[SECT%X \"%.*s\"] ==> %.*s[SECT%X \"%.*s\"]", str8_varg(lnk_loc_from_obj(scratch.arena, contrib_obj)), contrib->sect_idx+1, str8_varg(section_name), str8_varg(lnk_loc_from_obj(scratch.arena, leader_obj)), leader->sect_idx+1, str8_varg(leader_name)); + #endif + } + + if (lnk_get_log_status(LNK_Log_Debug)) { + U64 total_count = 0, total_size = 0; + for (LNK_ICF_ColorSpace color_space = (LNK_ICF_ColorSpace)(LNK_ICF_ColorSpace_Null + 1); color_space < LNK_ICF_ColorSpace_COUNT; color_space += 1) { + lnk_log(LNK_Log_Debug, " %-8S: %M, %.*s sections", lnk_string_from_icf_color_space(color_space), fold_stats[color_space].size, str8_varg(str8_from_count(scratch.arena, fold_stats[color_space].count))); + total_count += fold_stats[color_space].count; + total_size += fold_stats[color_space].size; + } + lnk_log(LNK_Log_Debug, " %-8s: %M, %.*s sections", "Total", total_size, str8_varg(str8_from_count(scratch.arena, total_count))); + } + } + barrier_wait(tp->barrier); + + // + // step 4: flatten COMDAT symlink chains so subsequent passes can assume symlinks are single hop + // + + for EachIndex(i, task->obj_indices[task_id].count) { + U64 obj_idx = task->obj_indices[task_id].v[i]; + LNK_Obj *obj = objs[obj_idx]; + for EachIndex(sect_idx, obj->header.section_count_no_null) { + U64 section_number = sect_idx + 1; + + LNK_ObjSymbolRef symlink_ref = {0}; + if (!lnk_obj_get_comdat_symlink(obj, section_number, &symlink_ref)) { continue; } + + { + Temp temp = temp_begin(scratch.arena); + + HashMap seen_hm = {0}; + U64 hop_count = 0; + U64 hop_cap = 1024; + for(; hop_count < hop_cap; hop_count += 1) { + COFF_ParsedSymbol symlink_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx); + + LNK_ObjSymbolRef next_symlink_ref = {0}; + if (!lnk_obj_get_comdat_symlink(symlink_ref.obj, symlink_parsed.section_number, &next_symlink_ref)) { break; } + + if (MemoryMatchStruct(&next_symlink_ref, &symlink_ref)) { break; } + + if (hash_map_search_string_u64(&seen_hm, str8_struct(&next_symlink_ref)) != 0) { + lnk_error_obj(LNK_Error_IllData, obj, "recursive COMDAT symlink in SECT%X", section_number); + MemoryZeroStruct(&symlink_ref); + break; + } + + symlink_ref = next_symlink_ref; + hash_map_push_string_u64(temp.arena, &seen_hm, str8_copy(temp.arena, str8_struct(&symlink_ref)), 1); + } + if (hop_count >= hop_cap) { + lnk_error_obj(LNK_Error_IllData, obj, "failed to flatten symlink for SECT%X; max number of hops reached", section_number); + MemoryZeroStruct(&symlink_ref); + } + + temp_end(temp); + } + + obj->symlinks[section_number] = symlink_ref; + } + } + barrier_wait(tp->barrier); + + scratch_end(scratch); + ProfEnd(); +} + +internal void +lnk_opt_icf(TP_Context *tp, LNK_SymbolTable *symtab, LNK_Config *config, LNK_Obj **objs, U64 objs_count) +{ + Temp scratch = scratch_begin(0,0); + U32Array *obj_indices = lnk_obj_indices_from_section_counts(scratch.arena, tp->worker_count, objs, objs_count); + + ProfScope("/OPT:ICF") + { + LNK_OptTask task = { .symtab = symtab, .config = config, .objs = objs, .objs_count = objs_count, .obj_indices = obj_indices }; + tp_for_parallel(tp, 0, tp->worker_count, lnk_opt_icf_task, &task); + } + + scratch_end(scratch); } internal @@ -2935,11 +3498,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(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(); @@ -2981,35 +3543,30 @@ THREAD_POOL_TASK_FUNC(lnk_patch_comdat_leaders_task) 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) { - LNK_Symbol *symlink = lnk_obj_get_comdat_symlink(obj, symbol.section_number); - if (symlink) { - LNK_ObjSymbolRef symlink_ref = lnk_ref_from_symbol(symlink); - if (symlink_ref.obj != obj) { - U32 section_number; - U32 value; - if (symbol.storage_class == COFF_SymStorageClass_External) { - // COMDAT leader may be at a different offset, so update this symbol with leader's offset - COFF_ParsedSymbol parsed_symlink = lnk_parsed_from_symbol(symlink); - section_number = symbol.section_number; - value = parsed_symlink.value; - } else { - // COMDAT section may have static symbols which are now invalid to relocate against - section_number = lnk_obj_get_removed_section_number(obj); - value = max_U32; - task->u.patch_symtabs.was_symbol_patched[obj_idx][symbol_idx] = 1; - } + if (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 parsed_symlink = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx); + if (symlink_ref.obj == obj && parsed_symlink.section_number == symbol.section_number) { continue; } + + B32 is_static_comdat_leader = symbol.storage_class == COFF_SymStorageClass_Static && + obj->comdats[symbol.section_number-1] == symbol_idx; + + if (symbol.storage_class == COFF_SymStorageClass_External || is_static_comdat_leader) { + // COMDAT leader may be at a different offset, so update this symbol with leader's offset + U32 section_number = symbol.section_number; + U32 value = parsed_symlink.value; + + if (obj->header.is_big_obj) { + COFF_Symbol32 *symbol32 = symbol.raw_symbol; + symbol32->section_number = section_number; + symbol32->value = value; + } else { + COFF_Symbol16 *symbol16 = symbol.raw_symbol; + symbol16->section_number = (U16)section_number; + symbol16->value = value; } } } diff --git a/src/linker/lnk.h b/src/linker/lnk.h index d6388d0a..b6a154ca 100644 --- a/src/linker/lnk.h +++ b/src/linker/lnk.h @@ -74,6 +74,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 @@ -215,10 +216,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 { @@ -390,7 +393,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 ------------------------------------------------------------- diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index ac624c0e..f4dc456e 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -24,6 +24,7 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_FailIfMismatch, 1, "FAILIFMISMATCH", "{id=value}", "Fails to link if same ids have conflicting values." }, { LNK_CmdSwitch_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_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." }, @@ -106,6 +107,8 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { LNK_CmdSwitch_RadTypeServer, 0, "RAD_TYPE_SERVER", ":FILENAME", "Merge types and store them in the specified file. The filename must have the .rrt extension." }, + { LNK_CmdSwitch_LLVM_AddrSig, 0, "LLVM_ADDRSIG", "[:NO]", "Use .llvm_addrsig to guide ICF." }, + { LNK_CmdSwitch_Help, 0, "HELP", "", "" }, { LNK_CmdSwitch_Help, 0, "?", "", "" }, }; @@ -2179,6 +2182,10 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List lnk_error_cmd_switch(LNK_Error_Cmdl, obj, cmd_switch, "missing type server file path"); } } break; + + case LNK_CmdSwitch_LLVM_AddrSig: { + lnk_cmd_switch_parse_flag(obj, cmd_switch, value_strings, &config->llvm_addrsig); + } break; } scratch_end(scratch); @@ -2208,6 +2215,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) { diff --git a/src/linker/lnk_config.h b/src/linker/lnk_config.h index a781d664..67ae4c3f 100644 --- a/src/linker/lnk_config.h +++ b/src/linker/lnk_config.h @@ -143,6 +143,8 @@ typedef enum LNK_CmdSwitch_RadTypeServer, LNK_CmdSwitch_RadTypeServer_MatchObj, + LNK_CmdSwitch_LLVM_AddrSig, + LNK_CmdSwitch_Help, LNK_CmdSwitch_Count @@ -316,6 +318,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; @@ -387,6 +390,7 @@ typedef struct LNK_Config String8 type_server_name; LNK_SwitchState type_server; LNK_SwitchState sort_imports; + LNK_SwitchState llvm_addrsig; } LNK_Config; // --- MSVC Error Codes -------------------------------------------------------- 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..c3144acf 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -100,7 +100,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) COFF_SectionFlags *section_flags = push_array_no_zero(arena, COFF_SectionFlags, header.section_count_no_null); 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 (~section_flags[sect_idx] & COFF_SectionFlag_CntUninitializedData) { if (coff_sect_header->fsize > 0) { @@ -344,6 +344,7 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) obj->debug_t_sect_idx = ~0; obj->debug_p_sect_idx = ~0; obj->debug_h_sect_idx = ~0; + obj->llvm_addrsig_sect_idx = ~0; } internal @@ -362,6 +363,19 @@ THREAD_POOL_TASK_FUNC(lnk_obj_find_debug_t) } } +internal +THREAD_POOL_TASK_FUNC(lnk_obj_find_llvm_addrsig) +{ + LNK_Obj *obj = &((LNK_ObjNode *)raw_task)[task_id].data; + for EachIndex(sect_idx, obj->header.section_count_no_null) { + String8 section_name = lnk_obj_section_name_from_sect_idx(obj, sect_idx); + if (str8_match(section_name, str8_lit(".llvm_addrsig"), 0)) { + obj->llvm_addrsig_sect_idx = sect_idx; + break; + } + } +} + internal LNK_ObjNode * lnk_obj_from_input_many(TP_Context *tp, TP_Arena *arena, LNK_Config *config, U64 inputs_count, LNK_Input **inputs) { @@ -369,10 +383,12 @@ lnk_obj_from_input_many(TP_Context *tp, TP_Arena *arena, LNK_Config *config, U64 if (inputs_count) { 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); } + if (config->opt_icf == LNK_SwitchState_Yes) { + tp_for_parallel(tp, arena, inputs_count, lnk_obj_find_llvm_addrsig, objs); + } } return objs; } @@ -451,19 +467,26 @@ THREAD_POOL_TASK_FUNC(lnk_input_coff_symbol_table) } } -internal LNK_SymbolHashTrie ** +internal LNK_ObjSymbolRef * lnk_symlinks_from_obj(Arena *arena, LNK_SymbolTable *symtab, LNK_Obj *obj) { - LNK_SymbolHashTrie **symlinks = push_array(arena, LNK_SymbolHashTrie *, obj->header.section_count_no_null+1); + LNK_ObjSymbolRef *symlinks = push_array(arena, LNK_ObjSymbolRef, obj->header.section_count_no_null+1); COFF_ParsedSymbol symbol; 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) { + if (interp == COFF_SymbolValueInterp_Regular) { 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); + if (symbol.aux_symbol_count == 0 && symbol.storage_class == COFF_SymStorageClass_External) { + if (symlinks[symbol.section_number].obj == 0 || symbol.value == 0) { + LNK_SymbolHashTrie *link_symbol = lnk_symbol_table_search_(symtab, symbol.name); + if (link_symbol) { + symlinks[symbol.section_number] = lnk_ref_from_symbol(link_symbol->symbol); + } + } + } else if (symlinks[symbol.section_number].obj == 0 && symbol.storage_class == COFF_SymStorageClass_Static && symbol.aux_symbol_count > 0) { + symlinks[symbol.section_number] = (LNK_ObjSymbolRef){ obj, symbol_idx }; } } } @@ -543,11 +566,15 @@ lnk_obj_get_removed_section_number(LNK_Obj *obj) return obj->header.is_big_obj ? LNK_REMOVED_SECTION_NUMBER_32 : LNK_REMOVED_SECTION_NUMBER_16; } -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 * diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index 9a21de59..339f2152 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -22,7 +22,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 +33,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; @@ -139,7 +142,7 @@ internal U32 lnk_obj_get_vol_md(LNK_Obj *obj); internal struct LNK_Lib * lnk_obj_get_lib(LNK_Obj *obj); internal 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); // --- Symbol & Section Helpers ------------------------------------------------ diff --git a/src/linker/lnk_symbol_table.c b/src/linker/lnk_symbol_table.c index 8cb6c4e5..1c4a7655 100644 --- a/src/linker/lnk_symbol_table.c +++ b/src/linker/lnk_symbol_table.c @@ -717,8 +717,8 @@ lnk_resolve_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_ObjSymb COFF_SymbolValueInterpType symbol_interp = coff_interp_symbol(symbol_parsed.section_number, symbol_parsed.value, symbol_parsed.storage_class); 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); diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 3221abe0..bd1c8d56 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 //////////////////////////////// @@ -2510,6 +2510,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) { @@ -4749,6 +4806,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 +4978,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; @@ -6963,9 +7113,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 +7381,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 +7413,7 @@ TEST(fold_two_funcs) T_Ok(str8_match(text_data, str8_array_fixed(expected_text), 0)); } - -TEST(same_but_different) +TEST(icf_same_but_different) { U8 text[] = { 0xe8, 0x00, 0x00, 0x00, 0x00, // call $ @@ -7138,8 +7514,7 @@ TEST(same_but_different) } } - -TEST(fold_diamond) +TEST(icf_fold_diamond) { U8 call_b_and_c[] = { 0xe8, 0x00, 0x00, 0x00, 0x00, @@ -7231,8 +7606,7 @@ TEST(fold_diamond) } } - -TEST(cyclic_icf) +TEST(icf_cyclic_icf) { U8 text[] = { 0xe8, 0x00, 0x00, 0x00, 0x00, @@ -7242,14 +7616,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 +7632,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 +7646,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 +7666,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 +7915,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 +7970,563 @@ 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); + + t_invoke_linkerf("%S /opt:icf /out:a.exe libcmt.lib", main_obj_path); + T_Ok(g_last_exit_code == 0); + + String8 a_path = t_make_file_path(arena, str8_lit("a.exe")); + t_invoke(a_path, str8_zero(), max_U64); + if (t_id_linker() == Linker_radlink) { + T_Ok(g_last_exit_code == 1); + } else { + T_Ok(g_last_exit_code == 0); + } + + if (t_id_linker() == Linker_radlink) { + 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[2 * 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 }, + { "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"), {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_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("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[2] = {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_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 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)) - From 941b8a011e6291193854e70eb0c6c4b0f99c72b6 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 3 Jul 2026 00:27:01 -0700 Subject: [PATCH 33/90] duplicate identical search aliases are not an error case --- src/linker/lnk_symbol_table.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/linker/lnk_symbol_table.c b/src/linker/lnk_symbol_table.c index 1c4a7655..ea02e1d9 100644 --- a/src/linker/lnk_symbol_table.c +++ b/src/linker/lnk_symbol_table.c @@ -209,7 +209,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); } From 83c55aebdc643e5f7e16d3e42f6093729c28a80b Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 3 Jul 2026 00:30:33 -0700 Subject: [PATCH 34/90] check that __imp_* archive symbols are import members before applying import queue flags to the member --- src/linker/lnk.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 5f4de897..f22ecafc 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1571,6 +1571,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); @@ -1606,14 +1611,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 { From 3eeae8d365385a0607eee62b1dfa4950289599d4 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sat, 11 Jul 2026 12:27:53 -0700 Subject: [PATCH 35/90] enable binary for stdout and stderr to bypass CRLF mangling issues in captured output under build systems --- src/linker/lnk_log.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 From 541727b819496b80776c6eaffc66bc3f34dd8198 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sat, 11 Jul 2026 12:54:20 -0700 Subject: [PATCH 36/90] debug log unresolved symbols specified in .llvm_addrsig --- src/linker/lnk.c | 62 ++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index f22ecafc..eb839f1e 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2967,22 +2967,18 @@ lnk_icf_color_space_from_section(LNK_Obj *obj, U32 sect_idx) return result; } -internal +// 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 and the other downside it is susceptible to adversarial +// inputs. THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) { ProfBeginFunction(); Temp scratch = scratch_begin(&arena,1); - // - // OPT uses a color-refinement algorithm for folding duplicate sections. - // If a color group contains multiple distinct hashes, the group is split - // and a new refinement pass is run. By default, the algorithm loops until - // partitions stabilize. Equivalence is established by comparing cryptographic - // 128-bit hashes; in theory, the chance of collisions are near the birthday - // paradox with BLAKE3 and the other downside it is susceptible to adversarial - // inputs. - // - LNK_OptTask *task = raw_task; LNK_Obj **objs = task->objs; @@ -3004,7 +3000,8 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) 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 + // 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; @@ -3015,11 +3012,16 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) LNK_ObjSymbolRef target_ref = { .obj = obj, .symbol_idx = symbol_idx }; B32 is_symbol_found = lnk_resolve_reloc_target_symbol(scratch.arena, task->symtab, target_ref, str8_lit("/OPT:ICF"), &target_ref); if (is_symbol_found) { - COFF_ParsedSymbol symbol = coff_parse_symbol(target_ref.obj->header, string_table, symbol_table, target_ref.symbol_idx); + COFF_ParsedSymbol symbol = lnk_parsed_symbol_from_coff_symbol_idx(target_ref.obj, target_ref.symbol_idx); if (coff_interp_from_parsed_symbol(symbol) == COFF_SymbolValueInterp_Regular) { - obj->section_flags[symbol.section_number - 1] |= LNK_SECTION_FLAG_NOICF; + 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(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_error_obj(LNK_Error_IllData, obj, ".llvm_addrsig: skip symbol 0x%x at offset 0x%x; symbol index must address a section-based symbol\n", symbol_idx, symbol_off); + 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 { @@ -3034,24 +3036,26 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) // 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); + { + // alloc total section counter + if (task_id == 0) { + contrib_counts = push_array(scratch.arena, U64, task->objs_count); + } + tp_broadcast(&contrib_counts); - // count contributions - for EachIndex(i, task->obj_indices[task_id].count) { - U64 obj_idx = task->obj_indices[task_id].v[i]; - LNK_Obj *obj = objs[obj_idx]; - for EachIndex(sect_idx, obj->header.section_count_no_null) { - if (lnk_icf_color_space_from_section(obj, sect_idx)) { - contrib_counts[obj_idx] += 1; + // count contributions + for EachIndex(i, task->obj_indices[task_id].count) { + U64 obj_idx = task->obj_indices[task_id].v[i]; + LNK_Obj *obj = objs[obj_idx]; + for EachIndex(sect_idx, obj->header.section_count_no_null) { + if (lnk_icf_color_space_from_section(obj, sect_idx)) { + contrib_counts[obj_idx] += 1; + } } } + barrier_wait(tp->barrier); } - barrier_wait(tp->barrier); typedef struct { struct { @@ -3244,7 +3248,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) } U64 split_count = *next_color - start_next_color; - lnk_log(LNK_Log_Debug, " Pass %llu found %S splits", iter_count, str8_from_count(scratch.arena, split_count)); + lnk_log(LNK_Log_Debug, " Round %llu found %S splits", iter_count, str8_from_count(scratch.arena, split_count)); temp_end(temp); } From afd454b41ff83ae125bba4ae5e33a8fd374b25f2 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sun, 12 Jul 2026 23:13:41 -0700 Subject: [PATCH 37/90] fix DBI SC image-section range mapping Log and skip section contributions that fail to map into the linked image. Keep a file-range-to-section-number map so compact file ranges produce the correct DBI section numbers. --- src/linker/lnk_debug_info.c | 130 +++++++++++++++---------------- src/linker/lnk_debug_info.h | 13 ++-- src/linker/pdb_ext/pdb_builder.c | 62 +++++++-------- src/linker/pdb_ext/pdb_builder.h | 50 ++++++------ src/pdb/pdb.h | 24 +++--- src/pdb/pdb_parse.c | 16 ++-- 6 files changed, 145 insertions(+), 150 deletions(-) diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index fe125133..eeb926fa 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -3067,67 +3067,60 @@ 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; } } } @@ -3143,17 +3136,18 @@ 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), }; // set min type indices @@ -3185,17 +3179,13 @@ lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config if (builder_flags & LNK_PDB_BuilderFlag_Modules) { ProfScope ("Alloc Modules") - for EachIndex(obj_idx, cv->obj_count) + 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); + 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); } ProfBegin("Add string tables"); @@ -3213,13 +3203,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); } diff --git a/src/linker/lnk_debug_info.h b/src/linker/lnk_debug_info.h index dc094e0f..300971b9 100644 --- a/src/linker/lnk_debug_info.h +++ b/src/linker/lnk_debug_info.h @@ -215,12 +215,13 @@ typedef struct 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 diff --git a/src/linker/pdb_ext/pdb_builder.c b/src/linker/pdb_ext/pdb_builder.c index be696012..c0608b20 100644 --- a/src/linker/pdb_ext/pdb_builder.c +++ b/src/linker/pdb_ext/pdb_builder.c @@ -2263,24 +2263,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 +2405,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 +2420,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 +2595,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; @@ -2622,7 +2622,7 @@ dbi_sc_compar(const PDB_DbiSectionContrib *a, const PDB_DbiSectionContrib *b) #endif internal void -lnk_radix_sort_dbi_sc_array(PDB_DbiSectionContrib *arr, U64 sc_count, U64 sect_count) +lnk_radix_sort_dbi_sc_array(PDB_DbiSC *arr, U64 sc_count, U64 sect_count) { ProfBeginFunction(); @@ -2638,9 +2638,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 +2651,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 +2693,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 +2701,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 +2709,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 +2731,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; } @@ -2758,14 +2758,14 @@ dbi_build_sec_con(Arena *arena, 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(); @@ -2956,7 +2956,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 +2965,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); diff --git a/src/linker/pdb_ext/pdb_builder.h b/src/linker/pdb_ext/pdb_builder.h index b4d0909e..8abcd2d5 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]; @@ -384,21 +384,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 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; From 65541eaf91b49c6eaf25a0610e9c983814f96f1c Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sun, 12 Jul 2026 23:13:51 -0700 Subject: [PATCH 38/90] LLVM BC magic sniffer --- src/llvm/llvm.c | 21 +++++++++++++++++++++ src/llvm/llvm.h | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) 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 - From 25430ea522558d264d1b482e53ecc791f6dbcdc4 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sun, 12 Jul 2026 23:19:07 -0700 Subject: [PATCH 39/90] assign COMDAT symlinks after weak symbol replacement Previously, COMDAT symlinks were assigned as each object batch was loaded. Weak symbol replacement could later change the selected leader, leaving objects from earlier passes with stale COMDAT leader refs. Library search does not need symlinks to be valid, so solve this by doing a single sweep assignment of COMDAT leaders after a fixed point is found. --- src/linker/lnk.c | 8 ++++++++ src/linker/lnk_obj.c | 10 +++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index eb839f1e..59213926 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2271,6 +2271,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? // diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index c3144acf..68b5ae4c 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -502,13 +502,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(); } From 35722f2a018d2dff6659edecf71629e606a17942 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sun, 12 Jul 2026 23:38:54 -0700 Subject: [PATCH 40/90] handle BOMs in linker response files Decode UTF-16LE response files with a BOM and strip UTF-8 BOMs before parsing arguments. Add coverage for UTF-16 response-file input. --- src/linker/lnk_config.c | 13 +++++++++++++ src/linker/tests/linker_tests.c | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index f4dc456e..16132546 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -711,6 +711,18 @@ 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) { + 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) { + result = str8_skip(data, 3); + } + return result; +} + internal B32 lnk_parse_merge_directive(String8 string, LNK_Obj *obj, LNK_MergeDirective *out) { @@ -1091,6 +1103,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); diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index bd1c8d56..80f4d52a 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -2791,6 +2791,19 @@ 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(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) From b77c958b5f490e5561dcf7ae3c8dc5beea671f8f Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 13 Jul 2026 01:51:12 -0700 Subject: [PATCH 41/90] add DEF, GUARD, and SECTION config support Parse /DEF, /GUARD, and /SECTION switches, including DEF handling for NAME/LIBRARY, BASE, VERSION, HEAPSIZE, STACKSIZE, EXPORTS, and SECTIONS. Store section directives in linker config and apply them consistently when gathering object sections when pushing/searching image sections. Pull load-config symbols for /GUARD without setting GUARD_CF until guard tables are emitted. Add coverage for DEF file parity, section directives, and guard load-config handling. --- src/linker/lnk.c | 69 ++-- src/linker/lnk.h | 1 + src/linker/lnk_config.c | 589 +++++++++++++++++++++++++++++--- src/linker/lnk_config.h | 27 ++ src/linker/tests/linker_tests.c | 200 +++++++++++ 5 files changed, 824 insertions(+), 62 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 59213926..00e255ac 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2008,12 +2008,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); @@ -3424,8 +3425,10 @@ THREAD_POOL_TASK_FUNC(lnk_gather_section_definitions_task) Temp temp = temp_begin(scratch.arena); // was section defined? + COFF_SectionFlags image_sect_flags = sect_flags & ~(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 @@ -3434,7 +3437,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); @@ -3471,9 +3474,11 @@ THREAD_POOL_TASK_FUNC(lnk_gather_section_contribs_task) 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); } @@ -5005,6 +5010,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) { @@ -5017,14 +5038,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, @@ -5322,7 +5344,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; @@ -5454,6 +5476,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; @@ -5465,7 +5488,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); @@ -5478,7 +5501,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); @@ -5490,7 +5513,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); @@ -5500,7 +5523,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) { @@ -5522,7 +5545,7 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // 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) { @@ -5542,7 +5565,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)); @@ -5572,7 +5595,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); @@ -5599,7 +5622,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); diff --git a/src/linker/lnk.h b/src/linker/lnk.h index b6a154ca..e238e26f 100644 --- a/src/linker/lnk.h +++ b/src/linker/lnk.h @@ -261,6 +261,7 @@ typedef struct LNK_ImageFillNode typedef struct { + LNK_Config *config; LNK_SymbolTable *symtab; LNK_SectionTable *sectab; U64 objs_count; diff --git a/src/linker/lnk_config.c b/src/linker/lnk_config.c index 16132546..d783784c 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." }, @@ -26,6 +27,8 @@ global read_only LNK_CmdSwitch g_cmd_switch_map[] = { 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." }, @@ -56,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." }, @@ -715,14 +719,51 @@ 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) { @@ -740,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) { @@ -760,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) { @@ -1148,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) { @@ -1266,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); @@ -1313,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; @@ -1400,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; @@ -1744,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; @@ -2204,6 +2388,331 @@ lnk_apply_cmd_option_to_config(LNK_Config *config, String8 cmd_name, String8List 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); +} + internal void lnk_config_pushf(LNK_Config *config, char *fmt, ...) { @@ -2354,13 +2863,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); diff --git a/src/linker/lnk_config.h b/src/linker/lnk_config.h index 67ae4c3f..d469a413 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, @@ -267,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, @@ -363,6 +387,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; @@ -575,6 +600,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); diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 80f4d52a..ea749f8f 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -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; @@ -2792,6 +2886,112 @@ TEST(import_export) // 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()); From d7904122192b2c599c4afb43ac74ea75eda5260f Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 13 Jul 2026 03:12:27 -0700 Subject: [PATCH 42/90] refactor radlink section gathering Fold section definition and contributions collection into a worker-count task with barriers around the serial merge. Share section filtering between both phases and keep zero-size COMDATs so offset-zero symbols still map to a section. --- src/coff/coff.h | 1 + src/linker/lnk.c | 301 +++++++++++++++++++++++++---------------------- src/linker/lnk.h | 2 + 3 files changed, 166 insertions(+), 138 deletions(-) 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/linker/lnk.c b/src/linker/lnk.c index 00e255ac..54d392bc 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -3404,28 +3404,65 @@ lnk_opt_icf(TP_Context *tp, LNK_SymbolTable *symtab, LNK_Config *config, LNK_Obj scratch_end(scratch); } +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(COFF_SectionHeader *sect_header, COFF_SectionFlags sect_flags) +{ + // 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; + } + + // non-COMDAT empty sections do not need image contributions + if (sect_header->fsize == 0) { + if (~sect_flags & COFF_SectionFlag_LnkCOMDAT) { + return 0; + } + + // keep zero-size COMDATs so symbols at offset zero still get mapped + } + + 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(sect_header, obj->section_flags[sect_idx])) { 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 = sect_flags & ~(COFF_SectionFlags_LnkFlags | COFF_SectionFlags_Reserved); + 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); 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); @@ -3449,28 +3486,113 @@ 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(sect_header, sect_flags)) { 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); @@ -3499,8 +3621,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(); @@ -3968,15 +4091,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) { @@ -5057,108 +5171,21 @@ 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; { @@ -5179,8 +5206,6 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT } 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"); diff --git a/src/linker/lnk.h b/src/linker/lnk.h index e238e26f..86485831 100644 --- a/src/linker/lnk.h +++ b/src/linker/lnk.h @@ -275,6 +275,8 @@ typedef struct LNK_SectionArray image_sects; union { struct { + Arena *arena; + Rng1U64 *ranges; HashTable **defns; } gather_sects; struct { From 7197398f31709cf152dca94b6a0d4fe30066d3c8 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 13 Jul 2026 02:50:56 -0700 Subject: [PATCH 43/90] split external-symbol handling from section-definition fallbacks --- src/linker/lnk.c | 2 +- src/linker/lnk_obj.c | 55 ++++++++++++++++++++++++++++++----------- src/msvc_crt/msvc_crt.h | 3 +++ 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 54d392bc..4d8f57cf 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2965,7 +2965,7 @@ lnk_icf_color_space_from_section(LNK_Obj *obj, U32 sect_idx) LNK_ObjSymbolRef symlink_ref = {0}; if (lnk_obj_get_comdat_symlink(obj, sect_idx + 1, &symlink_ref)) { COFF_ParsedSymbol symlink_symbol = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx); - if (str8_starts_with(symlink_symbol.name, str8_lit("??_7"))) { + if (str8_starts_with(symlink_symbol.name, str8_lit(MSCRT_VFTABLE_SYMBOL_PREFIX))) { result = LNK_ICF_ColorSpace_VFTable; } } diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index 68b5ae4c..6077b994 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -470,25 +470,50 @@ THREAD_POOL_TASK_FUNC(lnk_input_coff_symbol_table) internal LNK_ObjSymbolRef * lnk_symlinks_from_obj(Arena *arena, LNK_SymbolTable *symtab, LNK_Obj *obj) { - LNK_ObjSymbolRef *symlinks = push_array(arena, LNK_ObjSymbolRef, 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) { - LNK_ObjSection section = lnk_obj_section_from_section_number(obj, symbol.section_number); - if (*section.flags & COFF_SectionFlag_LnkCOMDAT) { - if (symbol.aux_symbol_count == 0 && symbol.storage_class == COFF_SymStorageClass_External) { - if (symlinks[symbol.section_number].obj == 0 || symbol.value == 0) { - LNK_SymbolHashTrie *link_symbol = lnk_symbol_table_search_(symtab, symbol.name); - if (link_symbol) { - symlinks[symbol.section_number] = lnk_ref_from_symbol(link_symbol->symbol); - } - } - } else if (symlinks[symbol.section_number].obj == 0 && symbol.storage_class == COFF_SymStorageClass_Static && symbol.aux_symbol_count > 0) { - symlinks[symbol.section_number] = (LNK_ObjSymbolRef){ obj, symbol_idx }; + 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) { + B32 can_set_symlink = (symlink->obj == 0 || symbol.value == 0); + if (!can_set_symlink && symlink->obj == obj) { + // NOTE: The section definition is a fallback symlink. Public symbols inside a + // discarded COMDAT need to target the selected public symbol's offset, + // not the selected section base plus the discarded symbol's offset. + // MSVC vftables are also preferred over other public symbols in the + // same section so ICF can keep vftable COMDATs in their own color space. + COFF_ParsedSymbol symlink_symbol = lnk_parsed_symbol_from_coff_symbol_idx(symlink->obj, symlink->symbol_idx); + B32 symlink_is_section_defn = (symlink_symbol.section_number == symbol.section_number && + symlink_symbol.storage_class == COFF_SymStorageClass_Static && + symlink_symbol.aux_symbol_count > 0); + B32 symlink_is_non_vftable = (symlink_symbol.section_number == symbol.section_number && + !str8_starts_with(symlink_symbol.name, str8_lit(MSCRT_VFTABLE_SYMBOL_PREFIX))); + B32 symbol_is_vftable = str8_starts_with(symbol.name, str8_lit(MSCRT_VFTABLE_SYMBOL_PREFIX)); + can_set_symlink = (symlink_is_section_defn || (symbol_is_vftable && symlink_is_non_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); } } + + continue; + } + + // section definitions + if (symlink->obj == 0 && symbol.storage_class == COFF_SymStorageClass_Static && symbol.aux_symbol_count > 0) { + *symlink = (LNK_ObjSymbolRef){ obj, symbol_idx }; } } return symlinks; diff --git a/src/msvc_crt/msvc_crt.h b/src/msvc_crt/msvc_crt.h index 4ad6354c..9932ee73 100644 --- a/src/msvc_crt/msvc_crt.h +++ b/src/msvc_crt/msvc_crt.h @@ -33,6 +33,9 @@ // PE_TLSHeader32 or PE_TLSHeader64, according to machine type. #define MSCRT_TLS_SYMBOL_NAME "_tls_used" +// MSVC C++ vftable symbols start with this decorated-name prefix. +#define MSCRT_VFTABLE_SYMBOL_PREFIX "??_7" + //////////////////////////////// // feature flags in absolute symbol @feat.00 From f3d2792fb808e6e5900bfe81b88e1fcc578cdce7 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 13 Jul 2026 03:00:07 -0700 Subject: [PATCH 44/90] simplify COMDAT leader symbol patching Collapse COMDAT leader symbol patching to a single raw-symbol write path and make the same-object versus cross-object symlink handling explicit. Preserve public symbol offsets for ICF folds while avoiding cross-object section number rewrites. --- src/linker/lnk.c | 51 ++-- src/linker/tests/linker_tests.c | 451 ++++++++++++++++++++++++++++++++ 2 files changed, 483 insertions(+), 19 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 4d8f57cf..c7f7cec2 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -3674,52 +3674,65 @@ 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); - COFF_SymbolValueInterpType interp = coff_interp_symbol(symbol.section_number, symbol.value, symbol.storage_class); + COFF_SymbolValueInterpType interp = coff_interp_from_parsed_symbol(symbol); if (interp != COFF_SymbolValueInterp_Regular) { continue; } LNK_ObjSymbolRef symlink_ref = {0}; if ( ! lnk_obj_get_comdat_symlink(obj, symbol.section_number, &symlink_ref)) { continue; } - COFF_ParsedSymbol parsed_symlink = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx); - if (symlink_ref.obj == obj && parsed_symlink.section_number == symbol.section_number) { continue; } + COFF_ParsedSymbol leader_symbol = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx); + if (symlink_ref.obj == obj && leader_symbol.section_number == symbol.section_number) { continue; } - B32 is_static_comdat_leader = symbol.storage_class == COFF_SymStorageClass_Static && - obj->comdats[symbol.section_number-1] == symbol_idx; + B32 is_external = symbol.storage_class == COFF_SymStorageClass_External; + B32 is_same_obj = symlink_ref.obj == obj; - if (symbol.storage_class == COFF_SymStorageClass_External || is_static_comdat_leader) { - // COMDAT leader may be at a different offset, so update this symbol with leader's offset - U32 section_number = symbol.section_number; - U32 value = parsed_symlink.value; + 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 { + if (is_external && str8_match(symbol.name, leader_symbol.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 = (U16)section_number; + symbol16->section_number = safe_cast_u16(section_number); symbol16->value = value; } } } ProfEnd(); - - ProfEnd(); - - scratch_end(scratch); } internal int diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index ea749f8f..8c74a606 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -3132,6 +3132,457 @@ 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); +} + +TEST(zero_length_comdat_referenced_by_reloc) +{ + if (t_id_linker() != Linker_radlink) { return; } + + U8 data[8] = {0}; + + T_Ok(t_write_entry_obj()); + T_Ok(t_write_def_obj("leader.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 }, + {0} + }, + .symbols = (T_COFF_DefSymbol[]){ + T_COFF_DefSymbol_Secdef("empty", COFF_ComdatSelect_Any), + T_COFF_DefSymbol_Extern("EMPTY", "empty", 0), + {0} + } + })); + + T_Ok(t_write_def_obj("ref.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 }, + { "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 leader.obj ref.obj"); + T_Ok(g_last_exit_code == 0); +} + +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){ From 048b93b1660c01a16145379be0ce605fd39e0708 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 13 Jul 2026 03:04:49 -0700 Subject: [PATCH 45/90] remove empty sections if it does not have any section contributions too --- src/linker/lnk.c | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index c7f7cec2..b2a9fa1a 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -5199,24 +5199,19 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT // ensure determinism by sorting section contribs in chunks by input index ProfScope("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); tp_for_parallel(tp, 0, total_chunk_count, lnk_sort_contribs_task, &task); } @@ -5303,30 +5298,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); From 8ec931d72424485124b20df3f71f499b539c181b Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 13 Jul 2026 03:06:40 -0700 Subject: [PATCH 46/90] fix import data directory bounds Include the null import descriptor in the import directory size and derive the IAT directory RVA from the first import descriptor of the start of the .idata contribution. --- src/linker/lnk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index b2a9fa1a..1e488200 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -5564,14 +5564,14 @@ 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); } } From d1b0bc0af75b40b165a81c8575be8fadd3f91653 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 13 Jul 2026 12:05:21 -0700 Subject: [PATCH 47/90] refine COMDAT symlink handling Stop treating COMDAT section-definition symbols as symlink anchors. Keep zero-sized COMDAT sections only when they have an owned symlink, so referenced empty COMDAT symbols still resolve while marker sections like .gsspr/.gssep are skipped. Update COMDAT/ICF tests to cover zero-sized COMDAT relocation behavior and the adjusted removed-COMDAT handling. --- src/linker/lnk.c | 24 +++++++------ src/linker/lnk_obj.c | 36 +++++++++---------- src/linker/tests/linker_tests.c | 64 ++++++++++++++++----------------- 3 files changed, 62 insertions(+), 62 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 1e488200..2c3dfb44 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -3414,8 +3414,10 @@ lnk_section_definition_is_before(void *raw_a, void *raw_b) } internal B32 -lnk_should_gather_section(COFF_SectionHeader *sect_header, COFF_SectionFlags sect_flags) +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; @@ -3426,13 +3428,19 @@ lnk_should_gather_section(COFF_SectionHeader *sect_header, COFF_SectionFlags sec return 0; } - // non-COMDAT empty sections do not need image contributions + // empty COMDATs with symlinks can still anchor symbols at offset zero if (sect_header->fsize == 0) { if (~sect_flags & COFF_SectionFlag_LnkCOMDAT) { return 0; } - // keep zero-size COMDATs so symbols at offset zero still get mapped + 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; @@ -3457,7 +3465,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) for EachIndex(sect_idx, obj->header.section_count_no_null) { COFF_SectionHeader *sect_header = §ion_table[sect_idx]; - if ( ! lnk_should_gather_section(sect_header, obj->section_flags[sect_idx])) { continue; } + if ( ! lnk_should_gather_section(obj, sect_idx, sect_header)) { continue; } Temp temp = temp_begin(scratch.arena); @@ -3591,7 +3599,7 @@ THREAD_POOL_TASK_FUNC(lnk_gather_sections_task) COFF_SectionFlags sect_flags = obj->section_flags[sect_idx]; task->sect_map[obj_idx][sect_idx] = sc; - if ( ! lnk_should_gather_section(sect_header, sect_flags)) { continue; } + if ( ! lnk_should_gather_section(obj, sect_idx, sect_header)) { continue; } LNK_SectionContribChunk *sc_chunk = 0; { @@ -3849,14 +3857,10 @@ THREAD_POOL_TASK_FUNC(lnk_patch_regular_symbols_task) 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); - 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; diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index 6077b994..6c292058 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -486,19 +486,15 @@ lnk_symlinks_from_obj(Arena *arena, LNK_SymbolTable *symtab, LNK_Obj *obj) if (symbol.storage_class == COFF_SymStorageClass_External && symbol.aux_symbol_count == 0) { B32 can_set_symlink = (symlink->obj == 0 || symbol.value == 0); if (!can_set_symlink && symlink->obj == obj) { - // NOTE: The section definition is a fallback symlink. Public symbols inside a - // discarded COMDAT need to target the selected public symbol's offset, - // not the selected section base plus the discarded symbol's offset. - // MSVC vftables are also preferred over other public symbols in the - // same section so ICF can keep vftable COMDATs in their own color space. - COFF_ParsedSymbol symlink_symbol = lnk_parsed_symbol_from_coff_symbol_idx(symlink->obj, symlink->symbol_idx); - B32 symlink_is_section_defn = (symlink_symbol.section_number == symbol.section_number && - symlink_symbol.storage_class == COFF_SymStorageClass_Static && - symlink_symbol.aux_symbol_count > 0); - B32 symlink_is_non_vftable = (symlink_symbol.section_number == symbol.section_number && - !str8_starts_with(symlink_symbol.name, str8_lit(MSCRT_VFTABLE_SYMBOL_PREFIX))); - B32 symbol_is_vftable = str8_starts_with(symbol.name, str8_lit(MSCRT_VFTABLE_SYMBOL_PREFIX)); - can_set_symlink = (symlink_is_section_defn || (symbol_is_vftable && symlink_is_non_vftable)); + COFF_ParsedSymbol leader = lnk_parsed_symbol_from_coff_symbol_idx(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(leader.name, 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) { @@ -507,15 +503,17 @@ lnk_symlinks_from_obj(Arena *arena, LNK_SymbolTable *symtab, LNK_Obj *obj) *symlink = lnk_ref_from_symbol(link_symbol->symbol); } } - - continue; } - - // section definitions - if (symlink->obj == 0 && symbol.storage_class == COFF_SymStorageClass_Static && symbol.aux_symbol_count > 0) { - *symlink = (LNK_ObjSymbolRef){ obj, symbol_idx }; + // 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; } diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 8c74a606..20415919 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -3510,30 +3510,18 @@ TEST(icf_vftable_external_symbol_at_nonzero_offset) 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) { - if (t_id_linker() != Linker_radlink) { return; } - U8 data[8] = {0}; T_Ok(t_write_entry_obj()); - T_Ok(t_write_def_obj("leader.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 }, - {0} - }, - .symbols = (T_COFF_DefSymbol[]){ - T_COFF_DefSymbol_Secdef("empty", COFF_ComdatSelect_Any), - T_COFF_DefSymbol_Extern("EMPTY", "empty", 0), - {0} - } - })); - T_Ok(t_write_def_obj("ref.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 }, + { "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} @@ -3547,8 +3535,26 @@ TEST(zero_length_comdat_referenced_by_reloc) } })); - t_invoke_linkerf("/subsystem:console /entry:entry /out:a.exe /opt:ref entry.obj leader.obj ref.obj"); + 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) @@ -4270,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) @@ -8874,25 +8880,17 @@ TEST(icf_llvm_addrsig) 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); - - String8 a_path = t_make_file_path(arena, str8_lit("a.exe")); t_invoke(a_path, str8_zero(), max_U64); - if (t_id_linker() == Linker_radlink) { - T_Ok(g_last_exit_code == 1); - } else { - T_Ok(g_last_exit_code == 0); - } - - if (t_id_linker() == Linker_radlink) { - 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); - } + 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 From abfea7c33abaeacfd69604f91f5587c54e5a69d4 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 13 Jul 2026 12:11:20 -0700 Subject: [PATCH 48/90] update CHANGELOG.md --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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 From b84d32d9fb3ea1002dba849349fb56ce27800b55 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 13 Jul 2026 19:57:53 -0700 Subject: [PATCH 49/90] optimize ICF refinement and COMDAT handling Cache immutable section and relocation metadata before color refinement, then reuse generated-tagged concurrent tables. Parallelize grouping and fold marking. Include associative COMDAT descendants in contribution hashes and canonicalize relocation targets to selected COMDAT leaders. --- src/linker/base_ext/base_arrays.c | 18 +- src/linker/base_ext/base_arrays.h | 5 +- src/linker/lnk.c | 741 +++++++++++++++++++----------- src/linker/lnk_obj.c | 39 ++ src/linker/lnk_obj.h | 1 + src/linker/tests/linker_tests.c | 118 ++++- 6 files changed, 659 insertions(+), 263 deletions(-) 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 2c3dfb44..5e69365c 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -465,7 +465,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 @@ -2740,27 +2740,13 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) 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); - - // 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); - } + for EachNode(section_n, U32Node, associated_sections.first) { + U32 section_number = section_n->data; COFF_SectionFlags section_flags = ref_symbol.obj->section_flags[section_number-1]; @@ -2790,8 +2776,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) } batch->v[batch->count++] = refs; - - } while (stack); + } temp_end(temp); } @@ -2976,6 +2961,17 @@ lnk_icf_color_space_from_section(LNK_Obj *obj, U32 sect_idx) 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 @@ -2986,16 +2982,56 @@ lnk_icf_color_space_from_section(LNK_Obj *obj, U32 sect_idx) THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) { ProfBeginFunction(); - Temp scratch = scratch_begin(&arena,1); + + 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_id == 0) { - lnk_log(LNK_Log_Debug, "/OPT:ICF:"); - } - 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) { @@ -3038,6 +3074,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) } } } + ProfEnd(); barrier_wait(tp->barrier); } @@ -3045,224 +3082,405 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) // step 1: fill out color map and contributions // + // alloc total section counter U64 *contrib_counts = 0; - { - // alloc total section counter - if (task_id == 0) { - contrib_counts = push_array(scratch.arena, U64, task->objs_count); - } - tp_broadcast(&contrib_counts); + if (task_id == 0) { + contrib_counts = push_array(scratch.arena, U64, task->objs_count); + } + tp_broadcast(&contrib_counts); - // count contributions - for EachIndex(i, task->obj_indices[task_id].count) { - U64 obj_idx = task->obj_indices[task_id].v[i]; - LNK_Obj *obj = objs[obj_idx]; - for EachIndex(sect_idx, obj->header.section_count_no_null) { - if (lnk_icf_color_space_from_section(obj, sect_idx)) { - contrib_counts[obj_idx] += 1; - } + 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; } } - barrier_wait(tp->barrier); } + ProfEnd(); + barrier_wait(tp->barrier); - typedef struct { - struct { - U128 hash; - U64 old_color; - } key; - U32 obj_idx; - U32 sect_idx; - } Contrib; - - U64 contrib_count = sum_array_u64(task->objs_count, contrib_counts); - U64 *contrib_offsets = 0; - U64 **color_map = 0; - Contrib *contribs = 0; - U32 *is_part_stable = 0; - U64 *next_color = 0; + 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) { - contrib_offsets = offsets_from_counts_array_u64(scratch.arena, contrib_counts, task->objs_count); - color_map = push_array(scratch.arena, U64 *, task->objs_count); + 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); } - contribs = push_array(scratch.arena, Contrib, contrib_count); - is_part_stable = push_array(scratch.arena, U32, 1); - next_color = push_array(scratch.arena, U64, 1); - *next_color = LNK_ICF_ColorSpace_COUNT; + + 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 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 unique color to sections that are not foldable - color_map[obj_idx][sect_idx] = ins_atomic_u64_inc_eval(next_color); + // 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; } - else { - // compute contribution index - U64 contrib_idx = contrib_offsets[obj_idx] + cursor++; - // seed foldable sections with a content-derived color - COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, sect_idx + 1); - String8 section_data = str8_substr(obj->data, r1u64s(section_header->foff, section_header->fsize)); - XXH3_state_t hasher; - XXH3_64bits_reset(&hasher); - XXH3_64bits_update(&hasher, &color_space, sizeof(color_space)); - XXH3_64bits_update(&hasher, section_data.str, section_data.size); + // 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, + }; - // fill out contribution - contribs[contrib_idx] = (Contrib){ - .obj_idx = safe_cast_u32(obj->input_idx), - .sect_idx = safe_cast_u32(sect_idx), - }; + 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); - // assign content-derived starting color value - color_map[obj_idx][sect_idx] = XXH3_64bits_digest(&hasher) | (1ull << 63); + 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(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(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); - // reset color status tracker + 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); - // compute colored hashes - for EachIndex(i, task->obj_indices[task_id].count) { - U64 obj_idx = task->obj_indices[task_id].v[i]; - LNK_Obj *obj = objs[obj_idx]; - COFF_SectionHeader *section_table = lnk_coff_section_table_from_obj(obj); - U64 contrib_cursor = 0; - for EachIndex(sect_idx, obj->header.section_count_no_null) { - LNK_ICF_ColorSpace color_space = lnk_icf_color_space_from_section(obj, sect_idx); - if (color_space == LNK_ICF_ColorSpace_Null) { continue; } + // 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; - // parse relocations - COFF_SectionHeader *section_header = section_table + sect_idx; - COFF_RelocArray relocs = lnk_coff_relocs_from_section_header(obj, section_header); + 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]; - U64 contrib_idx = contrib_offsets[obj_idx] + contrib_cursor; - contrib_cursor += 1; - contribs[contrib_idx].key.old_color = color_map[obj_idx][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)); - // parse section data - String8 section_data = str8_substr(obj->data, r1u64s(section_header->foff, section_header->fsize)); + // insert the colored hash into the concurrent table + contrib->key.hash = hash; - blake3_hasher hasher; blake3_hasher_init(&hasher); + 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); - // mix section non-recursive properties - blake3_hasher_update(&hasher, section_data.str, section_data.size); - blake3_hasher_update(&hasher, &relocs.count, sizeof(relocs.count)); - blake3_hasher_update(&hasher, &color_space, sizeof(color_space)); + 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; + } - for EachIndex(reloc_idx, relocs.count) { - COFF_Reloc *r = &relocs.v[reloc_idx]; + if (state == initializing_state) { + do { state = ins_atomic_u64_eval(&color_slot->state); } while (state == initializing_state); + continue; + } - // resolve symbol referenced by the relocation - LNK_ObjSymbolRef target_ref = { .obj = obj, .symbol_idx = r->isymbol }; - B32 is_symbol_found = lnk_resolve_reloc_target_symbol(scratch.arena, task->symtab, target_ref, str8_lit("/OPT:ICF"), &target_ref); + Assert(state == ready_state); - COFF_SymbolValueInterpType target_interp; - U64 target_id; - U32 target_value; + 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; + } - if (is_symbol_found) { - // parse COFF symbol and interpret the target symbol kind - COFF_ParsedSymbol target_symbol = lnk_parsed_symbol_from_coff_symbol_idx(target_ref.obj, target_ref.symbol_idx); - target_interp = coff_interp_from_parsed_symbol(target_symbol); + color_slot_idx = (color_slot_idx + 1) & (color_table.slots_count - 1); + } + } + ProfEnd(); + barrier_wait(tp->barrier); - switch (target_interp) { - case COFF_SymbolValueInterp_Regular: { - // color relocation with the referenced section - target_id = color_map[target_ref.obj->input_idx][target_symbol.section_number - 1]; - target_value = target_symbol.value; - } break; - default: { - // color relocation with unique symbol ref - target_id = Compose64Bit(target_ref.obj->input_idx, target_ref.symbol_idx); - target_value = target_symbol.value; - } break; + // 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; } - } else { - // color relocation with unique symbol ref - target_interp = max_U32; - target_id = Compose64Bit(obj_idx, r->isymbol); - target_value = 0; + continue; } - // mix relocation properties - blake3_hasher_update(&hasher, &r->apply_off, sizeof(r->apply_off)); - blake3_hasher_update(&hasher, &r->type, sizeof(r->type)); - blake3_hasher_update(&hasher, &target_interp, sizeof(target_interp)); - blake3_hasher_update(&hasher, &target_id, sizeof(target_id)); - blake3_hasher_update(&hasher, &target_value, sizeof(target_value)); + 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); } - - // finalize section hash - U128 hash; - blake3_hasher_finalize(&hasher, (U8*)&hash, sizeof(hash)); - - // update contribution hash and color - contribs[contrib_idx].key.hash = hash; + slot->old_color_slot_idx = old_slot_idx; } } + ProfEnd(); barrier_wait(tp->barrier); - // group sections by (color, hash) and handle color splits + // 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) { - Temp temp = temp_begin(scratch.arena); + ProfBegin("Prefix color splits"); - HashMap new_color_hm = {0}; - HashMap old_color_hm = {0}; - U64 start_next_color = *next_color; - - for EachIndex(contrib_idx, contrib_count) { - Contrib *contrib = &contribs[contrib_idx]; - U64 *new_color_ptr = hash_map_search_string_u64(&new_color_hm, str8_struct(&contrib->key)); - if (new_color_ptr == 0) { - U64 color; - if (hash_map_search_u64_u64(&old_color_hm, contrib->key.old_color) == 0) { - color = contrib->key.old_color; - hash_map_push_u64_u64(temp.arena, &old_color_hm, contrib->key.old_color, color); - } else { - // generate a new color for the hash - color = ++*next_color; - *is_part_stable = 0; - } - new_color_ptr = &hash_map_push_string_u64(temp.arena, &new_color_hm, str8_struct(&contrib->key), color)->v.value.value_u64; - } - color_map[contrib->obj_idx][contrib->sect_idx] = *new_color_ptr; + U64 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); - U64 split_count = *next_color - start_next_color; - lnk_log(LNK_Log_Debug, " Round %llu found %S splits", iter_count, str8_from_count(scratch.arena, split_count)); + lnk_log(LNK_Log_Debug, " Round %llu found %S splits", iter_count, str8_from_count(scratch.arena, total_split_count)); - temp_end(temp); + 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; } } @@ -3272,67 +3490,75 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) // step 3: flag folded sections for removal // - if (task_id == 0) { - HashMap leader_hm = { 0 }; - for EachIndex(contrib_idx, contrib_count) { - Contrib *contrib = &contribs[contrib_idx]; - U64 color = color_map[contrib->obj_idx][contrib->sect_idx]; - if (hash_map_search_u64_raw(&leader_hm, color) == 0) { - hash_map_push_u64_raw(scratch.arena, &leader_hm, color, contrib); - } + typedef struct { U64 count; U64 size; } FoldStats; + FoldStats *fold_stats = 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; } - typedef struct { U64 count; U64 size; } FoldStats; - FoldStats fold_stats[LNK_ICF_ColorSpace_COUNT] = {0}; + 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; } - for EachIndex(contrib_idx, contrib_count) { - Contrib *contrib = &contribs[contrib_idx]; - Contrib *leader = hash_map_search_u64_raw(&leader_hm, color_map[contrib->obj_idx][contrib->sect_idx]); - if (leader == 0 || leader == contrib) { continue; } - - LNK_Obj *contrib_obj = objs[contrib->obj_idx]; - LNK_Obj *leader_obj = objs[leader->obj_idx]; - - // update fold stats - if (lnk_get_log_status(LNK_Log_Debug)) { - COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(contrib_obj, contrib->sect_idx + 1); - LNK_ICF_ColorSpace color_space = lnk_icf_color_space_from_section(contrib_obj, contrib->sect_idx); - fold_stats[color_space].count += 1; - fold_stats[color_space].size += section_header->fsize; - } - - // update leader with largest alignment - U64 leader_align = coff_align_size_from_section_flags(leader_obj->section_flags[leader->sect_idx]); - U64 contrib_align = coff_align_size_from_section_flags(contrib_obj->section_flags[contrib->sect_idx]); - if (leader_align < contrib_align) { - leader_obj->section_flags[leader->sect_idx] &= ~(COFF_SectionFlag_AlignMask << COFF_SectionFlag_AlignShift); - leader_obj->section_flags[leader->sect_idx] |= coff_section_flag_from_align_size(contrib_align); - } - - // update COMDAT leader symbol link - Assert(leader_obj->comdats[leader->sect_idx] != max_U32); - LNK_ObjSymbolRef leader_symlink = { leader_obj, leader_obj->comdats[leader->sect_idx] }; - contrib_obj->symlinks[contrib->sect_idx + 1] = leader_symlink; - - // discard folded section - contrib_obj->section_flags[contrib->sect_idx] |= COFF_SectionFlag_LnkRemove; - - #if LNK_PARANOID - String8 section_name = lnk_obj_section_name_from_section_number(contrib_obj, contrib->sect_idx+1); - String8 leader_name = lnk_obj_section_name_from_section_number(leader_obj, leader->sect_idx+1); - lnk_log(LNK_Log_Debug, "fold %.*s[SECT%X \"%.*s\"] ==> %.*s[SECT%X \"%.*s\"]", str8_varg(lnk_loc_from_obj(scratch.arena, contrib_obj)), contrib->sect_idx+1, str8_varg(section_name), str8_varg(lnk_loc_from_obj(scratch.arena, leader_obj)), leader->sect_idx+1, str8_varg(leader_name)); - #endif + 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; } - if (lnk_get_log_status(LNK_Log_Debug)) { - U64 total_count = 0, total_size = 0; + 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) { - lnk_log(LNK_Log_Debug, " %-8S: %M, %.*s sections", lnk_string_from_icf_color_space(color_space), fold_stats[color_space].size, str8_varg(str8_from_count(scratch.arena, fold_stats[color_space].count))); - total_count += fold_stats[color_space].count; - total_size += fold_stats[color_space].size; + total_stats[color_space].count += worker_stats[color_space].count; + total_stats[color_space].size += worker_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))); } + + 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); @@ -3340,6 +3566,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) // 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]; @@ -3349,42 +3576,36 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) LNK_ObjSymbolRef symlink_ref = {0}; if (!lnk_obj_get_comdat_symlink(obj, section_number, &symlink_ref)) { continue; } - { - Temp temp = temp_begin(scratch.arena); - - HashMap seen_hm = {0}; - U64 hop_count = 0; - U64 hop_cap = 1024; - for(; hop_count < hop_cap; hop_count += 1) { - COFF_ParsedSymbol symlink_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx); - - LNK_ObjSymbolRef next_symlink_ref = {0}; - if (!lnk_obj_get_comdat_symlink(symlink_ref.obj, symlink_parsed.section_number, &next_symlink_ref)) { break; } - - if (MemoryMatchStruct(&next_symlink_ref, &symlink_ref)) { break; } - - if (hash_map_search_string_u64(&seen_hm, str8_struct(&next_symlink_ref)) != 0) { - lnk_error_obj(LNK_Error_IllData, obj, "recursive COMDAT symlink in SECT%X", section_number); - MemoryZeroStruct(&symlink_ref); - break; - } - - symlink_ref = next_symlink_ref; - hash_map_push_string_u64(temp.arena, &seen_hm, str8_copy(temp.arena, str8_struct(&symlink_ref)), 1); - } - if (hop_count >= hop_cap) { - lnk_error_obj(LNK_Error_IllData, obj, "failed to flatten symlink for SECT%X; max number of hops reached", section_number); + Temp temp = temp_begin(scratch.arena); + HashMap seen_hm = {0}; + U64 hop_count = 0; + U64 hop_cap = 1024; + for(; hop_count < hop_cap; hop_count += 1) { + COFF_ParsedSymbol symlink_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx); + LNK_ObjSymbolRef next_symlink_ref = {0}; + if (!lnk_obj_get_comdat_symlink(symlink_ref.obj, symlink_parsed.section_number, &next_symlink_ref)) { break; } + if (MemoryMatchStruct(&next_symlink_ref, &symlink_ref)) { break; } + if (hash_map_search_string_u64(&seen_hm, str8_struct(&next_symlink_ref)) != 0) { + lnk_error_obj(LNK_Error_IllData, obj, "recursive COMDAT symlink in SECT%X", section_number); MemoryZeroStruct(&symlink_ref); + break; } - - temp_end(temp); + 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(); } @@ -3392,16 +3613,16 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) 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); - - ProfScope("/OPT:ICF") - { - LNK_OptTask task = { .symtab = symtab, .config = config, .objs = objs, .objs_count = objs_count, .obj_indices = obj_indices }; - tp_for_parallel(tp, 0, tp->worker_count, lnk_opt_icf_task, &task); - } + 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 diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index 6c292058..baa482f7 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -517,6 +517,45 @@ lnk_symlinks_from_obj(Arena *arena, LNK_SymbolTable *symtab, LNK_Obj *obj) 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) { diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index 339f2152..1cba2e1c 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -143,6 +143,7 @@ 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 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 ------------------------------------------------ diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 20415919..db7da828 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -8083,6 +8083,123 @@ TEST(icf_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}; + + 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[] = { @@ -9350,4 +9467,3 @@ TEST(lib_member_reloc_apply_off_out_of_bounds) T_Ok(g_last_exit_code != 0); } #endif - From 58b5b3d92d57d8b025ad7192fc415603a5a68dff Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Tue, 14 Jul 2026 13:37:40 -0700 Subject: [PATCH 50/90] search chunks only grow while resolving libraries, so revisit only symbols appended since this library's last search --- src/linker/lnk.c | 48 +++++++++++++++++++++++++++++++++----------- src/linker/lnk.h | 2 +- src/linker/lnk_lib.h | 4 ++++ 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 5e69365c..77accc35 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1647,13 +1647,20 @@ 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; + 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_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) { U32 member_idx; if (lnk_search_lib(lib, symbol->name, &member_idx)) { @@ -1684,6 +1691,10 @@ THREAD_POOL_TASK_FUNC(lnk_search_lib_task) } } } + + // cache search cursors + lib->search_cursor_chunks[task_id] = end_chunk; + lib->search_cursor_indices[task_id] = end_count; } internal LNK_Lib * @@ -1819,19 +1830,32 @@ 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); + + // 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}; diff --git a/src/linker/lnk.h b/src/linker/lnk.h index 86485831..e4a80205 100644 --- a/src/linker/lnk.h +++ b/src/linker/lnk.h @@ -205,6 +205,7 @@ typedef struct LNK_BaseRelocPageArray typedef struct { B32 search_anti_deps; + B32 reset_search_cursor; LNK_Link *link; HashMap *imports_hm; LNK_SymbolTable *symtab; @@ -410,4 +411,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_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 From cb6443ce4eb8ec896e986ebb6e1fabe2106a6e8f Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 16 Jul 2026 13:12:06 -0700 Subject: [PATCH 51/90] release file maps before exit in parallel before exit --- src/linker/lnk.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ src/linker/lnk.h | 2 ++ 2 files changed, 53 insertions(+) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 77accc35..4488ee8a 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1211,8 +1211,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(); @@ -1234,6 +1236,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) { @@ -6471,6 +6515,13 @@ lnk_run_linker(TP_Context *tp, TP_Arena *arena, LNK_Config *config) 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 + // wait for the thread to finish writing image to disk thread_join(image_write_thread, -1); diff --git a/src/linker/lnk.h b/src/linker/lnk.h index e4a80205..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; @@ -381,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 ------------------------------------------------------------ From bd2cbfc127d88f49b351a60f3f96cbcd307da49b Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Thu, 16 Jul 2026 13:05:34 -0700 Subject: [PATCH 52/90] gate for page pre-faulting on windows --- src/linker/lnk.c | 1 + src/win32/base/win32_base.c | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 4488ee8a..38b8f989 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 --------------------------------------------------------------- diff --git a/src/win32/base/win32_base.c b/src/win32/base/win32_base.c index d2f9171a..105cbb4d 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; } From 493241c1da9660547ec8d86933ca439a7362df66 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Tue, 14 Jul 2026 16:26:37 -0700 Subject: [PATCH 53/90] WIP light COFF symbol table parse --- src/coff/coff_parse.c | 60 ++++++++++++++------ src/coff/coff_parse.h | 9 ++- src/linker/lnk.c | 68 +++++++++++----------- src/linker/lnk_obj.c | 104 +++++++++++++++++++++++++--------- src/linker/lnk_obj.h | 12 +++- src/linker/lnk_symbol_table.c | 39 ++++++------- 6 files changed, 193 insertions(+), 99 deletions(-) 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/linker/lnk.c b/src/linker/lnk.c index 38b8f989..194eff03 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1703,7 +1703,7 @@ THREAD_POOL_TASK_FUNC(lnk_search_lib_task) for (U64 i = i_begin; i < i_end; i += 1) { 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_Undefined) { @@ -1722,11 +1722,11 @@ THREAD_POOL_TASK_FUNC(lnk_search_lib_task) 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_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_parsed.name, &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); } } @@ -2668,7 +2668,7 @@ lnk_resolve_reloc_target_symbol(Arena *arena, LNK_SymbolTable *symtab, LNK_ObjSy } // unpack symbol - COFF_ParsedSymbol result_parsed = lnk_parsed_symbol_from_coff_symbol_idx(result.obj, result.symbol_idx); + 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 @@ -2803,7 +2803,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) 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) { @@ -3018,8 +3018,8 @@ lnk_icf_color_space_from_section(LNK_Obj *obj, U32 sect_idx) // fold MSVC vftables separately from other read-only data LNK_ObjSymbolRef symlink_ref = {0}; if (lnk_obj_get_comdat_symlink(obj, sect_idx + 1, &symlink_ref)) { - COFF_ParsedSymbol symlink_symbol = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx); - if (str8_starts_with(symlink_symbol.name, str8_lit(MSCRT_VFTABLE_SYMBOL_PREFIX))) { + String8 symlink_name = lnk_symbol_name_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx); + if (str8_starts_with(symlink_name, str8_lit(MSCRT_VFTABLE_SYMBOL_PREFIX))) { result = LNK_ICF_ColorSpace_VFTable; } } @@ -3126,12 +3126,12 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) 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(target_ref.obj, target_ref.symbol_idx); + 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(obj, symbol_idx); + 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 { @@ -3283,7 +3283,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) 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(target_ref.obj, target_ref.symbol_idx); + 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; @@ -3297,7 +3297,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) 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(leader_ref.obj, leader_ref.symbol_idx); + 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; @@ -3650,7 +3650,7 @@ THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) U64 hop_count = 0; U64 hop_cap = 1024; for(; hop_count < hop_cap; hop_count += 1) { - COFF_ParsedSymbol symlink_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx); + 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; } @@ -3944,7 +3944,7 @@ THREAD_POOL_TASK_FUNC(lnk_set_comdat_leaders_contribs_task) LNK_ObjSymbolRef symlink_ref = {0}; if ( ! lnk_obj_get_comdat_symlink(obj, section_number, &symlink_ref)) { continue; } - COFF_ParsedSymbol symlink_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx); + 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(); @@ -3959,7 +3959,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) { @@ -3979,7 +3979,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_comdat_leaders_task) 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_from_parsed_symbol(symbol); if (interp != COFF_SymbolValueInterp_Regular) { continue; } @@ -3987,7 +3987,7 @@ THREAD_POOL_TASK_FUNC(lnk_patch_comdat_leaders_task) 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(symlink_ref.obj, symlink_ref.symbol_idx); + 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; @@ -4012,7 +4012,9 @@ THREAD_POOL_TASK_FUNC(lnk_patch_comdat_leaders_task) should_patch = 1; } } else { - if (is_external && str8_match(symbol.name, leader_symbol.name, 0)) { + 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; } @@ -4111,10 +4113,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) { @@ -4145,7 +4147,7 @@ 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; } @@ -4183,7 +4185,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); @@ -4193,7 +4195,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); @@ -4341,13 +4343,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; } @@ -4358,7 +4361,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; @@ -4447,7 +4450,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]; @@ -4862,7 +4865,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) { @@ -4885,7 +4888,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); @@ -4901,7 +4905,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; @@ -4929,7 +4933,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); } } } @@ -4955,7 +4959,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; } @@ -4983,7 +4987,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); } diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index baa482f7..6b497992 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -125,13 +125,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 +174,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 +189,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 +236,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 +275,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; @@ -335,6 +363,7 @@ 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; @@ -426,7 +455,7 @@ 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); switch (interp) { case COFF_SymbolValueInterp_Regular: { @@ -435,27 +464,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); 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); 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); 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); 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); lnk_symbol_table_push_(task->symtab, arena, worker_id, defn); } } break; @@ -473,7 +502,7 @@ lnk_symlinks_from_obj(Arena *arena, LNK_SymbolTable *symtab, LNK_Obj *obj) 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); + 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; } @@ -484,13 +513,14 @@ lnk_symlinks_from_obj(Arena *arena, LNK_SymbolTable *symtab, LNK_Obj *obj) // 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(symlink->obj, symlink->symbol_idx); + 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(leader.name, str8_lit(MSCRT_VFTABLE_SYMBOL_PREFIX)); - B32 current_is_vftable = str8_starts_with(symbol.name, str8_lit(MSCRT_VFTABLE_SYMBOL_PREFIX)); + 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 @@ -498,7 +528,7 @@ lnk_symlinks_from_obj(Arena *arena, LNK_SymbolTable *symtab, LNK_Obj *obj) } if (can_set_symlink) { - LNK_SymbolHashTrie *link_symbol = lnk_symbol_table_search_(symtab, symbol.name); + LNK_SymbolHashTrie *link_symbol = lnk_symbol_table_search_(symtab, symbol_name); if (link_symbol) { *symlink = lnk_ref_from_symbol(link_symbol->symbol); } @@ -741,7 +771,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; } @@ -757,19 +787,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 1cba2e1c..7655072e 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; @@ -148,7 +156,9 @@ internal U32List lnk_obj_collect_associated_sections(Arena *arena, LNK_ // --- 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 ea02e1d9..33557435 100644 --- a/src/linker/lnk_symbol_table.c +++ b/src/linker/lnk_symbol_table.c @@ -105,12 +105,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; @@ -338,9 +338,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 @@ -537,7 +537,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); } @@ -662,7 +663,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 @@ -671,7 +672,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) { @@ -682,19 +683,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 @@ -719,7 +720,7 @@ 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: { @@ -727,7 +728,7 @@ lnk_resolve_symbol(LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, LNK_ObjSymb *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) { @@ -737,7 +738,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 { @@ -745,12 +746,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; @@ -769,12 +770,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); From a779d3f1a5fb9293ffcf433291caf4e7b8ee0758 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sat, 18 Jul 2026 19:40:37 -0700 Subject: [PATCH 54/90] WIP assigned symbols --- src/linker/lnk.c | 3 + src/linker/lnk_debug_info.c | 181 +++++++++++++++++------------------- src/linker/lnk_debug_info.h | 31 ++++-- 3 files changed, 111 insertions(+), 104 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 194eff03..de1799ac 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1792,6 +1792,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) { @@ -2047,6 +2049,7 @@ lnk_link_inputs(TP_Context *tp, resolved_members_count = lnk_inputer_has_items(inputer); } + ProfEnd(); if (resolved_members_count == 0) { break; } } diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index eeb926fa..40b0f5b1 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) { @@ -1096,6 +1108,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,6 +1125,34 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, } input.symbol_input_ranges[i] = r1u64(begin, cursor); } + + + if (input.symbol_input_count) { + ProfBegin(""); + + 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(); @@ -1370,29 +1411,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 +1764,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 +1777,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 +1816,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 +1833,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 +1986,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 +2231,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; diff --git a/src/linker/lnk_debug_info.h b/src/linker/lnk_debug_info.h index 300971b9..455738ca 100644 --- a/src/linker/lnk_debug_info.h +++ b/src/linker/lnk_debug_info.h @@ -68,6 +68,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 +101,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 +127,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 +165,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 +189,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; @@ -252,7 +265,7 @@ 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); From 4cc0f4813bd8537505bbb73a1c81cdeaaee70f54 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 17 Jul 2026 16:00:01 -0700 Subject: [PATCH 55/90] WIP cache COFF symbol interp --- src/linker/lnk.c | 39 ++++++++------------ src/linker/lnk_obj.c | 11 +++--- src/linker/lnk_symbol_table.c | 68 ++++++++++++++++++++++++++++++----- src/linker/lnk_symbol_table.h | 22 ++++++++++-- 4 files changed, 98 insertions(+), 42 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index de1799ac..bddb1755 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1701,35 +1701,24 @@ THREAD_POOL_TASK_FUNC(lnk_search_lib_task) 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_ObjSymbolRef symbol_ref = lnk_ref_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); + LNK_Symbol *symbol = c->v[i].symbol; + LNK_SymbolSearchType search_type = lnk_search_type_from_symbol(symbol); - if (symbol_interp == COFF_SymbolValueInterp_Undefined) { + 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_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); - } - } + } 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); } } } @@ -1979,7 +1968,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); diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index 6b497992..ce86069d 100644 --- a/src/linker/lnk_obj.c +++ b/src/linker/lnk_obj.c @@ -457,6 +457,7 @@ THREAD_POOL_TASK_FUNC(lnk_input_coff_symbol_table) 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_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) { @@ -464,27 +465,27 @@ THREAD_POOL_TASK_FUNC(lnk_input_coff_symbol_table) if (*section.flags & COFF_SectionFlag_LnkRemove) { break; } - LNK_Symbol *defn = lnk_make_symbol(arena, lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx), 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, lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx), 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, lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx), 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, lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx), 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, lnk_symbol_name_from_coff_symbol_idx(obj, symbol_idx), 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; diff --git a/src/linker/lnk_symbol_table.c b/src/linker/lnk_symbol_table.c index 33557435..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) { @@ -357,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 @@ -561,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]; @@ -790,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); - From 134cfce7993b1ffaa7d80c0cac68b5bdf1050860 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Fri, 17 Jul 2026 16:55:24 -0700 Subject: [PATCH 56/90] merge debug info and llvm_addrsig finders into lnk_obj_initer to reduce time spent in barrier_wait --- src/linker/lnk_obj.c | 110 ++++++++++++++++++------------------------- src/linker/lnk_obj.h | 2 + 2 files changed, 47 insertions(+), 65 deletions(-) diff --git a/src/linker/lnk_obj.c b/src/linker/lnk_obj.c index ce86069d..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 & ~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); @@ -290,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; @@ -370,39 +378,10 @@ THREAD_POOL_TASK_FUNC(lnk_obj_initer) 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; - obj->llvm_addrsig_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; - } - } -} - -internal -THREAD_POOL_TASK_FUNC(lnk_obj_find_llvm_addrsig) -{ - LNK_Obj *obj = &((LNK_ObjNode *)raw_task)[task_id].data; - for EachIndex(sect_idx, obj->header.section_count_no_null) { - String8 section_name = lnk_obj_section_name_from_sect_idx(obj, sect_idx); - if (str8_match(section_name, str8_lit(".llvm_addrsig"), 0)) { - obj->llvm_addrsig_sect_idx = sect_idx; - break; - } - } + 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 * @@ -411,13 +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); - } - if (config->opt_icf == LNK_SwitchState_Yes) { - tp_for_parallel(tp, arena, inputs_count, lnk_obj_find_llvm_addrsig, 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; } diff --git a/src/linker/lnk_obj.h b/src/linker/lnk_obj.h index 7655072e..2f771741 100644 --- a/src/linker/lnk_obj.h +++ b/src/linker/lnk_obj.h @@ -112,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 From 97a1fe600664cb9dc7096db0bc277c3a09d212ee Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sat, 18 Jul 2026 19:38:01 -0700 Subject: [PATCH 57/90] radix sort PSI address map --- src/linker/lnk_debug_info.c | 8 +- src/linker/pdb_ext/pdb_builder.c | 281 +++++++++++++++++++++++++++++-- src/linker/tests/linker_tests.c | 32 ++++ 3 files changed, 306 insertions(+), 15 deletions(-) diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index 40b0f5b1..ef1e0dfc 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -1126,9 +1126,10 @@ 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(""); + 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)); @@ -1150,10 +1151,9 @@ lnk_make_code_view_input(TP_Context *tp, TP_Arena *tp_arena, LNK_Config *config, } radsort(input.symbol_patch_task, input.symbol_patch_task_count, lnk_symbol_input_task_is_before); + + ProfEnd(); } - - - ProfEnd(); } ProfEnd(); diff --git a/src/linker/pdb_ext/pdb_builder.c b/src/linker/pdb_ext/pdb_builder.c index c0608b20..74426f2d 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(); diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index db7da828..cabae47c 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -7114,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; From fcee35f3e9b7965ef0322a4ac56eea8994897050 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 20 Jul 2026 16:51:59 -0700 Subject: [PATCH 58/90] rename RAD_TypeHashAlg; extend /RAD_DEBUG_TYPE_HASH with XXHASH option --- src/linker/lnk.c | 12 +++- src/linker/lnk_config.c | 9 +-- src/linker/lnk_config.h | 6 +- src/linker/lnk_debug_info.c | 110 +++++++++++++++++++------------- src/linker/lnk_debug_info.h | 3 +- src/linker/lnk_hasher.c | 60 +++++++++++++++++ src/linker/lnk_hasher.h | 37 +++++++++++ src/linker/tests/linker_tests.c | 2 +- 8 files changed, 183 insertions(+), 56 deletions(-) create mode 100644 src/linker/lnk_hasher.c create mode 100644 src/linker/lnk_hasher.h diff --git a/src/linker/lnk.c b/src/linker/lnk.c index bddb1755..1a611829 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -103,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" @@ -116,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" @@ -150,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", @@ -173,6 +174,13 @@ lnk_make_default_cmd_line(Arena *arena, LNK_CmdLine user_cmd_line) (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", }; @@ -6696,7 +6704,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_config.c b/src/linker/lnk_config.c index d783784c..eb08ca3b 100644 --- a/src/linker/lnk_config.c +++ b/src/linker/lnk_config.c @@ -102,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." }, @@ -2325,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); } @@ -2954,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 d469a413..9fd5057f 100644 --- a/src/linker/lnk_config.h +++ b/src/linker/lnk_config.h @@ -136,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, @@ -305,7 +305,6 @@ typedef enum LNK_TypeNameHashMode_Full, } LNK_TypeNameHashMode; - typedef struct LNK_Config { Arena *arena; @@ -411,7 +410,7 @@ 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; @@ -616,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 ef1e0dfc..65e42380 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -107,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; } @@ -320,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]; @@ -363,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(); @@ -404,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)) { @@ -421,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) { @@ -430,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)) { @@ -439,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)) { @@ -459,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) { @@ -479,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) { @@ -488,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); @@ -496,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); @@ -504,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); @@ -512,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]; @@ -524,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) { @@ -533,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) { @@ -542,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"); @@ -584,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); @@ -937,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]; @@ -1248,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 { @@ -1256,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 @@ -1273,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; } @@ -1285,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); @@ -1307,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); @@ -1324,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; } diff --git a/src/linker/lnk_debug_info.h b/src/linker/lnk_debug_info.h index 455738ca..28ecf301 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 { 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/tests/linker_tests.c b/src/linker/tests/linker_tests.c index cabae47c..66c25f58 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -7423,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); From a73e3e0790ec17c8e1630e11cc6e52298cc591d6 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 20 Jul 2026 00:53:14 -0700 Subject: [PATCH 59/90] use cycle tracking only on weak and undefined symbols * profile of /OPT:REF shows that early push to seen_hm dominates the pass; cost: 74.23ms -> 6.81ms (-90.8%) --- src/linker/lnk.c | 49 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 1a611829..1231ca3a 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2648,38 +2648,37 @@ lnk_obj_indices_from_section_counts(Arena *arena, U64 worker_count, LNK_Obj **ob internal B32 lnk_resolve_reloc_target_symbol(Arena *arena, LNK_SymbolTable *symtab, LNK_ObjSymbolRef symbol, String8 pass_name, LNK_ObjSymbolRef *resolved_symbol_out) { - B32 is_resolved = 1; - - Temp temp = temp_begin(arena); - HashMap seen_hm = {0}; - B32 keep_walking = 1; - LNK_ObjSymbolRef result = symbol; - do { - // detect cyclic chains - U64 symbol_key = ((U64)result.obj->input_idx << 32ull) | (U64)result.symbol_idx; - if (hash_map_search_u64_u64(&seen_hm, symbol_key) == 0) { - hash_map_push_u64_u64(temp.arena, &seen_hm, symbol_key, 1); - } else { - COFF_ParsedSymbol symbol_parsed = lnk_parsed_symbol_from_coff_symbol_idx(symbol.obj, symbol.symbol_idx); - lnk_error_obj(LNK_Warning_CyclicSymbol, symbol.obj, "symbol %S forms a cyclic chain (%S)", symbol_parsed.name, pass_name); - MemoryZeroStruct(&result); - is_resolved = 0; - break; - } - + 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)) { - keep_walking = (result_interp == COFF_SymbolValueInterp_Weak || result_interp == COFF_SymbolValueInterp_Undefined); - result = next_ref; - } else { - keep_walking = 0; + if (!lnk_resolve_symbol(symtab, result, &next_ref)) { + break; } - } while (keep_walking); + 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; From c96c9620d073b842aa9539a19d8d0053b3237d5b Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sun, 19 Jul 2026 20:05:25 -0700 Subject: [PATCH 60/90] WIP PDB Writer Thread --- src/linker/lnk.c | 9 +- src/linker/lnk_debug_info.c | 296 ++++++++++++++++++++++++++++++- src/linker/lnk_debug_info.h | 3 +- src/linker/pdb_ext/pdb_builder.c | 41 ++++- src/linker/pdb_ext/pdb_builder.h | 13 +- src/linker/tests/linker_tests.c | 2 +- 6 files changed, 343 insertions(+), 21 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 1231ca3a..eec4dad7 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -6424,10 +6424,9 @@ 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); - } + String8 pdb_output_path = config->debug_mode == LNK_DebugMode_Full ? config->pdb_name : str8_zero(); + String8 pdb_temp_output_path = config->debug_mode == LNK_DebugMode_Full ? config->temp_pdb_name : str8_zero(); + pdb_data = lnk_build_pdb(tp, arena, image_ctx.image_data, config, symtab, &cv, cv_types, pdb_output_path, pdb_temp_output_path, LNK_PDB_BuilderFlag_All); lnk_timer_end(LNK_Timer_Pdb); } @@ -6511,7 +6510,7 @@ 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); + String8List pdb_data = lnk_build_pdb(tp, arena, image_ctx.image_data, config, symtab, &stripped_cv, (LNK_MergedTypes){0}, str8_zero(), str8_zero(), 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); } diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index 65e42380..fbc943ab 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -3042,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 { @@ -3138,8 +3138,257 @@ THREAD_POOL_TASK_FUNC(lnk_push_dbi_sec_contrib_task) } } +typedef struct LNK_PdbWriteJob +{ + U64 file_off; + String8 data; +} LNK_PdbWriteJob; + +typedef struct LNK_PdbWriter +{ + Arena *queue_arena; + String8 path; + String8 temp_path; + String8 open_path; + File file; + GuardedRing *queue; + Thread thread; + MSF_StreamNumber *sealed_streams; + U64 sealed_stream_count; + U64 sealed_stream_cap; + U64 bytes_written; + B32 open_with_rename; + B32 is_open; + B32 write_failed; +} LNK_PdbWriter; + +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_writer_thread(void *raw_writer) +{ + ProfBeginFunction(); + LNK_PdbWriter *writer = raw_writer; + for (;;) { + LNK_PdbWriteJob 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.data.str == 0) { break; } + + U64 write_size = lnk_write_file(&writer->file, job.file_off, job.data.str, job.data.size); + if (write_size != job.data.size) { + writer->write_failed = 1; + } + writer->bytes_written += write_size; + } + ProfEnd(); +} + +internal B32 +lnk_pdb_writer_begin(Arena *arena, LNK_PdbWriter *writer, String8 path, String8 temp_path, U64 stream_cap) +{ + ProfBegin("PDB Writer Begin"); + + B32 is_ok = 1; + + writer->path = path; + writer->temp_path = temp_path; + writer->open_with_rename = (temp_path.size > 0); + writer->sealed_stream_cap = stream_cap; + writer->sealed_streams = push_array_no_zero(arena, MSF_StreamNumber, stream_cap); + + if (writer->open_with_rename) { + writer->file = lnk_file_open_with_rename_permissions(temp_path); + writer->open_path = temp_path; + } else { + lnk_open_file_write((char *)path.str, path.size, &writer->file, sizeof(writer->file)); + writer->open_path = path; + } + + writer->is_open = !file_match(writer->file, file_zero()); + if (!writer->is_open) { + lnk_error(LNK_Error_NoAccess, "don't have access to write to %S", path); + goto exit; + } + + if (writer->open_with_rename && !lnk_file_set_delete_on_close(writer->file, 1)) { + lnk_error(LNK_Error_IO, "failed to update file disposition on %S", writer->open_path); + } + + writer->queue_arena = arena_alloc(.reserve_size = MB(2), .commit_size = MB(2), .name = "PDB_WRITE_QUEUE"); + writer->queue = guarded_ring_alloc(writer->queue_arena, MB(1)); + writer->thread = thread_launch(lnk_pdb_writer_thread, writer); + + is_ok = 1; + exit:; + ProfEnd(); + return is_ok; +} + +internal void +lnk_pdb_writer_enqueue_job(LNK_PdbWriter *writer, U64 file_off, U8 *data, U64 size) +{ + ProfBegin("PDB Writer Enqueue Job"); + + if (writer->is_open && size > 0) { + LNK_PdbWriteJob job = { .file_off = file_off, .data = str8(data, size) }; + 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_pdb_writer_enqueue_stream(LNK_PdbWriter *writer, MSF_Context *msf, MSF_StreamNumber sn) +{ + if (!writer->is_open || sn == MSF_INVALID_STREAM_NUMBER) { return; } + Assert(writer->sealed_stream_count < writer->sealed_stream_cap); + writer->sealed_streams[writer->sealed_stream_count++] = sn; + + MSF_Stream *stream = msf_find_stream(msf, sn); + if (stream == 0 || 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_pdb_writer_enqueue_job(writer, (U64)run_first_pn * msf->page_size, 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_pdb_writer_enqueue_job(writer, (U64)run_first_pn * msf->page_size, run_data, run_size); + } +} + +internal void +lnk_pdb_writer_finalize_stream(void *user_data, MSF_Context *msf, MSF_StreamNumber sn) +{ + lnk_pdb_writer_enqueue_stream(user_data, msf, sn); +} + +internal B32 +lnk_pdb_writer_finish(LNK_PdbWriter *writer, MSF_Context *msf) +{ + if (!writer->is_open) { return 0; } + 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, writer->sealed_stream_count) { + MSF_Stream *stream = msf_find_stream(msf, writer->sealed_streams[i]); + if (stream == 0) { continue; } + 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_pdb_writer_enqueue_job(writer, run_first_pn * msf->page_size, run_data, run_size); + } + run_first_pn = pn; + run_data = page_data; + run_size = page_size; + } + } else if (run_data != 0) { + lnk_pdb_writer_enqueue_job(writer, run_first_pn * msf->page_size, run_data, run_size); + run_data = 0; + run_size = 0; + } + } + if (run_data != 0) { + lnk_pdb_writer_enqueue_job(writer, run_first_pn * msf->page_size, run_data, run_size); + } + + LNK_PdbWriteJob stop = {0}; + RingGuard guard = guarded_ring_open(writer->queue); + B32 is_written_stop = guarded_ring_write_struct_or_wait(&guard, &stop, max_U64); + guarded_ring_close(&guard); + Assert(is_written_stop); + thread_join(writer->thread, -1); + + B32 is_complete = !writer->write_failed && writer->bytes_written == save_size; + if (is_complete && writer->open_with_rename) { + if (!lnk_file_set_delete_on_close(writer->file, 0)) { + lnk_error(LNK_Error_IO, "failed to update file disposition on %S", writer->open_path); + is_complete = 0; + } else if (!lnk_file_rename(writer->file, writer->path)) { + lnk_error(LNK_Error_IO, "failed to rename %S -> %S", writer->temp_path, writer->path); + is_complete = 0; + } + } + + lnk_close_file(&writer->file); + guarded_ring_release(writer->queue); + arena_release(writer->queue_arena); + if (!is_complete) { + lnk_error(LNK_Error_IO, "incomplete PDB write, %M written, expected %M, file %S", writer->bytes_written, save_size, writer->path); + } else if (lnk_get_log_status(LNK_Log_IO_Write)) { + lnk_log(LNK_Log_IO_Write, "File \"%S\" %M written", writer->path, save_size); + } + scratch_end(scratch); + return is_complete; +} + 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) +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, String8 output_path, String8 temp_output_path, LNK_PDB_BuilderFlags builder_flags) { ProfBeginFunction(); Temp scratch = scratch_begin(tp_arena->v, tp_arena->count); @@ -3163,6 +3412,15 @@ lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config .image_section_file_section_numbers = push_array(scratch.arena, U64, task.image_section_table_count), }; + LNK_PdbWriter writer = {0}; + if (output_path.size > 0) { + lnk_pdb_writer_begin(scratch.arena, &writer, output_path, temp_output_path, cv->obj_count + 128); + } + PDB_BuildHooks build_hooks = { + .stream_finalize = lnk_pdb_writer_finalize_stream, + .user_data = &writer, + }; + // 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]; } @@ -3190,20 +3448,36 @@ 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(); + task.string_table_base_offset = task.pdb->info->strtab.size; + if (writer.is_open) { + 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); + for EachIndex(obj_idx, cv->obj_count) { + lnk_pdb_writer_enqueue_stream(&writer, 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); - ProfScope("Write Modules") tp_for_parallel(tp, 0, tp->worker_count, lnk_write_pdb_modules, &task); + lnk_pdb_writer_enqueue_stream(&writer, task.pdb->msf, task.pdb->dbi->publics_sn); + lnk_pdb_writer_enqueue_stream(&writer, task.pdb->msf, task.pdb->dbi->globals_sn); + lnk_pdb_writer_enqueue_stream(&writer, task.pdb->msf, task.pdb->dbi->symbols_sn); } - ProfBegin("Add string tables"); - pdb_strtab_add_cv_string_hash_table(&task.pdb->info->strtab, task.string_ht); - ProfEnd(); + if (!writer.is_open) { + ProfBegin("Add string tables"); + pdb_strtab_add_cv_string_hash_table(&task.pdb->info->strtab, task.string_ht); + ProfEnd(); + } if (builder_flags & LNK_PDB_BuilderFlag_SC) { ProfBegin("Build Section Contrib Map"); @@ -3265,7 +3539,11 @@ 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); + if (writer.is_open) { + pdb_build_dbi_info(tp, task.pdb, task.string_ht, 0, cv->is_stripped, &build_hooks); + } else { + pdb_build(tp, tp_arena, task.pdb, task.string_ht, 0, cv->is_stripped, 0); + } MSF_Error msf_err = msf_build(task.pdb->msf); if (msf_err != MSF_Error_OK) { @@ -3275,6 +3553,10 @@ lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config ProfBegin("Get Page Nodes"); String8List page_data_list = msf_get_page_data_nodes(tp_arena->v[0], task.pdb->msf); ProfEnd(); + + if (writer.is_open) { + lnk_pdb_writer_finish(&writer, task.pdb->msf); + } // NOTE: linker is about to exit so we can skip memory release // and let windows free memory since it does this faster diff --git a/src/linker/lnk_debug_info.h b/src/linker/lnk_debug_info.h index 28ecf301..3d9890ea 100644 --- a/src/linker/lnk_debug_info.h +++ b/src/linker/lnk_debug_info.h @@ -223,6 +223,7 @@ 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] @@ -273,4 +274,4 @@ internal void lnk_replace_type_names_with_hashes (TP_Context *tp, TP //////////////////////////////// // 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 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, String8 output_path, String8 temp_output_path, LNK_PDB_BuilderFlags builder_flags); diff --git a/src/linker/pdb_ext/pdb_builder.c b/src/linker/pdb_ext/pdb_builder.c index 74426f2d..767a4e30 100644 --- a/src/linker/pdb_ext/pdb_builder.c +++ b/src/linker/pdb_ext/pdb_builder.c @@ -3418,31 +3418,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 8abcd2d5..ec0b749a 100644 --- a/src/linker/pdb_ext/pdb_builder.h +++ b/src/linker/pdb_ext/pdb_builder.h @@ -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); @@ -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 66c25f58..58801bec 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -6843,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); From c629c5b10ee772a9469800ae88db30c2abc7a660 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Sun, 19 Jul 2026 22:18:56 -0700 Subject: [PATCH 61/90] refactor PDB background file output Add a shared multi-file background writer with lazy worker startup. Represent PDB output as a file artifact, keep PDB output state internal to the builder, and overlap deferred PDB writers with the file maps release. --- src/base/base_arena.c | 7 + src/base/base_ring.c | 4 + src/linker/lnk.c | 35 +++-- src/linker/lnk_debug_info.c | 250 ++++++++++-------------------------- src/linker/lnk_debug_info.h | 9 +- src/linker/lnk_io.c | 231 +++++++++++++++++++++++++++++++++ src/linker/lnk_io.h | 25 ++++ src/linux/base/linux_base.c | 2 + src/win32/base/win32_base.c | 2 + 9 files changed, 368 insertions(+), 197 deletions(-) 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_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/linker/lnk.c b/src/linker/lnk.c index eec4dad7..3581d12f 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -182,6 +182,9 @@ lnk_make_default_cmd_line(Arena *arena, LNK_CmdLine user_cmd_line) // 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[] = { @@ -6366,6 +6369,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 // @@ -6410,12 +6418,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], @@ -6424,16 +6431,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); } - String8 pdb_output_path = config->debug_mode == LNK_DebugMode_Full ? config->pdb_name : str8_zero(); - String8 pdb_temp_output_path = config->debug_mode == LNK_DebugMode_Full ? config->temp_pdb_name : str8_zero(); - pdb_data = lnk_build_pdb(tp, arena, image_ctx.image_data, config, symtab, &cv, cv_types, pdb_output_path, pdb_temp_output_path, LNK_PDB_BuilderFlag_All); + + 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); @@ -6441,8 +6450,6 @@ lnk_run_linker(TP_Context *tp, TP_Arena *arena, LNK_Config *config) lnk_timer_end(LNK_Timer_Rdi); } - - temp_end(huge_arena_temp); } // @@ -6510,8 +6517,8 @@ 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}, str8_zero(), str8_zero(), 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); @@ -6525,6 +6532,10 @@ lnk_run_linker(TP_Context *tp, TP_Arena *arena, LNK_Config *config) 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); diff --git a/src/linker/lnk_debug_info.c b/src/linker/lnk_debug_info.c index fbc943ab..e217d057 100644 --- a/src/linker/lnk_debug_info.c +++ b/src/linker/lnk_debug_info.c @@ -3138,29 +3138,14 @@ THREAD_POOL_TASK_FUNC(lnk_push_dbi_sec_contrib_task) } } -typedef struct LNK_PdbWriteJob +typedef struct { - U64 file_off; - String8 data; -} LNK_PdbWriteJob; - -typedef struct LNK_PdbWriter -{ - Arena *queue_arena; - String8 path; - String8 temp_path; - String8 open_path; - File file; - GuardedRing *queue; - Thread thread; - MSF_StreamNumber *sealed_streams; - U64 sealed_stream_count; - U64 sealed_stream_cap; - U64 bytes_written; - B32 open_with_rename; - B32 is_open; - B32 write_failed; -} LNK_PdbWriter; + LNK_BackgroundFileWriter *writer; + LNK_BackgroundFile *file; + MSF_StreamNumber *sealed_streams; + U64 sealed_stream_count; + U64 sealed_stream_cap; +} LNK_PdbOutput; typedef struct LNK_MsfPageCursor { @@ -3186,94 +3171,15 @@ lnk_msf_data_from_pn(LNK_MsfPageCursor *cursor, MSF_Context *msf, MSF_PageNumber } internal void -lnk_pdb_writer_thread(void *raw_writer) +lnk_pdb_output_enqueue_stream(LNK_PdbOutput *output, MSF_Context *msf, MSF_StreamNumber sn) { - ProfBeginFunction(); - LNK_PdbWriter *writer = raw_writer; - for (;;) { - LNK_PdbWriteJob 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.data.str == 0) { break; } - - U64 write_size = lnk_write_file(&writer->file, job.file_off, job.data.str, job.data.size); - if (write_size != job.data.size) { - writer->write_failed = 1; - } - writer->bytes_written += write_size; - } - ProfEnd(); -} - -internal B32 -lnk_pdb_writer_begin(Arena *arena, LNK_PdbWriter *writer, String8 path, String8 temp_path, U64 stream_cap) -{ - ProfBegin("PDB Writer Begin"); - - B32 is_ok = 1; - - writer->path = path; - writer->temp_path = temp_path; - writer->open_with_rename = (temp_path.size > 0); - writer->sealed_stream_cap = stream_cap; - writer->sealed_streams = push_array_no_zero(arena, MSF_StreamNumber, stream_cap); - - if (writer->open_with_rename) { - writer->file = lnk_file_open_with_rename_permissions(temp_path); - writer->open_path = temp_path; - } else { - lnk_open_file_write((char *)path.str, path.size, &writer->file, sizeof(writer->file)); - writer->open_path = path; - } - - writer->is_open = !file_match(writer->file, file_zero()); - if (!writer->is_open) { - lnk_error(LNK_Error_NoAccess, "don't have access to write to %S", path); - goto exit; - } - - if (writer->open_with_rename && !lnk_file_set_delete_on_close(writer->file, 1)) { - lnk_error(LNK_Error_IO, "failed to update file disposition on %S", writer->open_path); - } - - writer->queue_arena = arena_alloc(.reserve_size = MB(2), .commit_size = MB(2), .name = "PDB_WRITE_QUEUE"); - writer->queue = guarded_ring_alloc(writer->queue_arena, MB(1)); - writer->thread = thread_launch(lnk_pdb_writer_thread, writer); - - is_ok = 1; - exit:; - ProfEnd(); - return is_ok; -} - -internal void -lnk_pdb_writer_enqueue_job(LNK_PdbWriter *writer, U64 file_off, U8 *data, U64 size) -{ - ProfBegin("PDB Writer Enqueue Job"); - - if (writer->is_open && size > 0) { - LNK_PdbWriteJob job = { .file_off = file_off, .data = str8(data, size) }; - 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_pdb_writer_enqueue_stream(LNK_PdbWriter *writer, MSF_Context *msf, MSF_StreamNumber sn) -{ - if (!writer->is_open || sn == MSF_INVALID_STREAM_NUMBER) { return; } - Assert(writer->sealed_stream_count < writer->sealed_stream_cap); - writer->sealed_streams[writer->sealed_stream_count++] = 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); - if (stream == 0 || stream->page_list.count == 0) { return; } + Assert(stream != 0); + if (stream->page_list.count == 0) { return; } LNK_MsfPageCursor cursor = { .node = msf->page_data_list.first, @@ -3290,7 +3196,7 @@ lnk_pdb_writer_enqueue_stream(LNK_PdbWriter *writer, MSF_Context *msf, MSF_Strea run_size += msf->page_size; } else { if (run_data != 0) { - lnk_pdb_writer_enqueue_job(writer, (U64)run_first_pn * msf->page_size, run_data, run_size); + 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; @@ -3298,28 +3204,27 @@ lnk_pdb_writer_enqueue_stream(LNK_PdbWriter *writer, MSF_Context *msf, MSF_Strea } } if (run_data != 0) { - lnk_pdb_writer_enqueue_job(writer, (U64)run_first_pn * msf->page_size, run_data, run_size); + 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_writer_finalize_stream(void *user_data, MSF_Context *msf, MSF_StreamNumber sn) +lnk_pdb_output_finalize_stream(void *user_data, MSF_Context *msf, MSF_StreamNumber sn) { - lnk_pdb_writer_enqueue_stream(user_data, msf, sn); + lnk_pdb_output_enqueue_stream(user_data, msf, sn); } -internal B32 -lnk_pdb_writer_finish(LNK_PdbWriter *writer, MSF_Context *msf) +internal void +lnk_pdb_output_enqueue_remaining(LNK_PdbOutput *output, MSF_Context *msf) { - if (!writer->is_open) { return 0; } 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, writer->sealed_stream_count) { - MSF_Stream *stream = msf_find_stream(msf, writer->sealed_streams[i]); - if (stream == 0) { continue; } + 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; @@ -3341,54 +3246,26 @@ lnk_pdb_writer_finish(LNK_PdbWriter *writer, MSF_Context *msf) run_size += page_size; } else { if (run_data != 0) { - lnk_pdb_writer_enqueue_job(writer, run_first_pn * msf->page_size, run_data, run_size); + 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_pdb_writer_enqueue_job(writer, run_first_pn * msf->page_size, run_data, run_size); + 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_pdb_writer_enqueue_job(writer, run_first_pn * msf->page_size, run_data, run_size); - } - - LNK_PdbWriteJob stop = {0}; - RingGuard guard = guarded_ring_open(writer->queue); - B32 is_written_stop = guarded_ring_write_struct_or_wait(&guard, &stop, max_U64); - guarded_ring_close(&guard); - Assert(is_written_stop); - thread_join(writer->thread, -1); - - B32 is_complete = !writer->write_failed && writer->bytes_written == save_size; - if (is_complete && writer->open_with_rename) { - if (!lnk_file_set_delete_on_close(writer->file, 0)) { - lnk_error(LNK_Error_IO, "failed to update file disposition on %S", writer->open_path); - is_complete = 0; - } else if (!lnk_file_rename(writer->file, writer->path)) { - lnk_error(LNK_Error_IO, "failed to rename %S -> %S", writer->temp_path, writer->path); - is_complete = 0; - } - } - - lnk_close_file(&writer->file); - guarded_ring_release(writer->queue); - arena_release(writer->queue_arena); - if (!is_complete) { - lnk_error(LNK_Error_IO, "incomplete PDB write, %M written, expected %M, file %S", writer->bytes_written, save_size, writer->path); - } else if (lnk_get_log_status(LNK_Log_IO_Write)) { - lnk_log(LNK_Log_IO_Write, "File \"%S\" %M written", writer->path, save_size); + lnk_background_file_writer_enqueue(output->writer, output->file, run_first_pn * msf->page_size, str8(run_data, run_size)); } scratch_end(scratch); - return is_complete; } -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, String8 output_path, String8 temp_output_path, 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) { ProfBeginFunction(); Temp scratch = scratch_begin(tp_arena->v, tp_arena->count); @@ -3412,14 +3289,23 @@ lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config .image_section_file_section_numbers = push_array(scratch.arena, U64, task.image_section_table_count), }; - LNK_PdbWriter writer = {0}; - if (output_path.size > 0) { - lnk_pdb_writer_begin(scratch.arena, &writer, output_path, temp_output_path, cv->obj_count + 128); + 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; } - PDB_BuildHooks build_hooks = { - .stream_finalize = lnk_pdb_writer_finalize_stream, - .user_data = &writer, - }; // 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]; } @@ -3449,12 +3335,10 @@ lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config ProfEnd(); task.string_table_base_offset = task.pdb->info->strtab.size; - if (writer.is_open) { - 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); - } + 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") @@ -3463,20 +3347,18 @@ lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config } ProfScope("Write Modules") tp_for_parallel(tp, 0, tp->worker_count, lnk_write_pdb_modules, &task); - for EachIndex(obj_idx, cv->obj_count) { - lnk_pdb_writer_enqueue_stream(&writer, task.pdb->msf, task.mod_arr[obj_idx]->sn); + 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); - lnk_pdb_writer_enqueue_stream(&writer, task.pdb->msf, task.pdb->dbi->publics_sn); - lnk_pdb_writer_enqueue_stream(&writer, task.pdb->msf, task.pdb->dbi->globals_sn); - lnk_pdb_writer_enqueue_stream(&writer, task.pdb->msf, task.pdb->dbi->symbols_sn); - } - - if (!writer.is_open) { - ProfBegin("Add string tables"); - pdb_strtab_add_cv_string_hash_table(&task.pdb->info->strtab, task.string_ht); - ProfEnd(); + 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) { @@ -3539,25 +3421,25 @@ lnk_build_pdb(TP_Context *tp, TP_Arena *tp_arena, String8 image_data, LNK_Config ProfEnd(); } - if (writer.is_open) { - pdb_build_dbi_info(tp, task.pdb, task.string_ht, 0, cv->is_stripped, &build_hooks); - } else { - pdb_build(tp, tp_arena, task.pdb, task.string_ht, 0, cv->is_stripped, 0); - } + 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 (writer.is_open) { - lnk_pdb_writer_finish(&writer, task.pdb->msf); + 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 @@ -3568,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 3d9890ea..28364855 100644 --- a/src/linker/lnk_debug_info.h +++ b/src/linker/lnk_debug_info.h @@ -239,6 +239,13 @@ typedef struct 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; @@ -274,4 +281,4 @@ internal void lnk_replace_type_names_with_hashes (TP_Context *tp, TP //////////////////////////////// // 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, String8 output_path, String8 temp_output_path, 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_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/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/win32/base/win32_base.c b/src/win32/base/win32_base.c index 105cbb4d..296373d0 100644 --- a/src/win32/base/win32_base.c +++ b/src/win32/base/win32_base.c @@ -446,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; } From 2d827ac15d1524efefd3cc3ec64ce3ffc42b29b9 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 20 Jul 2026 14:17:31 -0700 Subject: [PATCH 62/90] make const data eligible for ICF --- src/linker/lnk.c | 14 +++++++++++++- src/linker/tests/linker_tests.c | 13 +++++++++---- src/msvc_crt/msvc_crt.h | 2 +- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 3581d12f..35cda232 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2977,6 +2977,7 @@ typedef enum LNK_ICF_ColorSpace 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; @@ -2989,6 +2990,7 @@ lnk_string_from_icf_color_space(LNK_ICF_ColorSpace color_space) 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; @@ -3017,13 +3019,23 @@ lnk_icf_color_space_from_section(LNK_Obj *obj, U32 sect_idx) if (str8_match(section_name, str8_lit(".xdata"), 0) || str8_match(section_name, str8_lit(".pdata"), 0)) { result = LNK_ICF_ColorSpace_Unwind; } else { - // fold MSVC vftables separately from other read-only data LNK_ObjSymbolRef symlink_ref = {0}; if (lnk_obj_get_comdat_symlink(obj, sect_idx + 1, &symlink_ref)) { String8 symlink_name = lnk_symbol_name_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx); + // fold MSVC vftables separately from other read-only data if (str8_starts_with(symlink_name, str8_lit(MSCRT_VFTABLE_SYMBOL_PREFIX))) { result = LNK_ICF_ColorSpace_VFTable; } + // fold ordinary read-only data COMDATs; do not fold declarations and vtables that require unique addresses + else if (!str8_starts_with(symlink_name, str8_lit(MSCRT_VBTABLE_SYMBOL_PREFIX))) { + COFF_ComdatSelectType select = COFF_ComdatSelect_Null; + if (lnk_try_comdat_props_from_section_number(obj, sect_idx + 1, &select, 0, 0, 0)) { + if (select == COFF_ComdatSelect_Any || select == COFF_ComdatSelect_SameSize || + select == COFF_ComdatSelect_ExactMatch || select == COFF_ComdatSelect_Largest) { + result = LNK_ICF_ColorSpace_ConstData; + } + } + } } } } diff --git a/src/linker/tests/linker_tests.c b/src/linker/tests/linker_tests.c index 58801bec..99b0b0ad 100644 --- a/src/linker/tests/linker_tests.c +++ b/src/linker/tests/linker_tests.c @@ -9249,7 +9249,7 @@ TEST(icf_readonly_non_vftable_data_policy) { U8 data_bytes[] = { 1, 2, 3, 4 }; U8 entry_text[] = { 0xc3 }; - U8 addresses[2 * sizeof(U64)] = {0}; + U8 addresses[4 * sizeof(U64)] = {0}; T_Ok(t_write_def_obj("icf_rdata_policy.obj", (T_COFF_DefObj){ .machine = T_COFF_DefSetMachine(X64), @@ -9257,12 +9257,15 @@ TEST(icf_readonly_non_vftable_data_policy) { "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 }, - { "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"), {0} } }, + { "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_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("addresses", "addresses", 0), + 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} } })); @@ -9270,11 +9273,13 @@ TEST(icf_readonly_non_vftable_data_policy) 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[2] = {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) diff --git a/src/msvc_crt/msvc_crt.h b/src/msvc_crt/msvc_crt.h index 9932ee73..fc9a96be 100644 --- a/src/msvc_crt/msvc_crt.h +++ b/src/msvc_crt/msvc_crt.h @@ -35,6 +35,7 @@ // MSVC C++ vftable symbols start with this decorated-name prefix. #define MSCRT_VFTABLE_SYMBOL_PREFIX "??_7" +#define MSCRT_VBTABLE_SYMBOL_PREFIX "??_8" //////////////////////////////// @@ -380,4 +381,3 @@ mscrt_catch_blocks_from_data_x8664(Arena *arena, internal String8 mscrt_string_from_eh_adjectives(Arena *arena, MSCRT_EhHandlerTypeFlags adjectives); #endif // MSVC_CRT - From bbb136c9ea1689df542c23a6f9037546b9446413 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 20 Jul 2026 14:31:03 -0700 Subject: [PATCH 63/90] radix sort pass over section contribs --- src/linker/lnk.c | 110 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 35cda232..394c30d1 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -4058,11 +4058,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(); @@ -5525,6 +5629,12 @@ lnk_build_image(TP_Arena *arena, TP_Context *tp, LNK_Config *config, LNK_SymbolT } 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); } From db0151c3e20756423b0af8714b92ff586e46e301 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 20 Jul 2026 15:03:56 -0700 Subject: [PATCH 64/90] parallel radix sort and coalesce adjacent section contribs --- src/linker/pdb_ext/pdb_builder.c | 151 +++++++++++++++++++++++++++++-- 1 file changed, 145 insertions(+), 6 deletions(-) diff --git a/src/linker/pdb_ext/pdb_builder.c b/src/linker/pdb_ext/pdb_builder.c index 767a4e30..06c1ddb3 100644 --- a/src/linker/pdb_ext/pdb_builder.c +++ b/src/linker/pdb_ext/pdb_builder.c @@ -2880,11 +2880,128 @@ dbi_sc_compar(const PDB_DbiSC *a, const PDB_DbiSC *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_DbiSC *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 @@ -3013,7 +3130,7 @@ lnk_radix_sort_dbi_sc_array(PDB_DbiSC *arr, U64 sc_count, U64 sect_count) } internal String8List -dbi_build_sec_con(Arena *arena, PDB_DbiContext *dbi) +dbi_build_sec_con(Arena *arena, TP_Context *tp, PDB_DbiContext *dbi) { ProfBeginFunction(); @@ -3030,13 +3147,35 @@ dbi_build_sec_con(Arena *arena, PDB_DbiContext *dbi) 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(); @@ -3121,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); From 4235fa85b44f63ddcb7abdee4c44beb9f42fd956 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Mon, 20 Jul 2026 16:51:45 -0700 Subject: [PATCH 65/90] search small libraries on the main thread to reduce wait times in barriers --- src/linker/lnk.c | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 394c30d1..0f311142 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -1742,6 +1742,25 @@ THREAD_POOL_TASK_FUNC(lnk_search_lib_task) 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 * lnk_find_first_crt_lib(LNK_Config *config, LNK_Inputer *inputer) { @@ -1899,7 +1918,23 @@ lnk_link_inputs(TP_Context *tp, .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; From cd82268a07a8c8942887f90dc6bb086a10bec929 Mon Sep 17 00:00:00 2001 From: Nikita Smith Date: Tue, 21 Jul 2026 17:26:47 -0700 Subject: [PATCH 66/90] polish ICF section color-space picker --- src/linker/lnk.c | 101 ++++++++++++++++++++++++---------------- src/msvc_crt/msvc_crt.h | 4 +- 2 files changed, 65 insertions(+), 40 deletions(-) diff --git a/src/linker/lnk.c b/src/linker/lnk.c index 0f311142..be253f18 100644 --- a/src/linker/lnk.c +++ b/src/linker/lnk.c @@ -2994,16 +2994,13 @@ THREAD_POOL_TASK_FUNC(lnk_opt_ref_task) internal void lnk_opt_ref(TP_Context *tp, LNK_SymbolTable *symtab, LNK_Config *config, LNK_Obj **objs, U64 objs_count) { + 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); - - ProfScope("/OPT:REF") - { - 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); - } - + 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 @@ -3036,46 +3033,73 @@ lnk_icf_color_space_from_section(LNK_Obj *obj, U32 sect_idx) { LNK_ICF_ColorSpace result = LNK_ICF_ColorSpace_Null; - // fold read-only COMDAT sections + // * 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] & expected_flags) == expected_flags && (obj->section_flags[sect_idx] & exclude_flags) == 0) { - // fold code - if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntCode) { - result = LNK_ICF_ColorSpace_Code; + 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; } - // fold data - else if (obj->section_flags[sect_idx] & COFF_SectionFlag_CntInitializedData) { - COFF_SectionHeader *section_header = lnk_coff_section_header_from_section_number(obj, sect_idx + 1); - String8 section_name = str8_cstring_capped(section_header->name, section_header->name + sizeof(section_header->name)); - // fold unwind info - if (str8_match(section_name, str8_lit(".xdata"), 0) || str8_match(section_name, str8_lit(".pdata"), 0)) { - result = LNK_ICF_ColorSpace_Unwind; - } else { - LNK_ObjSymbolRef symlink_ref = {0}; - if (lnk_obj_get_comdat_symlink(obj, sect_idx + 1, &symlink_ref)) { - String8 symlink_name = lnk_symbol_name_from_coff_symbol_idx(symlink_ref.obj, symlink_ref.symbol_idx); - // fold MSVC vftables separately from other read-only data - if (str8_starts_with(symlink_name, str8_lit(MSCRT_VFTABLE_SYMBOL_PREFIX))) { - result = LNK_ICF_ColorSpace_VFTable; - } - // fold ordinary read-only data COMDATs; do not fold declarations and vtables that require unique addresses - else if (!str8_starts_with(symlink_name, str8_lit(MSCRT_VBTABLE_SYMBOL_PREFIX))) { - COFF_ComdatSelectType select = COFF_ComdatSelect_Null; - if (lnk_try_comdat_props_from_section_number(obj, sect_idx + 1, &select, 0, 0, 0)) { - if (select == COFF_ComdatSelect_Any || select == COFF_ComdatSelect_SameSize || - select == COFF_ComdatSelect_ExactMatch || select == COFF_ComdatSelect_Largest) { - result = LNK_ICF_ColorSpace_ConstData; - } - } - } + // 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; } @@ -3095,8 +3119,7 @@ lnk_icf_atomic_min_u64(U64 *dst, U64 value) // 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 and the other downside it is susceptible to adversarial -// inputs. +// paradox with BLAKE3 THREAD_POOL_TASK_FUNC(lnk_opt_icf_task) { ProfBeginFunction(); diff --git a/src/msvc_crt/msvc_crt.h b/src/msvc_crt/msvc_crt.h index fc9a96be..e808c335 100644 --- a/src/msvc_crt/msvc_crt.h +++ b/src/msvc_crt/msvc_crt.h @@ -33,8 +33,10 @@ // PE_TLSHeader32 or PE_TLSHeader64, according to machine type. #define MSCRT_TLS_SYMBOL_NAME "_tls_used" -// MSVC C++ vftable symbols start with this decorated-name prefix. +// vftable symbols #define MSCRT_VFTABLE_SYMBOL_PREFIX "??_7" + +// vbptr symbols #define MSCRT_VBTABLE_SYMBOL_PREFIX "??_8" //////////////////////////////// From bb85f6f6923b78503d44750f1de0c38c463449e2 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 7 Jul 2026 20:09:18 -0700 Subject: [PATCH 67/90] text patches: checkpoint --- project.4coder | 2 +- src/base/base_memory_map.c | 36 +- src/base/base_memory_map.h | 1 + src/dbg_engine/dbg_engine_user.c | 2 +- src/dbg_engine/dbg_engine_user.h | 3 +- src/raddbg/generated/raddbg.meta.c | 14 +- src/raddbg/generated/raddbg.meta.h | 14 +- src/raddbg/raddbg.mdesk | 8 +- src/raddbg/raddbg_core.c | 143 +++--- src/raddbg/raddbg_core.h | 5 +- src/raddbg/raddbg_views.c | 428 +++++++++--------- src/raddbg/raddbg_views.h | 4 +- src/raddbg/raddbg_widgets.c | 258 +++++------ src/raddbg/raddbg_widgets.h | 12 +- src/text/text.c | 59 +-- src/text/text.h | 7 +- src/ui/ui_basic_widgets.c | 36 +- src/ui/ui_basic_widgets.h | 4 +- src/ui/ui_core.c | 74 ++- src/ui/ui_core.h | 10 +- .../window_manager/win32_window_manager.c | 6 +- src/window_manager/window_manager.c | 1 + 22 files changed, 562 insertions(+), 565 deletions(-) diff --git a/project.4coder b/project.4coder index 6d11dd66..0b10da9c 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 ryan_scratch && pushd build && ryan_scratch && popd", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, + .f1 = { .win = "raddbg_stable --ipc kill_all && build raddbg", .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/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/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/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index b8bcd0cd..adfc729f 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -477,7 +477,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 +499,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 +535,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 +555,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)}, @@ -755,7 +759,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("")}}, diff --git a/src/raddbg/generated/raddbg.meta.h b/src/raddbg/generated/raddbg.meta.h index 92db5712..0fb38188 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, @@ -478,8 +480,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 +585,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,\ @@ -619,8 +625,8 @@ 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_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 e5e11731..6aea21f3 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -870,8 +870,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 } @@ -1131,7 +1133,7 @@ RD_CmdTable: // | | | {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." "" "" } //- 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." "" "" } + {GoToLine 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 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." "" "" } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 87cc8d0d..ba4c7a30 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; @@ -2622,8 +2615,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; @@ -2748,11 +2741,11 @@ 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); } @@ -2765,9 +2758,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 @@ -4235,7 +4228,7 @@ 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); @@ -4548,7 +4541,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 +4559,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 +4595,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 +5944,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]); @@ -9597,7 +9600,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 @@ -10478,20 +10481,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 +10567,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 +11171,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 +11693,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 +11704,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 +11721,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); @@ -13028,7 +13030,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; @@ -14655,7 +14658,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: { @@ -14682,7 +14685,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; } } @@ -14718,7 +14721,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); @@ -15020,7 +15024,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, @@ -15168,7 +15173,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); @@ -15178,7 +15184,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; @@ -15230,7 +15236,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; @@ -15238,7 +15245,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; @@ -15267,7 +15275,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); @@ -15774,9 +15782,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); } @@ -16042,12 +16050,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)) @@ -16119,8 +16127,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; @@ -16227,17 +16235,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) { @@ -16259,12 +16268,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) { @@ -16276,7 +16286,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) { @@ -16341,7 +16351,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; @@ -16353,7 +16364,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)) { @@ -16579,21 +16590,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); - TXT_PatchList patches = {0}; - Rng1U64 expr_off_range = {0}; - if(range.min.column != range.max.column) - { - expr_off_range = r1u64(txt_off_from_pt(&info, &patches, range.min), txt_off_from_pt(&info, &patches, 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), diff --git a/src/raddbg/raddbg_core.h b/src/raddbg/raddbg_core.h index c473ee8f..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; diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index dd212d48..8c975327 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -48,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 @@ -66,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: { @@ -121,14 +122,17 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla } ////////////////////////////// - //- rjf: do keyboard interaction + //- 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()) { + rd_state->text_edit_mode = 1; + rd_state->text_edit_mode_multiline = 1; U64 line_count_per_page = ClampBot(num_possible_visible_lines, 10) - 10; - TxtPt *cursor = &rd_regs()->cursor; - TxtPt *mark = &rd_regs()->mark; + U64 *cursor = &rd_regs()->cursor; + U64 *mark = &rd_regs()->mark; S64 *preferred_column = &cv->preferred_column; B32 change = 0; for(UI_Event *evt = 0; ui_next_event(&evt);) @@ -138,96 +142,108 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla continue; } B32 taken = 0; - String8 line = txt_string_from_info_data_line_num(text_info, text_data, cursor->line); - UI_TxtOp single_line_op = ui_single_line_txt_op_from_event(scratch.arena, evt, line, *cursor, *mark); + 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: try to treat event as single-line operation; map into multi-line space + U64 line_cursor = *cursor - line_range.min; + U64 line_mark = *mark - line_range.min; + UI_TxtOp single_line_op = ui_single_line_txt_op_from_event(scratch.arena, evt, line, line_cursor, line_mark); + single_line_op.range = shift_1u64(single_line_op.range, line_range.min); + single_line_op.cursor += line_range.min; + single_line_op.mark += line_range.min; //- 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 = text_info->lines_count; - String8 prev_line = txt_string_from_info_data_line_num(text_info, text_data, cursor->line-1); - String8 next_line = txt_string_from_info_data_line_num(text_info, text_data, cursor->line+1); + U64 line_count = text_patched.line_map.total_line_count; 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) + if(evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x > 0 && *cursor == line_range.max+1 && line_num+1 <= line_count) { - cursor->line += 1; - cursor->column = 1; + Rng1U64 next_line_range = txt_range_from_line_num(&text_patched.line_map, line_num+1); + *cursor = next_line_range.min; *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) + if(evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x < 0 && *cursor == line_range.min && line_num-1 >= 1) { - cursor->line -= 1; - cursor->column = prev_line.size+1; - *preferred_column = prev_line.size+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; change = 1; taken = 1; } //- rjf: movement down (plain) - if(evt->delta_unit == UI_EventDeltaUnit_Char && delta.y > 0 && cursor->line+1 <= line_count) + if(evt->delta_unit == UI_EventDeltaUnit_Char && delta.y > 0 && line_num+1 <= line_count) { - cursor->line += 1; - cursor->column = Min(*preferred_column, next_line.size+1); + 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); change = 1; taken = 1; } //- rjf: movement up (plain) - if(evt->delta_unit == UI_EventDeltaUnit_Char && delta.y < 0 && cursor->line-1 >= 1) + if(evt->delta_unit == UI_EventDeltaUnit_Char && delta.y < 0 && line_num > 1) { - cursor->line -= 1; - cursor->column = Min(*preferred_column, prev_line.size+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); change = 1; taken = 1; } //- rjf: movement down (chunk) - if(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y > 0 && cursor->line+1 <= line_count) + if(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y > 0 && line_num+1 <= line_count) { - for(S64 line_num = cursor->line+1; line_num <= line_count; line_num += 1) + for(U64 scan_line_num = line_num+1; scan_line_num <= line_count; scan_line_num += 1) { - String8 line = txt_string_from_info_data_line_num(text_info, text_data, line_num); - U64 line_size = line.size; - if(line_size == 0) + 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 = line_num; - cursor->column = 1; - break; + *cursor = line_range.min + (U64)(line_without_whitespace.str - line.str); } - else if(line_num == line_count) + else if(scan_line_num == line_count) { - cursor->line = line_num; - cursor->column = line_size+1; + *cursor = text_patched.size; } + scratch_end(scratch); } change = 1; taken = 1; } //- rjf: movement up (chunk) - if(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y < 0 && cursor->line-1 >= 1) + if(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y < 0 && line_num > 1) { - for(S64 line_num = cursor->line-1; line_num > 0; line_num -= 1) + for(U64 scan_line_num = line_num-1; scan_line_num > 0; scan_line_num -= 1) { - String8 line = txt_string_from_info_data_line_num(text_info, text_data, line_num); - U64 line_size = line.size; - if(line_size == 0) + 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 = line_num; - cursor->column = 1; - break; + *cursor = line_range.min + (U64)(line_without_whitespace.str - line.str); } - else if(line_num == 1) + else if(scan_line_num == 1) { - cursor->line = line_num; - cursor->column = 1; + *cursor = 0; } + scratch_end(scratch); } change = 1; taken = 1; @@ -236,9 +252,11 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla //- 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); + 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; change = 1; taken = 1; } @@ -246,9 +264,15 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla //- 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); + S64 advance = -line_count_per_page; + if(line_num < line_count_per_page) + { + advance = -(line_num - 1); + } + U64 next_line = (U64)((S64)line_num + line_count_per_page); + 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; change = 1; taken = 1; } @@ -256,7 +280,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla //- rjf: movement to endpoint (+) if(evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y > 0 || delta.x > 0)) { - *cursor = txt_pt(line_count, text_info->lines_count ? dim_1u64(text_info->lines_ranges[text_info->lines_count-1])+1 : 1); + *cursor = txt_range_from_line_num(&text_patched.line_map, text_patched.line_map.total_line_count).max; change = 1; taken = 1; } @@ -264,7 +288,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla //- rjf: movement to endpoint (-) if(evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y < 0 || delta.x < 0)) { - *cursor = txt_pt(1, 1); + *cursor = 0; change = 1; taken = 1; } @@ -279,17 +303,16 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla //- rjf: valid single-line op => do single-line op else { -#if 0 - if(single_line_op.replace.size != 0) + if(single_line_op.range.min != single_line_op.range.max || single_line_op.replace.size != 0) { - Rng1U64 range = r1u64(txt_off_from_pt(text_info, &cv->patches, *cursor), - txt_off_from_pt(text_info, &cv->patches, *mark)); - txt_patch_list_push_new(cv->patch_arena, &cv->patches, range, single_line_op.replace); + txt_patch_list_push_new(cv->patch_arena, &cv->patches, single_line_op.range, single_line_op.replace); + text_patched = txt_patched_from_info_data_patches(scratch.arena, text_info, text_data, &cv->patches); } -#endif *cursor = single_line_op.cursor; *mark = single_line_op.mark; - *preferred_column = cursor->column; + 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); change = 1; taken = 1; } @@ -297,7 +320,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla //- rjf: copy if(evt->flags & UI_EventFlag_Copy) { - String8 text = txt_string_from_info_data_txt_rng(text_info, text_data, &cv->patches, txt_rng(*cursor, *mark)); + String8 text = memory_map_data_from_range(scratch.arena, &text_patched.memory_map, r1u64(*cursor, *mark)); wm_set_clipboard_text(text); taken = 1; } @@ -310,11 +333,6 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla } } - ////////////////////////////// - //- rjf: compute current patched text state - // - TXT_Patched text_patched = txt_patched_from_info_data_patches(scratch.arena, text_info, text_data, &cv->patches); - ////////////////////////////// //- rjf: determine visible line range / count // @@ -324,12 +342,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; @@ -345,7 +363,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); } ////////////////////////////// @@ -368,22 +386,28 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla ////////////////////////////// //- 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, &cv->patches, 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()); + } } } } @@ -436,12 +460,18 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla 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] = txt_string_from_info_data_line_num(text_info, text_data, line_num); - 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; + // TODO(rjf): tokens + // code_slice_params.line_tokens[visible_line_idx] = slice.line_tokens[visible_line_idx]; } } @@ -683,111 +713,75 @@ 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; + B32 done = 0; + for(U64 off = rd_regs()->cursor+1; !done && off < text_patched.size; off += search_chunk_size) { - // 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) + 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 needle_pos = str8_find_needle(data, 0, needle, StringMatchFlag_CaseInsensitive); + if(needle_pos < data.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; + done = 1; + rd_regs()->mark = needle_pos; + rd_regs()->cursor = rd_regs()->mark + needle.size; } + scratch_end(scratch); } - cv->center_cursor = found; - if(found == 0) + if(!done) { - log_user_errorf("Could not find `%S`", cv->find_text_fwd); + log_user_errorf("Could not find `%S`", needle); } + cv->center_cursor = done; } //- 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; + B32 done = 0; + for(U64 off = rd_regs()->cursor-1, next_off = 0; !done; off = next_off) { - // 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) + U64 advance = Min(search_chunk_size, off); + next_off = off - advance; + 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 needle_pos = 0; { - 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) + U64 start_search_pos = 0; + for(;;) { - next_needle_pos = needle_pos; - needle_pos += 1; + U64 next_needle_pos = str8_find_needle(data, start_search_pos, needle, StringMatchFlag_CaseInsensitive); + if(next_needle_pos < data.size) + { + needle_pos = next_needle_pos; + } + else + { + needle_pos = data.size; + break; + } + start_search_pos = next_needle_pos+1; } } - if(next_needle_pos < line_string.size) + if(needle_pos < data.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 = 1; + rd_regs()->mark = needle_pos; + rd_regs()->cursor = rd_regs()->mark + needle.size; } + scratch_end(scratch); } - cv->center_cursor = found; - if(found == 0) + if(!done) { - log_user_errorf("Could not find `%S`", cv->find_text_bwd); + log_user_errorf("Could not find `%S`", needle); } + cv->center_cursor = done; } MemoryZeroStruct(&cv->find_text_fwd); @@ -798,12 +792,13 @@ 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)); } @@ -846,27 +841,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, &cv->patches, 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, &cv->patches, 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; @@ -874,13 +870,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 { @@ -892,7 +889,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; @@ -903,8 +900,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, @@ -925,13 +923,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); } } @@ -973,7 +971,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) @@ -2263,14 +2261,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); @@ -2404,6 +2396,12 @@ 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); + ////////////////////////////// //- rjf: unpack cursor info // @@ -2411,7 +2409,7 @@ 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); } ////////////////////////////// @@ -2521,7 +2519,8 @@ 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); + Rng1U64 cursor_line_range = txt_range_from_line_num(&patched.line_map, cursor_line_num); + ui_labelf("Line: %I64d, Column: %I64d", cursor_line_num, 1 + rd_regs()->cursor - cursor_line_range.min); ui_spacer(ui_pct(1, 0)); ui_labelf("(read only)"); ui_labelf("%s", @@ -2535,10 +2534,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); @@ -2551,8 +2548,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; @@ -2574,8 +2571,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; @@ -2747,13 +2744,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); @@ -2773,13 +2776,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"); @@ -2813,8 +2817,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; @@ -4587,8 +4591,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) { diff --git a/src/raddbg/raddbg_views.h b/src/raddbg/raddbg_views.h index 7e5b22ab..1bae6b7d 100644 --- a/src/raddbg/raddbg_views.h +++ b/src/raddbg/raddbg_views.h @@ -163,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 b4d7c3a0..f99d3151 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -1291,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(); @@ -1887,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("")); } } } @@ -1901,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)); @@ -1917,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 @@ -1974,63 +1975,55 @@ 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); + mouse_y_line_idx = (U64)((mouse.y - text_container_box->rect.y0) / params->line_height_px); // 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_range.min, line_token_range_clamped.max-line_range.min); 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 = r1u64(0, dim_1u64(line_range)); } ////////////////////////////// @@ -2042,7 +2035,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; @@ -2055,24 +2048,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; } @@ -2080,7 +2061,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 @@ -2092,25 +2073,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, @@ -2127,40 +2108,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); @@ -2186,7 +2165,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, params->patches, rd_regs()->cursor); + cursor_scope_node = txt_scope_node_from_info_off(params->text_info, *cursor); } ////////////////////////////// @@ -2623,40 +2602,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 = r1u64(expr_off_rng.min - line_range.min, expr_off_rng.max - line_range.min); 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)); } } @@ -2665,11 +2641,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), @@ -2693,10 +2668,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); } @@ -2713,9 +2687,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); } @@ -2729,7 +2703,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; @@ -2738,16 +2712,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); @@ -2840,33 +2814,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 = { @@ -2882,19 +2845,21 @@ 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)); + 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 = line_box->rect.y0-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; @@ -2986,7 +2951,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; @@ -3883,8 +3848,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; } } @@ -3928,20 +3893,20 @@ 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]); 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; @@ -4026,9 +3991,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) { @@ -4095,7 +4060,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) { @@ -4132,9 +4097,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; } } @@ -4145,13 +4109,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 62d29b55..be3d5497 100644 --- a/src/raddbg/raddbg_widgets.h +++ b/src/raddbg/raddbg_widgets.h @@ -72,8 +72,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; @@ -128,8 +128,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; }; //////////////////////////////// @@ -172,8 +172,8 @@ 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, TXT_PatchList *patches, U64 line_count_per_page, TxtPt *cursor, TxtPt *mark, S64 *preferred_column); diff --git a/src/text/text.c b/src/text/text.c index 1043d970..896fe3c5 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -2329,14 +2329,14 @@ txt_patched_range_from_base_range(TXT_PatchList *patches, Rng1U64 base_range) } internal void -txt_line_map_push(Arena *arena, TXT_LineMap *map, Rng1U64 idx_range, Rng1U64 *ranges, S64 delta) +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->idx_range = idx_range; + 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(idx_range); + map->total_line_count += dim_1u64(num_range); } internal U64 @@ -2345,13 +2345,13 @@ 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) { - for EachInRange(idx, n->idx_range) + for EachInRange(num, n->num_range) { - Rng1U64 line_range = n->ranges[idx-n->idx_range.min]; + Rng1U64 line_range = n->ranges[num-n->num_range.min]; Rng1U64 line_range_shifted = r1u64(line_range.min + n->delta, line_range.max + n->delta); if(line_range_shifted.min <= off && off <= line_range_shifted.max) { - result = idx+1; + result = num; goto break_all; } } @@ -2361,14 +2361,14 @@ txt_line_num_from_off(TXT_LineMap *map, U64 off) } internal Rng1U64 -txt_range_from_line_idx(TXT_LineMap *map, U64 idx) +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->idx_range, idx)) + if(contains_1u64(n->num_range, num)) { - result = n->ranges[idx - n->idx_range.min]; + result = n->ranges[num - n->num_range.min]; result.min = (U64)((S64)result.min + n->delta); result.max = (U64)((S64)result.max + n->delta); } @@ -2386,7 +2386,7 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat U64 last_size = data.size; TXT_LineMap last_line_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(0, info->lines_count), info->lines_ranges, 0); + txt_line_map_push(scratch.arena, &last_line_map, r1u64(1, info->lines_count+1), info->lines_ranges, 0); // rjf: apply the patches in order, each being able to slice/dice the previous memory map for EachNode(n, TXT_PatchNode, patches->first) @@ -2405,7 +2405,7 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat replace_line_num_range.max = txt_line_num_from_off(&last_line_map, n->v.range.max); } - // rjf: compute portion of lines before/after this replace-range + // 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); @@ -2433,10 +2433,7 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat last_line_start_off = idx+1; } } - if(last_line_start_off < n->v.replace.size) - { - rng1u64_list_push(scratch.arena, &replace_line_ranges, r1u64(last_line_start_off, n->v.replace.size)); - } + 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 @@ -2469,56 +2466,62 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat { for EachNode(map_n, TXT_LineMapRangeNode, last_line_map.first_range) { - Rng1U64 num_range = shift_1u64(map_n->idx_range, 1); + 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-1, range_x_pre.max-1), map_n->ranges + (range_x_pre.min - num_range.min), map_n->delta); + 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-1, range_x_post_shifted.max-1), map_n->ranges + (range_x_post.min - num_range.min), map_n->delta + size_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 = dim_1u64(replace_line_num_range)+1; + 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 replace_line_range = replace_line_range_n->v; - Rng1U64 affected_line_range = r1u64(replace_line_range.min + n->v.range.min, replace_line_range.max + n->v.range.min); + 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_idx(&last_line_map, replace_line_num_range.min + affected_line_idx - 1); + 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 max from original line map, shift if(affected_line_idx == affected_line_count-1) { - Rng1U64 og_line_range = txt_range_from_line_idx(&last_line_map, replace_line_num_range.min + affected_line_idx - 1); - affected_line_range.max = og_line_range.max + size_delta; + Rng1U64 og_line_range = txt_range_from_line_num(&last_line_map, replace_line_num_range.min + affected_line_idx); + if(dim_1u64(og_line_range) != 0) + { + affected_line_range.max += dim_1u64(og_line_range); + } } // rjf: commit affected_line_ranges[affected_line_idx] = affected_line_range; - - replace_line_range_n = replace_line_range_n->next; } } // rjf: push affected line ranges - txt_line_map_push(scratch.arena, &next_line_map, r1u64(replace_line_num_range.min-1, (replace_line_num_range.max+1) - 1), affected_line_ranges, 0); + 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: advance to the next memory map last_memory_map = next_memory_map; @@ -2536,7 +2539,7 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat result.size = last_size; for EachNode(n, TXT_LineMapRangeNode, last_line_map.first_range) { - txt_line_map_push(arena, &result.line_map, n->idx_range, n->ranges, n->delta); + txt_line_map_push(arena, &result.line_map, n->num_range, n->ranges, n->delta); } } diff --git a/src/text/text.h b/src/text/text.h index efe94ad4..d8279b3d 100644 --- a/src/text/text.h +++ b/src/text/text.h @@ -196,7 +196,7 @@ typedef struct TXT_LineMapRangeNode TXT_LineMapRangeNode; struct TXT_LineMapRangeNode { TXT_LineMapRangeNode *next; - Rng1U64 idx_range; + Rng1U64 num_range; Rng1U64 *ranges; S64 delta; }; @@ -291,12 +291,11 @@ internal TXT_TokenArray txt_token_array_from_string__disasm_x64_intel(Arena *are internal U64 txt_patched_off_from_base_off(TXT_PatchList *patches, U64 base_off); internal Rng1U64 txt_patched_range_from_base_range(TXT_PatchList *patches, Rng1U64 base_range); -internal void txt_line_map_push(Arena *arena, TXT_LineMap *map, Rng1U64 idx_range, Rng1U64 *ranges, S64 delta); +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_idx(TXT_LineMap *map, U64 idx); +internal Rng1U64 txt_range_from_line_num(TXT_LineMap *map, U64 num); 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); diff --git a/src/ui/ui_basic_widgets.c b/src/ui/ui_basic_widgets.c index dffd3c34..9098bfbb 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); @@ -241,9 +241,9 @@ ui_line_edit(TxtPt *cursor, TxtPt *mark, U8 *edit_buffer, U64 edit_buffer_size, UI_TxtOp op = ui_single_line_txt_op_from_event(scratch.arena, evt, edit_string, *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; @@ -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 34aa9a66..77642562 100644 --- a/src/ui/ui_core.c +++ b/src/ui/ui_core.c @@ -142,11 +142,11 @@ 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, 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; @@ -164,85 +164,78 @@ 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+1, delta.x > 0 ? Side_Max : Side_Min)-1 - (S64)cursor); }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 == first_nonwhitespace_off) ? 0 : first_nonwhitespace_off; + delta.x = (delta.x > 0) ? ((S64)string.size - (S64)cursor) : ((S64)home_dest_off - (S64)cursor); }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; + 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(0, next_cursor, string.size); } //- 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; - } + copy = str8_substr(string, r1u64(cursor, mark)); + flags |= UI_TxtOpFlag_Copy; } //- rjf: pasting if(event->flags & UI_EventFlag_Paste) { - range = txt_rng(cursor, mark); + range = r1u64(cursor, mark); replace = wm_get_clipboard_text(arena); - next_cursor = next_mark = txt_pt(cursor.line, cursor.column+replace.size); + next_cursor = next_mark = cursor + replace.size; } //- 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,19 +248,19 @@ 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); + range = r1u64(cursor, mark); + replace = str8_copy(arena, event->string); + next_cursor = next_mark = range.min + event->string.size; } //- rjf: determine if this event should be taken, based on bounds of cursor { - if(next_cursor.column > string.size+replace.size+1 || 1 > next_cursor.column || event->delta_2s32.y != 0) + if(next_cursor > string.size+replace.size || 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); + next_cursor = Clamp(0, next_cursor, string.size+replace.size); + next_mark = Clamp(0, next_mark, string.size+replace.size); } //- rjf: build+fill @@ -284,15 +277,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..c7dcde6d 100644 --- a/src/ui/ui_core.h +++ b/src/ui/ui_core.h @@ -184,9 +184,9 @@ struct UI_TxtOp UI_TxtOpFlags flags; String8 replace; String8 copy; - TxtRng range; - TxtPt cursor; - TxtPt mark; + Rng1U64 range; + U64 cursor; + U64 mark; }; //////////////////////////////// @@ -776,8 +776,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, 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/window_manager/win32_window_manager.c b/src/win32/window_manager/win32_window_manager.c index bb155677..235a02f3 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 >= 10 && character != 127) { 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; 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}, From 315e0672d33dd8d1f22c1d6a834f02979c7d03ad Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 9 Jul 2026 10:23:26 -0700 Subject: [PATCH 68/90] checkpoint on text patches --- src/raddbg/raddbg_views.c | 14 ++++++++++++-- src/text/text.c | 10 +++++----- src/ui/ui_core.c | 13 ++++++++++++- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 8c975327..3e06e8e7 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -160,11 +160,12 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla //- 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 start_cursor = *cursor; U64 line_count = text_patched.line_map.total_line_count; Vec2S32 delta = evt->delta_2s32; //- rjf: wrap lines right - if(evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x > 0 && *cursor == line_range.max+1 && line_num+1 <= line_count) + if(evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x > 0 && *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; @@ -280,7 +281,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla //- rjf: movement to endpoint (+) if(evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y > 0 || delta.x > 0)) { - *cursor = txt_range_from_line_num(&text_patched.line_map, text_patched.line_map.total_line_count).max; + *cursor = text_patched.size; change = 1; taken = 1; } @@ -298,6 +299,15 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla { *mark = *cursor; } + + //- rjf: push patch if we have one + if(evt->flags & UI_EventFlag_Delete) + { + Rng1U64 range = r1u64(start_cursor, *cursor); + txt_patch_list_push_new(cv->patch_arena, &cv->patches, range, single_line_op.replace); + text_patched = txt_patched_from_info_data_patches(scratch.arena, text_info, text_data, &cv->patches); + *cursor = *mark = range.min; + } } //- rjf: valid single-line op => do single-line op diff --git a/src/text/text.c b/src/text/text.c index 896fe3c5..708f4e78 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -2505,13 +2505,13 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat affected_line_range.min = og_line_range.min; } - // rjf: the last line in the range -> take max from original line map, shift - if(affected_line_idx == affected_line_count-1) + // 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.min + affected_line_idx); - if(dim_1u64(og_line_range) != 0) + 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 += dim_1u64(og_line_range); + affected_line_range.max += og_line_range.max - n->v.range.max; } } diff --git a/src/ui/ui_core.c b/src/ui/ui_core.c index 77642562..684850fc 100644 --- a/src/ui/ui_core.c +++ b/src/ui/ui_core.c @@ -191,8 +191,19 @@ ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, } //- rjf: form next cursor + B32 cursor_out_of_bounds = 0; if(cursor == mark || !(event->flags & UI_EventFlag_ZeroDeltaOnSelect)) { + if(delta.x < -(S32)next_cursor) + { + delta.x = -(S32)next_cursor; + cursor_out_of_bounds = 1; + } + if(cursor + delta.x > string.size) + { + delta.x = (S32)((S64)string.size - (S64)cursor); + cursor_out_of_bounds = 1; + } next_cursor += delta.x; } @@ -255,7 +266,7 @@ ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, //- rjf: determine if this event should be taken, based on bounds of cursor { - if(next_cursor > string.size+replace.size || event->delta_2s32.y != 0) + if(next_cursor > string.size+replace.size || event->delta_2s32.y != 0 || cursor_out_of_bounds) { flags |= UI_TxtOpFlag_Invalid; } From c8dae72968d0b92b1129e7dc09bab1843507df12 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 9 Jul 2026 11:57:22 -0700 Subject: [PATCH 69/90] cleanup around single vs. multiline text mutation controls --- src/raddbg/raddbg_core.c | 6 +- src/raddbg/raddbg_views.c | 322 +++++++++--------- src/raddbg/raddbg_widgets.c | 8 +- src/ui/ui_basic_widgets.c | 4 +- src/ui/ui_core.c | 46 +-- src/ui/ui_core.h | 10 +- .../window_manager/win32_window_manager.c | 2 +- 7 files changed, 173 insertions(+), 225 deletions(-) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index ba4c7a30..986f53fb 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -2726,10 +2726,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); } @@ -2747,7 +2747,7 @@ rd_view_ui(Rng2F32 rect) edit_state->input_size = new_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 diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 3e06e8e7..57529d04 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -134,7 +134,6 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla U64 *cursor = &rd_regs()->cursor; U64 *mark = &rd_regs()->mark; S64 *preferred_column = &cv->preferred_column; - B32 change = 0; 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) @@ -142,6 +141,9 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla continue; } B32 taken = 0; + U64 start_cursor = *cursor; + 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}; @@ -149,196 +151,180 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla line.str = push_array(scratch.arena, U8, line.size); memory_map_read(&text_patched.memory_map, line_range, line.str); - //- rjf: try to treat event as single-line operation; map into multi-line space - U64 line_cursor = *cursor - line_range.min; - U64 line_mark = *mark - line_range.min; - UI_TxtOp single_line_op = ui_single_line_txt_op_from_event(scratch.arena, evt, line, line_cursor, line_mark); - single_line_op.range = shift_1u64(single_line_op.range, line_range.min); - single_line_op.cursor += line_range.min; - single_line_op.mark += line_range.min; + //- 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); - //- rjf: invalid single-line op or endpoint units => try multiline - if(evt->delta_unit == UI_EventDeltaUnit_Whole || single_line_op.flags & UI_TxtOpFlag_Invalid) + //- rjf: apply single-line navigations + *cursor = single_line_op.cursor; + *mark = single_line_op.mark; + + //- rjf: wrap lines right + if(evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x > 0 && start_cursor == line_range.max && line_num+1 <= line_count) { - U64 start_cursor = *cursor; - U64 line_count = text_patched.line_map.total_line_count; - Vec2S32 delta = evt->delta_2s32; - - //- rjf: wrap lines right - if(evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x > 0 && *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(evt->delta_unit != UI_EventDeltaUnit_Whole && 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(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(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(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y > 0 && line_num+1 <= line_count) + { + for(U64 scan_line_num = line_num+1; scan_line_num <= line_count; scan_line_num += 1) { - Rng1U64 next_line_range = txt_range_from_line_num(&text_patched.line_map, line_num+1); - *cursor = next_line_range.min; - *preferred_column = 1; - change = 1; - taken = 1; - } - - //- rjf: wrap lines left - if(evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x < 0 && *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; - change = 1; - taken = 1; - } - - //- rjf: movement down (plain) - if(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); - change = 1; - taken = 1; - } - - //- rjf: movement up (plain) - if(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); - change = 1; - taken = 1; - } - - //- rjf: movement down (chunk) - if(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y > 0 && line_num+1 <= line_count) - { - for(U64 scan_line_num = line_num+1; 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) { - 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); - } - else if(scan_line_num == line_count) - { - *cursor = text_patched.size; - } - scratch_end(scratch); + *cursor = line_range.min + (U64)(line_without_whitespace.str - line.str); } - change = 1; - taken = 1; - } - - //- rjf: movement up (chunk) - if(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y < 0 && line_num > 1) - { - for(U64 scan_line_num = line_num-1; scan_line_num > 0; scan_line_num -= 1) + else if(scan_line_num == line_count) { - 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); - } - else if(scan_line_num == 1) - { - *cursor = 0; - } - scratch_end(scratch); + *cursor = text_patched.size; } - change = 1; - taken = 1; - } - - //- rjf: movement down (page) - if(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; - change = 1; - taken = 1; - } - - //- rjf: movement up (page) - if(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 + line_count_per_page); - 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; - change = 1; - taken = 1; - } - - //- rjf: movement to endpoint (+) - if(evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y > 0 || delta.x > 0)) - { - *cursor = text_patched.size; - change = 1; - taken = 1; - } - - //- rjf: movement to endpoint (-) - if(evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y < 0 || delta.x < 0)) - { - *cursor = 0; - 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: push patch if we have one - if(evt->flags & UI_EventFlag_Delete) - { - Rng1U64 range = r1u64(start_cursor, *cursor); - txt_patch_list_push_new(cv->patch_arena, &cv->patches, range, single_line_op.replace); - text_patched = txt_patched_from_info_data_patches(scratch.arena, text_info, text_data, &cv->patches); - *cursor = *mark = range.min; + scratch_end(scratch); } } - //- rjf: valid single-line op => do single-line op - else + //- rjf: movement up (chunk) + if(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y < 0 && line_num > 1) { - if(single_line_op.range.min != single_line_op.range.max || single_line_op.replace.size != 0) + for(U64 scan_line_num = line_num-1; scan_line_num > 0; scan_line_num -= 1) { - txt_patch_list_push_new(cv->patch_arena, &cv->patches, single_line_op.range, single_line_op.replace); - text_patched = txt_patched_from_info_data_patches(scratch.arena, text_info, text_data, &cv->patches); + 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); + } + else if(scan_line_num == 1) + { + *cursor = 0; + } + scratch_end(scratch); } - *cursor = single_line_op.cursor; - *mark = single_line_op.mark; - 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); - change = 1; - taken = 1; } - //- rjf: copy + //- rjf: movement down (page) + if(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(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 + line_count_per_page); + 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(evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y > 0 || delta.x > 0)) + { + *cursor = text_patched.size; + } + + //- rjf: movement to endpoint (-) + if(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); - 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: 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 - if(taken) + ui_eat_event(evt); + + //- rjf: changed cursor -> snap in X + if(*cursor != start_cursor) { - ui_eat_event(evt); + snap[Axis2_X] = 1; + } + + //- rjf: changed cursor line -> snap in Y + if(*cursor < line_range.min || line_range.max < *cursor) + { + snap[Axis2_Y] = 1; } } } diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index f99d3151..7debd3ee 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -2860,7 +2860,7 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred if(line_range.min <= *cursor && *cursor <= line_range.max) { 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 = line_box->rect.y0-params->font_size*0.125f; + 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); @@ -3885,7 +3885,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) @@ -3899,7 +3899,7 @@ rd_cell(RD_CellParams *params, String8 string) params->edit_string_size_out[0] = new_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); } @@ -3913,7 +3913,7 @@ rd_cell(RD_CellParams *params, String8 string) } // rjf: perform copy - if(op.flags & UI_TxtOpFlag_Copy) + if(evt->flags & UI_EventFlag_Copy) { wm_set_clipboard_text(op.copy); } diff --git a/src/ui/ui_basic_widgets.c b/src/ui/ui_basic_widgets.c index 9098bfbb..93d8ea06 100644 --- a/src/ui/ui_basic_widgets.c +++ b/src/ui/ui_basic_widgets.c @@ -238,7 +238,7 @@ ui_line_edit(U64 *cursor, U64 *mark, U8 *edit_buffer, U64 edit_buffer_size, U64 } // 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(op.range.min != op.range.max || op.replace.size != 0) @@ -250,7 +250,7 @@ ui_line_edit(U64 *cursor, U64 *mark, U8 *edit_buffer, U64 edit_buffer_size, U64 } // rjf: perform copy - if(op.flags & UI_TxtOpFlag_Copy) + if(evt->flags & UI_EventFlag_Copy) { wm_set_clipboard_text(op.copy); } diff --git a/src/ui/ui_core.c b/src/ui/ui_core.c index 684850fc..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, U64 cursor, U64 mark) +ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, Rng1U64 cursor_range, U64 cursor, U64 mark) { 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,7 +163,7 @@ 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, (S64)cursor+1, delta.x > 0 ? Side_Max : Side_Min)-1 - (S64)cursor); + 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: @@ -179,8 +178,8 @@ ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, break; } } - U64 home_dest_off = (cursor == first_nonwhitespace_off) ? 0 : first_nonwhitespace_off; - delta.x = (delta.x > 0) ? ((S64)string.size - (S64)cursor) : ((S64)home_dest_off - (S64)cursor); + 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; } @@ -191,26 +190,17 @@ ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, } //- rjf: form next cursor - B32 cursor_out_of_bounds = 0; if(cursor == mark || !(event->flags & UI_EventFlag_ZeroDeltaOnSelect)) { - if(delta.x < -(S32)next_cursor) - { - delta.x = -(S32)next_cursor; - cursor_out_of_bounds = 1; - } - if(cursor + delta.x > string.size) - { - delta.x = (S32)((S64)string.size - (S64)cursor); - cursor_out_of_bounds = 1; - } + 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 = Clamp(0, next_cursor, string.size); + 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 @@ -229,16 +219,7 @@ ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, //- rjf: copying if(event->flags & UI_EventFlag_Copy) { - copy = str8_substr(string, r1u64(cursor, mark)); - flags |= UI_TxtOpFlag_Copy; - } - - //- rjf: pasting - if(event->flags & UI_EventFlag_Paste) - { - range = r1u64(cursor, mark); - replace = wm_get_clipboard_text(arena); - next_cursor = next_mark = cursor + replace.size; + copy = str8_substr(string, r1u64(cursor - cursor_range.min, mark - cursor_range.min)); } //- rjf: deletion @@ -264,20 +245,9 @@ ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, next_cursor = next_mark = range.min + event->string.size; } - //- rjf: determine if this event should be taken, based on bounds of cursor - { - if(next_cursor > string.size+replace.size || event->delta_2s32.y != 0 || cursor_out_of_bounds) - { - flags |= UI_TxtOpFlag_Invalid; - } - next_cursor = Clamp(0, next_cursor, string.size+replace.size); - next_mark = Clamp(0, next_mark, string.size+replace.size); - } - //- rjf: build+fill UI_TxtOp op = {0}; { - op.flags = flags; op.replace = replace; op.copy = copy; op.range = range; diff --git a/src/ui/ui_core.h b/src/ui/ui_core.h index c7dcde6d..5d2352ba 100644 --- a/src/ui/ui_core.h +++ b/src/ui/ui_core.h @@ -171,17 +171,9 @@ 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; Rng1U64 range; @@ -776,7 +768,7 @@ 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, U64 cursor, U64 mark); +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); //////////////////////////////// diff --git a/src/win32/window_manager/win32_window_manager.c b/src/win32/window_manager/win32_window_manager.c index 235a02f3..c107ef58 100644 --- a/src/win32/window_manager/win32_window_manager.c +++ b/src/win32/window_manager/win32_window_manager.c @@ -506,7 +506,7 @@ w32_wm_wnd_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) case WM_CHAR: { U32 character = wParam; - if(character >= 10 && 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) From 36da3465900334b27d1d0b639c1131456f6c5025 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 10 Jul 2026 08:28:07 -0600 Subject: [PATCH 70/90] binary search for off -> line num --- src/text/text.c | 63 +++++++++++++++++-------------------------------- src/text/text.h | 2 -- 2 files changed, 22 insertions(+), 43 deletions(-) diff --git a/src/text/text.c b/src/text/text.c index 708f4e78..5f54dd12 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -2293,41 +2293,6 @@ txt_token_array_from_string__disasm_x64_intel(Arena *arena, U64 *bytes_processed //////////////////////////////// //~ rjf: Text Info Extractor Helpers -internal U64 -txt_patched_off_from_base_off(TXT_PatchList *patches, U64 base_off) -{ - U64 result = base_off; - for EachNode(n, TXT_PatchNode, patches->first) - { - // rjf: this patch occurred before our result -> shift our offset - if(n->v.range.max < result) - { - S64 delta = (S64)n->v.replace.size - (S64)dim_1u64(n->v.range); - S64 result_adjusted = (S64)result + delta; - result_adjusted = ClampBot(0, result_adjusted); - result = (U64)result_adjusted; - } - - // rjf: if this patch covers our offset -> just reset to the beginning of the patch range - else if(contains_1u64(n->v.range, result)) - { - result = n->v.range.min; - } - - // NOTE(rjf): otherwise, this patch occurs *after* our offset, and so our offset remains - // valid. - } - return result; -} - -internal Rng1U64 -txt_patched_range_from_base_range(TXT_PatchList *patches, Rng1U64 base_range) -{ - Rng1U64 result = r1u64(txt_patched_off_from_base_off(patches, base_range.min), - txt_patched_off_from_base_off(patches, base_range.max)); - return result; -} - internal void txt_line_map_push(Arena *arena, TXT_LineMap *map, Rng1U64 num_range, Rng1U64 *ranges, S64 delta) { @@ -2345,14 +2310,30 @@ 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) { - for EachInRange(num, n->num_range) + if(n->num_range.max != n->num_range.min) { - Rng1U64 line_range = n->ranges[num-n->num_range.min]; - Rng1U64 line_range_shifted = r1u64(line_range.min + n->delta, line_range.max + n->delta); - if(line_range_shifted.min <= off && off <= line_range_shifted.max) + 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) { - result = num; - goto break_all; + 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; + } + } } } } diff --git a/src/text/text.h b/src/text/text.h index d8279b3d..a5467d36 100644 --- a/src/text/text.h +++ b/src/text/text.h @@ -289,8 +289,6 @@ internal TXT_TokenArray txt_token_array_from_string__disasm_x64_intel(Arena *are //////////////////////////////// //~ rjf: Text Info Extractor Helpers -internal U64 txt_patched_off_from_base_off(TXT_PatchList *patches, U64 base_off); -internal Rng1U64 txt_patched_range_from_base_range(TXT_PatchList *patches, Rng1U64 base_range); 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); From 604fe35d911ef976a0dfbf102ccdd7c8839abbf1 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 10 Jul 2026 20:55:03 -0700 Subject: [PATCH 71/90] fix navigations when zero-delta-on-select is enabled in multi-line controls; thread line break parsing --- src/raddbg/raddbg_views.c | 24 ++++++++++-------- src/text/text.c | 53 +++++++++++++++++++++++++++++++++------ 2 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 57529d04..d187d702 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -142,6 +142,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla } 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); @@ -158,8 +159,11 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla *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(evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x > 0 && start_cursor == line_range.max && line_num+1 <= line_count) + if(need_nav && evt->delta_unit != UI_EventDeltaUnit_Whole && 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; @@ -167,7 +171,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla } //- rjf: wrap lines left - if(evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x < 0 && start_cursor == line_range.min && line_num-1 >= 1) + if(need_nav && evt->delta_unit != UI_EventDeltaUnit_Whole && 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; @@ -175,7 +179,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla } //- rjf: movement down (plain) - if(evt->delta_unit == UI_EventDeltaUnit_Char && delta.y > 0 && line_num+1 <= line_count) + 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; @@ -183,7 +187,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla } //- rjf: movement up (plain) - if(evt->delta_unit == UI_EventDeltaUnit_Char && delta.y < 0 && line_num > 1) + 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; @@ -191,7 +195,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla } //- rjf: movement down (chunk) - if(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y > 0 && line_num+1 <= line_count) + if(need_nav && evt->delta_unit == UI_EventDeltaUnit_Word && delta.y > 0 && line_num+1 <= line_count) { for(U64 scan_line_num = line_num+1; scan_line_num <= line_count; scan_line_num += 1) { @@ -212,7 +216,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla } //- rjf: movement up (chunk) - if(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y < 0 && line_num > 1) + if(need_nav && evt->delta_unit == UI_EventDeltaUnit_Word && delta.y < 0 && line_num > 1) { for(U64 scan_line_num = line_num-1; scan_line_num > 0; scan_line_num -= 1) { @@ -233,7 +237,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla } //- rjf: movement down (page) - if(evt->delta_unit == UI_EventDeltaUnit_Page && delta.y > 0) + if(need_nav && evt->delta_unit == UI_EventDeltaUnit_Page && delta.y > 0) { U64 advance = line_count_per_page; U64 next_line = line_num + advance; @@ -243,7 +247,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla } //- rjf: movement up (page) - if(evt->delta_unit == UI_EventDeltaUnit_Page && delta.y < 0) + 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) @@ -257,13 +261,13 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla } //- rjf: movement to endpoint (+) - if(evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y > 0 || delta.x > 0)) + if(need_nav && evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y > 0 || delta.x > 0)) { *cursor = text_patched.size; } //- rjf: movement to endpoint (-) - if(evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y < 0 || delta.x < 0)) + if(need_nav && evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y < 0 || delta.x < 0)) { *cursor = 0; } diff --git a/src/text/text.c b/src/text/text.c index 5f54dd12..db426306 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -3004,19 +3004,21 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 //- 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; } @@ -3027,16 +3029,35 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 } } 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: 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 if(lane_idx() == 0) { 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(); + { + 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)) { @@ -3044,15 +3065,31 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 } 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) { line_range.max -= 1; } U64 line_size = dim_1u64(line_range); - shared->info.lines_ranges[line_idx] = line_range; + shared->info.lines_ranges[lane_line_base_idxs[lane_idx()] + lane_line_idx] = line_range; shared->info.lines_max_size = Max(shared->info.lines_max_size, line_size); - line_idx += 1; + lane_line_idx += 1; line_start_idx = idx+1; } if(idx && idx%1000 == 0) From 150a2247bb6b3d0ebecfd17fc4f35a563e6201ed Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Fri, 10 Jul 2026 21:03:23 -0700 Subject: [PATCH 72/90] fix page-up --- src/raddbg/raddbg_views.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index d187d702..73e0bb40 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -254,7 +254,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla { advance = -(line_num - 1); } - U64 next_line = (U64)((S64)line_num + line_count_per_page); + 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; From f247d3c52db545f72eb140ce1a0b681b2a650d2d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Sat, 11 Jul 2026 06:23:05 -0700 Subject: [PATCH 73/90] fix chunk-up/down --- src/raddbg/raddbg_views.c | 8 ++++++-- src/text/text.c | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 73e0bb40..cf0ca59a 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -197,7 +197,8 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla //- rjf: movement down (chunk) if(need_nav && evt->delta_unit == UI_EventDeltaUnit_Word && delta.y > 0 && line_num+1 <= line_count) { - for(U64 scan_line_num = line_num+1; scan_line_num <= line_count; scan_line_num += 1) + 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); @@ -206,6 +207,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla 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) { @@ -218,7 +220,8 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla //- rjf: movement up (chunk) if(need_nav && evt->delta_unit == UI_EventDeltaUnit_Word && delta.y < 0 && line_num > 1) { - for(U64 scan_line_num = line_num-1; scan_line_num > 0; scan_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); @@ -227,6 +230,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla 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) { diff --git a/src/text/text.c b/src/text/text.c index db426306..7669194a 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -3088,6 +3088,7 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 } U64 line_size = dim_1u64(line_range); shared->info.lines_ranges[lane_line_base_idxs[lane_idx()] + lane_line_idx] = line_range; + // TODO(rjf): this is a race: shared->info.lines_max_size = Max(shared->info.lines_max_size, line_size); lane_line_idx += 1; line_start_idx = idx+1; From 9ed2e020e8cad7fc4d5ff8bb0615eda49a48c58d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 27 Jul 2026 10:24:32 -0600 Subject: [PATCH 74/90] checkpoint --- project.raddbg | 1 - src/raddbg/raddbg.mdesk | 396 ++++++++-------- src/raddbg/raddbg_core.c | 13 +- src/raddbg/raddbg_views.c | 48 +- src/text/generated/text.meta.c | 4 +- src/text/text.c | 796 +++++++++++++++++++++++++++++---- src/text/text.h | 48 +- src/text/text.mdesk | 14 +- 8 files changed, 1010 insertions(+), 310 deletions(-) diff --git a/project.raddbg b/project.raddbg index 4adbb44b..739e03eb 100644 --- a/project.raddbg +++ b/project.raddbg @@ -20,7 +20,6 @@ target: { executable: "build/mule_main.exe" working_directory: build - output_label: output_a } target: { diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 6aea21f3..7464c3be 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -948,302 +948,302 @@ 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" "{tab_commands}" } - {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." "" "" } //- 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 "" 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 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: diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 986f53fb..6d3e735e 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -9802,12 +9802,13 @@ 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_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; } diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index cf0ca59a..184a9662 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -163,7 +163,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla 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 && delta.x > 0 && start_cursor == line_range.max && line_num+1 <= line_count) + 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; @@ -171,7 +171,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla } //- rjf: wrap lines left - if(need_nav && evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x < 0 && start_cursor == line_range.min && line_num-1 >= 1) + 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; @@ -385,7 +385,16 @@ 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, ctx_token_pt, visible_data, visible_byte_range.min, max_U64); ////////////////////////////// //- rjf: selection on single line, no query? -> set search text @@ -459,7 +468,7 @@ 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; @@ -474,8 +483,35 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla 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; - // TODO(rjf): tokens - // code_slice_params.line_tokens[visible_line_idx] = slice.line_tokens[visible_line_idx]; + } + } + + // 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); } } 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 7669194a..70159cc0 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -549,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 == '*' || @@ -624,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: { @@ -893,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 == '*' || @@ -968,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: { @@ -1179,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 == '*' || @@ -1254,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: { @@ -1465,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 == '*' || @@ -1547,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; @@ -1709,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 @@ -2066,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 @@ -2109,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 == '_') @@ -2259,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; @@ -2357,6 +2351,296 @@ txt_range_from_line_num(TXT_LineMap *map, U64 num) 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 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) + { + first_idx = mid_idx; + } + else if(off == mid_off) + { + first_idx = mid_idx; + break; + } + } + result = n->num_range.min + first_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_TokenPt ctx_token_pt, String8 data, U64 base_off, U64 limit) +{ + Temp scratch = scratch_begin(&arena, 1); + TXT_TokenChunkList tokens = {0}; + { + 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; + 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 + if(active_token_kind != TXT_TokenKind_Null && byte == '\\') + { + 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 == '~' || 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 == '"') + { + token_finished = 1; + advance = 1; + } + }break; + case TXT_TokenKind_Char: + { + if(byte == '\'') + { + 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: + { + if(byte == '\n') + { + token_finished = 1; + advance = 1; + } + }break; + case TXT_TokenKind_BlockComment: + { + if(byte == '*' && next_byte == '/') + { + token_finished = 1; + advance = 2; + } + }break; + case TXT_TokenKind_Meta: + { + if(byte == '\n') + { + token_finished = 1; + advance = 1; + } + }break; + } + + //- rjf: finish all tokens if we're at the end of the data + if(off == data.size) + { + token_finished = 1; + advance = 1; + } + + //- 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 + 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) { @@ -2366,14 +2650,17 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat 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(0, info->big_token_pts_count), 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); @@ -2390,6 +2677,17 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat 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 ranges of unchanged token pts before/after this replace-range + 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); + // 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); @@ -2417,6 +2715,12 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat rng1u64_list_push(scratch.arena, &replace_line_ranges, r1u64(last_line_start_off, n->v.replace.size)); } + // rjf: compute token pt delta + S64 token_pt_delta = 0; + { + // TODO(rjf) + } + // rjf: push all portions of pre-replace / post-replace ranges in previous memory map { for EachNode(map_n, MemoryMapRangeNode, last_memory_map.first_range) @@ -2504,10 +2808,32 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat // 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: 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); + } + } + } + // rjf: advance to the next memory map 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 @@ -2522,6 +2848,10 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat { 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); @@ -2969,38 +3299,8 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 } //- 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; @@ -3048,11 +3348,16 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 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); } - lane_sync(); + 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; @@ -3084,12 +3389,12 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 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[lane_line_base_idxs[lane_idx()] + lane_line_idx] = line_range; - // TODO(rjf): this is a race: - shared->info.lines_max_size = Max(shared->info.lines_max_size, line_size); + lane_line_max_size[lane_idx()] = Max(lane_line_max_size[lane_idx()], line_size); lane_line_idx += 1; line_start_idx = idx+1; } @@ -3102,6 +3407,345 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 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(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; + U8 prev_byte = (off > 0) ? 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(prev_byte != '\\' && (byte == '"' || byte == '\'')) + { + // NOTE(rjf): no-op + } + 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(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(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 + 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; + } + + //- 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; + } + } + + //- rjf: advance + 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 a5467d36..a6b2f043 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; @@ -209,25 +220,30 @@ struct TXT_LineMap 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; -}; - -typedef struct TXT_Line TXT_Line; -struct TXT_Line -{ - Rng1U64 range; -}; - -typedef struct TXT_LineArray TXT_LineArray; -struct TXT_LineArray -{ - TXT_Line *v; - U64 count; + TXT_TokenPtMap token_pt_map; }; //////////////////////////////// @@ -292,6 +308,10 @@ internal TXT_TokenArray txt_token_array_from_string__disasm_x64_intel(Arena *are 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_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: 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: { From e55fa733fd6ce4a3222090fc9c728f82ca42c8a1 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 27 Jul 2026 12:20:38 -0700 Subject: [PATCH 75/90] mostly restore existing read-only functionality, keep existing patching infrastructure but do not use --- project.raddbg | 1 + src/raddbg/raddbg_core.c | 1 + src/raddbg/raddbg_views.c | 8 +- src/text/text.c | 334 +++++++++++++++++++++++++++++++++----- src/text/text.h | 2 +- 5 files changed, 306 insertions(+), 40 deletions(-) diff --git a/project.raddbg b/project.raddbg index 739e03eb..4adbb44b 100644 --- a/project.raddbg +++ b/project.raddbg @@ -20,6 +20,7 @@ target: { executable: "build/mule_main.exe" working_directory: build + output_label: output_a } target: { diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 6d3e735e..14f952a3 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -9805,6 +9805,7 @@ rd_code_color_slot_from_txt_token_kind(TXT_TokenKind kind) 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; diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 184a9662..6f6ab47d 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -155,6 +155,12 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla //- 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; @@ -394,7 +400,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla // 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, ctx_token_pt, visible_data, visible_byte_range.min, max_U64); + 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 diff --git a/src/text/text.c b/src/text/text.c index 70159cc0..55ac73d9 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -2375,6 +2375,7 @@ txt_token_pt_num_from_off(TXT_TokenPtMap *map, U64 off) 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;) @@ -2387,15 +2388,16 @@ txt_token_pt_num_from_off(TXT_TokenPtMap *map, U64 off) } else if(mid_off < off) { - first_idx = mid_idx; + found_idx = mid_idx; + first_idx = mid_idx + 1; } else if(off == mid_off) { - first_idx = mid_idx; + found_idx = mid_idx; break; } } - result = n->num_range.min + first_idx; + result = n->num_range.min + found_idx; break; } } @@ -2423,14 +2425,17 @@ txt_token_pt_from_num(TXT_TokenPtMap *map, U64 num) } internal TXT_TokenArray -txt_token_array_from_data(Arena *arena, TXT_TokenPt ctx_token_pt, String8 data, U64 base_off, U64 limit) +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; + U64 active_token_start_off = ctx_token_pt.off - base_off; B32 escaped = 0; for(U64 off = 0; off <= data.size;) { @@ -2555,7 +2560,7 @@ txt_token_array_from_data(Arena *arena, TXT_TokenPt ctx_token_pt, String8 data, }break; case TXT_TokenKind_String: { - if(byte == '"') + if(byte == '"' && !escaped) { token_finished = 1; advance = 1; @@ -2563,7 +2568,7 @@ txt_token_array_from_data(Arena *arena, TXT_TokenPt ctx_token_pt, String8 data, }break; case TXT_TokenKind_Char: { - if(byte == '\'') + if(byte == '\'' && !escaped) { token_finished = 1; advance = 1; @@ -2590,7 +2595,7 @@ txt_token_array_from_data(Arena *arena, TXT_TokenPt ctx_token_pt, String8 data, }break; case TXT_TokenKind_LineComment: { - if(byte == '\n') + if(byte == '\n' && !escaped) { token_finished = 1; advance = 1; @@ -2606,7 +2611,7 @@ txt_token_array_from_data(Arena *arena, TXT_TokenPt ctx_token_pt, String8 data, }break; case TXT_TokenKind_Meta: { - if(byte == '\n') + if(byte == '\n' && !escaped) { token_finished = 1; advance = 1; @@ -2621,6 +2626,20 @@ txt_token_array_from_data(Arena *arena, TXT_TokenPt ctx_token_pt, String8 data, 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) { @@ -2646,53 +2665,65 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat { Temp scratch = scratch_begin(&arena, 1); - // rjf: produce default case, where we just have one range which covers the original data + ////////////////////////////// + //- 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(0, info->big_token_pts_count), info->big_token_pts, 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 + ////////////////////////////// + //- 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 + //////////////////////////// + //- 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 + //////////////////////////// + //- 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 + //////////////////////////// + //- 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 + //////////////////////////// + //- 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 ranges of unchanged token pts before/after this replace-range - 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); - - // rjf: compute delta & next size + //////////////////////////// + //- 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 + //////////////////////////// + //- rjf: compute line count delta, + list of line ranges inside of replace + // S64 line_delta = 0; Rng1U64List replace_line_ranges = {0}; { @@ -2715,13 +2746,9 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat rng1u64_list_push(scratch.arena, &replace_line_ranges, r1u64(last_line_start_off, n->v.replace.size)); } - // rjf: compute token pt delta - S64 token_pt_delta = 0; - { - // TODO(rjf) - } - - // rjf: push all portions of pre-replace / post-replace ranges in previous memory map + //////////////////////////// + //- rjf: push all portions of pre-replace / post-replace ranges in previous memory map + // { for EachNode(map_n, MemoryMapRangeNode, last_memory_map.first_range) { @@ -2741,13 +2768,17 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat } } - // rjf: push replaced range + //////////////////////////// + //- 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 + //////////////////////////// + //- 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) { @@ -2768,7 +2799,9 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat } } - // rjf: compute affected line ranges + //////////////////////////// + //- rjf: compute affected line ranges + // U64 affected_line_count = replace_line_ranges.count; Rng1U64 *affected_line_ranges = push_array(arena, Rng1U64, affected_line_count); { @@ -2805,10 +2838,223 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat } } - // rjf: push affected line ranges + //////////////////////////// + //- 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: push all portions of pre-replace / post-replace ranges in previous token pt map + //////////////////////////// + //- 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) { @@ -2824,12 +3070,22 @@ txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 dat 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); + 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: advance to the next memory map + //////////////////////////// + //- 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; @@ -3457,6 +3713,7 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 }; 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) { @@ -3526,7 +3783,7 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 //- rjf: join all token endpoint candidates from all lanes TokenEndpointCandidateChunkNode *first_token_endpoint_candidate_chunk = 0; TokenEndpointCandidateChunkNode *last_token_endpoint_candidate_chunk = 0; - if(lane_idx() == 0) + if(lang != TXT_LangKind_Null && lane_idx() == 0) { for EachIndex(l_idx, lane_count()) { @@ -3557,7 +3814,7 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 TokenPtChunkNode *first_token_pt_chunk = 0; TokenPtChunkNode *last_token_pt_chunk = 0; U64 total_token_pt_count = 0; - if(lane_idx() == 0) + if(lang != TXT_LangKind_Null && lane_idx() == 0) { TXT_TokenKind active_token_kind = TXT_TokenKind_Null; U64 active_token_start_off = 0; @@ -3712,6 +3969,7 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 chunk->v[chunk->count] = pts[pt_idx]; chunk->count += 1; total_token_pt_count += 1; + active_token_kind = TXT_TokenKind_Null; } } diff --git a/src/text/text.h b/src/text/text.h index a6b2f043..521a46c8 100644 --- a/src/text/text.h +++ b/src/text/text.h @@ -311,7 +311,7 @@ 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_TokenPt ctx_token_pt, String8 data, U64 base_off, U64 limit); +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: From a27dc03ca2b55b0ba687395e1b7e60b511f0314b Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 27 Jul 2026 12:48:42 -0700 Subject: [PATCH 76/90] start meta tokens correctly --- src/text/text.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/text/text.c b/src/text/text.c index 55ac73d9..8cdba0b6 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -2493,6 +2493,11 @@ txt_token_array_from_data(Arena *arena, TXT_LangKind lang_kind, TXT_TokenPt ctx_ 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 == '*' || From 65f5b81d494d55d1ee2cc1bcfde01734d728793e Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 27 Jul 2026 12:55:14 -0700 Subject: [PATCH 77/90] fix incorrect multiline hotkey mode setting in text view --- src/raddbg/raddbg_views.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 6f6ab47d..a3c977fd 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -128,8 +128,10 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla 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; - rd_state->text_edit_mode_multiline = 1; U64 line_count_per_page = ClampBot(num_possible_visible_lines, 10) - 10; U64 *cursor = &rd_regs()->cursor; U64 *mark = &rd_regs()->mark; From aa94eb69013d3769a6da03de74773243d3a4bed3 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 27 Jul 2026 12:56:41 -0700 Subject: [PATCH 78/90] fix find-text-forward --- src/raddbg/raddbg_views.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index a3c977fd..d96fa77e 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -776,7 +776,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla if(needle_pos < data.size) { done = 1; - rd_regs()->mark = needle_pos; + rd_regs()->mark = off+needle_pos; rd_regs()->cursor = rd_regs()->mark + needle.size; } scratch_end(scratch); @@ -820,7 +820,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla if(needle_pos < data.size) { done = 1; - rd_regs()->mark = needle_pos; + rd_regs()->mark = off+needle_pos; rd_regs()->cursor = rd_regs()->mark + needle.size; } scratch_end(scratch); From 62241f0d55e68c0f67688758e32b8207b8ef02bf Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 27 Jul 2026 13:45:24 -0700 Subject: [PATCH 79/90] fix text search fwds/bwds --- src/raddbg/raddbg_views.c | 97 ++++++++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index d96fa77e..93ebfc8e 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -767,69 +767,94 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla if(cv->find_text_fwd.size != 0) { String8 needle = cv->find_text_fwd; - B32 done = 0; - for(U64 off = rd_regs()->cursor+1; !done && off < text_patched.size; off += search_chunk_size) + Rng1U64 ranges[] = { - 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 needle_pos = str8_find_needle(data, 0, needle, StringMatchFlag_CaseInsensitive); - if(needle_pos < data.size) + 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) { - done = 1; - rd_regs()->mark = off+needle_pos; - rd_regs()->cursor = rd_regs()->mark + needle.size; + 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; } } - scratch_end(scratch); } - if(!done) + done_fwd:; + if(!found) { log_user_errorf("Could not find `%S`", needle); } - cv->center_cursor = done; + cv->center_cursor = found; } //- rjf: find text (backward) if(cv->find_text_bwd.size != 0 && rd_regs()->cursor > 0) { String8 needle = cv->find_text_bwd; - B32 done = 0; - for(U64 off = rd_regs()->cursor-1, next_off = 0; !done; off = next_off) + Rng1U64 ranges[] = { - U64 advance = Min(search_chunk_size, off); - next_off = off - advance; - 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 needle_pos = 0; + 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) { - U64 start_search_pos = 0; - for(;;) + 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;) { - U64 next_needle_pos = str8_find_needle(data, start_search_pos, needle, StringMatchFlag_CaseInsensitive); - if(next_needle_pos < data.size) + 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) { - needle_pos = next_needle_pos; + hint_needle_pos = next_hint_needle_pos; } else { - needle_pos = data.size; break; } - start_search_pos = next_needle_pos+1; } + 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(needle_pos < data.size) - { - done = 1; - rd_regs()->mark = off+needle_pos; - rd_regs()->cursor = rd_regs()->mark + needle.size; - } - scratch_end(scratch); } - if(!done) + done_bwd:; + if(!found) { log_user_errorf("Could not find `%S`", needle); } - cv->center_cursor = done; + cv->center_cursor = found; } MemoryZeroStruct(&cv->find_text_fwd); @@ -2568,7 +2593,7 @@ RD_VIEW_UI_FUNCTION_DEF(text) ui_spacer(ui_em(1.5f, 1)); } Rng1U64 cursor_line_range = txt_range_from_line_num(&patched.line_map, cursor_line_num); - ui_labelf("Line: %I64d, Column: %I64d", cursor_line_num, 1 + rd_regs()->cursor - cursor_line_range.min); + 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", From 7fbea68613dcaefe186eeca381a3f38ce2fe5ea1 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 27 Jul 2026 13:50:46 -0700 Subject: [PATCH 80/90] fix line-terminated tokens causing incorrect token pt parsing - e.g. unmatched quote in a comment --- src/text/text.c | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/text/text.c b/src/text/text.c index 8cdba0b6..b48987a8 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -3884,6 +3884,7 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 //- 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)) @@ -3905,6 +3906,7 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 } } active_token_end_off = line_range.max; + line_advance = 1; } //- rjf: try to end all other cases by looking at subsequent endpoint candidates @@ -3978,12 +3980,35 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 } } - //- rjf: advance - cand_chunk_idx += 1; - if(cand_chunk_idx >= cand_chunk_n->count) + //- rjf: advance across many token candidates until we find the new line + if(line_advance) { - cand_chunk_n = cand_chunk_n->next; - cand_chunk_idx = 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) + { + 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:; + } + + //- 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; + } } } } From 0c434583bcf30adb8ebb98e738f58926b9a2ba2d Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 27 Jul 2026 14:33:11 -0700 Subject: [PATCH 81/90] eval, local variable resolution -> do not assume offset evaluations. infer evaluation mode from location info --- project.raddbg | 5 +++ src/codeview/codeview_parse.c | 78 +++++++++++++++++++++-------------- src/eval/eval_core.c | 3 +- src/eval/eval_ir.c | 1 + src/text/text.c | 7 ++++ 5 files changed, 60 insertions(+), 34 deletions(-) diff --git a/project.raddbg b/project.raddbg index 4adbb44b..689b88cc 100644 --- a/project.raddbg +++ b/project.raddbg @@ -68,3 +68,8 @@ target: executable: "build/ryan_scratch.exe" working_directory: build } +target: +{ + executable: "../../Program Files/SuperTux/bin/supertux2.exe" + working_directory: "../../Program Files/SuperTux/bin/" +} 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/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/text/text.c b/src/text/text.c index b48987a8..6b56598c 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -3983,6 +3983,7 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 //- 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) { @@ -3990,6 +3991,7 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 { 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; @@ -3998,6 +4000,11 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 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 From 41735fbf8b2341fd49b6821b5b10210f35046728 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 27 Jul 2026 14:42:01 -0700 Subject: [PATCH 82/90] fix mismatched spaces for token/line dragging --- src/raddbg/raddbg_widgets.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index 7debd3ee..5d3ed676 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -2019,11 +2019,11 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred if(contains_1u64(line_token->range, mouse_off)) { Rng1U64 line_token_range_clamped = intersect_1u64(line_token->range, line_range); - mouse_token_rng = r1u64(line_token_range_clamped.min-line_range.min, 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 = r1u64(0, dim_1u64(line_range)); + mouse_line_rng = line_range; } ////////////////////////////// @@ -2630,7 +2630,7 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred 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 = r1u64(expr_off_rng.min - line_range.min, 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+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)); From 81722ebbaca21dfdeb021a4df55aa1fee91cefef Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 27 Jul 2026 14:46:00 -0700 Subject: [PATCH 83/90] fix oob mouse -> line path --- src/raddbg/raddbg_widgets.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index 5d3ed676..348a2a90 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -1982,7 +1982,9 @@ rd_code_slice(RD_CodeSliceParams *params, U64 *cursor, U64 *mark, S64 *preferred Vec2F32 mouse = ui_mouse(); // rjf: mouse y => index - 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); From 0b4c20b1652e69884fa4e4b3291dc4724a8b7d5e Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 27 Jul 2026 15:50:52 -0700 Subject: [PATCH 84/90] fix the lack of using contextual cursor info to commit to command parameter registers --- src/raddbg/raddbg_views.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 93ebfc8e..c067bf4c 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -2474,6 +2474,7 @@ RD_VIEW_UI_FUNCTION_DEF(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 @@ -2484,6 +2485,8 @@ RD_VIEW_UI_FUNCTION_DEF(text) 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, (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 @@ -2592,7 +2595,6 @@ RD_VIEW_UI_FUNCTION_DEF(text) ui_label(rd_regs()->file_path); ui_spacer(ui_em(1.5f, 1)); } - Rng1U64 cursor_line_range = txt_range_from_line_num(&patched.line_map, cursor_line_num); 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)"); From ec520227c541e7ef782b92c68b2b86d35d6ac464 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 27 Jul 2026 16:02:10 -0700 Subject: [PATCH 85/90] enforce standalone 'expression' child for watch expressions in cfg tree, adjust eval / ui to work on that child, instead of assuming the first child of a watch expression node is the expression string itself - this will let us tag watch expressions with other info, like project / lock status etc. --- project.raddbg | 2 +- src/raddbg/generated/raddbg.meta.c | 5 +++-- src/raddbg/generated/raddbg.meta.h | 2 +- src/raddbg/raddbg.mdesk | 3 ++- src/raddbg/raddbg_core.c | 30 ++++++++++++++++-------------- src/raddbg/raddbg_eval.c | 5 +++-- 6 files changed, 26 insertions(+), 21 deletions(-) diff --git a/project.raddbg b/project.raddbg index 689b88cc..9637d013 100644 --- a/project.raddbg +++ b/project.raddbg @@ -71,5 +71,5 @@ target: target: { executable: "../../Program Files/SuperTux/bin/supertux2.exe" - working_directory: "../../Program Files/SuperTux/bin/" + working_directory: "../../Program Files/SuperTux/bin" } diff --git a/src/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index adfc729f..6b9a64e0 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[369] = { {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}, @@ -456,7 +457,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")}, diff --git a/src/raddbg/generated/raddbg.meta.h b/src/raddbg/generated/raddbg.meta.h index 0fb38188..744003f2 100644 --- a/src/raddbg/generated/raddbg.meta.h +++ b/src/raddbg/generated/raddbg.meta.h @@ -623,7 +623,7 @@ 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[369]; extern RD_NameSchemaInfo rd_name_schema_info_table[39]; extern String8 rd_reg_slot_code_name_table[54]; extern Rng1U64 rd_reg_slot_range_table[54]; diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 7464c3be..4355bb5b 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, } ``` } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 14f952a3..38dc7316 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -2034,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); @@ -2778,13 +2778,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); @@ -3526,8 +3526,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; @@ -12187,10 +12188,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) { @@ -12383,10 +12384,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 = @@ -16563,7 +16564,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; @@ -16579,8 +16580,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; diff --git a/src/raddbg/raddbg_eval.c b/src/raddbg/raddbg_eval.c index f5fc95ca..ea0f0d62 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,8 @@ 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")); + evals_out[idx] = e_eval_from_string(expr->first->string); } } } From ae8960d5630434eb9f3eea9e27f078dc70acb298 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 28 Jul 2026 07:42:44 -0700 Subject: [PATCH 86/90] watch lock (ui part, no caching yet) --- src/raddbg/raddbg_core.c | 45 +++++++++++++++++++++++++++++++++++++ src/raddbg/raddbg_widgets.c | 41 +++++++++++++++++++++++++++++++++ src/raddbg/raddbg_widgets.h | 18 +++++++++------ 3 files changed, 97 insertions(+), 7 deletions(-) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 38dc7316..ed39fd8e 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -4024,9 +4024,30 @@ 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 && + cell->eval.msgs.count == 0) + { + cell_can_be_locked = ((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); @@ -4042,6 +4063,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; @@ -4145,6 +4167,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) { @@ -4234,6 +4262,23 @@ rd_view_ui(Rng2F32 rect) 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 + if(next_cell_is_locked != cell_is_locked) + { + CFG_Node *cfg = row_info->group_cfg_child; + if(cfg != &cfg_nil_node) + { + if(next_cell_is_locked) + { + cfg_node_child_from_string_or_alloc(rd_state->cfg, cfg, s("locked")); + } + else + { + cfg_node_release(rd_state->cfg, cfg_node_child_from_string(cfg, s("locked"))); + } + } + } } } diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index 348a2a90..7ccdc1a6 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -3607,6 +3607,47 @@ 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) + { + UI_PrefWidth(ui_em(2.f, 1.f)) + UI_TagF(".") + UI_TagF("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[(params->lock_out && params->lock_out[0]) ? 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_label((params->lock_out && params->lock_out[0]) ? s("Unlock Evaluated Location") : s("Lock Evaluated Location")); + } + if(ui_pressed(sig) && params->lock_out) + { + params->lock_out[0] ^= 1; + } + } + // TODO(rjf): @hack + { + ui_spacer(ui_em(0.5f, 1.f)); + } + } + ////////////////////////////// //- rjf: build toggle-switch // diff --git a/src/raddbg/raddbg_widgets.h b/src/raddbg/raddbg_widgets.h index be3d5497..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; From 0fe5aa5a4370c57d566124eed0e54cbd2b37c305 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 28 Jul 2026 11:18:00 -0700 Subject: [PATCH 87/90] first pass at watch expr location locks - still some jank around the edges due to eval incompleteness, but works for basic cases --- src/dbg_engine/dbg_engine_ctrl.c | 17 ++ src/dbg_engine/dbg_engine_ctrl.h | 1 + .../eval_visualization_core.c | 70 -------- .../eval_visualization_core.h | 25 --- src/raddbg/generated/raddbg.meta.c | 13 +- src/raddbg/generated/raddbg.meta.h | 5 +- src/raddbg/raddbg.mdesk | 4 + src/raddbg/raddbg_core.c | 163 +++++++++++++++++- src/raddbg/raddbg_eval.c | 19 +- src/raddbg/raddbg_widgets.c | 87 +++++----- src/ui/ui_core.h | 3 + 11 files changed, 258 insertions(+), 149 deletions(-) diff --git a/src/dbg_engine/dbg_engine_ctrl.c b/src/dbg_engine/dbg_engine_ctrl.c index a90b39f9..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) diff --git a/src/dbg_engine/dbg_engine_ctrl.h b/src/dbg_engine/dbg_engine_ctrl.h index c55144e1..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 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/raddbg/generated/raddbg.meta.c b/src/raddbg/generated/raddbg.meta.c index 6b9a64e0..7b4e96f5 100644 --- a/src/raddbg/generated/raddbg.meta.c +++ b/src/raddbg/generated/raddbg.meta.c @@ -62,7 +62,7 @@ str8_lit_comp(""), str8_lit_comp(""), }; -RD_VocabInfo rd_vocab_info_table[369] = +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}, @@ -300,6 +300,9 @@ RD_VocabInfo rd_vocab_info_table[369] = {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}, @@ -594,7 +597,7 @@ Rng1U64 rd_reg_slot_range_table[54] = {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")}}, @@ -719,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("")}}, @@ -854,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}}, @@ -907,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 744003f2..1b22d152 100644 --- a/src/raddbg/generated/raddbg.meta.h +++ b/src/raddbg/generated/raddbg.meta.h @@ -190,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, @@ -623,7 +626,7 @@ 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[369]; +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[54]; extern Rng1U64 rd_reg_slot_range_table[54]; diff --git a/src/raddbg/raddbg.mdesk b/src/raddbg/raddbg.mdesk index 4355bb5b..0d9817f2 100644 --- a/src/raddbg/raddbg.mdesk +++ b/src/raddbg/raddbg.mdesk @@ -1087,6 +1087,9 @@ RD_CmdTable: // | | | | {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 0 1 1 "" Null 0 0 0 0 0 0 0 Null "move_left" "Move Left" "Moves the cursor or selection left." "" "" } @@ -1373,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 } diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index ed39fd8e..b9e644eb 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -2049,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 // @@ -2694,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 // @@ -4027,11 +4129,14 @@ rd_view_ui(Rng2F32 rect) // 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 && - cell->eval.msgs.count == 0) + !(cell_info.flags & RD_WatchCellFlag_Expr) && + cell->eval.string.size != 0) { - cell_can_be_locked = ((cell->eval.irtree.mode == E_Mode_Offset) || + 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))))); } @@ -4264,14 +4369,24 @@ rd_view_ui(Rng2F32 rect) } // rjf: apply locks - if(next_cell_is_locked != cell_is_locked) { CFG_Node *cfg = row_info->group_cfg_child; - if(cfg != &cfg_nil_node) + if(next_cell_is_locked != cell_is_locked && cfg != &cfg_nil_node) { if(next_cell_is_locked) { - cfg_node_child_from_string_or_alloc(rd_state->cfg, cfg, s("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 { @@ -12338,12 +12453,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); + } } } @@ -16876,6 +16996,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_eval.c b/src/raddbg/raddbg_eval.c index ea0f0d62..ab9f89e2 100644 --- a/src/raddbg/raddbg_eval.c +++ b/src/raddbg/raddbg_eval.c @@ -1462,7 +1462,24 @@ E_TYPE_EXPAND_RANGE_FUNCTION_DEF(watches) { CFG_Node *cfg = accel->cfgs.v[cfg_idx]; CFG_Node *expr = cfg_node_child_from_string(cfg, s("expression")); - evals_out[idx] = e_eval_from_string(expr->first->string); + 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_widgets.c b/src/raddbg/raddbg_widgets.c index 7ccdc1a6..a1e797c4 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -3417,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 // @@ -3607,47 +3653,6 @@ 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) - { - UI_PrefWidth(ui_em(2.f, 1.f)) - UI_TagF(".") - UI_TagF("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[(params->lock_out && params->lock_out[0]) ? 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_label((params->lock_out && params->lock_out[0]) ? s("Unlock Evaluated Location") : s("Lock Evaluated Location")); - } - if(ui_pressed(sig) && params->lock_out) - { - params->lock_out[0] ^= 1; - } - } - // TODO(rjf): @hack - { - ui_spacer(ui_em(0.5f, 1.f)); - } - } - ////////////////////////////// //- rjf: build toggle-switch // diff --git a/src/ui/ui_core.h b/src/ui/ui_core.h index 5d2352ba..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; From b27585476a76d8b0d98f3d552bdf0ba94f9112e7 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Tue, 28 Jul 2026 14:12:27 -0700 Subject: [PATCH 88/90] fix popup controls --- src/raddbg/raddbg_core.c | 44 +++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index b9e644eb..52c1a7c3 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -6250,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| @@ -6263,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); @@ -8168,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() && From ba9d36624d11fb54724b4426bd59bcf766acc418 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 29 Jul 2026 11:35:28 -0700 Subject: [PATCH 89/90] lexer fixes; eval2 resolution qualifiers --- project.4coder | 2 +- project.raddbg | 2 +- src/eval2/eval2.c | 47 +++++++- src/eval2/eval2.h | 3 + src/eval2/tests/eval2_tests.c | 191 ++++++++++++++++++++++++++++++++ src/raddbg/tests/raddbg_tests.c | 2 +- src/scratch/ryan_scratch.c | 1 + src/text/text.c | 45 +++++--- src/torture/torture.c | 52 +++++++++ src/torture/torture_main.c | 3 + 10 files changed, 327 insertions(+), 21 deletions(-) create mode 100644 src/eval2/tests/eval2_tests.c diff --git a/project.4coder b/project.4coder index 0b10da9c..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 raddbg", .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 9637d013..afa0b2b8 100644 --- a/project.raddbg +++ b/project.raddbg @@ -26,7 +26,7 @@ target: { executable: "build/torture.exe" working_directory: build - arguments: "raddbg/*" + arguments: "eval2/*" } target: { diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index fd15cdd8..5990c1e6 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -1917,12 +1917,46 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, //- 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(definitions_are_allowed && !identifier_mapped && e2_try_token(lang, string, E2_TokenKind_Symbol, s("="), &off, 0)) + 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; @@ -1944,7 +1978,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } // rjf: try to resolve it as a macro definition - if(definitions_are_allowed && !identifier_mapped) + 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)) @@ -2012,7 +2046,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } // rjf: first try to resolve as an operator name - if(!identifier_mapped) + if(qualifiers.node_count == 0 && !identifier_mapped) { // rjf: string -> op kind E2_ExprKind expr_kind = E2_ExprKind_Null; @@ -2055,7 +2089,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, } // rjf: try to resolve it as a macro argument - if(!identifier_mapped && state->top_task->expr_kind == E2_ExprKind_Macro) + if(qualifiers.node_count == 0 && !identifier_mapped && state->top_task->expr_kind == E2_ExprKind_Macro) { U64 macro_arg_num = 0; { @@ -2086,6 +2120,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, done = 1; parse.status = E2_ParseStatus_CheckIdentifierIsType; parse.identifier = identifier; + parse.qualifiers = qualifiers; off = start_off; } else @@ -2093,6 +2128,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, identifier_mapped = 1; expr = e2_expr(arena, identifier_is_type ? E2_ExprKind_TypeIdentifier : E2_ExprKind_Identifier); expr->string = str8_substr(string, token.range); + expr->qualifiers = qualifiers; state->caller_request_count = 0; } } @@ -2651,6 +2687,7 @@ e2_compile_from_expr(Arena *arena, E2_CompileState *state, E2_IRNode *resolve_re done = 1; compile.status = E2_CompileStatus_MissedIdentifierResolution; compile.identifier = e->string; + compile.qualifiers = e->qualifiers; } }break; diff --git a/src/eval2/eval2.h b/src/eval2/eval2.h index 1479c7ca..c8736d19 100644 --- a/src/eval2/eval2.h +++ b/src/eval2/eval2.h @@ -326,6 +326,7 @@ struct E2_Expr U32 macro_arg_num; Rng1U64 src_range; String8 string; + String8List qualifiers; }; typedef struct E2_ExprMapNode E2_ExprMapNode; @@ -400,6 +401,7 @@ struct E2_Parse { E2_ParseStatus status; String8 identifier; + String8List qualifiers; E2_Expr *expr; String8 member_name; E2_Expr *params_expr; @@ -469,6 +471,7 @@ struct E2_Compile { E2_CompileStatus status; String8 identifier; + String8List qualifiers; E2_IRNode *irtree; String8 member_name; E2_IRNode *params_irtree; 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/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/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index e20bafa9..41a5572d 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -43,6 +43,7 @@ entry_point(CmdLine *cmdline) // (A) // int & B // (1 + (int)&B) + s("foo!bar"), s("int32(*)(int32, int32)"), s("int32 *(*)(int32, int32)"), s("int32 **(*)(int32, int32)"), diff --git a/src/text/text.c b/src/text/text.c index 6b56598c..f7f7ff3f 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -2445,8 +2445,10 @@ txt_token_array_from_data(Arena *arena, TXT_LangKind lang_kind, TXT_TokenPt ctx_ U64 advance = 1; //- rjf: adjust escaping state - if(active_token_kind != TXT_TokenKind_Null && byte == '\\') + B32 escaped_this_step = 0; + if(!escaped && active_token_kind != TXT_TokenKind_Null && byte == '\\') { + escaped_this_step = 1; escaped = 1; } @@ -2599,7 +2601,12 @@ txt_token_array_from_data(Arena *arena, TXT_LangKind lang_kind, TXT_TokenPt ctx_ } }break; case TXT_TokenKind_LineComment: + case TXT_TokenKind_Meta: { + if(byte == '\r' && escaped) + { + escaped_this_step = 1; + } if(byte == '\n' && !escaped) { token_finished = 1; @@ -2614,14 +2621,6 @@ txt_token_array_from_data(Arena *arena, TXT_LangKind lang_kind, TXT_TokenPt ctx_ advance = 2; } }break; - case TXT_TokenKind_Meta: - { - if(byte == '\n' && !escaped) - { - token_finished = 1; - advance = 1; - } - }break; } //- rjf: finish all tokens if we're at the end of the data @@ -2654,7 +2653,10 @@ txt_token_array_from_data(Arena *arena, TXT_LangKind lang_kind, TXT_TokenPt ctx_ } //- rjf: reset escaped state - escaped = 0; + if(!escaped_this_step) + { + escaped = 0; + } //- rjf: advance off += advance; @@ -3734,7 +3736,6 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 { U8 byte = data.str[off]; U8 next_byte = (off+1 < data.size) ? data.str[off+1] : 0; - U8 prev_byte = (off > 0) ? data.str[off-1] : 0; B32 off_is_endpoint = 1; U64 extra_advance = 0; if(byte == '/' && next_byte == '*') @@ -3757,9 +3758,27 @@ txt_artifact_create(String8 key, B32 *cancel_signal, AC_Status *status_out, U64 { // NOTE(rjf): no-op } - else if(prev_byte != '\\' && (byte == '"' || byte == '\'')) + else if(byte == '"' || byte == '\'') { - // NOTE(rjf): no-op + 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 { 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 4e03d65b..672f7d8b 100644 --- a/src/torture/torture_main.c +++ b/src/torture/torture_main.c @@ -76,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" @@ -156,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" @@ -184,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; } From 076d41130c59836d29a111e37905365a4fae13e5 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Wed, 29 Jul 2026 11:40:41 -0700 Subject: [PATCH 90/90] fix qualified identifier expression tree buil --- project.raddbg | 2 +- src/eval2/eval2.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/project.raddbg b/project.raddbg index afa0b2b8..73a545e7 100644 --- a/project.raddbg +++ b/project.raddbg @@ -7,7 +7,6 @@ target: working_directory: "../raddebugger" arguments: "--user:C:/devel/raddebugger/build/raddbg_test.user" debug_subprocesses: 0 - enabled: 1 } target: { @@ -67,6 +66,7 @@ target: { executable: "build/ryan_scratch.exe" working_directory: build + enabled: 1 } target: { diff --git a/src/eval2/eval2.c b/src/eval2/eval2.c index 5990c1e6..99d18520 100644 --- a/src/eval2/eval2.c +++ b/src/eval2/eval2.c @@ -2127,7 +2127,7 @@ e2_parse_from_string(Arena *arena, E2_ParseState *state, B32 identifier_is_type, { identifier_mapped = 1; expr = e2_expr(arena, identifier_is_type ? E2_ExprKind_TypeIdentifier : E2_ExprKind_Identifier); - expr->string = str8_substr(string, token.range); + expr->string = identifier; expr->qualifiers = qualifiers; state->caller_request_count = 0; }