From 1b74fb076003823b088bc6b761504add7a74bd5b Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 15 Jul 2024 17:23:01 -0700 Subject: [PATCH] simplification pass over os core layer; simplification pass over base arena; set up build.sh; stub out new spot for linux os core --- build.bat | 274 +- build.sh | 57 + src/base/base_arena.c | 497 +- src/base/base_arena.h | 167 +- src/base/base_core.h | 1588 +- src/base/base_entry_point.c | 187 +- src/base/base_strings.c | 3950 ++-- src/base/base_thread_context.h | 82 +- src/codeview/codeview.c | 1275 +- src/codeview/codeview.h | 6034 ++--- src/coff/coff.c | 2238 +- src/ctrl/ctrl_core.c | 10592 ++++----- src/dasm_cache/dasm_cache.c | 1476 +- src/dbgi/dbgi.c | 1766 +- src/demon/demon_core.h | 484 +- src/demon/linux/demon_os_linux.c | 4212 ++-- src/demon/win32/demon_core_win32.c | 5670 ++--- src/df/core/df_core.c | 18669 ++++++++------- src/df/core/df_core.h | 3663 ++- src/df/gfx/df_views.c | 18922 ++++++++-------- src/draw/draw.c | 1282 +- src/file_stream/file_stream.c | 620 +- src/fuzzy_search/fuzzy_search.c | 1106 +- src/geo_cache/geo_cache.c | 766 +- src/hash_store/hash_store.c | 678 +- src/lib_rdi_make/rdi_make.h | 3051 +-- src/metagen/metagen_os/metagen_os_inc.h | 78 +- src/mutable_text/mutable_text.c | 284 +- src/os/core/linux/os_core_linux.c | 1692 +- src/os/core/linux/os_core_linux.h | 95 +- src/os/core/linux/os_core_linux_old.c | 1677 ++ src/os/core/linux/os_core_linux_old.h | 88 + src/os/core/os_core.c | 416 +- src/os/core/os_core.h | 719 +- src/os/core/win32/os_core_win32.c | 3525 ++- src/os/core/win32/os_core_win32.h | 208 +- src/os/gfx/os_gfx.h | 373 +- src/os/gfx/stub/os_gfx_stub.c | 485 +- src/os/gfx/win32/os_gfx_win32.c | 2986 +-- src/os/gfx/win32/os_gfx_win32.h | 197 +- src/os/os_inc.c | 60 +- src/os/os_inc.h | 87 +- src/os/socket/os_socket.c | 16 - src/os/socket/os_socket.h | 49 - src/os/socket/win32/os_socket_win32.c | 353 - src/os/socket/win32/os_socket_win32.h | 54 - src/path/path.c | 332 +- src/pdb/pdb.c | 2007 +- src/raddbg/raddbg.c | 794 +- src/raddbg/raddbg_main.c | 1148 +- .../rdi_breakpad_from_pdb_main.c | 632 +- src/rdi_from_pdb/rdi_from_pdb.c | 9724 ++++---- src/rdi_from_pdb/rdi_from_pdb_main.c | 264 +- src/render/d3d11/render_d3d11.c | 3082 +-- src/task_system/task_system.c | 454 +- src/text_cache/text_cache.c | 4772 ++-- src/texture_cache/texture_cache.c | 810 +- src/ui/ui_core.c | 5990 ++--- 58 files changed, 66001 insertions(+), 66756 deletions(-) create mode 100644 build.sh create mode 100644 src/os/core/linux/os_core_linux_old.c create mode 100644 src/os/core/linux/os_core_linux_old.h delete mode 100644 src/os/socket/os_socket.c delete mode 100644 src/os/socket/os_socket.h delete mode 100644 src/os/socket/win32/os_socket_win32.c delete mode 100644 src/os/socket/win32/os_socket_win32.h diff --git a/build.bat b/build.bat index 234a4f1c..46e4c36e 100644 --- a/build.bat +++ b/build.bat @@ -1,137 +1,137 @@ -@echo off -setlocal -cd /D "%~dp0" - -:: --- Usage Notes (2024/1/10) ------------------------------------------------ -:: -:: This is a central build script for the RAD Debugger project. It takes a list -:: of simple alphanumeric-only arguments which control (a) what is built, (b) -:: which compiler & linker are used, and (c) extra high-level build options. By -:: default, if no options are passed, then the main "raddbg" graphical debugger -:: is built. -:: -:: Below is a non-exhaustive list of possible ways to use the script: -:: `build raddbg` -:: `build raddbg clang` -:: `build raddbg release` -:: `build raddbg asan telemetry` -:: `build rdi_from_pdb` -:: -:: For a full list of possible build targets and their build command lines, -:: search for @build_targets in this file. -:: -:: Below is a list of all possible non-target command line options: -:: -:: - `asan`: enable address sanitizer -:: - `telemetry`: enable RAD telemetry profiling support - -:: --- Unpack Arguments ------------------------------------------------------- -for %%a in (%*) do set "%%a=1" -if not "%msvc%"=="1" if not "%clang%"=="1" set msvc=1 -if not "%release%"=="1" set debug=1 -if "%debug%"=="1" set release=0 && echo [debug mode] -if "%release%"=="1" set debug=0 && echo [release mode] -if "%msvc%"=="1" set clang=0 && echo [msvc compile] -if "%clang%"=="1" set msvc=0 && echo [clang compile] -if "%~1"=="" echo [default mode, assuming `raddbg` build] && set raddbg=1 -if "%~1"=="release" if "%~2"=="" echo [default mode, assuming `raddbg` build] && set raddbg=1 - -:: --- Unpack Command Line Build Arguments ------------------------------------ -set auto_compile_flags= -if "%telemetry%"=="1" set auto_compile_flags=%auto_compile_flags% -DPROFILE_TELEMETRY=1 && echo [telemetry profiling enabled] -if "%asan%"=="1" set auto_compile_flags=%auto_compile_flags% -fsanitize=address && echo [asan enabled] - -:: --- Compile/Link Line Definitions ------------------------------------------ -set cl_common= /I..\src\ /I..\local\ /nologo /FC /Z7 -set clang_common= -I..\src\ -I..\local\ -gcodeview -fdiagnostics-absolute-paths -Wall -Wno-unknown-warning-option -Wno-missing-braces -Wno-unused-function -Wno-writable-strings -Wno-unused-value -Wno-unused-variable -Wno-unused-local-typedef -Wno-deprecated-register -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-single-bit-bitfield-constant-conversion -Wno-compare-distinct-pointer-types -Wno-initializer-overrides -Wno-incompatible-pointer-types-discards-qualifiers -Xclang -flto-visibility-public-std -D_USE_MATH_DEFINES -Dstrdup=_strdup -Dgnu_printf=printf -set cl_debug= call cl /Od /Ob1 /DBUILD_DEBUG=1 %cl_common% %auto_compile_flags% -set cl_release= call cl /O2 /DBUILD_DEBUG=0 %cl_common% %auto_compile_flags% -set clang_debug= call clang -g -O0 -DBUILD_DEBUG=1 %clang_common% %auto_compile_flags% -set clang_release= call clang -g -O2 -DBUILD_DEBUG=0 %clang_common% %auto_compile_flags% -set cl_link= /link /MANIFEST:EMBED /INCREMENTAL:NO /natvis:"%~dp0\src\natvis\base.natvis" logo.res -set clang_link= -fuse-ld=lld -Xlinker /MANIFEST:EMBED -Xlinker /natvis:"%~dp0\src\natvis\base.natvis" logo.res -set cl_out= /out: -set clang_out= -o - -:: --- Per-Build Settings ----------------------------------------------------- -set link_dll=-DLL -if "%msvc%"=="1" set only_compile=/c -if "%clang%"=="1" set only_compile=-c -if "%msvc%"=="1" set EHsc=/EHsc -if "%clang%"=="1" set EHsc= -if "%msvc%"=="1" set no_aslr=/DYNAMICBASE:NO -if "%clang%"=="1" set no_aslr=-Wl,/DYNAMICBASE:NO -if "%msvc%"=="1" set rc=call rc -if "%clang%"=="1" set rc=call llvm-rc - -:: --- Choose Compile/Link Lines ---------------------------------------------- -if "%msvc%"=="1" set compile_debug=%cl_debug% -if "%msvc%"=="1" set compile_release=%cl_release% -if "%msvc%"=="1" set compile_link=%cl_link% -if "%msvc%"=="1" set out=%cl_out% -if "%clang%"=="1" set compile_debug=%clang_debug% -if "%clang%"=="1" set compile_release=%clang_release% -if "%clang%"=="1" set compile_link=%clang_link% -if "%clang%"=="1" set out=%clang_out% -if "%debug%"=="1" set compile=%compile_debug% -if "%release%"=="1" set compile=%compile_release% - -:: --- Prep Directories ------------------------------------------------------- -if not exist build mkdir build -if not exist local mkdir local - -:: --- Produce Logo Icon File ------------------------------------------------- -pushd build -%rc% /nologo /fo logo.res ..\data\logo.rc || exit /b 1 -popd - -:: --- Get Current Git Commit Id ---------------------------------------------- -for /f %%i in ('call git describe --always --dirty') do set compile=%compile% -DBUILD_GIT_HASH=\"%%i\" - -:: --- Build & Run Metaprogram ------------------------------------------------ -if "%no_meta%"=="1" echo [skipping metagen] -if not "%no_meta%"=="1" ( - pushd build - %compile_debug% ..\src\metagen\metagen_main.c %compile_link% %out%metagen.exe || exit /b 1 - metagen.exe || exit /b 1 - popd -) - -:: --- Build Everything (@build_targets) -------------------------------------- -pushd build -if "%raddbg%"=="1" set didbuild=1 && %compile% ..\src\raddbg\raddbg_main.c %compile_link% %out%raddbg.exe || exit /b 1 -if "%rdi_from_pdb%"=="1" set didbuild=1 && %compile% ..\src\rdi_from_pdb\rdi_from_pdb_main.c %compile_link% %out%rdi_from_pdb.exe || exit /b 1 -if "%rdi_from_dwarf%"=="1" set didbuild=1 && %compile% ..\src\rdi_from_dwarf\rdi_from_dwarf.c %compile_link% %out%rdi_from_dwarf.exe || exit /b 1 -if "%rdi_dump%"=="1" set didbuild=1 && %compile% ..\src\rdi_dump\rdi_dump_main.c %compile_link% %out%rdi_dump.exe || exit /b 1 -if "%rdi_breakpad_from_pdb%"=="1" set didbuild=1 && %compile% ..\src\rdi_breakpad_from_pdb\rdi_breakpad_from_pdb_main.c %compile_link% %out%rdi_breakpad_from_pdb.exe || exit /b 1 -if "%ryan_scratch%"=="1" set didbuild=1 && %compile% ..\src\scratch\ryan_scratch.c %compile_link% %out%ryan_scratch.exe || exit /b 1 -if "%cpp_tests%"=="1" set didbuild=1 && %compile% ..\src\scratch\i_hate_c_plus_plus.cpp %compile_link% %out%cpp_tests.exe || exit /b 1 -if "%look_at_raddbg%"=="1" set didbuild=1 && %compile% ..\src\scratch\look_at_raddbg.c %compile_link% %out%look_at_raddbg.exe || exit /b 1 -if "%mule_main%"=="1" set didbuild=1 && del vc*.pdb mule*.pdb && %compile_release% %only_compile% ..\src\mule\mule_inline.cpp && %compile_release% %only_compile% ..\src\mule\mule_o2.cpp && %compile_debug% %EHsc% ..\src\mule\mule_main.cpp ..\src\mule\mule_c.c mule_inline.obj mule_o2.obj %compile_link% %no_aslr% %out%mule_main.exe || exit /b 1 -if "%mule_module%"=="1" set didbuild=1 && %compile% ..\src\mule\mule_module.cpp %compile_link% %link_dll% %out%mule_module.dll || exit /b 1 -if "%mule_hotload%"=="1" set didbuild=1 && %compile% ..\src\mule\mule_hotload_main.c %compile_link% %out%mule_hotload.exe & %compile% ..\src\mule\mule_hotload_module_main.c %compile_link% %link_dll% %out%mule_hotload_module.dll || exit /b 1 -if "%mule_peb_trample%"=="1" ( - set didbuild=1 - if exist mule_peb_trample.exe move mule_peb_trample.exe mule_peb_trample_old_%random%.exe - if exist mule_peb_trample_new.pdb move mule_peb_trample_new.pdb mule_peb_trample_old_%random%.pdb - if exist mule_peb_trample_new.rdi move mule_peb_trample_new.rdi mule_peb_trample_old_%random%.rdi - %compile% ..\src\mule\mule_peb_trample.c %compile_link% %out%mule_peb_trample_new.exe || exit /b 1 - move mule_peb_trample_new.exe mule_peb_trample.exe -) -popd - -:: --- Unset ------------------------------------------------------------------ -for %%a in (%*) do set "%%a=0" -set raddbg= -set compile= -set compile_link= -set out= -set msvc= -set debug= -set release= - -:: --- Warn On No Builds ------------------------------------------------------ -if "%didbuild%"=="" ( - echo [WARNING] no valid build target specified; must use build target names as arguments to this script, like `build raddbg` or `build rdi_from_pdb`. - exit /b 1 -) +@echo off +setlocal +cd /D "%~dp0" + +:: --- Usage Notes (2024/1/10) ------------------------------------------------ +:: +:: This is a central build script for the RAD Debugger project, for use in +:: Windows development environments. It takes a list of simple alphanumeric- +:: only arguments which control (a) what is built, (b) which compiler & linker +:: are used, and (c) extra high-level build options. By default, if no options +:: are passed, then the main "raddbg" graphical debugger is built. +:: +:: Below is a non-exhaustive list of possible ways to use the script: +:: `build raddbg` +:: `build raddbg clang` +:: `build raddbg release` +:: `build raddbg asan telemetry` +:: `build rdi_from_pdb` +:: +:: For a full list of possible build targets and their build command lines, +:: search for @build_targets in this file. +:: +:: Below is a list of all possible non-target command line options: +:: +:: - `asan`: enable address sanitizer +:: - `telemetry`: enable RAD telemetry profiling support + +:: --- Unpack Arguments ------------------------------------------------------- +for %%a in (%*) do set "%%a=1" +if not "%msvc%"=="1" if not "%clang%"=="1" set msvc=1 +if not "%release%"=="1" set debug=1 +if "%debug%"=="1" set release=0 && echo [debug mode] +if "%release%"=="1" set debug=0 && echo [release mode] +if "%msvc%"=="1" set clang=0 && echo [msvc compile] +if "%clang%"=="1" set msvc=0 && echo [clang compile] +if "%~1"=="" echo [default mode, assuming `raddbg` build] && set raddbg=1 +if "%~1"=="release" if "%~2"=="" echo [default mode, assuming `raddbg` build] && set raddbg=1 + +:: --- Unpack Command Line Build Arguments ------------------------------------ +set auto_compile_flags= +if "%telemetry%"=="1" set auto_compile_flags=%auto_compile_flags% -DPROFILE_TELEMETRY=1 && echo [telemetry profiling enabled] +if "%asan%"=="1" set auto_compile_flags=%auto_compile_flags% -fsanitize=address && echo [asan enabled] + +:: --- Compile/Link Line Definitions ------------------------------------------ +set cl_common= /I..\src\ /I..\local\ /nologo /FC /Z7 +set clang_common= -I..\src\ -I..\local\ -gcodeview -fdiagnostics-absolute-paths -Wall -Wno-unknown-warning-option -Wno-missing-braces -Wno-unused-function -Wno-writable-strings -Wno-unused-value -Wno-unused-variable -Wno-unused-local-typedef -Wno-deprecated-register -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-single-bit-bitfield-constant-conversion -Wno-compare-distinct-pointer-types -Wno-initializer-overrides -Wno-incompatible-pointer-types-discards-qualifiers -Xclang -flto-visibility-public-std -D_USE_MATH_DEFINES -Dstrdup=_strdup -Dgnu_printf=printf +set cl_debug= call cl /Od /Ob1 /DBUILD_DEBUG=1 %cl_common% %auto_compile_flags% +set cl_release= call cl /O2 /DBUILD_DEBUG=0 %cl_common% %auto_compile_flags% +set clang_debug= call clang -g -O0 -DBUILD_DEBUG=1 %clang_common% %auto_compile_flags% +set clang_release= call clang -g -O2 -DBUILD_DEBUG=0 %clang_common% %auto_compile_flags% +set cl_link= /link /MANIFEST:EMBED /INCREMENTAL:NO /natvis:"%~dp0\src\natvis\base.natvis" logo.res +set clang_link= -fuse-ld=lld -Xlinker /MANIFEST:EMBED -Xlinker /natvis:"%~dp0\src\natvis\base.natvis" logo.res +set cl_out= /out: +set clang_out= -o + +:: --- Per-Build Settings ----------------------------------------------------- +set link_dll=-DLL +if "%msvc%"=="1" set only_compile=/c +if "%clang%"=="1" set only_compile=-c +if "%msvc%"=="1" set EHsc=/EHsc +if "%clang%"=="1" set EHsc= +if "%msvc%"=="1" set no_aslr=/DYNAMICBASE:NO +if "%clang%"=="1" set no_aslr=-Wl,/DYNAMICBASE:NO +if "%msvc%"=="1" set rc=call rc +if "%clang%"=="1" set rc=call llvm-rc + +:: --- Choose Compile/Link Lines ---------------------------------------------- +if "%msvc%"=="1" set compile_debug=%cl_debug% +if "%msvc%"=="1" set compile_release=%cl_release% +if "%msvc%"=="1" set compile_link=%cl_link% +if "%msvc%"=="1" set out=%cl_out% +if "%clang%"=="1" set compile_debug=%clang_debug% +if "%clang%"=="1" set compile_release=%clang_release% +if "%clang%"=="1" set compile_link=%clang_link% +if "%clang%"=="1" set out=%clang_out% +if "%debug%"=="1" set compile=%compile_debug% +if "%release%"=="1" set compile=%compile_release% + +:: --- Prep Directories ------------------------------------------------------- +if not exist build mkdir build +if not exist local mkdir local + +:: --- Produce Logo Icon File ------------------------------------------------- +pushd build +%rc% /nologo /fo logo.res ..\data\logo.rc || exit /b 1 +popd + +:: --- Get Current Git Commit Id ---------------------------------------------- +for /f %%i in ('call git describe --always --dirty') do set compile=%compile% -DBUILD_GIT_HASH=\"%%i\" + +:: --- Build & Run Metaprogram ------------------------------------------------ +if "%no_meta%"=="1" echo [skipping metagen] +if not "%no_meta%"=="1" ( + pushd build + %compile_debug% ..\src\metagen\metagen_main.c %compile_link% %out%metagen.exe || exit /b 1 + metagen.exe || exit /b 1 + popd +) + +:: --- Build Everything (@build_targets) -------------------------------------- +pushd build +if "%raddbg%"=="1" set didbuild=1 && %compile% ..\src\raddbg\raddbg_main.c %compile_link% %out%raddbg.exe || exit /b 1 +if "%rdi_from_pdb%"=="1" set didbuild=1 && %compile% ..\src\rdi_from_pdb\rdi_from_pdb_main.c %compile_link% %out%rdi_from_pdb.exe || exit /b 1 +if "%rdi_from_dwarf%"=="1" set didbuild=1 && %compile% ..\src\rdi_from_dwarf\rdi_from_dwarf.c %compile_link% %out%rdi_from_dwarf.exe || exit /b 1 +if "%rdi_dump%"=="1" set didbuild=1 && %compile% ..\src\rdi_dump\rdi_dump_main.c %compile_link% %out%rdi_dump.exe || exit /b 1 +if "%rdi_breakpad_from_pdb%"=="1" set didbuild=1 && %compile% ..\src\rdi_breakpad_from_pdb\rdi_breakpad_from_pdb_main.c %compile_link% %out%rdi_breakpad_from_pdb.exe || exit /b 1 +if "%ryan_scratch%"=="1" set didbuild=1 && %compile% ..\src\scratch\ryan_scratch.c %compile_link% %out%ryan_scratch.exe || exit /b 1 +if "%cpp_tests%"=="1" set didbuild=1 && %compile% ..\src\scratch\i_hate_c_plus_plus.cpp %compile_link% %out%cpp_tests.exe || exit /b 1 +if "%look_at_raddbg%"=="1" set didbuild=1 && %compile% ..\src\scratch\look_at_raddbg.c %compile_link% %out%look_at_raddbg.exe || exit /b 1 +if "%mule_main%"=="1" set didbuild=1 && del vc*.pdb mule*.pdb && %compile_release% %only_compile% ..\src\mule\mule_inline.cpp && %compile_release% %only_compile% ..\src\mule\mule_o2.cpp && %compile_debug% %EHsc% ..\src\mule\mule_main.cpp ..\src\mule\mule_c.c mule_inline.obj mule_o2.obj %compile_link% %no_aslr% %out%mule_main.exe || exit /b 1 +if "%mule_module%"=="1" set didbuild=1 && %compile% ..\src\mule\mule_module.cpp %compile_link% %link_dll% %out%mule_module.dll || exit /b 1 +if "%mule_hotload%"=="1" set didbuild=1 && %compile% ..\src\mule\mule_hotload_main.c %compile_link% %out%mule_hotload.exe & %compile% ..\src\mule\mule_hotload_module_main.c %compile_link% %link_dll% %out%mule_hotload_module.dll || exit /b 1 +if "%mule_peb_trample%"=="1" ( + set didbuild=1 + if exist mule_peb_trample.exe move mule_peb_trample.exe mule_peb_trample_old_%random%.exe + if exist mule_peb_trample_new.pdb move mule_peb_trample_new.pdb mule_peb_trample_old_%random%.pdb + if exist mule_peb_trample_new.rdi move mule_peb_trample_new.rdi mule_peb_trample_old_%random%.rdi + %compile% ..\src\mule\mule_peb_trample.c %compile_link% %out%mule_peb_trample_new.exe || exit /b 1 + move mule_peb_trample_new.exe mule_peb_trample.exe +) +popd + +:: --- Unset ------------------------------------------------------------------ +for %%a in (%*) do set "%%a=0" +set raddbg= +set compile= +set compile_link= +set out= +set msvc= +set debug= +set release= + +:: --- Warn On No Builds ------------------------------------------------------ +if "%didbuild%"=="" ( + echo [WARNING] no valid build target specified; must use build target names as arguments to this script, like `build raddbg` or `build rdi_from_pdb`. + exit /b 1 +) diff --git a/build.sh b/build.sh new file mode 100644 index 00000000..6306f7fd --- /dev/null +++ b/build.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# --- Unpack Arguments -------------------------------------------------------- +for arg in "$@"; do declare $arg='1'; done +if [ ! "$gcc" = "1" ]; then clang=1; fi +if [ ! "$release" = "1" ]; then debug=1; fi +if [ "$debug" = "1" ]; then release=0 && echo "[debug mode]"; fi +if [ "$release" = "1" ]; then debug=0 && echo "[release mode]"; fi +if [ "$clang" = "1" ]; then gcc=0 && echo "[clang compile]"; fi +if [ "$gcc" = "1" ]; then clang=0 && echo "[gcc compile]"; fi + +# --- Unpack Command Line Build Arguments ------------------------------------- +auto_compile_flags='' + +# --- Compile/Link Line Definitions ------------------------------------------- +clang_common='-I../src/ -I../local/ -gcodeview -fdiagnostics-absolute-paths -Wall -Wno-unknown-warning-option -Wno-missing-braces -Wno-unused-function -Wno-writable-strings -Wno-unused-value -Wno-unused-variable -Wno-unused-local-typedef -Wno-deprecated-register -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-single-bit-bitfield-constant-conversion -Wno-compare-distinct-pointer-types -Wno-initializer-overrides -Wno-incompatible-pointer-types-discards-qualifiers -Xclang -flto-visibility-public-std -D_USE_MATH_DEFINES -Dstrdup=_strdup -Dgnu_printf=printf' +clang_debug="clang -g -O0 -DBUILD_DEBUG=1 ${clang_common} ${auto_compile_flags}" +clang_release="clang -g -O2 -DBUILD_DEBUG=0 ${clang_common} ${auto_compile_flags}" +clang_link="" +clang_out="-o" + +# --- Per-Build Settings ------------------------------------------------------ +link_dll="-fPIC" + +# --- Choose Compile/Link Lines ----------------------------------------------- +if [ "$clang" = "1" ]; then compile_debug="$clang_debug"; fi +if [ "$clang" = "1" ]; then compile_release="$clang_release"; fi +if [ "$clang" = "1" ]; then compile_link="$clang_link"; fi +if [ "$clang" = "1" ]; then out="$clang_out"; fi +if [ "$debug" = "1" ]; then compile="$compile_debug"; fi +if [ "$release" = "1" ]; then compile="$compile_release"; fi + +# --- Prep Directories -------------------------------------------------------- +if [ ! -d build ]; then mkdir build; fi +if [ ! -d local ]; then mkdir local; fi + +# --- Build & Run Metaprogram ------------------------------------------------- +if [ "$no_meta" = "1" ]; then echo "[skipping metagen]"; fi +if [ "$no_meta" = "" ] +then + cd build + $compile_debug ../src/metagen/metagen_main.c $compile_link $out metagen || exit 1 + metagen || exit 1 + cd .. +fi + +# --- Build Everything (@build_targets) --------------------------------------- +cd build +if [ "$raddbg" = "1" ]; then didbuild=1 && $compile ../src/raddbg/raddbg_main.c $compile_link $out raddbg || exit 1; fi +cd .. + +# --- Warn On No Builds ------------------------------------------------------- +#if [ "$didbuild" = "" ] +#then +# echo "[WARNING] no valid build target specified; must use build target names as arguments to this script, like `./build.sh raddbg` or `./build.sh rdi_from_pdb`." +# exit 1 +#fi diff --git a/src/base/base_arena.c b/src/base/base_arena.c index f75dd30f..d4dd1b69 100644 --- a/src/base/base_arena.c +++ b/src/base/base_arena.c @@ -1,302 +1,195 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -// Implementation - -internal Arena * -arena_alloc__sized(U64 init_res, U64 init_cmt) -{ - ProfBeginFunction(); - Assert(ARENA_HEADER_SIZE < init_cmt && init_cmt <= init_res); - - void *memory = 0; - U64 res = 0; - U64 cmt = 0; - B32 large_pages = os_large_pages_enabled(); - if(large_pages) - { - U64 page_size = os_large_page_size(); - res = AlignPow2(init_res, page_size); -#if OS_WINDOWS - cmt = res; -#else - cmt = AlignPow2(init_cmt, page_size); -#endif - memory = os_reserve_large(res); - if(!os_commit_large(memory, cmt)) - { - memory = 0; - os_release(memory, res); - } - } - else - { - U64 page_size = os_page_size(); - res = AlignPow2(init_res, page_size); - cmt = AlignPow2(init_cmt, page_size); - memory = os_reserve(res); - if(!os_commit(memory, cmt)) - { - memory = 0; - os_release(memory, res); - } - } - - Arena *arena = (Arena*)memory; - if(arena) - { - AsanPoisonMemoryRegion(memory, cmt); - AsanUnpoisonMemoryRegion(memory, ARENA_HEADER_SIZE); - arena->prev = 0; - arena->current = arena; - arena->base_pos = 0; - arena->pos = ARENA_HEADER_SIZE; - arena->cmt = cmt; - arena->res = res; - arena->align = 8; - arena->grow = 1; - arena->large_pages = large_pages; - } - - ProfEnd(); - return arena; -} - -internal Arena * -arena_alloc(void) -{ - ProfBeginFunction(); - - U64 init_res, init_cmt; - if (os_large_pages_enabled()) { - init_res = ARENA_RESERVE_SIZE_LARGE_PAGES; - init_cmt = ARENA_COMMIT_SIZE_LARGE_PAGES; - } else { - init_res = ARENA_RESERVE_SIZE; - init_cmt = ARENA_COMMIT_SIZE; - } - - Arena *arena = arena_alloc__sized(init_res, init_cmt); - - ProfEnd(); - return arena; -} - -internal void -arena_release(Arena *arena) -{ - for (Arena *node = arena->current, *prev = 0; node != 0; node = prev) { - prev = node->prev; - os_release(node, node->res); - } -} - -internal U64 -arena_huge_push_threshold(void) -{ - U64 reserve_size = os_large_pages_enabled() ? ARENA_RESERVE_SIZE_LARGE_PAGES : ARENA_RESERVE_SIZE; - U64 threshold = (reserve_size - ARENA_HEADER_SIZE) / 2 + 1; - return threshold; -} - -internal void * -arena_push__impl(Arena *arena, U64 size) -{ - Arena *current = arena->current; - U64 pos_mem = AlignPow2(current->pos, arena->align); - U64 pos_new = pos_mem + size; - - if (current->res < pos_new && arena->grow) { - Arena *new_block; - - // normal growth path - if (size < arena_huge_push_threshold()) { - new_block = arena_alloc(); - } - // huge growth path - else { - U64 new_block_size = size + ARENA_HEADER_SIZE; - new_block = arena_alloc__sized(new_block_size, new_block_size); - } - - if (new_block) { - new_block->base_pos = current->base_pos + current->res; - SLLStackPush_N(arena->current, new_block, prev); - - current = new_block; - pos_mem = AlignPow2(current->pos, current->align); - pos_new = pos_mem + size; - } - } - - if (current->cmt < pos_new) { - U64 cmt_new_aligned, cmt_new_clamped, cmt_new_size; - B32 is_cmt_ok; - - if (current->large_pages) { - cmt_new_aligned = AlignPow2(pos_new, ARENA_COMMIT_SIZE_LARGE_PAGES); - cmt_new_clamped = ClampTop(cmt_new_aligned, current->res); - cmt_new_size = cmt_new_clamped - current->cmt; - is_cmt_ok = os_commit_large((U8*)current + current->cmt, cmt_new_size); - } else { - cmt_new_aligned = AlignPow2(pos_new, ARENA_COMMIT_SIZE); - cmt_new_clamped = ClampTop(cmt_new_aligned, current->res); - cmt_new_size = cmt_new_clamped - current->cmt; - is_cmt_ok = os_commit((U8*)current + current->cmt, cmt_new_size); - } - - if (is_cmt_ok) { - current->cmt = cmt_new_clamped; - } - } - - void *memory = 0; - - if (current->cmt >= pos_new) { - memory = (U8*)current + pos_mem; - current->pos = pos_new; - AsanUnpoisonMemoryRegion(memory, size); - } - -#if OS_FEATURE_GRAPHICAL - if(Unlikely(memory == 0)) - { - os_graphical_message(1, str8_lit("Fatal Allocation Failure"), str8_lit("Unexpected memory allocation failure.")); - os_exit_process(1); - } -#endif - - return memory; -} - -internal U64 -arena_pos(Arena *arena) -{ - Arena *current = arena->current; - U64 pos = current->base_pos + current->pos; - return pos; -} - -internal void -arena_pop_to(Arena *arena, U64 big_pos_unclamped) -{ - U64 big_pos = ClampBot(ARENA_HEADER_SIZE, big_pos_unclamped); - - // unroll the chain - Arena *current = arena->current; - for (Arena *prev = 0; current->base_pos >= big_pos; current = prev) { - prev = current->prev; - os_release(current, current->res); - } - AssertAlways(current); - arena->current = current; - - // compute arena-relative position - U64 new_pos = big_pos - current->base_pos; - AssertAlways(new_pos <= current->pos); - - // poison popped memory block - AsanPoisonMemoryRegion((U8*)current + new_pos, (current->pos - new_pos)); - - // update position - current->pos = new_pos; -} - -internal void -arena_absorb(Arena *arena, Arena *sub) -{ - // base adjustment - Arena *current = arena->current; - U64 base_adjust = current->base_pos + current->res; - for (Arena *node = sub->current; node != 0; node = node->prev) { - node->base_pos += base_adjust; - } - - // attach sub to arena - sub->prev = arena->current; - arena->current = sub->current; - sub->current = sub; -} - -//////////////////////////////// -// Wrappers - -internal void * -arena_push(Arena *arena, U64 size) -{ - void *memory = arena_push__impl(arena, size); - return memory; -} - -internal void * -arena_push_contiguous(Arena *arena, U64 size) -{ - B32 restore = arena->grow; - arena->grow = 0; - void *memory = arena_push(arena, size); - arena->grow = restore; - return memory; -} - -internal void -arena_push_align(Arena *arena, U64 align) -{ - Assert(IsPow2(align)); - U64 amt = AlignPadPow2(arena->pos, align); - void *ptr = arena_push(arena, amt); - MemoryZero(ptr, amt); -} - -internal void -arena_put_back(Arena *arena, U64 amt) -{ - U64 pos_old = arena_pos(arena); - U64 pos_new = pos_old; - if (amt < pos_old) { - pos_new = pos_old - amt; - } - arena_pop_to(arena, pos_new); -} - -internal void -arena_clear(Arena *arena) -{ - arena_pop_to(arena, 0); -} - -internal Temp -temp_begin(Arena *arena) -{ - U64 pos = arena_pos(arena); - Temp temp = {arena, pos}; - return temp; -} - -internal void -temp_end(Temp temp) -{ - arena_pop_to(temp.arena, temp.pos); -} - -//////////////////////////////// -//~ NOTE(allen): "Mini-Arena" Helper - -internal B32 -ensure_commit(void **cmtptr, void *pos, U64 cmt_block_size){ - B32 result = 0; - U8 *cmt = (U8*)*cmtptr; - if (cmt < (U8*)pos){ - U64 cmt_size_raw = (U8*)pos - cmt; - U64 cmt_size = AlignPow2(cmt_size_raw, cmt_block_size); - if (os_commit(cmt, cmt_size)){ - *cmtptr = cmt + cmt_size; - result = 1; - } - } - else{ - result = 1; - } - return(result); -} - +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Arena Functions + +//- rjf: arena creation/destruction + +internal Arena * +arena_alloc_(ArenaParams *params) +{ + // rjf: round up reserve/commit sizes + U64 reserve_size = params->reserve_size; + U64 commit_size = params->commit_size; + if(params->flags & ArenaFlag_LargePages) + { + reserve_size = AlignPow2(reserve_size, os_get_system_info()->large_page_size); + commit_size = AlignPow2(commit_size, os_get_system_info()->large_page_size); + } + else + { + reserve_size = AlignPow2(reserve_size, os_get_system_info()->page_size); + commit_size = AlignPow2(commit_size, os_get_system_info()->page_size); + } + + // rjf: reserve/commit initial block + void *base = params->optional_backing_buffer; + if(base == 0) + { + if(params->flags & ArenaFlag_LargePages) + { + base = os_reserve_large(reserve_size); + os_commit_large(base, commit_size); + } + else + { + base = os_reserve(reserve_size); + os_commit(base, commit_size); + } + } + + // rjf: panic on arena creation failure +#if OS_FEATURE_GRAPHICAL + if(Unlikely(base == 0)) + { + os_graphical_message(1, str8_lit("Fatal Allocation Failure"), str8_lit("Unexpected memory allocation failure.")); + os_abort(1); + } +#endif + + // rjf: extract arena header & fill + Arena *arena = (Arena *)base; + arena->current = arena; + arena->flags = params->flags; + arena->cmt_size = (U32)params->commit_size; + arena->res_size = params->reserve_size; + arena->base_pos = 0; + arena->pos = ARENA_HEADER_SIZE; + arena->cmt = commit_size; + arena->res = reserve_size; + AsanPoisonMemoryRegion(base, commit_size); + AsanUnpoisonMemoryRegion(base, ARENA_HEADER_SIZE); + return arena; +} + +internal void +arena_release(Arena *arena) +{ + for(Arena *n = arena->current, *prev = 0; n != 0; n = prev) + { + prev = n->prev; + os_release(n, n->res); + } +} + +//- rjf: arena push/pop core functions + +internal void * +arena_push(Arena *arena, U64 size, U64 align) +{ + Arena *current = arena->current; + U64 pos_pre = AlignPow2(current->pos, align); + U64 pos_pst = pos_pre + size; + + // rjf: chain, if needed + if(current->res < pos_pst && !(arena->flags & ArenaFlag_NoChain)) + { + U64 res_size = current->res_size; + U64 cmt_size = current->cmt_size; + if(size > cmt_size) + { + res_size = size + ARENA_HEADER_SIZE; + cmt_size = size + ARENA_HEADER_SIZE; + } + Arena *new_block = arena_alloc(.reserve_size = res_size, + .commit_size = cmt_size, + .flags = current->flags); + new_block->base_pos = current->base_pos + current->res; + SLLStackPush_N(arena->current, new_block, prev); + current = new_block; + pos_pre = AlignPow2(current->pos, align); + pos_pst = pos_pst + size; + } + + // rjf: commit new pages, if needed + if(current->cmt < pos_pst && !(current->flags & ArenaFlag_LargePages)) + { + U64 cmt_pst_aligned = AlignPow2(pos_pst, current->cmt_size); + U64 cmt_pst_clamped = ClampTop(cmt_pst_aligned, current->res); + U64 cmt_size = cmt_pst_clamped - current->cmt; + os_commit((U8 *)current + current->cmt, cmt_size); + current->cmt = cmt_pst_clamped; + } + + // rjf: push onto current block + void *result = 0; + if(current->cmt >= pos_pst) + { + result = (U8 *)current+pos_pre; + current->pos = pos_pst; + AsanUnpoisonMemoryRegion(result, size); + } + + // rjf: panic on failure +#if OS_FEATURE_GRAPHICAL + if(Unlikely(result == 0)) + { + os_graphical_message(1, str8_lit("Fatal Allocation Failure"), str8_lit("Unexpected memory allocation failure.")); + os_abort(1); + } +#endif + + return result; +} + +internal U64 +arena_pos(Arena *arena) +{ + Arena *current = arena->current; + U64 pos = current->base_pos + current->pos; + return pos; +} + +internal void +arena_pop_to(Arena *arena, U64 pos) +{ + U64 big_pos = ClampBot(ARENA_HEADER_SIZE, pos); + Arena *current = arena->current; + for(Arena *prev = 0; current->base_pos >= big_pos; current = prev) + { + prev = current->prev; + os_release(current, current->res); + } + arena->current = current; + U64 new_pos = big_pos - current->base_pos; + AssertAlways(new_pos <= current->pos); + AsanPoisonMemoryRegion((U8*)current + new_pos, (current->pos - new_pos)); + current->pos = new_pos; +} + +//- rjf: arena push/pop helpers + +internal void +arena_clear(Arena *arena) +{ + arena_pop_to(arena, 0); +} + +internal void +arena_pop(Arena *arena, U64 amt) +{ + U64 pos_old = arena_pos(arena); + U64 pos_new = pos_old; + if(amt < pos_old) + { + pos_new = pos_old - amt; + } + arena_pop_to(arena, pos_new); +} + +//- rjf: temporary arena scopes + +internal Temp +temp_begin(Arena *arena) +{ + U64 pos = arena_pos(arena); + Temp temp = {arena, pos}; + return temp; +} + +internal void +temp_end(Temp temp) +{ + arena_pop_to(temp.arena, temp.pos); +} diff --git a/src/base/base_arena.h b/src/base/base_arena.h index dd8249ce..f941bcd7 100644 --- a/src/base/base_arena.h +++ b/src/base/base_arena.h @@ -1,87 +1,80 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef BASE_ARENA_H -#define BASE_ARENA_H - -//////////////////////////////// -//~ rjf: Constants - -#define ARENA_HEADER_SIZE 128 - -#ifndef ARENA_RESERVE_SIZE -# define ARENA_RESERVE_SIZE MB(64) -#endif -#ifndef ARENA_COMMIT_SIZE -# define ARENA_COMMIT_SIZE KB(64) -#endif - -#ifndef ARENA_RESERVE_SIZE_LARGE_PAGES -# define ARENA_RESERVE_SIZE_LARGE_PAGES MB(8) -#endif -#ifndef ARENA_COMMIT_SIZE_LARGE_PAGES -# define ARENA_COMMIT_SIZE_LARGE_PAGES MB(2) -#endif - -//////////////////////////////// -//~ rjf: Arena Types - -typedef struct Arena Arena; -struct Arena -{ - struct Arena *prev; - struct Arena *current; - U64 base_pos; - U64 pos; - U64 cmt; - U64 res; - U64 align; - B8 grow; - B8 large_pages; -}; - -typedef struct Temp Temp; -struct Temp -{ - Arena *arena; - U64 pos; -}; - -//////////////////////////////// -// Implementation - -internal Arena* arena_alloc__sized(U64 init_res, U64 init_cmt); - -internal Arena* arena_alloc(void); -internal void arena_release(Arena *arena); - -internal void* arena_push__impl(Arena *arena, U64 size); -internal U64 arena_pos(Arena *arena); -internal void arena_pop_to(Arena *arena, U64 pos); - -internal void arena_absorb(Arena *arena, Arena *sub); - -//////////////////////////////// -// Wrappers - -internal void* arena_push(Arena *arena, U64 size); -internal void* arena_push_contiguous(Arena *arena, U64 size); -internal void arena_clear(Arena *arena); -internal void arena_push_align(Arena *arena, U64 align); -internal void arena_put_back(Arena *arena, U64 amt); - -internal Temp temp_begin(Arena *arena); -internal void temp_end(Temp temp); - -//////////////////////////////// -//~ NOTE(allen): "Mini-Arena" Helper - -internal B32 ensure_commit(void **cmt, void *pos, U64 cmt_block_size); - -//////////////////////////////// -//~ NOTE(allen): Main API Macros - -#define push_array_no_zero(a,T,c) (T*)arena_push((a), sizeof(T)*(c)) -#define push_array(a,T,c) (T*)MemoryZero(push_array_no_zero(a,T,c), sizeof(T)*(c)) - -#endif // BASE_ARENA_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_ARENA_H +#define BASE_ARENA_H + +//////////////////////////////// +//~ rjf: Constants + +#define ARENA_HEADER_SIZE 64 + +//////////////////////////////// +//~ rjf: Types + +typedef U32 ArenaFlags; +enum +{ + ArenaFlag_NoChain = (1<<0), + ArenaFlag_LargePages = (1<<1), +}; + +typedef struct ArenaParams ArenaParams; +struct ArenaParams +{ + ArenaFlags flags; + U64 reserve_size; + U64 commit_size; + void *optional_backing_buffer; +}; + +typedef struct Arena Arena; +struct Arena +{ + Arena *prev; // previous arena in chain + Arena *current; // current arena in chain + ArenaFlags flags; + U32 cmt_size; + U64 res_size; + U64 base_pos; + U64 pos; + U64 cmt; + U64 res; +}; +StaticAssert(sizeof(Arena) <= ARENA_HEADER_SIZE, arena_header_size_check); + +typedef struct Temp Temp; +struct Temp +{ + Arena *arena; + U64 pos; +}; + +//////////////////////////////// +//~ rjf: Arena Functions + +//- rjf: arena creation/destruction +internal Arena *arena_alloc_(ArenaParams *params); +#define arena_alloc(...) arena_alloc_(&(ArenaParams){.reserve_size = MB(64), .commit_size = KB(64), __VA_ARGS__}) +internal void arena_release(Arena *arena); + +//- rjf: arena push/pop/pos core functions +internal void *arena_push(Arena *arena, U64 size, U64 align); +internal U64 arena_pos(Arena *arena); +internal void arena_pop_to(Arena *arena, U64 pos); + +//- rjf: arena push/pop helpers +internal void arena_clear(Arena *arena); +internal void arena_pop(Arena *arena, U64 amt); + +//- rjf: temporary arena scopes +internal Temp temp_begin(Arena *arena); +internal void temp_end(Temp temp); + +//- rjf: push helper macros +#define push_array_no_zero_aligned(a, T, c, align) (T *)arena_push((a), sizeof(T)*(c), (align)) +#define push_array_aligned(a, T, c, align) (T *)MemoryZero(push_array_no_zero_aligned(a, T, c, align), sizeof(T)*(c)) +#define push_array_no_zero(a, T, c) push_array_no_zero_aligned(a, T, c, Max(8, AlignOf(T))) +#define push_array(a, T, c) push_array_aligned(a, T, c, Max(8, AlignOf(T))) + +#endif // BASE_ARENA_H diff --git a/src/base/base_core.h b/src/base/base_core.h index e815c3f0..ef89f088 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -1,787 +1,801 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef BASE_TYPES_H -#define BASE_TYPES_H - -//////////////////////////////// -//~ rjf: Foreign Includes - -#include -#include -#include -#include -#include - -//////////////////////////////// -//~ rjf: Codebase Keywords - -#define internal static -#define global static -#define local_persist static - -#if COMPILER_MSVC || (COMPILER_CLANG && OS_WINDOWS) -# pragma section(".rdata$", read) -# define read_only __declspec(allocate(".rdata$")) -#elif (COMPILER_CLANG && OS_LINUX) -# define read_only __attribute__((section(".rodata"))) -#else -// NOTE(rjf): I don't know of a useful way to do this in GCC land. -// __attribute__((section(".rodata"))) looked promising, but it introduces a -// strange warning about malformed section attributes, and it doesn't look -// like writing to that section reliably produces access violations, strangely -// enough. (It does on Clang) -# define read_only -#endif - -#if COMPILER_MSVC -# define thread_static __declspec(thread) -#elif COMPILER_CLANG || COMPILER_GCC -# define thread_static __thread -#endif - -//////////////////////////////// -//~ rjf: Linkage Keyword Macros - -#if OS_WINDOWS -# define shared_function C_LINKAGE __declspec(dllexport) -#else -# define shared_function C_LINKAGE -#endif - -#if LANG_CPP -# define C_LINKAGE_BEGIN extern "C"{ -# define C_LINKAGE_END } -# define C_LINKAGE extern "C" -#else -# define C_LINKAGE_BEGIN -# define C_LINKAGE_END -# define C_LINKAGE -#endif - -//////////////////////////////// -//~ rjf: Units - -#define KB(n) (((U64)(n)) << 10) -#define MB(n) (((U64)(n)) << 20) -#define GB(n) (((U64)(n)) << 30) -#define TB(n) (((U64)(n)) << 40) -#define Thousand(n) ((n)*1000) -#define Million(n) ((n)*1000000) -#define Billion(n) ((n)*1000000000) - -//////////////////////////////// -//~ rjf: Branch Predictor Hints - -#if defined(__clang__) -# define Expect(expr, val) __builtin_expect((expr), (val)) -#else -# define Expect(expr, val) (expr) -#endif - -#define Likely(expr) Expect(expr,1) -#define Unlikely(expr) Expect(expr,0) - -//////////////////////////////// -//~ rjf: Clamps, Mins, Maxes - -#define Min(A,B) (((A)<(B))?(A):(B)) -#define Max(A,B) (((A)>(B))?(A):(B)) -#define ClampTop(A,X) Min(A,X) -#define ClampBot(X,B) Max(X,B) -#define Clamp(A,X,B) (((X)<(A))?(A):((X)>(B))?(B):(X)) - -//////////////////////////////// -//~ rjf: Member Offsets - -#define Member(T,m) (((T*)0)->m) -#define OffsetOf(T,m) IntFromPtr(&Member(T,m)) -#define MemberFromOffset(T,ptr,off) (T)((((U8 *)ptr)+(off))) -#define CastFromMember(T,m,ptr) (T*)(((U8*)ptr) - OffsetOf(T,m)) - -//////////////////////////////// -//~ rjf: For-Loop Construct Macros - -#define DeferLoop(begin, end) for(int _i_ = ((begin), 0); !_i_; _i_ += 1, (end)) -#define DeferLoopChecked(begin, end) for(int _i_ = 2 * !(begin); (_i_ == 2 ? ((end), 0) : !_i_); _i_ += 1, (end)) - -#define EachEnumVal(type, it) type it = (type)0; it < type##_COUNT; it = (type)(it+1) -#define EachNonZeroEnumVal(type, it) type it = (type)1; it < type##_COUNT; it = (type)(it+1) - -//////////////////////////////// -//~ rjf: Memory Operation Macros - -#define MemoryCopy(dst, src, size) memmove((dst), (src), (size)) -#define MemorySet(dst, byte, size) memset((dst), (byte), (size)) -#define MemoryCompare(a, b, size) memcmp((a), (b), (size)) -#define MemoryStrlen(ptr) strlen(ptr) - -#define MemoryCopyStruct(d,s) MemoryCopy((d),(s),sizeof(*(d))) -#define MemoryCopyArray(d,s) MemoryCopy((d),(s),sizeof(d)) -#define MemoryCopyTyped(d,s,c) MemoryCopy((d),(s),sizeof(*(d))*(c)) - -#define MemoryZero(s,z) memset((s),0,(z)) -#define MemoryZeroStruct(s) MemoryZero((s),sizeof(*(s))) -#define MemoryZeroArray(a) MemoryZero((a),sizeof(a)) -#define MemoryZeroTyped(m,c) MemoryZero((m),sizeof(*(m))*(c)) - -#define MemoryMatch(a,b,z) (MemoryCompare((a),(b),(z)) == 0) -#define MemoryMatchStruct(a,b) MemoryMatch((a),(b),sizeof(*(a))) -#define MemoryMatchArray(a,b) MemoryMatch((a),(b),sizeof(a)) - -#define MemoryRead(T,p,e) ( ((p)+sizeof(T)<=(e))?(*(T*)(p)):(0) ) -#define MemoryConsume(T,p,e) ( ((p)+sizeof(T)<=(e))?((p)+=sizeof(T),*(T*)((p)-sizeof(T))):((p)=(e),0) ) - -//////////////////////////////// -//~ rjf: Asserts - -#if COMPILER_MSVC -# define Trap() __debugbreak() -#elif COMPILER_CLANG || COMPILER_GCC -# define Trap() __builtin_trap() -#else -# error Unknown trap intrinsic for this compiler. -#endif - -#define AssertAlways(x) do{if(!(x)) {Trap();}}while(0) -#if BUILD_DEBUG -# define Assert(x) AssertAlways(x) -#else -# define Assert(x) (void)(x) -#endif -#define InvalidPath Assert(!"Invalid Path!") -#define NotImplemented Assert(!"Not Implemented!") -#define NoOp ((void)0) -#define StaticAssert(C, ID) global U8 Glue(ID, __LINE__)[(C)?1:-1] - -//////////////////////////////// -//~ rjf: Atomic Operations - -#if OS_WINDOWS -# include -# include -# include -# include -# if ARCH_X64 -# define ins_atomic_u64_eval(x) InterlockedAdd64((volatile __int64 *)(x), 0) -# define ins_atomic_u64_inc_eval(x) InterlockedIncrement64((volatile __int64 *)(x)) -# define ins_atomic_u64_dec_eval(x) InterlockedDecrement64((volatile __int64 *)(x)) -# define ins_atomic_u64_eval_assign(x,c) InterlockedExchange64((volatile __int64 *)(x),(c)) -# define ins_atomic_u64_add_eval(x,c) InterlockedAdd64((volatile __int64 *)(x), c) -# define ins_atomic_u64_eval_cond_assign(x,k,c) InterlockedCompareExchange64((volatile __int64 *)(x),(k),(c)) -# define ins_atomic_u32_eval(x,c) InterlockedAdd((volatile LONG *)(x), 0) -# define ins_atomic_u32_eval_assign(x,c) InterlockedExchange((volatile LONG *)(x),(c)) -# define ins_atomic_u32_eval_cond_assign(x,k,c) InterlockedCompareExchange((volatile LONG *)(x),(k),(c)) -# define ins_atomic_ptr_eval_assign(x,c) (void*)ins_atomic_u64_eval_assign((volatile __int64 *)(x), (__int64)(c)) -# else -# error Atomic intrinsics not defined for this operating system / architecture combination. -# endif -#elif OS_LINUX -# if ARCH_X64 -# define ins_atomic_u64_inc_eval(x) __sync_fetch_and_add((volatile U64 *)(x), 1) -# else -# error Atomic intrinsics not defined for this operating system / architecture combination. -# endif -#else -# error Atomic intrinsics not defined for this operating system. -#endif - -//////////////////////////////// -//~ rjf: Linked List Building Macros - -//- rjf: linked list macro helpers -#define CheckNil(nil,p) ((p) == 0 || (p) == nil) -#define SetNil(nil,p) ((p) = nil) - -//- rjf: doubly-linked-lists -#define DLLInsert_NPZ(nil,f,l,p,n,next,prev) (CheckNil(nil,f) ? \ -((f) = (l) = (n), SetNil(nil,(n)->next), SetNil(nil,(n)->prev)) :\ -CheckNil(nil,p) ? \ -((n)->next = (f), (f)->prev = (n), (f) = (n), SetNil(nil,(n)->prev)) :\ -((p)==(l)) ? \ -((l)->next = (n), (n)->prev = (l), (l) = (n), SetNil(nil, (n)->next)) :\ -(((!CheckNil(nil,p) && CheckNil(nil,(p)->next)) ? (0) : ((p)->next->prev = (n))), ((n)->next = (p)->next), ((p)->next = (n)), ((n)->prev = (p)))) -#define DLLPushBack_NPZ(nil,f,l,n,next,prev) DLLInsert_NPZ(nil,f,l,l,n,next,prev) -#define DLLPushFront_NPZ(nil,f,l,n,next,prev) DLLInsert_NPZ(nil,l,f,f,n,prev,next) -#define DLLRemove_NPZ(nil,f,l,n,next,prev) (((n) == (f) ? (f) = (n)->next : (0)),\ -((n) == (l) ? (l) = (l)->prev : (0)),\ -(CheckNil(nil,(n)->prev) ? (0) :\ -((n)->prev->next = (n)->next)),\ -(CheckNil(nil,(n)->next) ? (0) :\ -((n)->next->prev = (n)->prev))) - -//- rjf: singly-linked, doubly-headed lists (queues) -#define SLLQueuePush_NZ(nil,f,l,n,next) (CheckNil(nil,f)?\ -((f)=(l)=(n),SetNil(nil,(n)->next)):\ -((l)->next=(n),(l)=(n),SetNil(nil,(n)->next))) -#define SLLQueuePushFront_NZ(nil,f,l,n,next) (CheckNil(nil,f)?\ -((f)=(l)=(n),SetNil(nil,(n)->next)):\ -((n)->next=(f),(f)=(n))) -#define SLLQueuePop_NZ(nil,f,l,next) ((f)==(l)?\ -(SetNil(nil,f),SetNil(nil,l)):\ -((f)=(f)->next)) - -//- rjf: singly-linked, singly-headed lists (stacks) -#define SLLStackPush_N(f,n,next) ((n)->next=(f), (f)=(n)) -#define SLLStackPop_N(f,next) ((f)=(f)->next) - -//- rjf: doubly-linked-list helpers -#define DLLInsert_NP(f,l,p,n,next,prev) DLLInsert_NPZ(0,f,l,p,n,next,prev) -#define DLLPushBack_NP(f,l,n,next,prev) DLLPushBack_NPZ(0,f,l,n,next,prev) -#define DLLPushFront_NP(f,l,n,next,prev) DLLPushFront_NPZ(0,f,l,n,next,prev) -#define DLLRemove_NP(f,l,n,next,prev) DLLRemove_NPZ(0,f,l,n,next,prev) -#define DLLInsert(f,l,p,n) DLLInsert_NPZ(0,f,l,p,n,next,prev) -#define DLLPushBack(f,l,n) DLLPushBack_NPZ(0,f,l,n,next,prev) -#define DLLPushFront(f,l,n) DLLPushFront_NPZ(0,f,l,n,next,prev) -#define DLLRemove(f,l,n) DLLRemove_NPZ(0,f,l,n,next,prev) - -//- rjf: singly-linked, doubly-headed list helpers -#define SLLQueuePush_N(f,l,n,next) SLLQueuePush_NZ(0,f,l,n,next) -#define SLLQueuePushFront_N(f,l,n,next) SLLQueuePushFront_NZ(0,f,l,n,next) -#define SLLQueuePop_N(f,l,next) SLLQueuePop_NZ(0,f,l,next) -#define SLLQueuePush(f,l,n) SLLQueuePush_NZ(0,f,l,n,next) -#define SLLQueuePushFront(f,l,n) SLLQueuePushFront_NZ(0,f,l,n,next) -#define SLLQueuePop(f,l) SLLQueuePop_NZ(0,f,l,next) - -//- rjf: singly-linked, singly-headed list helpers -#define SLLStackPush(f,n) SLLStackPush_N(f,n,next) -#define SLLStackPop(f) SLLStackPop_N(f,next) - -//////////////////////////////// -//~ rjf: Address Sanitizer Markup - -#if COMPILER_MSVC -# if defined(__SANITIZE_ADDRESS__) -# define ASAN_ENABLED 1 -# define NO_ASAN __declspec(no_sanitize_address) -# else -# define NO_ASAN -# endif -#elif COMPILER_CLANG -# if defined(__has_feature) -# if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) -# define ASAN_ENABLED 1 -# endif -# endif -# define NO_ASAN __attribute__((no_sanitize("address"))) -#else -# error "NO_ASAN is not defined for this compiler." -#endif - -#if ASAN_ENABLED -#pragma comment(lib, "clang_rt.asan-x86_64.lib") -C_LINKAGE void __asan_poison_memory_region(void const volatile *addr, size_t size); -C_LINKAGE void __asan_unpoison_memory_region(void const volatile *addr, size_t size); -# define AsanPoisonMemoryRegion(addr, size) __asan_poison_memory_region((addr), (size)) -# define AsanUnpoisonMemoryRegion(addr, size) __asan_unpoison_memory_region((addr), (size)) -#else -# define AsanPoisonMemoryRegion(addr, size) ((void)(addr), (void)(size)) -# define AsanUnpoisonMemoryRegion(addr, size) ((void)(addr), (void)(size)) -#endif - -//////////////////////////////// -//~ rjf: Misc. Helper Macros - -#define Stringify_(S) #S -#define Stringify(S) Stringify_(S) - -#define Glue_(A,B) A##B -#define Glue(A,B) Glue_(A,B) - -#define ArrayCount(a) (sizeof(a) / sizeof((a)[0])) - -#define CeilIntegerDiv(a,b) (((a) + (b) - 1)/(b)) - -#define Swap(T,a,b) do{T t__ = a; a = b; b = t__;}while(0) - -#if ARCH_64BIT -# define IntFromPtr(ptr) ((U64)(ptr)) -#elif ARCH_32BIT -# define IntFromPtr(ptr) ((U32)(ptr)) -#else -# error Missing pointer-to-integer cast for this architecture. -#endif -#define PtrFromInt(i) (void*)((U8*)0 + (i)) - -#define Compose64Bit(a,b) ((((U64)a) << 32) | ((U64)b)); -#define AlignPow2(x,b) (((x) + (b) - 1)&(~((b) - 1))) -#define AlignDownPow2(x,b) ((x)&(~((b) - 1))) -#define AlignPadPow2(x,b) ((0-(x)) & ((b) - 1)) -#define IsPow2(x) ((x)!=0 && ((x)&((x)-1))==0) -#define IsPow2OrZero(x) ((((x) - 1)&(x)) == 0) - -#define ExtractBit(word, idx) (((word) >> (idx)) & 1) - -#if LANG_CPP -# define zero_struct {} -#else -# define zero_struct {0} -#endif - -#if COMPILER_MSVC && COMPILER_MSVC_YEAR < 2015 -# define this_function_name "unknown" -#else -# define this_function_name __func__ -#endif - -//////////////////////////////// -//~ rjf: Base Types - -typedef uint8_t U8; -typedef uint16_t U16; -typedef uint32_t U32; -typedef uint64_t U64; -typedef int8_t S8; -typedef int16_t S16; -typedef int32_t S32; -typedef int64_t S64; -typedef S8 B8; -typedef S16 B16; -typedef S32 B32; -typedef S64 B64; -typedef float F32; -typedef double F64; -typedef void VoidProc(void); -typedef struct U128 U128; -struct U128 -{ - U64 u64[2]; -}; - -//////////////////////////////// -//~ rjf: Basic Types & Spaces - -typedef enum Dimension -{ - Dimension_X, - Dimension_Y, - Dimension_Z, - Dimension_W, -} -Dimension; - -typedef enum Side -{ - Side_Invalid = -1, - Side_Min, - Side_Max, - Side_COUNT, -} -Side; -#define side_flip(s) ((Side)(!(s))) - -typedef enum Axis2 -{ - Axis2_Invalid = -1, - Axis2_X, - Axis2_Y, - Axis2_COUNT, -} -Axis2; -#define axis2_flip(a) ((Axis2)(!(a))) - -typedef enum Corner -{ - Corner_Invalid = -1, - Corner_00, - Corner_01, - Corner_10, - Corner_11, - Corner_COUNT -} -Corner; - -typedef enum Dir2 -{ - Dir2_Invalid = -1, - Dir2_Left, - Dir2_Up, - Dir2_Right, - Dir2_Down, - Dir2_COUNT -} -Dir2; -#define axis2_from_dir2(d) (((d) & 1) ? Axis2_Y : Axis2_X) -#define side_from_dir2(d) (((d) < Dir2_Right) ? Side_Min : Side_Max) - -//////////////////////////////// -//~ rjf: Toolchain/Environment Enums - -typedef enum OperatingSystem -{ - OperatingSystem_Null, - OperatingSystem_Windows, - OperatingSystem_Linux, - OperatingSystem_Mac, - OperatingSystem_COUNT, -} -OperatingSystem; - -typedef enum Architecture -{ - Architecture_Null, - Architecture_x64, - Architecture_x86, - Architecture_arm64, - Architecture_arm32, - Architecture_COUNT, -} -Architecture; - -typedef enum Compiler -{ - Compiler_Null, - Compiler_msvc, - Compiler_gcc, - Compiler_clang, - Compiler_COUNT, -} -Compiler; - -//////////////////////////////// -//~ rjf: Text 2D Coordinates & Ranges - -typedef struct TxtPt TxtPt; -struct TxtPt -{ - S64 line; - S64 column; -}; - -typedef struct TxtRng TxtRng; -struct TxtRng -{ - TxtPt min; - TxtPt max; -}; - -//////////////////////////////// -//~ NOTE(allen): Constants - -global U32 sign32 = 0x80000000; -global U32 exponent32 = 0x7F800000; -global U32 mantissa32 = 0x007FFFFF; - -global F32 big_golden32 = 1.61803398875f; -global F32 small_golden32 = 0.61803398875f; - -global F32 pi32 = 3.1415926535897f; - -global F64 machine_epsilon64 = 4.94065645841247e-324; - -global U64 max_U64 = 0xffffffffffffffffull; -global U32 max_U32 = 0xffffffff; -global U16 max_U16 = 0xffff; -global U8 max_U8 = 0xff; - -global S64 max_S64 = (S64)0x7fffffffffffffffull; -global S32 max_S32 = (S32)0x7fffffff; -global S16 max_S16 = (S16)0x7fff; -global S8 max_S8 = (S8)0x7f; - -global S64 min_S64 = (S64)0xffffffffffffffffull; -global S32 min_S32 = (S32)0xffffffff; -global S16 min_S16 = (S16)0xffff; -global S8 min_S8 = (S8)0xff; - -global const U32 bitmask1 = 0x00000001; -global const U32 bitmask2 = 0x00000003; -global const U32 bitmask3 = 0x00000007; -global const U32 bitmask4 = 0x0000000f; -global const U32 bitmask5 = 0x0000001f; -global const U32 bitmask6 = 0x0000003f; -global const U32 bitmask7 = 0x0000007f; -global const U32 bitmask8 = 0x000000ff; -global const U32 bitmask9 = 0x000001ff; -global const U32 bitmask10 = 0x000003ff; -global const U32 bitmask11 = 0x000007ff; -global const U32 bitmask12 = 0x00000fff; -global const U32 bitmask13 = 0x00001fff; -global const U32 bitmask14 = 0x00003fff; -global const U32 bitmask15 = 0x00007fff; -global const U32 bitmask16 = 0x0000ffff; -global const U32 bitmask17 = 0x0001ffff; -global const U32 bitmask18 = 0x0003ffff; -global const U32 bitmask19 = 0x0007ffff; -global const U32 bitmask20 = 0x000fffff; -global const U32 bitmask21 = 0x001fffff; -global const U32 bitmask22 = 0x003fffff; -global const U32 bitmask23 = 0x007fffff; -global const U32 bitmask24 = 0x00ffffff; -global const U32 bitmask25 = 0x01ffffff; -global const U32 bitmask26 = 0x03ffffff; -global const U32 bitmask27 = 0x07ffffff; -global const U32 bitmask28 = 0x0fffffff; -global const U32 bitmask29 = 0x1fffffff; -global const U32 bitmask30 = 0x3fffffff; -global const U32 bitmask31 = 0x7fffffff; -global const U32 bitmask32 = 0xffffffff; - -global const U64 bitmask33 = 0x00000001ffffffffull; -global const U64 bitmask34 = 0x00000003ffffffffull; -global const U64 bitmask35 = 0x00000007ffffffffull; -global const U64 bitmask36 = 0x0000000fffffffffull; -global const U64 bitmask37 = 0x0000001fffffffffull; -global const U64 bitmask38 = 0x0000003fffffffffull; -global const U64 bitmask39 = 0x0000007fffffffffull; -global const U64 bitmask40 = 0x000000ffffffffffull; -global const U64 bitmask41 = 0x000001ffffffffffull; -global const U64 bitmask42 = 0x000003ffffffffffull; -global const U64 bitmask43 = 0x000007ffffffffffull; -global const U64 bitmask44 = 0x00000fffffffffffull; -global const U64 bitmask45 = 0x00001fffffffffffull; -global const U64 bitmask46 = 0x00003fffffffffffull; -global const U64 bitmask47 = 0x00007fffffffffffull; -global const U64 bitmask48 = 0x0000ffffffffffffull; -global const U64 bitmask49 = 0x0001ffffffffffffull; -global const U64 bitmask50 = 0x0003ffffffffffffull; -global const U64 bitmask51 = 0x0007ffffffffffffull; -global const U64 bitmask52 = 0x000fffffffffffffull; -global const U64 bitmask53 = 0x001fffffffffffffull; -global const U64 bitmask54 = 0x003fffffffffffffull; -global const U64 bitmask55 = 0x007fffffffffffffull; -global const U64 bitmask56 = 0x00ffffffffffffffull; -global const U64 bitmask57 = 0x01ffffffffffffffull; -global const U64 bitmask58 = 0x03ffffffffffffffull; -global const U64 bitmask59 = 0x07ffffffffffffffull; -global const U64 bitmask60 = 0x0fffffffffffffffull; -global const U64 bitmask61 = 0x1fffffffffffffffull; -global const U64 bitmask62 = 0x3fffffffffffffffull; -global const U64 bitmask63 = 0x7fffffffffffffffull; -global const U64 bitmask64 = 0xffffffffffffffffull; - -global const U32 bit1 = (1<<0); -global const U32 bit2 = (1<<1); -global const U32 bit3 = (1<<2); -global const U32 bit4 = (1<<3); -global const U32 bit5 = (1<<4); -global const U32 bit6 = (1<<5); -global const U32 bit7 = (1<<6); -global const U32 bit8 = (1<<7); -global const U32 bit9 = (1<<8); -global const U32 bit10 = (1<<9); -global const U32 bit11 = (1<<10); -global const U32 bit12 = (1<<11); -global const U32 bit13 = (1<<12); -global const U32 bit14 = (1<<13); -global const U32 bit15 = (1<<14); -global const U32 bit16 = (1<<15); -global const U32 bit17 = (1<<16); -global const U32 bit18 = (1<<17); -global const U32 bit19 = (1<<18); -global const U32 bit20 = (1<<19); -global const U32 bit21 = (1<<20); -global const U32 bit22 = (1<<21); -global const U32 bit23 = (1<<22); -global const U32 bit24 = (1<<23); -global const U32 bit25 = (1<<24); -global const U32 bit26 = (1<<25); -global const U32 bit27 = (1<<26); -global const U32 bit28 = (1<<27); -global const U32 bit29 = (1<<28); -global const U32 bit30 = (1<<29); -global const U32 bit31 = (1<<30); -global const U32 bit32 = (1<<31); - -global const U64 bit33 = (1ull<<32); -global const U64 bit34 = (1ull<<33); -global const U64 bit35 = (1ull<<34); -global const U64 bit36 = (1ull<<35); -global const U64 bit37 = (1ull<<36); -global const U64 bit38 = (1ull<<37); -global const U64 bit39 = (1ull<<38); -global const U64 bit40 = (1ull<<39); -global const U64 bit41 = (1ull<<40); -global const U64 bit42 = (1ull<<41); -global const U64 bit43 = (1ull<<42); -global const U64 bit44 = (1ull<<43); -global const U64 bit45 = (1ull<<44); -global const U64 bit46 = (1ull<<45); -global const U64 bit47 = (1ull<<46); -global const U64 bit48 = (1ull<<47); -global const U64 bit49 = (1ull<<48); -global const U64 bit50 = (1ull<<49); -global const U64 bit51 = (1ull<<50); -global const U64 bit52 = (1ull<<51); -global const U64 bit53 = (1ull<<52); -global const U64 bit54 = (1ull<<53); -global const U64 bit55 = (1ull<<54); -global const U64 bit56 = (1ull<<55); -global const U64 bit57 = (1ull<<56); -global const U64 bit58 = (1ull<<57); -global const U64 bit59 = (1ull<<58); -global const U64 bit60 = (1ull<<59); -global const U64 bit61 = (1ull<<60); -global const U64 bit62 = (1ull<<61); -global const U64 bit63 = (1ull<<62); -global const U64 bit64 = (1ull<<63); - -//////////////////////////////// -//~ allen: Time - -typedef enum WeekDay -{ - WeekDay_Sun, - WeekDay_Mon, - WeekDay_Tue, - WeekDay_Wed, - WeekDay_Thu, - WeekDay_Fri, - WeekDay_Sat, - WeekDay_COUNT, -} -WeekDay; - -typedef enum Month -{ - Month_Jan, - Month_Feb, - Month_Mar, - Month_Apr, - Month_May, - Month_Jun, - Month_Jul, - Month_Aug, - Month_Sep, - Month_Oct, - Month_Nov, - Month_Dec, - Month_COUNT, -} -Month; - -typedef struct DateTime DateTime; -struct DateTime -{ - U16 micro_sec; // [0,999] - U16 msec; // [0,999] - U16 sec; // [0,60] - U16 min; // [0,59] - U16 hour; // [0,24] - U16 day; // [0,30] - union - { - WeekDay week_day; - U32 wday; - }; - union - { - Month month; - U32 mon; - }; - U32 year; // 1 = 1 CE, 0 = 1 BC -}; - -typedef U64 DenseTime; - -//////////////////////////////// -//~ allen: Files - -typedef U32 FilePropertyFlags; -enum -{ - FilePropertyFlag_IsFolder = (1 << 0), -}; - -typedef struct FileProperties FileProperties; -struct FileProperties -{ - U64 size; - DenseTime modified; - DenseTime created; - FilePropertyFlags flags; -}; - -//////////////////////////////// -//~ rjf: Safe Casts - -internal U16 safe_cast_u16(U32 x); -internal U32 safe_cast_u32(U64 x); -internal S32 safe_cast_s32(S64 x); - -//////////////////////////////// -//~ rjf: Large Base Type Functions - -internal U128 u128_zero(void); -internal U128 u128_make(U64 v0, U64 v1); -internal B32 u128_match(U128 a, U128 b); - -//////////////////////////////// -//~ rjf: Bit Patterns - -internal U32 u32_from_u64_saturate(U64 x); -internal U64 u64_up_to_pow2(U64 x); -internal S32 extend_sign32(U32 x, U32 size); -internal S64 extend_sign64(U64 x, U64 size); - -internal F32 inf32(void); -internal F32 neg_inf32(void); - -internal U16 bswap_u16(U16 x); -internal U32 bswap_u32(U32 x); -internal U64 bswap_u64(U64 x); - -internal U64 count_bits_set16(U16 val); -internal U64 count_bits_set32(U32 val); -internal U64 count_bits_set64(U64 val); - -internal U64 ctz32(U32 val); -internal U64 ctz64(U64 val); -internal U64 clz32(U32 val); -internal U64 clz64(U64 val); - -//////////////////////////////// -//~ rjf: Enum -> Sign - -internal S32 sign_from_side_S32(Side side); -internal F32 sign_from_side_F32(Side side); - -//////////////////////////////// -//~ rjf: Memory Functions - -internal B32 memory_is_zero(void *ptr, U64 size); - -//////////////////////////////// -//~ rjf: Text 2D Coordinate/Range Functions - -internal TxtPt txt_pt(S64 line, S64 column); -internal B32 txt_pt_match(TxtPt a, TxtPt b); -internal B32 txt_pt_less_than(TxtPt a, TxtPt b); -internal TxtPt txt_pt_min(TxtPt a, TxtPt b); -internal TxtPt txt_pt_max(TxtPt a, TxtPt b); -internal TxtRng txt_rng(TxtPt min, TxtPt max); -internal TxtRng txt_rng_intersect(TxtRng a, TxtRng b); -internal TxtRng txt_rng_union(TxtRng a, TxtRng b); -internal B32 txt_rng_contains(TxtRng r, TxtPt pt); - -//////////////////////////////// -//~ rjf: Toolchain/Environment Enum Functions - -internal U64 bit_size_from_arch(Architecture arch); -internal U64 max_instruction_size_from_arch(Architecture arch); - -internal OperatingSystem operating_system_from_context(void); -internal Architecture architecture_from_context(void); -internal Compiler compiler_from_context(void); - -//////////////////////////////// -//~ rjf: Time Functions - -internal DenseTime dense_time_from_date_time(DateTime date_time); -internal DateTime date_time_from_dense_time(DenseTime time); -internal DateTime date_time_from_micro_seconds(U64 time); - -//////////////////////////////// -//~ rjf: Non-Fancy Ring Buffer Reads/Writes - -internal U64 ring_write(U8 *ring_base, U64 ring_size, U64 ring_pos, void *src_data, U64 src_data_size); -internal U64 ring_read(U8 *ring_base, U64 ring_size, U64 ring_pos, void *dst_data, U64 read_size); -#define ring_write_struct(ring_base, ring_size, ring_pos, ptr) ring_write((ring_base), (ring_size), (ring_pos), (ptr), sizeof(*(ptr))) -#define ring_read_struct(ring_base, ring_size, ring_pos, ptr) ring_read((ring_base), (ring_size), (ring_pos), (ptr), sizeof(*(ptr))) - -//////////////////////////////// -//~ rjf: Sorts - -#define quick_sort(ptr, count, element_size, cmp_function) qsort((ptr), (count), (element_size), (int (*)(const void *, const void *))(cmp_function)) - -#endif // BASE_CORE_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_CORE_H +#define BASE_CORE_H + +//////////////////////////////// +//~ rjf: Foreign Includes + +#include +#include +#include +#include +#include + +//////////////////////////////// +//~ rjf: Codebase Keywords + +#define internal static +#define global static +#define local_persist static + +#if COMPILER_MSVC || (COMPILER_CLANG && OS_WINDOWS) +# pragma section(".rdata$", read) +# define read_only __declspec(allocate(".rdata$")) +#elif (COMPILER_CLANG && OS_LINUX) +# define read_only __attribute__((section(".rodata"))) +#else +// NOTE(rjf): I don't know of a useful way to do this in GCC land. +// __attribute__((section(".rodata"))) looked promising, but it introduces a +// strange warning about malformed section attributes, and it doesn't look +// like writing to that section reliably produces access violations, strangely +// enough. (It does on Clang) +# define read_only +#endif + +#if COMPILER_MSVC +# define thread_static __declspec(thread) +#elif COMPILER_CLANG || COMPILER_GCC +# define thread_static __thread +#endif + +//////////////////////////////// +//~ rjf: Linkage Keyword Macros + +#if OS_WINDOWS +# define shared_function C_LINKAGE __declspec(dllexport) +#else +# define shared_function C_LINKAGE +#endif + +#if LANG_CPP +# define C_LINKAGE_BEGIN extern "C"{ +# define C_LINKAGE_END } +# define C_LINKAGE extern "C" +#else +# define C_LINKAGE_BEGIN +# define C_LINKAGE_END +# define C_LINKAGE +#endif + +//////////////////////////////// +//~ rjf: Units + +#define KB(n) (((U64)(n)) << 10) +#define MB(n) (((U64)(n)) << 20) +#define GB(n) (((U64)(n)) << 30) +#define TB(n) (((U64)(n)) << 40) +#define Thousand(n) ((n)*1000) +#define Million(n) ((n)*1000000) +#define Billion(n) ((n)*1000000000) + +//////////////////////////////// +//~ rjf: Branch Predictor Hints + +#if defined(__clang__) +# define Expect(expr, val) __builtin_expect((expr), (val)) +#else +# define Expect(expr, val) (expr) +#endif + +#define Likely(expr) Expect(expr,1) +#define Unlikely(expr) Expect(expr,0) + +//////////////////////////////// +//~ rjf: Clamps, Mins, Maxes + +#define Min(A,B) (((A)<(B))?(A):(B)) +#define Max(A,B) (((A)>(B))?(A):(B)) +#define ClampTop(A,X) Min(A,X) +#define ClampBot(X,B) Max(X,B) +#define Clamp(A,X,B) (((X)<(A))?(A):((X)>(B))?(B):(X)) + +//////////////////////////////// +//~ rjf: Type -> Alignment + +#if COMPILER_MSVC +# define AlignOf(T) __alignof(T) +#elif COMPILER_CLANG +# define AlignOf(T) __alignof(T) +#elif COMPILER_GCC +# define AlignOf(T) __alignof__(T) +#else +#else +# error AlignOf not defined for this compiler. +#endif + +//////////////////////////////// +//~ rjf: Member Offsets + +#define Member(T,m) (((T*)0)->m) +#define OffsetOf(T,m) IntFromPtr(&Member(T,m)) +#define MemberFromOffset(T,ptr,off) (T)((((U8 *)ptr)+(off))) +#define CastFromMember(T,m,ptr) (T*)(((U8*)ptr) - OffsetOf(T,m)) + +//////////////////////////////// +//~ rjf: For-Loop Construct Macros + +#define DeferLoop(begin, end) for(int _i_ = ((begin), 0); !_i_; _i_ += 1, (end)) +#define DeferLoopChecked(begin, end) for(int _i_ = 2 * !(begin); (_i_ == 2 ? ((end), 0) : !_i_); _i_ += 1, (end)) + +#define EachEnumVal(type, it) type it = (type)0; it < type##_COUNT; it = (type)(it+1) +#define EachNonZeroEnumVal(type, it) type it = (type)1; it < type##_COUNT; it = (type)(it+1) + +//////////////////////////////// +//~ rjf: Memory Operation Macros + +#define MemoryCopy(dst, src, size) memmove((dst), (src), (size)) +#define MemorySet(dst, byte, size) memset((dst), (byte), (size)) +#define MemoryCompare(a, b, size) memcmp((a), (b), (size)) +#define MemoryStrlen(ptr) strlen(ptr) + +#define MemoryCopyStruct(d,s) MemoryCopy((d),(s),sizeof(*(d))) +#define MemoryCopyArray(d,s) MemoryCopy((d),(s),sizeof(d)) +#define MemoryCopyTyped(d,s,c) MemoryCopy((d),(s),sizeof(*(d))*(c)) + +#define MemoryZero(s,z) memset((s),0,(z)) +#define MemoryZeroStruct(s) MemoryZero((s),sizeof(*(s))) +#define MemoryZeroArray(a) MemoryZero((a),sizeof(a)) +#define MemoryZeroTyped(m,c) MemoryZero((m),sizeof(*(m))*(c)) + +#define MemoryMatch(a,b,z) (MemoryCompare((a),(b),(z)) == 0) +#define MemoryMatchStruct(a,b) MemoryMatch((a),(b),sizeof(*(a))) +#define MemoryMatchArray(a,b) MemoryMatch((a),(b),sizeof(a)) + +#define MemoryRead(T,p,e) ( ((p)+sizeof(T)<=(e))?(*(T*)(p)):(0) ) +#define MemoryConsume(T,p,e) ( ((p)+sizeof(T)<=(e))?((p)+=sizeof(T),*(T*)((p)-sizeof(T))):((p)=(e),0) ) + +//////////////////////////////// +//~ rjf: Asserts + +#if COMPILER_MSVC +# define Trap() __debugbreak() +#elif COMPILER_CLANG || COMPILER_GCC +# define Trap() __builtin_trap() +#else +# error Unknown trap intrinsic for this compiler. +#endif + +#define AssertAlways(x) do{if(!(x)) {Trap();}}while(0) +#if BUILD_DEBUG +# define Assert(x) AssertAlways(x) +#else +# define Assert(x) (void)(x) +#endif +#define InvalidPath Assert(!"Invalid Path!") +#define NotImplemented Assert(!"Not Implemented!") +#define NoOp ((void)0) +#define StaticAssert(C, ID) global U8 Glue(ID, __LINE__)[(C)?1:-1] + +//////////////////////////////// +//~ rjf: Atomic Operations + +#if OS_WINDOWS +# include +# include +# include +# include +# if ARCH_X64 +# define ins_atomic_u64_eval(x) InterlockedAdd64((volatile __int64 *)(x), 0) +# define ins_atomic_u64_inc_eval(x) InterlockedIncrement64((volatile __int64 *)(x)) +# define ins_atomic_u64_dec_eval(x) InterlockedDecrement64((volatile __int64 *)(x)) +# define ins_atomic_u64_eval_assign(x,c) InterlockedExchange64((volatile __int64 *)(x),(c)) +# define ins_atomic_u64_add_eval(x,c) InterlockedAdd64((volatile __int64 *)(x), c) +# define ins_atomic_u64_eval_cond_assign(x,k,c) InterlockedCompareExchange64((volatile __int64 *)(x),(k),(c)) +# define ins_atomic_u32_eval(x,c) InterlockedAdd((volatile LONG *)(x), 0) +# define ins_atomic_u32_eval_assign(x,c) InterlockedExchange((volatile LONG *)(x),(c)) +# define ins_atomic_u32_eval_cond_assign(x,k,c) InterlockedCompareExchange((volatile LONG *)(x),(k),(c)) +# define ins_atomic_ptr_eval_assign(x,c) (void*)ins_atomic_u64_eval_assign((volatile __int64 *)(x), (__int64)(c)) +# else +# error Atomic intrinsics not defined for this operating system / architecture combination. +# endif +#elif OS_LINUX +# if ARCH_X64 +# define ins_atomic_u64_inc_eval(x) __sync_fetch_and_add((volatile U64 *)(x), 1) +# else +# error Atomic intrinsics not defined for this operating system / architecture combination. +# endif +#else +# error Atomic intrinsics not defined for this operating system. +#endif + +//////////////////////////////// +//~ rjf: Linked List Building Macros + +//- rjf: linked list macro helpers +#define CheckNil(nil,p) ((p) == 0 || (p) == nil) +#define SetNil(nil,p) ((p) = nil) + +//- rjf: doubly-linked-lists +#define DLLInsert_NPZ(nil,f,l,p,n,next,prev) (CheckNil(nil,f) ? \ +((f) = (l) = (n), SetNil(nil,(n)->next), SetNil(nil,(n)->prev)) :\ +CheckNil(nil,p) ? \ +((n)->next = (f), (f)->prev = (n), (f) = (n), SetNil(nil,(n)->prev)) :\ +((p)==(l)) ? \ +((l)->next = (n), (n)->prev = (l), (l) = (n), SetNil(nil, (n)->next)) :\ +(((!CheckNil(nil,p) && CheckNil(nil,(p)->next)) ? (0) : ((p)->next->prev = (n))), ((n)->next = (p)->next), ((p)->next = (n)), ((n)->prev = (p)))) +#define DLLPushBack_NPZ(nil,f,l,n,next,prev) DLLInsert_NPZ(nil,f,l,l,n,next,prev) +#define DLLPushFront_NPZ(nil,f,l,n,next,prev) DLLInsert_NPZ(nil,l,f,f,n,prev,next) +#define DLLRemove_NPZ(nil,f,l,n,next,prev) (((n) == (f) ? (f) = (n)->next : (0)),\ +((n) == (l) ? (l) = (l)->prev : (0)),\ +(CheckNil(nil,(n)->prev) ? (0) :\ +((n)->prev->next = (n)->next)),\ +(CheckNil(nil,(n)->next) ? (0) :\ +((n)->next->prev = (n)->prev))) + +//- rjf: singly-linked, doubly-headed lists (queues) +#define SLLQueuePush_NZ(nil,f,l,n,next) (CheckNil(nil,f)?\ +((f)=(l)=(n),SetNil(nil,(n)->next)):\ +((l)->next=(n),(l)=(n),SetNil(nil,(n)->next))) +#define SLLQueuePushFront_NZ(nil,f,l,n,next) (CheckNil(nil,f)?\ +((f)=(l)=(n),SetNil(nil,(n)->next)):\ +((n)->next=(f),(f)=(n))) +#define SLLQueuePop_NZ(nil,f,l,next) ((f)==(l)?\ +(SetNil(nil,f),SetNil(nil,l)):\ +((f)=(f)->next)) + +//- rjf: singly-linked, singly-headed lists (stacks) +#define SLLStackPush_N(f,n,next) ((n)->next=(f), (f)=(n)) +#define SLLStackPop_N(f,next) ((f)=(f)->next) + +//- rjf: doubly-linked-list helpers +#define DLLInsert_NP(f,l,p,n,next,prev) DLLInsert_NPZ(0,f,l,p,n,next,prev) +#define DLLPushBack_NP(f,l,n,next,prev) DLLPushBack_NPZ(0,f,l,n,next,prev) +#define DLLPushFront_NP(f,l,n,next,prev) DLLPushFront_NPZ(0,f,l,n,next,prev) +#define DLLRemove_NP(f,l,n,next,prev) DLLRemove_NPZ(0,f,l,n,next,prev) +#define DLLInsert(f,l,p,n) DLLInsert_NPZ(0,f,l,p,n,next,prev) +#define DLLPushBack(f,l,n) DLLPushBack_NPZ(0,f,l,n,next,prev) +#define DLLPushFront(f,l,n) DLLPushFront_NPZ(0,f,l,n,next,prev) +#define DLLRemove(f,l,n) DLLRemove_NPZ(0,f,l,n,next,prev) + +//- rjf: singly-linked, doubly-headed list helpers +#define SLLQueuePush_N(f,l,n,next) SLLQueuePush_NZ(0,f,l,n,next) +#define SLLQueuePushFront_N(f,l,n,next) SLLQueuePushFront_NZ(0,f,l,n,next) +#define SLLQueuePop_N(f,l,next) SLLQueuePop_NZ(0,f,l,next) +#define SLLQueuePush(f,l,n) SLLQueuePush_NZ(0,f,l,n,next) +#define SLLQueuePushFront(f,l,n) SLLQueuePushFront_NZ(0,f,l,n,next) +#define SLLQueuePop(f,l) SLLQueuePop_NZ(0,f,l,next) + +//- rjf: singly-linked, singly-headed list helpers +#define SLLStackPush(f,n) SLLStackPush_N(f,n,next) +#define SLLStackPop(f) SLLStackPop_N(f,next) + +//////////////////////////////// +//~ rjf: Address Sanitizer Markup + +#if COMPILER_MSVC +# if defined(__SANITIZE_ADDRESS__) +# define ASAN_ENABLED 1 +# define NO_ASAN __declspec(no_sanitize_address) +# else +# define NO_ASAN +# endif +#elif COMPILER_CLANG +# if defined(__has_feature) +# if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +# define ASAN_ENABLED 1 +# endif +# endif +# define NO_ASAN __attribute__((no_sanitize("address"))) +#else +# error "NO_ASAN is not defined for this compiler." +#endif + +#if ASAN_ENABLED +#pragma comment(lib, "clang_rt.asan-x86_64.lib") +C_LINKAGE void __asan_poison_memory_region(void const volatile *addr, size_t size); +C_LINKAGE void __asan_unpoison_memory_region(void const volatile *addr, size_t size); +# define AsanPoisonMemoryRegion(addr, size) __asan_poison_memory_region((addr), (size)) +# define AsanUnpoisonMemoryRegion(addr, size) __asan_unpoison_memory_region((addr), (size)) +#else +# define AsanPoisonMemoryRegion(addr, size) ((void)(addr), (void)(size)) +# define AsanUnpoisonMemoryRegion(addr, size) ((void)(addr), (void)(size)) +#endif + +//////////////////////////////// +//~ rjf: Misc. Helper Macros + +#define Stringify_(S) #S +#define Stringify(S) Stringify_(S) + +#define Glue_(A,B) A##B +#define Glue(A,B) Glue_(A,B) + +#define ArrayCount(a) (sizeof(a) / sizeof((a)[0])) + +#define CeilIntegerDiv(a,b) (((a) + (b) - 1)/(b)) + +#define Swap(T,a,b) do{T t__ = a; a = b; b = t__;}while(0) + +#if ARCH_64BIT +# define IntFromPtr(ptr) ((U64)(ptr)) +#elif ARCH_32BIT +# define IntFromPtr(ptr) ((U32)(ptr)) +#else +# error Missing pointer-to-integer cast for this architecture. +#endif +#define PtrFromInt(i) (void*)((U8*)0 + (i)) + +#define Compose64Bit(a,b) ((((U64)a) << 32) | ((U64)b)); +#define AlignPow2(x,b) (((x) + (b) - 1)&(~((b) - 1))) +#define AlignDownPow2(x,b) ((x)&(~((b) - 1))) +#define AlignPadPow2(x,b) ((0-(x)) & ((b) - 1)) +#define IsPow2(x) ((x)!=0 && ((x)&((x)-1))==0) +#define IsPow2OrZero(x) ((((x) - 1)&(x)) == 0) + +#define ExtractBit(word, idx) (((word) >> (idx)) & 1) + +#if LANG_CPP +# define zero_struct {} +#else +# define zero_struct {0} +#endif + +#if COMPILER_MSVC && COMPILER_MSVC_YEAR < 2015 +# define this_function_name "unknown" +#else +# define this_function_name __func__ +#endif + +//////////////////////////////// +//~ rjf: Base Types + +typedef uint8_t U8; +typedef uint16_t U16; +typedef uint32_t U32; +typedef uint64_t U64; +typedef int8_t S8; +typedef int16_t S16; +typedef int32_t S32; +typedef int64_t S64; +typedef S8 B8; +typedef S16 B16; +typedef S32 B32; +typedef S64 B64; +typedef float F32; +typedef double F64; +typedef void VoidProc(void); +typedef struct U128 U128; +struct U128 +{ + U64 u64[2]; +}; + +//////////////////////////////// +//~ rjf: Basic Types & Spaces + +typedef enum Dimension +{ + Dimension_X, + Dimension_Y, + Dimension_Z, + Dimension_W, +} +Dimension; + +typedef enum Side +{ + Side_Invalid = -1, + Side_Min, + Side_Max, + Side_COUNT, +} +Side; +#define side_flip(s) ((Side)(!(s))) + +typedef enum Axis2 +{ + Axis2_Invalid = -1, + Axis2_X, + Axis2_Y, + Axis2_COUNT, +} +Axis2; +#define axis2_flip(a) ((Axis2)(!(a))) + +typedef enum Corner +{ + Corner_Invalid = -1, + Corner_00, + Corner_01, + Corner_10, + Corner_11, + Corner_COUNT +} +Corner; + +typedef enum Dir2 +{ + Dir2_Invalid = -1, + Dir2_Left, + Dir2_Up, + Dir2_Right, + Dir2_Down, + Dir2_COUNT +} +Dir2; +#define axis2_from_dir2(d) (((d) & 1) ? Axis2_Y : Axis2_X) +#define side_from_dir2(d) (((d) < Dir2_Right) ? Side_Min : Side_Max) + +//////////////////////////////// +//~ rjf: Toolchain/Environment Enums + +typedef enum OperatingSystem +{ + OperatingSystem_Null, + OperatingSystem_Windows, + OperatingSystem_Linux, + OperatingSystem_Mac, + OperatingSystem_COUNT, +} +OperatingSystem; + +typedef enum Architecture +{ + Architecture_Null, + Architecture_x64, + Architecture_x86, + Architecture_arm64, + Architecture_arm32, + Architecture_COUNT, +} +Architecture; + +typedef enum Compiler +{ + Compiler_Null, + Compiler_msvc, + Compiler_gcc, + Compiler_clang, + Compiler_COUNT, +} +Compiler; + +//////////////////////////////// +//~ rjf: Text 2D Coordinates & Ranges + +typedef struct TxtPt TxtPt; +struct TxtPt +{ + S64 line; + S64 column; +}; + +typedef struct TxtRng TxtRng; +struct TxtRng +{ + TxtPt min; + TxtPt max; +}; + +//////////////////////////////// +//~ NOTE(allen): Constants + +global U32 sign32 = 0x80000000; +global U32 exponent32 = 0x7F800000; +global U32 mantissa32 = 0x007FFFFF; + +global F32 big_golden32 = 1.61803398875f; +global F32 small_golden32 = 0.61803398875f; + +global F32 pi32 = 3.1415926535897f; + +global F64 machine_epsilon64 = 4.94065645841247e-324; + +global U64 max_U64 = 0xffffffffffffffffull; +global U32 max_U32 = 0xffffffff; +global U16 max_U16 = 0xffff; +global U8 max_U8 = 0xff; + +global S64 max_S64 = (S64)0x7fffffffffffffffull; +global S32 max_S32 = (S32)0x7fffffff; +global S16 max_S16 = (S16)0x7fff; +global S8 max_S8 = (S8)0x7f; + +global S64 min_S64 = (S64)0xffffffffffffffffull; +global S32 min_S32 = (S32)0xffffffff; +global S16 min_S16 = (S16)0xffff; +global S8 min_S8 = (S8)0xff; + +global const U32 bitmask1 = 0x00000001; +global const U32 bitmask2 = 0x00000003; +global const U32 bitmask3 = 0x00000007; +global const U32 bitmask4 = 0x0000000f; +global const U32 bitmask5 = 0x0000001f; +global const U32 bitmask6 = 0x0000003f; +global const U32 bitmask7 = 0x0000007f; +global const U32 bitmask8 = 0x000000ff; +global const U32 bitmask9 = 0x000001ff; +global const U32 bitmask10 = 0x000003ff; +global const U32 bitmask11 = 0x000007ff; +global const U32 bitmask12 = 0x00000fff; +global const U32 bitmask13 = 0x00001fff; +global const U32 bitmask14 = 0x00003fff; +global const U32 bitmask15 = 0x00007fff; +global const U32 bitmask16 = 0x0000ffff; +global const U32 bitmask17 = 0x0001ffff; +global const U32 bitmask18 = 0x0003ffff; +global const U32 bitmask19 = 0x0007ffff; +global const U32 bitmask20 = 0x000fffff; +global const U32 bitmask21 = 0x001fffff; +global const U32 bitmask22 = 0x003fffff; +global const U32 bitmask23 = 0x007fffff; +global const U32 bitmask24 = 0x00ffffff; +global const U32 bitmask25 = 0x01ffffff; +global const U32 bitmask26 = 0x03ffffff; +global const U32 bitmask27 = 0x07ffffff; +global const U32 bitmask28 = 0x0fffffff; +global const U32 bitmask29 = 0x1fffffff; +global const U32 bitmask30 = 0x3fffffff; +global const U32 bitmask31 = 0x7fffffff; +global const U32 bitmask32 = 0xffffffff; + +global const U64 bitmask33 = 0x00000001ffffffffull; +global const U64 bitmask34 = 0x00000003ffffffffull; +global const U64 bitmask35 = 0x00000007ffffffffull; +global const U64 bitmask36 = 0x0000000fffffffffull; +global const U64 bitmask37 = 0x0000001fffffffffull; +global const U64 bitmask38 = 0x0000003fffffffffull; +global const U64 bitmask39 = 0x0000007fffffffffull; +global const U64 bitmask40 = 0x000000ffffffffffull; +global const U64 bitmask41 = 0x000001ffffffffffull; +global const U64 bitmask42 = 0x000003ffffffffffull; +global const U64 bitmask43 = 0x000007ffffffffffull; +global const U64 bitmask44 = 0x00000fffffffffffull; +global const U64 bitmask45 = 0x00001fffffffffffull; +global const U64 bitmask46 = 0x00003fffffffffffull; +global const U64 bitmask47 = 0x00007fffffffffffull; +global const U64 bitmask48 = 0x0000ffffffffffffull; +global const U64 bitmask49 = 0x0001ffffffffffffull; +global const U64 bitmask50 = 0x0003ffffffffffffull; +global const U64 bitmask51 = 0x0007ffffffffffffull; +global const U64 bitmask52 = 0x000fffffffffffffull; +global const U64 bitmask53 = 0x001fffffffffffffull; +global const U64 bitmask54 = 0x003fffffffffffffull; +global const U64 bitmask55 = 0x007fffffffffffffull; +global const U64 bitmask56 = 0x00ffffffffffffffull; +global const U64 bitmask57 = 0x01ffffffffffffffull; +global const U64 bitmask58 = 0x03ffffffffffffffull; +global const U64 bitmask59 = 0x07ffffffffffffffull; +global const U64 bitmask60 = 0x0fffffffffffffffull; +global const U64 bitmask61 = 0x1fffffffffffffffull; +global const U64 bitmask62 = 0x3fffffffffffffffull; +global const U64 bitmask63 = 0x7fffffffffffffffull; +global const U64 bitmask64 = 0xffffffffffffffffull; + +global const U32 bit1 = (1<<0); +global const U32 bit2 = (1<<1); +global const U32 bit3 = (1<<2); +global const U32 bit4 = (1<<3); +global const U32 bit5 = (1<<4); +global const U32 bit6 = (1<<5); +global const U32 bit7 = (1<<6); +global const U32 bit8 = (1<<7); +global const U32 bit9 = (1<<8); +global const U32 bit10 = (1<<9); +global const U32 bit11 = (1<<10); +global const U32 bit12 = (1<<11); +global const U32 bit13 = (1<<12); +global const U32 bit14 = (1<<13); +global const U32 bit15 = (1<<14); +global const U32 bit16 = (1<<15); +global const U32 bit17 = (1<<16); +global const U32 bit18 = (1<<17); +global const U32 bit19 = (1<<18); +global const U32 bit20 = (1<<19); +global const U32 bit21 = (1<<20); +global const U32 bit22 = (1<<21); +global const U32 bit23 = (1<<22); +global const U32 bit24 = (1<<23); +global const U32 bit25 = (1<<24); +global const U32 bit26 = (1<<25); +global const U32 bit27 = (1<<26); +global const U32 bit28 = (1<<27); +global const U32 bit29 = (1<<28); +global const U32 bit30 = (1<<29); +global const U32 bit31 = (1<<30); +global const U32 bit32 = (1<<31); + +global const U64 bit33 = (1ull<<32); +global const U64 bit34 = (1ull<<33); +global const U64 bit35 = (1ull<<34); +global const U64 bit36 = (1ull<<35); +global const U64 bit37 = (1ull<<36); +global const U64 bit38 = (1ull<<37); +global const U64 bit39 = (1ull<<38); +global const U64 bit40 = (1ull<<39); +global const U64 bit41 = (1ull<<40); +global const U64 bit42 = (1ull<<41); +global const U64 bit43 = (1ull<<42); +global const U64 bit44 = (1ull<<43); +global const U64 bit45 = (1ull<<44); +global const U64 bit46 = (1ull<<45); +global const U64 bit47 = (1ull<<46); +global const U64 bit48 = (1ull<<47); +global const U64 bit49 = (1ull<<48); +global const U64 bit50 = (1ull<<49); +global const U64 bit51 = (1ull<<50); +global const U64 bit52 = (1ull<<51); +global const U64 bit53 = (1ull<<52); +global const U64 bit54 = (1ull<<53); +global const U64 bit55 = (1ull<<54); +global const U64 bit56 = (1ull<<55); +global const U64 bit57 = (1ull<<56); +global const U64 bit58 = (1ull<<57); +global const U64 bit59 = (1ull<<58); +global const U64 bit60 = (1ull<<59); +global const U64 bit61 = (1ull<<60); +global const U64 bit62 = (1ull<<61); +global const U64 bit63 = (1ull<<62); +global const U64 bit64 = (1ull<<63); + +//////////////////////////////// +//~ allen: Time + +typedef enum WeekDay +{ + WeekDay_Sun, + WeekDay_Mon, + WeekDay_Tue, + WeekDay_Wed, + WeekDay_Thu, + WeekDay_Fri, + WeekDay_Sat, + WeekDay_COUNT, +} +WeekDay; + +typedef enum Month +{ + Month_Jan, + Month_Feb, + Month_Mar, + Month_Apr, + Month_May, + Month_Jun, + Month_Jul, + Month_Aug, + Month_Sep, + Month_Oct, + Month_Nov, + Month_Dec, + Month_COUNT, +} +Month; + +typedef struct DateTime DateTime; +struct DateTime +{ + U16 micro_sec; // [0,999] + U16 msec; // [0,999] + U16 sec; // [0,60] + U16 min; // [0,59] + U16 hour; // [0,24] + U16 day; // [0,30] + union + { + WeekDay week_day; + U32 wday; + }; + union + { + Month month; + U32 mon; + }; + U32 year; // 1 = 1 CE, 0 = 1 BC +}; + +typedef U64 DenseTime; + +//////////////////////////////// +//~ allen: Files + +typedef U32 FilePropertyFlags; +enum +{ + FilePropertyFlag_IsFolder = (1 << 0), +}; + +typedef struct FileProperties FileProperties; +struct FileProperties +{ + U64 size; + DenseTime modified; + DenseTime created; + FilePropertyFlags flags; +}; + +//////////////////////////////// +//~ rjf: Safe Casts + +internal U16 safe_cast_u16(U32 x); +internal U32 safe_cast_u32(U64 x); +internal S32 safe_cast_s32(S64 x); + +//////////////////////////////// +//~ rjf: Large Base Type Functions + +internal U128 u128_zero(void); +internal U128 u128_make(U64 v0, U64 v1); +internal B32 u128_match(U128 a, U128 b); + +//////////////////////////////// +//~ rjf: Bit Patterns + +internal U32 u32_from_u64_saturate(U64 x); +internal U64 u64_up_to_pow2(U64 x); +internal S32 extend_sign32(U32 x, U32 size); +internal S64 extend_sign64(U64 x, U64 size); + +internal F32 inf32(void); +internal F32 neg_inf32(void); + +internal U16 bswap_u16(U16 x); +internal U32 bswap_u32(U32 x); +internal U64 bswap_u64(U64 x); + +internal U64 count_bits_set16(U16 val); +internal U64 count_bits_set32(U32 val); +internal U64 count_bits_set64(U64 val); + +internal U64 ctz32(U32 val); +internal U64 ctz64(U64 val); +internal U64 clz32(U32 val); +internal U64 clz64(U64 val); + +//////////////////////////////// +//~ rjf: Enum -> Sign + +internal S32 sign_from_side_S32(Side side); +internal F32 sign_from_side_F32(Side side); + +//////////////////////////////// +//~ rjf: Memory Functions + +internal B32 memory_is_zero(void *ptr, U64 size); + +//////////////////////////////// +//~ rjf: Text 2D Coordinate/Range Functions + +internal TxtPt txt_pt(S64 line, S64 column); +internal B32 txt_pt_match(TxtPt a, TxtPt b); +internal B32 txt_pt_less_than(TxtPt a, TxtPt b); +internal TxtPt txt_pt_min(TxtPt a, TxtPt b); +internal TxtPt txt_pt_max(TxtPt a, TxtPt b); +internal TxtRng txt_rng(TxtPt min, TxtPt max); +internal TxtRng txt_rng_intersect(TxtRng a, TxtRng b); +internal TxtRng txt_rng_union(TxtRng a, TxtRng b); +internal B32 txt_rng_contains(TxtRng r, TxtPt pt); + +//////////////////////////////// +//~ rjf: Toolchain/Environment Enum Functions + +internal U64 bit_size_from_arch(Architecture arch); +internal U64 max_instruction_size_from_arch(Architecture arch); + +internal OperatingSystem operating_system_from_context(void); +internal Architecture architecture_from_context(void); +internal Compiler compiler_from_context(void); + +//////////////////////////////// +//~ rjf: Time Functions + +internal DenseTime dense_time_from_date_time(DateTime date_time); +internal DateTime date_time_from_dense_time(DenseTime time); +internal DateTime date_time_from_micro_seconds(U64 time); + +//////////////////////////////// +//~ rjf: Non-Fancy Ring Buffer Reads/Writes + +internal U64 ring_write(U8 *ring_base, U64 ring_size, U64 ring_pos, void *src_data, U64 src_data_size); +internal U64 ring_read(U8 *ring_base, U64 ring_size, U64 ring_pos, void *dst_data, U64 read_size); +#define ring_write_struct(ring_base, ring_size, ring_pos, ptr) ring_write((ring_base), (ring_size), (ring_pos), (ptr), sizeof(*(ptr))) +#define ring_read_struct(ring_base, ring_size, ring_pos, ptr) ring_read((ring_base), (ring_size), (ring_pos), (ptr), sizeof(*(ptr))) + +//////////////////////////////// +//~ rjf: Sorts + +#define quick_sort(ptr, count, element_size, cmp_function) qsort((ptr), (count), (element_size), (int (*)(const void *, const void *))(cmp_function)) + +#endif // BASE_CORE_H diff --git a/src/base/base_entry_point.c b/src/base/base_entry_point.c index 95378b57..498ec0b5 100644 --- a/src/base/base_entry_point.c +++ b/src/base/base_entry_point.c @@ -1,95 +1,92 @@ -internal void -main_thread_base_entry_point(void (*entry_point)(CmdLine *cmdline), char **arguments, U64 arguments_count) -{ -#if PROFILE_TELEMETRY - local_persist U8 tm_data[MB(64)]; - tmLoadLibrary(TM_RELEASE); - tmSetMaxThreadCount(256); - tmInitialize(sizeof(tm_data), (char *)tm_data); -#endif - TCTX tctx; - tctx_init_and_equip(&tctx); - ThreadNameF("[main thread]"); - Temp scratch = scratch_begin(0, 0); - String8List command_line_argument_strings = os_string_list_from_argcv(scratch.arena, (int)arguments_count, arguments); - CmdLine cmdline = cmd_line_from_string_list(scratch.arena, command_line_argument_strings); - B32 capture = cmd_line_has_flag(&cmdline, str8_lit("capture")); - if(capture) - { - ProfBeginCapture(arguments[0]); - } -#if defined(OS_CORE_H) && !defined(OS_INIT_MANUAL) - os_init(); -#endif -#if defined(TASK_SYSTEM_H) && !defined(TS_INIT_MANUAL) - ts_init(); -#endif -#if defined(HASH_STORE_H) && !defined(HS_INIT_MANUAL) - hs_init(); -#endif -#if defined(FILE_STREAM_H) && !defined(FS_INIT_MANUAL) - fs_init(); -#endif -#if defined(TEXT_CACHE_H) && !defined(TXT_INIT_MANUAL) - txt_init(); -#endif -#if defined(MUTABLE_TEXT_H) && !defined(MTX_INIT_MANUAL) - mtx_init(); -#endif -#if defined(DASM_CACHE_H) && !defined(DASM_INIT_MANUAL) - dasm_init(); -#endif -#if defined(DI_H) && !defined(DI_INIT_MANUAL) - di_init(); -#endif -#if defined(FUZZY_SEARCH_H) && !defined(FZY_INIT_MANUAL) - fzy_init(); -#endif -#if defined(DEMON_CORE_H) && !defined(DMN_INIT_MANUAL) - dmn_init(); -#endif -#if defined(CTRL_CORE_H) && !defined(CTRL_INIT_MANUAL) - ctrl_init(); -#endif -#if defined(OS_GRAPHICAL_H) && !defined(OS_GFX_INIT_MANUAL) - os_graphical_init(); -#endif -#if defined(FONT_PROVIDER_H) && !defined(FP_INIT_MANUAL) - fp_init(); -#endif -#if defined(RENDER_CORE_H) && !defined(R_INIT_MANUAL) - r_init(&cmdline); -#endif -#if defined(TEXTURE_CACHE_H) && !defined(TEX_INIT_MANUAL) - tex_init(); -#endif -#if defined(GEO_CACHE_H) && !defined(GEO_INIT_MANUAL) - geo_init(); -#endif -#if defined(FONT_CACHE_H) && !defined(F_INIT_MANUAL) - f_init(); -#endif -#if defined(DF_CORE_H) && !defined(DF_INIT_MANUAL) - DF_StateDeltaHistory *hist = df_state_delta_history_alloc(); - df_core_init(&cmdline, hist); -#endif -#if defined(DF_GFX_H) && !defined(DF_GFX_INIT_MANUAL) - df_gfx_init(update_and_render, df_state_delta_history()); -#endif - entry_point(&cmdline); - if(capture) - { - ProfEndCapture(); - } - scratch_end(scratch); - tctx_release(); -} - -internal void -supplement_thread_base_entry_point(void (*entry_point)(void *params), void *params) -{ - TCTX tctx; - tctx_init_and_equip(&tctx); - entry_point(params); - tctx_release(); -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +internal void +main_thread_base_entry_point(void (*entry_point)(CmdLine *cmdline), char **arguments, U64 arguments_count) +{ +#if PROFILE_TELEMETRY + local_persist U8 tm_data[MB(64)]; + tmLoadLibrary(TM_RELEASE); + tmSetMaxThreadCount(256); + tmInitialize(sizeof(tm_data), (char *)tm_data); +#endif + ThreadNameF("[main thread]"); + Temp scratch = scratch_begin(0, 0); + String8List command_line_argument_strings = os_string_list_from_argcv(scratch.arena, (int)arguments_count, arguments); + CmdLine cmdline = cmd_line_from_string_list(scratch.arena, command_line_argument_strings); + B32 capture = cmd_line_has_flag(&cmdline, str8_lit("capture")); + if(capture) + { + ProfBeginCapture(arguments[0]); + } +#if defined(TASK_SYSTEM_H) && !defined(TS_INIT_MANUAL) + ts_init(); +#endif +#if defined(HASH_STORE_H) && !defined(HS_INIT_MANUAL) + hs_init(); +#endif +#if defined(FILE_STREAM_H) && !defined(FS_INIT_MANUAL) + fs_init(); +#endif +#if defined(TEXT_CACHE_H) && !defined(TXT_INIT_MANUAL) + txt_init(); +#endif +#if defined(MUTABLE_TEXT_H) && !defined(MTX_INIT_MANUAL) + mtx_init(); +#endif +#if defined(DASM_CACHE_H) && !defined(DASM_INIT_MANUAL) + dasm_init(); +#endif +#if defined(DI_H) && !defined(DI_INIT_MANUAL) + di_init(); +#endif +#if defined(FUZZY_SEARCH_H) && !defined(FZY_INIT_MANUAL) + fzy_init(); +#endif +#if defined(DEMON_CORE_H) && !defined(DMN_INIT_MANUAL) + dmn_init(); +#endif +#if defined(CTRL_CORE_H) && !defined(CTRL_INIT_MANUAL) + ctrl_init(); +#endif +#if defined(OS_GRAPHICAL_H) && !defined(OS_GFX_INIT_MANUAL) + os_gfx_init(); +#endif +#if defined(FONT_PROVIDER_H) && !defined(FP_INIT_MANUAL) + fp_init(); +#endif +#if defined(RENDER_CORE_H) && !defined(R_INIT_MANUAL) + r_init(&cmdline); +#endif +#if defined(TEXTURE_CACHE_H) && !defined(TEX_INIT_MANUAL) + tex_init(); +#endif +#if defined(GEO_CACHE_H) && !defined(GEO_INIT_MANUAL) + geo_init(); +#endif +#if defined(FONT_CACHE_H) && !defined(F_INIT_MANUAL) + f_init(); +#endif +#if defined(DF_CORE_H) && !defined(DF_INIT_MANUAL) + DF_StateDeltaHistory *hist = df_state_delta_history_alloc(); + df_core_init(&cmdline, hist); +#endif +#if defined(DF_GFX_H) && !defined(DF_GFX_INIT_MANUAL) + df_gfx_init(update_and_render, df_state_delta_history()); +#endif + entry_point(&cmdline); + if(capture) + { + ProfEndCapture(); + } + scratch_end(scratch); +} + +internal void +supplement_thread_base_entry_point(void (*entry_point)(void *params), void *params) +{ + TCTX tctx; + tctx_init_and_equip(&tctx); + entry_point(params); + tctx_release(); +} diff --git a/src/base/base_strings.c b/src/base/base_strings.c index 355cc62e..56f2c1b5 100644 --- a/src/base/base_strings.c +++ b/src/base/base_strings.c @@ -1,1975 +1,1975 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Third Party Includes - -#if !BUILD_SUPPLEMENTARY_UNIT -# define STB_SPRINTF_IMPLEMENTATION -# define STB_SPRINTF_STATIC -# include "third_party/stb/stb_sprintf.h" -#endif - -//////////////////////////////// -//~ NOTE(allen): String <-> Integer Tables - -read_only global U8 integer_symbols[16] = { - '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F', -}; - -// NOTE(allen): Includes reverses for uppercase and lowercase hex. -read_only global U8 integer_symbol_reverse[128] = { - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, -}; - -read_only global U8 base64[64] = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - '_', '$', -}; - -read_only global U8 base64_reverse[128] = { - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0xFF,0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, - 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0x00, - 0xFF,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32, - 0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0x3E, - 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18, - 0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0xFF,0xFF,0xFF,0xFF,0xFF, -}; - -//////////////////////////////// -//~ rjf: Character Classification & Conversion Functions - -internal B32 -char_is_space(U8 c){ - return(c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '\f' || c == '\v'); -} - -internal B32 -char_is_upper(U8 c){ - return('A' <= c && c <= 'Z'); -} - -internal B32 -char_is_lower(U8 c){ - return('a' <= c && c <= 'z'); -} - -internal B32 -char_is_alpha(U8 c){ - return(char_is_upper(c) || char_is_lower(c)); -} - -internal B32 -char_is_slash(U8 c){ - return(c == '/' || c == '\\'); -} - -internal B32 -char_is_digit(U8 c, U32 base){ - B32 result = 0; - if (0 < base && base <= 16){ - U8 val = integer_symbol_reverse[c]; - if (val < base){ - result = 1; - } - } - return(result); -} - -internal U8 -char_to_lower(U8 c){ - if (char_is_upper(c)){ - c += ('a' - 'A'); - } - return(c); -} - -internal U8 -char_to_upper(U8 c){ - if (char_is_lower(c)){ - c += ('A' - 'a'); - } - return(c); -} - -internal U8 -char_to_correct_slash(U8 c){ - if(char_is_slash(c)){ - c = '/'; - } - return(c); -} - -//////////////////////////////// -//~ rjf: C-String Measurement - -internal U64 -cstring8_length(U8 *c){ - U8 *p = c; - for (;*p != 0; p += 1); - return(p - c); -} - -internal U64 -cstring16_length(U16 *c){ - U16 *p = c; - for (;*p != 0; p += 1); - return(p - c); -} - -internal U64 -cstring32_length(U32 *c){ - U32 *p = c; - for (;*p != 0; p += 1); - return(p - c); -} - -//////////////////////////////// -//~ rjf: String Constructors - -internal String8 -str8(U8 *str, U64 size){ - String8 result = {str, size}; - return(result); -} - -internal String8 -str8_range(U8 *first, U8 *one_past_last){ - String8 result = {first, (U64)(one_past_last - first)}; - return(result); -} - -internal String8 -str8_zero(void){ - String8 result = {0}; - return(result); -} - -internal String16 -str16(U16 *str, U64 size){ - String16 result = {str, size}; - return(result); -} - -internal String16 -str16_range(U16 *first, U16 *one_past_last){ - String16 result = {first, (U64)(one_past_last - first)}; - return(result); -} - -internal String16 -str16_zero(void){ - String16 result = {0}; - return(result); -} - -internal String32 -str32(U32 *str, U64 size){ - String32 result = {str, size}; - return(result); -} - -internal String32 -str32_range(U32 *first, U32 *one_past_last){ - String32 result = {first, (U64)(one_past_last - first)}; - return(result); -} - -internal String32 -str32_zero(void){ - String32 result = {0}; - return(result); -} - -internal String8 -str8_cstring(char *c){ - String8 result = {(U8*)c, cstring8_length((U8*)c)}; - return(result); -} - -internal String16 -str16_cstring(U16 *c){ - String16 result = {(U16*)c, cstring16_length((U16*)c)}; - return(result); -} - -internal String32 -str32_cstring(U32 *c){ - String32 result = {(U32*)c, cstring32_length((U32*)c)}; - return(result); -} - -internal String8 -str8_cstring_capped(void *cstr, void *cap) -{ - char *ptr = (char*)cstr; - char *opl = (char*)cap; - for (;ptr < opl && *ptr != 0; ptr += 1); - U64 size = (U64)(ptr - (char *)cstr); - String8 result = {(U8*)cstr, size}; - return(result); -} - -//////////////////////////////// -//~ rjf: String Stylization - -internal String8 -upper_from_str8(Arena *arena, String8 string) -{ - string = push_str8_copy(arena, string); - for(U64 idx = 0; idx < string.size; idx += 1) - { - string.str[idx] = char_to_upper(string.str[idx]); - } - return string; -} - -internal String8 -lower_from_str8(Arena *arena, String8 string) -{ - string = push_str8_copy(arena, string); - for(U64 idx = 0; idx < string.size; idx += 1) - { - string.str[idx] = char_to_lower(string.str[idx]); - } - return string; -} - -internal String8 -backslashed_from_str8(Arena *arena, String8 string) -{ - string = push_str8_copy(arena, string); - for(U64 idx = 0; idx < string.size; idx += 1) - { - string.str[idx] = char_is_slash(string.str[idx]) ? '\\' : string.str[idx]; - } - return string; -} - -//////////////////////////////// -//~ rjf: String Matching - -internal B32 -str8_match(String8 a, String8 b, StringMatchFlags flags){ - B32 result = 0; - if (a.size == b.size || (flags & StringMatchFlag_RightSideSloppy)){ - B32 case_insensitive = (flags & StringMatchFlag_CaseInsensitive); - B32 slash_insensitive = (flags & StringMatchFlag_SlashInsensitive); - U64 size = Min(a.size, b.size); - result = 1; - for (U64 i = 0; i < size; i += 1){ - U8 at = a.str[i]; - U8 bt = b.str[i]; - if (case_insensitive){ - at = char_to_upper(at); - bt = char_to_upper(bt); - } - if (slash_insensitive){ - at = char_to_correct_slash(at); - bt = char_to_correct_slash(bt); - } - if (at != bt){ - result = 0; - break; - } - } - } - return(result); -} - -internal U64 -str8_find_needle(String8 string, U64 start_pos, String8 needle, StringMatchFlags flags){ - U8 *p = string.str + start_pos; - U64 stop_offset = Max(string.size + 1, needle.size) - needle.size; - U8 *stop_p = string.str + stop_offset; - if (needle.size > 0){ - U8 *string_opl = string.str + string.size; - String8 needle_tail = str8_skip(needle, 1); - StringMatchFlags adjusted_flags = flags | StringMatchFlag_RightSideSloppy; - U8 needle_first_char_adjusted = needle.str[0]; - if(adjusted_flags & StringMatchFlag_CaseInsensitive){ - needle_first_char_adjusted = char_to_upper(needle_first_char_adjusted); - } - for (;p < stop_p; p += 1){ - U8 haystack_char_adjusted = *p; - if(adjusted_flags & StringMatchFlag_CaseInsensitive){ - haystack_char_adjusted = char_to_upper(haystack_char_adjusted); - } - if (haystack_char_adjusted == needle_first_char_adjusted){ - if (str8_match(str8_range(p + 1, string_opl), needle_tail, adjusted_flags)){ - break; - } - } - } - } - U64 result = string.size; - if (p < stop_p){ - result = (U64)(p - string.str); - } - return(result); -} - -internal B32 -str8_ends_with(String8 string, String8 end, StringMatchFlags flags){ - String8 postfix = str8_postfix(string, end.size); - B32 is_match = str8_match(end, postfix, flags); - return is_match; -} - -//////////////////////////////// -//~ rjf: String Slicing - -internal String8 -str8_substr(String8 str, Rng1U64 range){ - range.min = ClampTop(range.min, str.size); - range.max = ClampTop(range.max, str.size); - str.str += range.min; - str.size = dim_1u64(range); - return(str); -} - -internal String8 -str8_prefix(String8 str, U64 size){ - str.size = ClampTop(size, str.size); - return(str); -} - -internal String8 -str8_skip(String8 str, U64 amt){ - amt = ClampTop(amt, str.size); - str.str += amt; - str.size -= amt; - return(str); -} - -internal String8 -str8_postfix(String8 str, U64 size){ - size = ClampTop(size, str.size); - str.str = (str.str + str.size) - size; - str.size = size; - return(str); -} - -internal String8 -str8_chop(String8 str, U64 amt){ - amt = ClampTop(amt, str.size); - str.size -= amt; - return(str); -} - -internal String8 -str8_skip_chop_whitespace(String8 string){ - U8 *first = string.str; - U8 *opl = first + string.size; - for (;first < opl; first += 1){ - if (!char_is_space(*first)){ - break; - } - } - for (;opl > first;){ - opl -= 1; - if (!char_is_space(*opl)){ - opl += 1; - break; - } - } - String8 result = str8_range(first, opl); - return(result); -} - -//////////////////////////////// -//~ rjf: String Formatting & Copying - -internal String8 -push_str8_cat(Arena *arena, String8 s1, String8 s2){ - String8 str; - str.size = s1.size + s2.size; - str.str = push_array_no_zero(arena, U8, str.size + 1); - MemoryCopy(str.str, s1.str, s1.size); - MemoryCopy(str.str + s1.size, s2.str, s2.size); - str.str[str.size] = 0; - return(str); -} - -internal String8 -push_str8_copy(Arena *arena, String8 s){ - String8 str; - str.size = s.size; - str.str = push_array_no_zero(arena, U8, str.size + 1); - MemoryCopy(str.str, s.str, s.size); - str.str[str.size] = 0; - return(str); -} - -internal String8 -push_str8fv(Arena *arena, char *fmt, va_list args){ - va_list args2; - va_copy(args2, args); - U32 needed_bytes = raddbg_vsnprintf(0, 0, fmt, args) + 1; - String8 result = {0}; - result.str = push_array_no_zero(arena, U8, needed_bytes); - result.size = raddbg_vsnprintf((char*)result.str, needed_bytes, fmt, args2); - result.str[result.size] = 0; - va_end(args2); - return(result); -} - -internal String8 -push_str8f(Arena *arena, char *fmt, ...){ - va_list args; - va_start(args, fmt); - String8 result = push_str8fv(arena, fmt, args); - va_end(args); - return(result); -} - -//////////////////////////////// -//~ rjf: String <=> Integer Conversions - -//- rjf: string -> integer - -internal S64 -sign_from_str8(String8 string, String8 *string_tail){ - // count negative signs - U64 neg_count = 0; - U64 i = 0; - for (; i < string.size; i += 1){ - if (string.str[i] == '-'){ - neg_count += 1; - } - else if (string.str[i] != '+'){ - break; - } - } - - // output part of string after signs - *string_tail = str8_skip(string, i); - - // output integer sign - S64 sign = (neg_count & 1)?-1:+1; - return(sign); -} - -internal B32 -str8_is_integer(String8 string, U32 radix){ - B32 result = 0; - if (string.size > 0){ - if (1 < radix && radix <= 16){ - result = 1; - for (U64 i = 0; i < string.size; i += 1){ - U8 c = string.str[i]; - if (!(c < 0x80) || integer_symbol_reverse[c] >= radix){ - result = 0; - break; - } - } - } - } - return(result); -} - -internal U64 -u64_from_str8(String8 string, U32 radix){ - U64 x = 0; - if (1 < radix && radix <= 16){ - for (U64 i = 0; i < string.size; i += 1){ - x *= radix; - x += integer_symbol_reverse[string.str[i]&0x7F]; - } - } - return(x); -} - -internal S64 -s64_from_str8(String8 string, U32 radix){ - S64 sign = sign_from_str8(string, &string); - S64 x = (S64)u64_from_str8(string, radix) * sign; - return(x); -} - -internal B32 -try_u64_from_str8_c_rules(String8 string, U64 *x){ - B32 is_integer = 0; - if (str8_is_integer(string, 10)){ - is_integer = 1; - *x = u64_from_str8(string, 10); - } - else{ - String8 hex_string = str8_skip(string, 2); - if (str8_match(str8_prefix(string, 2), str8_lit("0x"), 0) && - str8_is_integer(hex_string, 0x10)){ - is_integer = 1; - *x = u64_from_str8(hex_string, 0x10); - } - else if (str8_match(str8_prefix(string, 2), str8_lit("0b"), 0) && - str8_is_integer(hex_string, 2)){ - is_integer = 1; - *x = u64_from_str8(hex_string, 2); - } - else{ - String8 oct_string = str8_skip(string, 1); - if (str8_match(str8_prefix(string, 1), str8_lit("0"), 0) && - str8_is_integer(hex_string, 010)){ - is_integer = 1; - *x = u64_from_str8(oct_string, 010); - } - } - } - return(is_integer); -} - -internal B32 -try_s64_from_str8_c_rules(String8 string, S64 *x){ - String8 string_tail = {0}; - S64 sign = sign_from_str8(string, &string_tail); - U64 x_u64 = 0; - B32 is_integer = try_u64_from_str8_c_rules(string_tail, &x_u64); - *x = x_u64*sign; - return(is_integer); -} - -//- rjf: integer -> string - -internal String8 -str8_from_memory_size(Arena *arena, U64 z){ - String8 result = {0}; - if (z < KB(1)){ - result = push_str8f(arena, "%llu b", z); - } - else if (z < MB(1)){ - result = push_str8f(arena, "%llu.%02llu Kb", z/KB(1), ((100*z)/KB(1))%100); - } - else if (z < GB(1)){ - result = push_str8f(arena, "%llu.%02llu Mb", z/MB(1), ((100*z)/MB(1))%100); - } - else{ - result = push_str8f(arena, "%llu.%02llu Gb", z/GB(1), ((100*z)/GB(1))%100); - } - return(result); -} - -internal String8 -str8_from_u64(Arena *arena, U64 u64, U32 radix, U8 min_digits, U8 digit_group_separator) -{ - String8 result = {0}; - { - // rjf: prefix - String8 prefix = {0}; - switch(radix) - { - case 16:{prefix = str8_lit("0x");}break; - case 8: {prefix = str8_lit("0o");}break; - case 2: {prefix = str8_lit("0b");}break; - } - - // rjf: determine # of chars between separators - U8 digit_group_size = 3; - switch(radix) - { - default:break; - case 2: - case 8: - case 16: - {digit_group_size = 4;}break; - } - - // rjf: prep - U64 needed_leading_0s = 0; - { - U64 needed_digits = 1; - { - U64 u64_reduce = u64; - for(;;) - { - u64_reduce /= radix; - if(u64_reduce == 0) - { - break; - } - needed_digits += 1; - } - } - needed_leading_0s = (min_digits > needed_digits) ? min_digits - needed_digits : 0; - U64 needed_separators = 0; - if(digit_group_separator != 0) - { - needed_separators = (needed_digits+needed_leading_0s)/digit_group_size; - if(needed_separators > 0 && (needed_digits+needed_leading_0s)%digit_group_size == 0) - { - needed_separators -= 1; - } - } - result.size = prefix.size + needed_leading_0s + needed_separators + needed_digits; - result.str = push_array_no_zero(arena, U8, result.size + 1); - result.str[result.size] = 0; - } - - // rjf: fill contents - { - U64 u64_reduce = u64; - U64 digits_until_separator = digit_group_size; - for(U64 idx = 0; idx < result.size; idx += 1) - { - if(digits_until_separator == 0 && digit_group_separator != 0) - { - result.str[result.size - idx - 1] = digit_group_separator; - digits_until_separator = digit_group_size+1; - } - else - { - result.str[result.size - idx - 1] = char_to_lower(integer_symbols[u64_reduce%radix]); - u64_reduce /= radix; - } - digits_until_separator -= 1; - if(u64_reduce == 0) - { - break; - } - } - for(U64 leading_0_idx = 0; leading_0_idx < needed_leading_0s; leading_0_idx += 1) - { - result.str[prefix.size + leading_0_idx] = '0'; - } - } - - // rjf: fill prefix - if(prefix.size != 0) - { - MemoryCopy(result.str, prefix.str, prefix.size); - } - } - return result; -} - -internal String8 -str8_from_s64(Arena *arena, S64 s64, U32 radix, U8 min_digits, U8 digit_group_separator) -{ - String8 result = {0}; - // TODO(rjf): preeeeetty sloppy... - if(s64 < 0) - { - Temp scratch = scratch_begin(&arena, 1); - String8 numeric_part = str8_from_u64(scratch.arena, (U64)(-s64), radix, min_digits, digit_group_separator); - result = push_str8f(arena, "-%S", numeric_part); - scratch_end(scratch); - } - else - { - result = str8_from_u64(arena, (U64)s64, radix, min_digits, digit_group_separator); - } - return result; -} - -//////////////////////////////// -//~ rjf: String <=> Float Conversions - -internal F64 -f64_from_str8(String8 string) -{ - // TODO(rjf): crappy implementation for now that just uses atof. - F64 result = 0; - if(string.size > 0) - { - // rjf: find starting pos of numeric string, as well as sign - F64 sign = +1.0; - //U64 first_numeric = 0; - if(string.str[0] == '-') - { - //first_numeric = 1; - sign = -1.0; - } - else if(string.str[0] == '+') - { - //first_numeric = 1; - sign = 1.0; - } - - // rjf: gather numerics - U64 num_valid_chars = 0; - char buffer[64]; - for(U64 idx = 0; idx < string.size && num_valid_chars < sizeof(buffer)-1; idx += 1) - { - if(char_is_digit(string.str[idx], 10) || string.str[idx] == '.') - { - buffer[num_valid_chars] = string.str[idx]; - num_valid_chars += 1; - } - } - - // rjf: null-terminate (the reason for all of this!!!!!!) - buffer[num_valid_chars] = 0; - - // rjf: do final conversion - result = sign * atof(buffer); - } - return result; -} - -//////////////////////////////// -//~ rjf: String List Construction Functions - -internal String8Node* -str8_list_push_node(String8List *list, String8Node *node){ - SLLQueuePush(list->first, list->last, node); - list->node_count += 1; - list->total_size += node->string.size; - return(node); -} - -internal String8Node* -str8_list_push_node_set_string(String8List *list, String8Node *node, String8 string){ - SLLQueuePush(list->first, list->last, node); - list->node_count += 1; - list->total_size += string.size; - node->string = string; - return(node); -} - -internal String8Node* -str8_list_push_node_front(String8List *list, String8Node *node){ - SLLQueuePushFront(list->first, list->last, node); - list->node_count += 1; - list->total_size += node->string.size; - return(node); -} - -internal String8Node* -str8_list_push_node_front_set_string(String8List *list, String8Node *node, String8 string){ - SLLQueuePushFront(list->first, list->last, node); - list->node_count += 1; - list->total_size += string.size; - node->string = string; - return(node); -} - -internal String8Node* -str8_list_push(Arena *arena, String8List *list, String8 string){ - String8Node *node = push_array_no_zero(arena, String8Node, 1); - str8_list_push_node_set_string(list, node, string); - return(node); -} - -internal String8Node* -str8_list_push_front(Arena *arena, String8List *list, String8 string){ - String8Node *node = push_array_no_zero(arena, String8Node, 1); - str8_list_push_node_front_set_string(list, node, string); - return(node); -} - -internal void -str8_list_concat_in_place(String8List *list, String8List *to_push){ - if(to_push->node_count != 0){ - if (list->last){ - list->node_count += to_push->node_count; - list->total_size += to_push->total_size; - list->last->next = to_push->first; - list->last = to_push->last; - } - else{ - *list = *to_push; - } - MemoryZeroStruct(to_push); - } -} - -internal String8Node* -str8_list_push_aligner(Arena *arena, String8List *list, U64 min, U64 align){ - String8Node *node = push_array_no_zero(arena, String8Node, 1); - U64 new_size = list->total_size + min; - U64 increase_size = 0; - if (align > 1){ - // NOTE(allen): assert is power of 2 - Assert(((align - 1) & align) == 0); - U64 mask = align - 1; - new_size += mask; - new_size &= (~mask); - increase_size = new_size - list->total_size; - } - local_persist const U8 zeroes_buffer[64] = {0}; - Assert(increase_size <= ArrayCount(zeroes_buffer)); - SLLQueuePush(list->first, list->last, node); - list->node_count += 1; - list->total_size = new_size; - node->string.str = (U8*)zeroes_buffer; - node->string.size = increase_size; - return(node); -} - -internal String8Node* -str8_list_pushf(Arena *arena, String8List *list, char *fmt, ...){ - va_list args; - va_start(args, fmt); - String8 string = push_str8fv(arena, fmt, args); - String8Node *result = str8_list_push(arena, list, string); - va_end(args); - return(result); -} - -internal String8Node* -str8_list_push_frontf(Arena *arena, String8List *list, char *fmt, ...){ - va_list args; - va_start(args, fmt); - String8 string = push_str8fv(arena, fmt, args); - String8Node *result = str8_list_push_front(arena, list, string); - va_end(args); - return(result); -} - -internal String8List -str8_list_copy(Arena *arena, String8List *list){ - String8List result = {0}; - for (String8Node *node = list->first; - node != 0; - node = node->next){ - String8Node *new_node = push_array_no_zero(arena, String8Node, 1); - String8 new_string = push_str8_copy(arena, node->string); - str8_list_push_node_set_string(&result, new_node, new_string); - } - return(result); -} - -internal String8List -str8_split(Arena *arena, String8 string, U8 *split_chars, U64 split_char_count, StringSplitFlags flags){ - String8List list = {0}; - - B32 keep_empties = (flags & StringSplitFlag_KeepEmpties); - - U8 *ptr = string.str; - U8 *opl = string.str + string.size; - for (;ptr < opl;){ - U8 *first = ptr; - for (;ptr < opl; ptr += 1){ - U8 c = *ptr; - B32 is_split = 0; - for (U64 i = 0; i < split_char_count; i += 1){ - if (split_chars[i] == c){ - is_split = 1; - break; - } - } - if (is_split){ - break; - } - } - - String8 string = str8_range(first, ptr); - if (keep_empties || string.size > 0){ - str8_list_push(arena, &list, string); - } - ptr += 1; - } - - return(list); -} - -internal String8List -str8_split_by_string_chars(Arena *arena, String8 string, String8 split_chars, StringSplitFlags flags){ - String8List list = str8_split(arena, string, split_chars.str, split_chars.size, flags); - return list; -} - -internal String8List -str8_list_split_by_string_chars(Arena *arena, String8List list, String8 split_chars, StringSplitFlags flags){ - String8List result = {0}; - for (String8Node *node = list.first; node != 0; node = node->next){ - String8List split = str8_split_by_string_chars(arena, node->string, split_chars, flags); - str8_list_concat_in_place(&result, &split); - } - return result; -} - -internal String8 -str8_list_join(Arena *arena, String8List *list, StringJoin *optional_params){ - StringJoin join = {0}; - if (optional_params != 0){ - MemoryCopyStruct(&join, optional_params); - } - - U64 sep_count = 0; - if (list->node_count > 0){ - sep_count = list->node_count - 1; - } - - String8 result; - result.size = join.pre.size + join.post.size + sep_count*join.sep.size + list->total_size; - U8 *ptr = result.str = push_array_no_zero(arena, U8, result.size + 1); - - MemoryCopy(ptr, join.pre.str, join.pre.size); - ptr += join.pre.size; - for (String8Node *node = list->first; - node != 0; - node = node->next){ - MemoryCopy(ptr, node->string.str, node->string.size); - ptr += node->string.size; - if (node->next != 0){ - MemoryCopy(ptr, join.sep.str, join.sep.size); - ptr += join.sep.size; - } - } - MemoryCopy(ptr, join.post.str, join.post.size); - ptr += join.post.size; - - *ptr = 0; - - return(result); -} - -internal void -str8_list_from_flags(Arena *arena, String8List *list, - U32 flags, String8 *flag_string_table, U32 flag_string_count){ - for (U32 i = 0; i < flag_string_count; i += 1){ - U32 flag = (1 << i); - if (flags & flag){ - str8_list_push(arena, list, flag_string_table[i]); - } - } -} - -//////////////////////////////// -//~ rjf; String Arrays - -internal String8Array -str8_array_from_list(Arena *arena, String8List *list) -{ - String8Array array; - array.count = list->node_count; - array.v = push_array_no_zero(arena, String8, array.count); - U64 idx = 0; - for(String8Node *n = list->first; n != 0; n = n->next, idx += 1) - { - array.v[idx] = n->string; - } - return array; -} - -internal String8Array -str8_array_reserve(Arena *arena, U64 count) -{ - String8Array arr; - arr.count = 0; - arr.v = push_array(arena, String8, count); - return arr; -} - -//////////////////////////////// -//~ rjf: String Path Helpers - -internal String8 -str8_chop_last_slash(String8 string){ - if (string.size > 0){ - U8 *ptr = string.str + string.size - 1; - for (;ptr >= string.str; ptr -= 1){ - if (*ptr == '/' || *ptr == '\\'){ - break; - } - } - if (ptr >= string.str){ - string.size = (U64)(ptr - string.str); - } - else{ - string.size = 0; - } - } - return(string); -} - -internal String8 -str8_skip_last_slash(String8 string){ - if (string.size > 0){ - U8 *ptr = string.str + string.size - 1; - for (;ptr >= string.str; ptr -= 1){ - if (*ptr == '/' || *ptr == '\\'){ - break; - } - } - if (ptr >= string.str){ - ptr += 1; - string.size = (U64)(string.str + string.size - ptr); - string.str = ptr; - } - } - return(string); -} - -internal String8 -str8_chop_last_dot(String8 string) -{ - String8 result = string; - U64 p = string.size; - for (;p > 0;){ - p -= 1; - if (string.str[p] == '.'){ - result = str8_prefix(string, p); - break; - } - } - return(result); -} - -internal String8 -str8_skip_last_dot(String8 string){ - String8 result = string; - U64 p = string.size; - for (;p > 0;){ - p -= 1; - if (string.str[p] == '.'){ - result = str8_skip(string, p + 1); - break; - } - } - return(result); -} - -internal PathStyle -path_style_from_str8(String8 string){ - PathStyle result = PathStyle_Relative; - if (string.size >= 1 && string.str[0] == '/'){ - result = PathStyle_UnixAbsolute; - } - else if (string.size >= 2 && - char_is_alpha(string.str[0]) && - string.str[1] == ':'){ - if (string.size == 2 || - char_is_slash(string.str[2])){ - result = PathStyle_WindowsAbsolute; - } - } - return(result); -} - -internal String8List -str8_split_path(Arena *arena, String8 string){ - String8List result = str8_split(arena, string, (U8*)"/\\", 2, 0); - return(result); -} - -internal void -str8_path_list_resolve_dots_in_place(String8List *path, PathStyle style){ - Temp scratch = scratch_begin(0, 0); - - String8MetaNode *stack = 0; - String8MetaNode *free_meta_node = 0; - String8Node *first = path->first; - - MemoryZeroStruct(path); - for (String8Node *node = first, *next = 0; - node != 0; - node = next){ - // save next now - next = node->next; - - // cases: - if (node == first && style == PathStyle_WindowsAbsolute){ - goto save_without_stack; - } - if (node->string.size == 1 && node->string.str[0] == '.'){ - goto do_nothing; - } - if (node->string.size == 2 && node->string.str[0] == '.' && node->string.str[1] == '.'){ - if (stack != 0){ - goto eliminate_stack_top; - } - else{ - goto save_without_stack; - } - } - goto save_with_stack; - - - // handlers: - save_with_stack: - { - str8_list_push_node(path, node); - - String8MetaNode *stack_node = free_meta_node; - if (stack_node != 0){ - SLLStackPop(free_meta_node); - } - else{ - stack_node = push_array_no_zero(scratch.arena, String8MetaNode, 1); - } - SLLStackPush(stack, stack_node); - stack_node->node = node; - - continue; - } - - save_without_stack: - { - str8_list_push_node(path, node); - - continue; - } - - eliminate_stack_top: - { - path->node_count -= 1; - path->total_size -= stack->node->string.size; - - SLLStackPop(stack); - - if (stack == 0){ - path->last = path->first; - } - else{ - path->last = stack->node; - } - continue; - } - - do_nothing: continue; - } - scratch_end(scratch); -} - -internal String8 -str8_path_list_join_by_style(Arena *arena, String8List *path, PathStyle style){ - StringJoin params = {0}; - switch (style){ - case PathStyle_Relative: - case PathStyle_WindowsAbsolute: - { - params.sep = str8_lit("/"); - }break; - - case PathStyle_UnixAbsolute: - { - params.pre = str8_lit("/"); - params.sep = str8_lit("/"); - }break; - } - - String8 result = str8_list_join(arena, path, ¶ms); - return(result); -} - -internal String8TxtPtPair -str8_txt_pt_pair_from_string(String8 string) -{ - String8TxtPtPair pair = {0}; - { - String8 file_part = {0}; - String8 line_part = {0}; - String8 col_part = {0}; - - // rjf: grab file part - for(U64 idx = 0; idx <= string.size; idx += 1) - { - U8 byte = (idx < string.size) ? (string.str[idx]) : 0; - U8 next_byte = ((idx+1 < string.size) ? (string.str[idx+1]) : 0); - if(byte == ':' && next_byte != '/' && next_byte != '\\') - { - file_part = str8_prefix(string, idx); - line_part = str8_skip(string, idx+1); - break; - } - else if(byte == 0) - { - file_part = string; - break; - } - } - - // rjf: grab line/column - { - U64 colon_pos = str8_find_needle(line_part, 0, str8_lit(":"), 0); - if(colon_pos < line_part.size) - { - col_part = str8_skip(line_part, colon_pos+1); - line_part = str8_prefix(line_part, colon_pos); - } - } - - // rjf: convert line/column strings to numerics - U64 line = 0; - U64 column = 0; - try_u64_from_str8_c_rules(line_part, &line); - try_u64_from_str8_c_rules(col_part, &column); - - // rjf: fill - pair.string = file_part; - pair.pt = txt_pt((S64)line, (S64)column); - if(pair.pt.line == 0) { pair.pt.line = 1; } - if(pair.pt.column == 0) { pair.pt.column = 1; } - } - return pair; -} - -//////////////////////////////// -//~ rjf: UTF-8 & UTF-16 Decoding/Encoding - -read_only global U8 utf8_class[32] = { - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,2,2,2,2,3,3,4,5, -}; - -internal UnicodeDecode -utf8_decode(U8 *str, U64 max){ - UnicodeDecode result = {1, max_U32}; - U8 byte = str[0]; - U8 byte_class = utf8_class[byte >> 3]; - switch (byte_class) - { - case 1: - { - result.codepoint = byte; - }break; - case 2: - { - if (2 < max) - { - U8 cont_byte = str[1]; - if (utf8_class[cont_byte >> 3] == 0) - { - result.codepoint = (byte & bitmask5) << 6; - result.codepoint |= (cont_byte & bitmask6); - result.inc = 2; - } - } - }break; - case 3: - { - if (2 < max) - { - U8 cont_byte[2] = {str[1], str[2]}; - if (utf8_class[cont_byte[0] >> 3] == 0 && - utf8_class[cont_byte[1] >> 3] == 0) - { - result.codepoint = (byte & bitmask4) << 12; - result.codepoint |= ((cont_byte[0] & bitmask6) << 6); - result.codepoint |= (cont_byte[1] & bitmask6); - result.inc = 3; - } - } - }break; - case 4: - { - if (3 < max) - { - U8 cont_byte[3] = {str[1], str[2], str[3]}; - if (utf8_class[cont_byte[0] >> 3] == 0 && - utf8_class[cont_byte[1] >> 3] == 0 && - utf8_class[cont_byte[2] >> 3] == 0) - { - result.codepoint = (byte & bitmask3) << 18; - result.codepoint |= ((cont_byte[0] & bitmask6) << 12); - result.codepoint |= ((cont_byte[1] & bitmask6) << 6); - result.codepoint |= (cont_byte[2] & bitmask6); - result.inc = 4; - } - } - } - } - return(result); -} - -internal UnicodeDecode -utf16_decode(U16 *str, U64 max){ - UnicodeDecode result = {1, max_U32}; - result.codepoint = str[0]; - result.inc = 1; - if (max > 1 && 0xD800 <= str[0] && str[0] < 0xDC00 && 0xDC00 <= str[1] && str[1] < 0xE000){ - result.codepoint = ((str[0] - 0xD800) << 10) | (str[1] - 0xDC00) + 0x10000; - result.inc = 2; - } - return(result); -} - -internal U32 -utf8_encode(U8 *str, U32 codepoint){ - U32 inc = 0; - if (codepoint <= 0x7F){ - str[0] = (U8)codepoint; - inc = 1; - } - else if (codepoint <= 0x7FF){ - str[0] = (bitmask2 << 6) | ((codepoint >> 6) & bitmask5); - str[1] = bit8 | (codepoint & bitmask6); - inc = 2; - } - else if (codepoint <= 0xFFFF){ - str[0] = (bitmask3 << 5) | ((codepoint >> 12) & bitmask4); - str[1] = bit8 | ((codepoint >> 6) & bitmask6); - str[2] = bit8 | ( codepoint & bitmask6); - inc = 3; - } - else if (codepoint <= 0x10FFFF){ - str[0] = (bitmask4 << 4) | ((codepoint >> 18) & bitmask3); - str[1] = bit8 | ((codepoint >> 12) & bitmask6); - str[2] = bit8 | ((codepoint >> 6) & bitmask6); - str[3] = bit8 | ( codepoint & bitmask6); - inc = 4; - } - else{ - str[0] = '?'; - inc = 1; - } - return(inc); -} - -internal U32 -utf16_encode(U16 *str, U32 codepoint){ - U32 inc = 1; - if (codepoint == max_U32){ - str[0] = (U16)'?'; - } - else if (codepoint < 0x10000){ - str[0] = (U16)codepoint; - } - else{ - U32 v = codepoint - 0x10000; - str[0] = safe_cast_u16(0xD800 + (v >> 10)); - str[1] = safe_cast_u16(0xDC00 + (v & bitmask10)); - inc = 2; - } - return(inc); -} - -internal U32 -utf8_from_utf32_single(U8 *buffer, U32 character){ - return(utf8_encode(buffer, character)); -} - -//////////////////////////////// -//~ rjf: Unicode String Conversions - -internal String8 -str8_from_16(Arena *arena, String16 in){ - U64 cap = in.size*3; - U8 *str = push_array_no_zero(arena, U8, cap + 1); - U16 *ptr = in.str; - U16 *opl = ptr + in.size; - U64 size = 0; - UnicodeDecode consume; - for (;ptr < opl; ptr += consume.inc){ - consume = utf16_decode(ptr, opl - ptr); - size += utf8_encode(str + size, consume.codepoint); - } - str[size] = 0; - arena_put_back(arena, (cap - size)); - return(str8(str, size)); -} - -internal String16 -str16_from_8(Arena *arena, String8 in){ - U64 cap = in.size*2; - U16 *str = push_array_no_zero(arena, U16, cap + 1); - U8 *ptr = in.str; - U8 *opl = ptr + in.size; - U64 size = 0; - UnicodeDecode consume; - for (;ptr < opl; ptr += consume.inc){ - consume = utf8_decode(ptr, opl - ptr); - size += utf16_encode(str + size, consume.codepoint); - } - str[size] = 0; - arena_put_back(arena, (cap - size)*2); - return(str16(str, size)); -} - -internal String8 -str8_from_32(Arena *arena, String32 in){ - U64 cap = in.size*4; - U8 *str = push_array_no_zero(arena, U8, cap + 1); - U32 *ptr = in.str; - U32 *opl = ptr + in.size; - U64 size = 0; - for (;ptr < opl; ptr += 1){ - size += utf8_encode(str + size, *ptr); - } - str[size] = 0; - arena_put_back(arena, (cap - size)); - return(str8(str, size)); -} - -internal String32 -str32_from_8(Arena *arena, String8 in){ - U64 cap = in.size; - U32 *str = push_array_no_zero(arena, U32, cap + 1); - U8 *ptr = in.str; - U8 *opl = ptr + in.size; - U64 size = 0; - UnicodeDecode consume; - for (;ptr < opl; ptr += consume.inc){ - consume = utf8_decode(ptr, opl - ptr); - str[size] = consume.codepoint; - size += 1; - } - str[size] = 0; - arena_put_back(arena, (cap - size)*4); - return(str32(str, size)); -} - -//////////////////////////////// -//~ rjf: Basic Types & Space Enum -> String Conversions - -internal String8 -string_from_dimension(Dimension dimension){ - local_persist String8 strings[] = { - str8_lit_comp("X"), - str8_lit_comp("Y"), - str8_lit_comp("Z"), - str8_lit_comp("W"), - }; - String8 result = str8_lit("error"); - if ((U32)dimension < 4){ - result = strings[dimension]; - } - return(result); -} - -internal String8 -string_from_side(Side side){ - local_persist String8 strings[] = { - str8_lit_comp("Min"), - str8_lit_comp("Max"), - }; - String8 result = str8_lit("error"); - if ((U32)side < 2){ - result = strings[side]; - } - return(result); -} - -internal String8 -string_from_operating_system(OperatingSystem os){ - local_persist String8 strings[] = { - str8_lit_comp("Null"), - str8_lit_comp("Windows"), - str8_lit_comp("Linux"), - str8_lit_comp("Mac"), - }; - String8 result = str8_lit("error"); - if (os < OperatingSystem_COUNT){ - result = strings[os]; - } - return(result); -} - -internal String8 -string_from_architecture(Architecture arch){ - local_persist String8 strings[] = { - str8_lit_comp("Null"), - str8_lit_comp("x64"), - str8_lit_comp("x86"), - str8_lit_comp("arm64"), - str8_lit_comp("arm32"), - }; - String8 result = str8_lit("error"); - if (arch < Architecture_COUNT){ - result = strings[arch]; - } - return(result); -} - -//////////////////////////////// -//~ rjf: Time Types -> String - -internal String8 -string_from_week_day(WeekDay week_day){ - local_persist String8 strings[] = { - str8_lit_comp("Sun"), - str8_lit_comp("Mon"), - str8_lit_comp("Tue"), - str8_lit_comp("Wed"), - str8_lit_comp("Thu"), - str8_lit_comp("Fri"), - str8_lit_comp("Sat"), - }; - String8 result = str8_lit("Err"); - if ((U32)week_day < WeekDay_COUNT){ - result = strings[week_day]; - } - return(result); -} - -internal String8 -string_from_month(Month month){ - local_persist String8 strings[] = { - str8_lit_comp("Jan"), - str8_lit_comp("Feb"), - str8_lit_comp("Mar"), - str8_lit_comp("Apr"), - str8_lit_comp("May"), - str8_lit_comp("Jun"), - str8_lit_comp("Jul"), - str8_lit_comp("Aug"), - str8_lit_comp("Sep"), - str8_lit_comp("Oct"), - str8_lit_comp("Nov"), - str8_lit_comp("Dec"), - }; - String8 result = str8_lit("Err"); - if ((U32)month < Month_COUNT){ - result = strings[month]; - } - return(result); -} - -internal String8 -push_date_time_string(Arena *arena, DateTime *date_time){ - char *mon_str = (char*)string_from_month(date_time->month).str; - U32 adjusted_hour = date_time->hour%12; - if (adjusted_hour == 0){ - adjusted_hour = 12; - } - char *ampm = "am"; - if (date_time->hour >= 12){ - ampm = "pm"; - } - String8 result = push_str8f(arena, "%d %s %d, %02d:%02d:%02d %s", - date_time->day, mon_str, date_time->year, - adjusted_hour, date_time->min, date_time->sec, ampm); - return(result); -} - -internal String8 -push_file_name_date_time_string(Arena *arena, DateTime *date_time){ - char *mon_str = (char*)string_from_month(date_time->month).str; - String8 result = push_str8f(arena, "%d-%s-%0d--%02d-%02d-%02d", - date_time->year, mon_str, date_time->day, - date_time->hour, date_time->min, date_time->sec); - return(result); -} - -internal String8 -string_from_elapsed_time(Arena *arena, DateTime dt){ - Temp scratch = scratch_begin(&arena, 1); - String8List list = {0}; - if (dt.year){ - str8_list_pushf(scratch.arena, &list, "%dy", dt.year); - str8_list_pushf(scratch.arena, &list, "%um", dt.mon); - str8_list_pushf(scratch.arena, &list, "%ud", dt.day); - } else if (dt.mon){ - str8_list_pushf(scratch.arena, &list, "%um", dt.mon); - str8_list_pushf(scratch.arena, &list, "%ud", dt.day); - } else if (dt.day){ - str8_list_pushf(scratch.arena, &list, "%ud", dt.day); - } - str8_list_pushf(scratch.arena, &list, "%u:%u:%u:%u ms", dt.hour, dt.min, dt.sec, dt.msec); - StringJoin join = { str8_lit_comp(""), str8_lit_comp(" "), str8_lit_comp("") }; - String8 result = str8_list_join(arena, &list, &join); - scratch_end(scratch); - return(result); -} - -//////////////////////////////// -//~ rjf: Basic Text Indentation - -internal String8 -indented_from_string(Arena *arena, String8 string) -{ - Temp scratch = scratch_begin(&arena, 1); - read_only local_persist U8 indentation_bytes[] = " "; - String8List indented_strings = {0}; - S64 depth = 0; - S64 next_depth = 0; - U64 line_begin_off = 0; - for(U64 off = 0; off <= string.size; off += 1) - { - U8 byte = off width_this_line){ - String8 line = str8_substr(string, line_range); - if (wrapped_indent_level > 0){ - line = push_str8f(arena, "%.*s%S", wrapped_indent_level, spaces, line); - } - str8_list_push(arena, &list, line); - line_range = r1u64(line_range.max+1, candidate_line_range.max); - wrapped_indent_level = ClampTop(64, wrap_indent); - } - else{ - line_range = candidate_line_range; - } - } - } - if (line_range.min < string.size && line_range.max > line_range.min){ - String8 line = str8_substr(string, line_range); - if (wrapped_indent_level > 0){ - line = push_str8f(arena, "%.*s%S", wrapped_indent_level, spaces, line); - } - str8_list_push(arena, &list, line); - } - return list; -} - -//////////////////////////////// -//~ rjf: String <-> Color - -internal String8 -hex_string_from_rgba_4f32(Arena *arena, Vec4F32 rgba) -{ - String8 hex_string = push_str8f(arena, "%02x%02x%02x%02x", (U8)(rgba.x*255.f), (U8)(rgba.y*255.f), (U8)(rgba.z*255.f), (U8)(rgba.w*255.f)); - return hex_string; -} - -internal Vec4F32 -rgba_from_hex_string_4f32(String8 hex_string) -{ - U8 byte_text[8] = {0}; - U64 byte_text_idx = 0; - for(U64 idx = 0; idx < hex_string.size && byte_text_idx < ArrayCount(byte_text); idx += 1) - { - if(char_is_digit(hex_string.str[idx], 16)) - { - byte_text[byte_text_idx] = char_to_lower(hex_string.str[idx]); - byte_text_idx += 1; - } - } - U8 byte_vals[4] = {0}; - for(U64 idx = 0; idx < 4; idx += 1) - { - byte_vals[idx] = (U8)u64_from_str8(str8(&byte_text[idx*2], 2), 16); - } - Vec4F32 rgba = v4f32(byte_vals[0]/255.f, byte_vals[1]/255.f, byte_vals[2]/255.f, byte_vals[3]/255.f); - return rgba; -} - -//////////////////////////////// -//~ rjf: String Fuzzy Matching - -internal FuzzyMatchRangeList -fuzzy_match_find(Arena *arena, String8 needle, String8 haystack) -{ - FuzzyMatchRangeList result = {0}; - Temp scratch = scratch_begin(&arena, 1); - String8List needles = str8_split(scratch.arena, needle, (U8*)" ", 1, 0); - result.needle_part_count = needles.node_count; - for(String8Node *needle_n = needles.first; needle_n != 0; needle_n = needle_n->next) - { - U64 find_pos = 0; - for(;find_pos < haystack.size;) - { - find_pos = str8_find_needle(haystack, find_pos, needle_n->string, StringMatchFlag_CaseInsensitive); - B32 is_in_gathered_ranges = 0; - for(FuzzyMatchRangeNode *n = result.first; n != 0; n = n->next) - { - if(n->range.min <= find_pos && find_pos < n->range.max) - { - is_in_gathered_ranges = 1; - find_pos = n->range.max; - break; - } - } - if(!is_in_gathered_ranges) - { - break; - } - } - if(find_pos < haystack.size) - { - Rng1U64 range = r1u64(find_pos, find_pos+needle_n->string.size); - FuzzyMatchRangeNode *n = push_array(arena, FuzzyMatchRangeNode, 1); - n->range = range; - SLLQueuePush(result.first, result.last, n); - result.count += 1; - result.total_dim += dim_1u64(range); - } - } - scratch_end(scratch); - return result; -} - -internal FuzzyMatchRangeList -fuzzy_match_range_list_copy(Arena *arena, FuzzyMatchRangeList *src) -{ - FuzzyMatchRangeList dst = {0}; - for(FuzzyMatchRangeNode *src_n = src->first; src_n != 0; src_n = src_n->next) - { - FuzzyMatchRangeNode *dst_n = push_array(arena, FuzzyMatchRangeNode, 1); - SLLQueuePush(dst.first, dst.last, dst_n); - dst_n->range = src_n->range; - } - dst.count = src->count; - dst.needle_part_count = src->needle_part_count; - dst.total_dim = src->total_dim; - return dst; -} - -//////////////////////////////// -//~ NOTE(allen): Serialization Helpers - -internal void -str8_serial_begin(Arena *arena, String8List *srl){ - String8Node *node = push_array(arena, String8Node, 1); - node->string.str = push_array_no_zero(arena, U8, 0); - srl->first = srl->last = node; - srl->node_count = 1; - srl->total_size = 0; -} - -internal String8 -str8_serial_end(Arena *arena, String8List *srl){ - U64 size = srl->total_size; - U8 *out = push_array_no_zero(arena, U8, size); - str8_serial_write_to_dst(srl, out); - String8 result = str8(out, size); - return result; -} - -internal void -str8_serial_write_to_dst(String8List *srl, void *out){ - U8 *ptr = (U8*)out; - for (String8Node *node = srl->first; - node != 0; - node = node->next){ - U64 size = node->string.size; - MemoryCopy(ptr, node->string.str, size); - ptr += size; - } -} - -internal U64 -str8_serial_push_align(Arena *arena, String8List *srl, U64 align){ - Assert(IsPow2(align)); - - U64 pos = srl->total_size; - U64 new_pos = AlignPow2(pos, align); - U64 size = (new_pos - pos); - - if(size != 0) - { - U8 *buf = push_array(arena, U8, size); - - String8 *str = &srl->last->string; - if (str->str + str->size == buf){ - srl->last->string.size += size; - srl->total_size += size; - } - else{ - str8_list_push(arena, srl, str8(buf, size)); - } - } - return size; -} - -internal void * -str8_serial_push_size(Arena *arena, String8List *srl, U64 size) -{ - void *result = 0; - if(size != 0) - { - U8 *buf = push_array_no_zero(arena, U8, size); - String8 *str = &srl->last->string; - if (str->str + str->size == buf){ - srl->last->string.size += size; - srl->total_size += size; - } - else{ - str8_list_push(arena, srl, str8(buf, size)); - } - result = buf; - } - return result; -} - -internal void * -str8_serial_push_data(Arena *arena, String8List *srl, void *data, U64 size){ - void *result = str8_serial_push_size(arena, srl, size); - if(result != 0) - { - MemoryCopy(result, data, size); - } - return result; -} - -internal void -str8_serial_push_data_list(Arena *arena, String8List *srl, String8Node *first){ - for (String8Node *node = first; - node != 0; - node = node->next){ - str8_serial_push_data(arena, srl, node->string.str, node->string.size); - } -} - -internal void -str8_serial_push_u64(Arena *arena, String8List *srl, U64 x){ - U8 *buf = push_array_no_zero(arena, U8, 8); - MemoryCopy(buf, &x, 8); - String8 *str = &srl->last->string; - if (str->str + str->size == buf){ - srl->last->string.size += 8; - srl->total_size += 8; - } - else{ - str8_list_push(arena, srl, str8(buf, 8)); - } -} - -internal void -str8_serial_push_u32(Arena *arena, String8List *srl, U32 x){ - U8 *buf = push_array_no_zero(arena, U8, 4); - MemoryCopy(buf, &x, 4); - String8 *str = &srl->last->string; - if (str->str + str->size == buf){ - srl->last->string.size += 4; - srl->total_size += 4; - } - else{ - str8_list_push(arena, srl, str8(buf, 4)); - } -} - -internal void -str8_serial_push_u16(Arena *arena, String8List *srl, U16 x){ - str8_serial_push_data(arena, srl, &x, sizeof(x)); -} - -internal void -str8_serial_push_u8(Arena *arena, String8List *srl, U8 x){ - str8_serial_push_data(arena, srl, &x, sizeof(x)); -} - -internal void -str8_serial_push_cstr(Arena *arena, String8List *srl, String8 str){ - str8_serial_push_data(arena, srl, str.str, str.size); - str8_serial_push_u8(arena, srl, 0); -} - -internal void -str8_serial_push_string(Arena *arena, String8List *srl, String8 str){ - str8_serial_push_data(arena, srl, str.str, str.size); -} - -//////////////////////////////// -//~ rjf: Deserialization Helpers - -internal U64 -str8_deserial_read(String8 string, U64 off, void *read_dst, U64 read_size, U64 granularity) -{ - U64 bytes_left = string.size-Min(off, string.size); - U64 actually_readable_size = Min(bytes_left, read_size); - U64 legally_readable_size = actually_readable_size - actually_readable_size%granularity; - if(legally_readable_size > 0) - { - MemoryCopy(read_dst, string.str+off, legally_readable_size); - } - return legally_readable_size; -} - -internal U64 -str8_deserial_find_first_match(String8 string, U64 off, U16 scan_val) -{ - U64 cursor = off; - for (;;) { - U16 val = 0; - str8_deserial_read_struct(string, cursor, &val); - if (val == scan_val) { - break; - } - cursor += sizeof(val); - } - return cursor; -} - -internal void * -str8_deserial_get_raw_ptr(String8 string, U64 off, U64 size) -{ - void *raw_ptr = 0; - if (off + size <= string.size) { - raw_ptr = string.str + off; - } - return raw_ptr; -} - -internal U64 -str8_deserial_read_cstr(String8 string, U64 off, String8 *cstr_out) -{ - U64 cstr_size = 0; - if (off < string.size) { - U8 *ptr = string.str + off; - U8 *cap = string.str + string.size; - *cstr_out = str8_cstring_capped(ptr, cap); - cstr_size = (cstr_out->size + 1); - } - return cstr_size; -} - -internal U64 -str8_deserial_read_windows_utf16_string16(String8 string, U64 off, String16 *str_out) -{ - U64 null_off = str8_deserial_find_first_match(string, off, 0); - U64 size = null_off - off; - U16 *str = (U16 *)str8_deserial_get_raw_ptr(string, off, size); - U64 count = size / sizeof(*str); - *str_out = str16(str, count); - - U64 read_size_with_null = size + sizeof(*str); - return read_size_with_null; -} - -internal U64 -str8_deserial_read_block(String8 string, U64 off, U64 size, String8 *block_out) -{ - Rng1U64 range = rng_1u64(off, off + size); - *block_out = str8_substr(string, range); - return block_out->size; -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Third Party Includes + +#if !BUILD_SUPPLEMENTARY_UNIT +# define STB_SPRINTF_IMPLEMENTATION +# define STB_SPRINTF_STATIC +# include "third_party/stb/stb_sprintf.h" +#endif + +//////////////////////////////// +//~ NOTE(allen): String <-> Integer Tables + +read_only global U8 integer_symbols[16] = { + '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F', +}; + +// NOTE(allen): Includes reverses for uppercase and lowercase hex. +read_only global U8 integer_symbol_reverse[128] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +}; + +read_only global U8 base64[64] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '_', '$', +}; + +read_only global U8 base64_reverse[128] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0x00, + 0xFF,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32, + 0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0x3E, + 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18, + 0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0xFF,0xFF,0xFF,0xFF,0xFF, +}; + +//////////////////////////////// +//~ rjf: Character Classification & Conversion Functions + +internal B32 +char_is_space(U8 c){ + return(c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '\f' || c == '\v'); +} + +internal B32 +char_is_upper(U8 c){ + return('A' <= c && c <= 'Z'); +} + +internal B32 +char_is_lower(U8 c){ + return('a' <= c && c <= 'z'); +} + +internal B32 +char_is_alpha(U8 c){ + return(char_is_upper(c) || char_is_lower(c)); +} + +internal B32 +char_is_slash(U8 c){ + return(c == '/' || c == '\\'); +} + +internal B32 +char_is_digit(U8 c, U32 base){ + B32 result = 0; + if (0 < base && base <= 16){ + U8 val = integer_symbol_reverse[c]; + if (val < base){ + result = 1; + } + } + return(result); +} + +internal U8 +char_to_lower(U8 c){ + if (char_is_upper(c)){ + c += ('a' - 'A'); + } + return(c); +} + +internal U8 +char_to_upper(U8 c){ + if (char_is_lower(c)){ + c += ('A' - 'a'); + } + return(c); +} + +internal U8 +char_to_correct_slash(U8 c){ + if(char_is_slash(c)){ + c = '/'; + } + return(c); +} + +//////////////////////////////// +//~ rjf: C-String Measurement + +internal U64 +cstring8_length(U8 *c){ + U8 *p = c; + for (;*p != 0; p += 1); + return(p - c); +} + +internal U64 +cstring16_length(U16 *c){ + U16 *p = c; + for (;*p != 0; p += 1); + return(p - c); +} + +internal U64 +cstring32_length(U32 *c){ + U32 *p = c; + for (;*p != 0; p += 1); + return(p - c); +} + +//////////////////////////////// +//~ rjf: String Constructors + +internal String8 +str8(U8 *str, U64 size){ + String8 result = {str, size}; + return(result); +} + +internal String8 +str8_range(U8 *first, U8 *one_past_last){ + String8 result = {first, (U64)(one_past_last - first)}; + return(result); +} + +internal String8 +str8_zero(void){ + String8 result = {0}; + return(result); +} + +internal String16 +str16(U16 *str, U64 size){ + String16 result = {str, size}; + return(result); +} + +internal String16 +str16_range(U16 *first, U16 *one_past_last){ + String16 result = {first, (U64)(one_past_last - first)}; + return(result); +} + +internal String16 +str16_zero(void){ + String16 result = {0}; + return(result); +} + +internal String32 +str32(U32 *str, U64 size){ + String32 result = {str, size}; + return(result); +} + +internal String32 +str32_range(U32 *first, U32 *one_past_last){ + String32 result = {first, (U64)(one_past_last - first)}; + return(result); +} + +internal String32 +str32_zero(void){ + String32 result = {0}; + return(result); +} + +internal String8 +str8_cstring(char *c){ + String8 result = {(U8*)c, cstring8_length((U8*)c)}; + return(result); +} + +internal String16 +str16_cstring(U16 *c){ + String16 result = {(U16*)c, cstring16_length((U16*)c)}; + return(result); +} + +internal String32 +str32_cstring(U32 *c){ + String32 result = {(U32*)c, cstring32_length((U32*)c)}; + return(result); +} + +internal String8 +str8_cstring_capped(void *cstr, void *cap) +{ + char *ptr = (char*)cstr; + char *opl = (char*)cap; + for (;ptr < opl && *ptr != 0; ptr += 1); + U64 size = (U64)(ptr - (char *)cstr); + String8 result = {(U8*)cstr, size}; + return(result); +} + +//////////////////////////////// +//~ rjf: String Stylization + +internal String8 +upper_from_str8(Arena *arena, String8 string) +{ + string = push_str8_copy(arena, string); + for(U64 idx = 0; idx < string.size; idx += 1) + { + string.str[idx] = char_to_upper(string.str[idx]); + } + return string; +} + +internal String8 +lower_from_str8(Arena *arena, String8 string) +{ + string = push_str8_copy(arena, string); + for(U64 idx = 0; idx < string.size; idx += 1) + { + string.str[idx] = char_to_lower(string.str[idx]); + } + return string; +} + +internal String8 +backslashed_from_str8(Arena *arena, String8 string) +{ + string = push_str8_copy(arena, string); + for(U64 idx = 0; idx < string.size; idx += 1) + { + string.str[idx] = char_is_slash(string.str[idx]) ? '\\' : string.str[idx]; + } + return string; +} + +//////////////////////////////// +//~ rjf: String Matching + +internal B32 +str8_match(String8 a, String8 b, StringMatchFlags flags){ + B32 result = 0; + if (a.size == b.size || (flags & StringMatchFlag_RightSideSloppy)){ + B32 case_insensitive = (flags & StringMatchFlag_CaseInsensitive); + B32 slash_insensitive = (flags & StringMatchFlag_SlashInsensitive); + U64 size = Min(a.size, b.size); + result = 1; + for (U64 i = 0; i < size; i += 1){ + U8 at = a.str[i]; + U8 bt = b.str[i]; + if (case_insensitive){ + at = char_to_upper(at); + bt = char_to_upper(bt); + } + if (slash_insensitive){ + at = char_to_correct_slash(at); + bt = char_to_correct_slash(bt); + } + if (at != bt){ + result = 0; + break; + } + } + } + return(result); +} + +internal U64 +str8_find_needle(String8 string, U64 start_pos, String8 needle, StringMatchFlags flags){ + U8 *p = string.str + start_pos; + U64 stop_offset = Max(string.size + 1, needle.size) - needle.size; + U8 *stop_p = string.str + stop_offset; + if (needle.size > 0){ + U8 *string_opl = string.str + string.size; + String8 needle_tail = str8_skip(needle, 1); + StringMatchFlags adjusted_flags = flags | StringMatchFlag_RightSideSloppy; + U8 needle_first_char_adjusted = needle.str[0]; + if(adjusted_flags & StringMatchFlag_CaseInsensitive){ + needle_first_char_adjusted = char_to_upper(needle_first_char_adjusted); + } + for (;p < stop_p; p += 1){ + U8 haystack_char_adjusted = *p; + if(adjusted_flags & StringMatchFlag_CaseInsensitive){ + haystack_char_adjusted = char_to_upper(haystack_char_adjusted); + } + if (haystack_char_adjusted == needle_first_char_adjusted){ + if (str8_match(str8_range(p + 1, string_opl), needle_tail, adjusted_flags)){ + break; + } + } + } + } + U64 result = string.size; + if (p < stop_p){ + result = (U64)(p - string.str); + } + return(result); +} + +internal B32 +str8_ends_with(String8 string, String8 end, StringMatchFlags flags){ + String8 postfix = str8_postfix(string, end.size); + B32 is_match = str8_match(end, postfix, flags); + return is_match; +} + +//////////////////////////////// +//~ rjf: String Slicing + +internal String8 +str8_substr(String8 str, Rng1U64 range){ + range.min = ClampTop(range.min, str.size); + range.max = ClampTop(range.max, str.size); + str.str += range.min; + str.size = dim_1u64(range); + return(str); +} + +internal String8 +str8_prefix(String8 str, U64 size){ + str.size = ClampTop(size, str.size); + return(str); +} + +internal String8 +str8_skip(String8 str, U64 amt){ + amt = ClampTop(amt, str.size); + str.str += amt; + str.size -= amt; + return(str); +} + +internal String8 +str8_postfix(String8 str, U64 size){ + size = ClampTop(size, str.size); + str.str = (str.str + str.size) - size; + str.size = size; + return(str); +} + +internal String8 +str8_chop(String8 str, U64 amt){ + amt = ClampTop(amt, str.size); + str.size -= amt; + return(str); +} + +internal String8 +str8_skip_chop_whitespace(String8 string){ + U8 *first = string.str; + U8 *opl = first + string.size; + for (;first < opl; first += 1){ + if (!char_is_space(*first)){ + break; + } + } + for (;opl > first;){ + opl -= 1; + if (!char_is_space(*opl)){ + opl += 1; + break; + } + } + String8 result = str8_range(first, opl); + return(result); +} + +//////////////////////////////// +//~ rjf: String Formatting & Copying + +internal String8 +push_str8_cat(Arena *arena, String8 s1, String8 s2){ + String8 str; + str.size = s1.size + s2.size; + str.str = push_array_no_zero(arena, U8, str.size + 1); + MemoryCopy(str.str, s1.str, s1.size); + MemoryCopy(str.str + s1.size, s2.str, s2.size); + str.str[str.size] = 0; + return(str); +} + +internal String8 +push_str8_copy(Arena *arena, String8 s){ + String8 str; + str.size = s.size; + str.str = push_array_no_zero(arena, U8, str.size + 1); + MemoryCopy(str.str, s.str, s.size); + str.str[str.size] = 0; + return(str); +} + +internal String8 +push_str8fv(Arena *arena, char *fmt, va_list args){ + va_list args2; + va_copy(args2, args); + U32 needed_bytes = raddbg_vsnprintf(0, 0, fmt, args) + 1; + String8 result = {0}; + result.str = push_array_no_zero(arena, U8, needed_bytes); + result.size = raddbg_vsnprintf((char*)result.str, needed_bytes, fmt, args2); + result.str[result.size] = 0; + va_end(args2); + return(result); +} + +internal String8 +push_str8f(Arena *arena, char *fmt, ...){ + va_list args; + va_start(args, fmt); + String8 result = push_str8fv(arena, fmt, args); + va_end(args); + return(result); +} + +//////////////////////////////// +//~ rjf: String <=> Integer Conversions + +//- rjf: string -> integer + +internal S64 +sign_from_str8(String8 string, String8 *string_tail){ + // count negative signs + U64 neg_count = 0; + U64 i = 0; + for (; i < string.size; i += 1){ + if (string.str[i] == '-'){ + neg_count += 1; + } + else if (string.str[i] != '+'){ + break; + } + } + + // output part of string after signs + *string_tail = str8_skip(string, i); + + // output integer sign + S64 sign = (neg_count & 1)?-1:+1; + return(sign); +} + +internal B32 +str8_is_integer(String8 string, U32 radix){ + B32 result = 0; + if (string.size > 0){ + if (1 < radix && radix <= 16){ + result = 1; + for (U64 i = 0; i < string.size; i += 1){ + U8 c = string.str[i]; + if (!(c < 0x80) || integer_symbol_reverse[c] >= radix){ + result = 0; + break; + } + } + } + } + return(result); +} + +internal U64 +u64_from_str8(String8 string, U32 radix){ + U64 x = 0; + if (1 < radix && radix <= 16){ + for (U64 i = 0; i < string.size; i += 1){ + x *= radix; + x += integer_symbol_reverse[string.str[i]&0x7F]; + } + } + return(x); +} + +internal S64 +s64_from_str8(String8 string, U32 radix){ + S64 sign = sign_from_str8(string, &string); + S64 x = (S64)u64_from_str8(string, radix) * sign; + return(x); +} + +internal B32 +try_u64_from_str8_c_rules(String8 string, U64 *x){ + B32 is_integer = 0; + if (str8_is_integer(string, 10)){ + is_integer = 1; + *x = u64_from_str8(string, 10); + } + else{ + String8 hex_string = str8_skip(string, 2); + if (str8_match(str8_prefix(string, 2), str8_lit("0x"), 0) && + str8_is_integer(hex_string, 0x10)){ + is_integer = 1; + *x = u64_from_str8(hex_string, 0x10); + } + else if (str8_match(str8_prefix(string, 2), str8_lit("0b"), 0) && + str8_is_integer(hex_string, 2)){ + is_integer = 1; + *x = u64_from_str8(hex_string, 2); + } + else{ + String8 oct_string = str8_skip(string, 1); + if (str8_match(str8_prefix(string, 1), str8_lit("0"), 0) && + str8_is_integer(hex_string, 010)){ + is_integer = 1; + *x = u64_from_str8(oct_string, 010); + } + } + } + return(is_integer); +} + +internal B32 +try_s64_from_str8_c_rules(String8 string, S64 *x){ + String8 string_tail = {0}; + S64 sign = sign_from_str8(string, &string_tail); + U64 x_u64 = 0; + B32 is_integer = try_u64_from_str8_c_rules(string_tail, &x_u64); + *x = x_u64*sign; + return(is_integer); +} + +//- rjf: integer -> string + +internal String8 +str8_from_memory_size(Arena *arena, U64 z){ + String8 result = {0}; + if (z < KB(1)){ + result = push_str8f(arena, "%llu b", z); + } + else if (z < MB(1)){ + result = push_str8f(arena, "%llu.%02llu Kb", z/KB(1), ((100*z)/KB(1))%100); + } + else if (z < GB(1)){ + result = push_str8f(arena, "%llu.%02llu Mb", z/MB(1), ((100*z)/MB(1))%100); + } + else{ + result = push_str8f(arena, "%llu.%02llu Gb", z/GB(1), ((100*z)/GB(1))%100); + } + return(result); +} + +internal String8 +str8_from_u64(Arena *arena, U64 u64, U32 radix, U8 min_digits, U8 digit_group_separator) +{ + String8 result = {0}; + { + // rjf: prefix + String8 prefix = {0}; + switch(radix) + { + case 16:{prefix = str8_lit("0x");}break; + case 8: {prefix = str8_lit("0o");}break; + case 2: {prefix = str8_lit("0b");}break; + } + + // rjf: determine # of chars between separators + U8 digit_group_size = 3; + switch(radix) + { + default:break; + case 2: + case 8: + case 16: + {digit_group_size = 4;}break; + } + + // rjf: prep + U64 needed_leading_0s = 0; + { + U64 needed_digits = 1; + { + U64 u64_reduce = u64; + for(;;) + { + u64_reduce /= radix; + if(u64_reduce == 0) + { + break; + } + needed_digits += 1; + } + } + needed_leading_0s = (min_digits > needed_digits) ? min_digits - needed_digits : 0; + U64 needed_separators = 0; + if(digit_group_separator != 0) + { + needed_separators = (needed_digits+needed_leading_0s)/digit_group_size; + if(needed_separators > 0 && (needed_digits+needed_leading_0s)%digit_group_size == 0) + { + needed_separators -= 1; + } + } + result.size = prefix.size + needed_leading_0s + needed_separators + needed_digits; + result.str = push_array_no_zero(arena, U8, result.size + 1); + result.str[result.size] = 0; + } + + // rjf: fill contents + { + U64 u64_reduce = u64; + U64 digits_until_separator = digit_group_size; + for(U64 idx = 0; idx < result.size; idx += 1) + { + if(digits_until_separator == 0 && digit_group_separator != 0) + { + result.str[result.size - idx - 1] = digit_group_separator; + digits_until_separator = digit_group_size+1; + } + else + { + result.str[result.size - idx - 1] = char_to_lower(integer_symbols[u64_reduce%radix]); + u64_reduce /= radix; + } + digits_until_separator -= 1; + if(u64_reduce == 0) + { + break; + } + } + for(U64 leading_0_idx = 0; leading_0_idx < needed_leading_0s; leading_0_idx += 1) + { + result.str[prefix.size + leading_0_idx] = '0'; + } + } + + // rjf: fill prefix + if(prefix.size != 0) + { + MemoryCopy(result.str, prefix.str, prefix.size); + } + } + return result; +} + +internal String8 +str8_from_s64(Arena *arena, S64 s64, U32 radix, U8 min_digits, U8 digit_group_separator) +{ + String8 result = {0}; + // TODO(rjf): preeeeetty sloppy... + if(s64 < 0) + { + Temp scratch = scratch_begin(&arena, 1); + String8 numeric_part = str8_from_u64(scratch.arena, (U64)(-s64), radix, min_digits, digit_group_separator); + result = push_str8f(arena, "-%S", numeric_part); + scratch_end(scratch); + } + else + { + result = str8_from_u64(arena, (U64)s64, radix, min_digits, digit_group_separator); + } + return result; +} + +//////////////////////////////// +//~ rjf: String <=> Float Conversions + +internal F64 +f64_from_str8(String8 string) +{ + // TODO(rjf): crappy implementation for now that just uses atof. + F64 result = 0; + if(string.size > 0) + { + // rjf: find starting pos of numeric string, as well as sign + F64 sign = +1.0; + //U64 first_numeric = 0; + if(string.str[0] == '-') + { + //first_numeric = 1; + sign = -1.0; + } + else if(string.str[0] == '+') + { + //first_numeric = 1; + sign = 1.0; + } + + // rjf: gather numerics + U64 num_valid_chars = 0; + char buffer[64]; + for(U64 idx = 0; idx < string.size && num_valid_chars < sizeof(buffer)-1; idx += 1) + { + if(char_is_digit(string.str[idx], 10) || string.str[idx] == '.') + { + buffer[num_valid_chars] = string.str[idx]; + num_valid_chars += 1; + } + } + + // rjf: null-terminate (the reason for all of this!!!!!!) + buffer[num_valid_chars] = 0; + + // rjf: do final conversion + result = sign * atof(buffer); + } + return result; +} + +//////////////////////////////// +//~ rjf: String List Construction Functions + +internal String8Node* +str8_list_push_node(String8List *list, String8Node *node){ + SLLQueuePush(list->first, list->last, node); + list->node_count += 1; + list->total_size += node->string.size; + return(node); +} + +internal String8Node* +str8_list_push_node_set_string(String8List *list, String8Node *node, String8 string){ + SLLQueuePush(list->first, list->last, node); + list->node_count += 1; + list->total_size += string.size; + node->string = string; + return(node); +} + +internal String8Node* +str8_list_push_node_front(String8List *list, String8Node *node){ + SLLQueuePushFront(list->first, list->last, node); + list->node_count += 1; + list->total_size += node->string.size; + return(node); +} + +internal String8Node* +str8_list_push_node_front_set_string(String8List *list, String8Node *node, String8 string){ + SLLQueuePushFront(list->first, list->last, node); + list->node_count += 1; + list->total_size += string.size; + node->string = string; + return(node); +} + +internal String8Node* +str8_list_push(Arena *arena, String8List *list, String8 string){ + String8Node *node = push_array_no_zero(arena, String8Node, 1); + str8_list_push_node_set_string(list, node, string); + return(node); +} + +internal String8Node* +str8_list_push_front(Arena *arena, String8List *list, String8 string){ + String8Node *node = push_array_no_zero(arena, String8Node, 1); + str8_list_push_node_front_set_string(list, node, string); + return(node); +} + +internal void +str8_list_concat_in_place(String8List *list, String8List *to_push){ + if(to_push->node_count != 0){ + if (list->last){ + list->node_count += to_push->node_count; + list->total_size += to_push->total_size; + list->last->next = to_push->first; + list->last = to_push->last; + } + else{ + *list = *to_push; + } + MemoryZeroStruct(to_push); + } +} + +internal String8Node* +str8_list_push_aligner(Arena *arena, String8List *list, U64 min, U64 align){ + String8Node *node = push_array_no_zero(arena, String8Node, 1); + U64 new_size = list->total_size + min; + U64 increase_size = 0; + if (align > 1){ + // NOTE(allen): assert is power of 2 + Assert(((align - 1) & align) == 0); + U64 mask = align - 1; + new_size += mask; + new_size &= (~mask); + increase_size = new_size - list->total_size; + } + local_persist const U8 zeroes_buffer[64] = {0}; + Assert(increase_size <= ArrayCount(zeroes_buffer)); + SLLQueuePush(list->first, list->last, node); + list->node_count += 1; + list->total_size = new_size; + node->string.str = (U8*)zeroes_buffer; + node->string.size = increase_size; + return(node); +} + +internal String8Node* +str8_list_pushf(Arena *arena, String8List *list, char *fmt, ...){ + va_list args; + va_start(args, fmt); + String8 string = push_str8fv(arena, fmt, args); + String8Node *result = str8_list_push(arena, list, string); + va_end(args); + return(result); +} + +internal String8Node* +str8_list_push_frontf(Arena *arena, String8List *list, char *fmt, ...){ + va_list args; + va_start(args, fmt); + String8 string = push_str8fv(arena, fmt, args); + String8Node *result = str8_list_push_front(arena, list, string); + va_end(args); + return(result); +} + +internal String8List +str8_list_copy(Arena *arena, String8List *list){ + String8List result = {0}; + for (String8Node *node = list->first; + node != 0; + node = node->next){ + String8Node *new_node = push_array_no_zero(arena, String8Node, 1); + String8 new_string = push_str8_copy(arena, node->string); + str8_list_push_node_set_string(&result, new_node, new_string); + } + return(result); +} + +internal String8List +str8_split(Arena *arena, String8 string, U8 *split_chars, U64 split_char_count, StringSplitFlags flags){ + String8List list = {0}; + + B32 keep_empties = (flags & StringSplitFlag_KeepEmpties); + + U8 *ptr = string.str; + U8 *opl = string.str + string.size; + for (;ptr < opl;){ + U8 *first = ptr; + for (;ptr < opl; ptr += 1){ + U8 c = *ptr; + B32 is_split = 0; + for (U64 i = 0; i < split_char_count; i += 1){ + if (split_chars[i] == c){ + is_split = 1; + break; + } + } + if (is_split){ + break; + } + } + + String8 string = str8_range(first, ptr); + if (keep_empties || string.size > 0){ + str8_list_push(arena, &list, string); + } + ptr += 1; + } + + return(list); +} + +internal String8List +str8_split_by_string_chars(Arena *arena, String8 string, String8 split_chars, StringSplitFlags flags){ + String8List list = str8_split(arena, string, split_chars.str, split_chars.size, flags); + return list; +} + +internal String8List +str8_list_split_by_string_chars(Arena *arena, String8List list, String8 split_chars, StringSplitFlags flags){ + String8List result = {0}; + for (String8Node *node = list.first; node != 0; node = node->next){ + String8List split = str8_split_by_string_chars(arena, node->string, split_chars, flags); + str8_list_concat_in_place(&result, &split); + } + return result; +} + +internal String8 +str8_list_join(Arena *arena, String8List *list, StringJoin *optional_params){ + StringJoin join = {0}; + if (optional_params != 0){ + MemoryCopyStruct(&join, optional_params); + } + + U64 sep_count = 0; + if (list->node_count > 0){ + sep_count = list->node_count - 1; + } + + String8 result; + result.size = join.pre.size + join.post.size + sep_count*join.sep.size + list->total_size; + U8 *ptr = result.str = push_array_no_zero(arena, U8, result.size + 1); + + MemoryCopy(ptr, join.pre.str, join.pre.size); + ptr += join.pre.size; + for (String8Node *node = list->first; + node != 0; + node = node->next){ + MemoryCopy(ptr, node->string.str, node->string.size); + ptr += node->string.size; + if (node->next != 0){ + MemoryCopy(ptr, join.sep.str, join.sep.size); + ptr += join.sep.size; + } + } + MemoryCopy(ptr, join.post.str, join.post.size); + ptr += join.post.size; + + *ptr = 0; + + return(result); +} + +internal void +str8_list_from_flags(Arena *arena, String8List *list, + U32 flags, String8 *flag_string_table, U32 flag_string_count){ + for (U32 i = 0; i < flag_string_count; i += 1){ + U32 flag = (1 << i); + if (flags & flag){ + str8_list_push(arena, list, flag_string_table[i]); + } + } +} + +//////////////////////////////// +//~ rjf; String Arrays + +internal String8Array +str8_array_from_list(Arena *arena, String8List *list) +{ + String8Array array; + array.count = list->node_count; + array.v = push_array_no_zero(arena, String8, array.count); + U64 idx = 0; + for(String8Node *n = list->first; n != 0; n = n->next, idx += 1) + { + array.v[idx] = n->string; + } + return array; +} + +internal String8Array +str8_array_reserve(Arena *arena, U64 count) +{ + String8Array arr; + arr.count = 0; + arr.v = push_array(arena, String8, count); + return arr; +} + +//////////////////////////////// +//~ rjf: String Path Helpers + +internal String8 +str8_chop_last_slash(String8 string){ + if (string.size > 0){ + U8 *ptr = string.str + string.size - 1; + for (;ptr >= string.str; ptr -= 1){ + if (*ptr == '/' || *ptr == '\\'){ + break; + } + } + if (ptr >= string.str){ + string.size = (U64)(ptr - string.str); + } + else{ + string.size = 0; + } + } + return(string); +} + +internal String8 +str8_skip_last_slash(String8 string){ + if (string.size > 0){ + U8 *ptr = string.str + string.size - 1; + for (;ptr >= string.str; ptr -= 1){ + if (*ptr == '/' || *ptr == '\\'){ + break; + } + } + if (ptr >= string.str){ + ptr += 1; + string.size = (U64)(string.str + string.size - ptr); + string.str = ptr; + } + } + return(string); +} + +internal String8 +str8_chop_last_dot(String8 string) +{ + String8 result = string; + U64 p = string.size; + for (;p > 0;){ + p -= 1; + if (string.str[p] == '.'){ + result = str8_prefix(string, p); + break; + } + } + return(result); +} + +internal String8 +str8_skip_last_dot(String8 string){ + String8 result = string; + U64 p = string.size; + for (;p > 0;){ + p -= 1; + if (string.str[p] == '.'){ + result = str8_skip(string, p + 1); + break; + } + } + return(result); +} + +internal PathStyle +path_style_from_str8(String8 string){ + PathStyle result = PathStyle_Relative; + if (string.size >= 1 && string.str[0] == '/'){ + result = PathStyle_UnixAbsolute; + } + else if (string.size >= 2 && + char_is_alpha(string.str[0]) && + string.str[1] == ':'){ + if (string.size == 2 || + char_is_slash(string.str[2])){ + result = PathStyle_WindowsAbsolute; + } + } + return(result); +} + +internal String8List +str8_split_path(Arena *arena, String8 string){ + String8List result = str8_split(arena, string, (U8*)"/\\", 2, 0); + return(result); +} + +internal void +str8_path_list_resolve_dots_in_place(String8List *path, PathStyle style){ + Temp scratch = scratch_begin(0, 0); + + String8MetaNode *stack = 0; + String8MetaNode *free_meta_node = 0; + String8Node *first = path->first; + + MemoryZeroStruct(path); + for (String8Node *node = first, *next = 0; + node != 0; + node = next){ + // save next now + next = node->next; + + // cases: + if (node == first && style == PathStyle_WindowsAbsolute){ + goto save_without_stack; + } + if (node->string.size == 1 && node->string.str[0] == '.'){ + goto do_nothing; + } + if (node->string.size == 2 && node->string.str[0] == '.' && node->string.str[1] == '.'){ + if (stack != 0){ + goto eliminate_stack_top; + } + else{ + goto save_without_stack; + } + } + goto save_with_stack; + + + // handlers: + save_with_stack: + { + str8_list_push_node(path, node); + + String8MetaNode *stack_node = free_meta_node; + if (stack_node != 0){ + SLLStackPop(free_meta_node); + } + else{ + stack_node = push_array_no_zero(scratch.arena, String8MetaNode, 1); + } + SLLStackPush(stack, stack_node); + stack_node->node = node; + + continue; + } + + save_without_stack: + { + str8_list_push_node(path, node); + + continue; + } + + eliminate_stack_top: + { + path->node_count -= 1; + path->total_size -= stack->node->string.size; + + SLLStackPop(stack); + + if (stack == 0){ + path->last = path->first; + } + else{ + path->last = stack->node; + } + continue; + } + + do_nothing: continue; + } + scratch_end(scratch); +} + +internal String8 +str8_path_list_join_by_style(Arena *arena, String8List *path, PathStyle style){ + StringJoin params = {0}; + switch (style){ + case PathStyle_Relative: + case PathStyle_WindowsAbsolute: + { + params.sep = str8_lit("/"); + }break; + + case PathStyle_UnixAbsolute: + { + params.pre = str8_lit("/"); + params.sep = str8_lit("/"); + }break; + } + + String8 result = str8_list_join(arena, path, ¶ms); + return(result); +} + +internal String8TxtPtPair +str8_txt_pt_pair_from_string(String8 string) +{ + String8TxtPtPair pair = {0}; + { + String8 file_part = {0}; + String8 line_part = {0}; + String8 col_part = {0}; + + // rjf: grab file part + for(U64 idx = 0; idx <= string.size; idx += 1) + { + U8 byte = (idx < string.size) ? (string.str[idx]) : 0; + U8 next_byte = ((idx+1 < string.size) ? (string.str[idx+1]) : 0); + if(byte == ':' && next_byte != '/' && next_byte != '\\') + { + file_part = str8_prefix(string, idx); + line_part = str8_skip(string, idx+1); + break; + } + else if(byte == 0) + { + file_part = string; + break; + } + } + + // rjf: grab line/column + { + U64 colon_pos = str8_find_needle(line_part, 0, str8_lit(":"), 0); + if(colon_pos < line_part.size) + { + col_part = str8_skip(line_part, colon_pos+1); + line_part = str8_prefix(line_part, colon_pos); + } + } + + // rjf: convert line/column strings to numerics + U64 line = 0; + U64 column = 0; + try_u64_from_str8_c_rules(line_part, &line); + try_u64_from_str8_c_rules(col_part, &column); + + // rjf: fill + pair.string = file_part; + pair.pt = txt_pt((S64)line, (S64)column); + if(pair.pt.line == 0) { pair.pt.line = 1; } + if(pair.pt.column == 0) { pair.pt.column = 1; } + } + return pair; +} + +//////////////////////////////// +//~ rjf: UTF-8 & UTF-16 Decoding/Encoding + +read_only global U8 utf8_class[32] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,2,2,2,2,3,3,4,5, +}; + +internal UnicodeDecode +utf8_decode(U8 *str, U64 max){ + UnicodeDecode result = {1, max_U32}; + U8 byte = str[0]; + U8 byte_class = utf8_class[byte >> 3]; + switch (byte_class) + { + case 1: + { + result.codepoint = byte; + }break; + case 2: + { + if (2 < max) + { + U8 cont_byte = str[1]; + if (utf8_class[cont_byte >> 3] == 0) + { + result.codepoint = (byte & bitmask5) << 6; + result.codepoint |= (cont_byte & bitmask6); + result.inc = 2; + } + } + }break; + case 3: + { + if (2 < max) + { + U8 cont_byte[2] = {str[1], str[2]}; + if (utf8_class[cont_byte[0] >> 3] == 0 && + utf8_class[cont_byte[1] >> 3] == 0) + { + result.codepoint = (byte & bitmask4) << 12; + result.codepoint |= ((cont_byte[0] & bitmask6) << 6); + result.codepoint |= (cont_byte[1] & bitmask6); + result.inc = 3; + } + } + }break; + case 4: + { + if (3 < max) + { + U8 cont_byte[3] = {str[1], str[2], str[3]}; + if (utf8_class[cont_byte[0] >> 3] == 0 && + utf8_class[cont_byte[1] >> 3] == 0 && + utf8_class[cont_byte[2] >> 3] == 0) + { + result.codepoint = (byte & bitmask3) << 18; + result.codepoint |= ((cont_byte[0] & bitmask6) << 12); + result.codepoint |= ((cont_byte[1] & bitmask6) << 6); + result.codepoint |= (cont_byte[2] & bitmask6); + result.inc = 4; + } + } + } + } + return(result); +} + +internal UnicodeDecode +utf16_decode(U16 *str, U64 max){ + UnicodeDecode result = {1, max_U32}; + result.codepoint = str[0]; + result.inc = 1; + if (max > 1 && 0xD800 <= str[0] && str[0] < 0xDC00 && 0xDC00 <= str[1] && str[1] < 0xE000){ + result.codepoint = ((str[0] - 0xD800) << 10) | (str[1] - 0xDC00) + 0x10000; + result.inc = 2; + } + return(result); +} + +internal U32 +utf8_encode(U8 *str, U32 codepoint){ + U32 inc = 0; + if (codepoint <= 0x7F){ + str[0] = (U8)codepoint; + inc = 1; + } + else if (codepoint <= 0x7FF){ + str[0] = (bitmask2 << 6) | ((codepoint >> 6) & bitmask5); + str[1] = bit8 | (codepoint & bitmask6); + inc = 2; + } + else if (codepoint <= 0xFFFF){ + str[0] = (bitmask3 << 5) | ((codepoint >> 12) & bitmask4); + str[1] = bit8 | ((codepoint >> 6) & bitmask6); + str[2] = bit8 | ( codepoint & bitmask6); + inc = 3; + } + else if (codepoint <= 0x10FFFF){ + str[0] = (bitmask4 << 4) | ((codepoint >> 18) & bitmask3); + str[1] = bit8 | ((codepoint >> 12) & bitmask6); + str[2] = bit8 | ((codepoint >> 6) & bitmask6); + str[3] = bit8 | ( codepoint & bitmask6); + inc = 4; + } + else{ + str[0] = '?'; + inc = 1; + } + return(inc); +} + +internal U32 +utf16_encode(U16 *str, U32 codepoint){ + U32 inc = 1; + if (codepoint == max_U32){ + str[0] = (U16)'?'; + } + else if (codepoint < 0x10000){ + str[0] = (U16)codepoint; + } + else{ + U32 v = codepoint - 0x10000; + str[0] = safe_cast_u16(0xD800 + (v >> 10)); + str[1] = safe_cast_u16(0xDC00 + (v & bitmask10)); + inc = 2; + } + return(inc); +} + +internal U32 +utf8_from_utf32_single(U8 *buffer, U32 character){ + return(utf8_encode(buffer, character)); +} + +//////////////////////////////// +//~ rjf: Unicode String Conversions + +internal String8 +str8_from_16(Arena *arena, String16 in){ + U64 cap = in.size*3; + U8 *str = push_array_no_zero(arena, U8, cap + 1); + U16 *ptr = in.str; + U16 *opl = ptr + in.size; + U64 size = 0; + UnicodeDecode consume; + for (;ptr < opl; ptr += consume.inc){ + consume = utf16_decode(ptr, opl - ptr); + size += utf8_encode(str + size, consume.codepoint); + } + str[size] = 0; + arena_pop(arena, (cap - size)); + return(str8(str, size)); +} + +internal String16 +str16_from_8(Arena *arena, String8 in){ + U64 cap = in.size*2; + U16 *str = push_array_no_zero(arena, U16, cap + 1); + U8 *ptr = in.str; + U8 *opl = ptr + in.size; + U64 size = 0; + UnicodeDecode consume; + for (;ptr < opl; ptr += consume.inc){ + consume = utf8_decode(ptr, opl - ptr); + size += utf16_encode(str + size, consume.codepoint); + } + str[size] = 0; + arena_pop(arena, (cap - size)*2); + return(str16(str, size)); +} + +internal String8 +str8_from_32(Arena *arena, String32 in){ + U64 cap = in.size*4; + U8 *str = push_array_no_zero(arena, U8, cap + 1); + U32 *ptr = in.str; + U32 *opl = ptr + in.size; + U64 size = 0; + for (;ptr < opl; ptr += 1){ + size += utf8_encode(str + size, *ptr); + } + str[size] = 0; + arena_pop(arena, (cap - size)); + return(str8(str, size)); +} + +internal String32 +str32_from_8(Arena *arena, String8 in){ + U64 cap = in.size; + U32 *str = push_array_no_zero(arena, U32, cap + 1); + U8 *ptr = in.str; + U8 *opl = ptr + in.size; + U64 size = 0; + UnicodeDecode consume; + for (;ptr < opl; ptr += consume.inc){ + consume = utf8_decode(ptr, opl - ptr); + str[size] = consume.codepoint; + size += 1; + } + str[size] = 0; + arena_pop(arena, (cap - size)*4); + return(str32(str, size)); +} + +//////////////////////////////// +//~ rjf: Basic Types & Space Enum -> String Conversions + +internal String8 +string_from_dimension(Dimension dimension){ + local_persist String8 strings[] = { + str8_lit_comp("X"), + str8_lit_comp("Y"), + str8_lit_comp("Z"), + str8_lit_comp("W"), + }; + String8 result = str8_lit("error"); + if ((U32)dimension < 4){ + result = strings[dimension]; + } + return(result); +} + +internal String8 +string_from_side(Side side){ + local_persist String8 strings[] = { + str8_lit_comp("Min"), + str8_lit_comp("Max"), + }; + String8 result = str8_lit("error"); + if ((U32)side < 2){ + result = strings[side]; + } + return(result); +} + +internal String8 +string_from_operating_system(OperatingSystem os){ + local_persist String8 strings[] = { + str8_lit_comp("Null"), + str8_lit_comp("Windows"), + str8_lit_comp("Linux"), + str8_lit_comp("Mac"), + }; + String8 result = str8_lit("error"); + if (os < OperatingSystem_COUNT){ + result = strings[os]; + } + return(result); +} + +internal String8 +string_from_architecture(Architecture arch){ + local_persist String8 strings[] = { + str8_lit_comp("Null"), + str8_lit_comp("x64"), + str8_lit_comp("x86"), + str8_lit_comp("arm64"), + str8_lit_comp("arm32"), + }; + String8 result = str8_lit("error"); + if (arch < Architecture_COUNT){ + result = strings[arch]; + } + return(result); +} + +//////////////////////////////// +//~ rjf: Time Types -> String + +internal String8 +string_from_week_day(WeekDay week_day){ + local_persist String8 strings[] = { + str8_lit_comp("Sun"), + str8_lit_comp("Mon"), + str8_lit_comp("Tue"), + str8_lit_comp("Wed"), + str8_lit_comp("Thu"), + str8_lit_comp("Fri"), + str8_lit_comp("Sat"), + }; + String8 result = str8_lit("Err"); + if ((U32)week_day < WeekDay_COUNT){ + result = strings[week_day]; + } + return(result); +} + +internal String8 +string_from_month(Month month){ + local_persist String8 strings[] = { + str8_lit_comp("Jan"), + str8_lit_comp("Feb"), + str8_lit_comp("Mar"), + str8_lit_comp("Apr"), + str8_lit_comp("May"), + str8_lit_comp("Jun"), + str8_lit_comp("Jul"), + str8_lit_comp("Aug"), + str8_lit_comp("Sep"), + str8_lit_comp("Oct"), + str8_lit_comp("Nov"), + str8_lit_comp("Dec"), + }; + String8 result = str8_lit("Err"); + if ((U32)month < Month_COUNT){ + result = strings[month]; + } + return(result); +} + +internal String8 +push_date_time_string(Arena *arena, DateTime *date_time){ + char *mon_str = (char*)string_from_month(date_time->month).str; + U32 adjusted_hour = date_time->hour%12; + if (adjusted_hour == 0){ + adjusted_hour = 12; + } + char *ampm = "am"; + if (date_time->hour >= 12){ + ampm = "pm"; + } + String8 result = push_str8f(arena, "%d %s %d, %02d:%02d:%02d %s", + date_time->day, mon_str, date_time->year, + adjusted_hour, date_time->min, date_time->sec, ampm); + return(result); +} + +internal String8 +push_file_name_date_time_string(Arena *arena, DateTime *date_time){ + char *mon_str = (char*)string_from_month(date_time->month).str; + String8 result = push_str8f(arena, "%d-%s-%0d--%02d-%02d-%02d", + date_time->year, mon_str, date_time->day, + date_time->hour, date_time->min, date_time->sec); + return(result); +} + +internal String8 +string_from_elapsed_time(Arena *arena, DateTime dt){ + Temp scratch = scratch_begin(&arena, 1); + String8List list = {0}; + if (dt.year){ + str8_list_pushf(scratch.arena, &list, "%dy", dt.year); + str8_list_pushf(scratch.arena, &list, "%um", dt.mon); + str8_list_pushf(scratch.arena, &list, "%ud", dt.day); + } else if (dt.mon){ + str8_list_pushf(scratch.arena, &list, "%um", dt.mon); + str8_list_pushf(scratch.arena, &list, "%ud", dt.day); + } else if (dt.day){ + str8_list_pushf(scratch.arena, &list, "%ud", dt.day); + } + str8_list_pushf(scratch.arena, &list, "%u:%u:%u:%u ms", dt.hour, dt.min, dt.sec, dt.msec); + StringJoin join = { str8_lit_comp(""), str8_lit_comp(" "), str8_lit_comp("") }; + String8 result = str8_list_join(arena, &list, &join); + scratch_end(scratch); + return(result); +} + +//////////////////////////////// +//~ rjf: Basic Text Indentation + +internal String8 +indented_from_string(Arena *arena, String8 string) +{ + Temp scratch = scratch_begin(&arena, 1); + read_only local_persist U8 indentation_bytes[] = " "; + String8List indented_strings = {0}; + S64 depth = 0; + S64 next_depth = 0; + U64 line_begin_off = 0; + for(U64 off = 0; off <= string.size; off += 1) + { + U8 byte = off width_this_line){ + String8 line = str8_substr(string, line_range); + if (wrapped_indent_level > 0){ + line = push_str8f(arena, "%.*s%S", wrapped_indent_level, spaces, line); + } + str8_list_push(arena, &list, line); + line_range = r1u64(line_range.max+1, candidate_line_range.max); + wrapped_indent_level = ClampTop(64, wrap_indent); + } + else{ + line_range = candidate_line_range; + } + } + } + if (line_range.min < string.size && line_range.max > line_range.min){ + String8 line = str8_substr(string, line_range); + if (wrapped_indent_level > 0){ + line = push_str8f(arena, "%.*s%S", wrapped_indent_level, spaces, line); + } + str8_list_push(arena, &list, line); + } + return list; +} + +//////////////////////////////// +//~ rjf: String <-> Color + +internal String8 +hex_string_from_rgba_4f32(Arena *arena, Vec4F32 rgba) +{ + String8 hex_string = push_str8f(arena, "%02x%02x%02x%02x", (U8)(rgba.x*255.f), (U8)(rgba.y*255.f), (U8)(rgba.z*255.f), (U8)(rgba.w*255.f)); + return hex_string; +} + +internal Vec4F32 +rgba_from_hex_string_4f32(String8 hex_string) +{ + U8 byte_text[8] = {0}; + U64 byte_text_idx = 0; + for(U64 idx = 0; idx < hex_string.size && byte_text_idx < ArrayCount(byte_text); idx += 1) + { + if(char_is_digit(hex_string.str[idx], 16)) + { + byte_text[byte_text_idx] = char_to_lower(hex_string.str[idx]); + byte_text_idx += 1; + } + } + U8 byte_vals[4] = {0}; + for(U64 idx = 0; idx < 4; idx += 1) + { + byte_vals[idx] = (U8)u64_from_str8(str8(&byte_text[idx*2], 2), 16); + } + Vec4F32 rgba = v4f32(byte_vals[0]/255.f, byte_vals[1]/255.f, byte_vals[2]/255.f, byte_vals[3]/255.f); + return rgba; +} + +//////////////////////////////// +//~ rjf: String Fuzzy Matching + +internal FuzzyMatchRangeList +fuzzy_match_find(Arena *arena, String8 needle, String8 haystack) +{ + FuzzyMatchRangeList result = {0}; + Temp scratch = scratch_begin(&arena, 1); + String8List needles = str8_split(scratch.arena, needle, (U8*)" ", 1, 0); + result.needle_part_count = needles.node_count; + for(String8Node *needle_n = needles.first; needle_n != 0; needle_n = needle_n->next) + { + U64 find_pos = 0; + for(;find_pos < haystack.size;) + { + find_pos = str8_find_needle(haystack, find_pos, needle_n->string, StringMatchFlag_CaseInsensitive); + B32 is_in_gathered_ranges = 0; + for(FuzzyMatchRangeNode *n = result.first; n != 0; n = n->next) + { + if(n->range.min <= find_pos && find_pos < n->range.max) + { + is_in_gathered_ranges = 1; + find_pos = n->range.max; + break; + } + } + if(!is_in_gathered_ranges) + { + break; + } + } + if(find_pos < haystack.size) + { + Rng1U64 range = r1u64(find_pos, find_pos+needle_n->string.size); + FuzzyMatchRangeNode *n = push_array(arena, FuzzyMatchRangeNode, 1); + n->range = range; + SLLQueuePush(result.first, result.last, n); + result.count += 1; + result.total_dim += dim_1u64(range); + } + } + scratch_end(scratch); + return result; +} + +internal FuzzyMatchRangeList +fuzzy_match_range_list_copy(Arena *arena, FuzzyMatchRangeList *src) +{ + FuzzyMatchRangeList dst = {0}; + for(FuzzyMatchRangeNode *src_n = src->first; src_n != 0; src_n = src_n->next) + { + FuzzyMatchRangeNode *dst_n = push_array(arena, FuzzyMatchRangeNode, 1); + SLLQueuePush(dst.first, dst.last, dst_n); + dst_n->range = src_n->range; + } + dst.count = src->count; + dst.needle_part_count = src->needle_part_count; + dst.total_dim = src->total_dim; + return dst; +} + +//////////////////////////////// +//~ NOTE(allen): Serialization Helpers + +internal void +str8_serial_begin(Arena *arena, String8List *srl){ + String8Node *node = push_array(arena, String8Node, 1); + node->string.str = push_array_no_zero(arena, U8, 0); + srl->first = srl->last = node; + srl->node_count = 1; + srl->total_size = 0; +} + +internal String8 +str8_serial_end(Arena *arena, String8List *srl){ + U64 size = srl->total_size; + U8 *out = push_array_no_zero(arena, U8, size); + str8_serial_write_to_dst(srl, out); + String8 result = str8(out, size); + return result; +} + +internal void +str8_serial_write_to_dst(String8List *srl, void *out){ + U8 *ptr = (U8*)out; + for (String8Node *node = srl->first; + node != 0; + node = node->next){ + U64 size = node->string.size; + MemoryCopy(ptr, node->string.str, size); + ptr += size; + } +} + +internal U64 +str8_serial_push_align(Arena *arena, String8List *srl, U64 align){ + Assert(IsPow2(align)); + + U64 pos = srl->total_size; + U64 new_pos = AlignPow2(pos, align); + U64 size = (new_pos - pos); + + if(size != 0) + { + U8 *buf = push_array(arena, U8, size); + + String8 *str = &srl->last->string; + if (str->str + str->size == buf){ + srl->last->string.size += size; + srl->total_size += size; + } + else{ + str8_list_push(arena, srl, str8(buf, size)); + } + } + return size; +} + +internal void * +str8_serial_push_size(Arena *arena, String8List *srl, U64 size) +{ + void *result = 0; + if(size != 0) + { + U8 *buf = push_array_no_zero(arena, U8, size); + String8 *str = &srl->last->string; + if (str->str + str->size == buf){ + srl->last->string.size += size; + srl->total_size += size; + } + else{ + str8_list_push(arena, srl, str8(buf, size)); + } + result = buf; + } + return result; +} + +internal void * +str8_serial_push_data(Arena *arena, String8List *srl, void *data, U64 size){ + void *result = str8_serial_push_size(arena, srl, size); + if(result != 0) + { + MemoryCopy(result, data, size); + } + return result; +} + +internal void +str8_serial_push_data_list(Arena *arena, String8List *srl, String8Node *first){ + for (String8Node *node = first; + node != 0; + node = node->next){ + str8_serial_push_data(arena, srl, node->string.str, node->string.size); + } +} + +internal void +str8_serial_push_u64(Arena *arena, String8List *srl, U64 x){ + U8 *buf = push_array_no_zero(arena, U8, 8); + MemoryCopy(buf, &x, 8); + String8 *str = &srl->last->string; + if (str->str + str->size == buf){ + srl->last->string.size += 8; + srl->total_size += 8; + } + else{ + str8_list_push(arena, srl, str8(buf, 8)); + } +} + +internal void +str8_serial_push_u32(Arena *arena, String8List *srl, U32 x){ + U8 *buf = push_array_no_zero(arena, U8, 4); + MemoryCopy(buf, &x, 4); + String8 *str = &srl->last->string; + if (str->str + str->size == buf){ + srl->last->string.size += 4; + srl->total_size += 4; + } + else{ + str8_list_push(arena, srl, str8(buf, 4)); + } +} + +internal void +str8_serial_push_u16(Arena *arena, String8List *srl, U16 x){ + str8_serial_push_data(arena, srl, &x, sizeof(x)); +} + +internal void +str8_serial_push_u8(Arena *arena, String8List *srl, U8 x){ + str8_serial_push_data(arena, srl, &x, sizeof(x)); +} + +internal void +str8_serial_push_cstr(Arena *arena, String8List *srl, String8 str){ + str8_serial_push_data(arena, srl, str.str, str.size); + str8_serial_push_u8(arena, srl, 0); +} + +internal void +str8_serial_push_string(Arena *arena, String8List *srl, String8 str){ + str8_serial_push_data(arena, srl, str.str, str.size); +} + +//////////////////////////////// +//~ rjf: Deserialization Helpers + +internal U64 +str8_deserial_read(String8 string, U64 off, void *read_dst, U64 read_size, U64 granularity) +{ + U64 bytes_left = string.size-Min(off, string.size); + U64 actually_readable_size = Min(bytes_left, read_size); + U64 legally_readable_size = actually_readable_size - actually_readable_size%granularity; + if(legally_readable_size > 0) + { + MemoryCopy(read_dst, string.str+off, legally_readable_size); + } + return legally_readable_size; +} + +internal U64 +str8_deserial_find_first_match(String8 string, U64 off, U16 scan_val) +{ + U64 cursor = off; + for (;;) { + U16 val = 0; + str8_deserial_read_struct(string, cursor, &val); + if (val == scan_val) { + break; + } + cursor += sizeof(val); + } + return cursor; +} + +internal void * +str8_deserial_get_raw_ptr(String8 string, U64 off, U64 size) +{ + void *raw_ptr = 0; + if (off + size <= string.size) { + raw_ptr = string.str + off; + } + return raw_ptr; +} + +internal U64 +str8_deserial_read_cstr(String8 string, U64 off, String8 *cstr_out) +{ + U64 cstr_size = 0; + if (off < string.size) { + U8 *ptr = string.str + off; + U8 *cap = string.str + string.size; + *cstr_out = str8_cstring_capped(ptr, cap); + cstr_size = (cstr_out->size + 1); + } + return cstr_size; +} + +internal U64 +str8_deserial_read_windows_utf16_string16(String8 string, U64 off, String16 *str_out) +{ + U64 null_off = str8_deserial_find_first_match(string, off, 0); + U64 size = null_off - off; + U16 *str = (U16 *)str8_deserial_get_raw_ptr(string, off, size); + U64 count = size / sizeof(*str); + *str_out = str16(str, count); + + U64 read_size_with_null = size + sizeof(*str); + return read_size_with_null; +} + +internal U64 +str8_deserial_read_block(String8 string, U64 off, U64 size, String8 *block_out) +{ + Rng1U64 range = rng_1u64(off, off + size); + *block_out = str8_substr(string, range); + return block_out->size; +} diff --git a/src/base/base_thread_context.h b/src/base/base_thread_context.h index 4f61d009..90a396fe 100644 --- a/src/base/base_thread_context.h +++ b/src/base/base_thread_context.h @@ -1,41 +1,41 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef BASE_THREAD_CONTEXT_H -#define BASE_THREAD_CONTEXT_H - -//////////////////////////////// -// NOTE(allen): Thread Context - -typedef struct TCTX TCTX; -struct TCTX -{ - Arena *arenas[2]; - - U8 thread_name[32]; - U64 thread_name_size; - - char *file_name; - U64 line_number; -}; - -//////////////////////////////// -// NOTE(allen): Thread Context Functions - -internal void tctx_init_and_equip(TCTX *tctx); -internal void tctx_release(void); -internal TCTX* tctx_get_equipped(void); - -internal Arena* tctx_get_scratch(Arena **conflicts, U64 count); - -internal void tctx_set_thread_name(String8 name); -internal String8 tctx_get_thread_name(void); - -internal void tctx_write_srcloc(char *file_name, U64 line_number); -internal void tctx_read_srcloc(char **file_name, U64 *line_number); -#define tctx_write_this_srcloc() tctx_write_srcloc(__FILE__, __LINE__) - -#define scratch_begin(conflicts, count) temp_begin(tctx_get_scratch((conflicts), (count))) -#define scratch_end(scratch) temp_end(scratch) - -#endif //BASE_THREAD_CONTEXT_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_THREAD_CONTEXT_H +#define BASE_THREAD_CONTEXT_H + +//////////////////////////////// +// NOTE(allen): Thread Context + +typedef struct TCTX TCTX; +struct TCTX +{ + Arena *arenas[2]; + + U8 thread_name[32]; + U64 thread_name_size; + + char *file_name; + U64 line_number; +}; + +//////////////////////////////// +// NOTE(allen): Thread Context Functions + +internal void tctx_init_and_equip(TCTX *tctx); +internal void tctx_release(void); +internal TCTX* tctx_get_equipped(void); + +internal Arena* tctx_get_scratch(Arena **conflicts, U64 count); + +internal void tctx_set_thread_name(String8 name); +internal String8 tctx_get_thread_name(void); + +internal void tctx_write_srcloc(char *file_name, U64 line_number); +internal void tctx_read_srcloc(char **file_name, U64 *line_number); +#define tctx_write_this_srcloc() tctx_write_srcloc(__FILE__, __LINE__) + +#define scratch_begin(conflicts, count) temp_begin(tctx_get_scratch((conflicts), (count))) +#define scratch_end(scratch) temp_end(scratch) + +#endif // BASE_THREAD_CONTEXT_H diff --git a/src/codeview/codeview.c b/src/codeview/codeview.c index 439a960d..c6189600 100644 --- a/src/codeview/codeview.c +++ b/src/codeview/codeview.c @@ -1,638 +1,637 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ Generated Code - -#include "generated/codeview.meta.c" - -//////////////////////////////// -//~ CodeView Common Decoding Helper Functions - -internal U64 -cv_hash_from_string(String8 string) -{ - U64 result = 5381; - for(U64 i = 0; i < string.size; i += 1) - { - result = ((result << 5) + result) + string.str[i]; - } - return result; -} - -internal U64 -cv_hash_from_item_id(CV_ItemId item_id) -{ - U64 result = cv_hash_from_string(str8_struct(&item_id)); - return result; -} - -internal CV_NumericParsed -cv_numeric_from_data_range(U8 *first, U8 *opl) -{ - CV_NumericParsed result = {0}; - if(first + 2 <= opl) - { - U16 x = *(U16*)first; - if(x < 0x8000) - { - result.kind = CV_NumericKind_USHORT; - result.val = first; - result.encoded_size = 2; - } - else - { - U64 val_size = 0; - switch(x) - { - case CV_NumericKind_CHAR: val_size = 1; break; - case CV_NumericKind_SHORT: - case CV_NumericKind_USHORT: val_size = 2; break; - case CV_NumericKind_LONG: - case CV_NumericKind_ULONG: val_size = 4; break; - case CV_NumericKind_FLOAT32: val_size = 4; break; - case CV_NumericKind_FLOAT64: val_size = 8; break; - case CV_NumericKind_FLOAT80: val_size = 10; break; - case CV_NumericKind_FLOAT128: val_size = 16; break; - case CV_NumericKind_QUADWORD: - case CV_NumericKind_UQUADWORD: val_size = 8; break; - case CV_NumericKind_FLOAT48: val_size = 6; break; - case CV_NumericKind_COMPLEX32: val_size = 8; break; - case CV_NumericKind_COMPLEX64: val_size = 16; break; - case CV_NumericKind_COMPLEX80: val_size = 20; break; - case CV_NumericKind_COMPLEX128:val_size = 32; break; - case CV_NumericKind_VARSTRING: val_size = 0; break; // TODO: ??? - case CV_NumericKind_OCTWORD: - case CV_NumericKind_UOCTWORD: val_size = 16; break; - case CV_NumericKind_DECIMAL: val_size = 0; break; // TODO: ??? - case CV_NumericKind_DATE: val_size = 0; break; // TODO: ??? - case CV_NumericKind_UTF8STRING:val_size = 0; break; // TODO: ??? - case CV_NumericKind_FLOAT16: val_size = 2; break; - } - if(first + 2 + val_size <= opl) - { - result.kind = x; - result.val = (first + 2); - result.encoded_size = 2 + val_size; - } - } - } - return result; -} - -internal B32 -cv_numeric_fits_in_u64(CV_NumericParsed *num) -{ - B32 result = 0; - switch(num->kind) - { - case CV_NumericKind_USHORT: - case CV_NumericKind_ULONG: - case CV_NumericKind_UQUADWORD: - { - result = 1; - }break; - } - return result; -} - -internal B32 -cv_numeric_fits_in_s64(CV_NumericParsed *num) -{ - B32 result = 0; - switch(num->kind) - { - case CV_NumericKind_CHAR: - case CV_NumericKind_SHORT: - case CV_NumericKind_LONG: - case CV_NumericKind_QUADWORD: - { - result = 1; - }break; - } - return result; -} - -internal B32 -cv_numeric_fits_in_f64(CV_NumericParsed *num) -{ - B32 result = 0; - switch(num->kind) - { - case CV_NumericKind_FLOAT32: - case CV_NumericKind_FLOAT64: - { - result = 1; - }break; - } - return result; -} - -internal U64 -cv_u64_from_numeric(CV_NumericParsed *num) -{ - U64 result = 0; - switch(num->kind) - { - case CV_NumericKind_USHORT: {result = *(U16*)num->val;}break; - case CV_NumericKind_ULONG: {result = *(U32*)num->val;}break; - case CV_NumericKind_UQUADWORD:{result = *(U64*)num->val;}break; - } - return result; -} - -internal S64 -cv_s64_from_numeric(CV_NumericParsed *num) -{ - S64 result = 0; - switch(num->kind) - { - case CV_NumericKind_CHAR: {result = *(S8*)num->val;}break; - case CV_NumericKind_SHORT: {result = *(S16*)num->val;}break; - case CV_NumericKind_LONG: {result = *(S32*)num->val;}break; - case CV_NumericKind_QUADWORD: {result = *(S64*)num->val;}break; - } - return(result); -} - -internal F64 -cv_f64_from_numeric(CV_NumericParsed *num) -{ - F64 result = 0; - switch(num->kind) - { - case CV_NumericKind_FLOAT32:{result = *(F32*)num->val;}break; - case CV_NumericKind_FLOAT64:{result = *(F64*)num->val;}break; - } - return(result); -} - -internal U64 -cv_decode_inline_annot_u32(String8 data, U64 offset, U32 *out_value) -{ - U64 cursor = offset; - - // rjf: read header - U8 header = 0; - cursor += str8_deserial_read_struct(data, cursor, &header); - - // rjf: decode value - U32 value = 0; - { - // 1 byte - if((header & 0x80) == 0) - { - value = header; - } - - // 2 bytes - else if((header & 0xC0) == 0x80 && cursor+2 <= data.size) - { - U8 second_byte; - cursor += str8_deserial_read_struct(data, cursor, &second_byte); - value = ((header & 0x3F) << 8) | second_byte; - } - - // 4 bytes - else if((header & 0xE0) == 0xC0 && cursor+3 <= data.size) - { - U8 second_byte, third_byte, fourth_byte; - cursor += str8_deserial_read_struct(data, cursor, &second_byte); - cursor += str8_deserial_read_struct(data, cursor, &third_byte); - cursor += str8_deserial_read_struct(data, cursor, &fourth_byte); - value = (((U32)header & 0x1F) << 24) | ((U32)second_byte << 16) | ((U32)third_byte << 8) | (U32)fourth_byte; - } - - // bad encode - else if((header & 0xE0) == 0xE0) - { - value = max_U32; - } - } - - // rjf: output results - if(out_value) - { - *out_value = value; - } - - U64 read_size = cursor - offset; - return read_size; -} - -internal U64 -cv_decode_inline_annot_s32(String8 data, U64 offset, S32 *out_value) -{ - U32 value; - U64 read_size = cv_decode_inline_annot_u32(data, offset, &value); - if(value & 1) - { - value = -(value >> 1); - } - else - { - value = value >> 1; - } - *out_value = (S32)value; - return read_size; -} - -internal S32 -cv_inline_annot_signed_from_unsigned_operand(U32 value) -{ - if(value & 1) - { - value = -(value >> 1); - } - else - { - value = value >> 1; - } - S32 result = (S32)value; - return result; -} - -//////////////////////////////// -//~ CodeView Parsing Functions - -//- rjf: record range stream parsing - -internal CV_RecRangeStream* -cv_rec_range_stream_from_data(Arena *arena, String8 sym_data, U64 sym_align) -{ - Assert(1 <= sym_align && IsPow2OrZero(sym_align)); - CV_RecRangeStream *result = push_array(arena, CV_RecRangeStream, 1); - U8 *data = sym_data.str; - U64 cursor = 0; - U64 cap = sym_data.size; - for(;cursor + sizeof(CV_RecHeader) <= cap;) - { - // setup a new chunk - arena_push_align(arena, 64); - CV_RecRangeChunk *cur_chunk = push_array_no_zero(arena, CV_RecRangeChunk, 1); - SLLQueuePush(result->first_chunk, result->last_chunk, cur_chunk); - U64 partial_count = 0; - for(;partial_count < CV_REC_RANGE_CHUNK_SIZE && cursor + sizeof(CV_RecHeader) <= cap; partial_count += 1) - { - // compute cap - CV_RecHeader *hdr = (CV_RecHeader*)(data + cursor); - U64 symbol_cap_unclamped = cursor + 2 + hdr->size; - U64 symbol_cap = ClampTop(symbol_cap_unclamped, cap); - - // push on range - cur_chunk->ranges[partial_count].off = cursor + 2; - cur_chunk->ranges[partial_count].hdr = *hdr; - - // update cursor - U32 next_pos = AlignPow2(symbol_cap, sym_align); - cursor = next_pos; - } - result->total_count += partial_count; - } - return result; -} - -internal CV_RecRangeArray -cv_rec_range_array_from_stream(Arena *arena, CV_RecRangeStream *stream) -{ - U64 total_count = stream->total_count; - CV_RecRange *ranges = push_array_no_zero(arena, CV_RecRange, total_count); - U64 idx = 0; - for(CV_RecRangeChunk *chunk = stream->first_chunk; chunk != 0; chunk = chunk->next) - { - U64 copy_count_raw = total_count - idx; - U64 copy_count = ClampTop(copy_count_raw, CV_REC_RANGE_CHUNK_SIZE); - MemoryCopy(ranges + idx, chunk->ranges, copy_count*sizeof(CV_RecRange)); - idx += copy_count; - } - CV_RecRangeArray result = {0}; - result.ranges = ranges; - result.count = total_count; - return result; -} - -//- rjf: sym stream parsing - -internal CV_SymParsed * -cv_sym_from_data(Arena *arena, String8 sym_data, U64 sym_align) -{ - Assert(1 <= sym_align && IsPow2OrZero(sym_align)); - ProfBeginFunction(); - Temp scratch = scratch_begin(&arena, 1); - - //- rjf: gather symbols - CV_RecRangeStream *stream = cv_rec_range_stream_from_data(scratch.arena, sym_data, sym_align); - - //- rjf: convert to result, fill basics - CV_SymParsed *result = push_array(arena, CV_SymParsed, 1); - result->data = sym_data; - result->sym_align = sym_align; - result->sym_ranges = cv_rec_range_array_from_stream(arena, stream); - - //- rjf: extract top-level-info - { - CV_RecRange *range = result->sym_ranges.ranges; - CV_RecRange *opl = range + result->sym_ranges.count; - for(;range < opl; range += 1) - { - U8 *first = sym_data.str + range->off + 2; - U64 cap = range->hdr.size - 2; - switch(range->hdr.kind) - { - case CV_SymKind_COMPILE: - if(sizeof(CV_SymCompile) <= cap) - { - CV_SymCompile *compile = (CV_SymCompile*)first; - String8 ver_str = str8_cstring_capped((char*)(compile + 1), (char *)(first + cap)); - result->info.arch = compile->machine; - result->info.language = CV_CompileFlags_ExtractLanguage(compile->flags);; - result->info.compiler_name = ver_str; - }break; - case CV_SymKind_COMPILE2: - if(sizeof(CV_SymCompile2) <= cap) - { - CV_SymCompile2 *compile2 = (CV_SymCompile2*)first; - String8 ver_str = str8_cstring_capped((char*)(compile2 + 1), (char*)(first + cap)); - String8 compiler_name = push_str8f(arena, "%.*s %u.%u.%u", - str8_varg(ver_str), - compile2->ver_major, - compile2->ver_minor, - compile2->ver_build); - result->info.arch = compile2->machine; - result->info.language = CV_Compile2Flags_ExtractLanguage(compile2->flags);; - result->info.compiler_name = compiler_name; - }break; - case CV_SymKind_COMPILE3: - if(sizeof(CV_SymCompile3) <= cap) - { - CV_SymCompile3 *compile3 = (CV_SymCompile3*)first; - String8 ver_str = str8_cstring_capped((char*)(compile3 + 1), (char *)(first + cap)); - String8 compiler_name = push_str8f(arena, "%.*s %u.%u.%u", - str8_varg(ver_str), - compile3->ver_major, - compile3->ver_minor, - compile3->ver_build); - result->info.arch = compile3->machine; - result->info.language = CV_Compile3Flags_ExtractLanguage(compile3->flags);; - result->info.compiler_name = compiler_name; - }break; - } - } - } - - scratch_end(scratch); - ProfEnd(); - return result; -} - -//- rjf: leaf stream parsing - -internal CV_LeafParsed * -cv_leaf_from_data(Arena *arena, String8 leaf_data, CV_TypeId itype_first) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(&arena, 1); - - // gather up symbols - CV_RecRangeStream *stream = cv_rec_range_stream_from_data(scratch.arena, leaf_data, 1); - - // convert to result - CV_LeafParsed *result = push_array(arena, CV_LeafParsed, 1); - result->data = leaf_data; - result->itype_first = itype_first; - result->itype_opl = itype_first + stream->total_count; - result->leaf_ranges = cv_rec_range_array_from_stream(arena, stream); - - scratch_end(scratch); - ProfEnd(); - return result; -} - -//////////////////////////////// -//~ CodeView C13 Parser Functions - -internal CV_C13Parsed * -cv_c13_parsed_from_data(Arena *arena, String8 c13_data, PDB_Strtbl *strtbl, PDB_CoffSectionArray *sections) -{ - ProfBeginFunction(); - - ////////////////////////////// - //- rjf: gather c13 sub-sections - // - CV_C13SubSectionNode *file_chksms = 0; - CV_C13SubSectionNode *first = 0; - CV_C13SubSectionNode *last = 0; - U64 count = 0; - { - U32 cursor = 0; - for(; cursor + sizeof(CV_C13SubSectionHeader) <= c13_data.size;) - { - // read header - CV_C13SubSectionHeader *hdr = (CV_C13SubSectionHeader*)(c13_data.str + cursor); - - // get sub section info - U32 sub_section_off = cursor + sizeof(*hdr); - U32 sub_section_size_raw = hdr->size; - U32 after_sub_section_off_unclamped = sub_section_off + sub_section_size_raw; - U32 after_sub_section_off = ClampTop(after_sub_section_off_unclamped, c13_data.size); - U32 sub_section_size = after_sub_section_off - sub_section_off; - - // emit sub section - if(!(hdr->kind & CV_C13SubSectionKind_IgnoreFlag)) - { - CV_C13SubSectionNode *node = push_array(arena, CV_C13SubSectionNode, 1); - SLLQueuePush(first, last, node); - count += 1; - node->kind = hdr->kind; - node->off = sub_section_off; - node->size = sub_section_size; - if(hdr->kind == CV_C13SubSectionKind_FileChksms) - { - file_chksms = node; - } - } - - // move cursor - cursor = AlignPow2(after_sub_section_off, 4); - } - } - - ////////////////////////////// - //- rjf: parse each sub-section - // - U64 inlinee_lines_parsed_slots_count = 4096; - CV_C13InlineeLinesParsedNode **inlinee_lines_parsed_slots = push_array(arena, CV_C13InlineeLinesParsedNode *, inlinee_lines_parsed_slots_count); - for(CV_C13SubSectionNode *node = first; - node != 0; - node = node->next) - { - U8 *first = c13_data.str + node->off; - U32 cap = node->size; - switch(node->kind) - { - default:{}break; - - ////////////////////////// - //- rjf: line info sub-section - // - case CV_C13SubSectionKind_Lines: - if(sizeof(CV_C13SubSecLinesHeader) <= cap) - { - // read header - U32 read_off = 0; - U64 read_off_opl = node->size; - CV_C13SubSecLinesHeader *hdr = (CV_C13SubSecLinesHeader*)(first + read_off); - read_off += sizeof(*hdr); - - // extract top level info - U32 sec_idx = hdr->sec; - B32 has_cols = !!(hdr->flags & CV_C13SubSecLinesFlag_HasColumns); - U64 secrel_off = hdr->sec_off; - U64 secrel_opl = secrel_off + hdr->len; - U64 sec_base_off = sections->sections[sec_idx - 1].voff; - - // rjf: bad section index -> skip - if(sec_idx < 1 || sections->count < sec_idx) - { - continue; - } - - // read files - for(;read_off+sizeof(CV_C13File) <= read_off_opl;) - { - // rjf: grab next file header - CV_C13File *file = (CV_C13File*)(first + read_off); - U32 file_off = file->file_off; - U32 line_count_unclamped = file->num_lines; - U32 block_size = file->block_size; - - // file_name from file_off - String8 file_name = {0}; - if(file_off + sizeof(CV_C13Checksum) <= file_chksms->size) - { - CV_C13Checksum *checksum = (CV_C13Checksum*)(c13_data.str + file_chksms->off + file_off); - U32 name_off = checksum->name_off; - file_name = pdb_strtbl_string_from_off(strtbl, name_off); - } - - // array layouts - U32 line_item_size = sizeof(CV_C13Line); - if (has_cols){ - line_item_size += sizeof(CV_C13Column); - } - - U32 line_array_off = read_off + sizeof(*file); - U32 line_count_max = (read_off_opl - line_array_off) / line_item_size; - U32 line_count = ClampTop(line_count_unclamped, line_count_max); - - U32 col_array_off = line_array_off + line_count*sizeof(CV_C13Line); - - // parse lines - U64 *voffs = push_array_no_zero(arena, U64, line_count + 1); - U32 *line_nums = push_array_no_zero(arena, U32, line_count); - - { - CV_C13Line *line_ptr = (CV_C13Line*)(first + line_array_off); - CV_C13Line *line_opl = line_ptr + line_count; - - // TODO(allen): check order correctness here - - U32 i = 0; - for (; line_ptr < line_opl; line_ptr += 1, i += 1){ - voffs[i] = line_ptr->off + secrel_off + sec_base_off; - line_nums[i] = CV_C13LineFlags_ExtractLineNumber(line_ptr->flags); - } - voffs[i] = secrel_opl + sec_base_off; - } - - // emit parsed lines - CV_C13LinesParsedNode *lines_parsed_node = push_array(arena, CV_C13LinesParsedNode, 1); - CV_C13LinesParsed *lines_parsed = &lines_parsed_node->v; - lines_parsed->sec_idx = sec_idx; - lines_parsed->file_off = file_off; - lines_parsed->secrel_base_off = secrel_off; - lines_parsed->file_name = file_name; - lines_parsed->voffs = voffs; - lines_parsed->line_nums = line_nums; - lines_parsed->line_count = line_count; - SLLQueuePush(node->lines_first, node->lines_last, lines_parsed_node); - - // rjf: advance - read_off += sizeof(*file); - read_off += line_item_size*line_count; - } - }break; - - ////////////////////////// - //- rjf: inlinee line info sub-section - // - case CV_C13SubSectionKind_InlineeLines: - if(sizeof(CV_C13InlineeLinesSig) <= cap) - { - // rjf: read sig - U32 read_off = 0; - U64 read_off_opl = node->size; - CV_C13InlineeLinesSig *sig = (CV_C13InlineeLinesSig *)(first + read_off); - read_off += sizeof(*sig); - - // rjf: read source lines - for(;read_off + sizeof(CV_C13InlineeSourceLineHeader) <= read_off_opl;) - { - // rjf: read next header - CV_C13InlineeSourceLineHeader *hdr = (CV_C13InlineeSourceLineHeader *)(first + read_off); - read_off += sizeof(*hdr); - - // rjf: file_off -> file_name - String8 file_name = {0}; - if(hdr->file_off + sizeof(CV_C13Checksum) <= file_chksms->size) - { - CV_C13Checksum *checksum = (CV_C13Checksum*)(c13_data.str + file_chksms->off + hdr->file_off); - U32 name_off = checksum->name_off; - file_name = pdb_strtbl_string_from_off(strtbl, name_off); - } - - // rjf: parse extra files - U32 extra_file_count = 0; - U32 *extra_files = 0; - if(*sig == CV_C13InlineeLinesSig_EXTRA_FILES && read_off+sizeof(U32) <= read_off_opl) - { - U32 *extra_file_count_ptr = (U32 *)(first + read_off); - read_off += sizeof(*extra_file_count_ptr); - U32 max_extra_file_count = (read_off_opl-read_off)/sizeof(U32); - extra_file_count = Min(*extra_file_count_ptr, max_extra_file_count); - extra_files = (U32 *)(first + read_off); - read_off += sizeof(*extra_files)*extra_file_count; - } - - // rjf: push node for this inlinee lines parsed into this subsection's list - CV_C13InlineeLinesParsedNode *n = push_array(arena, CV_C13InlineeLinesParsedNode, 1); - SLLQueuePush(node->inlinee_lines_first, node->inlinee_lines_last, n); - n->v.inlinee = hdr->inlinee; - n->v.file_name = file_name; - n->v.first_source_ln = hdr->first_source_ln; - n->v.extra_file_count = extra_file_count; - n->v.extra_files = extra_files; - - // rjf: push node into inlinee parse hash table - U64 hash = cv_hash_from_item_id(hdr->inlinee); - U64 slot_idx = hash%inlinee_lines_parsed_slots_count; - SLLStackPush_N(inlinee_lines_parsed_slots[slot_idx], n, hash_next); - } - }break; - } - } - - ////////////////////////////// - //- rjf: fill output - // - CV_C13Parsed *result = push_array(arena, CV_C13Parsed, 1); - result->data = c13_data; - result->first_sub_section = first; - result->last_sub_section = last; - result->sub_section_count = count; - result->file_chksms_sub_section = file_chksms; - result->inlinee_lines_parsed_slots = inlinee_lines_parsed_slots; - result->inlinee_lines_parsed_slots_count = inlinee_lines_parsed_slots_count; - ProfEnd(); - return result; -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ Generated Code + +#include "generated/codeview.meta.c" + +//////////////////////////////// +//~ CodeView Common Decoding Helper Functions + +internal U64 +cv_hash_from_string(String8 string) +{ + U64 result = 5381; + for(U64 i = 0; i < string.size; i += 1) + { + result = ((result << 5) + result) + string.str[i]; + } + return result; +} + +internal U64 +cv_hash_from_item_id(CV_ItemId item_id) +{ + U64 result = cv_hash_from_string(str8_struct(&item_id)); + return result; +} + +internal CV_NumericParsed +cv_numeric_from_data_range(U8 *first, U8 *opl) +{ + CV_NumericParsed result = {0}; + if(first + 2 <= opl) + { + U16 x = *(U16*)first; + if(x < 0x8000) + { + result.kind = CV_NumericKind_USHORT; + result.val = first; + result.encoded_size = 2; + } + else + { + U64 val_size = 0; + switch(x) + { + case CV_NumericKind_CHAR: val_size = 1; break; + case CV_NumericKind_SHORT: + case CV_NumericKind_USHORT: val_size = 2; break; + case CV_NumericKind_LONG: + case CV_NumericKind_ULONG: val_size = 4; break; + case CV_NumericKind_FLOAT32: val_size = 4; break; + case CV_NumericKind_FLOAT64: val_size = 8; break; + case CV_NumericKind_FLOAT80: val_size = 10; break; + case CV_NumericKind_FLOAT128: val_size = 16; break; + case CV_NumericKind_QUADWORD: + case CV_NumericKind_UQUADWORD: val_size = 8; break; + case CV_NumericKind_FLOAT48: val_size = 6; break; + case CV_NumericKind_COMPLEX32: val_size = 8; break; + case CV_NumericKind_COMPLEX64: val_size = 16; break; + case CV_NumericKind_COMPLEX80: val_size = 20; break; + case CV_NumericKind_COMPLEX128:val_size = 32; break; + case CV_NumericKind_VARSTRING: val_size = 0; break; // TODO: ??? + case CV_NumericKind_OCTWORD: + case CV_NumericKind_UOCTWORD: val_size = 16; break; + case CV_NumericKind_DECIMAL: val_size = 0; break; // TODO: ??? + case CV_NumericKind_DATE: val_size = 0; break; // TODO: ??? + case CV_NumericKind_UTF8STRING:val_size = 0; break; // TODO: ??? + case CV_NumericKind_FLOAT16: val_size = 2; break; + } + if(first + 2 + val_size <= opl) + { + result.kind = x; + result.val = (first + 2); + result.encoded_size = 2 + val_size; + } + } + } + return result; +} + +internal B32 +cv_numeric_fits_in_u64(CV_NumericParsed *num) +{ + B32 result = 0; + switch(num->kind) + { + case CV_NumericKind_USHORT: + case CV_NumericKind_ULONG: + case CV_NumericKind_UQUADWORD: + { + result = 1; + }break; + } + return result; +} + +internal B32 +cv_numeric_fits_in_s64(CV_NumericParsed *num) +{ + B32 result = 0; + switch(num->kind) + { + case CV_NumericKind_CHAR: + case CV_NumericKind_SHORT: + case CV_NumericKind_LONG: + case CV_NumericKind_QUADWORD: + { + result = 1; + }break; + } + return result; +} + +internal B32 +cv_numeric_fits_in_f64(CV_NumericParsed *num) +{ + B32 result = 0; + switch(num->kind) + { + case CV_NumericKind_FLOAT32: + case CV_NumericKind_FLOAT64: + { + result = 1; + }break; + } + return result; +} + +internal U64 +cv_u64_from_numeric(CV_NumericParsed *num) +{ + U64 result = 0; + switch(num->kind) + { + case CV_NumericKind_USHORT: {result = *(U16*)num->val;}break; + case CV_NumericKind_ULONG: {result = *(U32*)num->val;}break; + case CV_NumericKind_UQUADWORD:{result = *(U64*)num->val;}break; + } + return result; +} + +internal S64 +cv_s64_from_numeric(CV_NumericParsed *num) +{ + S64 result = 0; + switch(num->kind) + { + case CV_NumericKind_CHAR: {result = *(S8*)num->val;}break; + case CV_NumericKind_SHORT: {result = *(S16*)num->val;}break; + case CV_NumericKind_LONG: {result = *(S32*)num->val;}break; + case CV_NumericKind_QUADWORD: {result = *(S64*)num->val;}break; + } + return(result); +} + +internal F64 +cv_f64_from_numeric(CV_NumericParsed *num) +{ + F64 result = 0; + switch(num->kind) + { + case CV_NumericKind_FLOAT32:{result = *(F32*)num->val;}break; + case CV_NumericKind_FLOAT64:{result = *(F64*)num->val;}break; + } + return(result); +} + +internal U64 +cv_decode_inline_annot_u32(String8 data, U64 offset, U32 *out_value) +{ + U64 cursor = offset; + + // rjf: read header + U8 header = 0; + cursor += str8_deserial_read_struct(data, cursor, &header); + + // rjf: decode value + U32 value = 0; + { + // 1 byte + if((header & 0x80) == 0) + { + value = header; + } + + // 2 bytes + else if((header & 0xC0) == 0x80 && cursor+2 <= data.size) + { + U8 second_byte; + cursor += str8_deserial_read_struct(data, cursor, &second_byte); + value = ((header & 0x3F) << 8) | second_byte; + } + + // 4 bytes + else if((header & 0xE0) == 0xC0 && cursor+3 <= data.size) + { + U8 second_byte, third_byte, fourth_byte; + cursor += str8_deserial_read_struct(data, cursor, &second_byte); + cursor += str8_deserial_read_struct(data, cursor, &third_byte); + cursor += str8_deserial_read_struct(data, cursor, &fourth_byte); + value = (((U32)header & 0x1F) << 24) | ((U32)second_byte << 16) | ((U32)third_byte << 8) | (U32)fourth_byte; + } + + // bad encode + else if((header & 0xE0) == 0xE0) + { + value = max_U32; + } + } + + // rjf: output results + if(out_value) + { + *out_value = value; + } + + U64 read_size = cursor - offset; + return read_size; +} + +internal U64 +cv_decode_inline_annot_s32(String8 data, U64 offset, S32 *out_value) +{ + U32 value; + U64 read_size = cv_decode_inline_annot_u32(data, offset, &value); + if(value & 1) + { + value = -(value >> 1); + } + else + { + value = value >> 1; + } + *out_value = (S32)value; + return read_size; +} + +internal S32 +cv_inline_annot_signed_from_unsigned_operand(U32 value) +{ + if(value & 1) + { + value = -(value >> 1); + } + else + { + value = value >> 1; + } + S32 result = (S32)value; + return result; +} + +//////////////////////////////// +//~ CodeView Parsing Functions + +//- rjf: record range stream parsing + +internal CV_RecRangeStream* +cv_rec_range_stream_from_data(Arena *arena, String8 sym_data, U64 sym_align) +{ + Assert(1 <= sym_align && IsPow2OrZero(sym_align)); + CV_RecRangeStream *result = push_array(arena, CV_RecRangeStream, 1); + U8 *data = sym_data.str; + U64 cursor = 0; + U64 cap = sym_data.size; + for(;cursor + sizeof(CV_RecHeader) <= cap;) + { + // setup a new chunk + CV_RecRangeChunk *cur_chunk = push_array_aligned(arena, CV_RecRangeChunk, 1, 64); + SLLQueuePush(result->first_chunk, result->last_chunk, cur_chunk); + U64 partial_count = 0; + for(;partial_count < CV_REC_RANGE_CHUNK_SIZE && cursor + sizeof(CV_RecHeader) <= cap; partial_count += 1) + { + // compute cap + CV_RecHeader *hdr = (CV_RecHeader*)(data + cursor); + U64 symbol_cap_unclamped = cursor + 2 + hdr->size; + U64 symbol_cap = ClampTop(symbol_cap_unclamped, cap); + + // push on range + cur_chunk->ranges[partial_count].off = cursor + 2; + cur_chunk->ranges[partial_count].hdr = *hdr; + + // update cursor + U32 next_pos = AlignPow2(symbol_cap, sym_align); + cursor = next_pos; + } + result->total_count += partial_count; + } + return result; +} + +internal CV_RecRangeArray +cv_rec_range_array_from_stream(Arena *arena, CV_RecRangeStream *stream) +{ + U64 total_count = stream->total_count; + CV_RecRange *ranges = push_array_no_zero_aligned(arena, CV_RecRange, total_count, 8); + U64 idx = 0; + for(CV_RecRangeChunk *chunk = stream->first_chunk; chunk != 0; chunk = chunk->next) + { + U64 copy_count_raw = total_count - idx; + U64 copy_count = ClampTop(copy_count_raw, CV_REC_RANGE_CHUNK_SIZE); + MemoryCopy(ranges + idx, chunk->ranges, copy_count*sizeof(CV_RecRange)); + idx += copy_count; + } + CV_RecRangeArray result = {0}; + result.ranges = ranges; + result.count = total_count; + return result; +} + +//- rjf: sym stream parsing + +internal CV_SymParsed * +cv_sym_from_data(Arena *arena, String8 sym_data, U64 sym_align) +{ + Assert(1 <= sym_align && IsPow2OrZero(sym_align)); + ProfBeginFunction(); + Temp scratch = scratch_begin(&arena, 1); + + //- rjf: gather symbols + CV_RecRangeStream *stream = cv_rec_range_stream_from_data(scratch.arena, sym_data, sym_align); + + //- rjf: convert to result, fill basics + CV_SymParsed *result = push_array(arena, CV_SymParsed, 1); + result->data = sym_data; + result->sym_align = sym_align; + result->sym_ranges = cv_rec_range_array_from_stream(arena, stream); + + //- rjf: extract top-level-info + { + CV_RecRange *range = result->sym_ranges.ranges; + CV_RecRange *opl = range + result->sym_ranges.count; + for(;range < opl; range += 1) + { + U8 *first = sym_data.str + range->off + 2; + U64 cap = range->hdr.size - 2; + switch(range->hdr.kind) + { + case CV_SymKind_COMPILE: + if(sizeof(CV_SymCompile) <= cap) + { + CV_SymCompile *compile = (CV_SymCompile*)first; + String8 ver_str = str8_cstring_capped((char*)(compile + 1), (char *)(first + cap)); + result->info.arch = compile->machine; + result->info.language = CV_CompileFlags_ExtractLanguage(compile->flags);; + result->info.compiler_name = ver_str; + }break; + case CV_SymKind_COMPILE2: + if(sizeof(CV_SymCompile2) <= cap) + { + CV_SymCompile2 *compile2 = (CV_SymCompile2*)first; + String8 ver_str = str8_cstring_capped((char*)(compile2 + 1), (char*)(first + cap)); + String8 compiler_name = push_str8f(arena, "%.*s %u.%u.%u", + str8_varg(ver_str), + compile2->ver_major, + compile2->ver_minor, + compile2->ver_build); + result->info.arch = compile2->machine; + result->info.language = CV_Compile2Flags_ExtractLanguage(compile2->flags);; + result->info.compiler_name = compiler_name; + }break; + case CV_SymKind_COMPILE3: + if(sizeof(CV_SymCompile3) <= cap) + { + CV_SymCompile3 *compile3 = (CV_SymCompile3*)first; + String8 ver_str = str8_cstring_capped((char*)(compile3 + 1), (char *)(first + cap)); + String8 compiler_name = push_str8f(arena, "%.*s %u.%u.%u", + str8_varg(ver_str), + compile3->ver_major, + compile3->ver_minor, + compile3->ver_build); + result->info.arch = compile3->machine; + result->info.language = CV_Compile3Flags_ExtractLanguage(compile3->flags);; + result->info.compiler_name = compiler_name; + }break; + } + } + } + + scratch_end(scratch); + ProfEnd(); + return result; +} + +//- rjf: leaf stream parsing + +internal CV_LeafParsed * +cv_leaf_from_data(Arena *arena, String8 leaf_data, CV_TypeId itype_first) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(&arena, 1); + + // gather up symbols + CV_RecRangeStream *stream = cv_rec_range_stream_from_data(scratch.arena, leaf_data, 1); + + // convert to result + CV_LeafParsed *result = push_array(arena, CV_LeafParsed, 1); + result->data = leaf_data; + result->itype_first = itype_first; + result->itype_opl = itype_first + stream->total_count; + result->leaf_ranges = cv_rec_range_array_from_stream(arena, stream); + + scratch_end(scratch); + ProfEnd(); + return result; +} + +//////////////////////////////// +//~ CodeView C13 Parser Functions + +internal CV_C13Parsed * +cv_c13_parsed_from_data(Arena *arena, String8 c13_data, PDB_Strtbl *strtbl, PDB_CoffSectionArray *sections) +{ + ProfBeginFunction(); + + ////////////////////////////// + //- rjf: gather c13 sub-sections + // + CV_C13SubSectionNode *file_chksms = 0; + CV_C13SubSectionNode *first = 0; + CV_C13SubSectionNode *last = 0; + U64 count = 0; + { + U32 cursor = 0; + for(; cursor + sizeof(CV_C13SubSectionHeader) <= c13_data.size;) + { + // read header + CV_C13SubSectionHeader *hdr = (CV_C13SubSectionHeader*)(c13_data.str + cursor); + + // get sub section info + U32 sub_section_off = cursor + sizeof(*hdr); + U32 sub_section_size_raw = hdr->size; + U32 after_sub_section_off_unclamped = sub_section_off + sub_section_size_raw; + U32 after_sub_section_off = ClampTop(after_sub_section_off_unclamped, c13_data.size); + U32 sub_section_size = after_sub_section_off - sub_section_off; + + // emit sub section + if(!(hdr->kind & CV_C13SubSectionKind_IgnoreFlag)) + { + CV_C13SubSectionNode *node = push_array(arena, CV_C13SubSectionNode, 1); + SLLQueuePush(first, last, node); + count += 1; + node->kind = hdr->kind; + node->off = sub_section_off; + node->size = sub_section_size; + if(hdr->kind == CV_C13SubSectionKind_FileChksms) + { + file_chksms = node; + } + } + + // move cursor + cursor = AlignPow2(after_sub_section_off, 4); + } + } + + ////////////////////////////// + //- rjf: parse each sub-section + // + U64 inlinee_lines_parsed_slots_count = 4096; + CV_C13InlineeLinesParsedNode **inlinee_lines_parsed_slots = push_array(arena, CV_C13InlineeLinesParsedNode *, inlinee_lines_parsed_slots_count); + for(CV_C13SubSectionNode *node = first; + node != 0; + node = node->next) + { + U8 *first = c13_data.str + node->off; + U32 cap = node->size; + switch(node->kind) + { + default:{}break; + + ////////////////////////// + //- rjf: line info sub-section + // + case CV_C13SubSectionKind_Lines: + if(sizeof(CV_C13SubSecLinesHeader) <= cap) + { + // read header + U32 read_off = 0; + U64 read_off_opl = node->size; + CV_C13SubSecLinesHeader *hdr = (CV_C13SubSecLinesHeader*)(first + read_off); + read_off += sizeof(*hdr); + + // extract top level info + U32 sec_idx = hdr->sec; + B32 has_cols = !!(hdr->flags & CV_C13SubSecLinesFlag_HasColumns); + U64 secrel_off = hdr->sec_off; + U64 secrel_opl = secrel_off + hdr->len; + U64 sec_base_off = sections->sections[sec_idx - 1].voff; + + // rjf: bad section index -> skip + if(sec_idx < 1 || sections->count < sec_idx) + { + continue; + } + + // read files + for(;read_off+sizeof(CV_C13File) <= read_off_opl;) + { + // rjf: grab next file header + CV_C13File *file = (CV_C13File*)(first + read_off); + U32 file_off = file->file_off; + U32 line_count_unclamped = file->num_lines; + U32 block_size = file->block_size; + + // file_name from file_off + String8 file_name = {0}; + if(file_off + sizeof(CV_C13Checksum) <= file_chksms->size) + { + CV_C13Checksum *checksum = (CV_C13Checksum*)(c13_data.str + file_chksms->off + file_off); + U32 name_off = checksum->name_off; + file_name = pdb_strtbl_string_from_off(strtbl, name_off); + } + + // array layouts + U32 line_item_size = sizeof(CV_C13Line); + if (has_cols){ + line_item_size += sizeof(CV_C13Column); + } + + U32 line_array_off = read_off + sizeof(*file); + U32 line_count_max = (read_off_opl - line_array_off) / line_item_size; + U32 line_count = ClampTop(line_count_unclamped, line_count_max); + + U32 col_array_off = line_array_off + line_count*sizeof(CV_C13Line); + + // parse lines + U64 *voffs = push_array_no_zero(arena, U64, line_count + 1); + U32 *line_nums = push_array_no_zero(arena, U32, line_count); + + { + CV_C13Line *line_ptr = (CV_C13Line*)(first + line_array_off); + CV_C13Line *line_opl = line_ptr + line_count; + + // TODO(allen): check order correctness here + + U32 i = 0; + for (; line_ptr < line_opl; line_ptr += 1, i += 1){ + voffs[i] = line_ptr->off + secrel_off + sec_base_off; + line_nums[i] = CV_C13LineFlags_ExtractLineNumber(line_ptr->flags); + } + voffs[i] = secrel_opl + sec_base_off; + } + + // emit parsed lines + CV_C13LinesParsedNode *lines_parsed_node = push_array(arena, CV_C13LinesParsedNode, 1); + CV_C13LinesParsed *lines_parsed = &lines_parsed_node->v; + lines_parsed->sec_idx = sec_idx; + lines_parsed->file_off = file_off; + lines_parsed->secrel_base_off = secrel_off; + lines_parsed->file_name = file_name; + lines_parsed->voffs = voffs; + lines_parsed->line_nums = line_nums; + lines_parsed->line_count = line_count; + SLLQueuePush(node->lines_first, node->lines_last, lines_parsed_node); + + // rjf: advance + read_off += sizeof(*file); + read_off += line_item_size*line_count; + } + }break; + + ////////////////////////// + //- rjf: inlinee line info sub-section + // + case CV_C13SubSectionKind_InlineeLines: + if(sizeof(CV_C13InlineeLinesSig) <= cap) + { + // rjf: read sig + U32 read_off = 0; + U64 read_off_opl = node->size; + CV_C13InlineeLinesSig *sig = (CV_C13InlineeLinesSig *)(first + read_off); + read_off += sizeof(*sig); + + // rjf: read source lines + for(;read_off + sizeof(CV_C13InlineeSourceLineHeader) <= read_off_opl;) + { + // rjf: read next header + CV_C13InlineeSourceLineHeader *hdr = (CV_C13InlineeSourceLineHeader *)(first + read_off); + read_off += sizeof(*hdr); + + // rjf: file_off -> file_name + String8 file_name = {0}; + if(hdr->file_off + sizeof(CV_C13Checksum) <= file_chksms->size) + { + CV_C13Checksum *checksum = (CV_C13Checksum*)(c13_data.str + file_chksms->off + hdr->file_off); + U32 name_off = checksum->name_off; + file_name = pdb_strtbl_string_from_off(strtbl, name_off); + } + + // rjf: parse extra files + U32 extra_file_count = 0; + U32 *extra_files = 0; + if(*sig == CV_C13InlineeLinesSig_EXTRA_FILES && read_off+sizeof(U32) <= read_off_opl) + { + U32 *extra_file_count_ptr = (U32 *)(first + read_off); + read_off += sizeof(*extra_file_count_ptr); + U32 max_extra_file_count = (read_off_opl-read_off)/sizeof(U32); + extra_file_count = Min(*extra_file_count_ptr, max_extra_file_count); + extra_files = (U32 *)(first + read_off); + read_off += sizeof(*extra_files)*extra_file_count; + } + + // rjf: push node for this inlinee lines parsed into this subsection's list + CV_C13InlineeLinesParsedNode *n = push_array(arena, CV_C13InlineeLinesParsedNode, 1); + SLLQueuePush(node->inlinee_lines_first, node->inlinee_lines_last, n); + n->v.inlinee = hdr->inlinee; + n->v.file_name = file_name; + n->v.first_source_ln = hdr->first_source_ln; + n->v.extra_file_count = extra_file_count; + n->v.extra_files = extra_files; + + // rjf: push node into inlinee parse hash table + U64 hash = cv_hash_from_item_id(hdr->inlinee); + U64 slot_idx = hash%inlinee_lines_parsed_slots_count; + SLLStackPush_N(inlinee_lines_parsed_slots[slot_idx], n, hash_next); + } + }break; + } + } + + ////////////////////////////// + //- rjf: fill output + // + CV_C13Parsed *result = push_array(arena, CV_C13Parsed, 1); + result->data = c13_data; + result->first_sub_section = first; + result->last_sub_section = last; + result->sub_section_count = count; + result->file_chksms_sub_section = file_chksms; + result->inlinee_lines_parsed_slots = inlinee_lines_parsed_slots; + result->inlinee_lines_parsed_slots_count = inlinee_lines_parsed_slots_count; + ProfEnd(); + return result; +} diff --git a/src/codeview/codeview.h b/src/codeview/codeview.h index b74dabd2..e64cba5c 100644 --- a/src/codeview/codeview.h +++ b/src/codeview/codeview.h @@ -1,3017 +1,3017 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef CODEVIEW_H -#define CODEVIEW_H - -#pragma pack(push, 1) - -// https://github.com/microsoft/microsoft-pdb/blob/master/include/cvinfo.h - -//////////////////////////////// -//~ rjf: CodeView Format Shared Types - -typedef U32 CV_TypeId; -typedef U32 CV_ItemId; -typedef U16 CV_ModIndex; -typedef U16 CV_SectionIndex; -typedef U16 CV_Reg; - -read_only global CV_TypeId cv_type_id_variadic = 0xFFFFFFFF; - -//////////////////////////////// -//~ rjf: Generated Code - -#include "generated/codeview.meta.h" - -//////////////////////////////// -//~ rjf: Registers - -// X(NAME, CODE, (RDI_RegCode_X86) NAME, BYTE_POS, BYTE_SIZE) -#define CV_Reg_X86_XList(X) \ -X(NONE, 0, nil, 0, 0)\ -X(AL, 1, eax, 0, 1)\ -X(CL, 2, ecx, 0, 1)\ -X(DL, 3, edx, 0, 1)\ -X(BL, 4, ebx, 0, 1)\ -X(AH, 5, eax, 1, 1)\ -X(CH, 6, ecx, 1, 1)\ -X(DH, 7, edx, 1, 1)\ -X(BH, 8, ebx, 1, 1)\ -X(AX, 9, eax, 0, 2)\ -X(CX, 10, ecx, 0, 2)\ -X(DX, 11, edx, 0, 2)\ -X(BX, 12, ebx, 0, 2)\ -X(SP, 13, esp, 0, 2)\ -X(BP, 14, ebp, 0, 2)\ -X(SI, 15, esi, 0, 2)\ -X(DI, 16, edi, 0, 2)\ -X(EAX, 17, eax, 0, 4)\ -X(ECX, 18, ecx, 0, 4)\ -X(EDX, 19, edx, 0, 4)\ -X(EBX, 20, ebx, 0, 4)\ -X(ESP, 21, esp, 0, 4)\ -X(EBP, 22, ebp, 0, 4)\ -X(ESI, 23, esi, 0, 4)\ -X(EDI, 24, edi, 0, 4)\ -X(ES, 25, es, 0, 2)\ -X(CS, 26, cs, 0, 2)\ -X(SS, 27, ss, 0, 2)\ -X(DS, 28, ds, 0, 2)\ -X(FS, 29, fs, 0, 2)\ -X(GS, 30, gs, 0, 2)\ -X(IP, 31, eip, 0, 2)\ -X(FLAGS, 32, eflags, 0, 2)\ -X(EIP, 33, eip, 0, 4)\ -X(EFLAGS, 34, eflags, 0, 4)\ -X(MM0, 146, fpr0, 0, 8)\ -X(MM1, 147, fpr1, 0, 8)\ -X(MM2, 148, fpr2, 0, 8)\ -X(MM3, 149, fpr3, 0, 8)\ -X(MM4, 150, fpr4, 0, 8)\ -X(MM5, 151, fpr5, 0, 8)\ -X(MM6, 152, fpr6, 0, 8)\ -X(MM7, 153, fpr7, 0, 8)\ -X(XMM0, 154, ymm0, 0, 16)\ -X(XMM1, 155, ymm1, 0, 16)\ -X(XMM2, 156, ymm2, 0, 16)\ -X(XMM3, 157, ymm3, 0, 16)\ -X(XMM4, 158, ymm4, 0, 16)\ -X(XMM5, 159, ymm5, 0, 16)\ -X(XMM6, 160, ymm6, 0, 16)\ -X(XMM7, 161, ymm7, 0, 16)\ -X(XMM00, 162, ymm0, 0, 4)\ -X(XMM01, 163, ymm0, 4, 4)\ -X(XMM02, 164, ymm0, 8, 4)\ -X(XMM03, 165, ymm0, 12, 4)\ -X(XMM10, 166, ymm1, 0, 4)\ -X(XMM11, 167, ymm1, 4, 4)\ -X(XMM12, 168, ymm1, 8, 4)\ -X(XMM13, 169, ymm1, 12, 4)\ -X(XMM20, 170, ymm2, 0, 4)\ -X(XMM21, 171, ymm2, 4, 4)\ -X(XMM22, 172, ymm2, 8, 4)\ -X(XMM23, 173, ymm2, 12, 4)\ -X(XMM30, 174, ymm3, 0, 4)\ -X(XMM31, 175, ymm3, 4, 4)\ -X(XMM32, 176, ymm3, 8, 4)\ -X(XMM33, 177, ymm3, 12, 4)\ -X(XMM40, 178, ymm4, 0, 4)\ -X(XMM41, 179, ymm4, 4, 4)\ -X(XMM42, 180, ymm4, 8, 4)\ -X(XMM43, 181, ymm4, 12, 4)\ -X(XMM50, 182, ymm5, 0, 4)\ -X(XMM51, 183, ymm5, 4, 4)\ -X(XMM52, 184, ymm5, 8, 4)\ -X(XMM53, 185, ymm5, 12, 4)\ -X(XMM60, 186, ymm6, 0, 4)\ -X(XMM61, 187, ymm6, 4, 4)\ -X(XMM62, 188, ymm6, 8, 4)\ -X(XMM63, 189, ymm6, 12, 4)\ -X(XMM70, 190, ymm7, 0, 4)\ -X(XMM71, 191, ymm7, 4, 4)\ -X(XMM72, 192, ymm7, 8, 4)\ -X(XMM73, 193, ymm7, 12, 4)\ -X(XMM0L, 194, ymm0, 0, 8)\ -X(XMM1L, 195, ymm1, 0, 8)\ -X(XMM2L, 196, ymm2, 0, 8)\ -X(XMM3L, 197, ymm3, 0, 8)\ -X(XMM4L, 198, ymm4, 0, 8)\ -X(XMM5L, 199, ymm5, 0, 8)\ -X(XMM6L, 200, ymm6, 0, 8)\ -X(XMM7L, 201, ymm7, 0, 8)\ -X(XMM0H, 202, ymm0, 8, 8)\ -X(XMM1H, 203, ymm1, 8, 8)\ -X(XMM2H, 204, ymm2, 8, 8)\ -X(XMM3H, 205, ymm3, 8, 8)\ -X(XMM4H, 206, ymm4, 8, 8)\ -X(XMM5H, 207, ymm5, 8, 8)\ -X(XMM6H, 208, ymm6, 8, 8)\ -X(XMM7H, 209, ymm7, 8, 8)\ -X(YMM0, 252, ymm0, 0, 32)\ -X(YMM1, 253, ymm1, 0, 32)\ -X(YMM2, 254, ymm2, 0, 32)\ -X(YMM3, 255, ymm3, 0, 32)\ -X(YMM4, 256, ymm4, 0, 32)\ -X(YMM5, 257, ymm5, 0, 32)\ -X(YMM6, 258, ymm6, 0, 32)\ -X(YMM7, 259, ymm7, 0, 32)\ -X(YMM0H, 260, ymm0, 16, 16)\ -X(YMM1H, 261, ymm1, 16, 16)\ -X(YMM2H, 262, ymm2, 16, 16)\ -X(YMM3H, 263, ymm3, 16, 16)\ -X(YMM4H, 264, ymm4, 16, 16)\ -X(YMM5H, 265, ymm5, 16, 16)\ -X(YMM6H, 266, ymm6, 16, 16)\ -X(YMM7H, 267, ymm7, 16, 16)\ -X(YMM0I0, 268, ymm0, 0, 8)\ -X(YMM0I1, 269, ymm0, 8, 8)\ -X(YMM0I2, 270, ymm0, 16, 8)\ -X(YMM0I3, 271, ymm0, 24, 8)\ -X(YMM1I0, 272, ymm1, 0, 8)\ -X(YMM1I1, 273, ymm1, 8, 8)\ -X(YMM1I2, 274, ymm1, 16, 8)\ -X(YMM1I3, 275, ymm1, 24, 8)\ -X(YMM2I0, 276, ymm2, 0, 8)\ -X(YMM2I1, 277, ymm2, 8, 8)\ -X(YMM2I2, 278, ymm2, 16, 8)\ -X(YMM2I3, 279, ymm2, 24, 8)\ -X(YMM3I0, 280, ymm3, 0, 8)\ -X(YMM3I1, 281, ymm3, 8, 8)\ -X(YMM3I2, 282, ymm3, 16, 8)\ -X(YMM3I3, 283, ymm3, 24, 8)\ -X(YMM4I0, 284, ymm4, 0, 8)\ -X(YMM4I1, 285, ymm4, 8, 8)\ -X(YMM4I2, 286, ymm4, 16, 8)\ -X(YMM4I3, 287, ymm4, 24, 8)\ -X(YMM5I0, 288, ymm5, 0, 8)\ -X(YMM5I1, 289, ymm5, 8, 8)\ -X(YMM5I2, 290, ymm5, 16, 8)\ -X(YMM5I3, 291, ymm5, 24, 8)\ -X(YMM6I0, 292, ymm6, 0, 8)\ -X(YMM6I1, 293, ymm6, 8, 8)\ -X(YMM6I2, 294, ymm6, 16, 8)\ -X(YMM6I3, 295, ymm6, 24, 8)\ -X(YMM7I0, 296, ymm7, 0, 8)\ -X(YMM7I1, 297, ymm7, 8, 8)\ -X(YMM7I2, 298, ymm7, 16, 8)\ -X(YMM7I3, 299, ymm7, 24, 8)\ -X(YMM0F0, 300, ymm0, 0, 4)\ -X(YMM0F1, 301, ymm0, 4, 4)\ -X(YMM0F2, 302, ymm0, 8, 4)\ -X(YMM0F3, 303, ymm0, 12, 4)\ -X(YMM0F4, 304, ymm0, 16, 4)\ -X(YMM0F5, 305, ymm0, 20, 4)\ -X(YMM0F6, 306, ymm0, 24, 4)\ -X(YMM0F7, 307, ymm0, 28, 4)\ -X(YMM1F0, 308, ymm1, 0, 4)\ -X(YMM1F1, 309, ymm1, 4, 4)\ -X(YMM1F2, 310, ymm1, 8, 4)\ -X(YMM1F3, 311, ymm1, 12, 4)\ -X(YMM1F4, 312, ymm1, 16, 4)\ -X(YMM1F5, 313, ymm1, 20, 4)\ -X(YMM1F6, 314, ymm1, 24, 4)\ -X(YMM1F7, 315, ymm1, 28, 4)\ -X(YMM2F0, 316, ymm2, 0, 4)\ -X(YMM2F1, 317, ymm2, 4, 4)\ -X(YMM2F2, 318, ymm2, 8, 4)\ -X(YMM2F3, 319, ymm2, 12, 4)\ -X(YMM2F4, 320, ymm2, 16, 4)\ -X(YMM2F5, 321, ymm2, 20, 4)\ -X(YMM2F6, 322, ymm2, 24, 4)\ -X(YMM2F7, 323, ymm2, 28, 4)\ -X(YMM3F0, 324, ymm3, 0, 4)\ -X(YMM3F1, 325, ymm3, 4, 4)\ -X(YMM3F2, 326, ymm3, 8, 4)\ -X(YMM3F3, 327, ymm3, 12, 4)\ -X(YMM3F4, 328, ymm3, 16, 4)\ -X(YMM3F5, 329, ymm3, 20, 4)\ -X(YMM3F6, 330, ymm3, 24, 4)\ -X(YMM3F7, 331, ymm3, 28, 4)\ -X(YMM4F0, 332, ymm4, 0, 4)\ -X(YMM4F1, 333, ymm4, 4, 4)\ -X(YMM4F2, 334, ymm4, 8, 4)\ -X(YMM4F3, 335, ymm4, 12, 4)\ -X(YMM4F4, 336, ymm4, 16, 4)\ -X(YMM4F5, 337, ymm4, 20, 4)\ -X(YMM4F6, 338, ymm4, 24, 4)\ -X(YMM4F7, 339, ymm4, 28, 4)\ -X(YMM5F0, 340, ymm5, 0, 4)\ -X(YMM5F1, 341, ymm5, 4, 4)\ -X(YMM5F2, 342, ymm5, 8, 4)\ -X(YMM5F3, 343, ymm5, 12, 4)\ -X(YMM5F4, 344, ymm5, 16, 4)\ -X(YMM5F5, 345, ymm5, 20, 4)\ -X(YMM5F6, 346, ymm5, 24, 4)\ -X(YMM5F7, 347, ymm5, 28, 4)\ -X(YMM6F0, 348, ymm6, 0, 4)\ -X(YMM6F1, 349, ymm6, 4, 4)\ -X(YMM6F2, 350, ymm6, 8, 4)\ -X(YMM6F3, 351, ymm6, 12, 4)\ -X(YMM6F4, 352, ymm6, 16, 4)\ -X(YMM6F5, 353, ymm6, 20, 4)\ -X(YMM6F6, 354, ymm6, 24, 4)\ -X(YMM6F7, 355, ymm6, 28, 4)\ -X(YMM7F0, 356, ymm7, 0, 4)\ -X(YMM7F1, 357, ymm7, 4, 4)\ -X(YMM7F2, 358, ymm7, 8, 4)\ -X(YMM7F3, 359, ymm7, 12, 4)\ -X(YMM7F4, 360, ymm7, 16, 4)\ -X(YMM7F5, 361, ymm7, 20, 4)\ -X(YMM7F6, 362, ymm7, 24, 4)\ -X(YMM7F7, 363, ymm7, 28, 4)\ -X(YMM0D0, 364, ymm0, 0, 8)\ -X(YMM0D1, 365, ymm0, 8, 8)\ -X(YMM0D2, 366, ymm0, 16, 8)\ -X(YMM0D3, 367, ymm0, 24, 8)\ -X(YMM1D0, 368, ymm1, 0, 8)\ -X(YMM1D1, 369, ymm1, 8, 8)\ -X(YMM1D2, 370, ymm1, 16, 8)\ -X(YMM1D3, 371, ymm1, 24, 8)\ -X(YMM2D0, 372, ymm2, 0, 8)\ -X(YMM2D1, 373, ymm2, 8, 8)\ -X(YMM2D2, 374, ymm2, 16, 8)\ -X(YMM2D3, 375, ymm2, 24, 8)\ -X(YMM3D0, 376, ymm3, 0, 8)\ -X(YMM3D1, 377, ymm3, 8, 8)\ -X(YMM3D2, 378, ymm3, 16, 8)\ -X(YMM3D3, 379, ymm3, 24, 8)\ -X(YMM4D0, 380, ymm4, 0, 8)\ -X(YMM4D1, 381, ymm4, 8, 8)\ -X(YMM4D2, 382, ymm4, 16, 8)\ -X(YMM4D3, 383, ymm4, 24, 8)\ -X(YMM5D0, 384, ymm5, 0, 8)\ -X(YMM5D1, 385, ymm5, 8, 8)\ -X(YMM5D2, 386, ymm5, 16, 8)\ -X(YMM5D3, 387, ymm5, 24, 8)\ -X(YMM6D0, 388, ymm6, 0, 8)\ -X(YMM6D1, 389, ymm6, 8, 8)\ -X(YMM6D2, 390, ymm6, 16, 8)\ -X(YMM6D3, 391, ymm6, 24, 8)\ -X(YMM7D0, 392, ymm7, 0, 8)\ -X(YMM7D1, 393, ymm7, 8, 8)\ -X(YMM7D2, 394, ymm7, 16, 8)\ -X(YMM7D3, 395, ymm7, 24, 8) - -typedef U16 CV_Regx86; -typedef enum CV_Regx86Enum -{ -#define X(CVN,C,RDN,BP,BZ) CV_Regx86_##CVN = C, - CV_Reg_X86_XList(X) -#undef X -} -CV_Regx86Enum; - -// X(NAME, CODE, (RDI_RegisterCode_X64) NAME, BYTE_POS, BYTE_SIZE) -#define CV_Reg_X64_XList(X) \ -X(NONE, 0, nil, 0, 0)\ -X(AL, 1, rax, 0, 1)\ -X(CL, 2, rcx, 0, 1)\ -X(DL, 3, rdx, 0, 1)\ -X(BL, 4, rbx, 0, 1)\ -X(AH, 5, rax, 1, 1)\ -X(CH, 6, rcx, 1, 1)\ -X(DH, 7, rdx, 1, 1)\ -X(BH, 8, rbx, 1, 1)\ -X(AX, 9, rax, 0, 2)\ -X(CX, 10, rcx, 0, 2)\ -X(DX, 11, rdx, 0, 2)\ -X(BX, 12, rbx, 0, 2)\ -X(SP, 13, rsp, 0, 2)\ -X(BP, 14, rbp, 0, 2)\ -X(SI, 15, rsi, 0, 2)\ -X(DI, 16, rdi, 0, 2)\ -X(EAX, 17, rax, 0, 4)\ -X(ECX, 18, rcx, 0, 4)\ -X(EDX, 19, rdx, 0, 4)\ -X(EBX, 20, rbx, 0, 4)\ -X(ESP, 21, rsp, 0, 4)\ -X(EBP, 22, rbp, 0, 4)\ -X(ESI, 23, rsi, 0, 4)\ -X(EDI, 24, rdi, 0, 4)\ -X(ES, 25, es, 0, 2)\ -X(CS, 26, cs, 0, 2)\ -X(SS, 27, ss, 0, 2)\ -X(DS, 28, ds, 0, 2)\ -X(FS, 29, fs, 0, 2)\ -X(GS, 30, gs, 0, 2)\ -X(FLAGS, 32, rflags, 0, 2)\ -X(RIP, 33, rip, 0, 8)\ -X(EFLAGS, 34, rflags, 0, 4)\ -/* TODO: possibly missing control registers in x64 definitions? */ \ -X(CR0, 80, nil, 0, 0)\ -X(CR1, 81, nil, 0, 0)\ -X(CR2, 82, nil, 0, 0)\ -X(CR3, 83, nil, 0, 0)\ -X(CR4, 84, nil, 0, 0)\ -X(CR8, 88, nil, 0, 0)\ -X(DR0, 90, dr0, 0, 4)\ -X(DR1, 91, dr1, 0, 4)\ -X(DR2, 92, dr2, 0, 4)\ -X(DR3, 93, dr3, 0, 4)\ -X(DR4, 94, dr4, 0, 4)\ -X(DR5, 95, dr5, 0, 4)\ -X(DR6, 96, dr6, 0, 4)\ -X(DR7, 97, dr7, 0, 4)\ -/* TODO: possibly missing debug registers 8-15 in x64 definitions? */ \ -X(DR8, 98, nil, 0, 0)\ -X(DR9, 99, nil, 0, 0)\ -X(DR10, 100, nil, 0, 0)\ -X(DR11, 101, nil, 0, 0)\ -X(DR12, 102, nil, 0, 0)\ -X(DR13, 103, nil, 0, 0)\ -X(DR14, 104, nil, 0, 0)\ -X(DR15, 105, nil, 0, 0)\ -/* TODO: possibly missing ~whatever these are~ in x64 definitions? */ \ -X(GDTR, 110, nil, 0, 0)\ -X(GDTL, 111, nil, 0, 0)\ -X(IDTR, 112, nil, 0, 0)\ -X(IDTL, 113, nil, 0, 0)\ -X(LDTR, 114, nil, 0, 0)\ -X(TR, 115, nil, 0, 0)\ -X(ST0, 128, st0, 0, 10)\ -X(ST1, 129, st1, 0, 10)\ -X(ST2, 130, st2, 0, 10)\ -X(ST3, 131, st3, 0, 10)\ -X(ST4, 132, st4, 0, 10)\ -X(ST5, 133, st5, 0, 10)\ -X(ST6, 134, st6, 0, 10)\ -X(ST7, 135, st7, 0, 10)\ -/* TODO: possibly missing these, or not sure how they map to our x64 definitions? */ \ -X(CTRL, 136, nil, 0, 0)\ -X(STAT, 137, nil, 0, 0)\ -X(TAG, 138, nil, 0, 0)\ -X(FPIP, 139, nil, 0, 0)\ -X(FPCS, 140, nil, 0, 0)\ -X(FPDO, 141, nil, 0, 0)\ -X(FPDS, 142, nil, 0, 0)\ -X(ISEM, 143, nil, 0, 0)\ -X(FPEIP, 144, nil, 0, 0)\ -X(FPEDO, 145, nil, 0, 0)\ -X(MM0, 146, fpr0, 0, 8)\ -X(MM1, 147, fpr1, 0, 8)\ -X(MM2, 148, fpr2, 0, 8)\ -X(MM3, 149, fpr3, 0, 8)\ -X(MM4, 150, fpr4, 0, 8)\ -X(MM5, 151, fpr5, 0, 8)\ -X(MM6, 152, fpr6, 0, 8)\ -X(MM7, 153, fpr7, 0, 8)\ -X(XMM0, 154, ymm0, 0, 16)\ -X(XMM1, 155, ymm1, 0, 16)\ -X(XMM2, 156, ymm2, 0, 16)\ -X(XMM3, 157, ymm3, 0, 16)\ -X(XMM4, 158, ymm4, 0, 16)\ -X(XMM5, 159, ymm5, 0, 16)\ -X(XMM6, 160, ymm6, 0, 16)\ -X(XMM7, 161, ymm7, 0, 16)\ -X(XMM0_0, 162, ymm0, 0, 4)\ -X(XMM0_1, 163, ymm0, 4, 4)\ -X(XMM0_2, 164, ymm0, 8, 4)\ -X(XMM0_3, 165, ymm0, 12, 4)\ -X(XMM1_0, 166, ymm1, 0, 4)\ -X(XMM1_1, 167, ymm1, 4, 4)\ -X(XMM1_2, 168, ymm1, 8, 4)\ -X(XMM1_3, 169, ymm1, 12, 4)\ -X(XMM2_0, 170, ymm2, 0, 4)\ -X(XMM2_1, 171, ymm2, 4, 4)\ -X(XMM2_2, 172, ymm2, 8, 4)\ -X(XMM2_3, 173, ymm2, 12, 4)\ -X(XMM3_0, 174, ymm3, 0, 4)\ -X(XMM3_1, 175, ymm3, 4, 4)\ -X(XMM3_2, 176, ymm3, 8, 4)\ -X(XMM3_3, 177, ymm3, 12, 4)\ -X(XMM4_0, 178, ymm4, 0, 4)\ -X(XMM4_1, 179, ymm4, 4, 4)\ -X(XMM4_2, 180, ymm4, 8, 4)\ -X(XMM4_3, 181, ymm4, 12, 4)\ -X(XMM5_0, 182, ymm5, 0, 4)\ -X(XMM5_1, 183, ymm5, 4, 4)\ -X(XMM5_2, 184, ymm5, 8, 4)\ -X(XMM5_3, 185, ymm5, 12, 4)\ -X(XMM6_0, 186, ymm6, 0, 4)\ -X(XMM6_1, 187, ymm6, 4, 4)\ -X(XMM6_2, 188, ymm6, 8, 4)\ -X(XMM6_3, 189, ymm6, 12, 4)\ -X(XMM7_0, 190, ymm7, 0, 4)\ -X(XMM7_1, 191, ymm7, 4, 4)\ -X(XMM7_2, 192, ymm7, 8, 4)\ -X(XMM7_3, 193, ymm7, 12, 4)\ -X(XMM0L, 194, ymm0, 0, 8)\ -X(XMM1L, 195, ymm1, 0, 8)\ -X(XMM2L, 196, ymm2, 0, 8)\ -X(XMM3L, 197, ymm3, 0, 8)\ -X(XMM4L, 198, ymm4, 0, 8)\ -X(XMM5L, 199, ymm5, 0, 8)\ -X(XMM6L, 200, ymm6, 0, 8)\ -X(XMM7L, 201, ymm7, 0, 8)\ -X(XMM0H, 202, ymm0, 8, 8)\ -X(XMM1H, 203, ymm1, 8, 8)\ -X(XMM2H, 204, ymm2, 8, 8)\ -X(XMM3H, 205, ymm3, 8, 8)\ -X(XMM4H, 206, ymm4, 8, 8)\ -X(XMM5H, 207, ymm5, 8, 8)\ -X(XMM6H, 208, ymm6, 8, 8)\ -X(XMM7H, 209, ymm7, 8, 8)\ -X(MXCSR, 211, mxcsr, 0, 4)\ -X(EMM0L, 220, ymm0, 0, 8)\ -X(EMM1L, 221, ymm1, 0, 8)\ -X(EMM2L, 222, ymm2, 0, 8)\ -X(EMM3L, 223, ymm3, 0, 8)\ -X(EMM4L, 224, ymm4, 0, 8)\ -X(EMM5L, 225, ymm5, 0, 8)\ -X(EMM6L, 226, ymm6, 0, 8)\ -X(EMM7L, 227, ymm7, 0, 8)\ -X(EMM0H, 228, ymm0, 8, 8)\ -X(EMM1H, 229, ymm1, 8, 8)\ -X(EMM2H, 230, ymm2, 8, 8)\ -X(EMM3H, 231, ymm3, 8, 8)\ -X(EMM4H, 232, ymm4, 8, 8)\ -X(EMM5H, 233, ymm5, 8, 8)\ -X(EMM6H, 234, ymm6, 8, 8)\ -X(EMM7H, 235, ymm7, 8, 8)\ -X(MM00, 236, fpr0, 0, 4)\ -X(MM01, 237, fpr0, 4, 4)\ -X(MM10, 238, fpr1, 0, 4)\ -X(MM11, 239, fpr1, 4, 4)\ -X(MM20, 240, fpr2, 0, 4)\ -X(MM21, 241, fpr2, 4, 4)\ -X(MM30, 242, fpr3, 0, 4)\ -X(MM31, 243, fpr3, 4, 4)\ -X(MM40, 244, fpr4, 0, 4)\ -X(MM41, 245, fpr4, 4, 4)\ -X(MM50, 246, fpr5, 0, 4)\ -X(MM51, 247, fpr5, 4, 4)\ -X(MM60, 248, fpr6, 0, 4)\ -X(MM61, 249, fpr6, 4, 4)\ -X(MM70, 250, fpr7, 0, 4)\ -X(MM71, 251, fpr7, 4, 4)\ -X(XMM8, 252, ymm8, 0, 16)\ -X(XMM9, 253, ymm9, 0, 16)\ -X(XMM10, 254, ymm10, 0, 16)\ -X(XMM11, 255, ymm11, 0, 16)\ -X(XMM12, 256, ymm12, 0, 16)\ -X(XMM13, 257, ymm13, 0, 16)\ -X(XMM14, 258, ymm14, 0, 16)\ -X(XMM15, 259, ymm15, 0, 16)\ -X(XMM8_0, 260, ymm8, 0, 16)\ -X(XMM8_1, 261, ymm8, 4, 16)\ -X(XMM8_2, 262, ymm8, 8, 16)\ -X(XMM8_3, 263, ymm8, 12, 16)\ -X(XMM9_0, 264, ymm9, 0, 4)\ -X(XMM9_1, 265, ymm9, 4, 4)\ -X(XMM9_2, 266, ymm9, 8, 4)\ -X(XMM9_3, 267, ymm9, 12, 4)\ -X(XMM10_0, 268, ymm10, 0, 4)\ -X(XMM10_1, 269, ymm10, 4, 4)\ -X(XMM10_2, 270, ymm10, 8, 4)\ -X(XMM10_3, 271, ymm10, 12, 4)\ -X(XMM11_0, 272, ymm11, 0, 4)\ -X(XMM11_1, 273, ymm11, 4, 4)\ -X(XMM11_2, 274, ymm11, 8, 4)\ -X(XMM11_3, 275, ymm11, 12, 4)\ -X(XMM12_0, 276, ymm12, 0, 4)\ -X(XMM12_1, 277, ymm12, 4, 4)\ -X(XMM12_2, 278, ymm12, 8, 4)\ -X(XMM12_3, 279, ymm12, 12, 4)\ -X(XMM13_0, 280, ymm13, 0, 4)\ -X(XMM13_1, 281, ymm13, 4, 4)\ -X(XMM13_2, 282, ymm13, 8, 4)\ -X(XMM13_3, 283, ymm13, 12, 4)\ -X(XMM14_0, 284, ymm14, 0, 4)\ -X(XMM14_1, 285, ymm14, 4, 4)\ -X(XMM14_2, 286, ymm14, 8, 4)\ -X(XMM14_3, 287, ymm14, 12, 4)\ -X(XMM15_0, 288, ymm15, 0, 4)\ -X(XMM15_1, 289, ymm15, 4, 4)\ -X(XMM15_2, 290, ymm15, 8, 4)\ -X(XMM15_3, 291, ymm15, 12, 4)\ -X(XMM8L, 292, ymm8, 0, 8)\ -X(XMM9L, 293, ymm9, 0, 8)\ -X(XMM10L, 294, ymm10, 0, 8)\ -X(XMM11L, 295, ymm11, 0, 8)\ -X(XMM12L, 296, ymm12, 0, 8)\ -X(XMM13L, 297, ymm13, 0, 8)\ -X(XMM14L, 298, ymm14, 0, 8)\ -X(XMM15L, 299, ymm15, 0, 8)\ -X(XMM8H, 300, ymm8, 8, 8)\ -X(XMM9H, 301, ymm9, 8, 8)\ -X(XMM10H, 302, ymm10, 8, 8)\ -X(XMM11H, 303, ymm11, 8, 8)\ -X(XMM12H, 304, ymm12, 8, 8)\ -X(XMM13H, 305, ymm13, 8, 8)\ -X(XMM14H, 306, ymm14, 8, 8)\ -X(XMM15H, 307, ymm15, 8, 8)\ -X(EMM8L, 308, ymm8, 0, 8)\ -X(EMM9L, 309, ymm9, 0, 8)\ -X(EMM10L, 310, ymm10, 0, 8)\ -X(EMM11L, 311, ymm11, 0, 8)\ -X(EMM12L, 312, ymm12, 0, 8)\ -X(EMM13L, 313, ymm13, 0, 8)\ -X(EMM14L, 314, ymm14, 0, 8)\ -X(EMM15L, 315, ymm15, 0, 8)\ -X(EMM8H, 316, ymm8, 8, 8)\ -X(EMM9H, 317, ymm9, 8, 8)\ -X(EMM10H, 318, ymm10, 8, 8)\ -X(EMM11H, 319, ymm11, 8, 8)\ -X(EMM12H, 320, ymm12, 8, 8)\ -X(EMM13H, 321, ymm13, 8, 8)\ -X(EMM14H, 322, ymm14, 8, 8)\ -X(EMM15H, 323, ymm15, 8, 8)\ -X(SIL, 324, rsi, 0, 1)\ -X(DIL, 325, rdi, 0, 1)\ -X(BPL, 326, rbp, 0, 1)\ -X(SPL, 327, rsp, 0, 1)\ -X(RAX, 328, rax, 0, 8)\ -X(RBX, 329, rbx, 0, 8)\ -X(RCX, 330, rcx, 0, 8)\ -X(RDX, 331, rdx, 0, 8)\ -X(RSI, 332, rsi, 0, 8)\ -X(RDI, 333, rdi, 0, 8)\ -X(RBP, 334, rbp, 0, 8)\ -X(RSP, 335, rsp, 0, 8)\ -X(R8, 336, r8, 0, 8)\ -X(R9, 337, r9, 0, 8)\ -X(R10, 338, r10, 0, 8)\ -X(R11, 339, r11, 0, 8)\ -X(R12, 340, r12, 0, 8)\ -X(R13, 341, r13, 0, 8)\ -X(R14, 342, r14, 0, 8)\ -X(R15, 343, r15, 0, 8)\ -X(R8B, 344, r8, 0, 1)\ -X(R9B, 345, r9, 0, 1)\ -X(R10B, 346, r10, 0, 1)\ -X(R11B, 347, r11, 0, 1)\ -X(R12B, 348, r12, 0, 1)\ -X(R13B, 349, r13, 0, 1)\ -X(R14B, 350, r14, 0, 1)\ -X(R15B, 351, r15, 0, 1)\ -X(R8W, 352, r8, 0, 2)\ -X(R9W, 353, r9, 0, 2)\ -X(R10W, 354, r10, 0, 2)\ -X(R11W, 355, r11, 0, 2)\ -X(R12W, 356, r12, 0, 2)\ -X(R13W, 357, r13, 0, 2)\ -X(R14W, 358, r14, 0, 2)\ -X(R15W, 359, r15, 0, 2)\ -X(R8D, 360, r8, 0, 4)\ -X(R9D, 361, r9, 0, 4)\ -X(R10D, 362, r10, 0, 4)\ -X(R11D, 363, r11, 0, 4)\ -X(R12D, 364, r12, 0, 4)\ -X(R13D, 365, r13, 0, 4)\ -X(R14D, 366, r14, 0, 4)\ -X(R15D, 367, r15, 0, 4)\ -X(YMM0, 368, ymm0, 0, 32)\ -X(YMM1, 369, ymm1, 0, 32)\ -X(YMM2, 370, ymm2, 0, 32)\ -X(YMM3, 371, ymm3, 0, 32)\ -X(YMM4, 372, ymm4, 0, 32)\ -X(YMM5, 373, ymm5, 0, 32)\ -X(YMM6, 374, ymm6, 0, 32)\ -X(YMM7, 375, ymm7, 0, 32)\ -X(YMM8, 376, ymm8, 0, 32)\ -X(YMM9, 377, ymm9, 0, 32)\ -X(YMM10, 378, ymm10, 0, 32)\ -X(YMM11, 379, ymm11, 0, 32)\ -X(YMM12, 380, ymm12, 0, 32)\ -X(YMM13, 381, ymm13, 0, 32)\ -X(YMM14, 382, ymm14, 0, 32)\ -X(YMM15, 383, ymm15, 0, 32)\ -X(YMM0H, 384, ymm0, 16, 32)\ -X(YMM1H, 385, ymm1, 16, 32)\ -X(YMM2H, 386, ymm2, 16, 32)\ -X(YMM3H, 387, ymm3, 16, 32)\ -X(YMM4H, 388, ymm4, 16, 32)\ -X(YMM5H, 389, ymm5, 16, 32)\ -X(YMM6H, 390, ymm6, 16, 32)\ -X(YMM7H, 391, ymm7, 16, 32)\ -X(YMM8H, 392, ymm8, 16, 32)\ -X(YMM9H, 393, ymm9, 16, 32)\ -X(YMM10H, 394, ymm10, 16, 32)\ -X(YMM11H, 395, ymm11, 16, 32)\ -X(YMM12H, 396, ymm12, 16, 32)\ -X(YMM13H, 397, ymm13, 16, 32)\ -X(YMM14H, 398, ymm14, 16, 32)\ -X(YMM15H, 399, ymm15, 16, 32)\ -X(XMM0IL, 400, ymm0, 0, 8)\ -X(XMM1IL, 401, ymm1, 0, 8)\ -X(XMM2IL, 402, ymm2, 0, 8)\ -X(XMM3IL, 403, ymm3, 0, 8)\ -X(XMM4IL, 404, ymm4, 0, 8)\ -X(XMM5IL, 405, ymm5, 0, 8)\ -X(XMM6IL, 406, ymm6, 0, 8)\ -X(XMM7IL, 407, ymm7, 0, 8)\ -X(XMM8IL, 408, ymm8, 0, 8)\ -X(XMM9IL, 409, ymm9, 0, 8)\ -X(XMM10IL, 410, ymm10, 0, 8)\ -X(XMM11IL, 411, ymm11, 0, 8)\ -X(XMM12IL, 412, ymm12, 0, 8)\ -X(XMM13IL, 413, ymm13, 0, 8)\ -X(XMM14IL, 414, ymm14, 0, 8)\ -X(XMM15IL, 415, ymm15, 0, 8)\ -X(XMM0IH, 416, ymm0, 8, 8)\ -X(XMM1IH, 417, ymm1, 8, 8)\ -X(XMM2IH, 418, ymm2, 8, 8)\ -X(XMM3IH, 419, ymm3, 8, 8)\ -X(XMM4IH, 420, ymm4, 8, 8)\ -X(XMM5IH, 421, ymm5, 8, 8)\ -X(XMM6IH, 422, ymm6, 8, 8)\ -X(XMM7IH, 423, ymm7, 8, 8)\ -X(XMM8IH, 424, ymm8, 8, 8)\ -X(XMM9IH, 425, ymm9, 8, 8)\ -X(XMM10IH, 426, ymm10, 8, 8)\ -X(XMM11IH, 427, ymm11, 8, 8)\ -X(XMM12IH, 428, ymm12, 8, 8)\ -X(XMM13IH, 429, ymm13, 8, 8)\ -X(XMM14IH, 430, ymm14, 8, 8)\ -X(XMM15IH, 431, ymm15, 8, 8)\ -X(YMM0I0, 432, ymm0, 0, 8)\ -X(YMM0I1, 433, ymm0, 8, 8)\ -X(YMM0I2, 434, ymm0, 16, 8)\ -X(YMM0I3, 435, ymm0, 24, 8)\ -X(YMM1I0, 436, ymm1, 0, 8)\ -X(YMM1I1, 437, ymm1, 8, 8)\ -X(YMM1I2, 438, ymm1, 16, 8)\ -X(YMM1I3, 439, ymm1, 24, 8)\ -X(YMM2I0, 440, ymm2, 0, 8)\ -X(YMM2I1, 441, ymm2, 8, 8)\ -X(YMM2I2, 442, ymm2, 16, 8)\ -X(YMM2I3, 443, ymm2, 24, 8)\ -X(YMM3I0, 444, ymm3, 0, 8)\ -X(YMM3I1, 445, ymm3, 8, 8)\ -X(YMM3I2, 446, ymm3, 16, 8)\ -X(YMM3I3, 447, ymm3, 24, 8)\ -X(YMM4I0, 448, ymm4, 0, 8)\ -X(YMM4I1, 449, ymm4, 8, 8)\ -X(YMM4I2, 450, ymm4, 16, 8)\ -X(YMM4I3, 451, ymm4, 24, 8)\ -X(YMM5I0, 452, ymm5, 0, 8)\ -X(YMM5I1, 453, ymm5, 8, 8)\ -X(YMM5I2, 454, ymm5, 16, 8)\ -X(YMM5I3, 455, ymm5, 24, 8)\ -X(YMM6I0, 456, ymm6, 0, 8)\ -X(YMM6I1, 457, ymm6, 8, 8)\ -X(YMM6I2, 458, ymm6, 16, 8)\ -X(YMM6I3, 459, ymm6, 24, 8)\ -X(YMM7I0, 460, ymm7, 0, 8)\ -X(YMM7I1, 461, ymm7, 8, 8)\ -X(YMM7I2, 462, ymm7, 16, 8)\ -X(YMM7I3, 463, ymm7, 24, 8)\ -X(YMM8I0, 464, ymm8, 0, 8)\ -X(YMM8I1, 465, ymm8, 8, 8)\ -X(YMM8I2, 466, ymm8, 16, 8)\ -X(YMM8I3, 467, ymm8, 24, 8)\ -X(YMM9I0, 468, ymm9, 0, 8)\ -X(YMM9I1, 469, ymm9, 8, 8)\ -X(YMM9I2, 470, ymm9, 16, 8)\ -X(YMM9I3, 471, ymm9, 24, 8)\ -X(YMM10I0, 472, ymm10, 0, 8)\ -X(YMM10I1, 473, ymm10, 8, 8)\ -X(YMM10I2, 474, ymm10, 16, 8)\ -X(YMM10I3, 475, ymm10, 24, 8)\ -X(YMM11I0, 476, ymm11, 0, 8)\ -X(YMM11I1, 477, ymm11, 8, 8)\ -X(YMM11I2, 478, ymm11, 16, 8)\ -X(YMM11I3, 479, ymm11, 24, 8)\ -X(YMM12I0, 480, ymm12, 0, 8)\ -X(YMM12I1, 481, ymm12, 8, 8)\ -X(YMM12I2, 482, ymm12, 16, 8)\ -X(YMM12I3, 483, ymm12, 24, 8)\ -X(YMM13I0, 484, ymm13, 0, 8)\ -X(YMM13I1, 485, ymm13, 8, 8)\ -X(YMM13I2, 486, ymm13, 16, 8)\ -X(YMM13I3, 487, ymm13, 24, 8)\ -X(YMM14I0, 488, ymm14, 0, 8)\ -X(YMM14I1, 489, ymm14, 8, 8)\ -X(YMM14I2, 490, ymm14, 16, 8)\ -X(YMM14I3, 491, ymm14, 24, 8)\ -X(YMM15I0, 492, ymm15, 0, 8)\ -X(YMM15I1, 493, ymm15, 8, 8)\ -X(YMM15I2, 494, ymm15, 16, 8)\ -X(YMM15I3, 495, ymm15, 24, 8)\ -X(YMM0F0, 496, ymm0, 0, 4)\ -X(YMM0F1, 497, ymm0, 4, 4)\ -X(YMM0F2, 498, ymm0, 8, 4)\ -X(YMM0F3, 499, ymm0, 12, 4)\ -X(YMM0F4, 500, ymm0, 16, 4)\ -X(YMM0F5, 501, ymm0, 20, 4)\ -X(YMM0F6, 502, ymm0, 24, 4)\ -X(YMM0F7, 503, ymm0, 28, 4)\ -X(YMM1F0, 504, ymm1, 0, 4)\ -X(YMM1F1, 505, ymm1, 4, 4)\ -X(YMM1F2, 506, ymm1, 8, 4)\ -X(YMM1F3, 507, ymm1, 12, 4)\ -X(YMM1F4, 508, ymm1, 16, 4)\ -X(YMM1F5, 509, ymm1, 20, 4)\ -X(YMM1F6, 510, ymm1, 24, 4)\ -X(YMM1F7, 511, ymm1, 28, 4)\ -X(YMM2F0, 512, ymm2, 0, 4)\ -X(YMM2F1, 513, ymm2, 4, 4)\ -X(YMM2F2, 514, ymm2, 8, 4)\ -X(YMM2F3, 515, ymm2, 12, 4)\ -X(YMM2F4, 516, ymm2, 16, 4)\ -X(YMM2F5, 517, ymm2, 20, 4)\ -X(YMM2F6, 518, ymm2, 24, 4)\ -X(YMM2F7, 519, ymm2, 28, 4)\ -X(YMM3F0, 520, ymm3, 0, 4)\ -X(YMM3F1, 521, ymm3, 4, 4)\ -X(YMM3F2, 522, ymm3, 8, 4)\ -X(YMM3F3, 523, ymm3, 12, 4)\ -X(YMM3F4, 524, ymm3, 16, 4)\ -X(YMM3F5, 525, ymm3, 20, 4)\ -X(YMM3F6, 526, ymm3, 24, 4)\ -X(YMM3F7, 527, ymm3, 28, 4)\ -X(YMM4F0, 528, ymm4, 0, 4)\ -X(YMM4F1, 529, ymm4, 4, 4)\ -X(YMM4F2, 530, ymm4, 8, 4)\ -X(YMM4F3, 531, ymm4, 12, 4)\ -X(YMM4F4, 532, ymm4, 16, 4)\ -X(YMM4F5, 533, ymm4, 20, 4)\ -X(YMM4F6, 534, ymm4, 24, 4)\ -X(YMM4F7, 535, ymm4, 28, 4)\ -X(YMM5F0, 536, ymm5, 0, 4)\ -X(YMM5F1, 537, ymm5, 4, 4)\ -X(YMM5F2, 538, ymm5, 8, 4)\ -X(YMM5F3, 539, ymm5, 12, 4)\ -X(YMM5F4, 540, ymm5, 16, 4)\ -X(YMM5F5, 541, ymm5, 20, 4)\ -X(YMM5F6, 542, ymm5, 24, 4)\ -X(YMM5F7, 543, ymm5, 28, 4)\ -X(YMM6F0, 544, ymm6, 0, 4)\ -X(YMM6F1, 545, ymm6, 4, 4)\ -X(YMM6F2, 546, ymm6, 8, 4)\ -X(YMM6F3, 547, ymm6, 12, 4)\ -X(YMM6F4, 548, ymm6, 16, 4)\ -X(YMM6F5, 549, ymm6, 20, 4)\ -X(YMM6F6, 550, ymm6, 24, 4)\ -X(YMM6F7, 551, ymm6, 28, 4)\ -X(YMM7F0, 552, ymm7, 0, 4)\ -X(YMM7F1, 553, ymm7, 4, 4)\ -X(YMM7F2, 554, ymm7, 8, 4)\ -X(YMM7F3, 555, ymm7, 12, 4)\ -X(YMM7F4, 556, ymm7, 16, 4)\ -X(YMM7F5, 557, ymm7, 20, 4)\ -X(YMM7F6, 558, ymm7, 24, 4)\ -X(YMM7F7, 559, ymm7, 28, 4)\ -X(YMM8F0, 560, ymm8, 0, 4)\ -X(YMM8F1, 561, ymm8, 4, 4)\ -X(YMM8F2, 562, ymm8, 8, 4)\ -X(YMM8F3, 563, ymm8, 12, 4)\ -X(YMM8F4, 564, ymm8, 16, 4)\ -X(YMM8F5, 565, ymm8, 20, 4)\ -X(YMM8F6, 566, ymm8, 24, 4)\ -X(YMM8F7, 567, ymm8, 28, 4)\ -X(YMM9F0, 568, ymm9, 0, 4)\ -X(YMM9F1, 569, ymm9, 4, 4)\ -X(YMM9F2, 570, ymm9, 8, 4)\ -X(YMM9F3, 571, ymm9, 12, 4)\ -X(YMM9F4, 572, ymm9, 16, 4)\ -X(YMM9F5, 573, ymm9, 20, 4)\ -X(YMM9F6, 574, ymm9, 24, 4)\ -X(YMM9F7, 575, ymm9, 28, 4)\ -X(YMM10F0, 576, ymm10, 0, 4)\ -X(YMM10F1, 577, ymm10, 4, 4)\ -X(YMM10F2, 578, ymm10, 8, 4)\ -X(YMM10F3, 579, ymm10, 12, 4)\ -X(YMM10F4, 580, ymm10, 16, 4)\ -X(YMM10F5, 581, ymm10, 20, 4)\ -X(YMM10F6, 582, ymm10, 24, 4)\ -X(YMM10F7, 583, ymm10, 28, 4)\ -X(YMM11F0, 584, ymm11, 0, 4)\ -X(YMM11F1, 585, ymm11, 4, 4)\ -X(YMM11F2, 586, ymm11, 8, 4)\ -X(YMM11F3, 587, ymm11, 12, 4)\ -X(YMM11F4, 588, ymm11, 16, 4)\ -X(YMM11F5, 589, ymm11, 20, 4)\ -X(YMM11F6, 590, ymm11, 24, 4)\ -X(YMM11F7, 591, ymm11, 28, 4)\ -X(YMM12F0, 592, ymm12, 0, 4)\ -X(YMM12F1, 593, ymm12, 4, 4)\ -X(YMM12F2, 594, ymm12, 8, 4)\ -X(YMM12F3, 595, ymm12, 12, 4)\ -X(YMM12F4, 596, ymm12, 16, 4)\ -X(YMM12F5, 597, ymm12, 20, 4)\ -X(YMM12F6, 598, ymm12, 24, 4)\ -X(YMM12F7, 599, ymm12, 28, 4)\ -X(YMM13F0, 600, ymm13, 0, 4)\ -X(YMM13F1, 601, ymm13, 4, 4)\ -X(YMM13F2, 602, ymm13, 8, 4)\ -X(YMM13F3, 603, ymm13, 12, 4)\ -X(YMM13F4, 604, ymm13, 16, 4)\ -X(YMM13F5, 605, ymm13, 20, 4)\ -X(YMM13F6, 606, ymm13, 24, 4)\ -X(YMM13F7, 607, ymm13, 28, 4)\ -X(YMM14F0, 608, ymm14, 0, 4)\ -X(YMM14F1, 609, ymm14, 4, 4)\ -X(YMM14F2, 610, ymm14, 8, 4)\ -X(YMM14F3, 611, ymm14, 12, 4)\ -X(YMM14F4, 612, ymm14, 16, 4)\ -X(YMM14F5, 613, ymm14, 20, 4)\ -X(YMM14F6, 614, ymm14, 24, 4)\ -X(YMM14F7, 615, ymm14, 28, 4)\ -X(YMM15F0, 616, ymm15, 0, 4)\ -X(YMM15F1, 617, ymm15, 4, 4)\ -X(YMM15F2, 618, ymm15, 8, 4)\ -X(YMM15F3, 619, ymm15, 12, 4)\ -X(YMM15F4, 620, ymm15, 16, 4)\ -X(YMM15F5, 621, ymm15, 20, 4)\ -X(YMM15F6, 622, ymm15, 24, 4)\ -X(YMM15F7, 623, ymm15, 28, 4)\ -X(YMM0D0, 624, ymm0, 0, 8)\ -X(YMM0D1, 625, ymm0, 8, 8)\ -X(YMM0D2, 626, ymm0, 16, 8)\ -X(YMM0D3, 627, ymm0, 24, 8)\ -X(YMM1D0, 628, ymm1, 0, 8)\ -X(YMM1D1, 629, ymm1, 8, 8)\ -X(YMM1D2, 630, ymm1, 16, 8)\ -X(YMM1D3, 631, ymm1, 24, 8)\ -X(YMM2D0, 632, ymm2, 0, 8)\ -X(YMM2D1, 633, ymm2, 8, 8)\ -X(YMM2D2, 634, ymm2, 16, 8)\ -X(YMM2D3, 635, ymm2, 24, 8)\ -X(YMM3D0, 636, ymm3, 0, 8)\ -X(YMM3D1, 637, ymm3, 8, 8)\ -X(YMM3D2, 638, ymm3, 16, 8)\ -X(YMM3D3, 639, ymm3, 24, 8)\ -X(YMM4D0, 640, ymm4, 0, 8)\ -X(YMM4D1, 641, ymm4, 8, 8)\ -X(YMM4D2, 642, ymm4, 16, 8)\ -X(YMM4D3, 643, ymm4, 24, 8)\ -X(YMM5D0, 644, ymm5, 0, 8)\ -X(YMM5D1, 645, ymm5, 8, 8)\ -X(YMM5D2, 646, ymm5, 16, 8)\ -X(YMM5D3, 647, ymm5, 24, 8)\ -X(YMM6D0, 648, ymm6, 0, 8)\ -X(YMM6D1, 649, ymm6, 8, 8)\ -X(YMM6D2, 650, ymm6, 16, 8)\ -X(YMM6D3, 651, ymm6, 24, 8)\ -X(YMM7D0, 652, ymm7, 0, 8)\ -X(YMM7D1, 653, ymm7, 8, 8)\ -X(YMM7D2, 654, ymm7, 16, 8)\ -X(YMM7D3, 655, ymm7, 24, 8)\ -X(YMM8D0, 656, ymm8, 0, 8)\ -X(YMM8D1, 657, ymm8, 8, 8)\ -X(YMM8D2, 658, ymm8, 16, 8)\ -X(YMM8D3, 659, ymm8, 24, 8)\ -X(YMM9D0, 660, ymm9, 0, 8)\ -X(YMM9D1, 661, ymm9, 8, 8)\ -X(YMM9D2, 662, ymm9, 16, 8)\ -X(YMM9D3, 663, ymm9, 24, 8)\ -X(YMM10D0, 664, ymm10, 0, 8)\ -X(YMM10D1, 665, ymm10, 8, 8)\ -X(YMM10D2, 666, ymm10, 16, 8)\ -X(YMM10D3, 667, ymm10, 24, 8)\ -X(YMM11D0, 668, ymm11, 0, 8)\ -X(YMM11D1, 669, ymm11, 8, 8)\ -X(YMM11D2, 670, ymm11, 16, 8)\ -X(YMM11D3, 671, ymm11, 24, 8)\ -X(YMM12D0, 672, ymm12, 0, 8)\ -X(YMM12D1, 673, ymm12, 8, 8)\ -X(YMM12D2, 674, ymm12, 16, 8)\ -X(YMM12D3, 675, ymm12, 24, 8)\ -X(YMM13D0, 676, ymm13, 0, 8)\ -X(YMM13D1, 677, ymm13, 8, 8)\ -X(YMM13D2, 678, ymm13, 16, 8)\ -X(YMM13D3, 679, ymm13, 24, 8)\ -X(YMM14D0, 680, ymm14, 0, 8)\ -X(YMM14D1, 681, ymm14, 8, 8)\ -X(YMM14D2, 682, ymm14, 16, 8)\ -X(YMM14D3, 683, ymm14, 24, 8)\ -X(YMM15D0, 684, ymm15, 0, 8)\ -X(YMM15D1, 685, ymm15, 8, 8)\ -X(YMM15D2, 686, ymm15, 16, 8)\ -X(YMM15D3, 687, ymm15, 24, 8) - -typedef U16 CV_Regx64; -typedef enum CV_Regx64Enum -{ -#define X(CVN,C,RDN,BP,BZ) CV_Regx64_##CVN = C, - CV_Reg_X64_XList(X) -#undef X -} -CV_Regx64Enum; - - -#define CV_SignatureXList(X) \ -X(C6, 0)\ -X(C7, 1)\ -X(C11, 2)\ -X(C13, 4)\ -X(RESERVED, 5) - -typedef U16 CV_Signature; -typedef enum CV_SignatureEnum -{ -#define X(N,c) CV_Signature_##N = c, - CV_SignatureXList(X) -#undef X -} -CV_SignatureEnum; - - -#define CV_LanguageXList(X) \ -X(C, 0x00)\ -X(CXX, 0x01)\ -X(FORTRAN, 0x02)\ -X(MASM, 0x03)\ -X(PASCAL, 0x04)\ -X(BASIC, 0x05)\ -X(COBOL, 0x06)\ -X(LINK, 0x07)\ -X(CVTRES, 0x08)\ -X(CVTPGD, 0x09)\ -X(CSHARP, 0x0A)\ -X(VB, 0x0B)\ -X(ILASM, 0x0C)\ -X(JAVA, 0x0D)\ -X(JSCRIPT, 0x0E)\ -X(MSIL, 0x0F)\ -X(HLSL, 0x10) - -typedef U16 CV_Language; -typedef enum CV_LanguageEnum -{ -#define X(N,c) CV_Language_##N = c, - CV_LanguageXList(X) -#undef X -} -CV_LanguageEnum; - -//////////////////////////////// -//~ rjf: CodeView Format "Sym" and "Leaf" Header Type - -typedef struct CV_RecHeader CV_RecHeader; -struct CV_RecHeader -{ - U16 size; - U16 kind; -}; - -//////////////////////////////// -//~ rjf: CodeView Format "Sym" Types -// -// (per-compilation-unit info, variables, procedures, etc.) -// - -typedef U8 CV_ProcFlags; -enum -{ - CV_ProcFlag_NoFPO = (1 << 0), - CV_ProcFlag_IntReturn = (1 << 1), - CV_ProcFlag_FarReturn = (1 << 2), - CV_ProcFlag_NeverReturn = (1 << 3), - CV_ProcFlag_NotReached = (1 << 4), - CV_ProcFlag_CustomCall = (1 << 5), - CV_ProcFlag_NoInline = (1 << 6), - CV_ProcFlag_OptDbgInfo = (1 << 7), -}; - -typedef U16 CV_LocalFlags; -enum -{ - CV_LocalFlag_Param = (1 << 0), - CV_LocalFlag_AddrTaken = (1 << 1), - CV_LocalFlag_Compgen = (1 << 2), - CV_LocalFlag_Aggregate = (1 << 3), - CV_LocalFlag_PartOfAggregate = (1 << 4), - CV_LocalFlag_Aliased = (1 << 5), - CV_LocalFlag_Alias = (1 << 6), - CV_LocalFlag_Retval = (1 << 7), - CV_LocalFlag_OptOut = (1 << 8), - CV_LocalFlag_Global = (1 << 9), - CV_LocalFlag_Static = (1 << 10), -}; - -typedef struct CV_LocalVarAttr CV_LocalVarAttr; -struct CV_LocalVarAttr -{ - U32 off; - U16 seg; - CV_LocalFlags flags; -}; - -//- (SymKind: COMPILE) - -typedef U32 CV_CompileFlags; -#define CV_CompileFlags_ExtractLanguage(f) (((f) )&0xFF) -#define CV_CompileFlags_ExtractFloatPrec(f) (((f)>> 8)&0x03) -#define CV_CompileFlags_ExtractFloatPkg(f) (((f)>>10)&0x03) -#define CV_CompileFlags_ExtractAmbientData(f) (((f)>>12)&0x07) -#define CV_CompileFlags_ExtractAmbientCode(f) (((f)>>15)&0x07) -#define CV_CompileFlags_ExtractMode(f) (((f)>>18)&0x01) - -typedef struct CV_SymCompile CV_SymCompile; -struct CV_SymCompile -{ - U8 machine; - CV_CompileFlags flags; - // U8[] ver_str (null terminated) -}; - -//- (SymKind: SSEARCH) - -typedef struct CV_SymStartSearch CV_SymStartSearch; -struct CV_SymStartSearch -{ - U32 start_symbol; - U16 segment; -}; - -//- (SymKind: END) (empty) - -//- (SymKind: RETURN) - -typedef U8 CV_GenericStyle; -typedef enum CV_GenericStyleEnum -{ - CV_GenericStyle_VOID, - CV_GenericStyle_REG, // "return data is in register" - CV_GenericStyle_ICAN, // "indirect caller allocated near" - CV_GenericStyle_ICAF, // "indirect caller allocated far" - CV_GenericStyle_IRAN, // "indirect returnee allocated near" - CV_GenericStyle_IRAF, // "indirect returnee allocated far" - CV_GenericStyle_UNUSED, -} -CV_GenericStyleEnum; - -typedef U16 CV_GenericFlags; -enum -{ - CV_GenericFlags_CSTYLE = (1 << 0), - CV_GenericFlags_RSCLEAN = (1 << 1), // "returnee stack cleanup" -}; - -typedef struct CV_SymReturn CV_SymReturn; -struct CV_SymReturn -{ - CV_GenericFlags flags; - CV_GenericStyle style; -}; - -//- (SymKind: SLINK32) - -typedef struct CV_SymSLink32 CV_SymSLink32; -struct CV_SymSLink32 -{ - U32 frame_size; - U32 offset; - U16 reg; -}; - -//- (SymKind: OEM) - -typedef struct CV_SymOEM CV_SymOEM; -struct CV_SymOEM -{ - COFF_Guid id; - CV_TypeId itype; - // padding align(4) -}; - -//- (SymKind: VFTABLE32) - -typedef struct CV_SymVPath32 CV_SymVPath32; -struct CV_SymVPath32 -{ - CV_TypeId root; - CV_TypeId path; - U32 off; - U16 seg; -}; - -//- (SymKind: FRAMEPROC) - -typedef U8 CV_EncodedFramePtrReg; -typedef enum CV_EncodedFramePtrRegEnum -{ - CV_EncodedFramePtrReg_None, - CV_EncodedFramePtrReg_StackPtr, - CV_EncodedFramePtrReg_FramePtr, - CV_EncodedFramePtrReg_BasePtr, -} -CV_EncodedFramePtrRegEnum; - -typedef U32 CV_FrameprocFlags; -enum -{ - CV_FrameprocFlag_UsesAlloca = (1 << 0), - CV_FrameprocFlag_UsesSetJmp = (1 << 1), - CV_FrameprocFlag_UsesLongJmp = (1 << 2), - CV_FrameprocFlag_UsesInlAsm = (1 << 3), - CV_FrameprocFlag_UsesEH = (1 << 4), - CV_FrameprocFlag_Inline = (1 << 5), - CV_FrameprocFlag_HasSEH = (1 << 6), - CV_FrameprocFlag_Naked = (1 << 7), - CV_FrameprocFlag_HasSecurityChecks = (1 << 8), - CV_FrameprocFlag_AsyncEH = (1 << 9), - CV_FrameprocFlag_GSNoStackOrdering = (1 << 10), - CV_FrameprocFlag_WasInlined = (1 << 11), - CV_FrameprocFlag_GSCheck = (1 << 12), - CV_FrameprocFlag_SafeBuffers = (1 << 13), - // LocalBasePointer: 14,15 - // ParamBasePointer: 16,17 - CV_FrameprocFlag_PogoOn = (1 << 18), - CV_FrameprocFlag_PogoCountsValid = (1 << 19), - CV_FrameprocFlag_OptSpeed = (1 << 20), - CV_FrameprocFlag_HasCFG = (1 << 21), - CV_FrameprocFlag_HasCFW = (1 << 22), -}; -#define CV_FrameprocFlags_ExtractLocalBasePointer(f) (((f) >> 14)&3) -#define CV_FrameprocFlags_ExtractParamBasePointer(f) (((f) >> 16)&3) - -typedef struct CV_SymFrameproc CV_SymFrameproc; -struct CV_SymFrameproc -{ - U32 frame_size; - U32 pad_size; - U32 pad_off; - U32 save_reg_size; - U32 eh_off; - CV_SectionIndex eh_sec; - CV_FrameprocFlags flags; -}; - -//- (SymKind: ANNOTATION) - -typedef struct CV_SymAnnotation CV_SymAnnotation; -struct CV_SymAnnotation -{ - U32 off; - U16 seg; - U16 count; - // U8[] annotation (null terminated) -}; - -//- (SymKind: OBJNAME) - -typedef struct CV_SymObjname CV_SymObjname; -struct CV_SymObjname -{ - U32 sig; - // U8[] name (null terminated) -}; - -//- (SymKind: THUNK32) - -typedef U8 CV_ThunkOrdinal; -typedef enum CV_ThunkOrdinalEnum -{ - CV_ThunkOrdinal_NoType, - CV_ThunkOrdinal_Adjustor, - CV_ThunkOrdinal_VCall, - CV_ThunkOrdinal_PCode, - CV_ThunkOrdinal_Load, - CV_ThunkOrdinal_TrampIncremental, - CV_ThunkOrdinal_TrampBranchIsland, -} -CV_ThunkOrdinalEnum; - -typedef struct CV_SymThunk32 CV_SymThunk32; -struct CV_SymThunk32 -{ - U32 parent; - U32 end; - U32 next; - U32 off; - U16 sec; - U16 len; - CV_ThunkOrdinal ord; - // U8[] name (null terminated) - // U8[] variant (null terminated) -}; - -//- (SymKind: BLOCK32) - -typedef struct CV_SymBlock32 CV_SymBlock32; -struct CV_SymBlock32 -{ - U32 parent; - U32 end; - U32 len; - U32 off; - U16 sec; - // U8[] name (null terminated) -}; - -//- (SymKind: LABEL32) - -typedef struct CV_SymLabel32 CV_SymLabel32; -struct CV_SymLabel32 -{ - U32 off; - U16 sec; - CV_ProcFlags flags; - // U8[] name (null terminated) -}; - -//- (SymKind: REGISTER) - -typedef struct CV_SymRegister CV_SymRegister; -struct CV_SymRegister -{ - CV_TypeId itype; - U16 reg; - // U8[] name (null terminated) -}; - -//- (SymKind: CONSTANT) - -typedef struct CV_SymConstant CV_SymConstant; -struct CV_SymConstant -{ - CV_TypeId itype; - // CV_Numeric num - // U8[] name (null terminated) -}; - -//- (SymKind: UDT) - -typedef struct CV_SymUDT CV_SymUDT; -struct CV_SymUDT -{ - CV_TypeId itype; - // U8[] name (null terminated) -}; - -//- (SymKind: MANYREG) - -typedef struct CV_SymManyreg CV_SymManyreg; -struct CV_SymManyreg -{ - CV_TypeId itype; - U8 count; - // U8[count] regs; -}; - -//- (SymKind: BPREL32) - -typedef struct CV_SymBPRel32 CV_SymBPRel32; -struct CV_SymBPRel32 -{ - U32 off; - CV_TypeId itype; - // U8[] name (null terminated) -}; - -//- (SymKind: LDATA32, GDATA32) - -typedef struct CV_SymData32 CV_SymData32; -struct CV_SymData32 -{ - CV_TypeId itype; - U32 off; - CV_SectionIndex sec; - // U8[] name (null terminated) -}; - -//- (SymKind: PUB32) - -typedef U32 CV_PubFlags; -enum -{ - CV_PubFlag_Code = (1 << 0), - CV_PubFlag_Function = (1 << 1), - CV_PubFlag_ManagedCode = (1 << 2), - CV_PubFlag_MSIL = (1 << 3), -}; - -typedef struct CV_SymPub32 CV_SymPub32; -struct CV_SymPub32 -{ - CV_PubFlags flags; - U32 off; - CV_SectionIndex sec; - // U8[] name (null terminated) -}; - -//- (SymKind: LPROC32, GPROC32) - -typedef struct CV_SymProc32 CV_SymProc32; -struct CV_SymProc32 -{ - U32 parent; - U32 end; - U32 next; - U32 len; - U32 dbg_start; - U32 dbg_end; - CV_TypeId itype; - U32 off; - U16 sec; - CV_ProcFlags flags; - // U8[] name (null terminated) -}; - -//- (SymKind: REGREL32) - -typedef struct CV_SymRegrel32 CV_SymRegrel32; -struct CV_SymRegrel32 -{ - U32 reg_off; - CV_TypeId itype; - CV_Reg reg; - // U8[] name (null terminated) -}; - -//- (SymKind: LTHREAD32, GTHREAD32) - -typedef struct CV_SymThread32 CV_SymThread32; -struct CV_SymThread32 -{ - CV_TypeId itype; - U32 tls_off; - U16 tls_seg; - // U8[] name (null terminated) -}; - -//- (SymKind: COMPILE2) - -typedef U32 CV_Compile2Flags; -#define CV_Compile2Flags_ExtractLanguage(f) (((f) )&0xFF) -#define CV_Compile2Flags_ExtractEditAndContinue(f) (((f)>> 8)&0x01) -#define CV_Compile2Flags_ExtractNoDbgInfo(f) (((f)>> 9)&0x01) -#define CV_Compile2Flags_ExtractLTCG(f) (((f)>>10)&0x01) -#define CV_Compile2Flags_ExtractNoDataAlign(f) (((f)>>11)&0x01) -#define CV_Compile2Flags_ExtractManagedPresent(f) (((f)>>12)&0x01) -#define CV_Compile2Flags_ExtractSecurityChecks(f) (((f)>>13)&0x01) -#define CV_Compile2Flags_ExtractHotPatch(f) (((f)>>14)&0x01) -#define CV_Compile2Flags_ExtractCVTCIL(f) (((f)>>15)&0x01) -#define CV_Compile2Flags_ExtractMSILModule(f) (((f)>>16)&0x01) - -typedef struct CV_SymCompile2 CV_SymCompile2; -struct CV_SymCompile2 -{ - CV_Compile2Flags flags; - CV_Arch machine; - U16 ver_fe_major; - U16 ver_fe_minor; - U16 ver_fe_build; - U16 ver_major; - U16 ver_minor; - U16 ver_build; - // U8[] ver_str (null terminated) -}; - -//- (SymKind: MANYREG2) - -typedef struct CV_SymManyreg2 CV_SymManyreg2; -struct CV_SymManyreg2 -{ - CV_TypeId itype; - U16 count; - // U16[count] regs; -}; - -//- (SymKind: LOCALSLOT) - -typedef struct CV_SymSlot CV_SymSlot; -struct CV_SymSlot -{ - U32 slot_index; - CV_TypeId itype; - // U8[] name (null terminated) -}; - -//- (SymKind: MANFRAMEREL, ATTR_FRAMEREL) - -typedef struct CV_SymAttrFrameRel CV_SymAttrFrameRel; -struct CV_SymAttrFrameRel -{ - U32 off; - CV_TypeId itype; - CV_LocalVarAttr attr; - // U8[] name (null terminated) -}; - -//- (SymKind: MANREGISTER, ATTR_REGISTER) - -typedef struct CV_SymAttrReg CV_SymAttrReg; -struct CV_SymAttrReg -{ - CV_TypeId itype; - CV_LocalVarAttr attr; - U16 reg; - // U8[] name (null terminated) -}; - -//- (SymKind: MANMANYREG, ATTR_MANYREG) - - -typedef struct CV_SymAttrManyReg CV_SymAttrManyReg; -struct CV_SymAttrManyReg -{ - CV_TypeId itype; - CV_LocalVarAttr attr; - U8 count; - // U8[count] regs - // U8[] name (null terminated) -}; - -//- (SymKind: MANREGREL, ATTR_REGREL) - -typedef struct CV_SymAttrRegRel CV_SymAttrRegRel; -struct CV_SymAttrRegRel -{ - U32 off; - CV_TypeId itype; - U16 reg; - CV_LocalVarAttr attr; - // U8[] name (null terminated) -}; - -//- (SymKind: UNAMESPACE) - -typedef struct CV_SymUNamespace CV_SymUNamespace; -struct CV_SymUNamespace -{ - // *** "dummy" is the first character of name - it should not be skipped! - // *** It is placed here so the C compiler will accept this struct. - // *** The actual fixed size part of this record has a size of zero. - - U8 dummy; - - // U8[] name (null terminated) -}; - -//- (SymKind: PROCREF, DATAREF, LPROCREF) - -typedef struct CV_SymRef2 CV_SymRef2; -struct CV_SymRef2 -{ - U32 suc_name; - U32 sym_off; - CV_ModIndex imod; - // U8[] name (null terminated) -}; - -//- (SymKind: TRAMPOLINE) - -typedef U16 CV_TrampolineKind; -typedef enum CV_TrampolineKindEnum -{ - CV_TrampolineKind_Incremental, - CV_TrampolineKind_BranchIsland, -} -CV_TrampolineKindEnum; - -typedef struct CV_SymTrampoline CV_SymTrampoline; -struct CV_SymTrampoline -{ - CV_TrampolineKind kind; - U16 thunk_size; - U32 thunk_sec_off; - U32 target_sec_off; - CV_SectionIndex thunk_sec; - CV_SectionIndex target_sec; -}; - -//- (SymKind: SEPCODE) - -typedef U32 CV_SepcodeFlags; -enum -{ - CV_SepcodeFlag_IsLexicalScope = (1 << 0), - CV_SepcodeFlag_ReturnsToParent = (1 << 1), -}; - -typedef struct CV_SymSepcode CV_SymSepcode; -struct CV_SymSepcode -{ - U32 parent; - U32 end; - U32 len; - CV_SepcodeFlags flags; - U32 sec_off; - U32 sec_parent_off; - U16 sec; - U16 sec_parent; -}; - -//- (SymKind: SECTION) - -typedef struct CV_SymSection CV_SymSection; -struct CV_SymSection -{ - U16 sec_index; - U8 align; - U8 pad; - U32 rva; - U32 size; - U32 characteristics; - // U8[] name (null terminated) -}; - -//- (SymKind: COFFGROUP) - -typedef struct CV_SymCoffGroup CV_SymCoffGroup; -struct CV_SymCoffGroup -{ - U32 size; - U32 characteristics; - U32 off; - U16 sec; - // U8[] name (null terminated) -}; - -//- (SymKind: EXPORT) - -typedef U16 CV_ExportFlags; -enum -{ - CV_ExportFlag_Constant = (1 << 0), - CV_ExportFlag_Data = (1 << 1), - CV_ExportFlag_Private = (1 << 2), - CV_ExportFlag_NoName = (1 << 3), - CV_ExportFlag_Ordinal = (1 << 4), - CV_ExportFlag_Forwarder = (1 << 5), -}; - -typedef struct CV_SymExport CV_SymExport; -struct CV_SymExport -{ - U16 ordinal; - CV_ExportFlags flags; - // U8[] name (null terminated) -}; - -//- (SymKind: CALLSITEINFO) - -typedef struct CV_SymCallSiteInfo CV_SymCallSiteInfo; -struct CV_SymCallSiteInfo -{ - U32 off; - U16 sec; - U16 pad; - CV_TypeId itype; -}; - -//- (SymKind: FRAMECOOKIE) - -typedef U8 CV_FrameCookieKind; -typedef enum CV_FrameCookieKindEnum -{ - CV_FrameCookieKind_Copy, - CV_FrameCookieKind_XorSP, - CV_FrameCookieKind_XorBP, - CV_FrameCookieKind_XorR13, -} -CV_FrameCookieKindEnum; - -typedef struct CV_SymFrameCookie CV_SymFrameCookie; -struct CV_SymFrameCookie -{ - U32 off; - CV_Reg reg; - CV_FrameCookieKind kind; - U8 flags; -}; - -//- (SymKind: DISCARDED) - -typedef U8 CV_DiscardedKind; -typedef enum CV_DiscardedKindEnum -{ - CV_DiscardedKind_Unknown, - CV_DiscardedKind_NotSelected, - CV_DiscardedKind_NotReferenced, -} -CV_DiscardedKindEnum; - -typedef struct CV_SymDiscarded CV_SymDiscarded; -struct CV_SymDiscarded -{ - CV_DiscardedKind kind; - U32 file_id; - U32 file_ln; - // U8[] data (rest of data) -}; - -//- (SymKind: COMPILE3) - -typedef U32 CV_Compile3Flags; -#define CV_Compile3Flags_ExtractLanguage(f) (((f) )&0xFF) -#define CV_Compile3Flags_ExtractEditAndContinue(f) (((f)>> 9)&0x01) -#define CV_Compile3Flags_ExtractNoDbgInfo(f) (((f)>>10)&0x01) -#define CV_Compile3Flags_ExtractLTCG(f) (((f)>>11)&0x01) -#define CV_Compile3Flags_ExtractNoDataAlign(f) (((f)>>12)&0x01) -#define CV_Compile3Flags_ExtractManagedPresent(f) (((f)>>13)&0x01) -#define CV_Compile3Flags_ExtractSecurityChecks(f) (((f)>>14)&0x01) -#define CV_Compile3Flags_ExtractHotPatch(f) (((f)>>15)&0x01) -#define CV_Compile3Flags_ExtractCVTCIL(f) (((f)>>16)&0x01) -#define CV_Compile3Flags_ExtractMSILModule(f) (((f)>>17)&0x01) -#define CV_Compile3Flags_ExtractSDL(f) (((f)>>18)&0x01) -#define CV_Compile3Flags_ExtractPGO(f) (((f)>>19)&0x01) -#define CV_Compile3Flags_ExtractEXP(f) (((f)>>20)&0x01) - -typedef struct CV_SymCompile3 CV_SymCompile3; -struct CV_SymCompile3 -{ - CV_Compile3Flags flags; - CV_Arch machine; - U16 ver_fe_major; - U16 ver_fe_minor; - U16 ver_fe_build; - U16 ver_feqfe; - U16 ver_major; - U16 ver_minor; - U16 ver_build; - U16 ver_qfe; - // U8[] ver_str (null terminated) -}; - -//- (SymKind: ENVBLOCK) - -typedef struct CV_SymEnvBlock CV_SymEnvBlock; -struct CV_SymEnvBlock -{ - U8 flags; - // U8[][] rgsz (sequence null terminated strings) -}; - -//- (SymKind: LOCAL) - -typedef struct CV_SymLocal CV_SymLocal; -struct CV_SymLocal -{ - CV_TypeId itype; - CV_LocalFlags flags; - // U8[] name (null terminated) -}; - -//- DEFRANGE - -typedef struct CV_LvarAddrRange CV_LvarAddrRange; -struct CV_LvarAddrRange -{ - U32 off; - U16 sec; - U16 len; -}; - -typedef struct CV_LvarAddrGap CV_LvarAddrGap; -struct CV_LvarAddrGap -{ - U16 off; - U16 len; -}; - -typedef U16 CV_RangeAttribs; -enum -{ - CV_RangeAttrib_Maybe = (1 << 0), -}; - -//- (SymKind: DEFRANGE_SUBFIELD) - -typedef struct CV_SymDefrangeSubfield CV_SymDefrangeSubfield; -struct CV_SymDefrangeSubfield -{ - U32 program; - U32 off_in_parent; - CV_LvarAddrRange range; - // CV_LvarAddrGap[] gaps (rest of data) -}; - -//- (SymKind: DEFRANGE_REGISTER) - -typedef struct CV_SymDefrangeRegister CV_SymDefrangeRegister; -struct CV_SymDefrangeRegister -{ - CV_Reg reg; - CV_RangeAttribs attribs; - CV_LvarAddrRange range; - // CV_LvarAddrGap[] gaps (rest of data) -}; - -//- (SymKind: DEFRANGE_FRAMEPOINTER_REL) - -typedef struct CV_SymDefrangeFramepointerRel CV_SymDefrangeFramepointerRel; -struct CV_SymDefrangeFramepointerRel -{ - S32 off; - CV_LvarAddrRange range; - // CV_LvarAddrGap[] gaps (rest of data) -}; - -//- (SymKind: DEFRANGE_SUBFIELD_REGISTER) - -typedef struct CV_SymDefrangeSubfieldRegister CV_SymDefrangeSubfieldRegister; -struct CV_SymDefrangeSubfieldRegister -{ - CV_Reg reg; - CV_RangeAttribs attribs; - U32 field_offset; - CV_LvarAddrRange range; - // CV_LvarAddrGap[] gaps (rest of data) -}; - -//- (SymKind: DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE) - -typedef struct CV_SymDefrangeFramepointerRelFullScope CV_SymDefrangeFramepointerRelFullScope; -struct CV_SymDefrangeFramepointerRelFullScope -{ - S32 off; -}; - -//- (SymKind: DEFRANGE_REGISTER_REL) - -typedef U16 CV_DefrangeRegisterRelFlags; -enum -{ - CV_DefrangeRegisterRelFlag_SpilledOutUDTMember = (1 << 0), -}; -#define CV_DefrangeRegisterRelFlag_ExtractOffsetParent(f) (((f)>>4)&0xFFF) - -typedef struct CV_SymDefrangeRegisterRel CV_SymDefrangeRegisterRel; -struct CV_SymDefrangeRegisterRel -{ - CV_Reg reg; - CV_DefrangeRegisterRelFlags flags; - S32 reg_off; - CV_LvarAddrRange range; - // CV_LvarAddGap[] gaps (rest of data) -}; - -//- (SymKind: BUILDINFO) - -typedef struct CV_SymBuildInfo CV_SymBuildInfo; -struct CV_SymBuildInfo -{ - CV_ItemId id; -}; - -//- (SymKind: INLINESITE) - -typedef U32 CV_InlineBinaryAnnotation; -typedef enum CV_InlineBinaryAnnotationEnum -{ - CV_InlineBinaryAnnotation_Null, - CV_InlineBinaryAnnotation_CodeOffset, - CV_InlineBinaryAnnotation_ChangeCodeOffsetBase, - CV_InlineBinaryAnnotation_ChangeCodeOffset, - CV_InlineBinaryAnnotation_ChangeCodeLength, - CV_InlineBinaryAnnotation_ChangeFile, - CV_InlineBinaryAnnotation_ChangeLineOffset, - CV_InlineBinaryAnnotation_ChangeLineEndDelta, - CV_InlineBinaryAnnotation_ChangeRangeKind, - CV_InlineBinaryAnnotation_ChangeColumnStart, - CV_InlineBinaryAnnotation_ChangeColumnEndDelta, - CV_InlineBinaryAnnotation_ChangeCodeOffsetAndLineOffset, - CV_InlineBinaryAnnotation_ChangeCodeLengthAndCodeOffset, - CV_InlineBinaryAnnotation_ChangeColumnEnd -} -CV_InlineBinaryAnnotationEnum; - -typedef U32 CV_InlineRangeKind; -typedef enum CV_InlineRangeKindEnum -{ - CV_InlineRangeKind_Expr, - CV_InlineRangeKind_Stmt -} -CV_InlineRangeKindEnum; - -typedef struct CV_SymInlineSite CV_SymInlineSite; -struct CV_SymInlineSite -{ - U32 parent; - U32 end; - CV_ItemId inlinee; - // U8 annotations[] (rest of data) -}; - -//- (SymKind: INLINESITE2) - -typedef struct CV_SymInlineSite2 CV_SymInlineSite2; -struct CV_SymInlineSite2 -{ - U32 parent_off; - U32 end_off; - CV_ItemId inlinee; - U32 invocations; - // U8 annotations[] (rest of data) -}; - -//- (SymKind: INLINESITE_END) (empty) - -//- (SymKind: FILESTATIC) - -typedef struct CV_SymFileStatic CV_SymFileStatic; -struct CV_SymFileStatic -{ - CV_TypeId itype; - U32 mod_offset; - CV_LocalFlags flags; - // U8[] name (null terminated) -}; - -//- (SymKind: ARMSWITCHTABLE) - -typedef U16 CV_ArmSwitchKind; -typedef enum CV_ArmSwitchKindEnum -{ - CV_ArmSwitchKind_INT1, - CV_ArmSwitchKind_UINT1, - CV_ArmSwitchKind_INT2, - CV_ArmSwitchKind_UINT2, - CV_ArmSwitchKind_INT4, - CV_ArmSwitchKind_UINT5, - CV_ArmSwitchKind_POINTER, - CV_ArmSwitchKind_UINT1SHL1, - CV_ArmSwitchKind_UINT2SHL1, - CV_ArmSwitchKind_INT1SSHL1, - CV_ArmSwitchKind_INT2SSHL1, -} -CV_ArmSwitchKindEnum; - -typedef struct CV_SymArmSwitchTable CV_SymArmSwitchTable; -struct CV_SymArmSwitchTable -{ - U32 off_base; - U16 sec_base; - CV_ArmSwitchKind kind; - U32 off_branch; - U32 off_table; - U16 sec_branch; - U16 sec_table; - U32 entry_count; -}; - -//- (SymKind: CALLEES, CALLERS) - -typedef struct CV_SymFunctionList CV_SymFunctionList; -struct CV_SymFunctionList -{ - U32 count; - // CV_TypeId[count] funcs - // U32[clamp(count, rest_of_data/4)] invocations -}; - -//- (SymKind: POGODATA) - -typedef struct CV_SymPogoInfo CV_SymPogoInfo; -struct CV_SymPogoInfo -{ - U32 invocations; - U64 dynamic_inst_count; - U32 static_inst_count; - U32 post_inline_static_inst_count; -}; - -//- (SymKind: HEAPALLOCSITE) - -typedef struct CV_SymHeapAllocSite CV_SymHeapAllocSite; -struct CV_SymHeapAllocSite -{ - U32 off; - U16 sec; - U16 call_inst_len; - CV_TypeId itype; -}; - -//- (SymKind: MOD_TYPEREF) - -typedef U32 CV_ModTypeRefFlags; -enum -{ - CV_ModTypeRefFlag_None = (1 << 0), - CV_ModTypeRefFlag_RefTMPCT = (1 << 1), - CV_ModTypeRefFlag_OwnTMPCT = (1 << 2), - CV_ModTypeRefFlag_OwnTMR = (1 << 3), - CV_ModTypeRefFlag_OwnTM = (1 << 4), - CV_ModTypeRefFlag_RefTM = (1 << 5), -}; - -typedef struct CV_SymModTypeRef CV_SymModTypeRef; -struct CV_SymModTypeRef -{ - CV_ModTypeRefFlags flags; - // contain stream number or module index depending on flags (undocumented) - U32 word0; - U32 word1; -}; - -//- (SymKind: REF_MINIPDB) - -typedef U16 CV_RefMiniPdbFlags; -enum -{ - CV_RefMiniPdbFlag_Local = (1 << 0), - CV_RefMiniPdbFlag_Data = (1 << 1), - CV_RefMiniPdbFlag_UDT = (1 << 2), - CV_RefMiniPdbFlag_Label = (1 << 3), - CV_RefMiniPdbFlag_Const = (1 << 4), -}; - -typedef struct CV_SymRefMiniPdb CV_SymRefMiniPdb; -struct CV_SymRefMiniPdb -{ - U32 data; - CV_ModIndex imod; - CV_RefMiniPdbFlags flags; - // U8[] name (null terminated) -}; - -//- (SymKind: FASTLINK) - -typedef U16 CV_FastLinkFlags; -enum -{ - CV_FastLinkFlag_IsGlobalData = (1 << 0), - CV_FastLinkFlag_IsData = (1 << 1), - CV_FastLinkFlag_IsUDT = (1 << 2), - // 3 ~ unknown/unused - CV_FastLinkFlag_IsConst = (1 << 4), - // 5 ~ unknown/unused - CV_FastLinkFlag_IsNamespace = (1 << 6), -}; - -typedef struct CV_SymFastLink CV_SymFastLink; -struct CV_SymFastLink -{ - CV_TypeId itype; - CV_FastLinkFlags flags; - // U8[] name (null terminated) -}; - -//- (SymKind: INLINEES) - -typedef struct CV_SymInlinees CV_SymInlinees; -struct CV_SymInlinees -{ - U32 count; - // U32[count] desc; -}; - -//////////////////////////////// -//~ rjf: CodeView Format "Leaf" Types -// -// (type info) -// - -#define CV_LeafIDKindXList(X) \ -X(FUNC_ID, 0x1601)\ -X(MFUNC_ID, 0x1602)\ -X(BUILDINFO, 0x1603)\ -X(SUBSTR_LIST, 0x1604)\ -X(STRING_ID, 0x1605)\ -X(UDT_SRC_LINE, 0x1606)\ -X(UDT_MOD_SRC_LINE, 0x1607) - -typedef U16 CV_LeafIDKind; -typedef enum CV_LeafIDKindEnum -{ -#define X(N,c) CV_LeafIDKind_##N = c, - CV_LeafIDKindXList(X) -#undef X -} -CV_LeafIDKindEnum; - -#define CV_TypeId_Variadic 0 - -#define CV_BasicPointerKindXList(X) \ -X(VALUE, 0x0)\ -X(16BIT, 0x1)\ -X(FAR_16BIT, 0x2)\ -X(HUGE_16BIT, 0x3)\ -X(32BIT, 0x4)\ -X(16_32BIT, 0x5)\ -X(64BIT, 0x6) - -typedef U8 CV_BasicPointerKind; -typedef enum -{ -#define X(N,c) CV_BasicPointerKind_##N = c, - CV_BasicPointerKindXList(X) -#undef X -} CV_BasicPointerKindEnum; - -#define CV_BasicTypeFromTypeId(x) ((x)&0xFF) -#define CV_BasicPointerKindFromTypeId(x) (((x)>>8)&0xFF) - -typedef U8 CV_HFAKind; -typedef enum CV_HFAKindEnum -{ - CV_HFAKind_None, - CV_HFAKind_Float, - CV_HFAKind_Double, - CV_HFAKind_Other -} -CV_HFAKindEnum; - -typedef U8 CV_MoComUDTKind; -typedef enum CV_MoComUDTKindEnum -{ - CV_MoComUDTKind_None, - CV_MoComUDTKind_Ref, - CV_MoComUDTKind_Value, - CV_MoComUDTKind_Interface -} -CV_MoComUDTKindEnum; - -typedef U16 CV_TypeProps; -enum -{ - CV_TypeProp_Packed = (1<<0), - CV_TypeProp_HasConstructorsDestructors = (1<<1), - CV_TypeProp_OverloadedOperators = (1<<2), - CV_TypeProp_IsNested = (1<<3), - CV_TypeProp_ContainsNested = (1<<4), - CV_TypeProp_OverloadedAssignment = (1<<5), - CV_TypeProp_OverloadedCasting = (1<<6), - CV_TypeProp_FwdRef = (1<<7), - CV_TypeProp_Scoped = (1<<8), - CV_TypeProp_HasUniqueName = (1<<9), - CV_TypeProp_Sealed = (1<<10), - // HFA: 11,12 - CV_TypeProp_Intrinsic = (1<<13), - // MOCOM: 14,15 -}; -#define CV_TypeProps_ExtractHFA(f) (((f)>>11)&0x3) -#define CV_TypeProps_ExtractMOCOM(f) (((f)>>14)&0x3) - -typedef U8 CV_PointerKind; -typedef enum CV_PointerKindEnum -{ - CV_PointerKind_Near, // 16 bit - CV_PointerKind_Far, // 16:16 bit - CV_PointerKind_Huge, // 16:16 bit - CV_PointerKind_BaseSeg, - CV_PointerKind_BaseVal, - CV_PointerKind_BaseSegVal, - CV_PointerKind_BaseAddr, - CV_PointerKind_BaseSegAddr, - CV_PointerKind_BaseType, - CV_PointerKind_BaseSelf, - CV_PointerKind_Near32, // 32 bit - CV_PointerKind_Far32, // 16:32 bit - CV_PointerKind_64, // 64 bit -} -CV_PointerKindEnum; - -typedef U8 CV_PointerMode; -typedef enum CV_PointerModeEnum -{ - CV_PointerMode_Ptr, - CV_PointerMode_LRef, - CV_PointerMode_PtrMem, - CV_PointerMode_PtrMethod, - CV_PointerMode_RRef, -} -CV_PointerModeEnum; - -typedef U16 CV_MemberPointerKind; -typedef enum CV_MemberPointerKindEnum -{ - CV_MemberPointerKind_Undef, - CV_MemberPointerKind_DataSingle, - CV_MemberPointerKind_DataMultiple, - CV_MemberPointerKind_DataVirtual, - CV_MemberPointerKind_DataGeneral, - CV_MemberPointerKind_FuncSingle, - CV_MemberPointerKind_FuncMultiple, - CV_MemberPointerKind_FuncVirtual, - CV_MemberPointerKind_FuncGeneral, -} -CV_MemberPointerKindEnum; - -typedef U32 CV_VirtualTableShape; -typedef enum CV_VirtualTableShapeEnum -{ - CV_VirtualTableShape_Near, // 16 bit ptr - CV_VirtualTableShape_Far, // 16:16 bit ptr - CV_VirtualTableShape_Thin, // ??? - CV_VirtualTableShape_Outer, // address point displacment to outermost class entry[-1] - CV_VirtualTableShape_Meta, // far pointer to metaclass descriptor entry[-2] - CV_VirtualTableShape_Near32, // 32 bit ptr - CV_VirtualTableShape_Far32, // ??? -} -CV_VirtualTableShapeEnum; - -typedef U8 CV_MethodProp; -enum -{ - CV_MethodProp_Vanilla, - CV_MethodProp_Virtual, - CV_MethodProp_Static, - CV_MethodProp_Friend, - CV_MethodProp_Intro, - CV_MethodProp_PureVirtual, - CV_MethodProp_PureIntro, -}; - -typedef U8 CV_MemberAccess; -typedef enum CV_MemberAccessEnum -{ - CV_MemberAccess_Null, - CV_MemberAccess_Private, - CV_MemberAccess_Protected, - CV_MemberAccess_Public -} -CV_MemberAccessEnum; - -typedef U16 CV_FieldAttribs; -enum -{ - // Access: 0,1 - // MethodProp: [2:4] - CV_FieldAttrib_Pseudo = (1<<5), - CV_FieldAttrib_NoInherit = (1<<6), - CV_FieldAttrib_NoConstruct = (1<<7), - CV_FieldAttrib_CompilerGenated = (1<<8), - CV_FieldAttrib_Sealed = (1<<9), -}; -#define CV_FieldAttribs_ExtractAccess(f) ((f)&0x3) -#define CV_FieldAttribs_ExtractMethodProp(f) (((f)>>2)&0x7) - -typedef U16 CV_LabelKind; -typedef enum CV_LabelKindEnum -{ - CV_LabelKind_Near = 0, - CV_LabelKind_Far = 4, -} -CV_LabelKindEnum; - -typedef U8 CV_FunctionAttribs; -enum -{ - CV_FunctionAttrib_CxxReturnUDT = (1<<0), - CV_FunctionAttrib_Constructor = (1<<1), - CV_FunctionAttrib_ConstructorVBase = (1<<2), -}; - -typedef U8 CV_CallKind; -typedef enum CV_CallKindEnum -{ - CV_CallKind_NearC, - CV_CallKind_FarC, - CV_CallKind_NearPascal, - CV_CallKind_FarPascal, - CV_CallKind_NearFast, - CV_CallKind_FarFast, - CV_CallKind_UNUSED, - CV_CallKind_NearStd, - CV_CallKind_FarStd, - CV_CallKind_NearSys, - CV_CallKind_FarSys, - CV_CallKind_This, - CV_CallKind_Mips, - CV_CallKind_Generic, - CV_CallKind_Alpha, - CV_CallKind_PPC, - CV_CallKind_HitachiSuperH, - CV_CallKind_Arm, - CV_CallKind_AM33, - CV_CallKind_TriCore, - CV_CallKind_HitachiSuperH5, - CV_CallKind_M32R, - CV_CallKind_Clr, - CV_CallKind_Inline, - CV_CallKind_NearVector, -} -CV_CallKindEnum; - -//- (LeafKind: PRECOMP) - -typedef struct CV_LeafPreComp CV_LeafPreComp; -struct CV_LeafPreComp -{ - U32 start_index; - U32 count; - U32 signature; - // U8[] name (null terminated) -}; - -//- (LeafKind: TYPESERVER) - -typedef struct CV_LeafTypeServer CV_LeafTypeServer; -struct CV_LeafTypeServer -{ - U32 sig; - U32 age; - // U8[] name (null terminated) -}; - -//- (LeafKind: TYPESERVER2) - -typedef struct CV_LeafTypeServer2 CV_LeafTypeServer2; -struct CV_LeafTypeServer2 -{ - COFF_Guid sig70; - U32 age; - // U8[] name (null terminated) -}; - -//- (LeafKind: SKIP) - -typedef struct CV_LeafSkip CV_LeafSkip; -struct CV_LeafSkip -{ - CV_TypeId itype; -}; - -//- (LeafKind: VTSHAPE) - -typedef struct CV_LeafVTShape CV_LeafVTShape; -struct CV_LeafVTShape -{ - U16 count; - // U4[count] shapes (CV_VirtualTableShape) -}; - -//- (LeafKind: LABEL) - -typedef struct CV_LeafLabel CV_LeafLabel; -struct CV_LeafLabel -{ - CV_LabelKind kind; -}; - -//- (LeafKind: MODIFIER) - -typedef U16 CV_ModifierFlags; -enum -{ - CV_ModifierFlag_Const = (1 << 0), - CV_ModifierFlag_Volatile = (1 << 1), - CV_ModifierFlag_Unaligned = (1 << 2), -}; - -typedef struct CV_LeafModifier CV_LeafModifier; -struct CV_LeafModifier -{ - CV_TypeId itype; - CV_ModifierFlags flags; -}; - -//- (LeafKind: POINTER) - -typedef U32 CV_PointerAttribs; -enum -{ - // Kind: [0:4] - // Mode: [5:7] - CV_PointerAttrib_IsFlat = (1 << 8), - CV_PointerAttrib_Volatile = (1 << 9), - CV_PointerAttrib_Const = (1 << 10), - CV_PointerAttrib_Unaligned = (1 << 11), - CV_PointerAttrib_Restricted = (1 << 12), - // Size: [13,18] - CV_PointerAttrib_MOCOM = (1 << 19), - CV_PointerAttrib_LRef = (1 << 21), - CV_PointerAttrib_RRef = (1 << 22) -}; - -#define CV_PointerAttribs_ExtractKind(a) ((a)&0x1F) -#define CV_PointerAttribs_ExtractMode(a) (((a)>>5)&0x7) -#define CV_PointerAttribs_ExtractSize(a) (((a)>>13)&0x3F) - -typedef struct CV_LeafPointer CV_LeafPointer; -struct CV_LeafPointer -{ - CV_TypeId itype; - CV_PointerAttribs attribs; -}; - -//- (LeafKind: PROCEDURE) - -typedef struct CV_LeafProcedure CV_LeafProcedure; -struct CV_LeafProcedure -{ - CV_TypeId ret_itype; - CV_CallKind call_kind; - CV_FunctionAttribs attribs; - U16 arg_count; - CV_TypeId arg_itype; -}; - -//- (LeafKind: MFUNCTION) - -typedef struct CV_LeafMFunction CV_LeafMFunction; -struct CV_LeafMFunction -{ - CV_TypeId ret_itype; - CV_TypeId class_itype; - CV_TypeId this_itype; - CV_CallKind call_kind; - CV_FunctionAttribs attribs; - U16 arg_count; - CV_TypeId arg_itype; - S32 this_adjust; -}; - -//- (LeafKind: ARGLIST) - -typedef struct CV_LeafArgList CV_LeafArgList; -struct CV_LeafArgList -{ - U32 count; - // CV_TypeId[count] itypes; -}; - -//- (LeafKind: BITFIELD) - -typedef struct CV_LeafBitField CV_LeafBitField; -struct CV_LeafBitField -{ - CV_TypeId itype; - U8 len; - U8 pos; -}; - -//- (LeafKind: METHODLIST) - -// ("jagged" array of these vvvvvvvv) -typedef struct CV_LeafMethodListMember CV_LeafMethodListMember; -struct CV_LeafMethodListMember -{ - CV_FieldAttribs attribs; - U16 pad; - CV_TypeId itype; - // U32 vbaseoff (when Intro or PureIntro) -}; - -//- (LeafKind: INDEX) - -typedef struct CV_LeafIndex CV_LeafIndex; -struct CV_LeafIndex -{ - U16 pad; - CV_TypeId itype; -}; - -//- (LeafKind: ARRAY) - -typedef struct CV_LeafArray CV_LeafArray; -struct CV_LeafArray -{ - CV_TypeId entry_itype; - CV_TypeId index_itype; - // CV_Numeric count -}; - -//- (LeafKind: CLASS, STRUCTURE, INTERFACE) - -typedef struct CV_LeafStruct CV_LeafStruct; -struct CV_LeafStruct -{ - U16 count; - CV_TypeProps props; - CV_TypeId field_itype; - CV_TypeId derived_itype; - CV_TypeId vshape_itype; - // CV_Numeric size - // U8[] name (null terminated) - // U8[] unique_name (null terminated) -}; - -//- (LeafKind: UNION) - -typedef struct CV_LeafUnion CV_LeafUnion; -struct CV_LeafUnion -{ - U16 count; - CV_TypeProps props; - CV_TypeId field_itype; - // CV_Numeric size - // U8[] name (null terminated) - // U8[] unique_name (null terminated) -}; - -//- (LeafKind: ENUM) - -typedef struct CV_LeafEnum CV_LeafEnum; -struct CV_LeafEnum -{ - U16 count; - CV_TypeProps props; - CV_TypeId base_itype; - CV_TypeId field_itype; - // U8[] name (null terminated) - // U8[] unique_name (null terminated) -}; - -//- (LeafKind: ALIAS) - -typedef struct CV_LeafAlias CV_LeafAlias; -struct CV_LeafAlias -{ - CV_TypeId itype; - // U8[] name (null terminated) -}; - -//- (LeafKind: MEMBER) - -typedef struct CV_LeafMember CV_LeafMember; -struct CV_LeafMember -{ - CV_FieldAttribs attribs; - CV_TypeId itype; - // CV_Numeric offset - // U8[] name (null terminated) -}; - -//- (LeafKind: STMEMBER) - -typedef struct CV_LeafStMember CV_LeafStMember; -struct CV_LeafStMember -{ - CV_FieldAttribs attribs; - CV_TypeId itype; - // U8[] name (null terminated) -}; - -//- (LeafKind: METHOD) - -typedef struct CV_LeafMethod CV_LeafMethod; -struct CV_LeafMethod -{ - U16 count; - CV_TypeId list_itype; - // U8[] name (null terminated) -}; - -//- (LeafKind: ONEMETHOD) - -typedef struct CV_LeafOneMethod CV_LeafOneMethod; -struct CV_LeafOneMethod -{ - CV_FieldAttribs attribs; - CV_TypeId itype; - // U32 vbaseoff (when Intro or PureIntro) - // U8[] name (null terminated) -}; - -//- (LeafKind: ENUMERATE) - -typedef struct CV_LeafEnumerate CV_LeafEnumerate; -struct CV_LeafEnumerate -{ - CV_FieldAttribs attribs; - // CV_Numeric val - // U8[] name (null terminated) -}; - -//- (LeafKind: NESTTYPE) - -typedef struct CV_LeafNestType CV_LeafNestType; -struct CV_LeafNestType -{ - U16 pad; - CV_TypeId itype; - // U8[] name (null terminated) -}; - -//- (LeafKind: NESTTYPEEX) - -typedef struct CV_LeafNestTypeEx CV_LeafNestTypeEx; -struct CV_LeafNestTypeEx -{ - CV_FieldAttribs attribs; - CV_TypeId itype; - // U8[] name (null terminated) -}; - -//- (LeafKind: BCLASS) - -typedef struct CV_LeafBClass CV_LeafBClass; -struct CV_LeafBClass -{ - CV_FieldAttribs attribs; - CV_TypeId itype; - // CV_Numeric offset -}; - -//- (LeafKind: VBCLASS, IVBCLASS) - -typedef struct CV_LeafVBClass CV_LeafVBClass; -struct CV_LeafVBClass -{ - CV_FieldAttribs attribs; - CV_TypeId itype; - CV_TypeId vbptr_itype; - // CV_Numeric vbptr_off - // CV_Numeric vtable_off -}; - -//- (LeafKind: VFUNCTAB) - -typedef struct CV_LeafVFuncTab CV_LeafVFuncTab; -struct CV_LeafVFuncTab -{ - U16 pad; - CV_TypeId itype; -}; - -//- (LeafKind: VFUNCOFF) - -typedef struct CV_LeafVFuncOff CV_LeafVFuncOff; -struct CV_LeafVFuncOff -{ - U16 pad; - CV_TypeId itype; - U32 off; -}; - -//- (LeafKind: VFTABLE) - -typedef struct CV_LeafVFTable CV_LeafVFTable; -struct CV_LeafVFTable -{ - CV_TypeId owner_itype; - CV_TypeId base_table_itype; - U32 offset_in_object_layout; - U32 names_len; - // U8[] names (multiple null terminated strings) -}; - -//- (LeafKind: VFTPATH) - -typedef struct CV_LeafVFPath CV_LeafVFPath; -struct CV_LeafVFPath -{ - U32 count; - // CV_TypeId[count] base; -}; - -//- (LeafKind: CLASS2, STRUCT2) - -typedef struct CV_LeafStruct2 CV_LeafStruct2; -struct CV_LeafStruct2 -{ - // NOTE: still reverse engineering this - if you find docs please help! - CV_TypeProps props; - U16 unknown1; - CV_TypeId field_itype; - CV_TypeId derived_itype; - CV_TypeId vshape_itype; - U16 unknown2; - // CV_Numeric size - // U8[] name (null terminated) - // U8[] unique_name (null terminated) -}; - -//- (LeafIDKind: FUNC_ID) - -typedef struct CV_LeafFuncId CV_LeafFuncId; -struct CV_LeafFuncId -{ - CV_ItemId scope_string_id; - CV_TypeId itype; - // U8[] name (null terminated) -}; - -//- (LeafIDKind: MFUNC_ID) - -typedef struct CV_LeafMFuncId CV_LeafMFuncId; -struct CV_LeafMFuncId -{ - CV_TypeId owner_itype; - CV_TypeId itype; - // U8[] name (null terminated) -}; - -//- (LeafIDKind: STRING_ID) - -typedef struct CV_LeafStringId CV_LeafStringId; -struct CV_LeafStringId -{ - CV_ItemId substr_list_id; - // U8[] string (null terminated) -}; - -//- (LeafIDKind: BUILDINFO) - -typedef enum CV_BuildInfoIndexEnum -{ - CV_BuildInfoIndex_BuildDirectory = 0, - CV_BuildInfoIndex_CompilerExecutable = 1, - CV_BuildInfoIndex_TargetSourceFile = 2, - CV_BuildInfoIndex_CombinedPdb = 3, - CV_BuildInfoIndex_CompileArguments = 4, -} -CV_BuildInfoIndexEnum; - -typedef struct CV_LeafBuildInfo CV_LeafBuildInfo; -struct CV_LeafBuildInfo -{ - U16 count; - // CV_ItemId[count] items -}; - -//- (LeafIDKind: SUBSTR_LIST) - -typedef struct CV_LeafSubstrList CV_LeafSubstrList; -struct CV_LeafSubstrList -{ - U32 count; - // CV_ItemId[count] items -}; - -//- (LeafIDKind: UDT_SRC_LINE) - -typedef struct CV_LeafUDTSrcLine CV_LeafUDTSrcLine; -struct CV_LeafUDTSrcLine -{ - CV_TypeId udt_itype; - CV_ItemId src_string_id; - U32 line; -}; - -//- (LeafIDKind: UDT_MOD_SRC_LINE) - -typedef struct CV_LeafUDTModSrcLine CV_LeafUDTModSrcLine; -struct CV_LeafUDTModSrcLine -{ - CV_TypeId udt_itype; - CV_ItemId src_string_id; - U32 line; - CV_ModIndex imod; -}; - -//////////////////////////////// -//~ CodeView Format C13 Line Info Types - -#define CV_C13SubSectionKind_IgnoreFlag 0x80000000 - -#define CV_C13SubSectionKindXList(X)\ -X(Symbols, 0xF1)\ -X(Lines, 0xF2)\ -X(StringTable, 0xF3)\ -X(FileChksms, 0xF4)\ -X(FrameData, 0xF5)\ -X(InlineeLines, 0xF6)\ -X(CrossScopeImports, 0xF7)\ -X(CrossScopeExports, 0xF8)\ -X(IlLines, 0xF9)\ -X(FuncMDTokenMap, 0xFA)\ -X(TypeMDTokenMap, 0xFB)\ -X(MergedAssemblyInput, 0xFC)\ -X(CoffSymbolRVA, 0xFD)\ -X(XfgHashType, 0xFF)\ -X(XfgHashVirtual, 0x100) - -typedef U32 CV_C13SubSectionKind; -typedef enum CV_C13SubSectionKindEnum -{ -#define X(N,c) CV_C13SubSectionKind_##N = c, - CV_C13SubSectionKindXList(X) -#undef X -} -CV_C13SubSectionKindEnum; - -typedef struct CV_C13SubSectionHeader CV_C13SubSectionHeader; -struct CV_C13SubSectionHeader -{ - CV_C13SubSectionKind kind; - U32 size; -}; - -//- FileChksms sub-section - -typedef U8 CV_C13ChecksumKind; -typedef enum CV_C13ChecksumKindEnum -{ - CV_C13ChecksumKind_Null, - CV_C13ChecksumKind_MD5, - CV_C13ChecksumKind_SHA1, - CV_C13ChecksumKind_SHA256, -} -CV_C13ChecksumKindEnum; - -typedef struct CV_C13Checksum CV_C13Checksum; -struct CV_C13Checksum -{ - U32 name_off; - U8 len; - CV_C13ChecksumKind kind; -}; - -//- Lines sub-section - -typedef U16 CV_C13SubSecLinesFlags; -enum -{ - CV_C13SubSecLinesFlag_HasColumns = (1 << 0) -}; - -typedef struct CV_C13SubSecLinesHeader CV_C13SubSecLinesHeader; -struct CV_C13SubSecLinesHeader -{ - U32 sec_off; - CV_SectionIndex sec; - CV_C13SubSecLinesFlags flags; - U32 len; -}; - -typedef struct CV_C13File CV_C13File; -struct CV_C13File -{ - U32 file_off; - U32 num_lines; - U32 block_size; - // CV_C13Line[num_lines] lines; - // CV_C13Column[num_lines] columns; (if HasColumns) -}; - -typedef U32 CV_C13LineFlags; -#define CV_C13LineFlags_ExtractLineNumber(f) ((f)&0xFFFFFF) -#define CV_C13LineFlags_ExtractDeltaToEnd(f) (((f)>>24)&0x7F) -#define CV_C13LineFlags_ExtractStatement(f) (((f)>>31)&0x1) - -typedef struct CV_C13Line CV_C13Line; -struct CV_C13Line -{ - U32 off; - CV_C13LineFlags flags; -}; - -typedef struct CV_C13Column CV_C13Column; -struct CV_C13Column -{ - U16 start; - U16 end; -}; - -//- FrameData sub-section - -typedef U32 CV_C13FrameDataFlags; -enum -{ - CV_C13FrameDataFlag_HasStructuredExceptionHandling = (1 << 0), - CV_C13FrameDataFlag_HasExceptionHandling = (1 << 1), - CV_C13FrameDataFlag_HasIsFuncStart = (1 << 2), -}; - -typedef struct CV_C13FrameData CV_C13FrameData; -struct CV_C13FrameData -{ - U32 start_voff; - U32 code_size; - U32 local_size; - U32 params_size; - U32 max_stack_size; - U32 frame_func; - U16 prolog_size; - U16 saved_reg_size; - CV_C13FrameDataFlags flags; -}; - -//- InlineLines sub-section - -typedef U32 CV_C13InlineeLinesSig; -enum -{ - CV_C13InlineeLinesSig_NORMAL, - CV_C13InlineeLinesSig_EXTRA_FILES, -}; - -typedef struct CV_C13InlineeSourceLineHeader CV_C13InlineeSourceLineHeader; -struct CV_C13InlineeSourceLineHeader -{ - CV_ItemId inlinee; // LF_FUNC_ID or LF_MFUNC_ID - U32 file_off; // offset into FileChksms sub-section - U32 first_source_ln; // base source line number for binary annotations - // if sig set to CV_C13InlineeLinesSig_EXTRA_FILES - // U32 extra_file_count; - // U32 files[]; -}; - -#pragma pack(pop) - -//////////////////////////////// -//~ CodeView Common Parser Types - -// CV_Numeric layout -// x: U16 -// buf: U8[] -// case (x < 0x8000): kind=U16 val=x -// case (x >= 0x8000): kind=x val=buf - -typedef struct CV_NumericParsed CV_NumericParsed; -struct CV_NumericParsed -{ - CV_NumericKind kind; - U8 *val; - U64 encoded_size; -}; - -typedef struct CV_RecRange CV_RecRange; -struct CV_RecRange -{ - U32 off; - CV_RecHeader hdr; -}; - -#define CV_REC_RANGE_CHUNK_SIZE 511 - -typedef struct CV_RecRangeChunk CV_RecRangeChunk; -struct CV_RecRangeChunk -{ - struct CV_RecRangeChunk *next; - CV_RecRange ranges[CV_REC_RANGE_CHUNK_SIZE]; -}; - -typedef struct CV_RecRangeStream CV_RecRangeStream; -struct CV_RecRangeStream -{ - CV_RecRangeChunk *first_chunk; - CV_RecRangeChunk *last_chunk; - U64 total_count; -}; - -typedef struct CV_RecRangeArray CV_RecRangeArray; -struct CV_RecRangeArray -{ - CV_RecRange *ranges; - U64 count; -}; - -//////////////////////////////// -//~ CodeView Sym Parser Types - -typedef struct CV_SymTopLevelInfo CV_SymTopLevelInfo; -struct CV_SymTopLevelInfo -{ - CV_Arch arch; - CV_Language language; - String8 compiler_name; -}; - -typedef struct CV_SymParsed CV_SymParsed; -struct CV_SymParsed -{ - // source information - String8 data; - U64 sym_align; - - // sym index derived from source - CV_RecRangeArray sym_ranges; - - // top-level info derived from the syms - CV_SymTopLevelInfo info; -}; - -//////////////////////////////// -//~ CodeView Leaf Parser Types - -typedef struct CV_LeafParsed CV_LeafParsed; -struct CV_LeafParsed -{ - // source information - String8 data; - CV_TypeId itype_first; - CV_TypeId itype_opl; - - // leaf index derived from source - CV_RecRangeArray leaf_ranges; -}; - -//////////////////////////////// -//~ CodeView C13 Info Parser Types - -typedef struct CV_C13LinesParsed CV_C13LinesParsed; -struct CV_C13LinesParsed -{ - // raw info - U32 sec_idx; - U32 file_off; - U64 secrel_base_off; - - // parsed info - String8 file_name; - U64 *voffs; // [line_count + 1] - U32 *line_nums; // [line_count] - U16 *col_nums; // [2*line_count] - U32 line_count; -}; - -typedef struct CV_C13LinesParsedNode CV_C13LinesParsedNode; -struct CV_C13LinesParsedNode -{ - CV_C13LinesParsedNode *next; - CV_C13LinesParsed v; -}; - -typedef struct CV_C13InlineeLinesParsed CV_C13InlineeLinesParsed; -struct CV_C13InlineeLinesParsed -{ - CV_ItemId inlinee; - String8 file_name; - U32 first_source_ln; - U32 extra_file_count; - U32 *extra_files; -}; - -typedef struct CV_C13InlineeLinesParsedNode CV_C13InlineeLinesParsedNode; -struct CV_C13InlineeLinesParsedNode -{ - CV_C13InlineeLinesParsedNode *next; - CV_C13InlineeLinesParsedNode *hash_next; - CV_C13InlineeLinesParsed v; -}; - -typedef struct CV_C13SubSectionNode CV_C13SubSectionNode; -struct CV_C13SubSectionNode -{ - struct CV_C13SubSectionNode *next; - CV_C13SubSectionKind kind; - U32 off; - U32 size; - CV_C13LinesParsedNode *lines_first; - CV_C13LinesParsedNode *lines_last; - CV_C13InlineeLinesParsedNode *inlinee_lines_first; - CV_C13InlineeLinesParsedNode *inlinee_lines_last; -}; - -typedef struct CV_C13Parsed CV_C13Parsed; -struct CV_C13Parsed -{ - // rjf: source data - String8 data; - - // rjf: full sub-section list - CV_C13SubSectionNode *first_sub_section; - CV_C13SubSectionNode *last_sub_section; - U64 sub_section_count; - - // rjf: fastpath to file checksums section - CV_C13SubSectionNode *file_chksms_sub_section; - - // rjf: fastpath to map inlinee CV_ItemId -> CV_InlineeLinesParsed quickly - CV_C13InlineeLinesParsedNode **inlinee_lines_parsed_slots; - U64 inlinee_lines_parsed_slots_count; -}; - -//////////////////////////////// -//~ CodeView Compound Types - -typedef struct CV_TypeIdArray CV_TypeIdArray; -struct CV_TypeIdArray -{ - CV_TypeId *itypes; - U64 count; -}; - -//////////////////////////////// -//~ CodeView Common Decoding Helper Functions - -internal U64 cv_hash_from_string(String8 string); -internal U64 cv_hash_from_item_id(CV_ItemId item_id); - -internal CV_NumericParsed cv_numeric_from_data_range(U8 *first, U8 *opl); - -internal B32 cv_numeric_fits_in_u64(CV_NumericParsed *num); -internal B32 cv_numeric_fits_in_s64(CV_NumericParsed *num); -internal B32 cv_numeric_fits_in_f64(CV_NumericParsed *num); - -internal U64 cv_u64_from_numeric(CV_NumericParsed *num); -internal S64 cv_s64_from_numeric(CV_NumericParsed *num); -internal F64 cv_f64_from_numeric(CV_NumericParsed *num); - -internal U64 cv_decode_inline_annot_u32(String8 data, U64 offset, U32 *out_value); -internal U64 cv_decode_inline_annot_s32(String8 data, U64 offset, S32 *out_value); - -internal S32 cv_inline_annot_signed_from_unsigned_operand(U32 value); - - -//////////////////////////////// -//~ CodeView Parsing Functions - -//- rjf: record range stream parsing -internal CV_RecRangeStream *cv_rec_range_stream_from_data(Arena *arena, String8 data, U64 align); -internal CV_RecRangeArray cv_rec_range_array_from_stream(Arena *arena, CV_RecRangeStream *stream); - -//- rjf: sym stream parsing -internal CV_SymParsed *cv_sym_from_data(Arena *arena, String8 sym_data, U64 sym_align); - -//- rjf: leaf stream parsing -internal CV_LeafParsed *cv_leaf_from_data(Arena *arena, String8 leaf_data, CV_TypeId first); - -//////////////////////////////// -//~ CodeView C13 Parser Functions - -typedef struct PDB_Strtbl PDB_Strtbl; -typedef struct PDB_CoffSectionArray PDB_CoffSectionArray; -internal CV_C13Parsed *cv_c13_parsed_from_data(Arena *arena, String8 c13_data, struct PDB_Strtbl *strtbl, struct PDB_CoffSectionArray *sections); - -#endif // CODEVIEW_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef CODEVIEW_H +#define CODEVIEW_H + +// https://github.com/microsoft/microsoft-pdb/blob/master/include/cvinfo.h + +//////////////////////////////// +//~ rjf: CodeView Format Shared Types + +typedef U32 CV_TypeId; +typedef U32 CV_ItemId; +typedef U16 CV_ModIndex; +typedef U16 CV_SectionIndex; +typedef U16 CV_Reg; + +read_only global CV_TypeId cv_type_id_variadic = 0xFFFFFFFF; + +//////////////////////////////// +//~ rjf: Generated Code + +#include "generated/codeview.meta.h" + +//////////////////////////////// +//~ rjf: Registers + +// X(NAME, CODE, (RDI_RegCode_X86) NAME, BYTE_POS, BYTE_SIZE) +#define CV_Reg_X86_XList(X) \ +X(NONE, 0, nil, 0, 0)\ +X(AL, 1, eax, 0, 1)\ +X(CL, 2, ecx, 0, 1)\ +X(DL, 3, edx, 0, 1)\ +X(BL, 4, ebx, 0, 1)\ +X(AH, 5, eax, 1, 1)\ +X(CH, 6, ecx, 1, 1)\ +X(DH, 7, edx, 1, 1)\ +X(BH, 8, ebx, 1, 1)\ +X(AX, 9, eax, 0, 2)\ +X(CX, 10, ecx, 0, 2)\ +X(DX, 11, edx, 0, 2)\ +X(BX, 12, ebx, 0, 2)\ +X(SP, 13, esp, 0, 2)\ +X(BP, 14, ebp, 0, 2)\ +X(SI, 15, esi, 0, 2)\ +X(DI, 16, edi, 0, 2)\ +X(EAX, 17, eax, 0, 4)\ +X(ECX, 18, ecx, 0, 4)\ +X(EDX, 19, edx, 0, 4)\ +X(EBX, 20, ebx, 0, 4)\ +X(ESP, 21, esp, 0, 4)\ +X(EBP, 22, ebp, 0, 4)\ +X(ESI, 23, esi, 0, 4)\ +X(EDI, 24, edi, 0, 4)\ +X(ES, 25, es, 0, 2)\ +X(CS, 26, cs, 0, 2)\ +X(SS, 27, ss, 0, 2)\ +X(DS, 28, ds, 0, 2)\ +X(FS, 29, fs, 0, 2)\ +X(GS, 30, gs, 0, 2)\ +X(IP, 31, eip, 0, 2)\ +X(FLAGS, 32, eflags, 0, 2)\ +X(EIP, 33, eip, 0, 4)\ +X(EFLAGS, 34, eflags, 0, 4)\ +X(MM0, 146, fpr0, 0, 8)\ +X(MM1, 147, fpr1, 0, 8)\ +X(MM2, 148, fpr2, 0, 8)\ +X(MM3, 149, fpr3, 0, 8)\ +X(MM4, 150, fpr4, 0, 8)\ +X(MM5, 151, fpr5, 0, 8)\ +X(MM6, 152, fpr6, 0, 8)\ +X(MM7, 153, fpr7, 0, 8)\ +X(XMM0, 154, ymm0, 0, 16)\ +X(XMM1, 155, ymm1, 0, 16)\ +X(XMM2, 156, ymm2, 0, 16)\ +X(XMM3, 157, ymm3, 0, 16)\ +X(XMM4, 158, ymm4, 0, 16)\ +X(XMM5, 159, ymm5, 0, 16)\ +X(XMM6, 160, ymm6, 0, 16)\ +X(XMM7, 161, ymm7, 0, 16)\ +X(XMM00, 162, ymm0, 0, 4)\ +X(XMM01, 163, ymm0, 4, 4)\ +X(XMM02, 164, ymm0, 8, 4)\ +X(XMM03, 165, ymm0, 12, 4)\ +X(XMM10, 166, ymm1, 0, 4)\ +X(XMM11, 167, ymm1, 4, 4)\ +X(XMM12, 168, ymm1, 8, 4)\ +X(XMM13, 169, ymm1, 12, 4)\ +X(XMM20, 170, ymm2, 0, 4)\ +X(XMM21, 171, ymm2, 4, 4)\ +X(XMM22, 172, ymm2, 8, 4)\ +X(XMM23, 173, ymm2, 12, 4)\ +X(XMM30, 174, ymm3, 0, 4)\ +X(XMM31, 175, ymm3, 4, 4)\ +X(XMM32, 176, ymm3, 8, 4)\ +X(XMM33, 177, ymm3, 12, 4)\ +X(XMM40, 178, ymm4, 0, 4)\ +X(XMM41, 179, ymm4, 4, 4)\ +X(XMM42, 180, ymm4, 8, 4)\ +X(XMM43, 181, ymm4, 12, 4)\ +X(XMM50, 182, ymm5, 0, 4)\ +X(XMM51, 183, ymm5, 4, 4)\ +X(XMM52, 184, ymm5, 8, 4)\ +X(XMM53, 185, ymm5, 12, 4)\ +X(XMM60, 186, ymm6, 0, 4)\ +X(XMM61, 187, ymm6, 4, 4)\ +X(XMM62, 188, ymm6, 8, 4)\ +X(XMM63, 189, ymm6, 12, 4)\ +X(XMM70, 190, ymm7, 0, 4)\ +X(XMM71, 191, ymm7, 4, 4)\ +X(XMM72, 192, ymm7, 8, 4)\ +X(XMM73, 193, ymm7, 12, 4)\ +X(XMM0L, 194, ymm0, 0, 8)\ +X(XMM1L, 195, ymm1, 0, 8)\ +X(XMM2L, 196, ymm2, 0, 8)\ +X(XMM3L, 197, ymm3, 0, 8)\ +X(XMM4L, 198, ymm4, 0, 8)\ +X(XMM5L, 199, ymm5, 0, 8)\ +X(XMM6L, 200, ymm6, 0, 8)\ +X(XMM7L, 201, ymm7, 0, 8)\ +X(XMM0H, 202, ymm0, 8, 8)\ +X(XMM1H, 203, ymm1, 8, 8)\ +X(XMM2H, 204, ymm2, 8, 8)\ +X(XMM3H, 205, ymm3, 8, 8)\ +X(XMM4H, 206, ymm4, 8, 8)\ +X(XMM5H, 207, ymm5, 8, 8)\ +X(XMM6H, 208, ymm6, 8, 8)\ +X(XMM7H, 209, ymm7, 8, 8)\ +X(YMM0, 252, ymm0, 0, 32)\ +X(YMM1, 253, ymm1, 0, 32)\ +X(YMM2, 254, ymm2, 0, 32)\ +X(YMM3, 255, ymm3, 0, 32)\ +X(YMM4, 256, ymm4, 0, 32)\ +X(YMM5, 257, ymm5, 0, 32)\ +X(YMM6, 258, ymm6, 0, 32)\ +X(YMM7, 259, ymm7, 0, 32)\ +X(YMM0H, 260, ymm0, 16, 16)\ +X(YMM1H, 261, ymm1, 16, 16)\ +X(YMM2H, 262, ymm2, 16, 16)\ +X(YMM3H, 263, ymm3, 16, 16)\ +X(YMM4H, 264, ymm4, 16, 16)\ +X(YMM5H, 265, ymm5, 16, 16)\ +X(YMM6H, 266, ymm6, 16, 16)\ +X(YMM7H, 267, ymm7, 16, 16)\ +X(YMM0I0, 268, ymm0, 0, 8)\ +X(YMM0I1, 269, ymm0, 8, 8)\ +X(YMM0I2, 270, ymm0, 16, 8)\ +X(YMM0I3, 271, ymm0, 24, 8)\ +X(YMM1I0, 272, ymm1, 0, 8)\ +X(YMM1I1, 273, ymm1, 8, 8)\ +X(YMM1I2, 274, ymm1, 16, 8)\ +X(YMM1I3, 275, ymm1, 24, 8)\ +X(YMM2I0, 276, ymm2, 0, 8)\ +X(YMM2I1, 277, ymm2, 8, 8)\ +X(YMM2I2, 278, ymm2, 16, 8)\ +X(YMM2I3, 279, ymm2, 24, 8)\ +X(YMM3I0, 280, ymm3, 0, 8)\ +X(YMM3I1, 281, ymm3, 8, 8)\ +X(YMM3I2, 282, ymm3, 16, 8)\ +X(YMM3I3, 283, ymm3, 24, 8)\ +X(YMM4I0, 284, ymm4, 0, 8)\ +X(YMM4I1, 285, ymm4, 8, 8)\ +X(YMM4I2, 286, ymm4, 16, 8)\ +X(YMM4I3, 287, ymm4, 24, 8)\ +X(YMM5I0, 288, ymm5, 0, 8)\ +X(YMM5I1, 289, ymm5, 8, 8)\ +X(YMM5I2, 290, ymm5, 16, 8)\ +X(YMM5I3, 291, ymm5, 24, 8)\ +X(YMM6I0, 292, ymm6, 0, 8)\ +X(YMM6I1, 293, ymm6, 8, 8)\ +X(YMM6I2, 294, ymm6, 16, 8)\ +X(YMM6I3, 295, ymm6, 24, 8)\ +X(YMM7I0, 296, ymm7, 0, 8)\ +X(YMM7I1, 297, ymm7, 8, 8)\ +X(YMM7I2, 298, ymm7, 16, 8)\ +X(YMM7I3, 299, ymm7, 24, 8)\ +X(YMM0F0, 300, ymm0, 0, 4)\ +X(YMM0F1, 301, ymm0, 4, 4)\ +X(YMM0F2, 302, ymm0, 8, 4)\ +X(YMM0F3, 303, ymm0, 12, 4)\ +X(YMM0F4, 304, ymm0, 16, 4)\ +X(YMM0F5, 305, ymm0, 20, 4)\ +X(YMM0F6, 306, ymm0, 24, 4)\ +X(YMM0F7, 307, ymm0, 28, 4)\ +X(YMM1F0, 308, ymm1, 0, 4)\ +X(YMM1F1, 309, ymm1, 4, 4)\ +X(YMM1F2, 310, ymm1, 8, 4)\ +X(YMM1F3, 311, ymm1, 12, 4)\ +X(YMM1F4, 312, ymm1, 16, 4)\ +X(YMM1F5, 313, ymm1, 20, 4)\ +X(YMM1F6, 314, ymm1, 24, 4)\ +X(YMM1F7, 315, ymm1, 28, 4)\ +X(YMM2F0, 316, ymm2, 0, 4)\ +X(YMM2F1, 317, ymm2, 4, 4)\ +X(YMM2F2, 318, ymm2, 8, 4)\ +X(YMM2F3, 319, ymm2, 12, 4)\ +X(YMM2F4, 320, ymm2, 16, 4)\ +X(YMM2F5, 321, ymm2, 20, 4)\ +X(YMM2F6, 322, ymm2, 24, 4)\ +X(YMM2F7, 323, ymm2, 28, 4)\ +X(YMM3F0, 324, ymm3, 0, 4)\ +X(YMM3F1, 325, ymm3, 4, 4)\ +X(YMM3F2, 326, ymm3, 8, 4)\ +X(YMM3F3, 327, ymm3, 12, 4)\ +X(YMM3F4, 328, ymm3, 16, 4)\ +X(YMM3F5, 329, ymm3, 20, 4)\ +X(YMM3F6, 330, ymm3, 24, 4)\ +X(YMM3F7, 331, ymm3, 28, 4)\ +X(YMM4F0, 332, ymm4, 0, 4)\ +X(YMM4F1, 333, ymm4, 4, 4)\ +X(YMM4F2, 334, ymm4, 8, 4)\ +X(YMM4F3, 335, ymm4, 12, 4)\ +X(YMM4F4, 336, ymm4, 16, 4)\ +X(YMM4F5, 337, ymm4, 20, 4)\ +X(YMM4F6, 338, ymm4, 24, 4)\ +X(YMM4F7, 339, ymm4, 28, 4)\ +X(YMM5F0, 340, ymm5, 0, 4)\ +X(YMM5F1, 341, ymm5, 4, 4)\ +X(YMM5F2, 342, ymm5, 8, 4)\ +X(YMM5F3, 343, ymm5, 12, 4)\ +X(YMM5F4, 344, ymm5, 16, 4)\ +X(YMM5F5, 345, ymm5, 20, 4)\ +X(YMM5F6, 346, ymm5, 24, 4)\ +X(YMM5F7, 347, ymm5, 28, 4)\ +X(YMM6F0, 348, ymm6, 0, 4)\ +X(YMM6F1, 349, ymm6, 4, 4)\ +X(YMM6F2, 350, ymm6, 8, 4)\ +X(YMM6F3, 351, ymm6, 12, 4)\ +X(YMM6F4, 352, ymm6, 16, 4)\ +X(YMM6F5, 353, ymm6, 20, 4)\ +X(YMM6F6, 354, ymm6, 24, 4)\ +X(YMM6F7, 355, ymm6, 28, 4)\ +X(YMM7F0, 356, ymm7, 0, 4)\ +X(YMM7F1, 357, ymm7, 4, 4)\ +X(YMM7F2, 358, ymm7, 8, 4)\ +X(YMM7F3, 359, ymm7, 12, 4)\ +X(YMM7F4, 360, ymm7, 16, 4)\ +X(YMM7F5, 361, ymm7, 20, 4)\ +X(YMM7F6, 362, ymm7, 24, 4)\ +X(YMM7F7, 363, ymm7, 28, 4)\ +X(YMM0D0, 364, ymm0, 0, 8)\ +X(YMM0D1, 365, ymm0, 8, 8)\ +X(YMM0D2, 366, ymm0, 16, 8)\ +X(YMM0D3, 367, ymm0, 24, 8)\ +X(YMM1D0, 368, ymm1, 0, 8)\ +X(YMM1D1, 369, ymm1, 8, 8)\ +X(YMM1D2, 370, ymm1, 16, 8)\ +X(YMM1D3, 371, ymm1, 24, 8)\ +X(YMM2D0, 372, ymm2, 0, 8)\ +X(YMM2D1, 373, ymm2, 8, 8)\ +X(YMM2D2, 374, ymm2, 16, 8)\ +X(YMM2D3, 375, ymm2, 24, 8)\ +X(YMM3D0, 376, ymm3, 0, 8)\ +X(YMM3D1, 377, ymm3, 8, 8)\ +X(YMM3D2, 378, ymm3, 16, 8)\ +X(YMM3D3, 379, ymm3, 24, 8)\ +X(YMM4D0, 380, ymm4, 0, 8)\ +X(YMM4D1, 381, ymm4, 8, 8)\ +X(YMM4D2, 382, ymm4, 16, 8)\ +X(YMM4D3, 383, ymm4, 24, 8)\ +X(YMM5D0, 384, ymm5, 0, 8)\ +X(YMM5D1, 385, ymm5, 8, 8)\ +X(YMM5D2, 386, ymm5, 16, 8)\ +X(YMM5D3, 387, ymm5, 24, 8)\ +X(YMM6D0, 388, ymm6, 0, 8)\ +X(YMM6D1, 389, ymm6, 8, 8)\ +X(YMM6D2, 390, ymm6, 16, 8)\ +X(YMM6D3, 391, ymm6, 24, 8)\ +X(YMM7D0, 392, ymm7, 0, 8)\ +X(YMM7D1, 393, ymm7, 8, 8)\ +X(YMM7D2, 394, ymm7, 16, 8)\ +X(YMM7D3, 395, ymm7, 24, 8) + +typedef U16 CV_Regx86; +typedef enum CV_Regx86Enum +{ +#define X(CVN,C,RDN,BP,BZ) CV_Regx86_##CVN = C, + CV_Reg_X86_XList(X) +#undef X +} +CV_Regx86Enum; + +// X(NAME, CODE, (RDI_RegisterCode_X64) NAME, BYTE_POS, BYTE_SIZE) +#define CV_Reg_X64_XList(X) \ +X(NONE, 0, nil, 0, 0)\ +X(AL, 1, rax, 0, 1)\ +X(CL, 2, rcx, 0, 1)\ +X(DL, 3, rdx, 0, 1)\ +X(BL, 4, rbx, 0, 1)\ +X(AH, 5, rax, 1, 1)\ +X(CH, 6, rcx, 1, 1)\ +X(DH, 7, rdx, 1, 1)\ +X(BH, 8, rbx, 1, 1)\ +X(AX, 9, rax, 0, 2)\ +X(CX, 10, rcx, 0, 2)\ +X(DX, 11, rdx, 0, 2)\ +X(BX, 12, rbx, 0, 2)\ +X(SP, 13, rsp, 0, 2)\ +X(BP, 14, rbp, 0, 2)\ +X(SI, 15, rsi, 0, 2)\ +X(DI, 16, rdi, 0, 2)\ +X(EAX, 17, rax, 0, 4)\ +X(ECX, 18, rcx, 0, 4)\ +X(EDX, 19, rdx, 0, 4)\ +X(EBX, 20, rbx, 0, 4)\ +X(ESP, 21, rsp, 0, 4)\ +X(EBP, 22, rbp, 0, 4)\ +X(ESI, 23, rsi, 0, 4)\ +X(EDI, 24, rdi, 0, 4)\ +X(ES, 25, es, 0, 2)\ +X(CS, 26, cs, 0, 2)\ +X(SS, 27, ss, 0, 2)\ +X(DS, 28, ds, 0, 2)\ +X(FS, 29, fs, 0, 2)\ +X(GS, 30, gs, 0, 2)\ +X(FLAGS, 32, rflags, 0, 2)\ +X(RIP, 33, rip, 0, 8)\ +X(EFLAGS, 34, rflags, 0, 4)\ +/* TODO: possibly missing control registers in x64 definitions? */ \ +X(CR0, 80, nil, 0, 0)\ +X(CR1, 81, nil, 0, 0)\ +X(CR2, 82, nil, 0, 0)\ +X(CR3, 83, nil, 0, 0)\ +X(CR4, 84, nil, 0, 0)\ +X(CR8, 88, nil, 0, 0)\ +X(DR0, 90, dr0, 0, 4)\ +X(DR1, 91, dr1, 0, 4)\ +X(DR2, 92, dr2, 0, 4)\ +X(DR3, 93, dr3, 0, 4)\ +X(DR4, 94, dr4, 0, 4)\ +X(DR5, 95, dr5, 0, 4)\ +X(DR6, 96, dr6, 0, 4)\ +X(DR7, 97, dr7, 0, 4)\ +/* TODO: possibly missing debug registers 8-15 in x64 definitions? */ \ +X(DR8, 98, nil, 0, 0)\ +X(DR9, 99, nil, 0, 0)\ +X(DR10, 100, nil, 0, 0)\ +X(DR11, 101, nil, 0, 0)\ +X(DR12, 102, nil, 0, 0)\ +X(DR13, 103, nil, 0, 0)\ +X(DR14, 104, nil, 0, 0)\ +X(DR15, 105, nil, 0, 0)\ +/* TODO: possibly missing ~whatever these are~ in x64 definitions? */ \ +X(GDTR, 110, nil, 0, 0)\ +X(GDTL, 111, nil, 0, 0)\ +X(IDTR, 112, nil, 0, 0)\ +X(IDTL, 113, nil, 0, 0)\ +X(LDTR, 114, nil, 0, 0)\ +X(TR, 115, nil, 0, 0)\ +X(ST0, 128, st0, 0, 10)\ +X(ST1, 129, st1, 0, 10)\ +X(ST2, 130, st2, 0, 10)\ +X(ST3, 131, st3, 0, 10)\ +X(ST4, 132, st4, 0, 10)\ +X(ST5, 133, st5, 0, 10)\ +X(ST6, 134, st6, 0, 10)\ +X(ST7, 135, st7, 0, 10)\ +/* TODO: possibly missing these, or not sure how they map to our x64 definitions? */ \ +X(CTRL, 136, nil, 0, 0)\ +X(STAT, 137, nil, 0, 0)\ +X(TAG, 138, nil, 0, 0)\ +X(FPIP, 139, nil, 0, 0)\ +X(FPCS, 140, nil, 0, 0)\ +X(FPDO, 141, nil, 0, 0)\ +X(FPDS, 142, nil, 0, 0)\ +X(ISEM, 143, nil, 0, 0)\ +X(FPEIP, 144, nil, 0, 0)\ +X(FPEDO, 145, nil, 0, 0)\ +X(MM0, 146, fpr0, 0, 8)\ +X(MM1, 147, fpr1, 0, 8)\ +X(MM2, 148, fpr2, 0, 8)\ +X(MM3, 149, fpr3, 0, 8)\ +X(MM4, 150, fpr4, 0, 8)\ +X(MM5, 151, fpr5, 0, 8)\ +X(MM6, 152, fpr6, 0, 8)\ +X(MM7, 153, fpr7, 0, 8)\ +X(XMM0, 154, ymm0, 0, 16)\ +X(XMM1, 155, ymm1, 0, 16)\ +X(XMM2, 156, ymm2, 0, 16)\ +X(XMM3, 157, ymm3, 0, 16)\ +X(XMM4, 158, ymm4, 0, 16)\ +X(XMM5, 159, ymm5, 0, 16)\ +X(XMM6, 160, ymm6, 0, 16)\ +X(XMM7, 161, ymm7, 0, 16)\ +X(XMM0_0, 162, ymm0, 0, 4)\ +X(XMM0_1, 163, ymm0, 4, 4)\ +X(XMM0_2, 164, ymm0, 8, 4)\ +X(XMM0_3, 165, ymm0, 12, 4)\ +X(XMM1_0, 166, ymm1, 0, 4)\ +X(XMM1_1, 167, ymm1, 4, 4)\ +X(XMM1_2, 168, ymm1, 8, 4)\ +X(XMM1_3, 169, ymm1, 12, 4)\ +X(XMM2_0, 170, ymm2, 0, 4)\ +X(XMM2_1, 171, ymm2, 4, 4)\ +X(XMM2_2, 172, ymm2, 8, 4)\ +X(XMM2_3, 173, ymm2, 12, 4)\ +X(XMM3_0, 174, ymm3, 0, 4)\ +X(XMM3_1, 175, ymm3, 4, 4)\ +X(XMM3_2, 176, ymm3, 8, 4)\ +X(XMM3_3, 177, ymm3, 12, 4)\ +X(XMM4_0, 178, ymm4, 0, 4)\ +X(XMM4_1, 179, ymm4, 4, 4)\ +X(XMM4_2, 180, ymm4, 8, 4)\ +X(XMM4_3, 181, ymm4, 12, 4)\ +X(XMM5_0, 182, ymm5, 0, 4)\ +X(XMM5_1, 183, ymm5, 4, 4)\ +X(XMM5_2, 184, ymm5, 8, 4)\ +X(XMM5_3, 185, ymm5, 12, 4)\ +X(XMM6_0, 186, ymm6, 0, 4)\ +X(XMM6_1, 187, ymm6, 4, 4)\ +X(XMM6_2, 188, ymm6, 8, 4)\ +X(XMM6_3, 189, ymm6, 12, 4)\ +X(XMM7_0, 190, ymm7, 0, 4)\ +X(XMM7_1, 191, ymm7, 4, 4)\ +X(XMM7_2, 192, ymm7, 8, 4)\ +X(XMM7_3, 193, ymm7, 12, 4)\ +X(XMM0L, 194, ymm0, 0, 8)\ +X(XMM1L, 195, ymm1, 0, 8)\ +X(XMM2L, 196, ymm2, 0, 8)\ +X(XMM3L, 197, ymm3, 0, 8)\ +X(XMM4L, 198, ymm4, 0, 8)\ +X(XMM5L, 199, ymm5, 0, 8)\ +X(XMM6L, 200, ymm6, 0, 8)\ +X(XMM7L, 201, ymm7, 0, 8)\ +X(XMM0H, 202, ymm0, 8, 8)\ +X(XMM1H, 203, ymm1, 8, 8)\ +X(XMM2H, 204, ymm2, 8, 8)\ +X(XMM3H, 205, ymm3, 8, 8)\ +X(XMM4H, 206, ymm4, 8, 8)\ +X(XMM5H, 207, ymm5, 8, 8)\ +X(XMM6H, 208, ymm6, 8, 8)\ +X(XMM7H, 209, ymm7, 8, 8)\ +X(MXCSR, 211, mxcsr, 0, 4)\ +X(EMM0L, 220, ymm0, 0, 8)\ +X(EMM1L, 221, ymm1, 0, 8)\ +X(EMM2L, 222, ymm2, 0, 8)\ +X(EMM3L, 223, ymm3, 0, 8)\ +X(EMM4L, 224, ymm4, 0, 8)\ +X(EMM5L, 225, ymm5, 0, 8)\ +X(EMM6L, 226, ymm6, 0, 8)\ +X(EMM7L, 227, ymm7, 0, 8)\ +X(EMM0H, 228, ymm0, 8, 8)\ +X(EMM1H, 229, ymm1, 8, 8)\ +X(EMM2H, 230, ymm2, 8, 8)\ +X(EMM3H, 231, ymm3, 8, 8)\ +X(EMM4H, 232, ymm4, 8, 8)\ +X(EMM5H, 233, ymm5, 8, 8)\ +X(EMM6H, 234, ymm6, 8, 8)\ +X(EMM7H, 235, ymm7, 8, 8)\ +X(MM00, 236, fpr0, 0, 4)\ +X(MM01, 237, fpr0, 4, 4)\ +X(MM10, 238, fpr1, 0, 4)\ +X(MM11, 239, fpr1, 4, 4)\ +X(MM20, 240, fpr2, 0, 4)\ +X(MM21, 241, fpr2, 4, 4)\ +X(MM30, 242, fpr3, 0, 4)\ +X(MM31, 243, fpr3, 4, 4)\ +X(MM40, 244, fpr4, 0, 4)\ +X(MM41, 245, fpr4, 4, 4)\ +X(MM50, 246, fpr5, 0, 4)\ +X(MM51, 247, fpr5, 4, 4)\ +X(MM60, 248, fpr6, 0, 4)\ +X(MM61, 249, fpr6, 4, 4)\ +X(MM70, 250, fpr7, 0, 4)\ +X(MM71, 251, fpr7, 4, 4)\ +X(XMM8, 252, ymm8, 0, 16)\ +X(XMM9, 253, ymm9, 0, 16)\ +X(XMM10, 254, ymm10, 0, 16)\ +X(XMM11, 255, ymm11, 0, 16)\ +X(XMM12, 256, ymm12, 0, 16)\ +X(XMM13, 257, ymm13, 0, 16)\ +X(XMM14, 258, ymm14, 0, 16)\ +X(XMM15, 259, ymm15, 0, 16)\ +X(XMM8_0, 260, ymm8, 0, 16)\ +X(XMM8_1, 261, ymm8, 4, 16)\ +X(XMM8_2, 262, ymm8, 8, 16)\ +X(XMM8_3, 263, ymm8, 12, 16)\ +X(XMM9_0, 264, ymm9, 0, 4)\ +X(XMM9_1, 265, ymm9, 4, 4)\ +X(XMM9_2, 266, ymm9, 8, 4)\ +X(XMM9_3, 267, ymm9, 12, 4)\ +X(XMM10_0, 268, ymm10, 0, 4)\ +X(XMM10_1, 269, ymm10, 4, 4)\ +X(XMM10_2, 270, ymm10, 8, 4)\ +X(XMM10_3, 271, ymm10, 12, 4)\ +X(XMM11_0, 272, ymm11, 0, 4)\ +X(XMM11_1, 273, ymm11, 4, 4)\ +X(XMM11_2, 274, ymm11, 8, 4)\ +X(XMM11_3, 275, ymm11, 12, 4)\ +X(XMM12_0, 276, ymm12, 0, 4)\ +X(XMM12_1, 277, ymm12, 4, 4)\ +X(XMM12_2, 278, ymm12, 8, 4)\ +X(XMM12_3, 279, ymm12, 12, 4)\ +X(XMM13_0, 280, ymm13, 0, 4)\ +X(XMM13_1, 281, ymm13, 4, 4)\ +X(XMM13_2, 282, ymm13, 8, 4)\ +X(XMM13_3, 283, ymm13, 12, 4)\ +X(XMM14_0, 284, ymm14, 0, 4)\ +X(XMM14_1, 285, ymm14, 4, 4)\ +X(XMM14_2, 286, ymm14, 8, 4)\ +X(XMM14_3, 287, ymm14, 12, 4)\ +X(XMM15_0, 288, ymm15, 0, 4)\ +X(XMM15_1, 289, ymm15, 4, 4)\ +X(XMM15_2, 290, ymm15, 8, 4)\ +X(XMM15_3, 291, ymm15, 12, 4)\ +X(XMM8L, 292, ymm8, 0, 8)\ +X(XMM9L, 293, ymm9, 0, 8)\ +X(XMM10L, 294, ymm10, 0, 8)\ +X(XMM11L, 295, ymm11, 0, 8)\ +X(XMM12L, 296, ymm12, 0, 8)\ +X(XMM13L, 297, ymm13, 0, 8)\ +X(XMM14L, 298, ymm14, 0, 8)\ +X(XMM15L, 299, ymm15, 0, 8)\ +X(XMM8H, 300, ymm8, 8, 8)\ +X(XMM9H, 301, ymm9, 8, 8)\ +X(XMM10H, 302, ymm10, 8, 8)\ +X(XMM11H, 303, ymm11, 8, 8)\ +X(XMM12H, 304, ymm12, 8, 8)\ +X(XMM13H, 305, ymm13, 8, 8)\ +X(XMM14H, 306, ymm14, 8, 8)\ +X(XMM15H, 307, ymm15, 8, 8)\ +X(EMM8L, 308, ymm8, 0, 8)\ +X(EMM9L, 309, ymm9, 0, 8)\ +X(EMM10L, 310, ymm10, 0, 8)\ +X(EMM11L, 311, ymm11, 0, 8)\ +X(EMM12L, 312, ymm12, 0, 8)\ +X(EMM13L, 313, ymm13, 0, 8)\ +X(EMM14L, 314, ymm14, 0, 8)\ +X(EMM15L, 315, ymm15, 0, 8)\ +X(EMM8H, 316, ymm8, 8, 8)\ +X(EMM9H, 317, ymm9, 8, 8)\ +X(EMM10H, 318, ymm10, 8, 8)\ +X(EMM11H, 319, ymm11, 8, 8)\ +X(EMM12H, 320, ymm12, 8, 8)\ +X(EMM13H, 321, ymm13, 8, 8)\ +X(EMM14H, 322, ymm14, 8, 8)\ +X(EMM15H, 323, ymm15, 8, 8)\ +X(SIL, 324, rsi, 0, 1)\ +X(DIL, 325, rdi, 0, 1)\ +X(BPL, 326, rbp, 0, 1)\ +X(SPL, 327, rsp, 0, 1)\ +X(RAX, 328, rax, 0, 8)\ +X(RBX, 329, rbx, 0, 8)\ +X(RCX, 330, rcx, 0, 8)\ +X(RDX, 331, rdx, 0, 8)\ +X(RSI, 332, rsi, 0, 8)\ +X(RDI, 333, rdi, 0, 8)\ +X(RBP, 334, rbp, 0, 8)\ +X(RSP, 335, rsp, 0, 8)\ +X(R8, 336, r8, 0, 8)\ +X(R9, 337, r9, 0, 8)\ +X(R10, 338, r10, 0, 8)\ +X(R11, 339, r11, 0, 8)\ +X(R12, 340, r12, 0, 8)\ +X(R13, 341, r13, 0, 8)\ +X(R14, 342, r14, 0, 8)\ +X(R15, 343, r15, 0, 8)\ +X(R8B, 344, r8, 0, 1)\ +X(R9B, 345, r9, 0, 1)\ +X(R10B, 346, r10, 0, 1)\ +X(R11B, 347, r11, 0, 1)\ +X(R12B, 348, r12, 0, 1)\ +X(R13B, 349, r13, 0, 1)\ +X(R14B, 350, r14, 0, 1)\ +X(R15B, 351, r15, 0, 1)\ +X(R8W, 352, r8, 0, 2)\ +X(R9W, 353, r9, 0, 2)\ +X(R10W, 354, r10, 0, 2)\ +X(R11W, 355, r11, 0, 2)\ +X(R12W, 356, r12, 0, 2)\ +X(R13W, 357, r13, 0, 2)\ +X(R14W, 358, r14, 0, 2)\ +X(R15W, 359, r15, 0, 2)\ +X(R8D, 360, r8, 0, 4)\ +X(R9D, 361, r9, 0, 4)\ +X(R10D, 362, r10, 0, 4)\ +X(R11D, 363, r11, 0, 4)\ +X(R12D, 364, r12, 0, 4)\ +X(R13D, 365, r13, 0, 4)\ +X(R14D, 366, r14, 0, 4)\ +X(R15D, 367, r15, 0, 4)\ +X(YMM0, 368, ymm0, 0, 32)\ +X(YMM1, 369, ymm1, 0, 32)\ +X(YMM2, 370, ymm2, 0, 32)\ +X(YMM3, 371, ymm3, 0, 32)\ +X(YMM4, 372, ymm4, 0, 32)\ +X(YMM5, 373, ymm5, 0, 32)\ +X(YMM6, 374, ymm6, 0, 32)\ +X(YMM7, 375, ymm7, 0, 32)\ +X(YMM8, 376, ymm8, 0, 32)\ +X(YMM9, 377, ymm9, 0, 32)\ +X(YMM10, 378, ymm10, 0, 32)\ +X(YMM11, 379, ymm11, 0, 32)\ +X(YMM12, 380, ymm12, 0, 32)\ +X(YMM13, 381, ymm13, 0, 32)\ +X(YMM14, 382, ymm14, 0, 32)\ +X(YMM15, 383, ymm15, 0, 32)\ +X(YMM0H, 384, ymm0, 16, 32)\ +X(YMM1H, 385, ymm1, 16, 32)\ +X(YMM2H, 386, ymm2, 16, 32)\ +X(YMM3H, 387, ymm3, 16, 32)\ +X(YMM4H, 388, ymm4, 16, 32)\ +X(YMM5H, 389, ymm5, 16, 32)\ +X(YMM6H, 390, ymm6, 16, 32)\ +X(YMM7H, 391, ymm7, 16, 32)\ +X(YMM8H, 392, ymm8, 16, 32)\ +X(YMM9H, 393, ymm9, 16, 32)\ +X(YMM10H, 394, ymm10, 16, 32)\ +X(YMM11H, 395, ymm11, 16, 32)\ +X(YMM12H, 396, ymm12, 16, 32)\ +X(YMM13H, 397, ymm13, 16, 32)\ +X(YMM14H, 398, ymm14, 16, 32)\ +X(YMM15H, 399, ymm15, 16, 32)\ +X(XMM0IL, 400, ymm0, 0, 8)\ +X(XMM1IL, 401, ymm1, 0, 8)\ +X(XMM2IL, 402, ymm2, 0, 8)\ +X(XMM3IL, 403, ymm3, 0, 8)\ +X(XMM4IL, 404, ymm4, 0, 8)\ +X(XMM5IL, 405, ymm5, 0, 8)\ +X(XMM6IL, 406, ymm6, 0, 8)\ +X(XMM7IL, 407, ymm7, 0, 8)\ +X(XMM8IL, 408, ymm8, 0, 8)\ +X(XMM9IL, 409, ymm9, 0, 8)\ +X(XMM10IL, 410, ymm10, 0, 8)\ +X(XMM11IL, 411, ymm11, 0, 8)\ +X(XMM12IL, 412, ymm12, 0, 8)\ +X(XMM13IL, 413, ymm13, 0, 8)\ +X(XMM14IL, 414, ymm14, 0, 8)\ +X(XMM15IL, 415, ymm15, 0, 8)\ +X(XMM0IH, 416, ymm0, 8, 8)\ +X(XMM1IH, 417, ymm1, 8, 8)\ +X(XMM2IH, 418, ymm2, 8, 8)\ +X(XMM3IH, 419, ymm3, 8, 8)\ +X(XMM4IH, 420, ymm4, 8, 8)\ +X(XMM5IH, 421, ymm5, 8, 8)\ +X(XMM6IH, 422, ymm6, 8, 8)\ +X(XMM7IH, 423, ymm7, 8, 8)\ +X(XMM8IH, 424, ymm8, 8, 8)\ +X(XMM9IH, 425, ymm9, 8, 8)\ +X(XMM10IH, 426, ymm10, 8, 8)\ +X(XMM11IH, 427, ymm11, 8, 8)\ +X(XMM12IH, 428, ymm12, 8, 8)\ +X(XMM13IH, 429, ymm13, 8, 8)\ +X(XMM14IH, 430, ymm14, 8, 8)\ +X(XMM15IH, 431, ymm15, 8, 8)\ +X(YMM0I0, 432, ymm0, 0, 8)\ +X(YMM0I1, 433, ymm0, 8, 8)\ +X(YMM0I2, 434, ymm0, 16, 8)\ +X(YMM0I3, 435, ymm0, 24, 8)\ +X(YMM1I0, 436, ymm1, 0, 8)\ +X(YMM1I1, 437, ymm1, 8, 8)\ +X(YMM1I2, 438, ymm1, 16, 8)\ +X(YMM1I3, 439, ymm1, 24, 8)\ +X(YMM2I0, 440, ymm2, 0, 8)\ +X(YMM2I1, 441, ymm2, 8, 8)\ +X(YMM2I2, 442, ymm2, 16, 8)\ +X(YMM2I3, 443, ymm2, 24, 8)\ +X(YMM3I0, 444, ymm3, 0, 8)\ +X(YMM3I1, 445, ymm3, 8, 8)\ +X(YMM3I2, 446, ymm3, 16, 8)\ +X(YMM3I3, 447, ymm3, 24, 8)\ +X(YMM4I0, 448, ymm4, 0, 8)\ +X(YMM4I1, 449, ymm4, 8, 8)\ +X(YMM4I2, 450, ymm4, 16, 8)\ +X(YMM4I3, 451, ymm4, 24, 8)\ +X(YMM5I0, 452, ymm5, 0, 8)\ +X(YMM5I1, 453, ymm5, 8, 8)\ +X(YMM5I2, 454, ymm5, 16, 8)\ +X(YMM5I3, 455, ymm5, 24, 8)\ +X(YMM6I0, 456, ymm6, 0, 8)\ +X(YMM6I1, 457, ymm6, 8, 8)\ +X(YMM6I2, 458, ymm6, 16, 8)\ +X(YMM6I3, 459, ymm6, 24, 8)\ +X(YMM7I0, 460, ymm7, 0, 8)\ +X(YMM7I1, 461, ymm7, 8, 8)\ +X(YMM7I2, 462, ymm7, 16, 8)\ +X(YMM7I3, 463, ymm7, 24, 8)\ +X(YMM8I0, 464, ymm8, 0, 8)\ +X(YMM8I1, 465, ymm8, 8, 8)\ +X(YMM8I2, 466, ymm8, 16, 8)\ +X(YMM8I3, 467, ymm8, 24, 8)\ +X(YMM9I0, 468, ymm9, 0, 8)\ +X(YMM9I1, 469, ymm9, 8, 8)\ +X(YMM9I2, 470, ymm9, 16, 8)\ +X(YMM9I3, 471, ymm9, 24, 8)\ +X(YMM10I0, 472, ymm10, 0, 8)\ +X(YMM10I1, 473, ymm10, 8, 8)\ +X(YMM10I2, 474, ymm10, 16, 8)\ +X(YMM10I3, 475, ymm10, 24, 8)\ +X(YMM11I0, 476, ymm11, 0, 8)\ +X(YMM11I1, 477, ymm11, 8, 8)\ +X(YMM11I2, 478, ymm11, 16, 8)\ +X(YMM11I3, 479, ymm11, 24, 8)\ +X(YMM12I0, 480, ymm12, 0, 8)\ +X(YMM12I1, 481, ymm12, 8, 8)\ +X(YMM12I2, 482, ymm12, 16, 8)\ +X(YMM12I3, 483, ymm12, 24, 8)\ +X(YMM13I0, 484, ymm13, 0, 8)\ +X(YMM13I1, 485, ymm13, 8, 8)\ +X(YMM13I2, 486, ymm13, 16, 8)\ +X(YMM13I3, 487, ymm13, 24, 8)\ +X(YMM14I0, 488, ymm14, 0, 8)\ +X(YMM14I1, 489, ymm14, 8, 8)\ +X(YMM14I2, 490, ymm14, 16, 8)\ +X(YMM14I3, 491, ymm14, 24, 8)\ +X(YMM15I0, 492, ymm15, 0, 8)\ +X(YMM15I1, 493, ymm15, 8, 8)\ +X(YMM15I2, 494, ymm15, 16, 8)\ +X(YMM15I3, 495, ymm15, 24, 8)\ +X(YMM0F0, 496, ymm0, 0, 4)\ +X(YMM0F1, 497, ymm0, 4, 4)\ +X(YMM0F2, 498, ymm0, 8, 4)\ +X(YMM0F3, 499, ymm0, 12, 4)\ +X(YMM0F4, 500, ymm0, 16, 4)\ +X(YMM0F5, 501, ymm0, 20, 4)\ +X(YMM0F6, 502, ymm0, 24, 4)\ +X(YMM0F7, 503, ymm0, 28, 4)\ +X(YMM1F0, 504, ymm1, 0, 4)\ +X(YMM1F1, 505, ymm1, 4, 4)\ +X(YMM1F2, 506, ymm1, 8, 4)\ +X(YMM1F3, 507, ymm1, 12, 4)\ +X(YMM1F4, 508, ymm1, 16, 4)\ +X(YMM1F5, 509, ymm1, 20, 4)\ +X(YMM1F6, 510, ymm1, 24, 4)\ +X(YMM1F7, 511, ymm1, 28, 4)\ +X(YMM2F0, 512, ymm2, 0, 4)\ +X(YMM2F1, 513, ymm2, 4, 4)\ +X(YMM2F2, 514, ymm2, 8, 4)\ +X(YMM2F3, 515, ymm2, 12, 4)\ +X(YMM2F4, 516, ymm2, 16, 4)\ +X(YMM2F5, 517, ymm2, 20, 4)\ +X(YMM2F6, 518, ymm2, 24, 4)\ +X(YMM2F7, 519, ymm2, 28, 4)\ +X(YMM3F0, 520, ymm3, 0, 4)\ +X(YMM3F1, 521, ymm3, 4, 4)\ +X(YMM3F2, 522, ymm3, 8, 4)\ +X(YMM3F3, 523, ymm3, 12, 4)\ +X(YMM3F4, 524, ymm3, 16, 4)\ +X(YMM3F5, 525, ymm3, 20, 4)\ +X(YMM3F6, 526, ymm3, 24, 4)\ +X(YMM3F7, 527, ymm3, 28, 4)\ +X(YMM4F0, 528, ymm4, 0, 4)\ +X(YMM4F1, 529, ymm4, 4, 4)\ +X(YMM4F2, 530, ymm4, 8, 4)\ +X(YMM4F3, 531, ymm4, 12, 4)\ +X(YMM4F4, 532, ymm4, 16, 4)\ +X(YMM4F5, 533, ymm4, 20, 4)\ +X(YMM4F6, 534, ymm4, 24, 4)\ +X(YMM4F7, 535, ymm4, 28, 4)\ +X(YMM5F0, 536, ymm5, 0, 4)\ +X(YMM5F1, 537, ymm5, 4, 4)\ +X(YMM5F2, 538, ymm5, 8, 4)\ +X(YMM5F3, 539, ymm5, 12, 4)\ +X(YMM5F4, 540, ymm5, 16, 4)\ +X(YMM5F5, 541, ymm5, 20, 4)\ +X(YMM5F6, 542, ymm5, 24, 4)\ +X(YMM5F7, 543, ymm5, 28, 4)\ +X(YMM6F0, 544, ymm6, 0, 4)\ +X(YMM6F1, 545, ymm6, 4, 4)\ +X(YMM6F2, 546, ymm6, 8, 4)\ +X(YMM6F3, 547, ymm6, 12, 4)\ +X(YMM6F4, 548, ymm6, 16, 4)\ +X(YMM6F5, 549, ymm6, 20, 4)\ +X(YMM6F6, 550, ymm6, 24, 4)\ +X(YMM6F7, 551, ymm6, 28, 4)\ +X(YMM7F0, 552, ymm7, 0, 4)\ +X(YMM7F1, 553, ymm7, 4, 4)\ +X(YMM7F2, 554, ymm7, 8, 4)\ +X(YMM7F3, 555, ymm7, 12, 4)\ +X(YMM7F4, 556, ymm7, 16, 4)\ +X(YMM7F5, 557, ymm7, 20, 4)\ +X(YMM7F6, 558, ymm7, 24, 4)\ +X(YMM7F7, 559, ymm7, 28, 4)\ +X(YMM8F0, 560, ymm8, 0, 4)\ +X(YMM8F1, 561, ymm8, 4, 4)\ +X(YMM8F2, 562, ymm8, 8, 4)\ +X(YMM8F3, 563, ymm8, 12, 4)\ +X(YMM8F4, 564, ymm8, 16, 4)\ +X(YMM8F5, 565, ymm8, 20, 4)\ +X(YMM8F6, 566, ymm8, 24, 4)\ +X(YMM8F7, 567, ymm8, 28, 4)\ +X(YMM9F0, 568, ymm9, 0, 4)\ +X(YMM9F1, 569, ymm9, 4, 4)\ +X(YMM9F2, 570, ymm9, 8, 4)\ +X(YMM9F3, 571, ymm9, 12, 4)\ +X(YMM9F4, 572, ymm9, 16, 4)\ +X(YMM9F5, 573, ymm9, 20, 4)\ +X(YMM9F6, 574, ymm9, 24, 4)\ +X(YMM9F7, 575, ymm9, 28, 4)\ +X(YMM10F0, 576, ymm10, 0, 4)\ +X(YMM10F1, 577, ymm10, 4, 4)\ +X(YMM10F2, 578, ymm10, 8, 4)\ +X(YMM10F3, 579, ymm10, 12, 4)\ +X(YMM10F4, 580, ymm10, 16, 4)\ +X(YMM10F5, 581, ymm10, 20, 4)\ +X(YMM10F6, 582, ymm10, 24, 4)\ +X(YMM10F7, 583, ymm10, 28, 4)\ +X(YMM11F0, 584, ymm11, 0, 4)\ +X(YMM11F1, 585, ymm11, 4, 4)\ +X(YMM11F2, 586, ymm11, 8, 4)\ +X(YMM11F3, 587, ymm11, 12, 4)\ +X(YMM11F4, 588, ymm11, 16, 4)\ +X(YMM11F5, 589, ymm11, 20, 4)\ +X(YMM11F6, 590, ymm11, 24, 4)\ +X(YMM11F7, 591, ymm11, 28, 4)\ +X(YMM12F0, 592, ymm12, 0, 4)\ +X(YMM12F1, 593, ymm12, 4, 4)\ +X(YMM12F2, 594, ymm12, 8, 4)\ +X(YMM12F3, 595, ymm12, 12, 4)\ +X(YMM12F4, 596, ymm12, 16, 4)\ +X(YMM12F5, 597, ymm12, 20, 4)\ +X(YMM12F6, 598, ymm12, 24, 4)\ +X(YMM12F7, 599, ymm12, 28, 4)\ +X(YMM13F0, 600, ymm13, 0, 4)\ +X(YMM13F1, 601, ymm13, 4, 4)\ +X(YMM13F2, 602, ymm13, 8, 4)\ +X(YMM13F3, 603, ymm13, 12, 4)\ +X(YMM13F4, 604, ymm13, 16, 4)\ +X(YMM13F5, 605, ymm13, 20, 4)\ +X(YMM13F6, 606, ymm13, 24, 4)\ +X(YMM13F7, 607, ymm13, 28, 4)\ +X(YMM14F0, 608, ymm14, 0, 4)\ +X(YMM14F1, 609, ymm14, 4, 4)\ +X(YMM14F2, 610, ymm14, 8, 4)\ +X(YMM14F3, 611, ymm14, 12, 4)\ +X(YMM14F4, 612, ymm14, 16, 4)\ +X(YMM14F5, 613, ymm14, 20, 4)\ +X(YMM14F6, 614, ymm14, 24, 4)\ +X(YMM14F7, 615, ymm14, 28, 4)\ +X(YMM15F0, 616, ymm15, 0, 4)\ +X(YMM15F1, 617, ymm15, 4, 4)\ +X(YMM15F2, 618, ymm15, 8, 4)\ +X(YMM15F3, 619, ymm15, 12, 4)\ +X(YMM15F4, 620, ymm15, 16, 4)\ +X(YMM15F5, 621, ymm15, 20, 4)\ +X(YMM15F6, 622, ymm15, 24, 4)\ +X(YMM15F7, 623, ymm15, 28, 4)\ +X(YMM0D0, 624, ymm0, 0, 8)\ +X(YMM0D1, 625, ymm0, 8, 8)\ +X(YMM0D2, 626, ymm0, 16, 8)\ +X(YMM0D3, 627, ymm0, 24, 8)\ +X(YMM1D0, 628, ymm1, 0, 8)\ +X(YMM1D1, 629, ymm1, 8, 8)\ +X(YMM1D2, 630, ymm1, 16, 8)\ +X(YMM1D3, 631, ymm1, 24, 8)\ +X(YMM2D0, 632, ymm2, 0, 8)\ +X(YMM2D1, 633, ymm2, 8, 8)\ +X(YMM2D2, 634, ymm2, 16, 8)\ +X(YMM2D3, 635, ymm2, 24, 8)\ +X(YMM3D0, 636, ymm3, 0, 8)\ +X(YMM3D1, 637, ymm3, 8, 8)\ +X(YMM3D2, 638, ymm3, 16, 8)\ +X(YMM3D3, 639, ymm3, 24, 8)\ +X(YMM4D0, 640, ymm4, 0, 8)\ +X(YMM4D1, 641, ymm4, 8, 8)\ +X(YMM4D2, 642, ymm4, 16, 8)\ +X(YMM4D3, 643, ymm4, 24, 8)\ +X(YMM5D0, 644, ymm5, 0, 8)\ +X(YMM5D1, 645, ymm5, 8, 8)\ +X(YMM5D2, 646, ymm5, 16, 8)\ +X(YMM5D3, 647, ymm5, 24, 8)\ +X(YMM6D0, 648, ymm6, 0, 8)\ +X(YMM6D1, 649, ymm6, 8, 8)\ +X(YMM6D2, 650, ymm6, 16, 8)\ +X(YMM6D3, 651, ymm6, 24, 8)\ +X(YMM7D0, 652, ymm7, 0, 8)\ +X(YMM7D1, 653, ymm7, 8, 8)\ +X(YMM7D2, 654, ymm7, 16, 8)\ +X(YMM7D3, 655, ymm7, 24, 8)\ +X(YMM8D0, 656, ymm8, 0, 8)\ +X(YMM8D1, 657, ymm8, 8, 8)\ +X(YMM8D2, 658, ymm8, 16, 8)\ +X(YMM8D3, 659, ymm8, 24, 8)\ +X(YMM9D0, 660, ymm9, 0, 8)\ +X(YMM9D1, 661, ymm9, 8, 8)\ +X(YMM9D2, 662, ymm9, 16, 8)\ +X(YMM9D3, 663, ymm9, 24, 8)\ +X(YMM10D0, 664, ymm10, 0, 8)\ +X(YMM10D1, 665, ymm10, 8, 8)\ +X(YMM10D2, 666, ymm10, 16, 8)\ +X(YMM10D3, 667, ymm10, 24, 8)\ +X(YMM11D0, 668, ymm11, 0, 8)\ +X(YMM11D1, 669, ymm11, 8, 8)\ +X(YMM11D2, 670, ymm11, 16, 8)\ +X(YMM11D3, 671, ymm11, 24, 8)\ +X(YMM12D0, 672, ymm12, 0, 8)\ +X(YMM12D1, 673, ymm12, 8, 8)\ +X(YMM12D2, 674, ymm12, 16, 8)\ +X(YMM12D3, 675, ymm12, 24, 8)\ +X(YMM13D0, 676, ymm13, 0, 8)\ +X(YMM13D1, 677, ymm13, 8, 8)\ +X(YMM13D2, 678, ymm13, 16, 8)\ +X(YMM13D3, 679, ymm13, 24, 8)\ +X(YMM14D0, 680, ymm14, 0, 8)\ +X(YMM14D1, 681, ymm14, 8, 8)\ +X(YMM14D2, 682, ymm14, 16, 8)\ +X(YMM14D3, 683, ymm14, 24, 8)\ +X(YMM15D0, 684, ymm15, 0, 8)\ +X(YMM15D1, 685, ymm15, 8, 8)\ +X(YMM15D2, 686, ymm15, 16, 8)\ +X(YMM15D3, 687, ymm15, 24, 8) + +typedef U16 CV_Regx64; +typedef enum CV_Regx64Enum +{ +#define X(CVN,C,RDN,BP,BZ) CV_Regx64_##CVN = C, + CV_Reg_X64_XList(X) +#undef X +} +CV_Regx64Enum; + + +#define CV_SignatureXList(X) \ +X(C6, 0)\ +X(C7, 1)\ +X(C11, 2)\ +X(C13, 4)\ +X(RESERVED, 5) + +typedef U16 CV_Signature; +typedef enum CV_SignatureEnum +{ +#define X(N,c) CV_Signature_##N = c, + CV_SignatureXList(X) +#undef X +} +CV_SignatureEnum; + + +#define CV_LanguageXList(X) \ +X(C, 0x00)\ +X(CXX, 0x01)\ +X(FORTRAN, 0x02)\ +X(MASM, 0x03)\ +X(PASCAL, 0x04)\ +X(BASIC, 0x05)\ +X(COBOL, 0x06)\ +X(LINK, 0x07)\ +X(CVTRES, 0x08)\ +X(CVTPGD, 0x09)\ +X(CSHARP, 0x0A)\ +X(VB, 0x0B)\ +X(ILASM, 0x0C)\ +X(JAVA, 0x0D)\ +X(JSCRIPT, 0x0E)\ +X(MSIL, 0x0F)\ +X(HLSL, 0x10) + +typedef U16 CV_Language; +typedef enum CV_LanguageEnum +{ +#define X(N,c) CV_Language_##N = c, + CV_LanguageXList(X) +#undef X +} +CV_LanguageEnum; + +#pragma pack(push, 1) + +//////////////////////////////// +//~ rjf: CodeView Format "Sym" and "Leaf" Header Type + +typedef struct CV_RecHeader CV_RecHeader; +struct CV_RecHeader +{ + U16 size; + U16 kind; +}; + +//////////////////////////////// +//~ rjf: CodeView Format "Sym" Types +// +// (per-compilation-unit info, variables, procedures, etc.) +// + +typedef U8 CV_ProcFlags; +enum +{ + CV_ProcFlag_NoFPO = (1 << 0), + CV_ProcFlag_IntReturn = (1 << 1), + CV_ProcFlag_FarReturn = (1 << 2), + CV_ProcFlag_NeverReturn = (1 << 3), + CV_ProcFlag_NotReached = (1 << 4), + CV_ProcFlag_CustomCall = (1 << 5), + CV_ProcFlag_NoInline = (1 << 6), + CV_ProcFlag_OptDbgInfo = (1 << 7), +}; + +typedef U16 CV_LocalFlags; +enum +{ + CV_LocalFlag_Param = (1 << 0), + CV_LocalFlag_AddrTaken = (1 << 1), + CV_LocalFlag_Compgen = (1 << 2), + CV_LocalFlag_Aggregate = (1 << 3), + CV_LocalFlag_PartOfAggregate = (1 << 4), + CV_LocalFlag_Aliased = (1 << 5), + CV_LocalFlag_Alias = (1 << 6), + CV_LocalFlag_Retval = (1 << 7), + CV_LocalFlag_OptOut = (1 << 8), + CV_LocalFlag_Global = (1 << 9), + CV_LocalFlag_Static = (1 << 10), +}; + +typedef struct CV_LocalVarAttr CV_LocalVarAttr; +struct CV_LocalVarAttr +{ + U32 off; + U16 seg; + CV_LocalFlags flags; +}; + +//- (SymKind: COMPILE) + +typedef U32 CV_CompileFlags; +#define CV_CompileFlags_ExtractLanguage(f) (((f) )&0xFF) +#define CV_CompileFlags_ExtractFloatPrec(f) (((f)>> 8)&0x03) +#define CV_CompileFlags_ExtractFloatPkg(f) (((f)>>10)&0x03) +#define CV_CompileFlags_ExtractAmbientData(f) (((f)>>12)&0x07) +#define CV_CompileFlags_ExtractAmbientCode(f) (((f)>>15)&0x07) +#define CV_CompileFlags_ExtractMode(f) (((f)>>18)&0x01) + +typedef struct CV_SymCompile CV_SymCompile; +struct CV_SymCompile +{ + U8 machine; + CV_CompileFlags flags; + // U8[] ver_str (null terminated) +}; + +//- (SymKind: SSEARCH) + +typedef struct CV_SymStartSearch CV_SymStartSearch; +struct CV_SymStartSearch +{ + U32 start_symbol; + U16 segment; +}; + +//- (SymKind: END) (empty) + +//- (SymKind: RETURN) + +typedef U8 CV_GenericStyle; +typedef enum CV_GenericStyleEnum +{ + CV_GenericStyle_VOID, + CV_GenericStyle_REG, // "return data is in register" + CV_GenericStyle_ICAN, // "indirect caller allocated near" + CV_GenericStyle_ICAF, // "indirect caller allocated far" + CV_GenericStyle_IRAN, // "indirect returnee allocated near" + CV_GenericStyle_IRAF, // "indirect returnee allocated far" + CV_GenericStyle_UNUSED, +} +CV_GenericStyleEnum; + +typedef U16 CV_GenericFlags; +enum +{ + CV_GenericFlags_CSTYLE = (1 << 0), + CV_GenericFlags_RSCLEAN = (1 << 1), // "returnee stack cleanup" +}; + +typedef struct CV_SymReturn CV_SymReturn; +struct CV_SymReturn +{ + CV_GenericFlags flags; + CV_GenericStyle style; +}; + +//- (SymKind: SLINK32) + +typedef struct CV_SymSLink32 CV_SymSLink32; +struct CV_SymSLink32 +{ + U32 frame_size; + U32 offset; + U16 reg; +}; + +//- (SymKind: OEM) + +typedef struct CV_SymOEM CV_SymOEM; +struct CV_SymOEM +{ + COFF_Guid id; + CV_TypeId itype; + // padding align(4) +}; + +//- (SymKind: VFTABLE32) + +typedef struct CV_SymVPath32 CV_SymVPath32; +struct CV_SymVPath32 +{ + CV_TypeId root; + CV_TypeId path; + U32 off; + U16 seg; +}; + +//- (SymKind: FRAMEPROC) + +typedef U8 CV_EncodedFramePtrReg; +typedef enum CV_EncodedFramePtrRegEnum +{ + CV_EncodedFramePtrReg_None, + CV_EncodedFramePtrReg_StackPtr, + CV_EncodedFramePtrReg_FramePtr, + CV_EncodedFramePtrReg_BasePtr, +} +CV_EncodedFramePtrRegEnum; + +typedef U32 CV_FrameprocFlags; +enum +{ + CV_FrameprocFlag_UsesAlloca = (1 << 0), + CV_FrameprocFlag_UsesSetJmp = (1 << 1), + CV_FrameprocFlag_UsesLongJmp = (1 << 2), + CV_FrameprocFlag_UsesInlAsm = (1 << 3), + CV_FrameprocFlag_UsesEH = (1 << 4), + CV_FrameprocFlag_Inline = (1 << 5), + CV_FrameprocFlag_HasSEH = (1 << 6), + CV_FrameprocFlag_Naked = (1 << 7), + CV_FrameprocFlag_HasSecurityChecks = (1 << 8), + CV_FrameprocFlag_AsyncEH = (1 << 9), + CV_FrameprocFlag_GSNoStackOrdering = (1 << 10), + CV_FrameprocFlag_WasInlined = (1 << 11), + CV_FrameprocFlag_GSCheck = (1 << 12), + CV_FrameprocFlag_SafeBuffers = (1 << 13), + // LocalBasePointer: 14,15 + // ParamBasePointer: 16,17 + CV_FrameprocFlag_PogoOn = (1 << 18), + CV_FrameprocFlag_PogoCountsValid = (1 << 19), + CV_FrameprocFlag_OptSpeed = (1 << 20), + CV_FrameprocFlag_HasCFG = (1 << 21), + CV_FrameprocFlag_HasCFW = (1 << 22), +}; +#define CV_FrameprocFlags_ExtractLocalBasePointer(f) (((f) >> 14)&3) +#define CV_FrameprocFlags_ExtractParamBasePointer(f) (((f) >> 16)&3) + +typedef struct CV_SymFrameproc CV_SymFrameproc; +struct CV_SymFrameproc +{ + U32 frame_size; + U32 pad_size; + U32 pad_off; + U32 save_reg_size; + U32 eh_off; + CV_SectionIndex eh_sec; + CV_FrameprocFlags flags; +}; + +//- (SymKind: ANNOTATION) + +typedef struct CV_SymAnnotation CV_SymAnnotation; +struct CV_SymAnnotation +{ + U32 off; + U16 seg; + U16 count; + // U8[] annotation (null terminated) +}; + +//- (SymKind: OBJNAME) + +typedef struct CV_SymObjname CV_SymObjname; +struct CV_SymObjname +{ + U32 sig; + // U8[] name (null terminated) +}; + +//- (SymKind: THUNK32) + +typedef U8 CV_ThunkOrdinal; +typedef enum CV_ThunkOrdinalEnum +{ + CV_ThunkOrdinal_NoType, + CV_ThunkOrdinal_Adjustor, + CV_ThunkOrdinal_VCall, + CV_ThunkOrdinal_PCode, + CV_ThunkOrdinal_Load, + CV_ThunkOrdinal_TrampIncremental, + CV_ThunkOrdinal_TrampBranchIsland, +} +CV_ThunkOrdinalEnum; + +typedef struct CV_SymThunk32 CV_SymThunk32; +struct CV_SymThunk32 +{ + U32 parent; + U32 end; + U32 next; + U32 off; + U16 sec; + U16 len; + CV_ThunkOrdinal ord; + // U8[] name (null terminated) + // U8[] variant (null terminated) +}; + +//- (SymKind: BLOCK32) + +typedef struct CV_SymBlock32 CV_SymBlock32; +struct CV_SymBlock32 +{ + U32 parent; + U32 end; + U32 len; + U32 off; + U16 sec; + // U8[] name (null terminated) +}; + +//- (SymKind: LABEL32) + +typedef struct CV_SymLabel32 CV_SymLabel32; +struct CV_SymLabel32 +{ + U32 off; + U16 sec; + CV_ProcFlags flags; + // U8[] name (null terminated) +}; + +//- (SymKind: REGISTER) + +typedef struct CV_SymRegister CV_SymRegister; +struct CV_SymRegister +{ + CV_TypeId itype; + U16 reg; + // U8[] name (null terminated) +}; + +//- (SymKind: CONSTANT) + +typedef struct CV_SymConstant CV_SymConstant; +struct CV_SymConstant +{ + CV_TypeId itype; + // CV_Numeric num + // U8[] name (null terminated) +}; + +//- (SymKind: UDT) + +typedef struct CV_SymUDT CV_SymUDT; +struct CV_SymUDT +{ + CV_TypeId itype; + // U8[] name (null terminated) +}; + +//- (SymKind: MANYREG) + +typedef struct CV_SymManyreg CV_SymManyreg; +struct CV_SymManyreg +{ + CV_TypeId itype; + U8 count; + // U8[count] regs; +}; + +//- (SymKind: BPREL32) + +typedef struct CV_SymBPRel32 CV_SymBPRel32; +struct CV_SymBPRel32 +{ + U32 off; + CV_TypeId itype; + // U8[] name (null terminated) +}; + +//- (SymKind: LDATA32, GDATA32) + +typedef struct CV_SymData32 CV_SymData32; +struct CV_SymData32 +{ + CV_TypeId itype; + U32 off; + CV_SectionIndex sec; + // U8[] name (null terminated) +}; + +//- (SymKind: PUB32) + +typedef U32 CV_PubFlags; +enum +{ + CV_PubFlag_Code = (1 << 0), + CV_PubFlag_Function = (1 << 1), + CV_PubFlag_ManagedCode = (1 << 2), + CV_PubFlag_MSIL = (1 << 3), +}; + +typedef struct CV_SymPub32 CV_SymPub32; +struct CV_SymPub32 +{ + CV_PubFlags flags; + U32 off; + CV_SectionIndex sec; + // U8[] name (null terminated) +}; + +//- (SymKind: LPROC32, GPROC32) + +typedef struct CV_SymProc32 CV_SymProc32; +struct CV_SymProc32 +{ + U32 parent; + U32 end; + U32 next; + U32 len; + U32 dbg_start; + U32 dbg_end; + CV_TypeId itype; + U32 off; + U16 sec; + CV_ProcFlags flags; + // U8[] name (null terminated) +}; + +//- (SymKind: REGREL32) + +typedef struct CV_SymRegrel32 CV_SymRegrel32; +struct CV_SymRegrel32 +{ + U32 reg_off; + CV_TypeId itype; + CV_Reg reg; + // U8[] name (null terminated) +}; + +//- (SymKind: LTHREAD32, GTHREAD32) + +typedef struct CV_SymThread32 CV_SymThread32; +struct CV_SymThread32 +{ + CV_TypeId itype; + U32 tls_off; + U16 tls_seg; + // U8[] name (null terminated) +}; + +//- (SymKind: COMPILE2) + +typedef U32 CV_Compile2Flags; +#define CV_Compile2Flags_ExtractLanguage(f) (((f) )&0xFF) +#define CV_Compile2Flags_ExtractEditAndContinue(f) (((f)>> 8)&0x01) +#define CV_Compile2Flags_ExtractNoDbgInfo(f) (((f)>> 9)&0x01) +#define CV_Compile2Flags_ExtractLTCG(f) (((f)>>10)&0x01) +#define CV_Compile2Flags_ExtractNoDataAlign(f) (((f)>>11)&0x01) +#define CV_Compile2Flags_ExtractManagedPresent(f) (((f)>>12)&0x01) +#define CV_Compile2Flags_ExtractSecurityChecks(f) (((f)>>13)&0x01) +#define CV_Compile2Flags_ExtractHotPatch(f) (((f)>>14)&0x01) +#define CV_Compile2Flags_ExtractCVTCIL(f) (((f)>>15)&0x01) +#define CV_Compile2Flags_ExtractMSILModule(f) (((f)>>16)&0x01) + +typedef struct CV_SymCompile2 CV_SymCompile2; +struct CV_SymCompile2 +{ + CV_Compile2Flags flags; + CV_Arch machine; + U16 ver_fe_major; + U16 ver_fe_minor; + U16 ver_fe_build; + U16 ver_major; + U16 ver_minor; + U16 ver_build; + // U8[] ver_str (null terminated) +}; + +//- (SymKind: MANYREG2) + +typedef struct CV_SymManyreg2 CV_SymManyreg2; +struct CV_SymManyreg2 +{ + CV_TypeId itype; + U16 count; + // U16[count] regs; +}; + +//- (SymKind: LOCALSLOT) + +typedef struct CV_SymSlot CV_SymSlot; +struct CV_SymSlot +{ + U32 slot_index; + CV_TypeId itype; + // U8[] name (null terminated) +}; + +//- (SymKind: MANFRAMEREL, ATTR_FRAMEREL) + +typedef struct CV_SymAttrFrameRel CV_SymAttrFrameRel; +struct CV_SymAttrFrameRel +{ + U32 off; + CV_TypeId itype; + CV_LocalVarAttr attr; + // U8[] name (null terminated) +}; + +//- (SymKind: MANREGISTER, ATTR_REGISTER) + +typedef struct CV_SymAttrReg CV_SymAttrReg; +struct CV_SymAttrReg +{ + CV_TypeId itype; + CV_LocalVarAttr attr; + U16 reg; + // U8[] name (null terminated) +}; + +//- (SymKind: MANMANYREG, ATTR_MANYREG) + + +typedef struct CV_SymAttrManyReg CV_SymAttrManyReg; +struct CV_SymAttrManyReg +{ + CV_TypeId itype; + CV_LocalVarAttr attr; + U8 count; + // U8[count] regs + // U8[] name (null terminated) +}; + +//- (SymKind: MANREGREL, ATTR_REGREL) + +typedef struct CV_SymAttrRegRel CV_SymAttrRegRel; +struct CV_SymAttrRegRel +{ + U32 off; + CV_TypeId itype; + U16 reg; + CV_LocalVarAttr attr; + // U8[] name (null terminated) +}; + +//- (SymKind: UNAMESPACE) + +typedef struct CV_SymUNamespace CV_SymUNamespace; +struct CV_SymUNamespace +{ + // *** "dummy" is the first character of name - it should not be skipped! + // *** It is placed here so the C compiler will accept this struct. + // *** The actual fixed size part of this record has a size of zero. + + U8 dummy; + + // U8[] name (null terminated) +}; + +//- (SymKind: PROCREF, DATAREF, LPROCREF) + +typedef struct CV_SymRef2 CV_SymRef2; +struct CV_SymRef2 +{ + U32 suc_name; + U32 sym_off; + CV_ModIndex imod; + // U8[] name (null terminated) +}; + +//- (SymKind: TRAMPOLINE) + +typedef U16 CV_TrampolineKind; +typedef enum CV_TrampolineKindEnum +{ + CV_TrampolineKind_Incremental, + CV_TrampolineKind_BranchIsland, +} +CV_TrampolineKindEnum; + +typedef struct CV_SymTrampoline CV_SymTrampoline; +struct CV_SymTrampoline +{ + CV_TrampolineKind kind; + U16 thunk_size; + U32 thunk_sec_off; + U32 target_sec_off; + CV_SectionIndex thunk_sec; + CV_SectionIndex target_sec; +}; + +//- (SymKind: SEPCODE) + +typedef U32 CV_SepcodeFlags; +enum +{ + CV_SepcodeFlag_IsLexicalScope = (1 << 0), + CV_SepcodeFlag_ReturnsToParent = (1 << 1), +}; + +typedef struct CV_SymSepcode CV_SymSepcode; +struct CV_SymSepcode +{ + U32 parent; + U32 end; + U32 len; + CV_SepcodeFlags flags; + U32 sec_off; + U32 sec_parent_off; + U16 sec; + U16 sec_parent; +}; + +//- (SymKind: SECTION) + +typedef struct CV_SymSection CV_SymSection; +struct CV_SymSection +{ + U16 sec_index; + U8 align; + U8 pad; + U32 rva; + U32 size; + U32 characteristics; + // U8[] name (null terminated) +}; + +//- (SymKind: COFFGROUP) + +typedef struct CV_SymCoffGroup CV_SymCoffGroup; +struct CV_SymCoffGroup +{ + U32 size; + U32 characteristics; + U32 off; + U16 sec; + // U8[] name (null terminated) +}; + +//- (SymKind: EXPORT) + +typedef U16 CV_ExportFlags; +enum +{ + CV_ExportFlag_Constant = (1 << 0), + CV_ExportFlag_Data = (1 << 1), + CV_ExportFlag_Private = (1 << 2), + CV_ExportFlag_NoName = (1 << 3), + CV_ExportFlag_Ordinal = (1 << 4), + CV_ExportFlag_Forwarder = (1 << 5), +}; + +typedef struct CV_SymExport CV_SymExport; +struct CV_SymExport +{ + U16 ordinal; + CV_ExportFlags flags; + // U8[] name (null terminated) +}; + +//- (SymKind: CALLSITEINFO) + +typedef struct CV_SymCallSiteInfo CV_SymCallSiteInfo; +struct CV_SymCallSiteInfo +{ + U32 off; + U16 sec; + U16 pad; + CV_TypeId itype; +}; + +//- (SymKind: FRAMECOOKIE) + +typedef U8 CV_FrameCookieKind; +typedef enum CV_FrameCookieKindEnum +{ + CV_FrameCookieKind_Copy, + CV_FrameCookieKind_XorSP, + CV_FrameCookieKind_XorBP, + CV_FrameCookieKind_XorR13, +} +CV_FrameCookieKindEnum; + +typedef struct CV_SymFrameCookie CV_SymFrameCookie; +struct CV_SymFrameCookie +{ + U32 off; + CV_Reg reg; + CV_FrameCookieKind kind; + U8 flags; +}; + +//- (SymKind: DISCARDED) + +typedef U8 CV_DiscardedKind; +typedef enum CV_DiscardedKindEnum +{ + CV_DiscardedKind_Unknown, + CV_DiscardedKind_NotSelected, + CV_DiscardedKind_NotReferenced, +} +CV_DiscardedKindEnum; + +typedef struct CV_SymDiscarded CV_SymDiscarded; +struct CV_SymDiscarded +{ + CV_DiscardedKind kind; + U32 file_id; + U32 file_ln; + // U8[] data (rest of data) +}; + +//- (SymKind: COMPILE3) + +typedef U32 CV_Compile3Flags; +#define CV_Compile3Flags_ExtractLanguage(f) (((f) )&0xFF) +#define CV_Compile3Flags_ExtractEditAndContinue(f) (((f)>> 9)&0x01) +#define CV_Compile3Flags_ExtractNoDbgInfo(f) (((f)>>10)&0x01) +#define CV_Compile3Flags_ExtractLTCG(f) (((f)>>11)&0x01) +#define CV_Compile3Flags_ExtractNoDataAlign(f) (((f)>>12)&0x01) +#define CV_Compile3Flags_ExtractManagedPresent(f) (((f)>>13)&0x01) +#define CV_Compile3Flags_ExtractSecurityChecks(f) (((f)>>14)&0x01) +#define CV_Compile3Flags_ExtractHotPatch(f) (((f)>>15)&0x01) +#define CV_Compile3Flags_ExtractCVTCIL(f) (((f)>>16)&0x01) +#define CV_Compile3Flags_ExtractMSILModule(f) (((f)>>17)&0x01) +#define CV_Compile3Flags_ExtractSDL(f) (((f)>>18)&0x01) +#define CV_Compile3Flags_ExtractPGO(f) (((f)>>19)&0x01) +#define CV_Compile3Flags_ExtractEXP(f) (((f)>>20)&0x01) + +typedef struct CV_SymCompile3 CV_SymCompile3; +struct CV_SymCompile3 +{ + CV_Compile3Flags flags; + CV_Arch machine; + U16 ver_fe_major; + U16 ver_fe_minor; + U16 ver_fe_build; + U16 ver_feqfe; + U16 ver_major; + U16 ver_minor; + U16 ver_build; + U16 ver_qfe; + // U8[] ver_str (null terminated) +}; + +//- (SymKind: ENVBLOCK) + +typedef struct CV_SymEnvBlock CV_SymEnvBlock; +struct CV_SymEnvBlock +{ + U8 flags; + // U8[][] rgsz (sequence null terminated strings) +}; + +//- (SymKind: LOCAL) + +typedef struct CV_SymLocal CV_SymLocal; +struct CV_SymLocal +{ + CV_TypeId itype; + CV_LocalFlags flags; + // U8[] name (null terminated) +}; + +//- DEFRANGE + +typedef struct CV_LvarAddrRange CV_LvarAddrRange; +struct CV_LvarAddrRange +{ + U32 off; + U16 sec; + U16 len; +}; + +typedef struct CV_LvarAddrGap CV_LvarAddrGap; +struct CV_LvarAddrGap +{ + U16 off; + U16 len; +}; + +typedef U16 CV_RangeAttribs; +enum +{ + CV_RangeAttrib_Maybe = (1 << 0), +}; + +//- (SymKind: DEFRANGE_SUBFIELD) + +typedef struct CV_SymDefrangeSubfield CV_SymDefrangeSubfield; +struct CV_SymDefrangeSubfield +{ + U32 program; + U32 off_in_parent; + CV_LvarAddrRange range; + // CV_LvarAddrGap[] gaps (rest of data) +}; + +//- (SymKind: DEFRANGE_REGISTER) + +typedef struct CV_SymDefrangeRegister CV_SymDefrangeRegister; +struct CV_SymDefrangeRegister +{ + CV_Reg reg; + CV_RangeAttribs attribs; + CV_LvarAddrRange range; + // CV_LvarAddrGap[] gaps (rest of data) +}; + +//- (SymKind: DEFRANGE_FRAMEPOINTER_REL) + +typedef struct CV_SymDefrangeFramepointerRel CV_SymDefrangeFramepointerRel; +struct CV_SymDefrangeFramepointerRel +{ + S32 off; + CV_LvarAddrRange range; + // CV_LvarAddrGap[] gaps (rest of data) +}; + +//- (SymKind: DEFRANGE_SUBFIELD_REGISTER) + +typedef struct CV_SymDefrangeSubfieldRegister CV_SymDefrangeSubfieldRegister; +struct CV_SymDefrangeSubfieldRegister +{ + CV_Reg reg; + CV_RangeAttribs attribs; + U32 field_offset; + CV_LvarAddrRange range; + // CV_LvarAddrGap[] gaps (rest of data) +}; + +//- (SymKind: DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE) + +typedef struct CV_SymDefrangeFramepointerRelFullScope CV_SymDefrangeFramepointerRelFullScope; +struct CV_SymDefrangeFramepointerRelFullScope +{ + S32 off; +}; + +//- (SymKind: DEFRANGE_REGISTER_REL) + +typedef U16 CV_DefrangeRegisterRelFlags; +enum +{ + CV_DefrangeRegisterRelFlag_SpilledOutUDTMember = (1 << 0), +}; +#define CV_DefrangeRegisterRelFlag_ExtractOffsetParent(f) (((f)>>4)&0xFFF) + +typedef struct CV_SymDefrangeRegisterRel CV_SymDefrangeRegisterRel; +struct CV_SymDefrangeRegisterRel +{ + CV_Reg reg; + CV_DefrangeRegisterRelFlags flags; + S32 reg_off; + CV_LvarAddrRange range; + // CV_LvarAddGap[] gaps (rest of data) +}; + +//- (SymKind: BUILDINFO) + +typedef struct CV_SymBuildInfo CV_SymBuildInfo; +struct CV_SymBuildInfo +{ + CV_ItemId id; +}; + +//- (SymKind: INLINESITE) + +typedef U32 CV_InlineBinaryAnnotation; +typedef enum CV_InlineBinaryAnnotationEnum +{ + CV_InlineBinaryAnnotation_Null, + CV_InlineBinaryAnnotation_CodeOffset, + CV_InlineBinaryAnnotation_ChangeCodeOffsetBase, + CV_InlineBinaryAnnotation_ChangeCodeOffset, + CV_InlineBinaryAnnotation_ChangeCodeLength, + CV_InlineBinaryAnnotation_ChangeFile, + CV_InlineBinaryAnnotation_ChangeLineOffset, + CV_InlineBinaryAnnotation_ChangeLineEndDelta, + CV_InlineBinaryAnnotation_ChangeRangeKind, + CV_InlineBinaryAnnotation_ChangeColumnStart, + CV_InlineBinaryAnnotation_ChangeColumnEndDelta, + CV_InlineBinaryAnnotation_ChangeCodeOffsetAndLineOffset, + CV_InlineBinaryAnnotation_ChangeCodeLengthAndCodeOffset, + CV_InlineBinaryAnnotation_ChangeColumnEnd +} +CV_InlineBinaryAnnotationEnum; + +typedef U32 CV_InlineRangeKind; +typedef enum CV_InlineRangeKindEnum +{ + CV_InlineRangeKind_Expr, + CV_InlineRangeKind_Stmt +} +CV_InlineRangeKindEnum; + +typedef struct CV_SymInlineSite CV_SymInlineSite; +struct CV_SymInlineSite +{ + U32 parent; + U32 end; + CV_ItemId inlinee; + // U8 annotations[] (rest of data) +}; + +//- (SymKind: INLINESITE2) + +typedef struct CV_SymInlineSite2 CV_SymInlineSite2; +struct CV_SymInlineSite2 +{ + U32 parent_off; + U32 end_off; + CV_ItemId inlinee; + U32 invocations; + // U8 annotations[] (rest of data) +}; + +//- (SymKind: INLINESITE_END) (empty) + +//- (SymKind: FILESTATIC) + +typedef struct CV_SymFileStatic CV_SymFileStatic; +struct CV_SymFileStatic +{ + CV_TypeId itype; + U32 mod_offset; + CV_LocalFlags flags; + // U8[] name (null terminated) +}; + +//- (SymKind: ARMSWITCHTABLE) + +typedef U16 CV_ArmSwitchKind; +typedef enum CV_ArmSwitchKindEnum +{ + CV_ArmSwitchKind_INT1, + CV_ArmSwitchKind_UINT1, + CV_ArmSwitchKind_INT2, + CV_ArmSwitchKind_UINT2, + CV_ArmSwitchKind_INT4, + CV_ArmSwitchKind_UINT5, + CV_ArmSwitchKind_POINTER, + CV_ArmSwitchKind_UINT1SHL1, + CV_ArmSwitchKind_UINT2SHL1, + CV_ArmSwitchKind_INT1SSHL1, + CV_ArmSwitchKind_INT2SSHL1, +} +CV_ArmSwitchKindEnum; + +typedef struct CV_SymArmSwitchTable CV_SymArmSwitchTable; +struct CV_SymArmSwitchTable +{ + U32 off_base; + U16 sec_base; + CV_ArmSwitchKind kind; + U32 off_branch; + U32 off_table; + U16 sec_branch; + U16 sec_table; + U32 entry_count; +}; + +//- (SymKind: CALLEES, CALLERS) + +typedef struct CV_SymFunctionList CV_SymFunctionList; +struct CV_SymFunctionList +{ + U32 count; + // CV_TypeId[count] funcs + // U32[clamp(count, rest_of_data/4)] invocations +}; + +//- (SymKind: POGODATA) + +typedef struct CV_SymPogoInfo CV_SymPogoInfo; +struct CV_SymPogoInfo +{ + U32 invocations; + U64 dynamic_inst_count; + U32 static_inst_count; + U32 post_inline_static_inst_count; +}; + +//- (SymKind: HEAPALLOCSITE) + +typedef struct CV_SymHeapAllocSite CV_SymHeapAllocSite; +struct CV_SymHeapAllocSite +{ + U32 off; + U16 sec; + U16 call_inst_len; + CV_TypeId itype; +}; + +//- (SymKind: MOD_TYPEREF) + +typedef U32 CV_ModTypeRefFlags; +enum +{ + CV_ModTypeRefFlag_None = (1 << 0), + CV_ModTypeRefFlag_RefTMPCT = (1 << 1), + CV_ModTypeRefFlag_OwnTMPCT = (1 << 2), + CV_ModTypeRefFlag_OwnTMR = (1 << 3), + CV_ModTypeRefFlag_OwnTM = (1 << 4), + CV_ModTypeRefFlag_RefTM = (1 << 5), +}; + +typedef struct CV_SymModTypeRef CV_SymModTypeRef; +struct CV_SymModTypeRef +{ + CV_ModTypeRefFlags flags; + // contain stream number or module index depending on flags (undocumented) + U32 word0; + U32 word1; +}; + +//- (SymKind: REF_MINIPDB) + +typedef U16 CV_RefMiniPdbFlags; +enum +{ + CV_RefMiniPdbFlag_Local = (1 << 0), + CV_RefMiniPdbFlag_Data = (1 << 1), + CV_RefMiniPdbFlag_UDT = (1 << 2), + CV_RefMiniPdbFlag_Label = (1 << 3), + CV_RefMiniPdbFlag_Const = (1 << 4), +}; + +typedef struct CV_SymRefMiniPdb CV_SymRefMiniPdb; +struct CV_SymRefMiniPdb +{ + U32 data; + CV_ModIndex imod; + CV_RefMiniPdbFlags flags; + // U8[] name (null terminated) +}; + +//- (SymKind: FASTLINK) + +typedef U16 CV_FastLinkFlags; +enum +{ + CV_FastLinkFlag_IsGlobalData = (1 << 0), + CV_FastLinkFlag_IsData = (1 << 1), + CV_FastLinkFlag_IsUDT = (1 << 2), + // 3 ~ unknown/unused + CV_FastLinkFlag_IsConst = (1 << 4), + // 5 ~ unknown/unused + CV_FastLinkFlag_IsNamespace = (1 << 6), +}; + +typedef struct CV_SymFastLink CV_SymFastLink; +struct CV_SymFastLink +{ + CV_TypeId itype; + CV_FastLinkFlags flags; + // U8[] name (null terminated) +}; + +//- (SymKind: INLINEES) + +typedef struct CV_SymInlinees CV_SymInlinees; +struct CV_SymInlinees +{ + U32 count; + // U32[count] desc; +}; + +//////////////////////////////// +//~ rjf: CodeView Format "Leaf" Types +// +// (type info) +// + +#define CV_LeafIDKindXList(X) \ +X(FUNC_ID, 0x1601)\ +X(MFUNC_ID, 0x1602)\ +X(BUILDINFO, 0x1603)\ +X(SUBSTR_LIST, 0x1604)\ +X(STRING_ID, 0x1605)\ +X(UDT_SRC_LINE, 0x1606)\ +X(UDT_MOD_SRC_LINE, 0x1607) + +typedef U16 CV_LeafIDKind; +typedef enum CV_LeafIDKindEnum +{ +#define X(N,c) CV_LeafIDKind_##N = c, + CV_LeafIDKindXList(X) +#undef X +} +CV_LeafIDKindEnum; + +#define CV_TypeId_Variadic 0 + +#define CV_BasicPointerKindXList(X) \ +X(VALUE, 0x0)\ +X(16BIT, 0x1)\ +X(FAR_16BIT, 0x2)\ +X(HUGE_16BIT, 0x3)\ +X(32BIT, 0x4)\ +X(16_32BIT, 0x5)\ +X(64BIT, 0x6) + +typedef U8 CV_BasicPointerKind; +typedef enum +{ +#define X(N,c) CV_BasicPointerKind_##N = c, + CV_BasicPointerKindXList(X) +#undef X +} CV_BasicPointerKindEnum; + +#define CV_BasicTypeFromTypeId(x) ((x)&0xFF) +#define CV_BasicPointerKindFromTypeId(x) (((x)>>8)&0xFF) + +typedef U8 CV_HFAKind; +typedef enum CV_HFAKindEnum +{ + CV_HFAKind_None, + CV_HFAKind_Float, + CV_HFAKind_Double, + CV_HFAKind_Other +} +CV_HFAKindEnum; + +typedef U8 CV_MoComUDTKind; +typedef enum CV_MoComUDTKindEnum +{ + CV_MoComUDTKind_None, + CV_MoComUDTKind_Ref, + CV_MoComUDTKind_Value, + CV_MoComUDTKind_Interface +} +CV_MoComUDTKindEnum; + +typedef U16 CV_TypeProps; +enum +{ + CV_TypeProp_Packed = (1<<0), + CV_TypeProp_HasConstructorsDestructors = (1<<1), + CV_TypeProp_OverloadedOperators = (1<<2), + CV_TypeProp_IsNested = (1<<3), + CV_TypeProp_ContainsNested = (1<<4), + CV_TypeProp_OverloadedAssignment = (1<<5), + CV_TypeProp_OverloadedCasting = (1<<6), + CV_TypeProp_FwdRef = (1<<7), + CV_TypeProp_Scoped = (1<<8), + CV_TypeProp_HasUniqueName = (1<<9), + CV_TypeProp_Sealed = (1<<10), + // HFA: 11,12 + CV_TypeProp_Intrinsic = (1<<13), + // MOCOM: 14,15 +}; +#define CV_TypeProps_ExtractHFA(f) (((f)>>11)&0x3) +#define CV_TypeProps_ExtractMOCOM(f) (((f)>>14)&0x3) + +typedef U8 CV_PointerKind; +typedef enum CV_PointerKindEnum +{ + CV_PointerKind_Near, // 16 bit + CV_PointerKind_Far, // 16:16 bit + CV_PointerKind_Huge, // 16:16 bit + CV_PointerKind_BaseSeg, + CV_PointerKind_BaseVal, + CV_PointerKind_BaseSegVal, + CV_PointerKind_BaseAddr, + CV_PointerKind_BaseSegAddr, + CV_PointerKind_BaseType, + CV_PointerKind_BaseSelf, + CV_PointerKind_Near32, // 32 bit + CV_PointerKind_Far32, // 16:32 bit + CV_PointerKind_64, // 64 bit +} +CV_PointerKindEnum; + +typedef U8 CV_PointerMode; +typedef enum CV_PointerModeEnum +{ + CV_PointerMode_Ptr, + CV_PointerMode_LRef, + CV_PointerMode_PtrMem, + CV_PointerMode_PtrMethod, + CV_PointerMode_RRef, +} +CV_PointerModeEnum; + +typedef U16 CV_MemberPointerKind; +typedef enum CV_MemberPointerKindEnum +{ + CV_MemberPointerKind_Undef, + CV_MemberPointerKind_DataSingle, + CV_MemberPointerKind_DataMultiple, + CV_MemberPointerKind_DataVirtual, + CV_MemberPointerKind_DataGeneral, + CV_MemberPointerKind_FuncSingle, + CV_MemberPointerKind_FuncMultiple, + CV_MemberPointerKind_FuncVirtual, + CV_MemberPointerKind_FuncGeneral, +} +CV_MemberPointerKindEnum; + +typedef U32 CV_VirtualTableShape; +typedef enum CV_VirtualTableShapeEnum +{ + CV_VirtualTableShape_Near, // 16 bit ptr + CV_VirtualTableShape_Far, // 16:16 bit ptr + CV_VirtualTableShape_Thin, // ??? + CV_VirtualTableShape_Outer, // address point displacment to outermost class entry[-1] + CV_VirtualTableShape_Meta, // far pointer to metaclass descriptor entry[-2] + CV_VirtualTableShape_Near32, // 32 bit ptr + CV_VirtualTableShape_Far32, // ??? +} +CV_VirtualTableShapeEnum; + +typedef U8 CV_MethodProp; +enum +{ + CV_MethodProp_Vanilla, + CV_MethodProp_Virtual, + CV_MethodProp_Static, + CV_MethodProp_Friend, + CV_MethodProp_Intro, + CV_MethodProp_PureVirtual, + CV_MethodProp_PureIntro, +}; + +typedef U8 CV_MemberAccess; +typedef enum CV_MemberAccessEnum +{ + CV_MemberAccess_Null, + CV_MemberAccess_Private, + CV_MemberAccess_Protected, + CV_MemberAccess_Public +} +CV_MemberAccessEnum; + +typedef U16 CV_FieldAttribs; +enum +{ + // Access: 0,1 + // MethodProp: [2:4] + CV_FieldAttrib_Pseudo = (1<<5), + CV_FieldAttrib_NoInherit = (1<<6), + CV_FieldAttrib_NoConstruct = (1<<7), + CV_FieldAttrib_CompilerGenated = (1<<8), + CV_FieldAttrib_Sealed = (1<<9), +}; +#define CV_FieldAttribs_ExtractAccess(f) ((f)&0x3) +#define CV_FieldAttribs_ExtractMethodProp(f) (((f)>>2)&0x7) + +typedef U16 CV_LabelKind; +typedef enum CV_LabelKindEnum +{ + CV_LabelKind_Near = 0, + CV_LabelKind_Far = 4, +} +CV_LabelKindEnum; + +typedef U8 CV_FunctionAttribs; +enum +{ + CV_FunctionAttrib_CxxReturnUDT = (1<<0), + CV_FunctionAttrib_Constructor = (1<<1), + CV_FunctionAttrib_ConstructorVBase = (1<<2), +}; + +typedef U8 CV_CallKind; +typedef enum CV_CallKindEnum +{ + CV_CallKind_NearC, + CV_CallKind_FarC, + CV_CallKind_NearPascal, + CV_CallKind_FarPascal, + CV_CallKind_NearFast, + CV_CallKind_FarFast, + CV_CallKind_UNUSED, + CV_CallKind_NearStd, + CV_CallKind_FarStd, + CV_CallKind_NearSys, + CV_CallKind_FarSys, + CV_CallKind_This, + CV_CallKind_Mips, + CV_CallKind_Generic, + CV_CallKind_Alpha, + CV_CallKind_PPC, + CV_CallKind_HitachiSuperH, + CV_CallKind_Arm, + CV_CallKind_AM33, + CV_CallKind_TriCore, + CV_CallKind_HitachiSuperH5, + CV_CallKind_M32R, + CV_CallKind_Clr, + CV_CallKind_Inline, + CV_CallKind_NearVector, +} +CV_CallKindEnum; + +//- (LeafKind: PRECOMP) + +typedef struct CV_LeafPreComp CV_LeafPreComp; +struct CV_LeafPreComp +{ + U32 start_index; + U32 count; + U32 signature; + // U8[] name (null terminated) +}; + +//- (LeafKind: TYPESERVER) + +typedef struct CV_LeafTypeServer CV_LeafTypeServer; +struct CV_LeafTypeServer +{ + U32 sig; + U32 age; + // U8[] name (null terminated) +}; + +//- (LeafKind: TYPESERVER2) + +typedef struct CV_LeafTypeServer2 CV_LeafTypeServer2; +struct CV_LeafTypeServer2 +{ + COFF_Guid sig70; + U32 age; + // U8[] name (null terminated) +}; + +//- (LeafKind: SKIP) + +typedef struct CV_LeafSkip CV_LeafSkip; +struct CV_LeafSkip +{ + CV_TypeId itype; +}; + +//- (LeafKind: VTSHAPE) + +typedef struct CV_LeafVTShape CV_LeafVTShape; +struct CV_LeafVTShape +{ + U16 count; + // U4[count] shapes (CV_VirtualTableShape) +}; + +//- (LeafKind: LABEL) + +typedef struct CV_LeafLabel CV_LeafLabel; +struct CV_LeafLabel +{ + CV_LabelKind kind; +}; + +//- (LeafKind: MODIFIER) + +typedef U16 CV_ModifierFlags; +enum +{ + CV_ModifierFlag_Const = (1 << 0), + CV_ModifierFlag_Volatile = (1 << 1), + CV_ModifierFlag_Unaligned = (1 << 2), +}; + +typedef struct CV_LeafModifier CV_LeafModifier; +struct CV_LeafModifier +{ + CV_TypeId itype; + CV_ModifierFlags flags; +}; + +//- (LeafKind: POINTER) + +typedef U32 CV_PointerAttribs; +enum +{ + // Kind: [0:4] + // Mode: [5:7] + CV_PointerAttrib_IsFlat = (1 << 8), + CV_PointerAttrib_Volatile = (1 << 9), + CV_PointerAttrib_Const = (1 << 10), + CV_PointerAttrib_Unaligned = (1 << 11), + CV_PointerAttrib_Restricted = (1 << 12), + // Size: [13,18] + CV_PointerAttrib_MOCOM = (1 << 19), + CV_PointerAttrib_LRef = (1 << 21), + CV_PointerAttrib_RRef = (1 << 22) +}; + +#define CV_PointerAttribs_ExtractKind(a) ((a)&0x1F) +#define CV_PointerAttribs_ExtractMode(a) (((a)>>5)&0x7) +#define CV_PointerAttribs_ExtractSize(a) (((a)>>13)&0x3F) + +typedef struct CV_LeafPointer CV_LeafPointer; +struct CV_LeafPointer +{ + CV_TypeId itype; + CV_PointerAttribs attribs; +}; + +//- (LeafKind: PROCEDURE) + +typedef struct CV_LeafProcedure CV_LeafProcedure; +struct CV_LeafProcedure +{ + CV_TypeId ret_itype; + CV_CallKind call_kind; + CV_FunctionAttribs attribs; + U16 arg_count; + CV_TypeId arg_itype; +}; + +//- (LeafKind: MFUNCTION) + +typedef struct CV_LeafMFunction CV_LeafMFunction; +struct CV_LeafMFunction +{ + CV_TypeId ret_itype; + CV_TypeId class_itype; + CV_TypeId this_itype; + CV_CallKind call_kind; + CV_FunctionAttribs attribs; + U16 arg_count; + CV_TypeId arg_itype; + S32 this_adjust; +}; + +//- (LeafKind: ARGLIST) + +typedef struct CV_LeafArgList CV_LeafArgList; +struct CV_LeafArgList +{ + U32 count; + // CV_TypeId[count] itypes; +}; + +//- (LeafKind: BITFIELD) + +typedef struct CV_LeafBitField CV_LeafBitField; +struct CV_LeafBitField +{ + CV_TypeId itype; + U8 len; + U8 pos; +}; + +//- (LeafKind: METHODLIST) + +// ("jagged" array of these vvvvvvvv) +typedef struct CV_LeafMethodListMember CV_LeafMethodListMember; +struct CV_LeafMethodListMember +{ + CV_FieldAttribs attribs; + U16 pad; + CV_TypeId itype; + // U32 vbaseoff (when Intro or PureIntro) +}; + +//- (LeafKind: INDEX) + +typedef struct CV_LeafIndex CV_LeafIndex; +struct CV_LeafIndex +{ + U16 pad; + CV_TypeId itype; +}; + +//- (LeafKind: ARRAY) + +typedef struct CV_LeafArray CV_LeafArray; +struct CV_LeafArray +{ + CV_TypeId entry_itype; + CV_TypeId index_itype; + // CV_Numeric count +}; + +//- (LeafKind: CLASS, STRUCTURE, INTERFACE) + +typedef struct CV_LeafStruct CV_LeafStruct; +struct CV_LeafStruct +{ + U16 count; + CV_TypeProps props; + CV_TypeId field_itype; + CV_TypeId derived_itype; + CV_TypeId vshape_itype; + // CV_Numeric size + // U8[] name (null terminated) + // U8[] unique_name (null terminated) +}; + +//- (LeafKind: UNION) + +typedef struct CV_LeafUnion CV_LeafUnion; +struct CV_LeafUnion +{ + U16 count; + CV_TypeProps props; + CV_TypeId field_itype; + // CV_Numeric size + // U8[] name (null terminated) + // U8[] unique_name (null terminated) +}; + +//- (LeafKind: ENUM) + +typedef struct CV_LeafEnum CV_LeafEnum; +struct CV_LeafEnum +{ + U16 count; + CV_TypeProps props; + CV_TypeId base_itype; + CV_TypeId field_itype; + // U8[] name (null terminated) + // U8[] unique_name (null terminated) +}; + +//- (LeafKind: ALIAS) + +typedef struct CV_LeafAlias CV_LeafAlias; +struct CV_LeafAlias +{ + CV_TypeId itype; + // U8[] name (null terminated) +}; + +//- (LeafKind: MEMBER) + +typedef struct CV_LeafMember CV_LeafMember; +struct CV_LeafMember +{ + CV_FieldAttribs attribs; + CV_TypeId itype; + // CV_Numeric offset + // U8[] name (null terminated) +}; + +//- (LeafKind: STMEMBER) + +typedef struct CV_LeafStMember CV_LeafStMember; +struct CV_LeafStMember +{ + CV_FieldAttribs attribs; + CV_TypeId itype; + // U8[] name (null terminated) +}; + +//- (LeafKind: METHOD) + +typedef struct CV_LeafMethod CV_LeafMethod; +struct CV_LeafMethod +{ + U16 count; + CV_TypeId list_itype; + // U8[] name (null terminated) +}; + +//- (LeafKind: ONEMETHOD) + +typedef struct CV_LeafOneMethod CV_LeafOneMethod; +struct CV_LeafOneMethod +{ + CV_FieldAttribs attribs; + CV_TypeId itype; + // U32 vbaseoff (when Intro or PureIntro) + // U8[] name (null terminated) +}; + +//- (LeafKind: ENUMERATE) + +typedef struct CV_LeafEnumerate CV_LeafEnumerate; +struct CV_LeafEnumerate +{ + CV_FieldAttribs attribs; + // CV_Numeric val + // U8[] name (null terminated) +}; + +//- (LeafKind: NESTTYPE) + +typedef struct CV_LeafNestType CV_LeafNestType; +struct CV_LeafNestType +{ + U16 pad; + CV_TypeId itype; + // U8[] name (null terminated) +}; + +//- (LeafKind: NESTTYPEEX) + +typedef struct CV_LeafNestTypeEx CV_LeafNestTypeEx; +struct CV_LeafNestTypeEx +{ + CV_FieldAttribs attribs; + CV_TypeId itype; + // U8[] name (null terminated) +}; + +//- (LeafKind: BCLASS) + +typedef struct CV_LeafBClass CV_LeafBClass; +struct CV_LeafBClass +{ + CV_FieldAttribs attribs; + CV_TypeId itype; + // CV_Numeric offset +}; + +//- (LeafKind: VBCLASS, IVBCLASS) + +typedef struct CV_LeafVBClass CV_LeafVBClass; +struct CV_LeafVBClass +{ + CV_FieldAttribs attribs; + CV_TypeId itype; + CV_TypeId vbptr_itype; + // CV_Numeric vbptr_off + // CV_Numeric vtable_off +}; + +//- (LeafKind: VFUNCTAB) + +typedef struct CV_LeafVFuncTab CV_LeafVFuncTab; +struct CV_LeafVFuncTab +{ + U16 pad; + CV_TypeId itype; +}; + +//- (LeafKind: VFUNCOFF) + +typedef struct CV_LeafVFuncOff CV_LeafVFuncOff; +struct CV_LeafVFuncOff +{ + U16 pad; + CV_TypeId itype; + U32 off; +}; + +//- (LeafKind: VFTABLE) + +typedef struct CV_LeafVFTable CV_LeafVFTable; +struct CV_LeafVFTable +{ + CV_TypeId owner_itype; + CV_TypeId base_table_itype; + U32 offset_in_object_layout; + U32 names_len; + // U8[] names (multiple null terminated strings) +}; + +//- (LeafKind: VFTPATH) + +typedef struct CV_LeafVFPath CV_LeafVFPath; +struct CV_LeafVFPath +{ + U32 count; + // CV_TypeId[count] base; +}; + +//- (LeafKind: CLASS2, STRUCT2) + +typedef struct CV_LeafStruct2 CV_LeafStruct2; +struct CV_LeafStruct2 +{ + // NOTE: still reverse engineering this - if you find docs please help! + CV_TypeProps props; + U16 unknown1; + CV_TypeId field_itype; + CV_TypeId derived_itype; + CV_TypeId vshape_itype; + U16 unknown2; + // CV_Numeric size + // U8[] name (null terminated) + // U8[] unique_name (null terminated) +}; + +//- (LeafIDKind: FUNC_ID) + +typedef struct CV_LeafFuncId CV_LeafFuncId; +struct CV_LeafFuncId +{ + CV_ItemId scope_string_id; + CV_TypeId itype; + // U8[] name (null terminated) +}; + +//- (LeafIDKind: MFUNC_ID) + +typedef struct CV_LeafMFuncId CV_LeafMFuncId; +struct CV_LeafMFuncId +{ + CV_TypeId owner_itype; + CV_TypeId itype; + // U8[] name (null terminated) +}; + +//- (LeafIDKind: STRING_ID) + +typedef struct CV_LeafStringId CV_LeafStringId; +struct CV_LeafStringId +{ + CV_ItemId substr_list_id; + // U8[] string (null terminated) +}; + +//- (LeafIDKind: BUILDINFO) + +typedef enum CV_BuildInfoIndexEnum +{ + CV_BuildInfoIndex_BuildDirectory = 0, + CV_BuildInfoIndex_CompilerExecutable = 1, + CV_BuildInfoIndex_TargetSourceFile = 2, + CV_BuildInfoIndex_CombinedPdb = 3, + CV_BuildInfoIndex_CompileArguments = 4, +} +CV_BuildInfoIndexEnum; + +typedef struct CV_LeafBuildInfo CV_LeafBuildInfo; +struct CV_LeafBuildInfo +{ + U16 count; + // CV_ItemId[count] items +}; + +//- (LeafIDKind: SUBSTR_LIST) + +typedef struct CV_LeafSubstrList CV_LeafSubstrList; +struct CV_LeafSubstrList +{ + U32 count; + // CV_ItemId[count] items +}; + +//- (LeafIDKind: UDT_SRC_LINE) + +typedef struct CV_LeafUDTSrcLine CV_LeafUDTSrcLine; +struct CV_LeafUDTSrcLine +{ + CV_TypeId udt_itype; + CV_ItemId src_string_id; + U32 line; +}; + +//- (LeafIDKind: UDT_MOD_SRC_LINE) + +typedef struct CV_LeafUDTModSrcLine CV_LeafUDTModSrcLine; +struct CV_LeafUDTModSrcLine +{ + CV_TypeId udt_itype; + CV_ItemId src_string_id; + U32 line; + CV_ModIndex imod; +}; + +//////////////////////////////// +//~ CodeView Format C13 Line Info Types + +#define CV_C13SubSectionKind_IgnoreFlag 0x80000000 + +#define CV_C13SubSectionKindXList(X)\ +X(Symbols, 0xF1)\ +X(Lines, 0xF2)\ +X(StringTable, 0xF3)\ +X(FileChksms, 0xF4)\ +X(FrameData, 0xF5)\ +X(InlineeLines, 0xF6)\ +X(CrossScopeImports, 0xF7)\ +X(CrossScopeExports, 0xF8)\ +X(IlLines, 0xF9)\ +X(FuncMDTokenMap, 0xFA)\ +X(TypeMDTokenMap, 0xFB)\ +X(MergedAssemblyInput, 0xFC)\ +X(CoffSymbolRVA, 0xFD)\ +X(XfgHashType, 0xFF)\ +X(XfgHashVirtual, 0x100) + +typedef U32 CV_C13SubSectionKind; +typedef enum CV_C13SubSectionKindEnum +{ +#define X(N,c) CV_C13SubSectionKind_##N = c, + CV_C13SubSectionKindXList(X) +#undef X +} +CV_C13SubSectionKindEnum; + +typedef struct CV_C13SubSectionHeader CV_C13SubSectionHeader; +struct CV_C13SubSectionHeader +{ + CV_C13SubSectionKind kind; + U32 size; +}; + +//- FileChksms sub-section + +typedef U8 CV_C13ChecksumKind; +typedef enum CV_C13ChecksumKindEnum +{ + CV_C13ChecksumKind_Null, + CV_C13ChecksumKind_MD5, + CV_C13ChecksumKind_SHA1, + CV_C13ChecksumKind_SHA256, +} +CV_C13ChecksumKindEnum; + +typedef struct CV_C13Checksum CV_C13Checksum; +struct CV_C13Checksum +{ + U32 name_off; + U8 len; + CV_C13ChecksumKind kind; +}; + +//- Lines sub-section + +typedef U16 CV_C13SubSecLinesFlags; +enum +{ + CV_C13SubSecLinesFlag_HasColumns = (1 << 0) +}; + +typedef struct CV_C13SubSecLinesHeader CV_C13SubSecLinesHeader; +struct CV_C13SubSecLinesHeader +{ + U32 sec_off; + CV_SectionIndex sec; + CV_C13SubSecLinesFlags flags; + U32 len; +}; + +typedef struct CV_C13File CV_C13File; +struct CV_C13File +{ + U32 file_off; + U32 num_lines; + U32 block_size; + // CV_C13Line[num_lines] lines; + // CV_C13Column[num_lines] columns; (if HasColumns) +}; + +typedef U32 CV_C13LineFlags; +#define CV_C13LineFlags_ExtractLineNumber(f) ((f)&0xFFFFFF) +#define CV_C13LineFlags_ExtractDeltaToEnd(f) (((f)>>24)&0x7F) +#define CV_C13LineFlags_ExtractStatement(f) (((f)>>31)&0x1) + +typedef struct CV_C13Line CV_C13Line; +struct CV_C13Line +{ + U32 off; + CV_C13LineFlags flags; +}; + +typedef struct CV_C13Column CV_C13Column; +struct CV_C13Column +{ + U16 start; + U16 end; +}; + +//- FrameData sub-section + +typedef U32 CV_C13FrameDataFlags; +enum +{ + CV_C13FrameDataFlag_HasStructuredExceptionHandling = (1 << 0), + CV_C13FrameDataFlag_HasExceptionHandling = (1 << 1), + CV_C13FrameDataFlag_HasIsFuncStart = (1 << 2), +}; + +typedef struct CV_C13FrameData CV_C13FrameData; +struct CV_C13FrameData +{ + U32 start_voff; + U32 code_size; + U32 local_size; + U32 params_size; + U32 max_stack_size; + U32 frame_func; + U16 prolog_size; + U16 saved_reg_size; + CV_C13FrameDataFlags flags; +}; + +//- InlineLines sub-section + +typedef U32 CV_C13InlineeLinesSig; +enum +{ + CV_C13InlineeLinesSig_NORMAL, + CV_C13InlineeLinesSig_EXTRA_FILES, +}; + +typedef struct CV_C13InlineeSourceLineHeader CV_C13InlineeSourceLineHeader; +struct CV_C13InlineeSourceLineHeader +{ + CV_ItemId inlinee; // LF_FUNC_ID or LF_MFUNC_ID + U32 file_off; // offset into FileChksms sub-section + U32 first_source_ln; // base source line number for binary annotations + // if sig set to CV_C13InlineeLinesSig_EXTRA_FILES + // U32 extra_file_count; + // U32 files[]; +}; + +#pragma pack(pop) + +//////////////////////////////// +//~ CodeView Common Parser Types + +// CV_Numeric layout +// x: U16 +// buf: U8[] +// case (x < 0x8000): kind=U16 val=x +// case (x >= 0x8000): kind=x val=buf + +typedef struct CV_NumericParsed CV_NumericParsed; +struct CV_NumericParsed +{ + CV_NumericKind kind; + U8 *val; + U64 encoded_size; +}; + +typedef struct CV_RecRange CV_RecRange; +struct CV_RecRange +{ + U32 off; + CV_RecHeader hdr; +}; + +#define CV_REC_RANGE_CHUNK_SIZE 511 + +typedef struct CV_RecRangeChunk CV_RecRangeChunk; +struct CV_RecRangeChunk +{ + struct CV_RecRangeChunk *next; + CV_RecRange ranges[CV_REC_RANGE_CHUNK_SIZE]; +}; + +typedef struct CV_RecRangeStream CV_RecRangeStream; +struct CV_RecRangeStream +{ + CV_RecRangeChunk *first_chunk; + CV_RecRangeChunk *last_chunk; + U64 total_count; +}; + +typedef struct CV_RecRangeArray CV_RecRangeArray; +struct CV_RecRangeArray +{ + CV_RecRange *ranges; + U64 count; +}; + +//////////////////////////////// +//~ CodeView Sym Parser Types + +typedef struct CV_SymTopLevelInfo CV_SymTopLevelInfo; +struct CV_SymTopLevelInfo +{ + CV_Arch arch; + CV_Language language; + String8 compiler_name; +}; + +typedef struct CV_SymParsed CV_SymParsed; +struct CV_SymParsed +{ + // source information + String8 data; + U64 sym_align; + + // sym index derived from source + CV_RecRangeArray sym_ranges; + + // top-level info derived from the syms + CV_SymTopLevelInfo info; +}; + +//////////////////////////////// +//~ CodeView Leaf Parser Types + +typedef struct CV_LeafParsed CV_LeafParsed; +struct CV_LeafParsed +{ + // source information + String8 data; + CV_TypeId itype_first; + CV_TypeId itype_opl; + + // leaf index derived from source + CV_RecRangeArray leaf_ranges; +}; + +//////////////////////////////// +//~ CodeView C13 Info Parser Types + +typedef struct CV_C13LinesParsed CV_C13LinesParsed; +struct CV_C13LinesParsed +{ + // raw info + U32 sec_idx; + U32 file_off; + U64 secrel_base_off; + + // parsed info + String8 file_name; + U64 *voffs; // [line_count + 1] + U32 *line_nums; // [line_count] + U16 *col_nums; // [2*line_count] + U32 line_count; +}; + +typedef struct CV_C13LinesParsedNode CV_C13LinesParsedNode; +struct CV_C13LinesParsedNode +{ + CV_C13LinesParsedNode *next; + CV_C13LinesParsed v; +}; + +typedef struct CV_C13InlineeLinesParsed CV_C13InlineeLinesParsed; +struct CV_C13InlineeLinesParsed +{ + CV_ItemId inlinee; + String8 file_name; + U32 first_source_ln; + U32 extra_file_count; + U32 *extra_files; +}; + +typedef struct CV_C13InlineeLinesParsedNode CV_C13InlineeLinesParsedNode; +struct CV_C13InlineeLinesParsedNode +{ + CV_C13InlineeLinesParsedNode *next; + CV_C13InlineeLinesParsedNode *hash_next; + CV_C13InlineeLinesParsed v; +}; + +typedef struct CV_C13SubSectionNode CV_C13SubSectionNode; +struct CV_C13SubSectionNode +{ + struct CV_C13SubSectionNode *next; + CV_C13SubSectionKind kind; + U32 off; + U32 size; + CV_C13LinesParsedNode *lines_first; + CV_C13LinesParsedNode *lines_last; + CV_C13InlineeLinesParsedNode *inlinee_lines_first; + CV_C13InlineeLinesParsedNode *inlinee_lines_last; +}; + +typedef struct CV_C13Parsed CV_C13Parsed; +struct CV_C13Parsed +{ + // rjf: source data + String8 data; + + // rjf: full sub-section list + CV_C13SubSectionNode *first_sub_section; + CV_C13SubSectionNode *last_sub_section; + U64 sub_section_count; + + // rjf: fastpath to file checksums section + CV_C13SubSectionNode *file_chksms_sub_section; + + // rjf: fastpath to map inlinee CV_ItemId -> CV_InlineeLinesParsed quickly + CV_C13InlineeLinesParsedNode **inlinee_lines_parsed_slots; + U64 inlinee_lines_parsed_slots_count; +}; + +//////////////////////////////// +//~ CodeView Compound Types + +typedef struct CV_TypeIdArray CV_TypeIdArray; +struct CV_TypeIdArray +{ + CV_TypeId *itypes; + U64 count; +}; + +//////////////////////////////// +//~ CodeView Common Decoding Helper Functions + +internal U64 cv_hash_from_string(String8 string); +internal U64 cv_hash_from_item_id(CV_ItemId item_id); + +internal CV_NumericParsed cv_numeric_from_data_range(U8 *first, U8 *opl); + +internal B32 cv_numeric_fits_in_u64(CV_NumericParsed *num); +internal B32 cv_numeric_fits_in_s64(CV_NumericParsed *num); +internal B32 cv_numeric_fits_in_f64(CV_NumericParsed *num); + +internal U64 cv_u64_from_numeric(CV_NumericParsed *num); +internal S64 cv_s64_from_numeric(CV_NumericParsed *num); +internal F64 cv_f64_from_numeric(CV_NumericParsed *num); + +internal U64 cv_decode_inline_annot_u32(String8 data, U64 offset, U32 *out_value); +internal U64 cv_decode_inline_annot_s32(String8 data, U64 offset, S32 *out_value); + +internal S32 cv_inline_annot_signed_from_unsigned_operand(U32 value); + + +//////////////////////////////// +//~ CodeView Parsing Functions + +//- rjf: record range stream parsing +internal CV_RecRangeStream *cv_rec_range_stream_from_data(Arena *arena, String8 data, U64 align); +internal CV_RecRangeArray cv_rec_range_array_from_stream(Arena *arena, CV_RecRangeStream *stream); + +//- rjf: sym stream parsing +internal CV_SymParsed *cv_sym_from_data(Arena *arena, String8 sym_data, U64 sym_align); + +//- rjf: leaf stream parsing +internal CV_LeafParsed *cv_leaf_from_data(Arena *arena, String8 leaf_data, CV_TypeId first); + +//////////////////////////////// +//~ CodeView C13 Parser Functions + +typedef struct PDB_Strtbl PDB_Strtbl; +typedef struct PDB_CoffSectionArray PDB_CoffSectionArray; +internal CV_C13Parsed *cv_c13_parsed_from_data(Arena *arena, String8 c13_data, struct PDB_Strtbl *strtbl, struct PDB_CoffSectionArray *sections); + +#endif // CODEVIEW_H diff --git a/src/coff/coff.c b/src/coff/coff.c index 8220cfab..64f8dbcd 100644 --- a/src/coff/coff.c +++ b/src/coff/coff.c @@ -1,1119 +1,1119 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -internal B32 -coff_is_big_obj(String8 data) -{ - B32 is_big_obj = 0; - if (data.size >= sizeof(COFF_HeaderBigObj)) { - COFF_HeaderBigObj *big_header = (COFF_HeaderBigObj*)(data.str); - is_big_obj = big_header->sig1 == COFF_MachineType_UNKNOWN && - big_header->sig2 == max_U16 && - big_header->version >= COFF_MIN_BIG_OBJ_VERSION && - MemoryCompare(big_header->magic, coff_big_obj_magic, sizeof(big_header->magic)) == 0; - } - return is_big_obj; -} - -internal B32 -coff_is_obj(String8 data) -{ - B32 is_obj = 0; - - if (data.size >= sizeof(COFF_Header)) { - COFF_Header *header = (COFF_Header*)(data.str); - - // validate machine - B32 is_machine_type_valid = 0; - switch (header->machine) { - case COFF_MachineType_UNKNOWN: - case COFF_MachineType_X86: case COFF_MachineType_X64: - case COFF_MachineType_ARM33: case COFF_MachineType_ARM: - case COFF_MachineType_ARM64: case COFF_MachineType_ARMNT: - case COFF_MachineType_EBC: case COFF_MachineType_IA64: - case COFF_MachineType_M32R: case COFF_MachineType_MIPS16: - case COFF_MachineType_MIPSFPU:case COFF_MachineType_MIPSFPU16: - case COFF_MachineType_POWERPC:case COFF_MachineType_POWERPCFP: - case COFF_MachineType_R4000: case COFF_MachineType_RISCV32: - case COFF_MachineType_RISCV64:case COFF_MachineType_RISCV128: - case COFF_MachineType_SH3: case COFF_MachineType_SH3DSP: - case COFF_MachineType_SH4: case COFF_MachineType_SH5: - case COFF_MachineType_THUMB: case COFF_MachineType_WCEMIPSV2: - { - is_machine_type_valid = 1; - }break; - } - - if (is_machine_type_valid) { - // validate section count - U64 section_count = header->section_count; - U64 section_hdr_opl_off = sizeof(*header) + section_count*sizeof(COFF_SectionHeader); - if (data.size >= section_hdr_opl_off) { - - COFF_SectionHeader *section_hdrs = (COFF_SectionHeader*)(data.str + sizeof(*header)); - COFF_SectionHeader *section_hdr_opl = section_hdrs + section_count; - - // validate section ranges - B32 is_sect_range_valid = 1; - for (COFF_SectionHeader *sec_hdr = section_hdrs; - sec_hdr < section_hdr_opl; - sec_hdr += 1) { - if (!(sec_hdr->flags & COFF_SectionFlag_CNT_UNINITIALIZED_DATA)) { - U64 min = sec_hdr->foff; - U64 max = min + sec_hdr->fsize; - if (sec_hdr->fsize > 0 && !(section_hdr_opl_off <= min && min <= max && max <= data.size)) { - is_sect_range_valid = 0; - break; - } - } - } - - if (is_sect_range_valid) { - // validate symbol table - U64 symbol_table_off = header->symbol_table_foff; - U64 symbol_table_size = sizeof(COFF_Symbol16)*header->symbol_count; - U64 symbol_table_opl_off = symbol_table_off + symbol_table_size; - - // don't validate symbol table when there is none - if (symbol_table_off == 0 && symbol_table_size == 0) { - symbol_table_off = section_hdr_opl_off; - symbol_table_opl_off = section_hdr_opl_off; - } - - is_obj = (section_hdr_opl_off <= symbol_table_off && - symbol_table_off <= symbol_table_opl_off && - symbol_table_opl_off <= data.size); - } - } - } - } - - return is_obj; -} - -internal COFF_HeaderInfo -coff_header_info_from_data(String8 data) -{ - COFF_HeaderInfo info = {0}; - if (coff_is_big_obj(data)) { - COFF_HeaderBigObj *big_header = (COFF_HeaderBigObj*)data.str; - info.machine = big_header->machine; - info.section_array_off = sizeof(COFF_HeaderBigObj); - info.section_count_no_null = big_header->section_count; - info.string_table_off = big_header->pointer_to_symbol_table + sizeof(COFF_Symbol32) * big_header->number_of_symbols; - info.symbol_size = sizeof(COFF_Symbol32); - info.symbol_off = big_header->pointer_to_symbol_table; - info.symbol_count = big_header->number_of_symbols; - } else if (coff_is_obj(data)) { - COFF_Header *header = (COFF_Header*)data.str; - info.machine = header->machine; - info.section_array_off = sizeof(COFF_Header); - info.section_count_no_null = header->section_count; - info.string_table_off = header->symbol_table_foff + sizeof(COFF_Symbol16) * header->symbol_count; - info.symbol_size = sizeof(COFF_Symbol16); - info.symbol_off = header->symbol_table_foff; - info.symbol_count = header->symbol_count; - } - return info; -} - -internal U64 -coff_align_size_from_section_flags(COFF_SectionFlags flags) -{ - U32 align = 0; - U32 align_index = COFF_SectionFlags_Extract_ALIGN(flags); - switch (align_index) { - default: break; - case 0: align = 1; break; // alignment isn't specified, default to 1 - case COFF_SectionAlign_1BYTES: align = 1; break; - case COFF_SectionAlign_2BYTES: align = 2; break; - case COFF_SectionAlign_4BYTES: align = 4; break; - case COFF_SectionAlign_8BYTES: align = 8; break; - case COFF_SectionAlign_16BYTES: align = 16; break; - case COFF_SectionAlign_32BYTES: align = 32; break; - case COFF_SectionAlign_64BYTES: align = 64; break; - case COFF_SectionAlign_128BYTES: align = 128; break; - case COFF_SectionAlign_256BYTES: align = 256; break; - case COFF_SectionAlign_512BYTES: align = 512; break; - case COFF_SectionAlign_1024BYTES: align = 1024; break; - case COFF_SectionAlign_2048BYTES: align = 2048; break; - case COFF_SectionAlign_4096BYTES: align = 4096; break; - case COFF_SectionAlign_8192BYTES: align = 8192; break; - } - return align; -} - -internal COFF_SymbolValueInterpType -coff_interp_symbol(COFF_Symbol32 *symbol) -{ - if (symbol->storage_class == COFF_SymStorageClass_SECTION && symbol->section_number == COFF_SYMBOL_UNDEFINED_SECTION) { - return COFF_SymbolValueInterp_UNDEFINED; - } - if (symbol->storage_class == COFF_SymStorageClass_EXTERNAL && symbol->value == 0 && symbol->section_number == COFF_SYMBOL_UNDEFINED_SECTION) { - return COFF_SymbolValueInterp_UNDEFINED; - } - if (symbol->storage_class == COFF_SymStorageClass_EXTERNAL && symbol->value != 0 && symbol->section_number == COFF_SYMBOL_UNDEFINED_SECTION) { - return COFF_SymbolValueInterp_COMMON; - } - if (symbol->section_number == COFF_SYMBOL_ABS_SECTION) { - return COFF_SymbolValueInterp_ABS; - } - if (symbol->section_number == COFF_SYMBOL_DEBUG_SECTION) { - return COFF_SymbolValueInterp_DEBUG; - } - if (symbol->storage_class == COFF_SymStorageClass_WEAK_EXTERNAL) { - return COFF_SymbolValueInterp_WEAK; - } - return COFF_SymbolValueInterp_REGULAR; -} - -internal U64 -coff_foff_from_voff(COFF_SectionHeader *sections, U64 section_count, U64 voff) -{ - U64 foff = 0; - for(U64 sect_idx = 0; sect_idx < section_count; sect_idx += 1) - { - COFF_SectionHeader *sect = §ions[sect_idx]; - if(sect->voff <= voff && voff < sect->voff+sect->vsize) - { - if(!(sect->flags & COFF_SectionFlag_CNT_UNINITIALIZED_DATA)) - { - foff = sect->foff + (voff - sect->voff); - } - break; - } - } - return foff; -} - -internal COFF_SectionHeader * -coff_section_header_from_num(String8 data, U64 section_headers_off, U64 n) -{ - COFF_SectionHeader *result = &coff_section_header_nil; - if(1 <= n && section_headers_off + n*sizeof(COFF_SectionHeader) <= data.size) - { - result = (COFF_SectionHeader*)(data.str + section_headers_off + (n-1)*sizeof(COFF_SectionHeader)); - } - return result; -} - -internal String8 -coff_section_header_get_name(COFF_SectionHeader *header, String8 coff_data, U64 string_table_base) -{ - U64 size = 0; - for (; size < sizeof(header->name); size += 1) { - if (header->name[size] == '\0') { - break; - } - } - String8 name = str8(header->name, size); - - if (name.str[0] == '/') { - String8 ascii_name_offset = str8_skip(name, 1); - U64 name_offset = u64_from_str8(ascii_name_offset, 10); - - name_offset += string_table_base; - if (name_offset < coff_data.size) { - char *ptr = (char *)coff_data.str + name_offset; - name = str8_cstring(ptr); - } - } - - return name; -} - -internal void -coff_parse_section_name(String8 full_name, String8 *name_out, String8 *postfix_out) -{ - // dollar sign has multiple interpretations that depend on the type of the section. - // 1. when section contains code/data it indicates section precedence - // 2. when section starts with .debug it indicates type of data inside the section - // T: Types - // S: Symbols - // P: Precompiled types - // F: FPO data - // H: Clang extension produced with /debug:ghash, array of type hashes - *name_out = full_name; - *postfix_out = str8_lit(""); - for (U64 i = 0; i < full_name.size; ++i) { - if (full_name.str[i] == '$') { - *name_out = str8(full_name.str, i); - *postfix_out = str8(full_name.str + i + 1, full_name.size - i - 1); - - // TLS sections don't have a postfix but we still have to sort them based - // on dollar sign so they are sloted between CRT's _tls_start and _tls_end sections. - if (str8_match(*name_out, str8_lit(".tls"), 0) && postfix_out->size == 0) { - *postfix_out = str8_lit("$"); - } - - break; - } - } -} - -internal COFF_RelocNode * -coff_reloc_list_push(Arena *arena, COFF_RelocList *list, COFF_Reloc reloc) -{ - COFF_RelocNode *node = push_array(arena, COFF_RelocNode, 1); - node->data = reloc; - SLLQueuePush(list->first, list->last, node); - ++list->count; - return node; -} - -//////////////////////////////// - -internal String8 -coff_read_symbol_name(String8 data, U64 string_table_base_offset, COFF_SymbolName *name) -{ - String8 name_str = str8_lit(""); - if (name->long_name.zeroes == 0) { - U64 string_table_offset = string_table_base_offset + name->long_name.string_table_offset; - str8_deserial_read_cstr(data, string_table_offset, &name_str); - } else { - U32 i; - for (i = 0; i < sizeof(name->short_name); ++i) { - if (name->short_name[i] == '\0') { - break; - } - } - name_str = str8(name->short_name, i); - } - return name_str; -} - -internal void -coff_symbol32_from_coff_symbol16(COFF_Symbol32 *sym32, COFF_Symbol16 *sym16) -{ - sym32->name = sym16->name; - sym32->value = sym16->value; - if (sym16->section_number == COFF_SYMBOL_DEBUG_SECTION_16) { - sym32->section_number = COFF_SYMBOL_DEBUG_SECTION; - } else if (sym16->section_number == COFF_SYMBOL_ABS_SECTION_16) { - sym32->section_number = COFF_SYMBOL_ABS_SECTION; - } else { - sym32->section_number = (U32)sym16->section_number; - } - sym32->type.v = sym16->type.v; - sym32->storage_class = sym16->storage_class; - sym32->aux_symbol_count = sym16->aux_symbol_count; -} - -internal COFF_Symbol32Array -coff_symbol_array_from_data_16(Arena *arena, String8 data, U64 symbol_array_off, U64 symbol_count) -{ - COFF_Symbol32Array result; - result.count = symbol_count; - result.v = push_array_no_zero(arena, COFF_Symbol32, result.count); - - COFF_Symbol16 *sym16_arr = (COFF_Symbol16 *)(data.str + symbol_array_off); - for (U64 isymbol = 0; isymbol < symbol_count; isymbol += 1) { - // read header symbol - COFF_Symbol16 *sym16 = &sym16_arr[isymbol]; - - // convert to 32bit - COFF_Symbol32 *sym32 = &result.v[isymbol]; - coff_symbol32_from_coff_symbol16(sym32, sym16); - - if (isymbol + 1 + sym16->aux_symbol_count > symbol_count) { - Assert(!"aux symbols out of bounds"); - } - - // copy aux symbols - for (U64 iaux = 0; iaux < sym16->aux_symbol_count; iaux += 1) { - COFF_Symbol16 *aux16 = sym16 + iaux + 1; - COFF_Symbol32 *aux32 = sym32 + iaux + 1; // 32bit COFF uses 16bit aux symbols - MemoryCopy(aux32, aux16, sizeof(COFF_Symbol16)); - } - - // take into account aux symbols - isymbol += sym32->aux_symbol_count; - } - - return result; -} - -internal COFF_Symbol32Array -coff_symbol_array_from_data_32(Arena *arena, String8 data, U64 symbol_array_off, U64 symbol_count) -{ - COFF_Symbol32Array result; - result.count = symbol_count; - result.v = (COFF_Symbol32 *)(data.str + symbol_array_off); - return result; -} - -internal COFF_Symbol32Array -coff_symbol_array_from_data(Arena *arena, String8 data, U64 symbol_off, U64 symbol_count, U64 symbol_size) -{ - COFF_Symbol32Array result = {0}; - switch (symbol_size) { - case sizeof(COFF_Symbol16): result = coff_symbol_array_from_data_16(arena, data, symbol_off, symbol_count); break; - case sizeof(COFF_Symbol32): result = coff_symbol_array_from_data_32(arena, data, symbol_off, symbol_count); break; - } - return result; -} - -internal COFF_Symbol16Node * -coff_symbol16_list_push(Arena *arena, COFF_Symbol16List *list, COFF_Symbol16 symbol) -{ - COFF_Symbol16Node *node = push_array(arena, COFF_Symbol16Node, 1); - node->next = 0; - node->data = symbol; - SLLQueuePush(list->first, list->last, node); - list->count += 1; - return node; -} - -internal COFF_RelocInfo -coff_reloc_info_from_section_header(String8 data, COFF_SectionHeader *header) -{ - COFF_RelocInfo result = {0}; - if (header->flags & COFF_SectionFlag_LNK_NRELOC_OVFL && header->reloc_count == max_U16) { - COFF_Reloc counter; - U64 read_size = str8_deserial_read_struct(data, header->relocs_foff, &counter); - if (read_size == sizeof(counter) && counter.apply_off > 0) { - result.array_off = header->relocs_foff + sizeof(COFF_Reloc); - result.count = counter.apply_off - 1; // exclude counter entry - } - } else { - result.array_off = header->relocs_foff; - result.count = header->reloc_count; - } - return result; -} - -internal U64 -coff_word_size_from_machine(COFF_MachineType machine) -{ - U64 result = 0; - switch (machine) { - case COFF_MachineType_X64: result = 8; break; - case COFF_MachineType_X86: result = 4; break; - } - return result; -} - -internal String8 -coff_make_import_lookup(Arena *arena, U16 hint, String8 name) -{ - U64 buffer_size = sizeof(hint) + (name.size + 1); - U8 *buffer = push_array(arena, U8, buffer_size); - *(U16*)buffer = hint; - MemoryCopy(buffer + sizeof(hint), name.str, name.size); - buffer[buffer_size - 1] = 0; - String8 result = str8(buffer, buffer_size); - return result; -} - -internal U32 -coff_make_ordinal_32(U16 hint) -{ - U32 ordinal = (1 << 31) | hint; - return ordinal; -} - -internal U64 -coff_make_ordinal_64(U16 hint) -{ - U64 ordinal = (1ULL << 63) | hint; - return ordinal; -} - -//////////////////////////////// - -internal B32 -coff_resource_id_is_equal(COFF_ResourceID a, COFF_ResourceID b) -{ - B32 is_equal = 0; - if (a.type == b.type) { - switch (a.type) { - case COFF_ResourceIDType_NULL: break; - case COFF_ResourceIDType_NUMBER: is_equal = (a.u.number == b.u.number); break; - case COFF_ResourceIDType_STRING: is_equal = str8_match(a.u.string, b.u.string, 0); break; - default: Assert(!"invalid resource id type"); - } - } - return is_equal; -} - -internal COFF_ResourceID -coff_resource_id_copy(Arena *arena, COFF_ResourceID id) -{ - COFF_ResourceID result = zero_struct; - switch (id.type) { - case COFF_ResourceIDType_NULL: break; - case COFF_ResourceIDType_NUMBER: { - result.type = COFF_ResourceIDType_NUMBER; - result.u.number = id.u.number; - } break; - case COFF_ResourceIDType_STRING: { - result.type = COFF_ResourceIDType_STRING; - result.u.string = id.u.string; - } break; - default: Assert(!"invalid resource id type"); - } - return result; -} - -internal COFF_ResourceID -coff_convert_resource_id(Arena *arena, COFF_ResourceID_16 *id_16) -{ - COFF_ResourceID id; - id.type = id_16->type; - switch (id_16->type) { - case COFF_ResourceIDType_NULL: break; - case COFF_ResourceIDType_NUMBER: { - id.u.number = id_16->u.number; - } break; - case COFF_ResourceIDType_STRING: { - id.u.string = str8_from_16(arena, id_16->u.string); - } break; - default: Assert(!"invalid resource id type"); - } - return id; -} - -internal U64 -coff_read_resource_id(String8 data, U64 off, COFF_ResourceID_16 *id_out) -{ - U64 cursor = off; - - U16 flag = 0; - str8_deserial_read_struct(data, cursor, &flag); - - B32 is_number = flag == max_U16; - if (is_number) { - cursor += sizeof(flag); - id_out->type = COFF_ResourceIDType_NUMBER; - cursor += str8_deserial_read_struct(data, cursor, &id_out->u.number); - } else { - id_out->type = COFF_ResourceIDType_STRING; - cursor += str8_deserial_read_windows_utf16_string16(data, cursor, &id_out->u.string); - } - - U64 read_size = cursor - off; - return read_size; -} - -internal U64 -coff_read_resource(String8 raw_res, U64 off, Arena *arena, COFF_Resource *res_out) -{ - // parse header - COFF_ResourceHeaderPrefix prefix; MemoryZeroStruct(&prefix); - U64 cursor = str8_deserial_read_struct(raw_res, off, &prefix); - String8 header_data = str8_substr(raw_res, rng_1u64(off, off + prefix.header_size)); - - COFF_ResourceID_16 type_16; MemoryZeroStruct(&type_16); - cursor += coff_read_resource_id(header_data, cursor, &type_16); - cursor = AlignPow2(cursor, COFF_RES_ALIGN); - - COFF_ResourceID_16 name_16; MemoryZeroStruct(&name_16); - cursor += coff_read_resource_id(header_data, cursor, &name_16); - cursor = AlignPow2(cursor, COFF_RES_ALIGN); - - U32 data_version = 0; - cursor += str8_deserial_read_struct(header_data, cursor, &data_version); - - COFF_ResourceMemoryFlags memory_flags = 0; - cursor += str8_deserial_read_struct(header_data, cursor, &memory_flags); - - U16 language_id = 0; - cursor += str8_deserial_read_struct(header_data, cursor, &language_id); - - U32 version = 0; - cursor += str8_deserial_read_struct(header_data, cursor, &version); - - U32 characteristics = 0; - cursor += str8_deserial_read_struct(header_data, cursor, &characteristics); - - String8 data; - cursor += str8_deserial_read_block(raw_res, off + prefix.header_size, prefix.data_size, &data); - - // was resource parsed? - Assert(cursor >= prefix.data_size + prefix.header_size); - - // fill out result - res_out->type = coff_convert_resource_id(arena, &type_16); - res_out->name = coff_convert_resource_id(arena, &name_16); - res_out->language_id = language_id; - res_out->data_version = data_version; - res_out->version = version; - res_out->memory_flags = memory_flags; - res_out->data = data; - - U64 resource_size = AlignPow2(prefix.data_size + prefix.header_size, COFF_RES_ALIGN); - return resource_size; -} - -internal COFF_ResourceList -coff_resource_list_from_data(Arena *arena, String8 data) -{ - COFF_ResourceList list; MemoryZeroStruct(&list); - for (U64 cursor = 0, stride; cursor < data.size; cursor += stride) { - COFF_ResourceNode *node = push_array(arena, COFF_ResourceNode, 1); - stride = coff_read_resource(data, cursor, arena, &node->data); - list.count += 1; - SLLQueuePush(list.first, list.last, node); - } - return list; -} - -//////////////////////////////// - -internal COFF_DataType -coff_data_type_from_data(String8 data) -{ - B32 is_big_obj = coff_is_big_obj(data); - if (is_big_obj) { - return COFF_DataType_BIG_OBJ; - } - - B32 is_import = coff_is_import(data); - if (is_import) { - return COFF_DataType_IMPORT; - } - - return COFF_DataType_OBJ; -} - -internal B32 -coff_is_import(String8 data) -{ - B32 is_import = 0; - if (data.size >= sizeof(U16)*2) { - U16 *sig1 = (U16*)data.str; - U16 *sig2 = sig1 + 1; - is_import = *sig1 == COFF_MachineType_UNKNOWN && *sig2 == 0xffff; - } - return is_import; -} - -internal B32 -coff_is_archive(String8 data) -{ - U64 sig = 0; - str8_deserial_read_struct(data, 0, &sig); - B32 is_archive = sig == COFF_ARCHIVE_SIG; - return is_archive; -} - -internal B32 -coff_is_thin_archive(String8 data) -{ - U64 sig = 0; - str8_deserial_read_struct(data, 0, &sig); - B32 is_archive = sig == COFF_THIN_ARCHIVE_SIG; - return is_archive; -} - -internal U64 -coff_read_archive_member_header(String8 data, U64 offset, COFF_ArchiveMemberHeader *header_out) -{ -#define NAME_SIZE 16 -#define DATE_SIZE 12 -#define USER_ID_SIZE 6 -#define GROUP_ID_SIZE 6 -#define MODE_SIZE 8 -#define SIZE_SIZE 10 -#define TOTAL_SIZE (NAME_SIZE + DATE_SIZE + USER_ID_SIZE + GROUP_ID_SIZE + MODE_SIZE + SIZE_SIZE) - - if (str8_deserial_get_raw_ptr(data, offset, TOTAL_SIZE) == NULL) { - return 0; - } - - U64 read_offset = offset; - - U8 *name = (U8 *)str8_deserial_get_raw_ptr(data, read_offset, NAME_SIZE); - read_offset += NAME_SIZE; - - U8 *date = (U8 *)str8_deserial_get_raw_ptr(data, read_offset, DATE_SIZE); - read_offset += DATE_SIZE; - - U8 *user_id = (U8 *)str8_deserial_get_raw_ptr(data, read_offset, USER_ID_SIZE); - read_offset += USER_ID_SIZE; - - U8 *group_id = (U8 *)str8_deserial_get_raw_ptr(data, read_offset, GROUP_ID_SIZE); - read_offset += GROUP_ID_SIZE; - - U8 *mode = (U8 *)str8_deserial_get_raw_ptr(data, read_offset, MODE_SIZE); - read_offset += MODE_SIZE; - - U8 *size = (U8 *)str8_deserial_get_raw_ptr(data, read_offset, SIZE_SIZE); - read_offset += SIZE_SIZE; - - U8 end[] = { 0, 0 }; - read_offset += str8_deserial_read_array(data, read_offset, &end[0], ArrayCount(end)); - - U64 i; - for (i = 0; i < NAME_SIZE; ++i) { - if (name[i] == ' ') { - break; - } - } - header_out->name = str8(name, i); - header_out->date = (U32)s64_from_str8(str8(date, DATE_SIZE), 10); - header_out->user_id = (U32)s64_from_str8(str8(user_id, USER_ID_SIZE), 10); - header_out->group_id = (U32)s64_from_str8(str8(group_id, GROUP_ID_SIZE), 10); - header_out->mode = str8(mode, MODE_SIZE); - for (i = 0; i < SIZE_SIZE; ++i) { - if (size[i] == ' ') { - break; - } - } - header_out->size = (U32)s64_from_str8(str8(size, i), 10); - header_out->is_end_correct = (end[0] == '`' && end[1] == '\n'); - - U64 result = (read_offset - offset); - return result; - -#undef NAME_SIZE -#undef DATE_SIZE -#undef USER_ID_SIZE -#undef GROUP_ID_SIZE -#undef MODE_SIZE -#undef SIZE_SIZE -#undef TOTAL_SIZE -} - -internal COFF_ArchiveMember -coff_read_archive_member(String8 data, U64 offset) -{ - COFF_ArchiveMember member; MemoryZeroStruct(&member); - coff_archive_member_iter_next(data, &offset, &member); - return member; -} - -internal COFF_ArchiveMember -coff_archive_member_from_data(String8 data) -{ - return coff_read_archive_member(data, 0); -} - -internal U64 -coff_read_archive_import(String8 data, U64 offset, COFF_ImportHeader *header_out) -{ - U64 cursor = offset; - - cursor += str8_deserial_read_struct(data, cursor, &header_out->sig1); - cursor += str8_deserial_read_struct(data, cursor, &header_out->sig2); - cursor += str8_deserial_read_struct(data, cursor, &header_out->version); - cursor += str8_deserial_read_struct(data, cursor, &header_out->machine); - cursor += str8_deserial_read_struct(data, cursor, &header_out->time_stamp); - cursor += str8_deserial_read_struct(data, cursor, &header_out->data_size); - cursor += str8_deserial_read_struct(data, cursor, &header_out->hint); - - U16 flags = 0; - cursor += str8_deserial_read_struct(data, cursor, &flags); - header_out->type = COFF_IMPORT_HEADER_GET_TYPE(flags); - header_out->name_type = COFF_IMPORT_HEADER_GET_NAME_TYPE(flags); - - header_out->func_name = str8(0,0); - cursor += str8_deserial_read_cstr(data, cursor, &header_out->func_name); - - header_out->dll_name = str8(0,0); - cursor += str8_deserial_read_cstr(data, cursor, &header_out->dll_name); - - Assert(header_out->func_name.size + header_out->dll_name.size + /* nulls */ 2 == header_out->data_size); - - U64 read_size = cursor - offset; - return read_size; -} - -internal COFF_ImportHeader -coff_archive_import_from_data(String8 data) -{ - COFF_ImportHeader header; MemoryZeroStruct(&header); - coff_read_archive_import(data, 0, &header); - return header; -} - -internal String8 -coff_read_archive_long_name(String8 long_names, String8 name) -{ - String8 result = name; - if (name.size > 0 && name.str[0] == '/') { - String8 offset_str = str8(name.str + 1, name.size - 1); - U64 offset = u64_from_str8(offset_str, 10); - if (offset < long_names.size) { - U8 *ptr = long_names.str + offset; - U8 *opl = long_names.str + long_names.size; - for (; ptr < opl; ++ptr) { - if (*ptr == '\0' || *ptr == '\n') { - break; - } - } - result = str8_range(long_names.str + offset, ptr); - } - } - return result; -} - -internal U64 -coff_archive_member_iter_init(String8 data) -{ - U64 cursor = 0; - U64 sig = 0; - cursor += str8_deserial_read_struct(data, cursor, &sig); - if (sig != COFF_ARCHIVE_SIG) { - cursor = data.size; - } - return cursor; -} - -internal B32 -coff_archive_member_iter_next(String8 data, U64 *offset, COFF_ArchiveMember *member_out) -{ - B32 is_parsed; - - COFF_ArchiveMemberHeader header; - U64 header_read_size = coff_read_archive_member_header(data, *offset, &header); - - if (header_read_size && header.is_end_correct) { - Rng1U64 data_range = rng_1u64(*offset + header_read_size, *offset + header_read_size + header.size); - - member_out->header = header; - member_out->offset = *offset; - member_out->data = str8_substr(data, data_range); - - *offset += header_read_size; - *offset += member_out->header.size; - *offset = AlignPow2(*offset, COFF_ARCHIVE_ALIGN); - - is_parsed = 1; - } else { - MemoryZeroStruct(&member_out->header); - member_out->offset = max_U64; - member_out->data = str8(0,0); - - is_parsed = 0; - } - - return is_parsed; -} - -internal B32 -coff_get_first_archive_member(COFF_ArchiveMember *member, COFF_ArchiveFirstMember *first_out) -{ - B32 is_header = str8_match(member->header.name, str8_lit("/"), 0); - if (is_header) { - U64 cursor = 0; - - U32 symbol_count = 0; - cursor += str8_deserial_read_struct(member->data, cursor, &symbol_count); - -#if ARCH_LITTLE_ENDIAN - symbol_count = bswap_u32(symbol_count); -#endif - - Rng1U64 member_offsets_range = rng_1u64(cursor, cursor + symbol_count * sizeof(U32)); - cursor += dim_1u64(member_offsets_range); - - Rng1U64 string_table_range = rng_1u64(cursor, member->data.size); - cursor += dim_1u64(string_table_range); - - first_out->symbol_count = symbol_count; - first_out->member_offsets = str8_substr(member->data, member_offsets_range); - first_out->string_table = str8_substr(member->data, string_table_range); - } - return is_header; -} - -internal B32 -coff_get_second_archive_member(COFF_ArchiveMember *member, COFF_ArchiveSecondMember *second_out) -{ - B32 is_header = str8_match(member->header.name, str8_lit("/"), 0); - if (is_header) { - U64 cursor = 0; - - U32 member_count = 0; - cursor += str8_deserial_read_struct(member->data, cursor, &member_count); - - Rng1U64 member_offsets_range = rng_1u64(cursor, cursor + member_count * sizeof(U32)); - cursor += dim_1u64(member_offsets_range); - - U32 symbol_count = 0; - cursor += str8_deserial_read_struct(member->data, cursor, &symbol_count); - - Rng1U64 symbol_indices_range = rng_1u64(cursor, cursor + symbol_count * sizeof(U16)); - cursor += dim_1u64(symbol_indices_range); - - Rng1U64 string_table_range = rng_1u64(cursor, member->data.size); - - second_out->member_count = member_count; - second_out->symbol_count = symbol_count; - second_out->member_offsets = str8_substr(member->data, member_offsets_range); - second_out->symbol_indices = str8_substr(member->data, symbol_indices_range); - second_out->string_table = str8_substr(member->data, string_table_range); - } - return is_header; -} - -internal void -coff_archive_member_list_push_node(COFF_ArchiveMemberList *list, COFF_ArchiveMemberNode *node) -{ - SLLQueuePush(list->first, list->last, node); - list->count += 1; -} - -internal COFF_ArchiveParse -coff_archive_parse_from_member_list(COFF_ArchiveMemberList member_list) -{ - COFF_ArchiveMember first_header; MemoryZeroStruct(&first_header); - COFF_ArchiveMember second_header; MemoryZeroStruct(&second_header); - COFF_ArchiveMember long_names_member; MemoryZeroStruct(&long_names_member); - - if (member_list.count) { - if (str8_match(member_list.first->data.header.name, str8_lit("/"), 0)) { - first_header = member_list.first->data; - SLLQueuePop(member_list.first, member_list.last); - member_list.count -= 1; - - if (member_list.count && str8_match(member_list.first->data.header.name, str8_lit("/"), 0)) { - second_header = member_list.first->data; - SLLQueuePop(member_list.first, member_list.last); - member_list.count -= 1; - } - - if (member_list.count && str8_match(member_list.first->data.header.name, str8_lit("//"), 0)) { - long_names_member = member_list.first->data; - SLLQueuePop(member_list.first, member_list.last); - member_list.count -= 1; - } - } - } - - COFF_ArchiveFirstMember first_member; MemoryZeroStruct(&first_member); - coff_get_first_archive_member(&first_header, &first_member); - - COFF_ArchiveSecondMember second_member; MemoryZeroStruct(&second_member); - coff_get_second_archive_member(&second_header, &second_member); - - COFF_ArchiveParse parse; MemoryZeroStruct(&parse); - parse.first_member = first_member; - parse.second_member = second_member; - parse.long_names = long_names_member.data; - - return parse; -} - -internal COFF_ArchiveParse -coff_archive_from_data(Arena *arena, String8 data) -{ - COFF_ArchiveMemberList list; MemoryZeroStruct(&list); - COFF_ArchiveMemberNode node_arr[3]; MemoryZeroStruct(&node_arr[0]); - U64 cursor = coff_archive_member_iter_init(data); - for (U64 i = 0; i < ArrayCount(node_arr); i += 1) { - COFF_ArchiveMemberNode *node = &node_arr[i]; - if (!coff_archive_member_iter_next(data, &cursor, &node->data)) { - break; - } - coff_archive_member_list_push_node(&list, node); - } - return coff_archive_parse_from_member_list(list); -} - -internal U64 -coff_thin_archive_member_iter_init(String8 data) -{ - U64 cursor = 0; - U64 sig = 0; - cursor += str8_deserial_read_struct(data, cursor, &sig); - if (sig != COFF_THIN_ARCHIVE_SIG) { - cursor = data.size; - } - return cursor; -} - -internal B32 -coff_thin_archive_member_iter_next(String8 data, U64 *offset, COFF_ArchiveMember *member_out) -{ - B32 is_parsed = 0; - - U64 header_size = coff_read_archive_member_header(data, *offset, &member_out->header); - if (header_size) { - member_out->offset = *offset; - *offset += header_size; - if (str8_match(member_out->header.name, str8_lit("/"), 0) || str8_match(member_out->header.name, str8_lit("//"), 0)) { - Rng1U64 data_range = rng_1u64(*offset, *offset + member_out->header.size); - member_out->data = str8_substr(data, data_range); - *offset += member_out->header.size; - } else { - // size field in non-header members means size of stand-alone obj - member_out->data = str8(0,0); - } - *offset = AlignPow2(*offset, COFF_ARCHIVE_ALIGN); - is_parsed = 1; - } - - return is_parsed; -} - -internal COFF_ArchiveParse -coff_thin_archive_from_data(Arena *arena, String8 data) -{ - COFF_ArchiveMemberList list; MemoryZeroStruct(&list); - COFF_ArchiveMemberNode node_arr[3]; MemoryZeroStruct(&node_arr[0]); - U64 cursor = coff_thin_archive_member_iter_init(data); - for (U64 i = 0; i < ArrayCount(node_arr); i += 1) { - COFF_ArchiveMemberNode *node = &node_arr[i]; - if (!coff_thin_archive_member_iter_next(data, &cursor, &node->data)) { - break; - } - coff_archive_member_list_push_node(&list, node); - } - return coff_archive_parse_from_member_list(list); -} - -internal COFF_ArchiveType -coff_archive_type_from_data(String8 data) -{ - if (coff_is_archive(data)) { - return COFF_Archive_Regular; - } else if (coff_is_thin_archive(data)) { - return COFF_Archive_Thin; - } - return COFF_Archive_Null; -} - -internal COFF_ArchiveParse -coff_archive_parse_from_data(Arena *arena, String8 data) -{ - COFF_ArchiveType type = coff_archive_type_from_data(data); - switch (type) { - case COFF_Archive_Null: break; - case COFF_Archive_Regular: return coff_archive_from_data(arena, data); - case COFF_Archive_Thin: return coff_thin_archive_from_data(arena, data); - } - COFF_ArchiveParse null_parse; MemoryZeroStruct(&null_parse); - return null_parse; -} - -//////////////////////////////// - -internal String8 -coff_string_from_comdat_select_type(COFF_ComdatSelectType select) -{ - String8 result = str8(0,0); - switch (select) { - case COFF_ComdatSelectType_NULL: result = str8_lit("NULL"); break; - case COFF_ComdatSelectType_NODUPLICATES: result = str8_lit("NODUPLICATES"); break; - case COFF_ComdatSelectType_ANY: result = str8_lit("ANY"); break; - case COFF_ComdatSelectType_SAME_SIZE: result = str8_lit("SAME_SIZE"); break; - case COFF_ComdatSelectType_EXACT_MATCH: result = str8_lit("EXACT_MATCH"); break; - case COFF_ComdatSelectType_ASSOCIATIVE: result = str8_lit("ASSOCIATIVE"); break; - case COFF_ComdatSelectType_LARGEST: result = str8_lit("LARGEST"); break; - } - return result; -} - -internal String8 -coff_string_from_machine_type(COFF_MachineType machine) -{ - String8 result = str8(0,0); - switch (machine) { - case COFF_MachineType_UNKNOWN: result = str8_lit("UNKNOWN"); break; - case COFF_MachineType_X86: result = str8_lit("X86"); break; - case COFF_MachineType_X64: result = str8_lit("X64"); break; - case COFF_MachineType_ARM33: result = str8_lit("ARM33"); break; - case COFF_MachineType_ARM: result = str8_lit("ARM"); break; - case COFF_MachineType_ARM64: result = str8_lit("ARM64"); break; - case COFF_MachineType_ARMNT: result = str8_lit("ARMNT"); break; - case COFF_MachineType_EBC: result = str8_lit("EBC"); break; - case COFF_MachineType_IA64: result = str8_lit("IA64"); break; - case COFF_MachineType_M32R: result = str8_lit("M32R"); break; - case COFF_MachineType_MIPS16: result = str8_lit("MIPS16"); break; - case COFF_MachineType_MIPSFPU: result = str8_lit("MIPSFPU"); break; - case COFF_MachineType_MIPSFPU16: result = str8_lit("MIPSFPU16"); break; - case COFF_MachineType_POWERPC: result = str8_lit("POWERPC"); break; - case COFF_MachineType_POWERPCFP: result = str8_lit("POWERPCFP"); break; - case COFF_MachineType_R4000: result = str8_lit("R4000"); break; - case COFF_MachineType_RISCV32: result = str8_lit("RISCV32"); break; - case COFF_MachineType_RISCV64: result = str8_lit("RISCV64"); break; - case COFF_MachineType_RISCV128: result = str8_lit("RISCV128"); break; - case COFF_MachineType_SH3: result = str8_lit("SH3"); break; - case COFF_MachineType_SH3DSP: result = str8_lit("SH3DSP"); break; - case COFF_MachineType_SH4: result = str8_lit("SH4"); break; - case COFF_MachineType_SH5: result = str8_lit("SH5"); break; - case COFF_MachineType_THUMB: result = str8_lit("THUMB"); break; - case COFF_MachineType_WCEMIPSV2: result = str8_lit("WCEMIPSV2"); break; - } - return result; -} - -internal String8 -coff_string_from_section_flags(Arena *arena, COFF_SectionFlags flags) -{ - Temp scratch = scratch_begin(&arena, 1); - String8List list = {0}; - - if (flags & COFF_SectionFlag_TYPE_NO_PAD) { - str8_list_pushf(scratch.arena, &list, "TYPE_NO_PAD"); - } - if (flags & COFF_SectionFlag_CNT_CODE) { - str8_list_pushf(scratch.arena, &list, "CNT_CODE"); - } - if (flags & COFF_SectionFlag_CNT_INITIALIZED_DATA) { - str8_list_pushf(scratch.arena, &list, "CNT_INITIALIZED_DATA"); - } - if (flags & COFF_SectionFlag_CNT_UNINITIALIZED_DATA) { - str8_list_pushf(scratch.arena, &list, "CNT_UNINITIALIZED_DATA"); - } - if (flags & COFF_SectionFlag_LNK_OTHER) { - str8_list_pushf(scratch.arena, &list, "LNK_OTHER"); - } - if (flags & COFF_SectionFlag_LNK_INFO) { - str8_list_pushf(scratch.arena, &list, "LNK_INFO"); - } - if (flags & COFF_SectionFlag_LNK_COMDAT) { - str8_list_pushf(scratch.arena, &list, "LNK_COMDAT"); - } - if (flags & COFF_SectionFlag_GPREL) { - str8_list_pushf(scratch.arena, &list, "GPREL"); - } - if (flags & COFF_SectionFlag_MEM_16BIT) { - str8_list_pushf(scratch.arena, &list, "MEM_16BIT"); - } - if (flags & COFF_SectionFlag_MEM_LOCKED) { - str8_list_pushf(scratch.arena, &list, "MEM_LOCKED"); - } - if (flags & COFF_SectionFlag_MEM_PRELOAD) { - str8_list_pushf(scratch.arena, &list, "MEM_PRELOAD"); - } - if (flags & COFF_SectionFlag_LNK_NRELOC_OVFL) { - str8_list_pushf(scratch.arena, &list, "LNK_NRELOC_OVFL"); - } - if (flags & COFF_SectionFlag_MEM_DISCARDABLE) { - str8_list_pushf(scratch.arena, &list, "MEM_DISCARDABLE"); - } - if (flags & COFF_SectionFlag_MEM_NOT_CACHED) { - str8_list_pushf(scratch.arena, &list, "MEM_NOT_CACHED"); - } - if (flags & COFF_SectionFlag_MEM_NOT_PAGED) { - str8_list_pushf(scratch.arena, &list, "MEM_NOT_PAGED"); - } - if (flags & COFF_SectionFlag_MEM_SHARED) { - str8_list_pushf(scratch.arena, &list, "MEM_SHARED"); - } - if (flags & COFF_SectionFlag_MEM_EXECUTE) { - str8_list_pushf(scratch.arena, &list, "MEM_EXECUTE"); - } - if (flags & COFF_SectionFlag_MEM_READ) { - str8_list_pushf(scratch.arena, &list, "MEM_READ"); - } - if (flags & COFF_SectionFlag_MEM_WRITE) { - str8_list_pushf(scratch.arena, &list, "MEM_WRITE"); - } - - U64 align = COFF_SectionFlags_Extract_ALIGN(flags); - if (align) { - str8_list_pushf(scratch.arena, &list, "ALIGN=%u", align); - } - - StringJoin join = {0}; - join.sep = str8_lit(", "); - String8 result = str8_list_join(arena, &list, &join); - - scratch_end(scratch); - return result; -} - +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +internal B32 +coff_is_big_obj(String8 data) +{ + B32 is_big_obj = 0; + if (data.size >= sizeof(COFF_HeaderBigObj)) { + COFF_HeaderBigObj *big_header = (COFF_HeaderBigObj*)(data.str); + is_big_obj = big_header->sig1 == COFF_MachineType_UNKNOWN && + big_header->sig2 == max_U16 && + big_header->version >= COFF_MIN_BIG_OBJ_VERSION && + MemoryCompare(big_header->magic, coff_big_obj_magic, sizeof(big_header->magic)) == 0; + } + return is_big_obj; +} + +internal B32 +coff_is_obj(String8 data) +{ + B32 is_obj = 0; + + if (data.size >= sizeof(COFF_Header)) { + COFF_Header *header = (COFF_Header*)(data.str); + + // validate machine + B32 is_machine_type_valid = 0; + switch (header->machine) { + case COFF_MachineType_UNKNOWN: + case COFF_MachineType_X86: case COFF_MachineType_X64: + case COFF_MachineType_ARM33: case COFF_MachineType_ARM: + case COFF_MachineType_ARM64: case COFF_MachineType_ARMNT: + case COFF_MachineType_EBC: case COFF_MachineType_IA64: + case COFF_MachineType_M32R: case COFF_MachineType_MIPS16: + case COFF_MachineType_MIPSFPU:case COFF_MachineType_MIPSFPU16: + case COFF_MachineType_POWERPC:case COFF_MachineType_POWERPCFP: + case COFF_MachineType_R4000: case COFF_MachineType_RISCV32: + case COFF_MachineType_RISCV64:case COFF_MachineType_RISCV128: + case COFF_MachineType_SH3: case COFF_MachineType_SH3DSP: + case COFF_MachineType_SH4: case COFF_MachineType_SH5: + case COFF_MachineType_THUMB: case COFF_MachineType_WCEMIPSV2: + { + is_machine_type_valid = 1; + }break; + } + + if (is_machine_type_valid) { + // validate section count + U64 section_count = header->section_count; + U64 section_hdr_opl_off = sizeof(*header) + section_count*sizeof(COFF_SectionHeader); + if (data.size >= section_hdr_opl_off) { + + COFF_SectionHeader *section_hdrs = (COFF_SectionHeader*)(data.str + sizeof(*header)); + COFF_SectionHeader *section_hdr_opl = section_hdrs + section_count; + + // validate section ranges + B32 is_sect_range_valid = 1; + for (COFF_SectionHeader *sec_hdr = section_hdrs; + sec_hdr < section_hdr_opl; + sec_hdr += 1) { + if (!(sec_hdr->flags & COFF_SectionFlag_CNT_UNINITIALIZED_DATA)) { + U64 min = sec_hdr->foff; + U64 max = min + sec_hdr->fsize; + if (sec_hdr->fsize > 0 && !(section_hdr_opl_off <= min && min <= max && max <= data.size)) { + is_sect_range_valid = 0; + break; + } + } + } + + if (is_sect_range_valid) { + // validate symbol table + U64 symbol_table_off = header->symbol_table_foff; + U64 symbol_table_size = sizeof(COFF_Symbol16)*header->symbol_count; + U64 symbol_table_opl_off = symbol_table_off + symbol_table_size; + + // don't validate symbol table when there is none + if (symbol_table_off == 0 && symbol_table_size == 0) { + symbol_table_off = section_hdr_opl_off; + symbol_table_opl_off = section_hdr_opl_off; + } + + is_obj = (section_hdr_opl_off <= symbol_table_off && + symbol_table_off <= symbol_table_opl_off && + symbol_table_opl_off <= data.size); + } + } + } + } + + return is_obj; +} + +internal COFF_HeaderInfo +coff_header_info_from_data(String8 data) +{ + COFF_HeaderInfo info = {0}; + if (coff_is_big_obj(data)) { + COFF_HeaderBigObj *big_header = (COFF_HeaderBigObj*)data.str; + info.machine = big_header->machine; + info.section_array_off = sizeof(COFF_HeaderBigObj); + info.section_count_no_null = big_header->section_count; + info.string_table_off = big_header->pointer_to_symbol_table + sizeof(COFF_Symbol32) * big_header->number_of_symbols; + info.symbol_size = sizeof(COFF_Symbol32); + info.symbol_off = big_header->pointer_to_symbol_table; + info.symbol_count = big_header->number_of_symbols; + } else if (coff_is_obj(data)) { + COFF_Header *header = (COFF_Header*)data.str; + info.machine = header->machine; + info.section_array_off = sizeof(COFF_Header); + info.section_count_no_null = header->section_count; + info.string_table_off = header->symbol_table_foff + sizeof(COFF_Symbol16) * header->symbol_count; + info.symbol_size = sizeof(COFF_Symbol16); + info.symbol_off = header->symbol_table_foff; + info.symbol_count = header->symbol_count; + } + return info; +} + +internal U64 +coff_align_size_from_section_flags(COFF_SectionFlags flags) +{ + U32 align = 0; + U32 align_index = COFF_SectionFlags_Extract_ALIGN(flags); + switch (align_index) { + default: break; + case 0: align = 1; break; // alignment isn't specified, default to 1 + case COFF_SectionAlign_1BYTES: align = 1; break; + case COFF_SectionAlign_2BYTES: align = 2; break; + case COFF_SectionAlign_4BYTES: align = 4; break; + case COFF_SectionAlign_8BYTES: align = 8; break; + case COFF_SectionAlign_16BYTES: align = 16; break; + case COFF_SectionAlign_32BYTES: align = 32; break; + case COFF_SectionAlign_64BYTES: align = 64; break; + case COFF_SectionAlign_128BYTES: align = 128; break; + case COFF_SectionAlign_256BYTES: align = 256; break; + case COFF_SectionAlign_512BYTES: align = 512; break; + case COFF_SectionAlign_1024BYTES: align = 1024; break; + case COFF_SectionAlign_2048BYTES: align = 2048; break; + case COFF_SectionAlign_4096BYTES: align = 4096; break; + case COFF_SectionAlign_8192BYTES: align = 8192; break; + } + return align; +} + +internal COFF_SymbolValueInterpType +coff_interp_symbol(COFF_Symbol32 *symbol) +{ + if (symbol->storage_class == COFF_SymStorageClass_SECTION && symbol->section_number == COFF_SYMBOL_UNDEFINED_SECTION) { + return COFF_SymbolValueInterp_UNDEFINED; + } + if (symbol->storage_class == COFF_SymStorageClass_EXTERNAL && symbol->value == 0 && symbol->section_number == COFF_SYMBOL_UNDEFINED_SECTION) { + return COFF_SymbolValueInterp_UNDEFINED; + } + if (symbol->storage_class == COFF_SymStorageClass_EXTERNAL && symbol->value != 0 && symbol->section_number == COFF_SYMBOL_UNDEFINED_SECTION) { + return COFF_SymbolValueInterp_COMMON; + } + if (symbol->section_number == COFF_SYMBOL_ABS_SECTION) { + return COFF_SymbolValueInterp_ABS; + } + if (symbol->section_number == COFF_SYMBOL_DEBUG_SECTION) { + return COFF_SymbolValueInterp_DEBUG; + } + if (symbol->storage_class == COFF_SymStorageClass_WEAK_EXTERNAL) { + return COFF_SymbolValueInterp_WEAK; + } + return COFF_SymbolValueInterp_REGULAR; +} + +internal U64 +coff_foff_from_voff(COFF_SectionHeader *sections, U64 section_count, U64 voff) +{ + U64 foff = 0; + for(U64 sect_idx = 0; sect_idx < section_count; sect_idx += 1) + { + COFF_SectionHeader *sect = §ions[sect_idx]; + if(sect->voff <= voff && voff < sect->voff+sect->vsize) + { + if(!(sect->flags & COFF_SectionFlag_CNT_UNINITIALIZED_DATA)) + { + foff = sect->foff + (voff - sect->voff); + } + break; + } + } + return foff; +} + +internal COFF_SectionHeader * +coff_section_header_from_num(String8 data, U64 section_headers_off, U64 n) +{ + COFF_SectionHeader *result = &coff_section_header_nil; + if(1 <= n && section_headers_off + n*sizeof(COFF_SectionHeader) <= data.size) + { + result = (COFF_SectionHeader*)(data.str + section_headers_off + (n-1)*sizeof(COFF_SectionHeader)); + } + return result; +} + +internal String8 +coff_section_header_get_name(COFF_SectionHeader *header, String8 coff_data, U64 string_table_base) +{ + U64 size = 0; + for (; size < sizeof(header->name); size += 1) { + if (header->name[size] == '\0') { + break; + } + } + String8 name = str8(header->name, size); + + if (name.str[0] == '/') { + String8 ascii_name_offset = str8_skip(name, 1); + U64 name_offset = u64_from_str8(ascii_name_offset, 10); + + name_offset += string_table_base; + if (name_offset < coff_data.size) { + char *ptr = (char *)coff_data.str + name_offset; + name = str8_cstring(ptr); + } + } + + return name; +} + +internal void +coff_parse_section_name(String8 full_name, String8 *name_out, String8 *postfix_out) +{ + // dollar sign has multiple interpretations that depend on the type of the section. + // 1. when section contains code/data it indicates section precedence + // 2. when section starts with .debug it indicates type of data inside the section + // T: Types + // S: Symbols + // P: Precompiled types + // F: FPO data + // H: Clang extension produced with /debug:ghash, array of type hashes + *name_out = full_name; + *postfix_out = str8_lit(""); + for (U64 i = 0; i < full_name.size; ++i) { + if (full_name.str[i] == '$') { + *name_out = str8(full_name.str, i); + *postfix_out = str8(full_name.str + i + 1, full_name.size - i - 1); + + // TLS sections don't have a postfix but we still have to sort them based + // on dollar sign so they are sloted between CRT's _tls_start and _tls_end sections. + if (str8_match(*name_out, str8_lit(".tls"), 0) && postfix_out->size == 0) { + *postfix_out = str8_lit("$"); + } + + break; + } + } +} + +internal COFF_RelocNode * +coff_reloc_list_push(Arena *arena, COFF_RelocList *list, COFF_Reloc reloc) +{ + COFF_RelocNode *node = push_array(arena, COFF_RelocNode, 1); + node->data = reloc; + SLLQueuePush(list->first, list->last, node); + ++list->count; + return node; +} + +//////////////////////////////// + +internal String8 +coff_read_symbol_name(String8 data, U64 string_table_base_offset, COFF_SymbolName *name) +{ + String8 name_str = str8_lit(""); + if (name->long_name.zeroes == 0) { + U64 string_table_offset = string_table_base_offset + name->long_name.string_table_offset; + str8_deserial_read_cstr(data, string_table_offset, &name_str); + } else { + U32 i; + for (i = 0; i < sizeof(name->short_name); ++i) { + if (name->short_name[i] == '\0') { + break; + } + } + name_str = str8(name->short_name, i); + } + return name_str; +} + +internal void +coff_symbol32_from_coff_symbol16(COFF_Symbol32 *sym32, COFF_Symbol16 *sym16) +{ + sym32->name = sym16->name; + sym32->value = sym16->value; + if (sym16->section_number == COFF_SYMBOL_DEBUG_SECTION_16) { + sym32->section_number = COFF_SYMBOL_DEBUG_SECTION; + } else if (sym16->section_number == COFF_SYMBOL_ABS_SECTION_16) { + sym32->section_number = COFF_SYMBOL_ABS_SECTION; + } else { + sym32->section_number = (U32)sym16->section_number; + } + sym32->type.v = sym16->type.v; + sym32->storage_class = sym16->storage_class; + sym32->aux_symbol_count = sym16->aux_symbol_count; +} + +internal COFF_Symbol32Array +coff_symbol_array_from_data_16(Arena *arena, String8 data, U64 symbol_array_off, U64 symbol_count) +{ + COFF_Symbol32Array result; + result.count = symbol_count; + result.v = push_array_no_zero_aligned(arena, COFF_Symbol32, result.count, 8); + + COFF_Symbol16 *sym16_arr = (COFF_Symbol16 *)(data.str + symbol_array_off); + for (U64 isymbol = 0; isymbol < symbol_count; isymbol += 1) { + // read header symbol + COFF_Symbol16 *sym16 = &sym16_arr[isymbol]; + + // convert to 32bit + COFF_Symbol32 *sym32 = &result.v[isymbol]; + coff_symbol32_from_coff_symbol16(sym32, sym16); + + if (isymbol + 1 + sym16->aux_symbol_count > symbol_count) { + Assert(!"aux symbols out of bounds"); + } + + // copy aux symbols + for (U64 iaux = 0; iaux < sym16->aux_symbol_count; iaux += 1) { + COFF_Symbol16 *aux16 = sym16 + iaux + 1; + COFF_Symbol32 *aux32 = sym32 + iaux + 1; // 32bit COFF uses 16bit aux symbols + MemoryCopy(aux32, aux16, sizeof(COFF_Symbol16)); + } + + // take into account aux symbols + isymbol += sym32->aux_symbol_count; + } + + return result; +} + +internal COFF_Symbol32Array +coff_symbol_array_from_data_32(Arena *arena, String8 data, U64 symbol_array_off, U64 symbol_count) +{ + COFF_Symbol32Array result; + result.count = symbol_count; + result.v = (COFF_Symbol32 *)(data.str + symbol_array_off); + return result; +} + +internal COFF_Symbol32Array +coff_symbol_array_from_data(Arena *arena, String8 data, U64 symbol_off, U64 symbol_count, U64 symbol_size) +{ + COFF_Symbol32Array result = {0}; + switch (symbol_size) { + case sizeof(COFF_Symbol16): result = coff_symbol_array_from_data_16(arena, data, symbol_off, symbol_count); break; + case sizeof(COFF_Symbol32): result = coff_symbol_array_from_data_32(arena, data, symbol_off, symbol_count); break; + } + return result; +} + +internal COFF_Symbol16Node * +coff_symbol16_list_push(Arena *arena, COFF_Symbol16List *list, COFF_Symbol16 symbol) +{ + COFF_Symbol16Node *node = push_array(arena, COFF_Symbol16Node, 1); + node->next = 0; + node->data = symbol; + SLLQueuePush(list->first, list->last, node); + list->count += 1; + return node; +} + +internal COFF_RelocInfo +coff_reloc_info_from_section_header(String8 data, COFF_SectionHeader *header) +{ + COFF_RelocInfo result = {0}; + if (header->flags & COFF_SectionFlag_LNK_NRELOC_OVFL && header->reloc_count == max_U16) { + COFF_Reloc counter; + U64 read_size = str8_deserial_read_struct(data, header->relocs_foff, &counter); + if (read_size == sizeof(counter) && counter.apply_off > 0) { + result.array_off = header->relocs_foff + sizeof(COFF_Reloc); + result.count = counter.apply_off - 1; // exclude counter entry + } + } else { + result.array_off = header->relocs_foff; + result.count = header->reloc_count; + } + return result; +} + +internal U64 +coff_word_size_from_machine(COFF_MachineType machine) +{ + U64 result = 0; + switch (machine) { + case COFF_MachineType_X64: result = 8; break; + case COFF_MachineType_X86: result = 4; break; + } + return result; +} + +internal String8 +coff_make_import_lookup(Arena *arena, U16 hint, String8 name) +{ + U64 buffer_size = sizeof(hint) + (name.size + 1); + U8 *buffer = push_array(arena, U8, buffer_size); + *(U16*)buffer = hint; + MemoryCopy(buffer + sizeof(hint), name.str, name.size); + buffer[buffer_size - 1] = 0; + String8 result = str8(buffer, buffer_size); + return result; +} + +internal U32 +coff_make_ordinal_32(U16 hint) +{ + U32 ordinal = (1 << 31) | hint; + return ordinal; +} + +internal U64 +coff_make_ordinal_64(U16 hint) +{ + U64 ordinal = (1ULL << 63) | hint; + return ordinal; +} + +//////////////////////////////// + +internal B32 +coff_resource_id_is_equal(COFF_ResourceID a, COFF_ResourceID b) +{ + B32 is_equal = 0; + if (a.type == b.type) { + switch (a.type) { + case COFF_ResourceIDType_NULL: break; + case COFF_ResourceIDType_NUMBER: is_equal = (a.u.number == b.u.number); break; + case COFF_ResourceIDType_STRING: is_equal = str8_match(a.u.string, b.u.string, 0); break; + default: Assert(!"invalid resource id type"); + } + } + return is_equal; +} + +internal COFF_ResourceID +coff_resource_id_copy(Arena *arena, COFF_ResourceID id) +{ + COFF_ResourceID result = zero_struct; + switch (id.type) { + case COFF_ResourceIDType_NULL: break; + case COFF_ResourceIDType_NUMBER: { + result.type = COFF_ResourceIDType_NUMBER; + result.u.number = id.u.number; + } break; + case COFF_ResourceIDType_STRING: { + result.type = COFF_ResourceIDType_STRING; + result.u.string = id.u.string; + } break; + default: Assert(!"invalid resource id type"); + } + return result; +} + +internal COFF_ResourceID +coff_convert_resource_id(Arena *arena, COFF_ResourceID_16 *id_16) +{ + COFF_ResourceID id; + id.type = id_16->type; + switch (id_16->type) { + case COFF_ResourceIDType_NULL: break; + case COFF_ResourceIDType_NUMBER: { + id.u.number = id_16->u.number; + } break; + case COFF_ResourceIDType_STRING: { + id.u.string = str8_from_16(arena, id_16->u.string); + } break; + default: Assert(!"invalid resource id type"); + } + return id; +} + +internal U64 +coff_read_resource_id(String8 data, U64 off, COFF_ResourceID_16 *id_out) +{ + U64 cursor = off; + + U16 flag = 0; + str8_deserial_read_struct(data, cursor, &flag); + + B32 is_number = flag == max_U16; + if (is_number) { + cursor += sizeof(flag); + id_out->type = COFF_ResourceIDType_NUMBER; + cursor += str8_deserial_read_struct(data, cursor, &id_out->u.number); + } else { + id_out->type = COFF_ResourceIDType_STRING; + cursor += str8_deserial_read_windows_utf16_string16(data, cursor, &id_out->u.string); + } + + U64 read_size = cursor - off; + return read_size; +} + +internal U64 +coff_read_resource(String8 raw_res, U64 off, Arena *arena, COFF_Resource *res_out) +{ + // parse header + COFF_ResourceHeaderPrefix prefix; MemoryZeroStruct(&prefix); + U64 cursor = str8_deserial_read_struct(raw_res, off, &prefix); + String8 header_data = str8_substr(raw_res, rng_1u64(off, off + prefix.header_size)); + + COFF_ResourceID_16 type_16; MemoryZeroStruct(&type_16); + cursor += coff_read_resource_id(header_data, cursor, &type_16); + cursor = AlignPow2(cursor, COFF_RES_ALIGN); + + COFF_ResourceID_16 name_16; MemoryZeroStruct(&name_16); + cursor += coff_read_resource_id(header_data, cursor, &name_16); + cursor = AlignPow2(cursor, COFF_RES_ALIGN); + + U32 data_version = 0; + cursor += str8_deserial_read_struct(header_data, cursor, &data_version); + + COFF_ResourceMemoryFlags memory_flags = 0; + cursor += str8_deserial_read_struct(header_data, cursor, &memory_flags); + + U16 language_id = 0; + cursor += str8_deserial_read_struct(header_data, cursor, &language_id); + + U32 version = 0; + cursor += str8_deserial_read_struct(header_data, cursor, &version); + + U32 characteristics = 0; + cursor += str8_deserial_read_struct(header_data, cursor, &characteristics); + + String8 data; + cursor += str8_deserial_read_block(raw_res, off + prefix.header_size, prefix.data_size, &data); + + // was resource parsed? + Assert(cursor >= prefix.data_size + prefix.header_size); + + // fill out result + res_out->type = coff_convert_resource_id(arena, &type_16); + res_out->name = coff_convert_resource_id(arena, &name_16); + res_out->language_id = language_id; + res_out->data_version = data_version; + res_out->version = version; + res_out->memory_flags = memory_flags; + res_out->data = data; + + U64 resource_size = AlignPow2(prefix.data_size + prefix.header_size, COFF_RES_ALIGN); + return resource_size; +} + +internal COFF_ResourceList +coff_resource_list_from_data(Arena *arena, String8 data) +{ + COFF_ResourceList list; MemoryZeroStruct(&list); + for (U64 cursor = 0, stride; cursor < data.size; cursor += stride) { + COFF_ResourceNode *node = push_array(arena, COFF_ResourceNode, 1); + stride = coff_read_resource(data, cursor, arena, &node->data); + list.count += 1; + SLLQueuePush(list.first, list.last, node); + } + return list; +} + +//////////////////////////////// + +internal COFF_DataType +coff_data_type_from_data(String8 data) +{ + B32 is_big_obj = coff_is_big_obj(data); + if (is_big_obj) { + return COFF_DataType_BIG_OBJ; + } + + B32 is_import = coff_is_import(data); + if (is_import) { + return COFF_DataType_IMPORT; + } + + return COFF_DataType_OBJ; +} + +internal B32 +coff_is_import(String8 data) +{ + B32 is_import = 0; + if (data.size >= sizeof(U16)*2) { + U16 *sig1 = (U16*)data.str; + U16 *sig2 = sig1 + 1; + is_import = *sig1 == COFF_MachineType_UNKNOWN && *sig2 == 0xffff; + } + return is_import; +} + +internal B32 +coff_is_archive(String8 data) +{ + U64 sig = 0; + str8_deserial_read_struct(data, 0, &sig); + B32 is_archive = sig == COFF_ARCHIVE_SIG; + return is_archive; +} + +internal B32 +coff_is_thin_archive(String8 data) +{ + U64 sig = 0; + str8_deserial_read_struct(data, 0, &sig); + B32 is_archive = sig == COFF_THIN_ARCHIVE_SIG; + return is_archive; +} + +internal U64 +coff_read_archive_member_header(String8 data, U64 offset, COFF_ArchiveMemberHeader *header_out) +{ +#define NAME_SIZE 16 +#define DATE_SIZE 12 +#define USER_ID_SIZE 6 +#define GROUP_ID_SIZE 6 +#define MODE_SIZE 8 +#define SIZE_SIZE 10 +#define TOTAL_SIZE (NAME_SIZE + DATE_SIZE + USER_ID_SIZE + GROUP_ID_SIZE + MODE_SIZE + SIZE_SIZE) + + if (str8_deserial_get_raw_ptr(data, offset, TOTAL_SIZE) == NULL) { + return 0; + } + + U64 read_offset = offset; + + U8 *name = (U8 *)str8_deserial_get_raw_ptr(data, read_offset, NAME_SIZE); + read_offset += NAME_SIZE; + + U8 *date = (U8 *)str8_deserial_get_raw_ptr(data, read_offset, DATE_SIZE); + read_offset += DATE_SIZE; + + U8 *user_id = (U8 *)str8_deserial_get_raw_ptr(data, read_offset, USER_ID_SIZE); + read_offset += USER_ID_SIZE; + + U8 *group_id = (U8 *)str8_deserial_get_raw_ptr(data, read_offset, GROUP_ID_SIZE); + read_offset += GROUP_ID_SIZE; + + U8 *mode = (U8 *)str8_deserial_get_raw_ptr(data, read_offset, MODE_SIZE); + read_offset += MODE_SIZE; + + U8 *size = (U8 *)str8_deserial_get_raw_ptr(data, read_offset, SIZE_SIZE); + read_offset += SIZE_SIZE; + + U8 end[] = { 0, 0 }; + read_offset += str8_deserial_read_array(data, read_offset, &end[0], ArrayCount(end)); + + U64 i; + for (i = 0; i < NAME_SIZE; ++i) { + if (name[i] == ' ') { + break; + } + } + header_out->name = str8(name, i); + header_out->date = (U32)s64_from_str8(str8(date, DATE_SIZE), 10); + header_out->user_id = (U32)s64_from_str8(str8(user_id, USER_ID_SIZE), 10); + header_out->group_id = (U32)s64_from_str8(str8(group_id, GROUP_ID_SIZE), 10); + header_out->mode = str8(mode, MODE_SIZE); + for (i = 0; i < SIZE_SIZE; ++i) { + if (size[i] == ' ') { + break; + } + } + header_out->size = (U32)s64_from_str8(str8(size, i), 10); + header_out->is_end_correct = (end[0] == '`' && end[1] == '\n'); + + U64 result = (read_offset - offset); + return result; + +#undef NAME_SIZE +#undef DATE_SIZE +#undef USER_ID_SIZE +#undef GROUP_ID_SIZE +#undef MODE_SIZE +#undef SIZE_SIZE +#undef TOTAL_SIZE +} + +internal COFF_ArchiveMember +coff_read_archive_member(String8 data, U64 offset) +{ + COFF_ArchiveMember member; MemoryZeroStruct(&member); + coff_archive_member_iter_next(data, &offset, &member); + return member; +} + +internal COFF_ArchiveMember +coff_archive_member_from_data(String8 data) +{ + return coff_read_archive_member(data, 0); +} + +internal U64 +coff_read_archive_import(String8 data, U64 offset, COFF_ImportHeader *header_out) +{ + U64 cursor = offset; + + cursor += str8_deserial_read_struct(data, cursor, &header_out->sig1); + cursor += str8_deserial_read_struct(data, cursor, &header_out->sig2); + cursor += str8_deserial_read_struct(data, cursor, &header_out->version); + cursor += str8_deserial_read_struct(data, cursor, &header_out->machine); + cursor += str8_deserial_read_struct(data, cursor, &header_out->time_stamp); + cursor += str8_deserial_read_struct(data, cursor, &header_out->data_size); + cursor += str8_deserial_read_struct(data, cursor, &header_out->hint); + + U16 flags = 0; + cursor += str8_deserial_read_struct(data, cursor, &flags); + header_out->type = COFF_IMPORT_HEADER_GET_TYPE(flags); + header_out->name_type = COFF_IMPORT_HEADER_GET_NAME_TYPE(flags); + + header_out->func_name = str8(0,0); + cursor += str8_deserial_read_cstr(data, cursor, &header_out->func_name); + + header_out->dll_name = str8(0,0); + cursor += str8_deserial_read_cstr(data, cursor, &header_out->dll_name); + + Assert(header_out->func_name.size + header_out->dll_name.size + /* nulls */ 2 == header_out->data_size); + + U64 read_size = cursor - offset; + return read_size; +} + +internal COFF_ImportHeader +coff_archive_import_from_data(String8 data) +{ + COFF_ImportHeader header; MemoryZeroStruct(&header); + coff_read_archive_import(data, 0, &header); + return header; +} + +internal String8 +coff_read_archive_long_name(String8 long_names, String8 name) +{ + String8 result = name; + if (name.size > 0 && name.str[0] == '/') { + String8 offset_str = str8(name.str + 1, name.size - 1); + U64 offset = u64_from_str8(offset_str, 10); + if (offset < long_names.size) { + U8 *ptr = long_names.str + offset; + U8 *opl = long_names.str + long_names.size; + for (; ptr < opl; ++ptr) { + if (*ptr == '\0' || *ptr == '\n') { + break; + } + } + result = str8_range(long_names.str + offset, ptr); + } + } + return result; +} + +internal U64 +coff_archive_member_iter_init(String8 data) +{ + U64 cursor = 0; + U64 sig = 0; + cursor += str8_deserial_read_struct(data, cursor, &sig); + if (sig != COFF_ARCHIVE_SIG) { + cursor = data.size; + } + return cursor; +} + +internal B32 +coff_archive_member_iter_next(String8 data, U64 *offset, COFF_ArchiveMember *member_out) +{ + B32 is_parsed; + + COFF_ArchiveMemberHeader header; + U64 header_read_size = coff_read_archive_member_header(data, *offset, &header); + + if (header_read_size && header.is_end_correct) { + Rng1U64 data_range = rng_1u64(*offset + header_read_size, *offset + header_read_size + header.size); + + member_out->header = header; + member_out->offset = *offset; + member_out->data = str8_substr(data, data_range); + + *offset += header_read_size; + *offset += member_out->header.size; + *offset = AlignPow2(*offset, COFF_ARCHIVE_ALIGN); + + is_parsed = 1; + } else { + MemoryZeroStruct(&member_out->header); + member_out->offset = max_U64; + member_out->data = str8(0,0); + + is_parsed = 0; + } + + return is_parsed; +} + +internal B32 +coff_get_first_archive_member(COFF_ArchiveMember *member, COFF_ArchiveFirstMember *first_out) +{ + B32 is_header = str8_match(member->header.name, str8_lit("/"), 0); + if (is_header) { + U64 cursor = 0; + + U32 symbol_count = 0; + cursor += str8_deserial_read_struct(member->data, cursor, &symbol_count); + +#if ARCH_LITTLE_ENDIAN + symbol_count = bswap_u32(symbol_count); +#endif + + Rng1U64 member_offsets_range = rng_1u64(cursor, cursor + symbol_count * sizeof(U32)); + cursor += dim_1u64(member_offsets_range); + + Rng1U64 string_table_range = rng_1u64(cursor, member->data.size); + cursor += dim_1u64(string_table_range); + + first_out->symbol_count = symbol_count; + first_out->member_offsets = str8_substr(member->data, member_offsets_range); + first_out->string_table = str8_substr(member->data, string_table_range); + } + return is_header; +} + +internal B32 +coff_get_second_archive_member(COFF_ArchiveMember *member, COFF_ArchiveSecondMember *second_out) +{ + B32 is_header = str8_match(member->header.name, str8_lit("/"), 0); + if (is_header) { + U64 cursor = 0; + + U32 member_count = 0; + cursor += str8_deserial_read_struct(member->data, cursor, &member_count); + + Rng1U64 member_offsets_range = rng_1u64(cursor, cursor + member_count * sizeof(U32)); + cursor += dim_1u64(member_offsets_range); + + U32 symbol_count = 0; + cursor += str8_deserial_read_struct(member->data, cursor, &symbol_count); + + Rng1U64 symbol_indices_range = rng_1u64(cursor, cursor + symbol_count * sizeof(U16)); + cursor += dim_1u64(symbol_indices_range); + + Rng1U64 string_table_range = rng_1u64(cursor, member->data.size); + + second_out->member_count = member_count; + second_out->symbol_count = symbol_count; + second_out->member_offsets = str8_substr(member->data, member_offsets_range); + second_out->symbol_indices = str8_substr(member->data, symbol_indices_range); + second_out->string_table = str8_substr(member->data, string_table_range); + } + return is_header; +} + +internal void +coff_archive_member_list_push_node(COFF_ArchiveMemberList *list, COFF_ArchiveMemberNode *node) +{ + SLLQueuePush(list->first, list->last, node); + list->count += 1; +} + +internal COFF_ArchiveParse +coff_archive_parse_from_member_list(COFF_ArchiveMemberList member_list) +{ + COFF_ArchiveMember first_header; MemoryZeroStruct(&first_header); + COFF_ArchiveMember second_header; MemoryZeroStruct(&second_header); + COFF_ArchiveMember long_names_member; MemoryZeroStruct(&long_names_member); + + if (member_list.count) { + if (str8_match(member_list.first->data.header.name, str8_lit("/"), 0)) { + first_header = member_list.first->data; + SLLQueuePop(member_list.first, member_list.last); + member_list.count -= 1; + + if (member_list.count && str8_match(member_list.first->data.header.name, str8_lit("/"), 0)) { + second_header = member_list.first->data; + SLLQueuePop(member_list.first, member_list.last); + member_list.count -= 1; + } + + if (member_list.count && str8_match(member_list.first->data.header.name, str8_lit("//"), 0)) { + long_names_member = member_list.first->data; + SLLQueuePop(member_list.first, member_list.last); + member_list.count -= 1; + } + } + } + + COFF_ArchiveFirstMember first_member; MemoryZeroStruct(&first_member); + coff_get_first_archive_member(&first_header, &first_member); + + COFF_ArchiveSecondMember second_member; MemoryZeroStruct(&second_member); + coff_get_second_archive_member(&second_header, &second_member); + + COFF_ArchiveParse parse; MemoryZeroStruct(&parse); + parse.first_member = first_member; + parse.second_member = second_member; + parse.long_names = long_names_member.data; + + return parse; +} + +internal COFF_ArchiveParse +coff_archive_from_data(Arena *arena, String8 data) +{ + COFF_ArchiveMemberList list; MemoryZeroStruct(&list); + COFF_ArchiveMemberNode node_arr[3]; MemoryZeroStruct(&node_arr[0]); + U64 cursor = coff_archive_member_iter_init(data); + for (U64 i = 0; i < ArrayCount(node_arr); i += 1) { + COFF_ArchiveMemberNode *node = &node_arr[i]; + if (!coff_archive_member_iter_next(data, &cursor, &node->data)) { + break; + } + coff_archive_member_list_push_node(&list, node); + } + return coff_archive_parse_from_member_list(list); +} + +internal U64 +coff_thin_archive_member_iter_init(String8 data) +{ + U64 cursor = 0; + U64 sig = 0; + cursor += str8_deserial_read_struct(data, cursor, &sig); + if (sig != COFF_THIN_ARCHIVE_SIG) { + cursor = data.size; + } + return cursor; +} + +internal B32 +coff_thin_archive_member_iter_next(String8 data, U64 *offset, COFF_ArchiveMember *member_out) +{ + B32 is_parsed = 0; + + U64 header_size = coff_read_archive_member_header(data, *offset, &member_out->header); + if (header_size) { + member_out->offset = *offset; + *offset += header_size; + if (str8_match(member_out->header.name, str8_lit("/"), 0) || str8_match(member_out->header.name, str8_lit("//"), 0)) { + Rng1U64 data_range = rng_1u64(*offset, *offset + member_out->header.size); + member_out->data = str8_substr(data, data_range); + *offset += member_out->header.size; + } else { + // size field in non-header members means size of stand-alone obj + member_out->data = str8(0,0); + } + *offset = AlignPow2(*offset, COFF_ARCHIVE_ALIGN); + is_parsed = 1; + } + + return is_parsed; +} + +internal COFF_ArchiveParse +coff_thin_archive_from_data(Arena *arena, String8 data) +{ + COFF_ArchiveMemberList list; MemoryZeroStruct(&list); + COFF_ArchiveMemberNode node_arr[3]; MemoryZeroStruct(&node_arr[0]); + U64 cursor = coff_thin_archive_member_iter_init(data); + for (U64 i = 0; i < ArrayCount(node_arr); i += 1) { + COFF_ArchiveMemberNode *node = &node_arr[i]; + if (!coff_thin_archive_member_iter_next(data, &cursor, &node->data)) { + break; + } + coff_archive_member_list_push_node(&list, node); + } + return coff_archive_parse_from_member_list(list); +} + +internal COFF_ArchiveType +coff_archive_type_from_data(String8 data) +{ + if (coff_is_archive(data)) { + return COFF_Archive_Regular; + } else if (coff_is_thin_archive(data)) { + return COFF_Archive_Thin; + } + return COFF_Archive_Null; +} + +internal COFF_ArchiveParse +coff_archive_parse_from_data(Arena *arena, String8 data) +{ + COFF_ArchiveType type = coff_archive_type_from_data(data); + switch (type) { + case COFF_Archive_Null: break; + case COFF_Archive_Regular: return coff_archive_from_data(arena, data); + case COFF_Archive_Thin: return coff_thin_archive_from_data(arena, data); + } + COFF_ArchiveParse null_parse; MemoryZeroStruct(&null_parse); + return null_parse; +} + +//////////////////////////////// + +internal String8 +coff_string_from_comdat_select_type(COFF_ComdatSelectType select) +{ + String8 result = str8(0,0); + switch (select) { + case COFF_ComdatSelectType_NULL: result = str8_lit("NULL"); break; + case COFF_ComdatSelectType_NODUPLICATES: result = str8_lit("NODUPLICATES"); break; + case COFF_ComdatSelectType_ANY: result = str8_lit("ANY"); break; + case COFF_ComdatSelectType_SAME_SIZE: result = str8_lit("SAME_SIZE"); break; + case COFF_ComdatSelectType_EXACT_MATCH: result = str8_lit("EXACT_MATCH"); break; + case COFF_ComdatSelectType_ASSOCIATIVE: result = str8_lit("ASSOCIATIVE"); break; + case COFF_ComdatSelectType_LARGEST: result = str8_lit("LARGEST"); break; + } + return result; +} + +internal String8 +coff_string_from_machine_type(COFF_MachineType machine) +{ + String8 result = str8(0,0); + switch (machine) { + case COFF_MachineType_UNKNOWN: result = str8_lit("UNKNOWN"); break; + case COFF_MachineType_X86: result = str8_lit("X86"); break; + case COFF_MachineType_X64: result = str8_lit("X64"); break; + case COFF_MachineType_ARM33: result = str8_lit("ARM33"); break; + case COFF_MachineType_ARM: result = str8_lit("ARM"); break; + case COFF_MachineType_ARM64: result = str8_lit("ARM64"); break; + case COFF_MachineType_ARMNT: result = str8_lit("ARMNT"); break; + case COFF_MachineType_EBC: result = str8_lit("EBC"); break; + case COFF_MachineType_IA64: result = str8_lit("IA64"); break; + case COFF_MachineType_M32R: result = str8_lit("M32R"); break; + case COFF_MachineType_MIPS16: result = str8_lit("MIPS16"); break; + case COFF_MachineType_MIPSFPU: result = str8_lit("MIPSFPU"); break; + case COFF_MachineType_MIPSFPU16: result = str8_lit("MIPSFPU16"); break; + case COFF_MachineType_POWERPC: result = str8_lit("POWERPC"); break; + case COFF_MachineType_POWERPCFP: result = str8_lit("POWERPCFP"); break; + case COFF_MachineType_R4000: result = str8_lit("R4000"); break; + case COFF_MachineType_RISCV32: result = str8_lit("RISCV32"); break; + case COFF_MachineType_RISCV64: result = str8_lit("RISCV64"); break; + case COFF_MachineType_RISCV128: result = str8_lit("RISCV128"); break; + case COFF_MachineType_SH3: result = str8_lit("SH3"); break; + case COFF_MachineType_SH3DSP: result = str8_lit("SH3DSP"); break; + case COFF_MachineType_SH4: result = str8_lit("SH4"); break; + case COFF_MachineType_SH5: result = str8_lit("SH5"); break; + case COFF_MachineType_THUMB: result = str8_lit("THUMB"); break; + case COFF_MachineType_WCEMIPSV2: result = str8_lit("WCEMIPSV2"); break; + } + return result; +} + +internal String8 +coff_string_from_section_flags(Arena *arena, COFF_SectionFlags flags) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List list = {0}; + + if (flags & COFF_SectionFlag_TYPE_NO_PAD) { + str8_list_pushf(scratch.arena, &list, "TYPE_NO_PAD"); + } + if (flags & COFF_SectionFlag_CNT_CODE) { + str8_list_pushf(scratch.arena, &list, "CNT_CODE"); + } + if (flags & COFF_SectionFlag_CNT_INITIALIZED_DATA) { + str8_list_pushf(scratch.arena, &list, "CNT_INITIALIZED_DATA"); + } + if (flags & COFF_SectionFlag_CNT_UNINITIALIZED_DATA) { + str8_list_pushf(scratch.arena, &list, "CNT_UNINITIALIZED_DATA"); + } + if (flags & COFF_SectionFlag_LNK_OTHER) { + str8_list_pushf(scratch.arena, &list, "LNK_OTHER"); + } + if (flags & COFF_SectionFlag_LNK_INFO) { + str8_list_pushf(scratch.arena, &list, "LNK_INFO"); + } + if (flags & COFF_SectionFlag_LNK_COMDAT) { + str8_list_pushf(scratch.arena, &list, "LNK_COMDAT"); + } + if (flags & COFF_SectionFlag_GPREL) { + str8_list_pushf(scratch.arena, &list, "GPREL"); + } + if (flags & COFF_SectionFlag_MEM_16BIT) { + str8_list_pushf(scratch.arena, &list, "MEM_16BIT"); + } + if (flags & COFF_SectionFlag_MEM_LOCKED) { + str8_list_pushf(scratch.arena, &list, "MEM_LOCKED"); + } + if (flags & COFF_SectionFlag_MEM_PRELOAD) { + str8_list_pushf(scratch.arena, &list, "MEM_PRELOAD"); + } + if (flags & COFF_SectionFlag_LNK_NRELOC_OVFL) { + str8_list_pushf(scratch.arena, &list, "LNK_NRELOC_OVFL"); + } + if (flags & COFF_SectionFlag_MEM_DISCARDABLE) { + str8_list_pushf(scratch.arena, &list, "MEM_DISCARDABLE"); + } + if (flags & COFF_SectionFlag_MEM_NOT_CACHED) { + str8_list_pushf(scratch.arena, &list, "MEM_NOT_CACHED"); + } + if (flags & COFF_SectionFlag_MEM_NOT_PAGED) { + str8_list_pushf(scratch.arena, &list, "MEM_NOT_PAGED"); + } + if (flags & COFF_SectionFlag_MEM_SHARED) { + str8_list_pushf(scratch.arena, &list, "MEM_SHARED"); + } + if (flags & COFF_SectionFlag_MEM_EXECUTE) { + str8_list_pushf(scratch.arena, &list, "MEM_EXECUTE"); + } + if (flags & COFF_SectionFlag_MEM_READ) { + str8_list_pushf(scratch.arena, &list, "MEM_READ"); + } + if (flags & COFF_SectionFlag_MEM_WRITE) { + str8_list_pushf(scratch.arena, &list, "MEM_WRITE"); + } + + U64 align = COFF_SectionFlags_Extract_ALIGN(flags); + if (align) { + str8_list_pushf(scratch.arena, &list, "ALIGN=%u", align); + } + + StringJoin join = {0}; + join.sep = str8_lit(", "); + String8 result = str8_list_join(arena, &list, &join); + + scratch_end(scratch); + return result; +} + diff --git a/src/ctrl/ctrl_core.c b/src/ctrl/ctrl_core.c index 631821b7..bdb95643 100644 --- a/src/ctrl/ctrl_core.c +++ b/src/ctrl/ctrl_core.c @@ -1,5296 +1,5296 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Generated Code - -#include "generated/ctrl.meta.c" - -//////////////////////////////// -//~ rjf: Basic Type Functions - -internal U64 -ctrl_hash_from_string(String8 string) -{ - U64 result = 5381; - for(U64 i = 0; i < string.size; i += 1) - { - result = ((result << 5) + result) + string.str[i]; - } - return result; -} - -internal U64 -ctrl_hash_from_machine_id_handle(CTRL_MachineID machine_id, DMN_Handle handle) -{ - U64 buf[] = {machine_id, handle.u64[0]}; - U64 hash = ctrl_hash_from_string(str8((U8 *)buf, sizeof(buf))); - return hash; -} - -internal CTRL_EventCause -ctrl_event_cause_from_dmn_event_kind(DMN_EventKind event_kind) -{ - CTRL_EventCause cause = CTRL_EventCause_Null; - switch(event_kind) - { - default:{}break; - case DMN_EventKind_Error: {cause = CTRL_EventCause_Error;}break; - case DMN_EventKind_Exception:{cause = CTRL_EventCause_InterruptedByException;}break; - case DMN_EventKind_Trap: {cause = CTRL_EventCause_InterruptedByTrap;}break; - case DMN_EventKind_Halt: {cause = CTRL_EventCause_InterruptedByHalt;}break; - } - return cause; -} - -internal String8 -ctrl_string_from_event_kind(CTRL_EventKind kind) -{ - String8 result = {0}; - switch(kind) - { - default:{}break; - case CTRL_EventKind_Null: { result = str8_lit("Null");}break; - case CTRL_EventKind_Error: { result = str8_lit("Error");}break; - case CTRL_EventKind_Started: { result = str8_lit("Started");}break; - case CTRL_EventKind_Stopped: { result = str8_lit("Stopped");}break; - case CTRL_EventKind_NewProc: { result = str8_lit("NewProc");}break; - case CTRL_EventKind_NewThread: { result = str8_lit("NewThread");}break; - case CTRL_EventKind_NewModule: { result = str8_lit("NewModule");}break; - case CTRL_EventKind_EndProc: { result = str8_lit("EndProc");}break; - case CTRL_EventKind_EndThread: { result = str8_lit("EndThread");}break; - case CTRL_EventKind_EndModule: { result = str8_lit("EndModule");}break; - case CTRL_EventKind_ModuleDebugInfoPathChange: { result = str8_lit("ModuleDebugInfoPathChange");}break; - case CTRL_EventKind_DebugString: { result = str8_lit("DebugString");}break; - case CTRL_EventKind_ThreadName: { result = str8_lit("ThreadName");}break; - case CTRL_EventKind_MemReserve: { result = str8_lit("MemReserve");}break; - case CTRL_EventKind_MemCommit: { result = str8_lit("MemCommit");}break; - case CTRL_EventKind_MemDecommit: { result = str8_lit("MemDecommit");}break; - case CTRL_EventKind_MemRelease: { result = str8_lit("MemRelease");}break; - } - return result; -} - -internal String8 -ctrl_string_from_msg_kind(CTRL_MsgKind kind) -{ - String8 result = {0}; - switch(kind) - { - default:{}break; - case CTRL_MsgKind_Launch: {result = str8_lit("Launch");}break; - case CTRL_MsgKind_Attach: {result = str8_lit("Attach");}break; - case CTRL_MsgKind_Kill: {result = str8_lit("Kill");}break; - case CTRL_MsgKind_Detach: {result = str8_lit("Detach");}break; - case CTRL_MsgKind_Run: {result = str8_lit("Run");}break; - case CTRL_MsgKind_SingleStep: {result = str8_lit("SingleStep");}break; - case CTRL_MsgKind_SetUserEntryPoints: {result = str8_lit("SetUserEntryPoints");}break; - case CTRL_MsgKind_SetModuleDebugInfoPath: {result = str8_lit("SetModuleDebugInfoPath");}break; - } - return result; -} - -//////////////////////////////// -//~ rjf: Machine/Handle Pair Type Functions - -internal void -ctrl_machine_id_handle_pair_list_push(Arena *arena, CTRL_MachineIDHandlePairList *list, CTRL_MachineIDHandlePair *pair) -{ - CTRL_MachineIDHandlePairNode *n = push_array(arena, CTRL_MachineIDHandlePairNode, 1); - MemoryCopyStruct(&n->v, pair); - SLLQueuePush(list->first, list->last, n); - list->count += 1; -} - -internal CTRL_MachineIDHandlePairList -ctrl_machine_id_handle_pair_list_copy(Arena *arena, CTRL_MachineIDHandlePairList *src) -{ - CTRL_MachineIDHandlePairList dst = {0}; - for(CTRL_MachineIDHandlePairNode *n = src->first; n != 0; n = n->next) - { - ctrl_machine_id_handle_pair_list_push(arena, &dst, &n->v); - } - return dst; -} - -//////////////////////////////// -//~ rjf: Trap Type Functions - -internal void -ctrl_trap_list_push(Arena *arena, CTRL_TrapList *list, CTRL_Trap *trap) -{ - CTRL_TrapNode *node = push_array(arena, CTRL_TrapNode, 1); - MemoryCopyStruct(&node->v, trap); - SLLQueuePush(list->first, list->last, node); - list->count += 1; -} - -internal CTRL_TrapList -ctrl_trap_list_copy(Arena *arena, CTRL_TrapList *src) -{ - CTRL_TrapList dst = {0}; - for(CTRL_TrapNode *src_n = src->first; src_n != 0; src_n = src_n->next) - { - ctrl_trap_list_push(arena, &dst, &src_n->v); - } - return dst; -} - -//////////////////////////////// -//~ rjf: User Breakpoint Type Functions - -internal void -ctrl_user_breakpoint_list_push(Arena *arena, CTRL_UserBreakpointList *list, CTRL_UserBreakpoint *bp) -{ - CTRL_UserBreakpointNode *n = push_array(arena, CTRL_UserBreakpointNode, 1); - MemoryCopyStruct(&n->v, bp); - SLLQueuePush(list->first, list->last, n); - list->count += 1; -} - -internal CTRL_UserBreakpointList -ctrl_user_breakpoint_list_copy(Arena *arena, CTRL_UserBreakpointList *src) -{ - CTRL_UserBreakpointList dst = {0}; - for(CTRL_UserBreakpointNode *src_n = src->first; src_n != 0; src_n = src_n->next) - { - CTRL_UserBreakpoint dst_bp = zero_struct; - MemoryCopyStruct(&dst_bp, &src_n->v); - dst_bp.string = push_str8_copy(arena, src_n->v.string); - dst_bp.condition = push_str8_copy(arena, src_n->v.condition); - ctrl_user_breakpoint_list_push(arena, &dst, &dst_bp); - } - return dst; -} - -//////////////////////////////// -//~ rjf: Message Type Functions - -//- rjf: deep copying - -internal void -ctrl_msg_deep_copy(Arena *arena, CTRL_Msg *dst, CTRL_Msg *src) -{ - MemoryCopyStruct(dst, src); - dst->path = push_str8_copy(arena, src->path); - dst->entry_points = str8_list_copy(arena, &src->entry_points); - dst->cmd_line_string_list = str8_list_copy(arena, &src->cmd_line_string_list); - dst->env_string_list = str8_list_copy(arena, &src->env_string_list); - dst->traps = ctrl_trap_list_copy(arena, &src->traps); - dst->user_bps = ctrl_user_breakpoint_list_copy(arena, &src->user_bps); - dst->freeze_state_threads = ctrl_machine_id_handle_pair_list_copy(arena, &src->freeze_state_threads); -} - -//- rjf: list building - -internal CTRL_Msg * -ctrl_msg_list_push(Arena *arena, CTRL_MsgList *list) -{ - CTRL_MsgNode *n = push_array(arena, CTRL_MsgNode, 1); - SLLQueuePush(list->first, list->last, n); - list->count += 1; - CTRL_Msg *msg = &n->v; - return msg; -} - -//- rjf: serialization - -internal String8 -ctrl_serialized_string_from_msg_list(Arena *arena, CTRL_MsgList *msgs) -{ - Temp scratch = scratch_begin(&arena, 1); - String8List msgs_srlzed = {0}; - str8_serial_begin(scratch.arena, &msgs_srlzed); - { - // rjf: write message count - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msgs->count); - - // rjf: write all message data - for(CTRL_MsgNode *msg_n = msgs->first; msg_n != 0; msg_n = msg_n->next) - { - CTRL_Msg *msg = &msg_n->v; - - // rjf: write flat parts - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->kind); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->run_flags); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->msg_id); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->machine_id); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->entity); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->parent); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->entity_id); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->exit_code); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->env_inherit); - str8_serial_push_array (scratch.arena, &msgs_srlzed, &msg->exception_code_filters[0], ArrayCount(msg->exception_code_filters)); - - // rjf: write path string - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->path.size); - str8_serial_push_data(scratch.arena, &msgs_srlzed, msg->path.str, msg->path.size); - - // rjf: write entry point string list - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->entry_points.node_count); - for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) - { - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &n->string.size); - str8_serial_push_data(scratch.arena, &msgs_srlzed, n->string.str, n->string.size); - } - - // rjf: write command line string list - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->cmd_line_string_list.node_count); - for(String8Node *n = msg->cmd_line_string_list.first; n != 0; n = n->next) - { - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &n->string.size); - str8_serial_push_data(scratch.arena, &msgs_srlzed, n->string.str, n->string.size); - } - - // rjf: write environment string list - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->env_string_list.node_count); - for(String8Node *n = msg->env_string_list.first; n != 0; n = n->next) - { - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &n->string.size); - str8_serial_push_data(scratch.arena, &msgs_srlzed, n->string.str, n->string.size); - } - - // rjf: write trap list - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->traps.count); - for(CTRL_TrapNode *n = msg->traps.first; n != 0; n = n->next) - { - CTRL_Trap *trap = &n->v; - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &trap->flags); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &trap->vaddr); - } - - // rjf: write user breakpoint list - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->user_bps.count); - for(CTRL_UserBreakpointNode *n = msg->user_bps.first; n != 0; n = n->next) - { - CTRL_UserBreakpoint *bp = &n->v; - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->kind); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->string.size); - str8_serial_push_data(scratch.arena, &msgs_srlzed, bp->string.str, bp->string.size); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->pt); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->u64); - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->condition.size); - str8_serial_push_data(scratch.arena, &msgs_srlzed, bp->condition.str, bp->condition.size); - } - - // rjf: write freeze state thread list - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->freeze_state_threads.count); - for(CTRL_MachineIDHandlePairNode *n = msg->freeze_state_threads.first; n != 0; n = n->next) - { - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &n->v); - } - - // rjf: write freeze state - str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->freeze_state_is_frozen); - } - } - String8 string = str8_serial_end(arena, &msgs_srlzed); - scratch_end(scratch); - return string; -} - -internal CTRL_MsgList -ctrl_msg_list_from_serialized_string(Arena *arena, String8 string) -{ - CTRL_MsgList msgs = {0}; - { - U64 read_off = 0; - - // rjf: read message count - U64 msg_count = 0; - read_off += str8_deserial_read_struct(string, read_off, &msg_count); - - // rjf: read data for all messages - for(U64 msg_idx = 0; msg_idx < msg_count; msg_idx += 1) - { - // rjf: construct message - CTRL_MsgNode *msg_node = push_array(arena, CTRL_MsgNode, 1); - SLLQueuePush(msgs.first, msgs.last, msg_node); - msgs.count += 1; - CTRL_Msg *msg = &msg_node->v; - - // rjf: read flat data - read_off += str8_deserial_read_struct(string, read_off, &msg->kind); - read_off += str8_deserial_read_struct(string, read_off, &msg->run_flags); - read_off += str8_deserial_read_struct(string, read_off, &msg->msg_id); - read_off += str8_deserial_read_struct(string, read_off, &msg->machine_id); - read_off += str8_deserial_read_struct(string, read_off, &msg->entity); - read_off += str8_deserial_read_struct(string, read_off, &msg->parent); - read_off += str8_deserial_read_struct(string, read_off, &msg->entity_id); - read_off += str8_deserial_read_struct(string, read_off, &msg->exit_code); - read_off += str8_deserial_read_struct(string, read_off, &msg->env_inherit); - read_off += str8_deserial_read_array (string, read_off, &msg->exception_code_filters[0], ArrayCount(msg->exception_code_filters)); - - // rjf: read path string - read_off += str8_deserial_read_struct(string, read_off, &msg->path.size); - msg->path.str = push_array_no_zero(arena, U8, msg->path.size); - read_off += str8_deserial_read(string, read_off, msg->path.str, msg->path.size, 1); - - // rjf: read entry point string list - U64 entry_point_list_string_count = 0; - read_off += str8_deserial_read_struct(string, read_off, &entry_point_list_string_count); - for(U64 idx = 0; idx < entry_point_list_string_count; idx += 1) - { - String8 str = {0}; - read_off += str8_deserial_read_struct(string, read_off, &str.size); - str.str = push_array_no_zero(arena, U8, str.size); - read_off += str8_deserial_read(string, read_off, str.str, str.size, 1); - str8_list_push(arena, &msg->entry_points, str); - } - - // rjf: read command line string list - U64 cmd_line_string_count = 0; - read_off += str8_deserial_read_struct(string, read_off, &cmd_line_string_count); - for(U64 idx = 0; idx < cmd_line_string_count; idx += 1) - { - String8 cmd_line_str = {0}; - read_off += str8_deserial_read_struct(string, read_off, &cmd_line_str.size); - cmd_line_str.str = push_array_no_zero(arena, U8, cmd_line_str.size); - read_off += str8_deserial_read(string, read_off, cmd_line_str.str, cmd_line_str.size, 1); - str8_list_push(arena, &msg->cmd_line_string_list, cmd_line_str); - } - - // rjf: read environment string list - U64 env_string_count = 0; - read_off += str8_deserial_read_struct(string, read_off, &env_string_count); - for(U64 idx = 0; idx < env_string_count; idx += 1) - { - String8 env_str = {0}; - read_off += str8_deserial_read_struct(string, read_off, &env_str.size); - env_str.str = push_array_no_zero(arena, U8, env_str.size); - read_off += str8_deserial_read(string, read_off, env_str.str, env_str.size, 1); - str8_list_push(arena, &msg->env_string_list, env_str); - } - - // rjf: read trap list - U64 trap_count = 0; - read_off += str8_deserial_read_struct(string, read_off, &trap_count); - for(U64 idx = 0; idx < trap_count; idx += 1) - { - CTRL_TrapNode *n = push_array(arena, CTRL_TrapNode, 1); - SLLQueuePush(msg->traps.first, msg->traps.last, n); - msg->traps.count += 1; - CTRL_Trap *trap = &n->v; - read_off += str8_deserial_read_struct(string, read_off, &trap->flags); - read_off += str8_deserial_read_struct(string, read_off, &trap->vaddr); - } - - // rjf: read user breakpoint list - U64 user_bp_count = 0; - read_off += str8_deserial_read_struct(string, read_off, &user_bp_count); - for(U64 idx = 0; idx < user_bp_count; idx += 1) - { - CTRL_UserBreakpointNode *n = push_array(arena, CTRL_UserBreakpointNode, 1); - SLLQueuePush(msg->user_bps.first, msg->user_bps.last, n); - msg->user_bps.count += 1; - CTRL_UserBreakpoint *bp = &n->v; - read_off += str8_deserial_read_struct(string, read_off, &bp->kind); - read_off += str8_deserial_read_struct(string, read_off, &bp->string.size); - bp->string.str = push_array_no_zero(arena, U8, bp->string.size); - read_off += str8_deserial_read(string, read_off, bp->string.str, bp->string.size, 1); - read_off += str8_deserial_read_struct(string, read_off, &bp->pt); - read_off += str8_deserial_read_struct(string, read_off, &bp->u64); - read_off += str8_deserial_read_struct(string, read_off, &bp->condition.size); - bp->condition.str = push_array_no_zero(arena, U8, bp->condition.size); - read_off += str8_deserial_read(string, read_off, bp->condition.str, bp->condition.size, 1); - } - - // rjf: read freeze state thread list - U64 frozen_thread_count = 0; - read_off += str8_deserial_read_struct(string, read_off, &frozen_thread_count); - for(U64 idx = 0; idx < frozen_thread_count; idx += 1) - { - CTRL_MachineIDHandlePair pair = {0}; - read_off += str8_deserial_read_struct(string, read_off, &pair); - ctrl_machine_id_handle_pair_list_push(arena, &msg->freeze_state_threads, &pair); - } - - // rjf: read freeze state - read_off += str8_deserial_read_struct(string, read_off, &msg->freeze_state_is_frozen); - } - } - return msgs; -} - -//////////////////////////////// -//~ rjf: Event Type Functions - -//- rjf: list building - -internal CTRL_Event * -ctrl_event_list_push(Arena *arena, CTRL_EventList *list) -{ - CTRL_EventNode *n = push_array(arena, CTRL_EventNode, 1); - SLLQueuePush(list->first, list->last, n); - list->count += 1; - CTRL_Event *event = &n->v; - return event; -} - -internal void -ctrl_event_list_concat_in_place(CTRL_EventList *dst, CTRL_EventList *to_push) -{ - if(dst->last == 0) - { - MemoryCopyStruct(dst, to_push); - } - else if(to_push->first != 0) - { - dst->last->next = to_push->first; - dst->last = to_push->last; - dst->count += to_push->count; - } - MemoryZeroStruct(to_push); -} - -//- rjf: serialization - -internal String8 -ctrl_serialized_string_from_event(Arena *arena, CTRL_Event *event, U64 max) -{ - Temp scratch = scratch_begin(&arena, 1); - String8List srl = {0}; - str8_serial_begin(scratch.arena, &srl); - { - str8_serial_push_struct(scratch.arena, &srl, &event->kind); - str8_serial_push_struct(scratch.arena, &srl, &event->cause); - str8_serial_push_struct(scratch.arena, &srl, &event->exception_kind); - str8_serial_push_struct(scratch.arena, &srl, &event->msg_id); - str8_serial_push_struct(scratch.arena, &srl, &event->machine_id); - str8_serial_push_struct(scratch.arena, &srl, &event->entity); - str8_serial_push_struct(scratch.arena, &srl, &event->parent); - str8_serial_push_struct(scratch.arena, &srl, &event->arch); - str8_serial_push_struct(scratch.arena, &srl, &event->u64_code); - str8_serial_push_struct(scratch.arena, &srl, &event->entity_id); - str8_serial_push_struct(scratch.arena, &srl, &event->vaddr_rng); - str8_serial_push_struct(scratch.arena, &srl, &event->rip_vaddr); - str8_serial_push_struct(scratch.arena, &srl, &event->stack_base); - str8_serial_push_struct(scratch.arena, &srl, &event->tls_root); - str8_serial_push_struct(scratch.arena, &srl, &event->timestamp); - str8_serial_push_struct(scratch.arena, &srl, &event->exception_code); - String8 string = event->string; - string.size = Min(string.size, max-srl.total_size); - str8_serial_push_struct(scratch.arena, &srl, &string.size); - str8_serial_push_data(scratch.arena, &srl, string.str, string.size); - } - String8 string = str8_serial_end(arena, &srl); - scratch_end(scratch); - return string; -} - -internal CTRL_Event -ctrl_event_from_serialized_string(Arena *arena, String8 string) -{ - CTRL_Event event = zero_struct; - { - U64 read_off = 0; - read_off += str8_deserial_read_struct(string, read_off, &event.kind); - read_off += str8_deserial_read_struct(string, read_off, &event.cause); - read_off += str8_deserial_read_struct(string, read_off, &event.exception_kind); - read_off += str8_deserial_read_struct(string, read_off, &event.msg_id); - read_off += str8_deserial_read_struct(string, read_off, &event.machine_id); - read_off += str8_deserial_read_struct(string, read_off, &event.entity); - read_off += str8_deserial_read_struct(string, read_off, &event.parent); - read_off += str8_deserial_read_struct(string, read_off, &event.arch); - read_off += str8_deserial_read_struct(string, read_off, &event.u64_code); - read_off += str8_deserial_read_struct(string, read_off, &event.entity_id); - read_off += str8_deserial_read_struct(string, read_off, &event.vaddr_rng); - read_off += str8_deserial_read_struct(string, read_off, &event.rip_vaddr); - read_off += str8_deserial_read_struct(string, read_off, &event.stack_base); - read_off += str8_deserial_read_struct(string, read_off, &event.tls_root); - read_off += str8_deserial_read_struct(string, read_off, &event.timestamp); - read_off += str8_deserial_read_struct(string, read_off, &event.exception_code); - read_off += str8_deserial_read_struct(string, read_off, &event.string.size); - event.string.str = push_array_no_zero(arena, U8, event.string.size); - read_off += str8_deserial_read(string, read_off, event.string.str, event.string.size, 1); - } - return event; -} - -//////////////////////////////// -//~ rjf: Entity Type Functions - -//- rjf: cache creation/destruction - -internal CTRL_EntityStore * -ctrl_entity_store_alloc(void) -{ - Arena *arena = arena_alloc(); - CTRL_EntityStore *store = push_array(arena, CTRL_EntityStore, 1); - store->arena = arena; - store->hash_slots_count = 1024; - store->hash_slots = push_array(arena, CTRL_EntityHashSlot, store->hash_slots_count); - CTRL_Entity *root = store->root = ctrl_entity_alloc(store, &ctrl_entity_nil, CTRL_EntityKind_Root, Architecture_Null, 0, dmn_handle_zero(), 0); - CTRL_Entity *local_machine = ctrl_entity_alloc(store, root, CTRL_EntityKind_Machine, architecture_from_context(), CTRL_MachineID_Local, dmn_handle_zero(), 0); - (void)local_machine; - return store; -} - -internal void -ctrl_entity_store_release(CTRL_EntityStore *cache) -{ - arena_release(cache->arena); -} - -//- rjf: string allocation/deletion - -internal U64 -ctrl_name_bucket_idx_from_string_size(U64 size) -{ - U64 size_rounded = u64_up_to_pow2(size+1); - size_rounded = ClampBot((1<<4), size_rounded); - U64 bucket_idx = 0; - switch(size_rounded) - { - case 1<<4: {bucket_idx = 0;}break; - case 1<<5: {bucket_idx = 1;}break; - case 1<<6: {bucket_idx = 2;}break; - case 1<<7: {bucket_idx = 3;}break; - case 1<<8: {bucket_idx = 4;}break; - case 1<<9: {bucket_idx = 5;}break; - case 1<<10:{bucket_idx = 6;}break; - default:{bucket_idx = ArrayCount(((CTRL_EntityStore *)0)->free_string_chunks)-1;}break; - } - return bucket_idx; -} - -internal String8 -ctrl_entity_string_alloc(CTRL_EntityStore *store, String8 string) -{ - if(string.size == 0) {return str8_zero();} - U64 bucket_idx = ctrl_name_bucket_idx_from_string_size(string.size); - CTRL_EntityStringChunkNode *node = store->free_string_chunks[bucket_idx]; - - // rjf: pull from bucket free list - if(node != 0) - { - if(bucket_idx == ArrayCount(store->free_string_chunks)-1) - { - node = 0; - CTRL_EntityStringChunkNode *prev = 0; - for(CTRL_EntityStringChunkNode *n = store->free_string_chunks[bucket_idx]; - n != 0; - prev = n, n = n->next) - { - if(n->size >= string.size+1) - { - if(prev == 0) - { - store->free_string_chunks[bucket_idx] = n->next; - } - else - { - prev->next = n->next; - } - node = n; - break; - } - } - } - else - { - SLLStackPop(store->free_string_chunks[bucket_idx]); - } - } - - // rjf: no found node -> allocate new - if(node == 0) - { - U64 chunk_size = 0; - if(bucket_idx < ArrayCount(store->free_string_chunks)-1) - { - chunk_size = 1<<(bucket_idx+4); - } - else - { - chunk_size = u64_up_to_pow2(string.size); - } - U8 *chunk_memory = push_array(store->arena, U8, chunk_size); - node = (CTRL_EntityStringChunkNode *)chunk_memory; - } - - // rjf: fill string & return - String8 allocated_string = str8((U8 *)node, string.size); - MemoryCopy((U8 *)node, string.str, string.size); - return allocated_string; -} - -internal void -ctrl_entity_string_release(CTRL_EntityStore *store, String8 string) -{ - if(string.size == 0) {return;} - U64 bucket_idx = ctrl_name_bucket_idx_from_string_size(string.size); - CTRL_EntityStringChunkNode *node = (CTRL_EntityStringChunkNode *)string.str; - node->size = u64_up_to_pow2(string.size); - SLLStackPush(store->free_string_chunks[bucket_idx], node); -} - -//- rjf: entity construction/deletion - -internal CTRL_Entity * -ctrl_entity_alloc(CTRL_EntityStore *store, CTRL_Entity *parent, CTRL_EntityKind kind, Architecture arch, CTRL_MachineID machine_id, DMN_Handle handle, U64 id) -{ - CTRL_Entity *entity = &ctrl_entity_nil; - { - // rjf: allocate - entity = store->free; - { - if(entity != 0) - { - SLLStackPop(store->free); - } - else - { - entity = push_array_no_zero(store->arena, CTRL_Entity, 1); - } - MemoryZeroStruct(entity); - } - - // rjf: fill - { - entity->kind = kind; - entity->arch = arch; - entity->machine_id = machine_id; - entity->handle = handle; - entity->id = id; - entity->parent = parent; - entity->next = entity->prev = entity->first = entity->last = &ctrl_entity_nil; - if(parent != &ctrl_entity_nil) - { - DLLPushBack_NPZ(&ctrl_entity_nil, parent->first, parent->last, entity, next, prev); - } - } - - // rjf: insert into hash map - { - U64 hash = ctrl_hash_from_machine_id_handle(machine_id, handle); - U64 slot_idx = hash%store->hash_slots_count; - CTRL_EntityHashSlot *slot = &store->hash_slots[slot_idx]; - CTRL_EntityHashNode *node = 0; - for(CTRL_EntityHashNode *n = slot->first; n != 0; n = n->next) - { - if(n->entity->machine_id == machine_id && dmn_handle_match(n->entity->handle, handle)) - { - node = n; - break; - } - } - if(node == 0) - { - node = store->hash_node_free; - if(node != 0) - { - SLLStackPop(store->hash_node_free); - } - else - { - node = push_array_no_zero(store->arena, CTRL_EntityHashNode, 1); - } - MemoryZeroStruct(node); - DLLPushBack(slot->first, slot->last, node); - node->entity = entity; - } - } - } - return entity; -} - -internal void -ctrl_entity_release(CTRL_EntityStore *store, CTRL_Entity *entity) -{ - // rjf: unhook root - if(entity->parent != &ctrl_entity_nil) - { - DLLRemove_NPZ(&ctrl_entity_nil, entity->parent->first, entity->parent->last, entity, next, prev); - } - - // rjf: walk every entity in this tree, free each - if(entity != &ctrl_entity_nil) - { - Temp scratch = scratch_begin(0, 0); - typedef struct Task Task; - struct Task - { - Task *next; - CTRL_Entity *e; - }; - Task start_task = {0, entity}; - Task *first_task = &start_task; - Task *last_task = &start_task; - for(Task *t = first_task; t != 0; t = t->next) - { - for(CTRL_Entity *child = t->e->first; child != &ctrl_entity_nil; child = child->next) - { - Task *t = push_array(scratch.arena, Task, 1); - t->e = child; - SLLQueuePush(first_task, last_task, t); - } - - // rjf: free entity - SLLStackPush(store->free, t->e); - - // rjf: remove from hash map - { - U64 hash = ctrl_hash_from_machine_id_handle(t->e->machine_id, t->e->handle); - U64 slot_idx = hash%store->hash_slots_count; - CTRL_EntityHashSlot *slot = &store->hash_slots[slot_idx]; - CTRL_EntityHashNode *node = 0; - for(CTRL_EntityHashNode *n = slot->first; n != 0; n = n->next) - { - if(n->entity->machine_id == t->e->machine_id && dmn_handle_match(n->entity->handle, t->e->handle)) - { - DLLRemove(slot->first, slot->last, n); - SLLStackPush(store->hash_node_free, n); - break; - } - } - } - } - scratch_end(scratch); - } -} - -//- rjf: entity equipment - -internal void -ctrl_entity_equip_string(CTRL_EntityStore *store, CTRL_Entity *entity, String8 string) -{ - if(entity->string.size != 0) - { - ctrl_entity_string_release(store, entity->string); - } - entity->string = ctrl_entity_string_alloc(store, string); -} - -//- rjf: entity store lookups - -internal CTRL_Entity * -ctrl_entity_from_machine_id_handle(CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle handle) -{ - CTRL_Entity *entity = &ctrl_entity_nil; - { - U64 hash = ctrl_hash_from_machine_id_handle(machine_id, handle); - U64 slot_idx = hash%store->hash_slots_count; - CTRL_EntityHashSlot *slot = &store->hash_slots[slot_idx]; - CTRL_EntityHashNode *node = 0; - for(CTRL_EntityHashNode *n = slot->first; n != 0; n = n->next) - { - if(n->entity->machine_id == machine_id && dmn_handle_match(n->entity->handle, handle)) - { - entity = n->entity; - break; - } - } - } - return entity; -} - -internal CTRL_Entity * -ctrl_entity_child_from_kind(CTRL_Entity *parent, CTRL_EntityKind kind) -{ - CTRL_Entity *result = &ctrl_entity_nil; - for(CTRL_Entity *child = parent->first; - child != &ctrl_entity_nil; - child = child->next) - { - if(child->kind == kind) - { - result = child; - break; - } - } - return result; -} - -//- rjf: applying events to entity caches - -internal void -ctrl_entity_store_apply_events(CTRL_EntityStore *store, CTRL_EventList *list) -{ - //- rjf: scan events & construct entities - for(CTRL_EventNode *n = list->first; n != 0; n = n->next) - { - CTRL_Event *event = &n->v; - switch(event->kind) - { - default:{}break; - - //- rjf: processes - case CTRL_EventKind_NewProc: - { - CTRL_Entity *machine = ctrl_entity_from_machine_id_handle(store, event->machine_id, dmn_handle_zero()); - CTRL_Entity *process = ctrl_entity_alloc(store, machine, CTRL_EntityKind_Process, event->arch, event->machine_id, event->entity, (U64)event->entity_id); - }break; - case CTRL_EventKind_EndProc: - { - CTRL_Entity *process = ctrl_entity_from_machine_id_handle(store, event->machine_id, event->entity); - ctrl_entity_release(store, process); - for(CTRL_Entity *entry = store->root->first, *next = &ctrl_entity_nil; - entry != &ctrl_entity_nil; - entry = next) - { - next = entry->next; - if(entry->kind == CTRL_EntityKind_EntryPoint && entry->id == process->id) - { - ctrl_entity_release(store, entry); - } - } - }break; - - //- rjf: threads - case CTRL_EventKind_NewThread: - { - CTRL_Entity *process = ctrl_entity_from_machine_id_handle(store, event->machine_id, event->parent); - CTRL_Entity *thread = ctrl_entity_alloc(store, process, CTRL_EntityKind_Thread, event->arch, event->machine_id, event->entity, (U64)event->entity_id); - }break; - case CTRL_EventKind_EndThread: - { - CTRL_Entity *thread = ctrl_entity_from_machine_id_handle(store, event->machine_id, event->entity); - ctrl_entity_release(store, thread); - }break; - case CTRL_EventKind_ThreadName: - { - CTRL_Entity *thread = ctrl_entity_from_machine_id_handle(store, event->machine_id, event->entity); - ctrl_entity_equip_string(store, thread, event->string); - }break; - - //- rjf: modules - case CTRL_EventKind_NewModule: - { - Temp scratch = scratch_begin(0, 0); - CTRL_Entity *process = ctrl_entity_from_machine_id_handle(store, event->machine_id, event->parent); - CTRL_Entity *module = ctrl_entity_alloc(store, process, CTRL_EntityKind_Module, event->arch, event->machine_id, event->entity, event->vaddr_rng.min); - ctrl_entity_equip_string(store, module, event->string); - module->timestamp = event->timestamp; - module->vaddr_range = event->vaddr_rng; - scratch_end(scratch); - }break; - case CTRL_EventKind_EndModule: - { - CTRL_Entity *module = ctrl_entity_from_machine_id_handle(store, event->machine_id, event->entity); - ctrl_entity_release(store, module); - }break; - case CTRL_EventKind_ModuleDebugInfoPathChange: - { - CTRL_Entity *module = ctrl_entity_from_machine_id_handle(store, event->machine_id, event->entity); - CTRL_Entity *debug_info_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); - if(debug_info_path == &ctrl_entity_nil) - { - debug_info_path = ctrl_entity_alloc(store, module, CTRL_EntityKind_DebugInfoPath, Architecture_Null, 0, dmn_handle_zero(), 0); - } - ctrl_entity_equip_string(store, debug_info_path, event->string); - debug_info_path->timestamp = event->timestamp; - }break; - } - } -} - -//////////////////////////////// -//~ rjf: Main Layer Initialization - -internal void -ctrl_init(void) -{ - Arena *arena = arena_alloc(); - ctrl_state = push_array(arena, CTRL_State, 1); - ctrl_state->arena = arena; - for(Architecture arch = (Architecture)0; arch < Architecture_COUNT; arch = (Architecture)(arch+1)) - { - String8 *reg_names = regs_reg_code_string_table_from_architecture(arch); - U64 reg_count = regs_reg_code_count_from_architecture(arch); - String8 *alias_names = regs_alias_code_string_table_from_architecture(arch); - U64 alias_count = regs_alias_code_count_from_architecture(arch); - ctrl_state->arch_string2reg_tables[arch] = eval_string2num_map_make(ctrl_state->arena, 256); - ctrl_state->arch_string2alias_tables[arch] = eval_string2num_map_make(ctrl_state->arena, 256); - for(U64 idx = 1; idx < reg_count; idx += 1) - { - eval_string2num_map_insert(ctrl_state->arena, &ctrl_state->arch_string2reg_tables[arch], reg_names[idx], idx); - } - for(U64 idx = 1; idx < alias_count; idx += 1) - { - eval_string2num_map_insert(ctrl_state->arena, &ctrl_state->arch_string2alias_tables[arch], alias_names[idx], idx); - } - } - ctrl_state->process_memory_cache.slots_count = 256; - ctrl_state->process_memory_cache.slots = push_array(arena, CTRL_ProcessMemoryCacheSlot, ctrl_state->process_memory_cache.slots_count); - ctrl_state->process_memory_cache.stripes_count = os_logical_core_count(); - ctrl_state->process_memory_cache.stripes = push_array(arena, CTRL_ProcessMemoryCacheStripe, ctrl_state->process_memory_cache.stripes_count); - for(U64 idx = 0; idx < ctrl_state->process_memory_cache.stripes_count; idx += 1) - { - ctrl_state->process_memory_cache.stripes[idx].rw_mutex = os_rw_mutex_alloc(); - ctrl_state->process_memory_cache.stripes[idx].cv = os_condition_variable_alloc(); - } - ctrl_state->thread_reg_cache.slots_count = 1024; - ctrl_state->thread_reg_cache.slots = push_array(arena, CTRL_ThreadRegCacheSlot, ctrl_state->thread_reg_cache.slots_count); - ctrl_state->thread_reg_cache.stripes_count = os_logical_core_count(); - ctrl_state->thread_reg_cache.stripes = push_array(arena, CTRL_ThreadRegCacheStripe, ctrl_state->thread_reg_cache.stripes_count); - for(U64 idx = 0; idx < ctrl_state->thread_reg_cache.stripes_count; idx += 1) - { - ctrl_state->thread_reg_cache.stripes[idx].arena = arena_alloc(); - ctrl_state->thread_reg_cache.stripes[idx].rw_mutex = os_rw_mutex_alloc(); - } - ctrl_state->module_image_info_cache.slots_count = 1024; - ctrl_state->module_image_info_cache.slots = push_array(arena, CTRL_ModuleImageInfoCacheSlot, ctrl_state->module_image_info_cache.slots_count); - ctrl_state->module_image_info_cache.stripes_count = os_logical_core_count(); - ctrl_state->module_image_info_cache.stripes = push_array(arena, CTRL_ModuleImageInfoCacheStripe, ctrl_state->module_image_info_cache.stripes_count); - for(U64 idx = 0; idx < ctrl_state->module_image_info_cache.stripes_count; idx += 1) - { - ctrl_state->module_image_info_cache.stripes[idx].arena = arena_alloc(); - ctrl_state->module_image_info_cache.stripes[idx].rw_mutex = os_rw_mutex_alloc(); - } - ctrl_state->u2c_ring_size = KB(64); - ctrl_state->u2c_ring_base = push_array_no_zero(arena, U8, ctrl_state->u2c_ring_size); - ctrl_state->u2c_ring_mutex = os_mutex_alloc(); - ctrl_state->u2c_ring_cv = os_condition_variable_alloc(); - ctrl_state->c2u_ring_size = KB(64); - ctrl_state->c2u_ring_max_string_size = ctrl_state->c2u_ring_size/2; - ctrl_state->c2u_ring_base = push_array_no_zero(arena, U8, ctrl_state->c2u_ring_size); - ctrl_state->c2u_ring_mutex = os_mutex_alloc(); - ctrl_state->c2u_ring_cv = os_condition_variable_alloc(); - { - Temp scratch = scratch_begin(0, 0); - String8 user_program_data_path = os_string_from_system_path(scratch.arena, OS_SystemPath_UserProgramData); - String8 user_data_folder = push_str8f(scratch.arena, "%S/raddbg/logs", user_program_data_path); - os_make_directory(user_data_folder); - ctrl_state->ctrl_thread_log_path = push_str8f(ctrl_state->arena, "%S/ctrl_thread.raddbg_log", user_data_folder); - os_write_data_to_file_path(ctrl_state->ctrl_thread_log_path, str8_zero()); - scratch_end(scratch); - } - ctrl_state->ctrl_thread_entity_store = ctrl_entity_store_alloc(); - ctrl_state->dmn_event_arena = arena_alloc(); - ctrl_state->user_entry_point_arena = arena_alloc(); - for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)0; k < CTRL_ExceptionCodeKind_COUNT; k = (CTRL_ExceptionCodeKind)(k+1)) - { - if(ctrl_exception_code_kind_default_enable_table[k]) - { - ctrl_state->exception_code_filters[k/64] |= 1ull<<(k%64); - } - } - ctrl_state->u2ms_ring_size = KB(64); - ctrl_state->u2ms_ring_base = push_array(arena, U8, ctrl_state->u2ms_ring_size); - ctrl_state->u2ms_ring_mutex = os_mutex_alloc(); - ctrl_state->u2ms_ring_cv = os_condition_variable_alloc(); - ctrl_state->ctrl_thread_log = log_alloc(); - ctrl_state->ctrl_thread = os_launch_thread(ctrl_thread__entry_point, 0, 0); - ctrl_state->ms_thread_count = Clamp(1, os_logical_core_count()-1, 4); - ctrl_state->ms_threads = push_array(arena, OS_Handle, ctrl_state->ms_thread_count); - for(U64 idx = 0; idx < ctrl_state->ms_thread_count; idx += 1) - { - ctrl_state->ms_threads[idx] = os_launch_thread(ctrl_mem_stream_thread__entry_point, (void *)idx, 0); - } -} - -//////////////////////////////// -//~ rjf: Wakeup Callback Registration - -internal void -ctrl_set_wakeup_hook(CTRL_WakeupFunctionType *wakeup_hook) -{ - ctrl_state->wakeup_hook = wakeup_hook; -} - -//////////////////////////////// -//~ rjf: Process Memory Functions - -//- rjf: process memory cache interaction - -internal U128 -ctrl_calc_hash_store_key_from_process_vaddr_range(CTRL_MachineID machine_id, DMN_Handle process, Rng1U64 range, B32 zero_terminated) -{ - U64 key_hash_data[] = - { - (U64)machine_id, - (U64)process.u64[0], - range.min, - range.max, - (U64)zero_terminated, - }; - U128 key = hs_hash_from_data(str8((U8*)key_hash_data, sizeof(key_hash_data))); - return key; -} - -internal U128 -ctrl_stored_hash_from_process_vaddr_range(CTRL_MachineID machine_id, DMN_Handle process, Rng1U64 range, B32 zero_terminated, B32 *out_is_stale, U64 endt_us) -{ - U128 result = {0}; - U64 size = dim_1u64(range); - U64 pre_mem_gen = dmn_mem_gen(); - if(size != 0) for(;;) - { - CTRL_ProcessMemoryCache *cache = &ctrl_state->process_memory_cache; - U64 process_hash = ctrl_hash_from_string(str8_struct(&process)); - U64 process_slot_idx = process_hash%cache->slots_count; - U64 process_stripe_idx = process_slot_idx%cache->stripes_count; - CTRL_ProcessMemoryCacheSlot *process_slot = &cache->slots[process_slot_idx]; - CTRL_ProcessMemoryCacheStripe *process_stripe = &cache->stripes[process_stripe_idx]; - U64 range_hash = ctrl_hash_from_string(str8_struct(&range)); - - //- rjf: try to read from cache - B32 is_good = 0; - B32 is_stale = 1; - OS_MutexScopeR(process_stripe->rw_mutex) - { - for(CTRL_ProcessMemoryCacheNode *n = process_slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->process, process)) - { - U64 range_slot_idx = range_hash%n->range_hash_slots_count; - CTRL_ProcessMemoryRangeHashSlot *range_slot = &n->range_hash_slots[range_slot_idx]; - for(CTRL_ProcessMemoryRangeHashNode *range_n = range_slot->first; range_n != 0; range_n = range_n->next) - { - if(MemoryMatchStruct(&range_n->vaddr_range, &range) && range_n->zero_terminated == zero_terminated) - { - result = range_n->hash; - is_good = 1; - is_stale = (range_n->mem_gen != pre_mem_gen); - goto read_cache__break_all; - } - } - } - } - read_cache__break_all:; - } - - //- rjf: not good -> create process cache node if necessary - if(!is_good) - { - OS_MutexScopeW(process_stripe->rw_mutex) - { - B32 process_node_exists = 0; - for(CTRL_ProcessMemoryCacheNode *n = process_slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->process, process)) - { - process_node_exists = 1; - break; - } - } - if(!process_node_exists) - { - Arena *node_arena = arena_alloc(); - CTRL_ProcessMemoryCacheNode *node = push_array(node_arena, CTRL_ProcessMemoryCacheNode, 1); - node->arena = node_arena; - node->machine_id = machine_id; - node->process = process; - node->range_hash_slots_count = 1024; - node->range_hash_slots = push_array(node_arena, CTRL_ProcessMemoryRangeHashSlot, node->range_hash_slots_count); - DLLPushBack(process_slot->first, process_slot->last, node); - } - } - } - - //- rjf: not good -> create range node if necessary - U64 last_time_requested_us = 0; - if(!is_good) - { - OS_MutexScopeW(process_stripe->rw_mutex) - { - for(CTRL_ProcessMemoryCacheNode *n = process_slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->process, process)) - { - U64 range_slot_idx = range_hash%n->range_hash_slots_count; - CTRL_ProcessMemoryRangeHashSlot *range_slot = &n->range_hash_slots[range_slot_idx]; - B32 range_node_exists = 0; - for(CTRL_ProcessMemoryRangeHashNode *range_n = range_slot->first; range_n != 0; range_n = range_n->next) - { - if(MemoryMatchStruct(&range_n->vaddr_range, &range) && range_n->zero_terminated == zero_terminated) - { - last_time_requested_us = range_n->last_time_requested_us; - range_node_exists = 1; - break; - } - } - if(!range_node_exists) - { - CTRL_ProcessMemoryRangeHashNode *range_n = push_array(n->arena, CTRL_ProcessMemoryRangeHashNode, 1); - SLLQueuePush(range_slot->first, range_slot->last, range_n); - range_n->vaddr_range = range; - range_n->zero_terminated = zero_terminated; - range_n->vaddr_range_clamped = range; - { - range_n->vaddr_range_clamped.max = Max(range_n->vaddr_range_clamped.max, range_n->vaddr_range_clamped.min); - U64 max_size_cap = Min(max_U64-range_n->vaddr_range_clamped.min, GB(1)); - range_n->vaddr_range_clamped.max = Min(range_n->vaddr_range_clamped.max, range_n->vaddr_range_clamped.min+max_size_cap); - } - break; - } - } - } - } - } - - //- rjf: not good, or is stale -> submit hash request - if((!is_good || is_stale) && os_now_microseconds() >= last_time_requested_us+10000) - { - if(ctrl_u2ms_enqueue_req(machine_id, process, range, zero_terminated, endt_us)) OS_MutexScopeW(process_stripe->rw_mutex) - { - for(CTRL_ProcessMemoryCacheNode *n = process_slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->process, process)) - { - U64 range_slot_idx = range_hash%n->range_hash_slots_count; - CTRL_ProcessMemoryRangeHashSlot *range_slot = &n->range_hash_slots[range_slot_idx]; - for(CTRL_ProcessMemoryRangeHashNode *range_n = range_slot->first; range_n != 0; range_n = range_n->next) - { - if(MemoryMatchStruct(&range_n->vaddr_range, &range) && range_n->zero_terminated == zero_terminated) - { - range_n->last_time_requested_us = os_now_microseconds(); - break; - } - } - } - } - } - } - - //- rjf: out of time? -> exit - if(os_now_microseconds() >= endt_us) - { - if(is_stale && out_is_stale) - { - out_is_stale[0] = 1; - } - break; - } - - //- rjf: done? -> exit - if(is_good && !is_stale) - { - break; - } - } - U64 post_mem_gen = dmn_mem_gen(); - if(post_mem_gen != pre_mem_gen && out_is_stale) - { - out_is_stale[0] = 1; - } - return result; -} - -//- rjf: bundled key/stream helper - -internal U128 -ctrl_hash_store_key_from_process_vaddr_range(CTRL_MachineID machine_id, DMN_Handle process, Rng1U64 range, B32 zero_terminated) -{ - U128 key = ctrl_calc_hash_store_key_from_process_vaddr_range(machine_id, process, range, zero_terminated); - ctrl_stored_hash_from_process_vaddr_range(machine_id, process, range, zero_terminated, 0, 0); - return key; -} - -//- rjf: process memory cache reading helpers - -internal CTRL_ProcessMemorySlice -ctrl_query_cached_data_from_process_vaddr_range(Arena *arena, CTRL_MachineID machine_id, DMN_Handle process, Rng1U64 range, U64 endt_us) -{ - CTRL_ProcessMemorySlice result = {0}; - if(range.max > range.min && - dim_1u64(range) <= MB(256) && - range.min <= 0x000FFFFFFFFFFFFFull && - range.max <= 0x000FFFFFFFFFFFFFull) - { - Temp scratch = scratch_begin(&arena, 1); - HS_Scope *scope = hs_scope_open(); - CTRL_ProcessMemoryCache *cache = &ctrl_state->process_memory_cache; - - //- rjf: unpack address range, prepare per-touched-page info - U64 page_size = KB(4); - Rng1U64 page_range = r1u64(AlignDownPow2(range.min, page_size), AlignPow2(range.max, page_size)); - U64 page_count = dim_1u64(page_range)/page_size; - U128 *page_hashes = push_array(scratch.arena, U128, page_count); - U128 *page_last_hashes = push_array(scratch.arena, U128, page_count); - - //- rjf: gather hashes & last-hashes for each page - for(U64 page_idx = 0; page_idx < page_count; page_idx += 1) - { - U64 page_base_vaddr = page_range.min + page_idx*page_size; - U128 page_key = ctrl_calc_hash_store_key_from_process_vaddr_range(machine_id, process, r1u64(page_base_vaddr, page_base_vaddr+page_size), 0); - B32 page_is_stale = 0; - U128 page_hash = ctrl_stored_hash_from_process_vaddr_range(machine_id, process, r1u64(page_base_vaddr, page_base_vaddr+page_size), 0, &page_is_stale, endt_us); - U128 page_last_hash = hs_hash_from_key(page_key, 1); - result.stale = (result.stale || page_is_stale); - page_hashes[page_idx] = page_hash; - page_last_hashes[page_idx] = page_last_hash; - } - - //- rjf: setup output buffers - void *read_out = push_array(arena, U8, dim_1u64(range)); - U64 *byte_bad_flags = push_array(arena, U64, (dim_1u64(range)+63)/64); - U64 *byte_changed_flags = push_array(arena, U64, (dim_1u64(range)+63)/64); - - //- rjf: iterate pages, fill output - { - U64 write_off = 0; - for(U64 page_idx = 0; page_idx < page_count; page_idx += 1) - { - // rjf: read data for this page - String8 data = hs_data_from_hash(scope, page_hashes[page_idx]); - Rng1U64 data_vaddr_range = r1u64(page_range.min + page_idx*page_size, page_range.min + page_idx*page_size+data.size); - - // rjf: skip/chop bytes which are irrelevant for the actual requested read - String8 in_range_data = data; - if(page_idx == page_count-1 && data_vaddr_range.max > range.max) - { - in_range_data = str8_chop(in_range_data, data_vaddr_range.max-range.max); - } - if(page_idx == 0 && range.min > data_vaddr_range.min) - { - in_range_data = str8_skip(in_range_data, range.min-data_vaddr_range.min); - } - - // rjf: write this chunk - MemoryCopy((U8*)read_out+write_off, in_range_data.str, in_range_data.size); - - // rjf; if this page's data doesn't fill the entire range, mark - // missing bytes as bad - if(data.size < page_size) - { - for(U64 invalid_vaddr = data_vaddr_range.min+data.size; - invalid_vaddr < data_vaddr_range.min + page_size; - invalid_vaddr += 1) - { - if(contains_1u64(range, invalid_vaddr)) - { - U64 idx_in_range = invalid_vaddr-range.min; - byte_bad_flags[idx_in_range/64] |= (1ull<<(idx_in_range%64)); - } - } - } - - // rjf: if this page's hash & last_hash don't match, diff each byte & - // fill out changed flags - if(!u128_match(page_hashes[page_idx], page_last_hashes[page_idx])) - { - String8 last_data = hs_data_from_hash(scope, page_last_hashes[page_idx]); - String8 in_range_last_data = last_data; - if(page_idx == page_count-1 && data_vaddr_range.max > range.max) - { - in_range_last_data = str8_chop(in_range_last_data, data_vaddr_range.max-range.max); - } - if(page_idx == 0 && range.min > data_vaddr_range.min) - { - in_range_last_data = str8_skip(in_range_last_data, range.min-data_vaddr_range.min); - } - for(U64 idx = 0; idx < in_range_data.size; idx += 1) - { - U8 last_byte = idx < in_range_last_data.size ? in_range_last_data.str[idx] : 0; - U8 now_byte = idx < in_range_data.size ? in_range_data.str[idx] : 0; - if(last_byte != now_byte) - { - U64 idx_in_read_out = write_off+idx; - byte_changed_flags[idx_in_read_out/64] |= (1ull<<(idx_in_read_out%64)); - } - } - } - - // rjf: increment past this chunk - write_off += in_range_data.size; - if(data.size < page_size) - { - U64 missed_byte_count = page_size-data.size; - write_off += missed_byte_count; - } - } - } - - //- rjf: fill result - result.data.str = (U8*)read_out; - result.data.size = dim_1u64(range); - result.byte_bad_flags = byte_bad_flags; - result.byte_changed_flags = byte_changed_flags; - if(byte_bad_flags != 0) - { - for(U64 idx = 0; idx < (dim_1u64(range)+63)/64; idx += 1) - { - result.any_byte_bad = result.any_byte_bad || !!result.byte_bad_flags[idx]; - } - } - if(byte_changed_flags != 0) - { - for(U64 idx = 0; idx < (dim_1u64(range)+63)/64; idx += 1) - { - result.any_byte_changed = result.any_byte_changed || !!result.byte_changed_flags[idx]; - } - } - - hs_scope_close(scope); - scratch_end(scratch); - } - return result; -} - -internal CTRL_ProcessMemorySlice -ctrl_query_cached_zero_terminated_data_from_process_vaddr_limit(Arena *arena, CTRL_MachineID machine_id, DMN_Handle process, U64 vaddr, U64 limit, U64 element_size, U64 endt_us) -{ - CTRL_ProcessMemorySlice result = ctrl_query_cached_data_from_process_vaddr_range(arena, machine_id, process, r1u64(vaddr, vaddr+limit), endt_us); - U64 element_count = result.data.size/element_size; - for(U64 element_idx = 0; element_idx < element_count; element_idx += 1) - { - B32 element_is_zero = 1; - for(U64 element_byte_idx = 0; element_byte_idx < element_size; element_byte_idx += 1) - { - if(result.data.str[element_idx*element_size + element_byte_idx] != 0) - { - element_is_zero = 0; - break; - } - } - if(element_is_zero) - { - result.data.size = element_idx*element_size; - break; - } - } - return result; -} - -internal B32 -ctrl_read_cached_process_memory(CTRL_MachineID machine_id, DMN_Handle process, Rng1U64 range, B32 *is_stale_out, void *out, U64 endt_us) -{ - Temp scratch = scratch_begin(0, 0); - U64 needed_size = dim_1u64(range); - CTRL_ProcessMemorySlice slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, machine_id, process, range, endt_us); - B32 good = (slice.data.size >= needed_size && !slice.any_byte_bad); - if(good) - { - MemoryCopy(out, slice.data.str, needed_size); - } - if(slice.stale && is_stale_out) - { - *is_stale_out = 1; - } - scratch_end(scratch); - return good; -} - -//- rjf: process memory writing - -internal B32 -ctrl_process_write(CTRL_MachineID machine_id, DMN_Handle process, Rng1U64 range, void *src) -{ - ProfBeginFunction(); - B32 result = dmn_process_write(process, range, src); - - //- rjf: success -> wait for cache updates, for small regions - prefer relatively seamless - // writes within calling frame's "view" of the memory, at the expense of a small amount of - // time. - if(result) - { - Temp scratch = scratch_begin(0, 0); - U64 endt_us = os_now_microseconds()+5000; - - //- rjf: gather tasks for all affected cached regions - typedef struct Task Task; - struct Task - { - Task *next; - CTRL_MachineID machine_id; - DMN_Handle process; - Rng1U64 range; - }; - Task *first_task = 0; - Task *last_task = 0; - CTRL_ProcessMemoryCache *cache = &ctrl_state->process_memory_cache; - for(U64 slot_idx = 0; slot_idx < cache->slots_count; slot_idx += 1) - { - U64 stripe_idx = slot_idx%cache->stripes_count; - CTRL_ProcessMemoryCacheSlot *slot = &cache->slots[slot_idx]; - CTRL_ProcessMemoryCacheStripe *stripe = &cache->stripes[stripe_idx]; - OS_MutexScopeW(stripe->rw_mutex) - { - for(CTRL_ProcessMemoryCacheNode *proc_n = slot->first; proc_n != 0; proc_n = proc_n->next) - { - for(U64 range_hash_idx = 0; range_hash_idx < proc_n->range_hash_slots_count; range_hash_idx += 1) - { - CTRL_ProcessMemoryRangeHashSlot *range_slot = &proc_n->range_hash_slots[range_hash_idx]; - for(CTRL_ProcessMemoryRangeHashNode *n = range_slot->first; n != 0; n = n->next) - { - Rng1U64 intersection_w_range = intersect_1u64(range, n->vaddr_range); - if(dim_1u64(intersection_w_range) != 0 && dim_1u64(n->vaddr_range) <= KB(64)) - { - Task *task = push_array(scratch.arena, Task, 1); - task->machine_id = proc_n->machine_id; - task->process = proc_n->process; - task->range = n->vaddr_range; - SLLQueuePush(first_task, last_task, task); - } - } - } - } - } - } - - //- rjf: for all tasks, wait for up-to-date results - for(Task *task = first_task; task != 0; task = task->next) - { - Temp temp = temp_begin(scratch.arena); - ctrl_query_cached_data_from_process_vaddr_range(temp.arena, task->machine_id, task->process, task->range, endt_us); - temp_end(temp); - } - - scratch_end(scratch); - } - - ProfEnd(); - return result; -} - -//////////////////////////////// -//~ rjf: Thread Register Functions - -//- rjf: thread register cache reading - -internal void * -ctrl_query_cached_reg_block_from_thread(Arena *arena, CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle thread) -{ - CTRL_ThreadRegCache *cache = &ctrl_state->thread_reg_cache; - CTRL_Entity *thread_entity = ctrl_entity_from_machine_id_handle(store, machine_id, thread); - Architecture arch = thread_entity->arch; - U64 reg_block_size = regs_block_size_from_architecture(arch); - U64 hash = ctrl_hash_from_machine_id_handle(machine_id, thread); - U64 slot_idx = hash%cache->slots_count; - U64 stripe_idx = slot_idx%cache->stripes_count; - CTRL_ThreadRegCacheSlot *slot = &cache->slots[slot_idx]; - CTRL_ThreadRegCacheStripe *stripe = &cache->stripes[stripe_idx]; - void *result = push_array(arena, U8, reg_block_size); - OS_MutexScopeR(stripe->rw_mutex) - { - // rjf: find existing node - CTRL_ThreadRegCacheNode *node = 0; - for(CTRL_ThreadRegCacheNode *n = slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->thread, thread)) - { - node = n; - break; - } - } - - // rjf: allocate existing node - if(!node) - { - OS_MutexScopeRWPromote(stripe->rw_mutex) - { - for(CTRL_ThreadRegCacheNode *n = slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->thread, thread)) - { - node = n; - break; - } - } - if(!node) - { - node = push_array(stripe->arena, CTRL_ThreadRegCacheNode, 1); - DLLPushBack(slot->first, slot->last, node); - node->machine_id = machine_id; - node->thread = thread; - node->block_size = reg_block_size; - node->block = push_array(stripe->arena, U8, reg_block_size); - } - } - for(CTRL_ThreadRegCacheNode *n = slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->thread, thread)) - { - node = n; - break; - } - } - } - - // rjf: copy from node - if(node) - { - U64 current_reg_gen = dmn_reg_gen(); - B32 need_stale = 1; - if(node->reg_gen != current_reg_gen && dmn_thread_read_reg_block(thread, result)) - { - OS_MutexScopeRWPromote(stripe->rw_mutex) - { - for(CTRL_ThreadRegCacheNode *n = slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->thread, thread)) - { - node = n; - break; - } - } - if(node != 0) - { - need_stale = 0; - node->reg_gen = current_reg_gen; - MemoryCopy(node->block, result, reg_block_size); - } - } - } - if(need_stale) - { - MemoryCopy(result, node->block, reg_block_size); - } - } - } - return result; -} - -internal U64 -ctrl_query_cached_tls_root_vaddr_from_thread(CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle thread) -{ - U64 result = dmn_tls_root_vaddr_from_thread(thread); - return result; -} - -internal U64 -ctrl_query_cached_rip_from_thread(CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle thread) -{ - Temp scratch = scratch_begin(0, 0); - CTRL_Entity *thread_entity = ctrl_entity_from_machine_id_handle(store, machine_id, thread); - Architecture arch = thread_entity->arch; - void *block = ctrl_query_cached_reg_block_from_thread(scratch.arena, store, machine_id, thread); - U64 result = regs_rip_from_arch_block(arch, block); - scratch_end(scratch); - return result; -} - -internal U64 -ctrl_query_cached_rsp_from_thread(CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle thread) -{ - Temp scratch = scratch_begin(0, 0); - CTRL_Entity *thread_entity = ctrl_entity_from_machine_id_handle(store, machine_id, thread); - Architecture arch = thread_entity->arch; - void *block = ctrl_query_cached_reg_block_from_thread(scratch.arena, store, machine_id, thread); - U64 result = regs_rsp_from_arch_block(arch, block); - scratch_end(scratch); - return result; -} - -//- rjf: thread register writing - -internal B32 -ctrl_thread_write_reg_block(CTRL_MachineID machine_id, DMN_Handle thread, void *block) -{ - B32 good = dmn_thread_write_reg_block(thread, block); - return good; -} - -//////////////////////////////// -//~ rjf: Module Image Info Functions - -//- rjf: cache lookups - -internal PE_IntelPdata * -ctrl_intel_pdata_from_module_voff(Arena *arena, CTRL_MachineID machine_id, DMN_Handle module_handle, U64 voff) -{ - PE_IntelPdata *first_pdata = 0; - { - U64 hash = ctrl_hash_from_machine_id_handle(machine_id, module_handle); - U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; - U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; - CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; - CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; - OS_MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->module, module_handle)) - { - PE_IntelPdata *pdatas = n->pdatas; - U64 pdatas_count = n->pdatas_count; - if(n->pdatas_count != 0 && voff >= n->pdatas[0].voff_first) - { - // NOTE(rjf): - // - // binary search: - // find max index s.t. pdata_array[index].voff_first <= voff - // we assume (i < j) -> (pdata_array[i].voff_first < pdata_array[j].voff_first) - U64 index = pdatas_count; - U64 min = 0; - U64 opl = pdatas_count; - for(;;) - { - U64 mid = (min + opl)/2; - PE_IntelPdata *pdata = pdatas + mid; - if(voff < pdata->voff_first) - { - opl = mid; - } - else if(pdata->voff_first < voff) - { - min = mid; - } - else - { - index = mid; - break; - } - if(min + 1 >= opl) - { - index = min; - break; - } - } - - // rjf: if we are in range fill result - { - PE_IntelPdata *pdata = pdatas + index; - if(pdata->voff_first <= voff && voff < pdata->voff_one_past_last) - { - first_pdata = push_array(arena, PE_IntelPdata, 1); - MemoryCopyStruct(first_pdata, pdata); - } - } - } - break; - } - } - } - return first_pdata; -} - -internal U64 -ctrl_entry_point_voff_from_module(CTRL_MachineID machine_id, DMN_Handle module_handle) -{ - U64 result = 0; - U64 hash = ctrl_hash_from_machine_id_handle(machine_id, module_handle); - U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; - U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; - CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; - CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; - OS_MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->module, module_handle)) - { - result = n->entry_point_voff; - break; - } - } - return result; -} - -internal Rng1U64 -ctrl_tls_vaddr_range_from_module(CTRL_MachineID machine_id, DMN_Handle module_handle) -{ - Rng1U64 result = {0}; - U64 hash = ctrl_hash_from_machine_id_handle(machine_id, module_handle); - U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; - U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; - CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; - CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; - OS_MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->module, module_handle)) - { - result = n->tls_vaddr_range; - break; - } - } - return result; -} - -internal String8 -ctrl_initial_debug_info_path_from_module(Arena *arena, CTRL_MachineID machine_id, DMN_Handle module_handle) -{ - String8 result = {0}; - U64 hash = ctrl_hash_from_machine_id_handle(machine_id, module_handle); - U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; - U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; - CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; - CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; - OS_MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->module, module_handle)) - { - result = push_str8_copy(arena, n->initial_debug_info_path); - break; - } - } - return result; -} - -//////////////////////////////// -//~ rjf: Unwinding Functions - -//- rjf: unwind deep copier - -internal CTRL_Unwind -ctrl_unwind_deep_copy(Arena *arena, Architecture arch, CTRL_Unwind *src) -{ - CTRL_Unwind dst = {0}; - { - dst.flags = src->flags; - dst.frames.count = src->frames.count; - dst.frames.v = push_array(arena, CTRL_UnwindFrame, dst.frames.count); - MemoryCopy(dst.frames.v, src->frames.v, sizeof(dst.frames.v[0])*dst.frames.count); - U64 block_size = regs_block_size_from_architecture(arch); - for(U64 idx = 0; idx < dst.frames.count; idx += 1) - { - dst.frames.v[idx].regs = push_array_no_zero(arena, U8, block_size); - MemoryCopy(dst.frames.v[idx].regs, src->frames.v[idx].regs, block_size); - } - } - return dst; -} - -//- rjf: [x64] - -internal REGS_Reg64 * -ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(REGS_RegBlockX64 *regs, PE_UnwindGprRegX64 gpr_reg) -{ - local_persist REGS_Reg64 dummy = {0}; - REGS_Reg64 *result = &dummy; - switch(gpr_reg) - { - case PE_UnwindGprRegX64_RAX:{result = ®s->rax;}break; - case PE_UnwindGprRegX64_RCX:{result = ®s->rcx;}break; - case PE_UnwindGprRegX64_RDX:{result = ®s->rdx;}break; - case PE_UnwindGprRegX64_RBX:{result = ®s->rbx;}break; - case PE_UnwindGprRegX64_RSP:{result = ®s->rsp;}break; - case PE_UnwindGprRegX64_RBP:{result = ®s->rbp;}break; - case PE_UnwindGprRegX64_RSI:{result = ®s->rsi;}break; - case PE_UnwindGprRegX64_RDI:{result = ®s->rdi;}break; - case PE_UnwindGprRegX64_R8 :{result = ®s->r8 ;}break; - case PE_UnwindGprRegX64_R9 :{result = ®s->r9 ;}break; - case PE_UnwindGprRegX64_R10:{result = ®s->r10;}break; - case PE_UnwindGprRegX64_R11:{result = ®s->r11;}break; - case PE_UnwindGprRegX64_R12:{result = ®s->r12;}break; - case PE_UnwindGprRegX64_R13:{result = ®s->r13;}break; - case PE_UnwindGprRegX64_R14:{result = ®s->r14;}break; - case PE_UnwindGprRegX64_R15:{result = ®s->r15;}break; - } - return result; -} - -internal CTRL_UnwindStepResult -ctrl_unwind_step__pe_x64(CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle process_handle, DMN_Handle module_handle, REGS_RegBlockX64 *regs, U64 endt_us) -{ - B32 is_stale = 0; - B32 is_good = 1; - Temp scratch = scratch_begin(0, 0); - - ////////////////////////////// - //- rjf: unpack parameters - // - CTRL_Entity *module = ctrl_entity_from_machine_id_handle(store, machine_id, module_handle); - CTRL_Entity *process = ctrl_entity_from_machine_id_handle(store, machine_id, process_handle); - U64 rip_voff = regs->rip.u64 - module->vaddr_range.min; - - ////////////////////////////// - //- rjf: rip_voff -> first pdata - // - PE_IntelPdata *first_pdata = ctrl_intel_pdata_from_module_voff(scratch.arena, machine_id, module_handle, rip_voff); - - ////////////////////////////// - //- rjf: pdata -> detect if in epilog - // - B32 has_pdata_and_in_epilog = 0; - if(first_pdata) ProfScope("pdata -> detect if in epilog") - { - // NOTE(allen): There are restrictions placed on how an epilog is allowed - // to be formed (https://docs.microsoft.com/en-us/cpp/build/prolog-and-epilog?view=msvc-160) - // Here we interpret machine code directly according to the rules - // given there to determine if the code we're looking at looks like an epilog. - - //- rjf: set up parsing state - B32 is_epilog = 0; - B32 keep_parsing = 1; - U64 read_vaddr = regs->rip.u64; - U64 read_vaddr_opl = read_vaddr + 256; - - //- rjf: check first instruction - { - B32 inst_good = 0; - U8 inst[4] = {0}; - if(read_vaddr + sizeof(inst) <= read_vaddr_opl) - { - inst_good = ctrl_read_cached_process_memory(machine_id, process->handle, r1u64(read_vaddr, read_vaddr+sizeof(inst)), &is_stale, inst, endt_us); - inst_good = inst_good && !is_stale; - } - if(!inst_good) - { - keep_parsing = 0; - } - else if((inst[0] & 0xF8) == 0x48) - { - switch(inst[1]) - { - // rjf: add $nnnn,%rsp - case 0x81: - { - if(inst[0] == 0x48 && inst[2] == 0xC4) - { - read_vaddr += 7; - } - else - { - keep_parsing = 0; - } - }break; - - // rjf: add $n,%rsp - case 0x83: - { - if(inst[0] == 0x48 && inst[2] == 0xC4) - { - read_vaddr += 4; - } - else - { - keep_parsing = 0; - } - }break; - - // rjf: lea n(reg),%rsp - case 0x8D: - { - if((inst[0] & 0x06) == 0 && - ((inst[2] >> 3) & 0x07) == 0x04 && - (inst[2] & 0x07) != 0x04) - { - U8 imm_size = (inst[2] >> 6); - - // rjf: 1-byte immediate - if(imm_size == 1) - { - read_vaddr += 4; - } - - // rjf: 4-byte immediate - else if(imm_size == 2) - { - read_vaddr += 7; - } - - // rjf: other case - else - { - keep_parsing = 0; - } - } - else - { - keep_parsing = 0; - } - }break; - } - } - } - - //- rjf: continue parsing instructions - for(;keep_parsing;) - { - // rjf: read next instruction byte - B32 inst_byte_good = 0; - U8 inst_byte = 0; - if(read_vaddr + sizeof(inst_byte) <= read_vaddr_opl) - { - inst_byte_good = ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &inst_byte, endt_us); - } - if(!inst_byte_good || is_stale) - { - keep_parsing = 0; - } - - // rjf: when (... I don't know ...) rely on the next byte - B32 check_inst_byte_good = inst_byte_good; - U64 check_vaddr = read_vaddr; - U8 check_inst_byte = inst_byte; - if(inst_byte_good && (inst_byte & 0xF0) == 0x40) - { - check_vaddr = read_vaddr + 1; - if(read_vaddr + sizeof(check_inst_byte) <= read_vaddr_opl) - { - check_inst_byte_good = ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &check_inst_byte, endt_us); - } - if(!check_inst_byte_good || is_stale) - { - keep_parsing = 0; - } - } - - // rjf: check instruction byte - if(check_inst_byte_good) - { - switch(check_inst_byte) - { - // rjf: pop - case 0x58:case 0x59:case 0x5A:case 0x5B: - case 0x5C:case 0x5D:case 0x5E:case 0x5F: - { - read_vaddr = check_vaddr + 1; - }break; - - // rjf: ret - case 0xC2: - case 0xC3: - { - is_epilog = 1; - keep_parsing = 0; - }break; - - // rjf: jmp nnnn - case 0xE9: - { - U64 imm_vaddr = check_vaddr + 1; - S32 imm = 0; - B32 imm_good = 0; - if(read_vaddr + sizeof(imm) <= read_vaddr_opl) - { - imm_good = ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &imm, endt_us); - } - if(!imm_good || is_stale) - { - keep_parsing = 0; - } - if(imm_good) - { - U64 next_vaddr = (U64)(imm_vaddr + sizeof(imm) + imm); - U64 next_voff = next_vaddr - module->vaddr_range.min; // TODO(rjf): verify that this offset is from module base vaddr, not section - if(!(first_pdata->voff_first <= next_voff && next_voff < first_pdata->voff_one_past_last)) - { - keep_parsing = 0; - } - else - { - read_vaddr = next_vaddr; - } - } - // TODO(allen): why isn't this just the end of the epilog? - }break; - - // rjf: rep; ret (for amd64 prediction bug) - case 0xF3: - { - U8 next_inst_byte = 0; - B32 next_inst_byte_good = 0; - if(read_vaddr + sizeof(next_inst_byte) <= read_vaddr_opl) - { - next_inst_byte_good = ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &next_inst_byte, endt_us); - } - if(next_inst_byte_good) - { - is_epilog = (next_inst_byte == 0xC3); - } - keep_parsing = 0; - }break; - - default:{keep_parsing = 0;}break; - } - } - } - has_pdata_and_in_epilog = is_epilog; - } - - ////////////////////////////// - //- rjf: pdata & in epilog -> epilog unwind - // - if(first_pdata && has_pdata_and_in_epilog) ProfScope("pdata & in epilog -> epilog unwind") - { - U64 read_vaddr = regs->rip.u64; - for(B32 keep_parsing = 1;keep_parsing != 0;) - { - //- rjf: assume no more parsing after this instruction - keep_parsing = 0; - - //- rjf: read next instruction byte - U8 inst_byte = 0; - is_good = is_good && ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &inst_byte, endt_us); - is_good = is_good && !is_stale; - read_vaddr += 1; - - //- rjf: extract rex from instruction byte - U8 rex = 0; - if((inst_byte & 0xF0) == 0x40) - { - rex = inst_byte & 0xF; // rex prefix - is_good = is_good && ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &inst_byte, endt_us); - is_good = is_good && !is_stale; - read_vaddr += 1; - } - - //- rjf: parse remainder of instruction - switch(inst_byte) - { - // rjf: pop - case 0x58: - case 0x59: - case 0x5A: - case 0x5B: - case 0x5C: - case 0x5D: - case 0x5E: - case 0x5F: - { - // rjf: read value at rsp - U64 sp = regs->rsp.u64; - U64 value = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, sp, &is_stale, &value, endt_us) || - is_stale) - { - is_good = 0; - break; - } - - // rjf: modify registers - PE_UnwindGprRegX64 gpr_reg = (inst_byte - 0x58) + (rex & 1)*8; - REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, gpr_reg); - reg->u64 = value; - regs->rsp.u64 = sp + 8; - - // rjf: not a final instruction, so keep mparsing - keep_parsing = 1; - }break; - - // rjf: add $nnnn,%rsp - case 0x81: - { - // rjf: skip one byte (we already know what it is in this scenario) - read_vaddr += 1; - - // rjf: read the 4-byte immediate - S32 imm = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &imm, endt_us) || - is_stale) - { - is_good = 0; - break; - } - read_vaddr += 4; - - // rjf: update stack pointer - regs->rsp.u64 = (U64)(regs->rsp.u64 + imm); - - // rjf: not a final instruction; keep parsing - keep_parsing = 1; - }break; - - // rjf: add $n,%rsp - case 0x83: - { - // rjf: skip one byte (we already know what it is in this scenario) - read_vaddr += 1; - - // rjf: read the 4-byte immediate - S8 imm = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &imm, endt_us) || - is_stale) - { - is_good = 0; - break; - } - read_vaddr += 1; - - // rjf: update stack pointer - regs->rsp.u64 = (U64)(regs->rsp.u64 + imm); - - // rjf: not a final instruction; keep parsing - keep_parsing = 1; - }break; - - // rjf: lea imm8/imm32,$rsp - case 0x8D: - { - // rjf: read source register - U8 modrm = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &modrm, endt_us) || - is_stale) - { - is_good = 0; - break; - } - read_vaddr += 1; - PE_UnwindGprRegX64 gpr_reg = (modrm & 7) + (rex & 1)*8; - REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, gpr_reg); - U64 reg_value = reg->u64; - - // rjf: read immediate - S32 imm = 0; - { - // rjf: read 1-byte immediate - if((modrm >> 6) == 1) - { - S8 imm8 = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &imm8, endt_us) || - is_stale) - { - is_good = 0; - break; - } - read_vaddr += 1; - imm = (S32)imm8; - } - - // rjf: read 4-byte immediate - else - { - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &imm, endt_us) || - is_stale) - { - is_good = 0; - break; - } - read_vaddr += 4; - } - } - - // rjf: update stack pointer - regs->rsp.u64 = (U64)(reg_value + imm); - - // rjf: not a final instruction; keep parsing - keep_parsing = 1; - }break; - - // rjf: ret $nn - case 0xC2: - { - // rjf: read new ip - U64 sp = regs->rsp.u64; - U64 new_ip = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, sp, &is_stale, &new_ip, endt_us) || - is_stale) - { - is_good = 0; - break; - } - - // rjf: read 2-byte immediate & advance stack pointer - U16 imm = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &imm, endt_us) || - is_stale) - { - is_good = 0; - break; - } - U64 new_sp = sp + 8 + imm; - - // rjf: commit registers - regs->rip.u64 = new_ip; - regs->rsp.u64 = new_sp; - }break; - - // rjf: ret / rep; ret - case 0xF3: - { - // Assert(!"Hit me!"); - }break; - case 0xC3: - { - // rjf: read new ip - U64 sp = regs->rsp.u64; - U64 new_ip = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, sp, &is_stale, &new_ip, endt_us) || - is_stale) - { - is_good = 0; - break; - } - - // rjf: advance stack pointer - U64 new_sp = sp + 8; - - // rjf: commit registers - regs->rip.u64 = new_ip; - regs->rsp.u64 = new_sp; - }break; - - // rjf: jmp nnnn - case 0xE9: - { - // Assert(!"Hit Me"); - // TODO(allen): general idea: read the immediate, move the ip, leave the sp, done - // we don't have any cases to exercise this right now. no guess implementation! - }break; - - // rjf: Sjmp n - case 0xEB: - { - // Assert(!"Hit Me"); - // TODO(allen): general idea: read the immediate, move the ip, leave the sp, done - // we don't have any cases to exercise this right now. no guess implementation! - }break; - } - } - } - - ////////////////////////////// - //- rjf: pdata & not in epilog -> xdata unwind - // - B32 xdata_unwind_did_machframe = 0; - if(first_pdata && !has_pdata_and_in_epilog) ProfScope("pdata & not in epilog -> xdata unwind") - { - //- rjf: get frame reg - B32 bad_frame_reg_info = 0; - REGS_Reg64 *frame_reg = 0; - U64 frame_off = 0; - { - U64 unwind_info_off = first_pdata->voff_unwind_info; - PE_UnwindInfo unwind_info = {0}; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, module->vaddr_range.min+unwind_info_off, &is_stale, &unwind_info, endt_us) || - is_stale) - { - is_good = 0; - } - U32 frame_reg_id = PE_UNWIND_INFO_REG_FROM_FRAME(unwind_info.frame); - U64 frame_off_val = PE_UNWIND_INFO_OFF_FROM_FRAME(unwind_info.frame); - if(frame_reg_id != 0) - { - frame_reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, frame_reg_id); - bad_frame_reg_info = (frame_reg == 0); // NOTE(rjf): frame_reg should never be 0 at this point, in valid exe - } - frame_off = frame_off_val; - } - - //- rjf: iterate pdatas, apply opcodes - PE_IntelPdata *last_pdata = 0; - PE_IntelPdata *pdata = first_pdata; - if(!bad_frame_reg_info) for(B32 keep_parsing = 1; keep_parsing && pdata != last_pdata;) - { - //- rjf: unpack unwind info & codes - B32 good_unwind_info = 1; - U64 unwind_info_off = pdata->voff_unwind_info; - PE_UnwindInfo unwind_info = {0}; - good_unwind_info = good_unwind_info && ctrl_read_cached_process_memory_struct(machine_id, process->handle, module->vaddr_range.min+unwind_info_off, &is_stale, &unwind_info, endt_us); - PE_UnwindCode *unwind_codes = push_array(scratch.arena, PE_UnwindCode, unwind_info.codes_num); - good_unwind_info = good_unwind_info && ctrl_read_cached_process_memory(machine_id, process->handle, r1u64(module->vaddr_range.min+unwind_info_off+sizeof(unwind_info), - module->vaddr_range.min+unwind_info_off+sizeof(unwind_info)+sizeof(PE_UnwindCode)*unwind_info.codes_num), - &is_stale, unwind_codes, endt_us); - good_unwind_info = good_unwind_info && !is_stale; - - //- rjf: bad unwind info -> abort - if(!good_unwind_info) - { - is_good = 0; - break; - } - - //- rjf: unpack frame base - U64 frame_base = regs->rsp.u64; - if(frame_reg != 0) - { - U64 raw_frame_base = frame_reg->u64; - U64 adjusted_frame_base = raw_frame_base - frame_off*16; - if(adjusted_frame_base < raw_frame_base) - { - frame_base = adjusted_frame_base; - } - } - - //- rjf: apply opcodes - PE_UnwindCode *code_ptr = unwind_codes; - PE_UnwindCode *code_opl = unwind_codes + unwind_info.codes_num; - for(PE_UnwindCode *next_code_ptr = 0; code_ptr < code_opl; code_ptr = next_code_ptr) - { - // rjf: unpack opcode info - U32 op_code = PE_UNWIND_OPCODE_FROM_FLAGS(code_ptr->flags); - U32 op_info = PE_UNWIND_INFO_FROM_FLAGS(code_ptr->flags); - U32 slot_count = pe_slot_count_from_unwind_op_code(op_code); - if(op_code == PE_UnwindOpCode_ALLOC_LARGE && op_info == 1) - { - slot_count += 1; - } - - // rjf: detect bad slot counts - if(slot_count == 0 || code_ptr+slot_count > code_opl) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: set next op code pointer - next_code_ptr = code_ptr + slot_count; - - // rjf: interpret this op code - U64 code_voff = pdata->voff_first + code_ptr->off_in_prolog; - if(code_voff <= rip_voff) - { - switch(op_code) - { - case PE_UnwindOpCode_PUSH_NONVOL: - { - // rjf: read value from stack pointer - U64 rsp = regs->rsp.u64; - U64 value = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, rsp, &is_stale, &value, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: advance stack ptr - U64 new_rsp = rsp + 8; - - // rjf: commit registers - REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, op_info); - reg->u64 = value; - regs->rsp.u64 = new_rsp; - }break; - - case PE_UnwindOpCode_ALLOC_LARGE: - { - // rjf: read alloc size - U64 size = 0; - if(op_info == 0) - { - size = code_ptr[1].u16*8; - } - else if(op_info == 1) - { - size = code_ptr[1].u16 + ((U32)code_ptr[2].u16 << 16); - } - else - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: advance stack pointer - U64 rsp = regs->rsp.u64; - U64 new_rsp = rsp + size; - - // rjf: advance stack pointer - regs->rsp.u64 = new_rsp; - }break; - - case PE_UnwindOpCode_ALLOC_SMALL: - { - // rjf: advance stack pointer - regs->rsp.u64 += op_info*8 + 8; - }break; - - case PE_UnwindOpCode_SET_FPREG: - { - // rjf: put stack pointer back to the frame base - regs->rsp.u64 = frame_base; - }break; - - case PE_UnwindOpCode_SAVE_NONVOL: - { - // rjf: read value from frame base - U64 off = code_ptr[1].u16*8; - U64 addr = frame_base + off; - U64 value = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, addr, &is_stale, &value, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: commit to register - REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, op_info); - reg->u64 = value; - }break; - - case PE_UnwindOpCode_SAVE_NONVOL_FAR: - { - // rjf: read value from frame base - U64 off = code_ptr[1].u16 + ((U32)code_ptr[2].u16 << 16); - U64 addr = frame_base + off; - U64 value = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, addr, &is_stale, &value, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: commit to register - REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, op_info); - reg->u64 = value; - }break; - - case PE_UnwindOpCode_EPILOG: - { - keep_parsing = 0; - is_good = 0; - }break; - - case PE_UnwindOpCode_SPARE_CODE: - { - // TODO(rjf): ??? - keep_parsing = 0; - is_good = 0; - }break; - - case PE_UnwindOpCode_SAVE_XMM128: - { - // rjf: read new register values - U8 buf[16]; - U64 off = code_ptr[1].u16*16; - U64 addr = frame_base + off; - if(!ctrl_read_cached_process_memory(machine_id, process->handle, r1u64(addr, addr+sizeof(buf)), &is_stale, buf, endt_us)) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: commit to register - void *xmm_reg = (®s->ymm0) + op_info; - MemoryCopy(xmm_reg, buf, sizeof(buf)); - }break; - - case PE_UnwindOpCode_SAVE_XMM128_FAR: - { - // rjf: read new register values - U8 buf[16]; - U64 off = code_ptr[1].u16 + ((U32)code_ptr[2].u16 << 16); - U64 addr = frame_base + off; - if(!ctrl_read_cached_process_memory(machine_id, process->handle, r1u64(addr, addr+16), &is_stale, buf, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: commit to register - void *xmm_reg = (®s->ymm0) + op_info; - MemoryCopy(xmm_reg, buf, sizeof(buf)); - }break; - - case PE_UnwindOpCode_PUSH_MACHFRAME: - { - // NOTE(rjf): this was found by stepping through kernel code after an exception was - // thrown, encountered in the exception_stepping_tests (after the throw) in mule_main - if(op_info > 1) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: read values - U64 sp_og = regs->rsp.u64; - U64 sp_adj = sp_og; - if(op_info == 1) - { - sp_adj += 8; - } - U64 ip_value = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, sp_adj, &is_stale, &ip_value, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - U64 sp_after_ip = sp_adj + 8; - U16 ss_value = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, sp_after_ip, &is_stale, &ss_value, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - U64 sp_after_ss = sp_after_ip + 8; - U64 rflags_value = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, sp_after_ss, &is_stale, &rflags_value, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - U64 sp_after_rflags = sp_after_ss + 8; - U64 sp_value = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, sp_after_rflags, &is_stale, &sp_value, endt_us) || - is_stale) - { - keep_parsing = 0; - is_good = 0; - break; - } - - // rjf: commit registers - regs->rip.u64 = ip_value; - regs->ss.u16 = ss_value; - regs->rflags.u64 = rflags_value; - regs->rsp.u64 = sp_value; - - // rjf: mark machine frame - xdata_unwind_did_machframe = 1; - }break; - } - } - } - - //- rjf: iterate to next pdata - if(keep_parsing) - { - U32 flags = PE_UNWIND_INFO_FLAGS_FROM_HDR(unwind_info.header); - if(!(flags & PE_UnwindInfoFlag_CHAINED)) - { - break; - } - U64 code_count_rounded = AlignPow2(unwind_info.codes_num, sizeof(PE_UnwindCode)); - U64 code_size = code_count_rounded*sizeof(PE_UnwindCode); - U64 chained_pdata_off = unwind_info_off + sizeof(PE_UnwindInfo) + code_size; - last_pdata = pdata; - pdata = push_array(scratch.arena, PE_IntelPdata, 1); - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, module->vaddr_range.min+chained_pdata_off, &is_stale, pdata, endt_us) || - is_stale) - { - is_good = 0; - break; - } - } - } - } - - ////////////////////////////// - //- rjf: no pdata, or didn't do machframe in xdata unwind -> unwind by reading stack pointer - // - if(!first_pdata || (!has_pdata_and_in_epilog && !xdata_unwind_did_machframe)) ProfScope("no pdata, or didn't do machframe in xdata unwind -> unwind by reading stack pointer") - { - // rjf: read rip from stack pointer - U64 rsp = regs->rsp.u64; - U64 new_rip = 0; - if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, rsp, &is_stale, &new_rip, endt_us) || - is_stale) - { - is_good = 0; - } - - // rjf: commit registers - if(is_good) - { - U64 new_rsp = rsp + 8; - regs->rip.u64 = new_rip; - regs->rsp.u64 = new_rsp; - } - } - - ////////////////////////////// - //- rjf: fill & return - // - scratch_end(scratch); - CTRL_UnwindStepResult result = {0}; - if(!is_good) {result.flags |= CTRL_UnwindFlag_Error;} - if(is_stale) {result.flags |= CTRL_UnwindFlag_Stale;} - return result; -} - -//- rjf: abstracted unwind step - -internal CTRL_UnwindStepResult -ctrl_unwind_step(CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle process, DMN_Handle module, Architecture arch, void *reg_block, U64 endt_us) -{ - CTRL_UnwindStepResult result = {0}; - switch(arch) - { - default:{}break; - case Architecture_x64: - { - result = ctrl_unwind_step__pe_x64(store, machine_id, process, module, (REGS_RegBlockX64 *)reg_block, endt_us); - }break; - } - return result; -} - -//- rjf: abstracted full unwind - -internal CTRL_Unwind -ctrl_unwind_from_thread(Arena *arena, CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle thread, U64 endt_us) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(&arena, 1); - CTRL_Unwind unwind = {0}; - unwind.flags |= CTRL_UnwindFlag_Error; - - //- rjf: unpack args - CTRL_Entity *thread_entity = ctrl_entity_from_machine_id_handle(store, machine_id, thread); - CTRL_Entity *process_entity = thread_entity->parent; - Architecture arch = thread_entity->arch; - U64 arch_reg_block_size = regs_block_size_from_architecture(arch); - - //- rjf: grab initial register block - void *regs_block = ctrl_query_cached_reg_block_from_thread(scratch.arena, store, machine_id, thread); - B32 regs_block_good = (arch != Architecture_Null && regs_block != 0); - - //- rjf: loop & unwind - CTRL_UnwindFrameNode *first_frame_node = 0; - CTRL_UnwindFrameNode *last_frame_node = 0; - U64 frame_node_count = 0; - if(regs_block_good) - { - unwind.flags = 0; - for(;;) - { - // rjf: regs -> rip*module - U64 rip = regs_rip_from_arch_block(arch, regs_block); - DMN_Handle module = {0}; - for(CTRL_Entity *m = process_entity->first; m != &ctrl_entity_nil; m = m->next) - { - if(m->kind == CTRL_EntityKind_Module && contains_1u64(m->vaddr_range, rip)) - { - module = m->handle; - break; - } - } - - // rjf: cancel on 0 rip - if(rip == 0) - { - break; - } - - // rjf: valid step -> push frame - CTRL_UnwindFrameNode *frame_node = push_array(scratch.arena, CTRL_UnwindFrameNode, 1); - CTRL_UnwindFrame *frame = &frame_node->v; - frame->regs = push_array_no_zero(arena, U8, arch_reg_block_size); - MemoryCopy(frame->regs, regs_block, arch_reg_block_size); - DLLPushBack(first_frame_node, last_frame_node, frame_node); - frame_node_count += 1; - - // rjf: unwind one step - CTRL_UnwindStepResult step = ctrl_unwind_step(store, machine_id, process_entity->handle, module, arch, regs_block, endt_us); - unwind.flags |= step.flags; - if(step.flags & CTRL_UnwindFlag_Error || - regs_rsp_from_arch_block(arch, regs_block) == 0 || - regs_rip_from_arch_block(arch, regs_block) == 0 || - regs_rip_from_arch_block(arch, regs_block) == rip) - { - break; - } - } - } - - //- rjf: bake frames list into result array - { - unwind.frames.count = frame_node_count; - unwind.frames.v = push_array(arena, CTRL_UnwindFrame, unwind.frames.count); - U64 idx = 0; - for(CTRL_UnwindFrameNode *n = first_frame_node; n != 0; n = n->next, idx += 1) - { - unwind.frames.v[idx] = n->v; - } - } - - scratch_end(scratch); - ProfEnd(); - return unwind; -} - -//////////////////////////////// -//~ rjf: Halting All Attached Processes - -internal void -ctrl_halt(void) -{ - dmn_halt(0, 0); -} - -//////////////////////////////// -//~ rjf: Shared Accessor Functions - -//- rjf: run generation counter - -internal U64 -ctrl_run_gen(void) -{ - U64 result = dmn_run_gen(); - return result; -} - -internal U64 -ctrl_mem_gen(void) -{ - U64 result = dmn_mem_gen(); - return result; -} - -internal U64 -ctrl_reg_gen(void) -{ - U64 result = dmn_reg_gen(); - return result; -} - -//- rjf: name -> register/alias hash tables, for eval - -internal EVAL_String2NumMap * -ctrl_string2reg_from_arch(Architecture arch) -{ - return &ctrl_state->arch_string2reg_tables[arch]; -} - -internal EVAL_String2NumMap * -ctrl_string2alias_from_arch(Architecture arch) -{ - return &ctrl_state->arch_string2alias_tables[arch]; -} - -//////////////////////////////// -//~ rjf: Control-Thread Functions - -//- rjf: user -> control thread communication - -internal B32 -ctrl_u2c_push_msgs(CTRL_MsgList *msgs, U64 endt_us) -{ - Temp scratch = scratch_begin(0, 0); - String8 msgs_srlzed_baked = ctrl_serialized_string_from_msg_list(scratch.arena, msgs); - B32 good = 0; - OS_MutexScope(ctrl_state->u2c_ring_mutex) for(;;) - { - U64 unconsumed_size = (ctrl_state->u2c_ring_write_pos-ctrl_state->u2c_ring_read_pos); - U64 available_size = ctrl_state->u2c_ring_size-unconsumed_size; - if(available_size >= sizeof(U64) + msgs_srlzed_baked.size) - { - ctrl_state->u2c_ring_write_pos += ring_write_struct(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_write_pos, &msgs_srlzed_baked.size); - ctrl_state->u2c_ring_write_pos += ring_write(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_write_pos, msgs_srlzed_baked.str, msgs_srlzed_baked.size); - ctrl_state->u2c_ring_write_pos += 7; - ctrl_state->u2c_ring_write_pos -= ctrl_state->u2c_ring_write_pos%8; - good = 1; - break; - } - if(os_now_microseconds() >= endt_us) - { - break; - } - os_condition_variable_wait(ctrl_state->u2c_ring_cv, ctrl_state->u2c_ring_mutex, endt_us); - } - if(good) - { - os_condition_variable_broadcast(ctrl_state->u2c_ring_cv); - } - scratch_end(scratch); - return good; -} - -internal CTRL_MsgList -ctrl_u2c_pop_msgs(Arena *arena) -{ - Temp scratch = scratch_begin(&arena, 1); - String8 msgs_srlzed_baked = {0}; - OS_MutexScope(ctrl_state->u2c_ring_mutex) for(;;) - { - U64 unconsumed_size = (ctrl_state->u2c_ring_write_pos-ctrl_state->u2c_ring_read_pos); - if(unconsumed_size >= sizeof(U64)) - { - U64 size_to_decode = 0; - ctrl_state->u2c_ring_read_pos += ring_read_struct(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_read_pos, &size_to_decode); - msgs_srlzed_baked.size = size_to_decode; - msgs_srlzed_baked.str = push_array_no_zero(scratch.arena, U8, msgs_srlzed_baked.size); - ctrl_state->u2c_ring_read_pos += ring_read(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_read_pos, msgs_srlzed_baked.str, size_to_decode); - ctrl_state->u2c_ring_read_pos += 7; - ctrl_state->u2c_ring_read_pos -= ctrl_state->u2c_ring_read_pos%8; - break; - } - os_condition_variable_wait(ctrl_state->u2c_ring_cv, ctrl_state->u2c_ring_mutex, max_U64); - } - os_condition_variable_broadcast(ctrl_state->u2c_ring_cv); - CTRL_MsgList msgs = ctrl_msg_list_from_serialized_string(arena, msgs_srlzed_baked); - scratch_end(scratch); - return msgs; -} - -//- rjf: control -> user thread communication - -internal void -ctrl_c2u_push_events(CTRL_EventList *events) -{ - if(events->count != 0) ProfScope("ctrl_c2u_push_events") - { - ctrl_entity_store_apply_events(ctrl_state->ctrl_thread_entity_store, events); - for(CTRL_EventNode *n = events->first; n != 0; n = n ->next) - { - Temp scratch = scratch_begin(0, 0); - String8 event_srlzed = ctrl_serialized_string_from_event(scratch.arena, &n->v, ctrl_state->c2u_ring_size-sizeof(U64)); - OS_MutexScope(ctrl_state->c2u_ring_mutex) for(;;) - { - U64 unconsumed_size = (ctrl_state->c2u_ring_write_pos-ctrl_state->c2u_ring_read_pos); - U64 available_size = ctrl_state->c2u_ring_size-unconsumed_size; - if(available_size >= sizeof(U64) + event_srlzed.size) - { - ctrl_state->c2u_ring_write_pos += ring_write_struct(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_write_pos, &event_srlzed.size); - ctrl_state->c2u_ring_write_pos += ring_write(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_write_pos, event_srlzed.str, event_srlzed.size); - ctrl_state->c2u_ring_write_pos += 7; - ctrl_state->c2u_ring_write_pos -= ctrl_state->c2u_ring_write_pos%8; - break; - } - os_condition_variable_wait(ctrl_state->c2u_ring_cv, ctrl_state->c2u_ring_mutex, os_now_microseconds()+100); - } - os_condition_variable_broadcast(ctrl_state->c2u_ring_cv); - if(ctrl_state->wakeup_hook != 0) - { - ctrl_state->wakeup_hook(); - } - scratch_end(scratch); - } - } -} - -internal CTRL_EventList -ctrl_c2u_pop_events(Arena *arena) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(&arena, 1); - CTRL_EventList events = {0}; - OS_MutexScope(ctrl_state->c2u_ring_mutex) for(;;) - { - U64 unconsumed_size = (ctrl_state->c2u_ring_write_pos-ctrl_state->c2u_ring_read_pos); - if(unconsumed_size >= sizeof(U64)) - { - U64 size_to_decode = 0; - ctrl_state->c2u_ring_read_pos += ring_read_struct(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_read_pos, &size_to_decode); - String8 event_srlzed = {0}; - event_srlzed.size = size_to_decode; - event_srlzed.str = push_array_no_zero(scratch.arena, U8, event_srlzed.size); - ctrl_state->c2u_ring_read_pos += ring_read(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_read_pos, event_srlzed.str, event_srlzed.size); - ctrl_state->c2u_ring_read_pos += 7; - ctrl_state->c2u_ring_read_pos -= ctrl_state->c2u_ring_read_pos%8; - CTRL_Event *new_event = ctrl_event_list_push(arena, &events); - *new_event = ctrl_event_from_serialized_string(arena, event_srlzed); - } - else - { - break; - } - } - os_condition_variable_broadcast(ctrl_state->c2u_ring_cv); - scratch_end(scratch); - ProfEnd(); - return events; -} - -//- rjf: entry point - -internal void -ctrl_thread__entry_point(void *p) -{ - ThreadNameF("[ctrl] thread"); - ProfBeginFunction(); - DMN_CtrlCtx *ctrl_ctx = dmn_ctrl_begin(); - log_select(ctrl_state->ctrl_thread_log); - - //- rjf: loop - Temp scratch = scratch_begin(0, 0); - for(;;) - { - temp_end(scratch); - log_scope_begin(); - - //- rjf: get next messages - CTRL_MsgList msgs = ctrl_u2c_pop_msgs(scratch.arena); - - //- rjf: process messages - DMN_CtrlExclusiveAccessScope - { - B32 done = 0; - for(CTRL_MsgNode *msg_n = msgs.first; msg_n != 0 && done == 0; msg_n = msg_n->next) - { - CTRL_Msg *msg = &msg_n->v; - { - log_infof("user2ctrl_msg:{kind:\"%S\"}\n", ctrl_string_from_msg_kind(msg->kind)); - } - MemoryCopyArray(ctrl_state->exception_code_filters, msg->exception_code_filters); - switch(msg->kind) - { - case CTRL_MsgKind_Null: - case CTRL_MsgKind_COUNT:{}break; - - //- rjf: target operations - case CTRL_MsgKind_Launch: {ctrl_thread__launch (ctrl_ctx, msg);}break; - case CTRL_MsgKind_Attach: {ctrl_thread__attach (ctrl_ctx, msg);}break; - case CTRL_MsgKind_Kill: {ctrl_thread__kill (ctrl_ctx, msg);}break; - case CTRL_MsgKind_Detach: {ctrl_thread__detach (ctrl_ctx, msg);}break; - case CTRL_MsgKind_Run: {ctrl_thread__run (ctrl_ctx, msg); done = 1;}break; - case CTRL_MsgKind_SingleStep: {ctrl_thread__single_step (ctrl_ctx, msg); done = 1;}break; - - //- rjf: configuration - case CTRL_MsgKind_SetUserEntryPoints: - { - arena_clear(ctrl_state->user_entry_point_arena); - MemoryZeroStruct(&ctrl_state->user_entry_points); - for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) - { - str8_list_push(ctrl_state->user_entry_point_arena, &ctrl_state->user_entry_points, n->string); - } - }break; - case CTRL_MsgKind_SetModuleDebugInfoPath: - { - String8 path = msg->path; - CTRL_Entity *module = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, msg->machine_id, msg->entity); - CTRL_Entity *debug_info_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); - DI_Key old_dbgi_key = {debug_info_path->string, debug_info_path->timestamp}; - di_close(&old_dbgi_key); - ctrl_entity_equip_string(ctrl_state->ctrl_thread_entity_store, debug_info_path, path); - U64 new_dbgi_timestamp = os_properties_from_file_path(path).modified; - debug_info_path->timestamp = new_dbgi_timestamp; - DI_Key new_dbgi_key = {debug_info_path->string, new_dbgi_timestamp}; - di_open(&new_dbgi_key); - CTRL_EventList evts = {0}; - CTRL_Event *evt = ctrl_event_list_push(scratch.arena, &evts); - evt->kind = CTRL_EventKind_ModuleDebugInfoPathChange; - evt->machine_id = msg->machine_id; - evt->entity = msg->entity; - evt->string = path; - evt->timestamp = new_dbgi_timestamp; - ctrl_c2u_push_events(&evts); - }break; - } - } - } - - //- rjf: gather & output logs - LogScopeResult log = log_scope_end(scratch.arena); - ctrl_thread__flush_info_log(log.strings[LogMsgKind_Info]); - if(log.strings[LogMsgKind_UserError].size != 0) - { - CTRL_EventList evts = {0}; - CTRL_Event *evt = ctrl_event_list_push(scratch.arena, &evts); - evt->kind = CTRL_EventKind_Error; - evt->string = log.strings[LogMsgKind_UserError]; - ctrl_c2u_push_events(&evts); - } - } - - scratch_end(scratch); - ProfEnd(); -} - -//- rjf: breakpoint resolution - -internal void -ctrl_thread__append_resolved_module_user_bp_traps(Arena *arena, CTRL_MachineID machine_id, DMN_Handle process, DMN_Handle module, CTRL_UserBreakpointList *user_bps, DMN_TrapChunkList *traps_out) -{ - Temp scratch = scratch_begin(&arena, 1); - DI_Scope *di_scope = di_scope_open(); - CTRL_Entity *module_entity = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, machine_id, module); - CTRL_Entity *debug_info_path_entity = ctrl_entity_child_from_kind(module_entity, CTRL_EntityKind_DebugInfoPath); - DI_Key dbgi_key = {debug_info_path_entity->string, debug_info_path_entity->timestamp}; - RDI_Parsed *rdi = di_rdi_from_key(di_scope, &dbgi_key, max_U64); - U64 base_vaddr = module_entity->vaddr_range.min; - for(CTRL_UserBreakpointNode *n = user_bps->first; n != 0; n = n->next) - { - CTRL_UserBreakpoint *bp = &n->v; - switch(bp->kind) - { - default:{}break; - - //- rjf: file:line-based breakpoints - case CTRL_UserBreakpointKind_FileNameAndLineColNumber: - { - // rjf: unpack & normalize - TxtPt pt = bp->pt; - String8 filename = bp->string; - String8 filename_normalized = push_str8_copy(scratch.arena, filename); - for(U64 idx = 0; idx < filename_normalized.size; idx += 1) - { - filename_normalized.str[idx] = char_to_lower(filename_normalized.str[idx]); - filename_normalized.str[idx] = char_to_correct_slash(filename_normalized.str[idx]); - } - - // rjf: filename -> src_id - U32 src_id = 0; - { - RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_NormalSourcePaths); - if(mapptr != 0) - { - RDI_ParsedNameMap map = {0}; - rdi_parsed_from_name_map(rdi, mapptr, &map); - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, filename_normalized.str, filename_normalized.size); - if(node != 0) - { - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - src_id = ids[0]; - } - } - } - } - - // rjf: src_id * pt -> push - { - RDI_SourceFile *src = rdi_element_from_name_idx(rdi, SourceFiles, src_id); - RDI_SourceLineMap *src_line_map = rdi_element_from_name_idx(rdi, SourceLineMaps, src->source_line_map_idx); - RDI_ParsedSourceLineMap line_map = {0}; - rdi_parsed_from_source_line_map(rdi, src_line_map, &line_map); - U32 voff_count = 0; - U64 *voffs = rdi_line_voffs_from_num(&line_map, pt.line, &voff_count); - for(U32 i = 0; i < voff_count; i += 1) - { - U64 vaddr = voffs[i] + base_vaddr; - DMN_Trap trap = {process, vaddr, (U64)bp}; - dmn_trap_chunk_list_push(arena, traps_out, 256, &trap); - } - } - }break; - - //- rjf: symbol:voff-based breakpoints - case CTRL_UserBreakpointKind_SymbolNameAndOffset: - { - String8 symbol_name = bp->string; - U64 voff = bp->u64; - RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_Procedures); - RDI_ParsedNameMap map = {0}; - rdi_parsed_from_name_map(rdi, mapptr, &map); - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, symbol_name.str, symbol_name.size); - if(node != 0) - { - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - for(U32 match_i = 0; match_i < id_count; match_i += 1) - { - RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, ids[match_i]); - U64 proc_voff = rdi_first_voff_from_procedure(rdi, procedure); - U64 proc_vaddr = proc_voff + base_vaddr; - DMN_Trap trap = {process, proc_vaddr + voff, (U64)bp}; - dmn_trap_chunk_list_push(arena, traps_out, 256, &trap); - } - } - }break; - } - } - di_scope_close(di_scope); - scratch_end(scratch); -} - -internal void -ctrl_thread__append_resolved_process_user_bp_traps(Arena *arena, CTRL_MachineID machine_id, DMN_Handle process, CTRL_UserBreakpointList *user_bps, DMN_TrapChunkList *traps_out) -{ - for(CTRL_UserBreakpointNode *n = user_bps->first; n != 0; n = n->next) - { - CTRL_UserBreakpoint *bp = &n->v; - if(bp->kind == CTRL_UserBreakpointKind_VirtualAddress) - { - DMN_Trap trap = {process, bp->u64, (U64)bp}; - dmn_trap_chunk_list_push(arena, traps_out, 256, &trap); - } - } -} - -//- rjf: module lifetime open/close work - -internal void -ctrl_thread__module_open(CTRL_MachineID machine_id, DMN_Handle process, DMN_Handle module, Rng1U64 vaddr_range, String8 path) -{ - ////////////////////////////// - //- rjf: parse module image info - // - Arena *arena = arena_alloc(); - PE_IntelPdata *pdatas = 0; - U64 pdatas_count = 0; - U64 entry_point_voff = 0; - Rng1U64 tls_vaddr_range = {0}; - String8 builtin_debug_info_path = {0}; - ProfScope("unpack relevant PE info") - { - B32 is_valid = 1; - - //- rjf: read DOS header - PE_DosHeader dos_header = {0}; - if(is_valid) - { - if(!dmn_process_read_struct(process, vaddr_range.min, &dos_header) || - dos_header.magic != PE_DOS_MAGIC) - { - is_valid = 0; - } - } - - //- rjf: read PE magic - U32 pe_magic = 0; - if(is_valid) - { - if(!dmn_process_read_struct(process, vaddr_range.min + dos_header.coff_file_offset, &pe_magic) || - pe_magic != PE_MAGIC) - { - is_valid = 0; - } - } - - //- rjf: read COFF header - U64 coff_header_off = dos_header.coff_file_offset + sizeof(pe_magic); - COFF_Header coff_header = {0}; - if(is_valid) - { - if(!dmn_process_read_struct(process, vaddr_range.min + coff_header_off, &coff_header)) - { - is_valid = 0; - } - } - - //- rjf: unpack range of optional extension header - U32 opt_ext_size = coff_header.optional_header_size; - Rng1U64 opt_ext_off_range = r1u64(coff_header_off + sizeof(coff_header), - coff_header_off + sizeof(coff_header) + opt_ext_size); - - //- rjf: read optional header - U16 optional_magic = 0; - U64 image_base = 0; - U64 entry_point = 0; - U32 data_dir_count = 0; - U64 virt_section_align = 0; - U64 file_section_align = 0; - Rng1U64 *data_dir_franges = 0; - if(opt_ext_size > 0) - { - // rjf: read magic number - U16 opt_ext_magic = 0; - dmn_process_read_struct(process, vaddr_range.min + opt_ext_off_range.min, &opt_ext_magic); - - // rjf: read info - U32 reported_data_dir_offset = 0; - U32 reported_data_dir_count = 0; - switch(opt_ext_magic) - { - case PE_PE32_MAGIC: - { - PE_OptionalHeader32 pe_optional = {0}; - dmn_process_read_struct(process, vaddr_range.min + opt_ext_off_range.min, &pe_optional); - image_base = pe_optional.image_base; - entry_point = pe_optional.entry_point_va; - virt_section_align = pe_optional.section_alignment; - file_section_align = pe_optional.file_alignment; - reported_data_dir_offset = sizeof(pe_optional); - reported_data_dir_count = pe_optional.data_dir_count; - }break; - case PE_PE32PLUS_MAGIC: - { - PE_OptionalHeader32Plus pe_optional = {0}; - dmn_process_read_struct(process, vaddr_range.min + opt_ext_off_range.min, &pe_optional); - image_base = pe_optional.image_base; - entry_point = pe_optional.entry_point_va; - virt_section_align = pe_optional.section_alignment; - file_section_align = pe_optional.file_alignment; - reported_data_dir_offset = sizeof(pe_optional); - reported_data_dir_count = pe_optional.data_dir_count; - }break; - } - - // rjf: find number of data directories - U32 data_dir_max = (opt_ext_size - reported_data_dir_offset) / sizeof(PE_DataDirectory); - data_dir_count = ClampTop(reported_data_dir_count, data_dir_max); - - // rjf: grab pdatas from exceptions section - if(data_dir_count > PE_DataDirectoryIndex_EXCEPTIONS) - { - PE_DataDirectory dir = {0}; - dmn_process_read_struct(process, vaddr_range.min + opt_ext_off_range.min + reported_data_dir_offset + sizeof(PE_DataDirectory)*PE_DataDirectoryIndex_EXCEPTIONS, &dir); - Rng1U64 pdatas_voff_range = r1u64((U64)dir.virt_off, (U64)dir.virt_off + (U64)dir.virt_size); - pdatas_count = dim_1u64(pdatas_voff_range)/sizeof(PE_IntelPdata); - pdatas = push_array(arena, PE_IntelPdata, pdatas_count); - dmn_process_read(process, r1u64(vaddr_range.min + pdatas_voff_range.min, vaddr_range.min + pdatas_voff_range.max), pdatas); - } - - // rjf: extract tls header - PE_TLSHeader64 tls_header = {0}; - if(data_dir_count > PE_DataDirectoryIndex_TLS) - { - PE_DataDirectory dir = {0}; - dmn_process_read_struct(process, vaddr_range.min + opt_ext_off_range.min + reported_data_dir_offset + sizeof(PE_DataDirectory)*PE_DataDirectoryIndex_TLS, &dir); - Rng1U64 tls_voff_range = r1u64((U64)dir.virt_off, (U64)dir.virt_off + (U64)dir.virt_size); - switch(coff_header.machine) - { - default:{}break; - case COFF_MachineType_X86: - { - PE_TLSHeader32 tls_header32 = {0}; - dmn_process_read_struct(process, vaddr_range.min + tls_voff_range.min, &tls_header32); - tls_header.raw_data_start = (U64)tls_header32.raw_data_start; - tls_header.raw_data_end = (U64)tls_header32.raw_data_end; - tls_header.index_address = (U64)tls_header32.index_address; - tls_header.callbacks_address = (U64)tls_header32.callbacks_address; - tls_header.zero_fill_size = (U64)tls_header32.zero_fill_size; - tls_header.characteristics = (U64)tls_header32.characteristics; - }break; - case COFF_MachineType_X64: - { - dmn_process_read_struct(process, vaddr_range.min + tls_voff_range.min, &tls_header); - }break; - } - } - - // rjf: grab entry point vaddr - entry_point_voff = entry_point; - - // rjf: calculate TLS vaddr range - tls_vaddr_range = r1u64(tls_header.index_address, tls_header.index_address+sizeof(U32)); - - // rjf: grab data about debug info - U32 dbg_time = 0; - U32 dbg_age = 0; - OS_Guid dbg_guid = {0}; - if(data_dir_count > PE_DataDirectoryIndex_DEBUG) - { - // rjf: read data dir - PE_DataDirectory dir = {0}; - dmn_process_read_struct(process, vaddr_range.min + opt_ext_off_range.min + reported_data_dir_offset + sizeof(PE_DataDirectory)*PE_DataDirectoryIndex_DEBUG, &dir); - - // rjf: read debug directory - PE_DebugDirectory dbg_data = {0}; - dmn_process_read_struct(process, vaddr_range.min+(U64)dir.virt_off, &dbg_data); - - // rjf: extract external file info from codeview header - if(dbg_data.type == PE_DebugDirectoryType_CODEVIEW) - { - U64 dbg_path_off = 0; - U64 dbg_path_size = 0; - U64 cv_offset = dbg_data.voff; - U32 cv_magic = 0; - dmn_process_read_struct(process, vaddr_range.min+cv_offset, &cv_magic); - switch(cv_magic) - { - default:break; - case PE_CODEVIEW_PDB20_MAGIC: - { - PE_CvHeaderPDB20 cv = {0}; - dmn_process_read_struct(process, vaddr_range.min+cv_offset, &cv); - dbg_time = cv.time; - dbg_age = cv.age; - dbg_path_off = cv_offset + sizeof(cv); - }break; - case PE_CODEVIEW_PDB70_MAGIC: - { - PE_CvHeaderPDB70 cv = {0}; - dmn_process_read_struct(process, vaddr_range.min+cv_offset, &cv); - dbg_guid = cv.guid; - dbg_age = cv.age; - dbg_path_off = cv_offset + sizeof(cv); - }break; - } - if(dbg_path_off > 0) - { - Temp scratch = scratch_begin(0, 0); - String8List parts = {0}; - for(U64 off = dbg_path_off;; off += 256) - { - U8 bytes[256] = {0}; - dmn_process_read(process, r1u64(vaddr_range.min+off, vaddr_range.min+off+sizeof(bytes)), bytes); - U64 size = cstring8_length(&bytes[0]); - String8 part = str8(bytes, size); - str8_list_push(scratch.arena, &parts, part); - if(size < sizeof(bytes)) - { - break; - } - } - builtin_debug_info_path = str8_list_join(arena, &parts, 0); - scratch_end(scratch); - } - } - } - } - } - - ////////////////////////////// - //- rjf: pick default initial debug info path - // - String8 initial_debug_info_path = builtin_debug_info_path; - { - Temp scratch = scratch_begin(0, 0); - String8 exe_folder = str8_chop_last_slash(path); - String8 builtin_debug_info_path__absolute = builtin_debug_info_path; - String8 builtin_debug_info_path__relative = push_str8f(scratch.arena, "%S/%S", exe_folder, builtin_debug_info_path); - String8 dbg_path_candidates[] = - { - /* inferred (treated as relative): */ builtin_debug_info_path__relative, - /* inferred (treated as absolute): */ builtin_debug_info_path__absolute, - /* "foo.exe" -> "foo.pdb" */ push_str8f(scratch.arena, "%S.pdb", str8_chop_last_dot(path)), - /* "foo.exe" -> "foo.exe.pdb" */ push_str8f(scratch.arena, "%S.pdb", path), - }; - for(U64 idx = 0; idx < ArrayCount(dbg_path_candidates); idx += 1) - { - FileProperties props = os_properties_from_file_path(dbg_path_candidates[idx]); - if(props.modified != 0 && props.size != 0) - { - initial_debug_info_path = push_str8_copy(arena, dbg_path_candidates[idx]); - break; - } - } - scratch_end(scratch); - } - - ////////////////////////////// - //- rjf: insert info into cache - // - { - U64 hash = ctrl_hash_from_machine_id_handle(machine_id, module); - U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; - U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; - CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; - CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; - OS_MutexScopeW(stripe->rw_mutex) - { - CTRL_ModuleImageInfoCacheNode *node = 0; - for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->module, module)) - { - node = n; - break; - } - } - if(!node) - { - node = push_array(arena, CTRL_ModuleImageInfoCacheNode, 1); - DLLPushBack(slot->first, slot->last, node); - node->machine_id = machine_id; - node->module = module; - node->arena = arena; - node->pdatas = pdatas; - node->pdatas_count = pdatas_count; - node->entry_point_voff = entry_point_voff; - node->initial_debug_info_path = initial_debug_info_path; - } - } - } -} - -internal void -ctrl_thread__module_close(CTRL_MachineID machine_id, DMN_Handle module) -{ - ////////////////////////////// - //- rjf: evict module image info from cache - // - { - U64 hash = ctrl_hash_from_machine_id_handle(machine_id, module); - U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; - U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; - CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; - CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; - OS_MutexScopeW(stripe->rw_mutex) - { - CTRL_ModuleImageInfoCacheNode *node = 0; - for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->module, module)) - { - node = n; - break; - } - } - if(node) - { - DLLRemove(slot->first, slot->last, node); - arena_release(node->arena); - } - } - } -} - -//- rjf: attached process running/event gathering - -internal DMN_Event * -ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg, DMN_RunCtrls *run_ctrls, CTRL_Spoof *spoof) -{ - ProfBeginFunction(); - DMN_Event *event = push_array(arena, DMN_Event, 1); - Temp scratch = scratch_begin(&arena, 1); - - //- rjf: loop -> try to get event, run, repeat - U64 spoof_old_ip_value = 0; - ProfScope("loop -> try to get event, run, repeat") for(B32 got_event = 0; got_event == 0;) - { - //- rjf: get next event - ProfScope("get next event") - { - // rjf: grab first event - DMN_EventNode *next_event_node = ctrl_state->first_dmn_event_node; - - // rjf: log event - if(next_event_node != 0) - { - DMN_Event *ev = &next_event_node->v; - LogInfoNamedBlockF("dmn_event") - { - log_infof("kind: %S\n", dmn_event_kind_string_table[ev->kind]); - log_infof("exception_kind: %S\n", dmn_exception_kind_string_table[ev->exception_kind]); - log_infof("process: [%I64u]\n", ev->process.u64[0]); - log_infof("thread: [%I64u]\n", ev->thread.u64[0]); - log_infof("module: [%I64u]\n", ev->module.u64[0]); - log_infof("arch: %S\n", string_from_architecture(ev->arch)); - log_infof("address: 0x%I64x\n", ev->address); - log_infof("string: \"%S\"\n", ev->string); - log_infof("ip_vaddr: 0x%I64x\n", ev->instruction_pointer); - } - } - - // rjf: determine if we should filter - B32 should_filter_event = 0; - if(next_event_node != 0) - { - DMN_Event *ev = &next_event_node->v; - switch(ev->kind) - { - default:{}break; - case DMN_EventKind_Exception: - { - // NOTE(rjf): first chance exceptions -> try ignoring - should_filter_event = (ev->exception_repeated == 0 && (spoof == 0 || ev->instruction_pointer != spoof->new_ip_value)); - - // rjf: exception code -> kind - CTRL_ExceptionCodeKind code_kind = CTRL_ExceptionCodeKind_Null; - if(should_filter_event) - { - for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)0; k < CTRL_ExceptionCodeKind_COUNT; k = (CTRL_ExceptionCodeKind)(k+1)) - { - if(ctrl_exception_code_kind_code_table[k] == ev->code) - { - code_kind = k; - break; - } - } - } - - // rjf: exception code kind -> shouldn't stop? if so, do not filter - if(should_filter_event) - { - B32 shouldnt_filter = !!(ctrl_state->exception_code_filters[code_kind/64] & (1ull<<(code_kind%64))); - if(should_filter_event && shouldnt_filter) - { - should_filter_event = 0; - } - } - - // rjf: special case: be gracious with ASan modules or symbols if - // they do their cute little 0xc0000005 exception trick... - if(!should_filter_event && ev->code == 0xc0000005 && - (spoof == 0 || ev->instruction_pointer != spoof->new_ip_value)) - { - DI_Scope *di_scope = di_scope_open(); - CTRL_Entity *process = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, CTRL_MachineID_Local, ev->process); - CTRL_Entity *module = &ctrl_entity_nil; - for(CTRL_Entity *child = process->first; child != &ctrl_entity_nil; child = child->next) - { - if(child->kind == CTRL_EntityKind_Module) - { - module = child; - break; - } - } - if(module != &ctrl_entity_nil) - { - // rjf: determine base address of asan shadow space - U64 asan_shadow_base_vaddr = 0; - B32 asan_shadow_variable_exists_but_is_zero = 0; - CTRL_Entity *dbg_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); - DI_Key dbgi_key = {dbg_path->string, dbg_path->timestamp}; - RDI_Parsed *rdi = di_rdi_from_key(di_scope, &dbgi_key, max_U64); - RDI_NameMap *unparsed_map = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_GlobalVariables); - { - RDI_ParsedNameMap map = {0}; - rdi_parsed_from_name_map(rdi, unparsed_map, &map); - String8 name = str8_lit("__asan_shadow_memory_dynamic_address"); - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); - if(node != 0) - { - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - RDI_GlobalVariable *global_var = rdi_element_from_name_idx(rdi, GlobalVariables, ids[0]); - U64 global_var_voff = global_var->voff; - U64 global_var_vaddr = global_var->voff + module->vaddr_range.min; - Architecture arch = process->arch; - U64 addr_size = bit_size_from_arch(arch)/8; - dmn_process_read(ev->process, r1u64(global_var_vaddr, global_var_vaddr+addr_size), &asan_shadow_base_vaddr); - asan_shadow_variable_exists_but_is_zero = (asan_shadow_base_vaddr == 0); - } - } - } - - // rjf: determine if this was a read/write to the shadow space - B32 violation_in_shadow_space = 0; - if(asan_shadow_base_vaddr != 0) - { - U64 asan_shadow_space_size = TB(128)/8; - if(asan_shadow_base_vaddr <= ev->address && ev->address < asan_shadow_base_vaddr+asan_shadow_space_size) - { - violation_in_shadow_space = 1; - } - } - - // rjf: filter event if this violation occurred in asan's shadow space - if(violation_in_shadow_space || asan_shadow_variable_exists_but_is_zero) - { - should_filter_event = 1; - } - } - - di_scope_close(di_scope); - } - }break; - } - } - - // rjf: good event & unfiltered? -> pop from queue & grab as result - if(next_event_node != 0 && !should_filter_event) - { - got_event = 1; - SLLQueuePop(ctrl_state->first_dmn_event_node, ctrl_state->last_dmn_event_node); - MemoryCopyStruct(event, &next_event_node->v); - event->string = push_str8_copy(arena, event->string); - run_ctrls->ignore_previous_exception = 1; - } - - // rjf: good event but filtered? pop from queue - if(next_event_node != 0 && should_filter_event) - { - SLLQueuePop(ctrl_state->first_dmn_event_node, ctrl_state->last_dmn_event_node); - run_ctrls->ignore_previous_exception = 0; - } - } - - //- rjf: no event -> dmn_ctrl_run for a new one - if(got_event == 0) ProfScope("no event -> dmn_ctrl_run for a new one") - { - // rjf: prep spoof - B32 do_spoof = (spoof != 0 && dmn_handle_match(run_ctrls->single_step_thread, dmn_handle_zero())); - U64 size_of_spoof = 0; - if(do_spoof) ProfScope("prep spoof") - { - CTRL_Entity *spoof_process = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, CTRL_MachineID_Local, spoof->process); - Architecture arch = spoof_process->arch; - size_of_spoof = bit_size_from_arch(arch)/8; - dmn_process_read(spoof_process->handle, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof_old_ip_value); - } - - // rjf: set spoof - if(do_spoof) ProfScope("set spoof") - { - dmn_process_write(spoof->process, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof->new_ip_value); - } - - // rjf: run for new events - ProfScope("run for new events") - { - LogInfoNamedBlockF("dmn_ctrl_run") - { - log_infof("single_step_thread: [0x%I64x]\n", run_ctrls->single_step_thread); - log_infof("ignore_previous_exception: %i\n", !!run_ctrls->ignore_previous_exception); - log_infof("run_entities_are_unfrozen: %i\n", !!run_ctrls->run_entities_are_unfrozen); - log_infof("run_entities_are_processes: %i\n", !!run_ctrls->run_entities_are_processes); - log_infof("run_entity_count: %I64u\n", run_ctrls->run_entity_count); - LogInfoNamedBlockF("run_entities") for(U64 idx = 0; idx < run_ctrls->run_entity_count; idx += 1) - { - log_infof("[0x%I64x]\n", run_ctrls->run_entities[idx]); - } - log_infof("trap_count: %I64u\n", run_ctrls->traps.trap_count); - LogInfoNamedBlockF("traps") for(DMN_TrapChunkNode *n = run_ctrls->traps.first; n != 0; n = n->next) - { - for(U64 idx = 0; idx < n->count; idx += 1) - { - log_infof("{process:[0x%I64x], vaddr:0x%I64x, id:0x%I64x}\n", n->v[idx].process.u64[0], n->v[idx].vaddr, n->v[idx].id); - } - } - } - DMN_EventList events = dmn_ctrl_run(scratch.arena, ctrl_ctx, run_ctrls); - for(DMN_EventNode *src_n = events.first; src_n != 0; src_n = src_n->next) - { - DMN_EventNode *dst_n = ctrl_state->free_dmn_event_node; - if(dst_n != 0) - { - SLLStackPop(ctrl_state->free_dmn_event_node); - } - else - { - dst_n = push_array(ctrl_state->dmn_event_arena, DMN_EventNode, 1); - } - MemoryCopyStruct(&dst_n->v, &src_n->v); - dst_n->v.string = push_str8_copy(ctrl_state->dmn_event_arena, dst_n->v.string); - SLLQueuePush(ctrl_state->first_dmn_event_node, ctrl_state->last_dmn_event_node, dst_n); - } - } - - // rjf: unset spoof - if(do_spoof) ProfScope("unset spoof") - { - dmn_process_write(spoof->process, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof_old_ip_value); - } - } - } - - //- rjf: irrespective of what event came back, we should ALWAYS check the - // spoof's thread and see if it hit the spoof address, because we may have - // simply been sent other debug events first - if(spoof != 0) - { - CTRL_Entity *thread = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, CTRL_MachineID_Local, spoof->thread); - Architecture arch = thread->arch; - void *regs_block = push_array(scratch.arena, U8, regs_block_size_from_architecture(arch)); - dmn_thread_read_reg_block(spoof->thread, regs_block); - U64 spoof_thread_rip = regs_rip_from_arch_block(arch, regs_block); - if(spoof_thread_rip == spoof->new_ip_value) - { - regs_arch_block_write_rip(arch, regs_block, spoof_old_ip_value); - ctrl_thread_write_reg_block(CTRL_MachineID_Local, spoof->thread, regs_block); - } - } - - //- rjf: push ctrl events associated with this demon event - CTRL_EventList evts = {0}; - ProfScope("push ctrl events associated with this demon event") switch(event->kind) - { - default:{}break; - case DMN_EventKind_CreateProcess: - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_NewProc; - out_evt->msg_id = msg->msg_id; - out_evt->machine_id = CTRL_MachineID_Local; - out_evt->entity = event->process; - out_evt->arch = event->arch; - out_evt->entity_id = event->code; - ctrl_state->process_counter += 1; - }break; - case DMN_EventKind_CreateThread: - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_NewThread; - out_evt->msg_id = msg->msg_id; - out_evt->machine_id = CTRL_MachineID_Local; - out_evt->entity = event->thread; - out_evt->parent = event->process; - out_evt->arch = event->arch; - out_evt->entity_id = event->code; - out_evt->stack_base = dmn_stack_base_vaddr_from_thread(event->thread); - out_evt->tls_root = dmn_tls_root_vaddr_from_thread(event->thread); - out_evt->rip_vaddr = event->instruction_pointer; - out_evt->string = event->string; - }break; - case DMN_EventKind_LoadModule: - { - CTRL_Event *out_evt1 = ctrl_event_list_push(scratch.arena, &evts); - String8 module_path = event->string; - U64 exe_timestamp = os_properties_from_file_path(module_path).modified; - ctrl_thread__module_open(CTRL_MachineID_Local, event->process, event->module, r1u64(event->address, event->address+event->size), module_path); - out_evt1->kind = CTRL_EventKind_NewModule; - out_evt1->msg_id = msg->msg_id; - out_evt1->machine_id = CTRL_MachineID_Local; - out_evt1->entity = event->module; - out_evt1->parent = event->process; - out_evt1->arch = event->arch; - out_evt1->entity_id = event->code; - out_evt1->vaddr_rng = r1u64(event->address, event->address+event->size); - out_evt1->rip_vaddr = event->address; - out_evt1->timestamp = exe_timestamp; - out_evt1->string = module_path; - CTRL_Event *out_evt2 = ctrl_event_list_push(scratch.arena, &evts); - String8 initial_debug_info_path = ctrl_initial_debug_info_path_from_module(scratch.arena, CTRL_MachineID_Local, event->module); - U64 debug_info_timestamp = os_properties_from_file_path(initial_debug_info_path).modified; - out_evt2->kind = CTRL_EventKind_ModuleDebugInfoPathChange; - out_evt2->msg_id = msg->msg_id; - out_evt2->machine_id = CTRL_MachineID_Local; - out_evt2->entity = event->module; - out_evt2->parent = event->process; - out_evt2->timestamp = debug_info_timestamp; - out_evt2->string = initial_debug_info_path; - DI_Key initial_dbgi_key = {initial_debug_info_path, debug_info_timestamp}; - di_open(&initial_dbgi_key); - }break; - case DMN_EventKind_ExitProcess: - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_EndProc; - out_evt->msg_id = msg->msg_id; - out_evt->machine_id = CTRL_MachineID_Local; - out_evt->entity = event->process; - out_evt->u64_code = event->code; - ctrl_state->process_counter -= 1; - }break; - case DMN_EventKind_ExitThread: - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_EndThread; - out_evt->msg_id = msg->msg_id; - out_evt->machine_id = CTRL_MachineID_Local; - out_evt->entity = event->thread; - out_evt->entity_id = event->code; - }break; - case DMN_EventKind_UnloadModule: - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - String8 module_path = event->string; - ctrl_thread__module_close(CTRL_MachineID_Local, event->module); - out_evt->kind = CTRL_EventKind_EndModule; - out_evt->msg_id = msg->msg_id; - out_evt->machine_id = CTRL_MachineID_Local; - out_evt->entity = event->module; - out_evt->string = module_path; - CTRL_Entity *module_ent = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, CTRL_MachineID_Local, event->module); - CTRL_Entity *debug_info_path_ent = ctrl_entity_child_from_kind(module_ent, CTRL_EntityKind_DebugInfoPath); - if(debug_info_path_ent != &ctrl_entity_nil) - { - DI_Key dbgi_key = {debug_info_path_ent->string, debug_info_path_ent->timestamp}; - di_close(&dbgi_key); - } - }break; - case DMN_EventKind_DebugString: - { - U64 num_strings = (event->string.size + ctrl_state->c2u_ring_max_string_size-1) / ctrl_state->c2u_ring_max_string_size; - for(U64 string_idx = 0; string_idx < num_strings; string_idx += 1) - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_DebugString; - out_evt->msg_id = msg->msg_id; - out_evt->machine_id = CTRL_MachineID_Local; - out_evt->entity = event->thread; - out_evt->parent = event->process; - out_evt->string = str8_substr(event->string, r1u64(string_idx*ctrl_state->c2u_ring_max_string_size, (string_idx+1)*ctrl_state->c2u_ring_max_string_size)); - } - }break; - case DMN_EventKind_SetThreadName: - { - CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); - out_evt->kind = CTRL_EventKind_ThreadName; - out_evt->msg_id = msg->msg_id; - out_evt->machine_id = CTRL_MachineID_Local; - out_evt->entity = event->thread; - out_evt->parent = event->process; - out_evt->string = event->string; - out_evt->entity_id = event->code; - }break; - } - ctrl_c2u_push_events(&evts); - - //- rjf: clear process memory cache, if we've just started a lone process - if(event->kind == DMN_EventKind_CreateProcess && ctrl_state->process_counter == 1) - { - CTRL_ProcessMemoryCache *cache = &ctrl_state->process_memory_cache; - for(U64 slot_idx = 0; slot_idx < cache->slots_count; slot_idx += 1) - { - U64 stripe_idx = slot_idx%cache->stripes_count; - CTRL_ProcessMemoryCacheSlot *slot = &cache->slots[slot_idx]; - CTRL_ProcessMemoryCacheStripe *stripe = &cache->stripes[stripe_idx]; - OS_MutexScopeW(stripe->rw_mutex) - { - for(CTRL_ProcessMemoryCacheNode *n = slot->first, *next = 0; n != 0; n = next) - { - next = n->next; - arena_clear(n->arena); - } - } - MemoryZeroStruct(slot); - } - } - - //- rjf: out of queued up demon events -> clear event arena - if(ctrl_state->first_dmn_event_node == 0) - { - ctrl_state->free_dmn_event_node = 0; - arena_clear(ctrl_state->dmn_event_arena); - } - - scratch_end(scratch); - ProfEnd(); - return(event); -} - -//- rjf: eval helpers - -internal B32 -ctrl_eval_memory_read(void *u, void *out, U64 addr, U64 size) -{ - DMN_Handle process = *(DMN_Handle *)u; - U64 read_size = dmn_process_read(process, r1u64(addr, addr+size), out); - B32 result = (read_size == size); - return result; -} - -//- rjf: log flusher - -internal void -ctrl_thread__flush_info_log(String8 string) -{ - os_append_data_to_file_path(ctrl_state->ctrl_thread_log_path, string); -} - -internal void -ctrl_thread__end_and_flush_info_log(void) -{ - Temp scratch = scratch_begin(0, 0); - LogScopeResult log = log_scope_end(scratch.arena); - ctrl_thread__flush_info_log(log.strings[LogMsgKind_Info]); - scratch_end(scratch); -} - -//- rjf: msg kind implementations - -internal void -ctrl_thread__launch(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) -{ - //- rjf: launch - OS_LaunchOptions opts = {0}; - { - opts.cmd_line = msg->cmd_line_string_list; - opts.path = msg->path; - opts.env = msg->env_string_list; - opts.inherit_env = msg->env_inherit; - } - U32 id = dmn_ctrl_launch(ctrl_ctx, &opts); - - //- rjf: record (id -> entry points), so that we know custom entry points for this PID - for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) - { - String8 string = n->string; - CTRL_Entity *entry = ctrl_entity_alloc(ctrl_state->ctrl_thread_entity_store, ctrl_state->ctrl_thread_entity_store->root, CTRL_EntityKind_EntryPoint, Architecture_Null, 0, dmn_handle_zero(), (U64)id); - ctrl_entity_equip_string(ctrl_state->ctrl_thread_entity_store, entry, string); - } -} - -internal void -ctrl_thread__attach(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - - //- rjf: attach - B32 attach_successful = dmn_ctrl_attach(ctrl_ctx, msg->entity_id); - - //- rjf: run to handshake - if(attach_successful) - { - DMN_Handle unfrozen_process = {0}; - DMN_RunCtrls run_ctrls = {0}; - run_ctrls.run_entities_are_unfrozen = 1; - run_ctrls.run_entities_are_processes = 1; - for(B32 done = 0; done == 0;) - { - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); - switch(event->kind) - { - default:{}break; - case DMN_EventKind_CreateProcess: - { - unfrozen_process = event->process; - run_ctrls.run_entities = &unfrozen_process; - run_ctrls.run_entity_count = 1; - }break; - case DMN_EventKind_Halt: - case DMN_EventKind_Exception: - case DMN_EventKind_Error: - case DMN_EventKind_HandshakeComplete: - { - done = 1; - }break; - } - } - } - - //- rjf: record stop - { - CTRL_EventList evts = {0}; - CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); - event->kind = CTRL_EventKind_Stopped; - event->cause = CTRL_EventCause_Finished; - event->machine_id = CTRL_MachineID_Local; - event->msg_id = msg->msg_id; - event->entity_id = !!attach_successful * msg->entity_id; - ctrl_c2u_push_events(&evts); - } - - scratch_end(scratch); - ProfEnd(); -} - -internal void -ctrl_thread__kill(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DMN_Handle process = msg->entity; - U32 exit_code = msg->exit_code; - - //- rjf: send kill - B32 kill_worked = dmn_ctrl_kill(ctrl_ctx, process, exit_code); - - //- rjf: wait for process to be dead - if(kill_worked) - { - DMN_RunCtrls run_ctrls = {0}; - run_ctrls.run_entities_are_unfrozen = 1; - run_ctrls.run_entities_are_processes = 1; - run_ctrls.run_entities = &process; - run_ctrls.run_entity_count = 1; - for(B32 done = 0; done == 0;) - { - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); - if(event->kind == DMN_EventKind_ExitProcess && dmn_handle_match(event->process, process)) - { - done = 1; - } - if(event->kind == DMN_EventKind_Halt) - { - done = 1; - } - } - } - - //- rjf: record stop - { - CTRL_EventList evts = {0}; - CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); - event->kind = CTRL_EventKind_Stopped; - event->cause = CTRL_EventCause_Finished; - event->machine_id = CTRL_MachineID_Local; - event->msg_id = msg->msg_id; - if(kill_worked) - { - event->entity = msg->entity; - } - ctrl_c2u_push_events(&evts); - } - - scratch_end(scratch); - ProfEnd(); -} - -internal void -ctrl_thread__detach(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DMN_Handle process = msg->entity; - - //- rjf: detach - B32 detach_worked = dmn_ctrl_detach(ctrl_ctx, process); - - //- rjf: wait for process to be dead - if(detach_worked) - { - DMN_RunCtrls run_ctrls = {0}; - run_ctrls.run_entities_are_unfrozen = 1; - run_ctrls.run_entities_are_processes = 1; - run_ctrls.run_entities = &process; - run_ctrls.run_entity_count = 1; - for(B32 done = 0; done == 0;) - { - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); - if(event->kind == DMN_EventKind_ExitProcess && dmn_handle_match(event->process, process)) - { - done = 1; - } - if(event->kind == DMN_EventKind_Halt) - { - done = 1; - } - } - } - - //- rjf: record stop - { - CTRL_EventList evts = {0}; - CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); - event->kind = CTRL_EventKind_Stopped; - event->cause = CTRL_EventCause_Finished; - event->machine_id = CTRL_MachineID_Local; - event->msg_id = msg->msg_id; - if(detach_worked) - { - event->entity = msg->entity; - } - ctrl_c2u_push_events(&evts); - } - - scratch_end(scratch); - ProfEnd(); -} - -internal void -ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DI_Scope *di_scope = di_scope_open(); - DMN_Event *stop_event = 0; - CTRL_EventCause stop_cause = CTRL_EventCause_Null; - DMN_Handle target_thread = msg->entity; - DMN_Handle target_process = msg->parent; - CTRL_Entity *target_process_entity = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, msg->machine_id, target_process); - U64 spoof_ip_vaddr = 911; - log_infof("ctrl_thread__run:\n{\n"); - - ////////////////////////////// - //- rjf: gather all initial breakpoints - // - DMN_TrapChunkList user_traps = {0}; - for(CTRL_Entity *machine = ctrl_state->ctrl_thread_entity_store->root->first; - machine != &ctrl_entity_nil; - machine = machine->next) - { - if(machine->kind != CTRL_EntityKind_Machine) { continue; } - for(CTRL_Entity *process = machine->first; process != &ctrl_entity_nil; process = process->next) - { - if(process->kind != CTRL_EntityKind_Process) { continue; } - - // rjf: resolve module-dependent user bps - for(CTRL_Entity *module = process->first; module != &ctrl_entity_nil; module = module->next) - { - if(module->kind != CTRL_EntityKind_Module) { continue; } - ctrl_thread__append_resolved_module_user_bp_traps(scratch.arena, machine->machine_id, process->handle, module->handle, &msg->user_bps, &user_traps); - } - - // rjf: push virtual-address user breakpoints per-process - ctrl_thread__append_resolved_process_user_bp_traps(scratch.arena, machine->machine_id, process->handle, &msg->user_bps, &user_traps); - } - } - - ////////////////////////////// - //- rjf: read initial stack-pointer-check value - // - // This MUST happen before any threads move, including single-stepping stuck - // threads, because otherwise, their stack pointer may change, if single-stepping - // causes e.g. entrance into a function via a call instruction. - // - U64 sp_check_value = dmn_rsp_from_thread(target_thread); - log_infof("sp_check_value := 0x%I64x\n", sp_check_value); - - ////////////////////////////// - //- rjf: single step "stuck threads" - // - // "Stuck threads" are threads that are already on a User BP and would hit - // it immediately if resumed with all User BPs enabled. To get them "unstuck" - // we just need to single step them to get them off their current instruction. - // - // This only applies to threads OTHER THAN the target thread. If the target - // thread is on a user breakpoint, then we need to let trap net logic run, - // which may include features put on a trap net trap at the same address as - // the user breakpoint. - // - B32 target_thread_is_on_user_bp_and_trap_net_trap = 0; - if(stop_event == 0) - { - // rjf: gather stuck threads - DMN_HandleList stuck_threads = {0}; - for(CTRL_Entity *machine = ctrl_state->ctrl_thread_entity_store->root->first; - machine != &ctrl_entity_nil; - machine = machine->next) - { - if(machine->kind != CTRL_EntityKind_Machine) { continue; } - for(CTRL_Entity *process = machine->first; process != &ctrl_entity_nil; process = process->next) - { - if(process->kind != CTRL_EntityKind_Process) { continue; } - for(CTRL_Entity *thread = process->first; thread != &ctrl_entity_nil; thread = thread->next) - { - U64 rip = dmn_rip_from_thread(thread->handle); - - // rjf: determine if thread is frozen - B32 thread_is_frozen = !msg->freeze_state_is_frozen; - for(CTRL_MachineIDHandlePairNode *n = msg->freeze_state_threads.first; n != 0; n = n->next) - { - if(dmn_handle_match(n->v.handle, thread->handle)) - { - thread_is_frozen ^= 1; - break; - } - } - - // rjf: not frozen? -> check if stuck & gather if so - if(thread_is_frozen == 0) - { - for(DMN_TrapChunkNode *n = user_traps.first; n != 0; n = n->next) - { - B32 is_on_user_bp = 0; - for(DMN_Trap *trap_ptr = n->v; trap_ptr < n->v+n->count; trap_ptr += 1) - { - if(dmn_handle_match(trap_ptr->process, process->handle) && trap_ptr->vaddr == rip) - { - is_on_user_bp = 1; - } - } - - B32 is_on_net_trap = 0; - for(CTRL_TrapNode *n = msg->traps.first; n != 0; n = n->next) - { - if(n->v.vaddr == rip) - { - is_on_net_trap = 1; - } - } - - if(is_on_user_bp && (!is_on_net_trap || !dmn_handle_match(thread->handle, target_thread))) - { - dmn_handle_list_push(scratch.arena, &stuck_threads, thread->handle); - } - - if(is_on_user_bp && is_on_net_trap && dmn_handle_match(thread->handle, target_thread)) - { - target_thread_is_on_user_bp_and_trap_net_trap = 1; - } - } - } - } - } - } - - // rjf: actually step stuck threads - for(DMN_HandleNode *node = stuck_threads.first; - node != 0; - node = node->next) - { - DMN_Handle thread = node->v; - U64 thread_pre_rip = dmn_rip_from_thread(thread); - U64 thread_post_rip = thread_pre_rip; - for(B32 done = 0; !done;) - { - log_infof("single_step_stuck_thread([0x%I64x])\n", thread.u64[0]); - DMN_RunCtrls run_ctrls = {0}; - run_ctrls.run_entities_are_unfrozen = 1; - run_ctrls.run_entities = &thread; - run_ctrls.run_entity_count = 1; - if(thread_post_rip == thread_pre_rip) - { - run_ctrls.single_step_thread = thread; - } - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); - thread_post_rip = dmn_rip_from_thread(thread); - switch(event->kind) - { - default:{}break; - case DMN_EventKind_Error: stop_cause = CTRL_EventCause_Error; goto stop; - case DMN_EventKind_Exception: stop_cause = CTRL_EventCause_InterruptedByException; goto stop; - case DMN_EventKind_Trap: stop_cause = CTRL_EventCause_InterruptedByTrap; goto stop; - case DMN_EventKind_Halt: stop_cause = CTRL_EventCause_InterruptedByHalt; goto stop; - stop:; - { - stop_event = event; - done = 1; - }break; - case DMN_EventKind_SingleStep: - { - done = dmn_handle_match(node->v, event->thread); - }break; - } - } - } - } - - ////////////////////////////// - //- rjf: resolve trap net - // - DMN_TrapChunkList trap_net_traps = {0}; - for(CTRL_TrapNode *node = msg->traps.first; - node != 0; - node = node->next) - { - DMN_Trap trap = {target_process, node->v.vaddr}; - dmn_trap_chunk_list_push(scratch.arena, &trap_net_traps, 256, &trap); - } - - ////////////////////////////// - //- rjf: join user breakpoints and trap net traps - // - DMN_TrapChunkList joined_traps = {0}; - { - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &user_traps); - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &trap_net_traps); - } - - ////////////////////////////// - //- rjf: record start - // - if(stop_event == 0) - { - CTRL_EventList evts = {0}; - CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); - event->kind = CTRL_EventKind_Started; - ctrl_c2u_push_events(&evts); - } - - ////////////////////////////// - //- rjf: run loop - // - if(stop_event == 0) - { - B32 spoof_mode = 0; - CTRL_Spoof spoof = {0}; - DMN_TrapChunkList entry_traps = {0}; - for(;;) - { - ////////////////////////// - //- rjf: choose low level traps - // - DMN_TrapChunkList *trap_list = &joined_traps; - if(spoof_mode) - { - trap_list = &user_traps; - } - - ////////////////////////// - //- rjf: choose spoof - // - CTRL_Spoof *run_spoof = 0; - if(spoof_mode) - { - run_spoof = &spoof; - } - - ////////////////////////// - //- rjf: setup run controls - // - DMN_RunCtrls run_ctrls = {0}; - run_ctrls.ignore_previous_exception = 1; - run_ctrls.run_entity_count = msg->freeze_state_threads.count; - run_ctrls.run_entities = push_array(scratch.arena, DMN_Handle, run_ctrls.run_entity_count); - run_ctrls.run_entities_are_unfrozen = !msg->freeze_state_is_frozen; - { - U64 idx = 0; - for(CTRL_MachineIDHandlePairNode *n = msg->freeze_state_threads.first; n != 0; n = n->next) - { - run_ctrls.run_entities[idx] = n->v.handle; - idx += 1; - } - } - run_ctrls.traps = *trap_list; - - ////////////////////////// - //- rjf: get next run-related event - // - log_infof("get_next_event:\n{\n"); - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, run_spoof); - log_infof("}\n\n"); - - ////////////////////////// - //- rjf: determine event handling - // - B32 launch_done_first_module = 0; - B32 hard_stop = 0; - CTRL_EventCause hard_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); - B32 use_stepping_logic = 0; - switch(event->kind) - { - default:{}break; - case DMN_EventKind_Error: - case DMN_EventKind_Halt: - case DMN_EventKind_SingleStep: - case DMN_EventKind_Trap: - { - hard_stop = 1; - log_infof("step_rule: unexpected -> hard_stop\n"); - }break; - case DMN_EventKind_Exception: - case DMN_EventKind_Breakpoint: - { - use_stepping_logic = 1; - log_infof("step_rule: exception/breakpoint -> stepping_logic\n"); - }break; - case DMN_EventKind_CreateProcess: - { - DMN_TrapChunkList new_traps = {0}; - ctrl_thread__append_resolved_process_user_bp_traps(scratch.arena, CTRL_MachineID_Local, event->process, &msg->user_bps, &new_traps); - log_infof("step_rule: create_process -> resolve traps\n"); - log_infof("new_traps:\n{\n"); - for(DMN_TrapChunkNode *n = new_traps.first; n != 0; n = n->next) - { - for(U64 idx = 0; idx < n->count; idx += 1) - { - DMN_Trap *trap = &n->v[idx]; - log_infof("{process:[0x%I64x], vaddr:0x%I64x}\n", trap->process.u64[0], trap->vaddr); - } - } - log_infof("}\n\n"); - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &new_traps); - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &user_traps, &new_traps); - }break; - case DMN_EventKind_LoadModule: - { - DMN_TrapChunkList new_traps = {0}; - ctrl_thread__append_resolved_module_user_bp_traps(scratch.arena, CTRL_MachineID_Local, event->process, event->module, &msg->user_bps, &new_traps); - log_infof("step_rule: load_module -> resolve traps\n"); - log_infof("new_traps:\n{\n"); - for(DMN_TrapChunkNode *n = new_traps.first; n != 0; n = n->next) - { - for(U64 idx = 0; idx < n->count; idx += 1) - { - DMN_Trap *trap = &n->v[idx]; - log_infof("{process:[0x%I64x], vaddr:0x%I64x}\n", trap->process.u64[0], trap->vaddr); - } - } - log_infof("}\n\n"); - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &new_traps); - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &user_traps, &new_traps); - }break; - } - - ////////////////////////// - //- rjf: on launches, detect entry points, place traps - // - if(msg->run_flags & CTRL_RunFlag_StopOnEntryPoint && !launch_done_first_module && event->kind == DMN_EventKind_HandshakeComplete) - { - launch_done_first_module = 1; - - //- rjf: unpack process/module info - CTRL_Entity *process = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, CTRL_MachineID_Local, event->process); - CTRL_Entity *module = &ctrl_entity_nil; - for(CTRL_Entity *child = process->first; child != &ctrl_entity_nil; child = child->next) - { - if(child->kind == CTRL_EntityKind_Module) - { - module = child; - break; - } - } - U64 module_base_vaddr = module->vaddr_range.min; - CTRL_Entity *dbg_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); - DI_Key dbgi_key = {dbg_path->string, dbg_path->timestamp}; - RDI_Parsed *rdi = di_rdi_from_key(di_scope, &dbgi_key, max_U64); - RDI_NameMap *unparsed_map = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_Procedures); - RDI_ParsedNameMap map = {0}; - rdi_parsed_from_name_map(rdi, unparsed_map, &map); - - //- rjf: add traps for user-specified entry points on this message, if specified - B32 entries_found = 0; - if(!entries_found) - { - for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) - { - U32 procedure_id = 0; - { - String8 name = n->string; - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - procedure_id = ids[0]; - } - } - RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); - U64 voff = rdi_first_voff_from_procedure(rdi, procedure); - if(voff != 0) - { - entries_found = 1; - DMN_Trap trap = {process->handle, module_base_vaddr + voff}; - dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); - } - } - } - - //- rjf: add traps for PID-correllated entry points - if(!entries_found) - { - for(CTRL_Entity *e = ctrl_state->ctrl_thread_entity_store->root->first; e != &ctrl_entity_nil; e = e->next) - { - if(e->id == process->id) - { - U32 procedure_id = 0; - { - String8 name = e->string; - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - procedure_id = ids[0]; - } - } - RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); - U64 voff = rdi_first_voff_from_procedure(rdi, procedure); - if(voff != 0) - { - entries_found = 1; - DMN_Trap trap = {process->handle, module_base_vaddr + voff}; - dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); - } - } - } - } - - //- rjf: add traps for all custom user entry points - if(!entries_found) - { - for(String8Node *n = ctrl_state->user_entry_points.first; n != 0; n = n->next) - { - U32 procedure_id = 0; - { - String8 name = n->string; - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - procedure_id = ids[0]; - } - } - RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); - U64 voff = rdi_first_voff_from_procedure(rdi, procedure); - if(voff != 0) - { - DMN_Trap trap = {process->handle, module_base_vaddr + voff}; - dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); - break; - } - } - } - - //- rjf: add traps for all high-level entry points - if(!entries_found) - { - String8 hi_entry_points[] = - { - str8_lit("WinMain"), - str8_lit("wWinMain"), - str8_lit("main"), - str8_lit("wmain"), - }; - for(U64 idx = 0; idx < ArrayCount(hi_entry_points); idx += 1) - { - U32 procedure_id = 0; - { - String8 name = hi_entry_points[idx]; - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - procedure_id = ids[0]; - } - } - RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); - U64 voff = rdi_first_voff_from_procedure(rdi, procedure); - if(voff != 0) - { - entries_found = 1; - DMN_Trap trap = {process->handle, module_base_vaddr + voff}; - dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); - } - } - } - - //- rjf: add trap for PE header entry - if(!entries_found) - { - U64 voff = ctrl_entry_point_voff_from_module(CTRL_MachineID_Local, module->handle); - if(voff != 0) - { - DMN_Trap trap = {process->handle, module_base_vaddr + voff}; - dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); - } - } - - //- rjf: add traps for all low-level entry points - if(!entries_found) - { - String8 lo_entry_points[] = - { - str8_lit("WinMainCRTStartup"), - str8_lit("wWinMainCRTStartup"), - str8_lit("mainCRTStartup"), - str8_lit("wmainCRTStartup"), - }; - for(U64 idx = 0; idx < ArrayCount(lo_entry_points); idx += 1) - { - U32 procedure_id = 0; - { - String8 name = lo_entry_points[idx]; - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - procedure_id = ids[0]; - } - } - RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); - U64 voff = rdi_first_voff_from_procedure(rdi, procedure); - if(voff != 0) - { - entries_found = 1; - DMN_Trap trap = {process->handle, module_base_vaddr + voff}; - dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); - } - } - } - - //- rjf: no entry point found -> done - if(entry_traps.trap_count == 0) - { - hard_stop = 1; - } - - //- rjf: found entry points -> add to joined traps - dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &entry_traps); - } - - ////////////////////////// - //- rjf: unpack info about thread attached to event - // - CTRL_Entity *thread = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, CTRL_MachineID_Local, event->thread); - Architecture arch = thread->arch; - U64 thread_rip_vaddr = dmn_rip_from_thread(event->thread); - CTRL_Entity *module = &ctrl_entity_nil; - { - CTRL_Entity *process = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, CTRL_MachineID_Local, event->process); - for(CTRL_Entity *m = process->first; m != &ctrl_entity_nil; m = m->next) - { - if(m->kind == CTRL_EntityKind_Module && contains_1u64(m->vaddr_range, thread_rip_vaddr)) - { - module = m; - break; - } - } - } - - ////////////////////////// - //- rjf: extract module-dependent info - // - CTRL_Entity *dbg_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); - U64 thread_rip_voff = thread_rip_vaddr - module->vaddr_range.min; - - ////////////////////////// - //- rjf: stepping logic - // - //{ - - ////////////////////////// - //- rjf: handle if hitting a spoof - // - B32 exception_stop = 0; - B32 hit_spoof = 0; - if(!hard_stop && use_stepping_logic && event->kind == DMN_EventKind_Exception) - { - if(spoof_mode && - dmn_handle_match(target_process, event->process) && - dmn_handle_match(target_thread, event->thread) && - spoof.new_ip_value == event->instruction_pointer) - { - hit_spoof = 1; - log_infof("hit_spoof\n"); - } - else - { - exception_stop = 1; - use_stepping_logic = 0; - } - } - - //- rjf: handle spoof hit - if(hit_spoof) - { - log_infof("exit_spoof_mode\n"); - - // rjf: clear spoof mode - spoof_mode = 0; - MemoryZeroStruct(&spoof); - - // rjf: skip remainder of handling - use_stepping_logic = 0; - } - - //- rjf: for breakpoint events, gather bp info - B32 hit_entry = 0; - B32 hit_user_bp = 0; - B32 hit_trap_net_bp = 0; - B32 hit_conditional_bp_but_filtered = 0; - CTRL_TrapFlags hit_trap_flags = 0; - if(!hard_stop && use_stepping_logic && event->kind == DMN_EventKind_Breakpoint) - ProfScope("for breakpoint events, gather bp info") - { - Temp temp = temp_begin(scratch.arena); - String8List conditions = {0}; - - // rjf: entry breakpoints - for(DMN_TrapChunkNode *n = entry_traps.first; n != 0; n = n->next) - { - DMN_Trap *trap = n->v; - DMN_Trap *opl = n->v + n->count; - for(;trap < opl; trap += 1) - { - if(dmn_handle_match(trap->process, event->process) && trap->vaddr == event->instruction_pointer) - { - hit_entry = 1; - } - } - } - - // rjf: user breakpoints - for(DMN_TrapChunkNode *n = user_traps.first; n != 0; n = n->next) - { - DMN_Trap *trap = n->v; - DMN_Trap *opl = n->v + n->count; - for(;trap < opl; trap += 1) - { - if(dmn_handle_match(trap->process, event->process) && - trap->vaddr == event->instruction_pointer && - (!dmn_handle_match(event->thread, target_thread) || !target_thread_is_on_user_bp_and_trap_net_trap)) - { - CTRL_UserBreakpoint *user_bp = (CTRL_UserBreakpoint *)trap->id; - hit_user_bp = 1; - if(user_bp != 0 && user_bp->condition.size != 0) - { - str8_list_push(temp.arena, &conditions, user_bp->condition); - } - } - } - } - - // rjf: evaluate hit stop conditions - if(conditions.node_count != 0) ProfScope("evaluate hit stop conditions") - { - DI_Key dbgi_key = {dbg_path->string, dbg_path->timestamp}; - RDI_Parsed *rdi = di_rdi_from_key(di_scope, &dbgi_key, max_U64); - for(String8Node *condition_n = conditions.first; condition_n != 0; condition_n = condition_n->next) - { - ProfBegin("compile expression"); - String8 string = condition_n->string; - EVAL_ParseCtx parse_ctx = zero_struct; - { - parse_ctx.arch = arch; - parse_ctx.ip_voff = thread_rip_voff; - parse_ctx.rdi = rdi; - parse_ctx.type_graph = tg_graph_begin(bit_size_from_arch(arch)/8, 256); - parse_ctx.regs_map = ctrl_string2reg_from_arch(arch); - parse_ctx.reg_alias_map = ctrl_string2alias_from_arch(arch); - parse_ctx.locals_map = eval_push_locals_map_from_rdi_voff(temp.arena, rdi, thread_rip_voff); - parse_ctx.member_map = eval_push_member_map_from_rdi_voff(temp.arena, rdi, thread_rip_voff); - } - EVAL_TokenArray tokens = eval_token_array_from_text(temp.arena, string); - EVAL_ParseResult parse = eval_parse_expr_from_text_tokens(temp.arena, &parse_ctx, string, &tokens); - EVAL_ErrorList errors = parse.errors; - B32 parse_has_expr = (parse.expr != &eval_expr_nil); - B32 parse_is_type = (parse_has_expr && parse.expr->kind == EVAL_ExprKind_TypeIdent); - EVAL_IRTreeAndType ir_tree_and_type = {&eval_irtree_nil}; - if(parse_has_expr && errors.count == 0) - { - ir_tree_and_type = eval_irtree_and_type_from_expr(temp.arena, parse_ctx.type_graph, rdi, &eval_string2expr_map_nil, parse.expr, &errors); - } - EVAL_OpList op_list = {0}; - if(parse_has_expr && ir_tree_and_type.tree != &eval_irtree_nil) - { - eval_oplist_from_irtree(scratch.arena, ir_tree_and_type.tree, &op_list); - } - String8 bytecode = {0}; - if(parse_has_expr && parse_is_type == 0 && op_list.encoded_size != 0) - { - bytecode = eval_bytecode_from_oplist(scratch.arena, &op_list); - } - ProfEnd(); - EVAL_Result eval = {0}; - if(bytecode.size != 0) ProfScope("evaluate expression") - { - U64 module_base = module->vaddr_range.min; - U64 tls_base = dmn_tls_root_vaddr_from_thread(event->thread); - EVAL_Machine machine = {0}; - machine.u = &event->process; - machine.arch = arch; - machine.memory_read = ctrl_eval_memory_read; - machine.reg_size = regs_block_size_from_architecture(arch); - machine.reg_data = push_array(scratch.arena, U8, machine.reg_size); - dmn_thread_read_reg_block(event->thread, machine.reg_data); - machine.module_base = &module_base; - machine.tls_base = &tls_base; - eval = eval_interpret(&machine, bytecode); - } - if(eval.code == EVAL_ResultCode_Good && eval.value.u64 == 0) - { - hit_user_bp = 0; - hit_conditional_bp_but_filtered = 1; - log_infof("conditional_breakpoint_hit: 'condition eval'd to 0, and so filtered'\n"); - } - else - { - hit_user_bp = 1; - hit_conditional_bp_but_filtered = 0; - log_infof("conditional_breakpoint_hit: 'conditional eval'd to nonzero, hit'\n"); - break; - } - } - } - - // rjf: gather trap net hits - ProfScope("gather trap net hits") - { - if(!hit_user_bp && dmn_handle_match(event->process, target_process)) - { - for(CTRL_TrapNode *node = msg->traps.first; - node != 0; - node = node->next) - { - if(node->v.vaddr == event->instruction_pointer) - { - hit_trap_net_bp = 1; - hit_trap_flags |= node->v.flags; - } - } - } - } - - log_infof("user_breakpoint_hit: %i\n", hit_user_bp); - log_infof("entry_point_hit: %i\n", hit_entry); - temp_end(temp); - } - - //- rjf: hit conditional user bp but filtered -> single step - B32 cond_bp_single_step_stop = 0; - CTRL_EventCause cond_bp_single_step_stop_cause = CTRL_EventCause_Null; - if(hit_conditional_bp_but_filtered) LogInfoNamedBlockF("conditional_bp_hit_single_step") - { - DMN_Handle thread = event->thread; - U64 thread_pre_rip = dmn_rip_from_thread(thread); - U64 thread_post_rip = thread_pre_rip; - for(B32 single_step_done = 0; !single_step_done;) - { - DMN_RunCtrls single_step_ctrls = {0}; - single_step_ctrls.run_entities_are_unfrozen = 1; - single_step_ctrls.run_entities = &thread; - single_step_ctrls.run_entity_count = 1; - if(thread_post_rip == thread_pre_rip) - { - single_step_ctrls.single_step_thread = thread; - } - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &single_step_ctrls, 0); - thread_post_rip = dmn_rip_from_thread(thread); - switch(event->kind) - { - default:{}break; - case DMN_EventKind_Error: - case DMN_EventKind_Exception: - case DMN_EventKind_Halt: - case DMN_EventKind_Trap: - { - cond_bp_single_step_stop = 1; - single_step_done = 1; - use_stepping_logic = 0; - cond_bp_single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); - }break; - case DMN_EventKind_SingleStep: - { - single_step_done = dmn_handle_match(event->thread, thread); - cond_bp_single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); - }break; - } - } - } - - //- rjf: hit entry points on *any thread* cause a stop, if this msg says as such - B32 entry_stop = 0; - if(msg->run_flags & CTRL_RunFlag_StopOnEntryPoint && hit_entry) - { - entry_stop = 1; - use_stepping_logic = 0; - } - - //- rjf: user breakpoints on *any thread* cause a stop - B32 user_bp_stop = 0; - if(!hard_stop && use_stepping_logic && hit_user_bp) - { - user_bp_stop = 1; - use_stepping_logic = 0; - } - - //- rjf: trap net on off-target threads are ignored - B32 step_past_trap_net = 0; - if(!hard_stop && use_stepping_logic && hit_trap_net_bp) - { - if(!dmn_handle_match(event->thread, target_thread)) - { - step_past_trap_net = 1; - use_stepping_logic = 0; - } - } - - //- rjf: trap net on on-target threads trigger trap net logic - B32 use_trap_net_logic = 0; - if(!hard_stop && use_stepping_logic && hit_trap_net_bp) - { - if(dmn_handle_match(event->thread, target_thread)) - { - use_trap_net_logic = 1; - } - } - - //- rjf: trap net logic: stack pointer check - B32 stack_pointer_matches = 0; - if(use_trap_net_logic) - { - U64 sp = dmn_rsp_from_thread(target_thread); - stack_pointer_matches = (sp == sp_check_value); - } - - //- rjf: trap net logic: single step after hit - B32 single_step_stop = 0; - CTRL_EventCause single_step_stop_cause = CTRL_EventCause_Null; - if(!hard_stop && use_trap_net_logic) - { - if(hit_trap_flags & CTRL_TrapFlag_SingleStepAfterHit) LogInfoNamedBlockF("trap_net__single_step_after_hit") - { - U64 thread_pre_rip = dmn_rip_from_thread(target_thread); - U64 thread_post_rip = thread_pre_rip; - for(B32 single_step_done = 0; single_step_done == 0;) - { - DMN_RunCtrls single_step_ctrls = {0}; - single_step_ctrls.run_entities_are_unfrozen = 1; - single_step_ctrls.run_entities = &target_thread; - single_step_ctrls.run_entity_count = 1; - if(thread_post_rip == thread_pre_rip) - { - single_step_ctrls.single_step_thread = target_thread; - } - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &single_step_ctrls, 0); - thread_post_rip = dmn_rip_from_thread(target_thread); - switch(event->kind) - { - default:{}break; - case DMN_EventKind_Error: - case DMN_EventKind_Exception: - case DMN_EventKind_Halt: - case DMN_EventKind_Trap: - { - single_step_stop = 1; - single_step_done = 1; - use_stepping_logic = 0; - use_trap_net_logic = 0; - single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); - }break; - case DMN_EventKind_SingleStep: - { - single_step_done = dmn_handle_match(event->thread, target_thread);; - single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); - }break; - } - } - } - } - - //- rjf: trap net logic: begin spoof mode - B32 begin_spoof_mode = 0; - if(!hard_stop && use_trap_net_logic) - { - if(hit_trap_flags & CTRL_TrapFlag_BeginSpoofMode) LogInfoNamedBlockF("trap_net__begin_spoof_mode") - { - // rjf: setup spoof mode - begin_spoof_mode = 1; - U64 spoof_sp = dmn_rsp_from_thread(target_thread); - spoof_mode = 1; - spoof.process = target_process; - spoof.thread = target_thread; - spoof.vaddr = spoof_sp; - spoof.new_ip_value = spoof_ip_vaddr; - log_infof("spoof:{process:[0x%I64x], thread:[0x%I64x], vaddr:0x%I64x, new_ip_value:0x%I64x}\n", spoof.process.u64[0], spoof.thread.u64[0], spoof.vaddr, spoof.new_ip_value); - } - } - - //- rjf: trap net logic: save stack pointer - B32 save_stack_pointer = 0; - if(!hard_stop && use_trap_net_logic) - { - if(hit_trap_flags & CTRL_TrapFlag_SaveStackPointer) - { - if(stack_pointer_matches) LogInfoNamedBlockF("trap_net__save_sp") - { - save_stack_pointer = 1; - sp_check_value = dmn_rsp_from_thread(target_thread); - log_infof("sp_check_value = 0x%I64x\n", sp_check_value); - } - } - } - - //- rjf: trap net logic: end stepping - B32 trap_net_stop = 0; - if(!hard_stop && use_trap_net_logic) - { - if(hit_trap_flags & CTRL_TrapFlag_EndStepping) LogInfoNamedBlockF("trap_net__end_step") - { - if((hit_trap_flags & CTRL_TrapFlag_IgnoreStackPointerCheck) || - stack_pointer_matches) - { - trap_net_stop = 1; - use_trap_net_logic = 0; - } - } - } - - //} - // - //- rjf: stepping logic - //////////////////////////////// - - //- rjf: handle step past trap net - B32 step_past_trap_net_stop = 0; - CTRL_EventCause step_past_trap_net_stop_cause = CTRL_EventCause_Null; - if(step_past_trap_net) LogInfoNamedBlockF("trap_net__single_step_past_trap_net") - { - DMN_Handle thread = event->thread; - U64 thread_pre_rip = dmn_rip_from_thread(thread); - U64 thread_post_rip = thread_pre_rip; - for(B32 single_step_done = 0; single_step_done == 0;) - { - DMN_RunCtrls single_step_ctrls = {0}; - single_step_ctrls.run_entities_are_unfrozen = 1; - single_step_ctrls.run_entities = &thread; - single_step_ctrls.run_entity_count = 1; - if(thread_post_rip == thread_pre_rip) - { - single_step_ctrls.single_step_thread = thread; - } - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &single_step_ctrls, 0); - thread_post_rip = dmn_rip_from_thread(thread); - switch(event->kind) - { - default:{}break; - case DMN_EventKind_Error: - case DMN_EventKind_Exception: - case DMN_EventKind_Halt: - case DMN_EventKind_Trap: - { - step_past_trap_net_stop = 1; - single_step_done = 1; - step_past_trap_net_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); - }break; - case DMN_EventKind_SingleStep: - { - single_step_done = dmn_handle_match(event->thread, thread); - step_past_trap_net_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); - }break; - } - } - } - - //- rjf: loop exit condition - CTRL_EventCause stage_stop_cause = CTRL_EventCause_Null; - if(hard_stop) - { - stage_stop_cause = hard_stop_cause; - } - else if(cond_bp_single_step_stop) - { - stage_stop_cause = cond_bp_single_step_stop_cause; - } - else if(single_step_stop) - { - stage_stop_cause = single_step_stop_cause; - } - else if(step_past_trap_net_stop) - { - stage_stop_cause = step_past_trap_net_stop_cause; - } - else if(exception_stop) - { - stage_stop_cause = CTRL_EventCause_InterruptedByException; - } - else if(user_bp_stop) - { - stage_stop_cause = CTRL_EventCause_UserBreakpoint; - } - else if(entry_stop) - { - stage_stop_cause = CTRL_EventCause_EntryPoint; - } - else if(trap_net_stop) - { - stage_stop_cause = CTRL_EventCause_Finished; - } - log_infof("stop_cause: %i\n", stage_stop_cause); - if(stage_stop_cause != CTRL_EventCause_Null) - { - stop_event = event; - stop_cause = stage_stop_cause; - break; - } - } - } - - ////////////////////////////// - //- rjf: record stop - // - if(stop_event != 0) - { - CTRL_EventList evts = {0}; - CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); - event->kind = CTRL_EventKind_Stopped; - event->cause = stop_cause; - event->machine_id = CTRL_MachineID_Local; - event->entity = stop_event->thread; - event->parent = stop_event->process; - event->exception_code = stop_event->code; - event->vaddr_rng = r1u64(stop_event->address, stop_event->address); - event->rip_vaddr = stop_event->instruction_pointer; - ctrl_c2u_push_events(&evts); - } - - log_infof("}\n\n"); - di_scope_close(di_scope); - scratch_end(scratch); - ProfEnd(); -} - -internal void -ctrl_thread__single_step(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - - //- rjf: record start - { - CTRL_EventList evts = {0}; - CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); - event->kind = CTRL_EventKind_Started; - ctrl_c2u_push_events(&evts); - } - - //- rjf: single step - DMN_Event *stop_event = 0; - CTRL_EventCause stop_cause = CTRL_EventCause_Null; - { - DMN_Handle thread = msg->entity; - U64 thread_pre_rip = dmn_rip_from_thread(thread); - U64 thread_post_rip = thread_pre_rip; - for(B32 done = 0; done == 0;) - { - DMN_RunCtrls run_ctrls = {0}; - run_ctrls.run_entities_are_unfrozen = 1; - run_ctrls.run_entities = &thread; - run_ctrls.run_entity_count = 1; - if(thread_post_rip == thread_pre_rip) - { - run_ctrls.single_step_thread = msg->entity; - } - DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); - thread_post_rip = dmn_rip_from_thread(msg->entity); - switch(event->kind) - { - default:{}break; - case DMN_EventKind_Error: {stop_cause = CTRL_EventCause_Error;}goto end_single_step; - case DMN_EventKind_Exception: {stop_cause = CTRL_EventCause_InterruptedByException;}goto end_single_step; - case DMN_EventKind_Halt: {stop_cause = CTRL_EventCause_InterruptedByHalt;}goto end_single_step; - case DMN_EventKind_Trap: {stop_cause = CTRL_EventCause_InterruptedByTrap;}goto end_single_step; - case DMN_EventKind_Breakpoint: {stop_cause = CTRL_EventCause_UserBreakpoint;}goto end_single_step; - case DMN_EventKind_SingleStep: {stop_cause = CTRL_EventCause_Finished;}goto end_single_step; - end_single_step: - { - stop_event = event; - done = 1; - }break; - } - } - } - - //- rjf: record stop - if(stop_event != 0) - { - CTRL_EventList evts = {0}; - CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); - event->kind = CTRL_EventKind_Stopped; - event->cause = stop_cause; - event->machine_id = CTRL_MachineID_Local; - event->entity = stop_event->thread; - event->parent = stop_event->process; - event->exception_code = stop_event->code; - event->vaddr_rng = r1u64(stop_event->address, stop_event->address); - event->rip_vaddr = stop_event->instruction_pointer; - ctrl_c2u_push_events(&evts); - } - - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Memory-Stream-Thread-Only Functions - -//- rjf: user -> memory stream communication - -internal B32 -ctrl_u2ms_enqueue_req(CTRL_MachineID machine_id, DMN_Handle process, Rng1U64 vaddr_range, B32 zero_terminated, U64 endt_us) -{ - B32 good = 0; - OS_MutexScope(ctrl_state->u2ms_ring_mutex) for(;;) - { - U64 unconsumed_size = ctrl_state->u2ms_ring_write_pos-ctrl_state->u2ms_ring_read_pos; - U64 available_size = ctrl_state->u2ms_ring_size-unconsumed_size; - if(available_size >= sizeof(machine_id)+sizeof(process)+sizeof(vaddr_range)) - { - good = 1; - ctrl_state->u2ms_ring_write_pos += ring_write_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_write_pos, &machine_id); - ctrl_state->u2ms_ring_write_pos += ring_write_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_write_pos, &process); - ctrl_state->u2ms_ring_write_pos += ring_write_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_write_pos, &vaddr_range); - ctrl_state->u2ms_ring_write_pos += ring_write_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_write_pos, &zero_terminated); - break; - } - if(os_now_microseconds() >= endt_us) {break;} - os_condition_variable_wait(ctrl_state->u2ms_ring_cv, ctrl_state->u2ms_ring_mutex, endt_us); - } - os_condition_variable_broadcast(ctrl_state->u2ms_ring_cv); - return good; -} - -internal void -ctrl_u2ms_dequeue_req(CTRL_MachineID *out_machine_id, DMN_Handle *out_process, Rng1U64 *out_vaddr_range, B32 *out_zero_terminated) -{ - OS_MutexScope(ctrl_state->u2ms_ring_mutex) for(;;) - { - U64 unconsumed_size = ctrl_state->u2ms_ring_write_pos-ctrl_state->u2ms_ring_read_pos; - if(unconsumed_size >= sizeof(*out_machine_id)+sizeof(*out_process)+sizeof(*out_vaddr_range)) - { - ctrl_state->u2ms_ring_read_pos += ring_read_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_read_pos, out_machine_id); - ctrl_state->u2ms_ring_read_pos += ring_read_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_read_pos, out_process); - ctrl_state->u2ms_ring_read_pos += ring_read_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_read_pos, out_vaddr_range); - ctrl_state->u2ms_ring_read_pos += ring_read_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_read_pos, out_zero_terminated); - break; - } - os_condition_variable_wait(ctrl_state->u2ms_ring_cv, ctrl_state->u2ms_ring_mutex, max_U64); - } - os_condition_variable_broadcast(ctrl_state->u2ms_ring_cv); -} - -//- rjf: entry point - -internal void -ctrl_mem_stream_thread__entry_point(void *p) -{ - CTRL_ProcessMemoryCache *cache = &ctrl_state->process_memory_cache; - for(;;) - { - //- rjf: unpack next request - CTRL_MachineID machine_id = 0; - DMN_Handle process = {0}; - Rng1U64 vaddr_range = {0}; - B32 zero_terminated = 0; - ctrl_u2ms_dequeue_req(&machine_id, &process, &vaddr_range, &zero_terminated); - U128 key = ctrl_calc_hash_store_key_from_process_vaddr_range(machine_id, process, vaddr_range, zero_terminated); - - //- rjf: unpack process memory cache key - U64 process_hash = ctrl_hash_from_string(str8_struct(&process)); - U64 process_slot_idx = process_hash%cache->slots_count; - U64 process_stripe_idx = process_slot_idx%cache->stripes_count; - CTRL_ProcessMemoryCacheSlot *process_slot = &cache->slots[process_slot_idx]; - CTRL_ProcessMemoryCacheStripe *process_stripe = &cache->stripes[process_stripe_idx]; - - //- rjf: unpack address range hash cache key - U64 range_hash = ctrl_hash_from_string(str8_struct(&vaddr_range)); - - //- rjf: take task - B32 got_task = 0; - U64 preexisting_mem_gen = 0; - U128 preexisting_hash = {0}; - Rng1U64 vaddr_range_clamped = {0}; - OS_MutexScopeW(process_stripe->rw_mutex) - { - for(CTRL_ProcessMemoryCacheNode *n = process_slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->process, process)) - { - U64 range_slot_idx = range_hash%n->range_hash_slots_count; - CTRL_ProcessMemoryRangeHashSlot *range_slot = &n->range_hash_slots[range_slot_idx]; - for(CTRL_ProcessMemoryRangeHashNode *range_n = range_slot->first; range_n != 0; range_n = range_n->next) - { - if(MemoryMatchStruct(&range_n->vaddr_range, &vaddr_range) && range_n->zero_terminated == zero_terminated) - { - got_task = !ins_atomic_u32_eval_cond_assign(&range_n->is_taken, 1, 0); - preexisting_mem_gen = range_n->mem_gen; - preexisting_hash = range_n->hash; - vaddr_range_clamped = range_n->vaddr_range_clamped; - goto take_task__break_all; - } - } - } - } - take_task__break_all:; - } - - //- rjf: task was taken -> read memory - U64 range_size = 0; - Arena *range_arena = 0; - void *range_base = 0; - U64 zero_terminated_size = 0; - U64 pre_read_mem_gen = dmn_mem_gen(); - U64 post_read_mem_gen = 0; - if(got_task && pre_read_mem_gen != preexisting_mem_gen) - { - range_size = dim_1u64(vaddr_range_clamped); - U64 arena_size = AlignPow2(range_size + ARENA_HEADER_SIZE, os_page_size()); - range_arena = arena_alloc__sized(range_size+ARENA_HEADER_SIZE, range_size+ARENA_HEADER_SIZE); - if(range_arena == 0) - { - range_size = 0; - } - else - { - range_base = push_array_no_zero(range_arena, U8, range_size); - U64 bytes_read = 0; - U64 retry_count = 0; - for(Rng1U64 vaddr_range_clamped_retry = vaddr_range_clamped; retry_count < 64; retry_count += 1) - { - bytes_read = dmn_process_read(process, vaddr_range_clamped_retry, range_base); - if(bytes_read == 0 && vaddr_range_clamped_retry.max > vaddr_range_clamped_retry.min) - { - U64 diff = (vaddr_range_clamped_retry.max-vaddr_range_clamped_retry.min)/2; - vaddr_range_clamped_retry.max -= diff; - if(diff == 0) - { - break; - } - } - else - { - break; - } - } - if(bytes_read == 0) - { - arena_release(range_arena); - range_base = 0; - range_size = 0; - range_arena = 0; - } - else if(bytes_read < range_size) - { - MemoryZero((U8 *)range_base + bytes_read, range_size-bytes_read); - } - zero_terminated_size = range_size; - if(zero_terminated) - { - for(U64 idx = 0; idx < bytes_read; idx += 1) - { - if(((U8 *)range_base)[idx] == 0) - { - zero_terminated_size = idx; - break; - } - } - } - } - post_read_mem_gen = dmn_mem_gen(); - } - - //- rjf: read successful -> submit to hash store - U128 hash = {0}; - if(got_task && range_base != 0) - { - hash = hs_submit_data(key, &range_arena, str8((U8*)range_base, zero_terminated_size)); - } - - //- rjf: commit hash to cache - if(got_task) OS_MutexScopeW(process_stripe->rw_mutex) - { - for(CTRL_ProcessMemoryCacheNode *n = process_slot->first; n != 0; n = n->next) - { - if(n->machine_id == machine_id && dmn_handle_match(n->process, process)) - { - U64 range_slot_idx = range_hash%n->range_hash_slots_count; - CTRL_ProcessMemoryRangeHashSlot *range_slot = &n->range_hash_slots[range_slot_idx]; - for(CTRL_ProcessMemoryRangeHashNode *range_n = range_slot->first; range_n != 0; range_n = range_n->next) - { - if(MemoryMatchStruct(&range_n->vaddr_range, &vaddr_range) && range_n->zero_terminated == zero_terminated) - { - if(!u128_match(u128_zero(), hash)) - { - range_n->hash = hash; - } - if(!u128_match(u128_zero(), hash)) - { - range_n->mem_gen = post_read_mem_gen; - } - ins_atomic_u32_eval_assign(&range_n->is_taken, 0); - goto commit__break_all; - } - } - } - } - commit__break_all:; - } - - //- rjf: broadcast changes - os_condition_variable_broadcast(process_stripe->cv); - } -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Generated Code + +#include "generated/ctrl.meta.c" + +//////////////////////////////// +//~ rjf: Basic Type Functions + +internal U64 +ctrl_hash_from_string(String8 string) +{ + U64 result = 5381; + for(U64 i = 0; i < string.size; i += 1) + { + result = ((result << 5) + result) + string.str[i]; + } + return result; +} + +internal U64 +ctrl_hash_from_machine_id_handle(CTRL_MachineID machine_id, DMN_Handle handle) +{ + U64 buf[] = {machine_id, handle.u64[0]}; + U64 hash = ctrl_hash_from_string(str8((U8 *)buf, sizeof(buf))); + return hash; +} + +internal CTRL_EventCause +ctrl_event_cause_from_dmn_event_kind(DMN_EventKind event_kind) +{ + CTRL_EventCause cause = CTRL_EventCause_Null; + switch(event_kind) + { + default:{}break; + case DMN_EventKind_Error: {cause = CTRL_EventCause_Error;}break; + case DMN_EventKind_Exception:{cause = CTRL_EventCause_InterruptedByException;}break; + case DMN_EventKind_Trap: {cause = CTRL_EventCause_InterruptedByTrap;}break; + case DMN_EventKind_Halt: {cause = CTRL_EventCause_InterruptedByHalt;}break; + } + return cause; +} + +internal String8 +ctrl_string_from_event_kind(CTRL_EventKind kind) +{ + String8 result = {0}; + switch(kind) + { + default:{}break; + case CTRL_EventKind_Null: { result = str8_lit("Null");}break; + case CTRL_EventKind_Error: { result = str8_lit("Error");}break; + case CTRL_EventKind_Started: { result = str8_lit("Started");}break; + case CTRL_EventKind_Stopped: { result = str8_lit("Stopped");}break; + case CTRL_EventKind_NewProc: { result = str8_lit("NewProc");}break; + case CTRL_EventKind_NewThread: { result = str8_lit("NewThread");}break; + case CTRL_EventKind_NewModule: { result = str8_lit("NewModule");}break; + case CTRL_EventKind_EndProc: { result = str8_lit("EndProc");}break; + case CTRL_EventKind_EndThread: { result = str8_lit("EndThread");}break; + case CTRL_EventKind_EndModule: { result = str8_lit("EndModule");}break; + case CTRL_EventKind_ModuleDebugInfoPathChange: { result = str8_lit("ModuleDebugInfoPathChange");}break; + case CTRL_EventKind_DebugString: { result = str8_lit("DebugString");}break; + case CTRL_EventKind_ThreadName: { result = str8_lit("ThreadName");}break; + case CTRL_EventKind_MemReserve: { result = str8_lit("MemReserve");}break; + case CTRL_EventKind_MemCommit: { result = str8_lit("MemCommit");}break; + case CTRL_EventKind_MemDecommit: { result = str8_lit("MemDecommit");}break; + case CTRL_EventKind_MemRelease: { result = str8_lit("MemRelease");}break; + } + return result; +} + +internal String8 +ctrl_string_from_msg_kind(CTRL_MsgKind kind) +{ + String8 result = {0}; + switch(kind) + { + default:{}break; + case CTRL_MsgKind_Launch: {result = str8_lit("Launch");}break; + case CTRL_MsgKind_Attach: {result = str8_lit("Attach");}break; + case CTRL_MsgKind_Kill: {result = str8_lit("Kill");}break; + case CTRL_MsgKind_Detach: {result = str8_lit("Detach");}break; + case CTRL_MsgKind_Run: {result = str8_lit("Run");}break; + case CTRL_MsgKind_SingleStep: {result = str8_lit("SingleStep");}break; + case CTRL_MsgKind_SetUserEntryPoints: {result = str8_lit("SetUserEntryPoints");}break; + case CTRL_MsgKind_SetModuleDebugInfoPath: {result = str8_lit("SetModuleDebugInfoPath");}break; + } + return result; +} + +//////////////////////////////// +//~ rjf: Machine/Handle Pair Type Functions + +internal void +ctrl_machine_id_handle_pair_list_push(Arena *arena, CTRL_MachineIDHandlePairList *list, CTRL_MachineIDHandlePair *pair) +{ + CTRL_MachineIDHandlePairNode *n = push_array(arena, CTRL_MachineIDHandlePairNode, 1); + MemoryCopyStruct(&n->v, pair); + SLLQueuePush(list->first, list->last, n); + list->count += 1; +} + +internal CTRL_MachineIDHandlePairList +ctrl_machine_id_handle_pair_list_copy(Arena *arena, CTRL_MachineIDHandlePairList *src) +{ + CTRL_MachineIDHandlePairList dst = {0}; + for(CTRL_MachineIDHandlePairNode *n = src->first; n != 0; n = n->next) + { + ctrl_machine_id_handle_pair_list_push(arena, &dst, &n->v); + } + return dst; +} + +//////////////////////////////// +//~ rjf: Trap Type Functions + +internal void +ctrl_trap_list_push(Arena *arena, CTRL_TrapList *list, CTRL_Trap *trap) +{ + CTRL_TrapNode *node = push_array(arena, CTRL_TrapNode, 1); + MemoryCopyStruct(&node->v, trap); + SLLQueuePush(list->first, list->last, node); + list->count += 1; +} + +internal CTRL_TrapList +ctrl_trap_list_copy(Arena *arena, CTRL_TrapList *src) +{ + CTRL_TrapList dst = {0}; + for(CTRL_TrapNode *src_n = src->first; src_n != 0; src_n = src_n->next) + { + ctrl_trap_list_push(arena, &dst, &src_n->v); + } + return dst; +} + +//////////////////////////////// +//~ rjf: User Breakpoint Type Functions + +internal void +ctrl_user_breakpoint_list_push(Arena *arena, CTRL_UserBreakpointList *list, CTRL_UserBreakpoint *bp) +{ + CTRL_UserBreakpointNode *n = push_array(arena, CTRL_UserBreakpointNode, 1); + MemoryCopyStruct(&n->v, bp); + SLLQueuePush(list->first, list->last, n); + list->count += 1; +} + +internal CTRL_UserBreakpointList +ctrl_user_breakpoint_list_copy(Arena *arena, CTRL_UserBreakpointList *src) +{ + CTRL_UserBreakpointList dst = {0}; + for(CTRL_UserBreakpointNode *src_n = src->first; src_n != 0; src_n = src_n->next) + { + CTRL_UserBreakpoint dst_bp = zero_struct; + MemoryCopyStruct(&dst_bp, &src_n->v); + dst_bp.string = push_str8_copy(arena, src_n->v.string); + dst_bp.condition = push_str8_copy(arena, src_n->v.condition); + ctrl_user_breakpoint_list_push(arena, &dst, &dst_bp); + } + return dst; +} + +//////////////////////////////// +//~ rjf: Message Type Functions + +//- rjf: deep copying + +internal void +ctrl_msg_deep_copy(Arena *arena, CTRL_Msg *dst, CTRL_Msg *src) +{ + MemoryCopyStruct(dst, src); + dst->path = push_str8_copy(arena, src->path); + dst->entry_points = str8_list_copy(arena, &src->entry_points); + dst->cmd_line_string_list = str8_list_copy(arena, &src->cmd_line_string_list); + dst->env_string_list = str8_list_copy(arena, &src->env_string_list); + dst->traps = ctrl_trap_list_copy(arena, &src->traps); + dst->user_bps = ctrl_user_breakpoint_list_copy(arena, &src->user_bps); + dst->freeze_state_threads = ctrl_machine_id_handle_pair_list_copy(arena, &src->freeze_state_threads); +} + +//- rjf: list building + +internal CTRL_Msg * +ctrl_msg_list_push(Arena *arena, CTRL_MsgList *list) +{ + CTRL_MsgNode *n = push_array(arena, CTRL_MsgNode, 1); + SLLQueuePush(list->first, list->last, n); + list->count += 1; + CTRL_Msg *msg = &n->v; + return msg; +} + +//- rjf: serialization + +internal String8 +ctrl_serialized_string_from_msg_list(Arena *arena, CTRL_MsgList *msgs) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List msgs_srlzed = {0}; + str8_serial_begin(scratch.arena, &msgs_srlzed); + { + // rjf: write message count + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msgs->count); + + // rjf: write all message data + for(CTRL_MsgNode *msg_n = msgs->first; msg_n != 0; msg_n = msg_n->next) + { + CTRL_Msg *msg = &msg_n->v; + + // rjf: write flat parts + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->kind); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->run_flags); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->msg_id); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->machine_id); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->entity); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->parent); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->entity_id); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->exit_code); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->env_inherit); + str8_serial_push_array (scratch.arena, &msgs_srlzed, &msg->exception_code_filters[0], ArrayCount(msg->exception_code_filters)); + + // rjf: write path string + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->path.size); + str8_serial_push_data(scratch.arena, &msgs_srlzed, msg->path.str, msg->path.size); + + // rjf: write entry point string list + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->entry_points.node_count); + for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) + { + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &n->string.size); + str8_serial_push_data(scratch.arena, &msgs_srlzed, n->string.str, n->string.size); + } + + // rjf: write command line string list + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->cmd_line_string_list.node_count); + for(String8Node *n = msg->cmd_line_string_list.first; n != 0; n = n->next) + { + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &n->string.size); + str8_serial_push_data(scratch.arena, &msgs_srlzed, n->string.str, n->string.size); + } + + // rjf: write environment string list + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->env_string_list.node_count); + for(String8Node *n = msg->env_string_list.first; n != 0; n = n->next) + { + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &n->string.size); + str8_serial_push_data(scratch.arena, &msgs_srlzed, n->string.str, n->string.size); + } + + // rjf: write trap list + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->traps.count); + for(CTRL_TrapNode *n = msg->traps.first; n != 0; n = n->next) + { + CTRL_Trap *trap = &n->v; + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &trap->flags); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &trap->vaddr); + } + + // rjf: write user breakpoint list + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->user_bps.count); + for(CTRL_UserBreakpointNode *n = msg->user_bps.first; n != 0; n = n->next) + { + CTRL_UserBreakpoint *bp = &n->v; + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->kind); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->string.size); + str8_serial_push_data(scratch.arena, &msgs_srlzed, bp->string.str, bp->string.size); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->pt); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->u64); + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &bp->condition.size); + str8_serial_push_data(scratch.arena, &msgs_srlzed, bp->condition.str, bp->condition.size); + } + + // rjf: write freeze state thread list + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->freeze_state_threads.count); + for(CTRL_MachineIDHandlePairNode *n = msg->freeze_state_threads.first; n != 0; n = n->next) + { + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &n->v); + } + + // rjf: write freeze state + str8_serial_push_struct(scratch.arena, &msgs_srlzed, &msg->freeze_state_is_frozen); + } + } + String8 string = str8_serial_end(arena, &msgs_srlzed); + scratch_end(scratch); + return string; +} + +internal CTRL_MsgList +ctrl_msg_list_from_serialized_string(Arena *arena, String8 string) +{ + CTRL_MsgList msgs = {0}; + { + U64 read_off = 0; + + // rjf: read message count + U64 msg_count = 0; + read_off += str8_deserial_read_struct(string, read_off, &msg_count); + + // rjf: read data for all messages + for(U64 msg_idx = 0; msg_idx < msg_count; msg_idx += 1) + { + // rjf: construct message + CTRL_MsgNode *msg_node = push_array(arena, CTRL_MsgNode, 1); + SLLQueuePush(msgs.first, msgs.last, msg_node); + msgs.count += 1; + CTRL_Msg *msg = &msg_node->v; + + // rjf: read flat data + read_off += str8_deserial_read_struct(string, read_off, &msg->kind); + read_off += str8_deserial_read_struct(string, read_off, &msg->run_flags); + read_off += str8_deserial_read_struct(string, read_off, &msg->msg_id); + read_off += str8_deserial_read_struct(string, read_off, &msg->machine_id); + read_off += str8_deserial_read_struct(string, read_off, &msg->entity); + read_off += str8_deserial_read_struct(string, read_off, &msg->parent); + read_off += str8_deserial_read_struct(string, read_off, &msg->entity_id); + read_off += str8_deserial_read_struct(string, read_off, &msg->exit_code); + read_off += str8_deserial_read_struct(string, read_off, &msg->env_inherit); + read_off += str8_deserial_read_array (string, read_off, &msg->exception_code_filters[0], ArrayCount(msg->exception_code_filters)); + + // rjf: read path string + read_off += str8_deserial_read_struct(string, read_off, &msg->path.size); + msg->path.str = push_array_no_zero(arena, U8, msg->path.size); + read_off += str8_deserial_read(string, read_off, msg->path.str, msg->path.size, 1); + + // rjf: read entry point string list + U64 entry_point_list_string_count = 0; + read_off += str8_deserial_read_struct(string, read_off, &entry_point_list_string_count); + for(U64 idx = 0; idx < entry_point_list_string_count; idx += 1) + { + String8 str = {0}; + read_off += str8_deserial_read_struct(string, read_off, &str.size); + str.str = push_array_no_zero(arena, U8, str.size); + read_off += str8_deserial_read(string, read_off, str.str, str.size, 1); + str8_list_push(arena, &msg->entry_points, str); + } + + // rjf: read command line string list + U64 cmd_line_string_count = 0; + read_off += str8_deserial_read_struct(string, read_off, &cmd_line_string_count); + for(U64 idx = 0; idx < cmd_line_string_count; idx += 1) + { + String8 cmd_line_str = {0}; + read_off += str8_deserial_read_struct(string, read_off, &cmd_line_str.size); + cmd_line_str.str = push_array_no_zero(arena, U8, cmd_line_str.size); + read_off += str8_deserial_read(string, read_off, cmd_line_str.str, cmd_line_str.size, 1); + str8_list_push(arena, &msg->cmd_line_string_list, cmd_line_str); + } + + // rjf: read environment string list + U64 env_string_count = 0; + read_off += str8_deserial_read_struct(string, read_off, &env_string_count); + for(U64 idx = 0; idx < env_string_count; idx += 1) + { + String8 env_str = {0}; + read_off += str8_deserial_read_struct(string, read_off, &env_str.size); + env_str.str = push_array_no_zero(arena, U8, env_str.size); + read_off += str8_deserial_read(string, read_off, env_str.str, env_str.size, 1); + str8_list_push(arena, &msg->env_string_list, env_str); + } + + // rjf: read trap list + U64 trap_count = 0; + read_off += str8_deserial_read_struct(string, read_off, &trap_count); + for(U64 idx = 0; idx < trap_count; idx += 1) + { + CTRL_TrapNode *n = push_array(arena, CTRL_TrapNode, 1); + SLLQueuePush(msg->traps.first, msg->traps.last, n); + msg->traps.count += 1; + CTRL_Trap *trap = &n->v; + read_off += str8_deserial_read_struct(string, read_off, &trap->flags); + read_off += str8_deserial_read_struct(string, read_off, &trap->vaddr); + } + + // rjf: read user breakpoint list + U64 user_bp_count = 0; + read_off += str8_deserial_read_struct(string, read_off, &user_bp_count); + for(U64 idx = 0; idx < user_bp_count; idx += 1) + { + CTRL_UserBreakpointNode *n = push_array(arena, CTRL_UserBreakpointNode, 1); + SLLQueuePush(msg->user_bps.first, msg->user_bps.last, n); + msg->user_bps.count += 1; + CTRL_UserBreakpoint *bp = &n->v; + read_off += str8_deserial_read_struct(string, read_off, &bp->kind); + read_off += str8_deserial_read_struct(string, read_off, &bp->string.size); + bp->string.str = push_array_no_zero(arena, U8, bp->string.size); + read_off += str8_deserial_read(string, read_off, bp->string.str, bp->string.size, 1); + read_off += str8_deserial_read_struct(string, read_off, &bp->pt); + read_off += str8_deserial_read_struct(string, read_off, &bp->u64); + read_off += str8_deserial_read_struct(string, read_off, &bp->condition.size); + bp->condition.str = push_array_no_zero(arena, U8, bp->condition.size); + read_off += str8_deserial_read(string, read_off, bp->condition.str, bp->condition.size, 1); + } + + // rjf: read freeze state thread list + U64 frozen_thread_count = 0; + read_off += str8_deserial_read_struct(string, read_off, &frozen_thread_count); + for(U64 idx = 0; idx < frozen_thread_count; idx += 1) + { + CTRL_MachineIDHandlePair pair = {0}; + read_off += str8_deserial_read_struct(string, read_off, &pair); + ctrl_machine_id_handle_pair_list_push(arena, &msg->freeze_state_threads, &pair); + } + + // rjf: read freeze state + read_off += str8_deserial_read_struct(string, read_off, &msg->freeze_state_is_frozen); + } + } + return msgs; +} + +//////////////////////////////// +//~ rjf: Event Type Functions + +//- rjf: list building + +internal CTRL_Event * +ctrl_event_list_push(Arena *arena, CTRL_EventList *list) +{ + CTRL_EventNode *n = push_array(arena, CTRL_EventNode, 1); + SLLQueuePush(list->first, list->last, n); + list->count += 1; + CTRL_Event *event = &n->v; + return event; +} + +internal void +ctrl_event_list_concat_in_place(CTRL_EventList *dst, CTRL_EventList *to_push) +{ + if(dst->last == 0) + { + MemoryCopyStruct(dst, to_push); + } + else if(to_push->first != 0) + { + dst->last->next = to_push->first; + dst->last = to_push->last; + dst->count += to_push->count; + } + MemoryZeroStruct(to_push); +} + +//- rjf: serialization + +internal String8 +ctrl_serialized_string_from_event(Arena *arena, CTRL_Event *event, U64 max) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List srl = {0}; + str8_serial_begin(scratch.arena, &srl); + { + str8_serial_push_struct(scratch.arena, &srl, &event->kind); + str8_serial_push_struct(scratch.arena, &srl, &event->cause); + str8_serial_push_struct(scratch.arena, &srl, &event->exception_kind); + str8_serial_push_struct(scratch.arena, &srl, &event->msg_id); + str8_serial_push_struct(scratch.arena, &srl, &event->machine_id); + str8_serial_push_struct(scratch.arena, &srl, &event->entity); + str8_serial_push_struct(scratch.arena, &srl, &event->parent); + str8_serial_push_struct(scratch.arena, &srl, &event->arch); + str8_serial_push_struct(scratch.arena, &srl, &event->u64_code); + str8_serial_push_struct(scratch.arena, &srl, &event->entity_id); + str8_serial_push_struct(scratch.arena, &srl, &event->vaddr_rng); + str8_serial_push_struct(scratch.arena, &srl, &event->rip_vaddr); + str8_serial_push_struct(scratch.arena, &srl, &event->stack_base); + str8_serial_push_struct(scratch.arena, &srl, &event->tls_root); + str8_serial_push_struct(scratch.arena, &srl, &event->timestamp); + str8_serial_push_struct(scratch.arena, &srl, &event->exception_code); + String8 string = event->string; + string.size = Min(string.size, max-srl.total_size); + str8_serial_push_struct(scratch.arena, &srl, &string.size); + str8_serial_push_data(scratch.arena, &srl, string.str, string.size); + } + String8 string = str8_serial_end(arena, &srl); + scratch_end(scratch); + return string; +} + +internal CTRL_Event +ctrl_event_from_serialized_string(Arena *arena, String8 string) +{ + CTRL_Event event = zero_struct; + { + U64 read_off = 0; + read_off += str8_deserial_read_struct(string, read_off, &event.kind); + read_off += str8_deserial_read_struct(string, read_off, &event.cause); + read_off += str8_deserial_read_struct(string, read_off, &event.exception_kind); + read_off += str8_deserial_read_struct(string, read_off, &event.msg_id); + read_off += str8_deserial_read_struct(string, read_off, &event.machine_id); + read_off += str8_deserial_read_struct(string, read_off, &event.entity); + read_off += str8_deserial_read_struct(string, read_off, &event.parent); + read_off += str8_deserial_read_struct(string, read_off, &event.arch); + read_off += str8_deserial_read_struct(string, read_off, &event.u64_code); + read_off += str8_deserial_read_struct(string, read_off, &event.entity_id); + read_off += str8_deserial_read_struct(string, read_off, &event.vaddr_rng); + read_off += str8_deserial_read_struct(string, read_off, &event.rip_vaddr); + read_off += str8_deserial_read_struct(string, read_off, &event.stack_base); + read_off += str8_deserial_read_struct(string, read_off, &event.tls_root); + read_off += str8_deserial_read_struct(string, read_off, &event.timestamp); + read_off += str8_deserial_read_struct(string, read_off, &event.exception_code); + read_off += str8_deserial_read_struct(string, read_off, &event.string.size); + event.string.str = push_array_no_zero(arena, U8, event.string.size); + read_off += str8_deserial_read(string, read_off, event.string.str, event.string.size, 1); + } + return event; +} + +//////////////////////////////// +//~ rjf: Entity Type Functions + +//- rjf: cache creation/destruction + +internal CTRL_EntityStore * +ctrl_entity_store_alloc(void) +{ + Arena *arena = arena_alloc(); + CTRL_EntityStore *store = push_array(arena, CTRL_EntityStore, 1); + store->arena = arena; + store->hash_slots_count = 1024; + store->hash_slots = push_array(arena, CTRL_EntityHashSlot, store->hash_slots_count); + CTRL_Entity *root = store->root = ctrl_entity_alloc(store, &ctrl_entity_nil, CTRL_EntityKind_Root, Architecture_Null, 0, dmn_handle_zero(), 0); + CTRL_Entity *local_machine = ctrl_entity_alloc(store, root, CTRL_EntityKind_Machine, architecture_from_context(), CTRL_MachineID_Local, dmn_handle_zero(), 0); + (void)local_machine; + return store; +} + +internal void +ctrl_entity_store_release(CTRL_EntityStore *cache) +{ + arena_release(cache->arena); +} + +//- rjf: string allocation/deletion + +internal U64 +ctrl_name_bucket_idx_from_string_size(U64 size) +{ + U64 size_rounded = u64_up_to_pow2(size+1); + size_rounded = ClampBot((1<<4), size_rounded); + U64 bucket_idx = 0; + switch(size_rounded) + { + case 1<<4: {bucket_idx = 0;}break; + case 1<<5: {bucket_idx = 1;}break; + case 1<<6: {bucket_idx = 2;}break; + case 1<<7: {bucket_idx = 3;}break; + case 1<<8: {bucket_idx = 4;}break; + case 1<<9: {bucket_idx = 5;}break; + case 1<<10:{bucket_idx = 6;}break; + default:{bucket_idx = ArrayCount(((CTRL_EntityStore *)0)->free_string_chunks)-1;}break; + } + return bucket_idx; +} + +internal String8 +ctrl_entity_string_alloc(CTRL_EntityStore *store, String8 string) +{ + if(string.size == 0) {return str8_zero();} + U64 bucket_idx = ctrl_name_bucket_idx_from_string_size(string.size); + CTRL_EntityStringChunkNode *node = store->free_string_chunks[bucket_idx]; + + // rjf: pull from bucket free list + if(node != 0) + { + if(bucket_idx == ArrayCount(store->free_string_chunks)-1) + { + node = 0; + CTRL_EntityStringChunkNode *prev = 0; + for(CTRL_EntityStringChunkNode *n = store->free_string_chunks[bucket_idx]; + n != 0; + prev = n, n = n->next) + { + if(n->size >= string.size+1) + { + if(prev == 0) + { + store->free_string_chunks[bucket_idx] = n->next; + } + else + { + prev->next = n->next; + } + node = n; + break; + } + } + } + else + { + SLLStackPop(store->free_string_chunks[bucket_idx]); + } + } + + // rjf: no found node -> allocate new + if(node == 0) + { + U64 chunk_size = 0; + if(bucket_idx < ArrayCount(store->free_string_chunks)-1) + { + chunk_size = 1<<(bucket_idx+4); + } + else + { + chunk_size = u64_up_to_pow2(string.size); + } + U8 *chunk_memory = push_array(store->arena, U8, chunk_size); + node = (CTRL_EntityStringChunkNode *)chunk_memory; + } + + // rjf: fill string & return + String8 allocated_string = str8((U8 *)node, string.size); + MemoryCopy((U8 *)node, string.str, string.size); + return allocated_string; +} + +internal void +ctrl_entity_string_release(CTRL_EntityStore *store, String8 string) +{ + if(string.size == 0) {return;} + U64 bucket_idx = ctrl_name_bucket_idx_from_string_size(string.size); + CTRL_EntityStringChunkNode *node = (CTRL_EntityStringChunkNode *)string.str; + node->size = u64_up_to_pow2(string.size); + SLLStackPush(store->free_string_chunks[bucket_idx], node); +} + +//- rjf: entity construction/deletion + +internal CTRL_Entity * +ctrl_entity_alloc(CTRL_EntityStore *store, CTRL_Entity *parent, CTRL_EntityKind kind, Architecture arch, CTRL_MachineID machine_id, DMN_Handle handle, U64 id) +{ + CTRL_Entity *entity = &ctrl_entity_nil; + { + // rjf: allocate + entity = store->free; + { + if(entity != 0) + { + SLLStackPop(store->free); + } + else + { + entity = push_array_no_zero(store->arena, CTRL_Entity, 1); + } + MemoryZeroStruct(entity); + } + + // rjf: fill + { + entity->kind = kind; + entity->arch = arch; + entity->machine_id = machine_id; + entity->handle = handle; + entity->id = id; + entity->parent = parent; + entity->next = entity->prev = entity->first = entity->last = &ctrl_entity_nil; + if(parent != &ctrl_entity_nil) + { + DLLPushBack_NPZ(&ctrl_entity_nil, parent->first, parent->last, entity, next, prev); + } + } + + // rjf: insert into hash map + { + U64 hash = ctrl_hash_from_machine_id_handle(machine_id, handle); + U64 slot_idx = hash%store->hash_slots_count; + CTRL_EntityHashSlot *slot = &store->hash_slots[slot_idx]; + CTRL_EntityHashNode *node = 0; + for(CTRL_EntityHashNode *n = slot->first; n != 0; n = n->next) + { + if(n->entity->machine_id == machine_id && dmn_handle_match(n->entity->handle, handle)) + { + node = n; + break; + } + } + if(node == 0) + { + node = store->hash_node_free; + if(node != 0) + { + SLLStackPop(store->hash_node_free); + } + else + { + node = push_array_no_zero(store->arena, CTRL_EntityHashNode, 1); + } + MemoryZeroStruct(node); + DLLPushBack(slot->first, slot->last, node); + node->entity = entity; + } + } + } + return entity; +} + +internal void +ctrl_entity_release(CTRL_EntityStore *store, CTRL_Entity *entity) +{ + // rjf: unhook root + if(entity->parent != &ctrl_entity_nil) + { + DLLRemove_NPZ(&ctrl_entity_nil, entity->parent->first, entity->parent->last, entity, next, prev); + } + + // rjf: walk every entity in this tree, free each + if(entity != &ctrl_entity_nil) + { + Temp scratch = scratch_begin(0, 0); + typedef struct Task Task; + struct Task + { + Task *next; + CTRL_Entity *e; + }; + Task start_task = {0, entity}; + Task *first_task = &start_task; + Task *last_task = &start_task; + for(Task *t = first_task; t != 0; t = t->next) + { + for(CTRL_Entity *child = t->e->first; child != &ctrl_entity_nil; child = child->next) + { + Task *t = push_array(scratch.arena, Task, 1); + t->e = child; + SLLQueuePush(first_task, last_task, t); + } + + // rjf: free entity + SLLStackPush(store->free, t->e); + + // rjf: remove from hash map + { + U64 hash = ctrl_hash_from_machine_id_handle(t->e->machine_id, t->e->handle); + U64 slot_idx = hash%store->hash_slots_count; + CTRL_EntityHashSlot *slot = &store->hash_slots[slot_idx]; + CTRL_EntityHashNode *node = 0; + for(CTRL_EntityHashNode *n = slot->first; n != 0; n = n->next) + { + if(n->entity->machine_id == t->e->machine_id && dmn_handle_match(n->entity->handle, t->e->handle)) + { + DLLRemove(slot->first, slot->last, n); + SLLStackPush(store->hash_node_free, n); + break; + } + } + } + } + scratch_end(scratch); + } +} + +//- rjf: entity equipment + +internal void +ctrl_entity_equip_string(CTRL_EntityStore *store, CTRL_Entity *entity, String8 string) +{ + if(entity->string.size != 0) + { + ctrl_entity_string_release(store, entity->string); + } + entity->string = ctrl_entity_string_alloc(store, string); +} + +//- rjf: entity store lookups + +internal CTRL_Entity * +ctrl_entity_from_machine_id_handle(CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle handle) +{ + CTRL_Entity *entity = &ctrl_entity_nil; + { + U64 hash = ctrl_hash_from_machine_id_handle(machine_id, handle); + U64 slot_idx = hash%store->hash_slots_count; + CTRL_EntityHashSlot *slot = &store->hash_slots[slot_idx]; + CTRL_EntityHashNode *node = 0; + for(CTRL_EntityHashNode *n = slot->first; n != 0; n = n->next) + { + if(n->entity->machine_id == machine_id && dmn_handle_match(n->entity->handle, handle)) + { + entity = n->entity; + break; + } + } + } + return entity; +} + +internal CTRL_Entity * +ctrl_entity_child_from_kind(CTRL_Entity *parent, CTRL_EntityKind kind) +{ + CTRL_Entity *result = &ctrl_entity_nil; + for(CTRL_Entity *child = parent->first; + child != &ctrl_entity_nil; + child = child->next) + { + if(child->kind == kind) + { + result = child; + break; + } + } + return result; +} + +//- rjf: applying events to entity caches + +internal void +ctrl_entity_store_apply_events(CTRL_EntityStore *store, CTRL_EventList *list) +{ + //- rjf: scan events & construct entities + for(CTRL_EventNode *n = list->first; n != 0; n = n->next) + { + CTRL_Event *event = &n->v; + switch(event->kind) + { + default:{}break; + + //- rjf: processes + case CTRL_EventKind_NewProc: + { + CTRL_Entity *machine = ctrl_entity_from_machine_id_handle(store, event->machine_id, dmn_handle_zero()); + CTRL_Entity *process = ctrl_entity_alloc(store, machine, CTRL_EntityKind_Process, event->arch, event->machine_id, event->entity, (U64)event->entity_id); + }break; + case CTRL_EventKind_EndProc: + { + CTRL_Entity *process = ctrl_entity_from_machine_id_handle(store, event->machine_id, event->entity); + ctrl_entity_release(store, process); + for(CTRL_Entity *entry = store->root->first, *next = &ctrl_entity_nil; + entry != &ctrl_entity_nil; + entry = next) + { + next = entry->next; + if(entry->kind == CTRL_EntityKind_EntryPoint && entry->id == process->id) + { + ctrl_entity_release(store, entry); + } + } + }break; + + //- rjf: threads + case CTRL_EventKind_NewThread: + { + CTRL_Entity *process = ctrl_entity_from_machine_id_handle(store, event->machine_id, event->parent); + CTRL_Entity *thread = ctrl_entity_alloc(store, process, CTRL_EntityKind_Thread, event->arch, event->machine_id, event->entity, (U64)event->entity_id); + }break; + case CTRL_EventKind_EndThread: + { + CTRL_Entity *thread = ctrl_entity_from_machine_id_handle(store, event->machine_id, event->entity); + ctrl_entity_release(store, thread); + }break; + case CTRL_EventKind_ThreadName: + { + CTRL_Entity *thread = ctrl_entity_from_machine_id_handle(store, event->machine_id, event->entity); + ctrl_entity_equip_string(store, thread, event->string); + }break; + + //- rjf: modules + case CTRL_EventKind_NewModule: + { + Temp scratch = scratch_begin(0, 0); + CTRL_Entity *process = ctrl_entity_from_machine_id_handle(store, event->machine_id, event->parent); + CTRL_Entity *module = ctrl_entity_alloc(store, process, CTRL_EntityKind_Module, event->arch, event->machine_id, event->entity, event->vaddr_rng.min); + ctrl_entity_equip_string(store, module, event->string); + module->timestamp = event->timestamp; + module->vaddr_range = event->vaddr_rng; + scratch_end(scratch); + }break; + case CTRL_EventKind_EndModule: + { + CTRL_Entity *module = ctrl_entity_from_machine_id_handle(store, event->machine_id, event->entity); + ctrl_entity_release(store, module); + }break; + case CTRL_EventKind_ModuleDebugInfoPathChange: + { + CTRL_Entity *module = ctrl_entity_from_machine_id_handle(store, event->machine_id, event->entity); + CTRL_Entity *debug_info_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); + if(debug_info_path == &ctrl_entity_nil) + { + debug_info_path = ctrl_entity_alloc(store, module, CTRL_EntityKind_DebugInfoPath, Architecture_Null, 0, dmn_handle_zero(), 0); + } + ctrl_entity_equip_string(store, debug_info_path, event->string); + debug_info_path->timestamp = event->timestamp; + }break; + } + } +} + +//////////////////////////////// +//~ rjf: Main Layer Initialization + +internal void +ctrl_init(void) +{ + Arena *arena = arena_alloc(); + ctrl_state = push_array(arena, CTRL_State, 1); + ctrl_state->arena = arena; + for(Architecture arch = (Architecture)0; arch < Architecture_COUNT; arch = (Architecture)(arch+1)) + { + String8 *reg_names = regs_reg_code_string_table_from_architecture(arch); + U64 reg_count = regs_reg_code_count_from_architecture(arch); + String8 *alias_names = regs_alias_code_string_table_from_architecture(arch); + U64 alias_count = regs_alias_code_count_from_architecture(arch); + ctrl_state->arch_string2reg_tables[arch] = eval_string2num_map_make(ctrl_state->arena, 256); + ctrl_state->arch_string2alias_tables[arch] = eval_string2num_map_make(ctrl_state->arena, 256); + for(U64 idx = 1; idx < reg_count; idx += 1) + { + eval_string2num_map_insert(ctrl_state->arena, &ctrl_state->arch_string2reg_tables[arch], reg_names[idx], idx); + } + for(U64 idx = 1; idx < alias_count; idx += 1) + { + eval_string2num_map_insert(ctrl_state->arena, &ctrl_state->arch_string2alias_tables[arch], alias_names[idx], idx); + } + } + ctrl_state->process_memory_cache.slots_count = 256; + ctrl_state->process_memory_cache.slots = push_array(arena, CTRL_ProcessMemoryCacheSlot, ctrl_state->process_memory_cache.slots_count); + ctrl_state->process_memory_cache.stripes_count = os_get_system_info()->logical_processor_count; + ctrl_state->process_memory_cache.stripes = push_array(arena, CTRL_ProcessMemoryCacheStripe, ctrl_state->process_memory_cache.stripes_count); + for(U64 idx = 0; idx < ctrl_state->process_memory_cache.stripes_count; idx += 1) + { + ctrl_state->process_memory_cache.stripes[idx].rw_mutex = os_rw_mutex_alloc(); + ctrl_state->process_memory_cache.stripes[idx].cv = os_condition_variable_alloc(); + } + ctrl_state->thread_reg_cache.slots_count = 1024; + ctrl_state->thread_reg_cache.slots = push_array(arena, CTRL_ThreadRegCacheSlot, ctrl_state->thread_reg_cache.slots_count); + ctrl_state->thread_reg_cache.stripes_count = os_get_system_info()->logical_processor_count; + ctrl_state->thread_reg_cache.stripes = push_array(arena, CTRL_ThreadRegCacheStripe, ctrl_state->thread_reg_cache.stripes_count); + for(U64 idx = 0; idx < ctrl_state->thread_reg_cache.stripes_count; idx += 1) + { + ctrl_state->thread_reg_cache.stripes[idx].arena = arena_alloc(); + ctrl_state->thread_reg_cache.stripes[idx].rw_mutex = os_rw_mutex_alloc(); + } + ctrl_state->module_image_info_cache.slots_count = 1024; + ctrl_state->module_image_info_cache.slots = push_array(arena, CTRL_ModuleImageInfoCacheSlot, ctrl_state->module_image_info_cache.slots_count); + ctrl_state->module_image_info_cache.stripes_count = os_get_system_info()->logical_processor_count; + ctrl_state->module_image_info_cache.stripes = push_array(arena, CTRL_ModuleImageInfoCacheStripe, ctrl_state->module_image_info_cache.stripes_count); + for(U64 idx = 0; idx < ctrl_state->module_image_info_cache.stripes_count; idx += 1) + { + ctrl_state->module_image_info_cache.stripes[idx].arena = arena_alloc(); + ctrl_state->module_image_info_cache.stripes[idx].rw_mutex = os_rw_mutex_alloc(); + } + ctrl_state->u2c_ring_size = KB(64); + ctrl_state->u2c_ring_base = push_array_no_zero(arena, U8, ctrl_state->u2c_ring_size); + ctrl_state->u2c_ring_mutex = os_mutex_alloc(); + ctrl_state->u2c_ring_cv = os_condition_variable_alloc(); + ctrl_state->c2u_ring_size = KB(64); + ctrl_state->c2u_ring_max_string_size = ctrl_state->c2u_ring_size/2; + ctrl_state->c2u_ring_base = push_array_no_zero(arena, U8, ctrl_state->c2u_ring_size); + ctrl_state->c2u_ring_mutex = os_mutex_alloc(); + ctrl_state->c2u_ring_cv = os_condition_variable_alloc(); + { + Temp scratch = scratch_begin(0, 0); + String8 user_program_data_path = os_get_process_info()->user_program_data_path; + String8 user_data_folder = push_str8f(scratch.arena, "%S/raddbg/logs", user_program_data_path); + os_make_directory(user_data_folder); + ctrl_state->ctrl_thread_log_path = push_str8f(ctrl_state->arena, "%S/ctrl_thread.raddbg_log", user_data_folder); + os_write_data_to_file_path(ctrl_state->ctrl_thread_log_path, str8_zero()); + scratch_end(scratch); + } + ctrl_state->ctrl_thread_entity_store = ctrl_entity_store_alloc(); + ctrl_state->dmn_event_arena = arena_alloc(); + ctrl_state->user_entry_point_arena = arena_alloc(); + for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)0; k < CTRL_ExceptionCodeKind_COUNT; k = (CTRL_ExceptionCodeKind)(k+1)) + { + if(ctrl_exception_code_kind_default_enable_table[k]) + { + ctrl_state->exception_code_filters[k/64] |= 1ull<<(k%64); + } + } + ctrl_state->u2ms_ring_size = KB(64); + ctrl_state->u2ms_ring_base = push_array(arena, U8, ctrl_state->u2ms_ring_size); + ctrl_state->u2ms_ring_mutex = os_mutex_alloc(); + ctrl_state->u2ms_ring_cv = os_condition_variable_alloc(); + ctrl_state->ctrl_thread_log = log_alloc(); + ctrl_state->ctrl_thread = os_thread_launch(ctrl_thread__entry_point, 0, 0); + ctrl_state->ms_thread_count = Clamp(1, os_get_system_info()->logical_processor_count-1, 4); + ctrl_state->ms_threads = push_array(arena, OS_Handle, ctrl_state->ms_thread_count); + for(U64 idx = 0; idx < ctrl_state->ms_thread_count; idx += 1) + { + ctrl_state->ms_threads[idx] = os_thread_launch(ctrl_mem_stream_thread__entry_point, (void *)idx, 0); + } +} + +//////////////////////////////// +//~ rjf: Wakeup Callback Registration + +internal void +ctrl_set_wakeup_hook(CTRL_WakeupFunctionType *wakeup_hook) +{ + ctrl_state->wakeup_hook = wakeup_hook; +} + +//////////////////////////////// +//~ rjf: Process Memory Functions + +//- rjf: process memory cache interaction + +internal U128 +ctrl_calc_hash_store_key_from_process_vaddr_range(CTRL_MachineID machine_id, DMN_Handle process, Rng1U64 range, B32 zero_terminated) +{ + U64 key_hash_data[] = + { + (U64)machine_id, + (U64)process.u64[0], + range.min, + range.max, + (U64)zero_terminated, + }; + U128 key = hs_hash_from_data(str8((U8*)key_hash_data, sizeof(key_hash_data))); + return key; +} + +internal U128 +ctrl_stored_hash_from_process_vaddr_range(CTRL_MachineID machine_id, DMN_Handle process, Rng1U64 range, B32 zero_terminated, B32 *out_is_stale, U64 endt_us) +{ + U128 result = {0}; + U64 size = dim_1u64(range); + U64 pre_mem_gen = dmn_mem_gen(); + if(size != 0) for(;;) + { + CTRL_ProcessMemoryCache *cache = &ctrl_state->process_memory_cache; + U64 process_hash = ctrl_hash_from_string(str8_struct(&process)); + U64 process_slot_idx = process_hash%cache->slots_count; + U64 process_stripe_idx = process_slot_idx%cache->stripes_count; + CTRL_ProcessMemoryCacheSlot *process_slot = &cache->slots[process_slot_idx]; + CTRL_ProcessMemoryCacheStripe *process_stripe = &cache->stripes[process_stripe_idx]; + U64 range_hash = ctrl_hash_from_string(str8_struct(&range)); + + //- rjf: try to read from cache + B32 is_good = 0; + B32 is_stale = 1; + OS_MutexScopeR(process_stripe->rw_mutex) + { + for(CTRL_ProcessMemoryCacheNode *n = process_slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->process, process)) + { + U64 range_slot_idx = range_hash%n->range_hash_slots_count; + CTRL_ProcessMemoryRangeHashSlot *range_slot = &n->range_hash_slots[range_slot_idx]; + for(CTRL_ProcessMemoryRangeHashNode *range_n = range_slot->first; range_n != 0; range_n = range_n->next) + { + if(MemoryMatchStruct(&range_n->vaddr_range, &range) && range_n->zero_terminated == zero_terminated) + { + result = range_n->hash; + is_good = 1; + is_stale = (range_n->mem_gen != pre_mem_gen); + goto read_cache__break_all; + } + } + } + } + read_cache__break_all:; + } + + //- rjf: not good -> create process cache node if necessary + if(!is_good) + { + OS_MutexScopeW(process_stripe->rw_mutex) + { + B32 process_node_exists = 0; + for(CTRL_ProcessMemoryCacheNode *n = process_slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->process, process)) + { + process_node_exists = 1; + break; + } + } + if(!process_node_exists) + { + Arena *node_arena = arena_alloc(); + CTRL_ProcessMemoryCacheNode *node = push_array(node_arena, CTRL_ProcessMemoryCacheNode, 1); + node->arena = node_arena; + node->machine_id = machine_id; + node->process = process; + node->range_hash_slots_count = 1024; + node->range_hash_slots = push_array(node_arena, CTRL_ProcessMemoryRangeHashSlot, node->range_hash_slots_count); + DLLPushBack(process_slot->first, process_slot->last, node); + } + } + } + + //- rjf: not good -> create range node if necessary + U64 last_time_requested_us = 0; + if(!is_good) + { + OS_MutexScopeW(process_stripe->rw_mutex) + { + for(CTRL_ProcessMemoryCacheNode *n = process_slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->process, process)) + { + U64 range_slot_idx = range_hash%n->range_hash_slots_count; + CTRL_ProcessMemoryRangeHashSlot *range_slot = &n->range_hash_slots[range_slot_idx]; + B32 range_node_exists = 0; + for(CTRL_ProcessMemoryRangeHashNode *range_n = range_slot->first; range_n != 0; range_n = range_n->next) + { + if(MemoryMatchStruct(&range_n->vaddr_range, &range) && range_n->zero_terminated == zero_terminated) + { + last_time_requested_us = range_n->last_time_requested_us; + range_node_exists = 1; + break; + } + } + if(!range_node_exists) + { + CTRL_ProcessMemoryRangeHashNode *range_n = push_array(n->arena, CTRL_ProcessMemoryRangeHashNode, 1); + SLLQueuePush(range_slot->first, range_slot->last, range_n); + range_n->vaddr_range = range; + range_n->zero_terminated = zero_terminated; + range_n->vaddr_range_clamped = range; + { + range_n->vaddr_range_clamped.max = Max(range_n->vaddr_range_clamped.max, range_n->vaddr_range_clamped.min); + U64 max_size_cap = Min(max_U64-range_n->vaddr_range_clamped.min, GB(1)); + range_n->vaddr_range_clamped.max = Min(range_n->vaddr_range_clamped.max, range_n->vaddr_range_clamped.min+max_size_cap); + } + break; + } + } + } + } + } + + //- rjf: not good, or is stale -> submit hash request + if((!is_good || is_stale) && os_now_microseconds() >= last_time_requested_us+10000) + { + if(ctrl_u2ms_enqueue_req(machine_id, process, range, zero_terminated, endt_us)) OS_MutexScopeW(process_stripe->rw_mutex) + { + for(CTRL_ProcessMemoryCacheNode *n = process_slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->process, process)) + { + U64 range_slot_idx = range_hash%n->range_hash_slots_count; + CTRL_ProcessMemoryRangeHashSlot *range_slot = &n->range_hash_slots[range_slot_idx]; + for(CTRL_ProcessMemoryRangeHashNode *range_n = range_slot->first; range_n != 0; range_n = range_n->next) + { + if(MemoryMatchStruct(&range_n->vaddr_range, &range) && range_n->zero_terminated == zero_terminated) + { + range_n->last_time_requested_us = os_now_microseconds(); + break; + } + } + } + } + } + } + + //- rjf: out of time? -> exit + if(os_now_microseconds() >= endt_us) + { + if(is_stale && out_is_stale) + { + out_is_stale[0] = 1; + } + break; + } + + //- rjf: done? -> exit + if(is_good && !is_stale) + { + break; + } + } + U64 post_mem_gen = dmn_mem_gen(); + if(post_mem_gen != pre_mem_gen && out_is_stale) + { + out_is_stale[0] = 1; + } + return result; +} + +//- rjf: bundled key/stream helper + +internal U128 +ctrl_hash_store_key_from_process_vaddr_range(CTRL_MachineID machine_id, DMN_Handle process, Rng1U64 range, B32 zero_terminated) +{ + U128 key = ctrl_calc_hash_store_key_from_process_vaddr_range(machine_id, process, range, zero_terminated); + ctrl_stored_hash_from_process_vaddr_range(machine_id, process, range, zero_terminated, 0, 0); + return key; +} + +//- rjf: process memory cache reading helpers + +internal CTRL_ProcessMemorySlice +ctrl_query_cached_data_from_process_vaddr_range(Arena *arena, CTRL_MachineID machine_id, DMN_Handle process, Rng1U64 range, U64 endt_us) +{ + CTRL_ProcessMemorySlice result = {0}; + if(range.max > range.min && + dim_1u64(range) <= MB(256) && + range.min <= 0x000FFFFFFFFFFFFFull && + range.max <= 0x000FFFFFFFFFFFFFull) + { + Temp scratch = scratch_begin(&arena, 1); + HS_Scope *scope = hs_scope_open(); + CTRL_ProcessMemoryCache *cache = &ctrl_state->process_memory_cache; + + //- rjf: unpack address range, prepare per-touched-page info + U64 page_size = KB(4); + Rng1U64 page_range = r1u64(AlignDownPow2(range.min, page_size), AlignPow2(range.max, page_size)); + U64 page_count = dim_1u64(page_range)/page_size; + U128 *page_hashes = push_array(scratch.arena, U128, page_count); + U128 *page_last_hashes = push_array(scratch.arena, U128, page_count); + + //- rjf: gather hashes & last-hashes for each page + for(U64 page_idx = 0; page_idx < page_count; page_idx += 1) + { + U64 page_base_vaddr = page_range.min + page_idx*page_size; + U128 page_key = ctrl_calc_hash_store_key_from_process_vaddr_range(machine_id, process, r1u64(page_base_vaddr, page_base_vaddr+page_size), 0); + B32 page_is_stale = 0; + U128 page_hash = ctrl_stored_hash_from_process_vaddr_range(machine_id, process, r1u64(page_base_vaddr, page_base_vaddr+page_size), 0, &page_is_stale, endt_us); + U128 page_last_hash = hs_hash_from_key(page_key, 1); + result.stale = (result.stale || page_is_stale); + page_hashes[page_idx] = page_hash; + page_last_hashes[page_idx] = page_last_hash; + } + + //- rjf: setup output buffers + void *read_out = push_array(arena, U8, dim_1u64(range)); + U64 *byte_bad_flags = push_array(arena, U64, (dim_1u64(range)+63)/64); + U64 *byte_changed_flags = push_array(arena, U64, (dim_1u64(range)+63)/64); + + //- rjf: iterate pages, fill output + { + U64 write_off = 0; + for(U64 page_idx = 0; page_idx < page_count; page_idx += 1) + { + // rjf: read data for this page + String8 data = hs_data_from_hash(scope, page_hashes[page_idx]); + Rng1U64 data_vaddr_range = r1u64(page_range.min + page_idx*page_size, page_range.min + page_idx*page_size+data.size); + + // rjf: skip/chop bytes which are irrelevant for the actual requested read + String8 in_range_data = data; + if(page_idx == page_count-1 && data_vaddr_range.max > range.max) + { + in_range_data = str8_chop(in_range_data, data_vaddr_range.max-range.max); + } + if(page_idx == 0 && range.min > data_vaddr_range.min) + { + in_range_data = str8_skip(in_range_data, range.min-data_vaddr_range.min); + } + + // rjf: write this chunk + MemoryCopy((U8*)read_out+write_off, in_range_data.str, in_range_data.size); + + // rjf; if this page's data doesn't fill the entire range, mark + // missing bytes as bad + if(data.size < page_size) + { + for(U64 invalid_vaddr = data_vaddr_range.min+data.size; + invalid_vaddr < data_vaddr_range.min + page_size; + invalid_vaddr += 1) + { + if(contains_1u64(range, invalid_vaddr)) + { + U64 idx_in_range = invalid_vaddr-range.min; + byte_bad_flags[idx_in_range/64] |= (1ull<<(idx_in_range%64)); + } + } + } + + // rjf: if this page's hash & last_hash don't match, diff each byte & + // fill out changed flags + if(!u128_match(page_hashes[page_idx], page_last_hashes[page_idx])) + { + String8 last_data = hs_data_from_hash(scope, page_last_hashes[page_idx]); + String8 in_range_last_data = last_data; + if(page_idx == page_count-1 && data_vaddr_range.max > range.max) + { + in_range_last_data = str8_chop(in_range_last_data, data_vaddr_range.max-range.max); + } + if(page_idx == 0 && range.min > data_vaddr_range.min) + { + in_range_last_data = str8_skip(in_range_last_data, range.min-data_vaddr_range.min); + } + for(U64 idx = 0; idx < in_range_data.size; idx += 1) + { + U8 last_byte = idx < in_range_last_data.size ? in_range_last_data.str[idx] : 0; + U8 now_byte = idx < in_range_data.size ? in_range_data.str[idx] : 0; + if(last_byte != now_byte) + { + U64 idx_in_read_out = write_off+idx; + byte_changed_flags[idx_in_read_out/64] |= (1ull<<(idx_in_read_out%64)); + } + } + } + + // rjf: increment past this chunk + write_off += in_range_data.size; + if(data.size < page_size) + { + U64 missed_byte_count = page_size-data.size; + write_off += missed_byte_count; + } + } + } + + //- rjf: fill result + result.data.str = (U8*)read_out; + result.data.size = dim_1u64(range); + result.byte_bad_flags = byte_bad_flags; + result.byte_changed_flags = byte_changed_flags; + if(byte_bad_flags != 0) + { + for(U64 idx = 0; idx < (dim_1u64(range)+63)/64; idx += 1) + { + result.any_byte_bad = result.any_byte_bad || !!result.byte_bad_flags[idx]; + } + } + if(byte_changed_flags != 0) + { + for(U64 idx = 0; idx < (dim_1u64(range)+63)/64; idx += 1) + { + result.any_byte_changed = result.any_byte_changed || !!result.byte_changed_flags[idx]; + } + } + + hs_scope_close(scope); + scratch_end(scratch); + } + return result; +} + +internal CTRL_ProcessMemorySlice +ctrl_query_cached_zero_terminated_data_from_process_vaddr_limit(Arena *arena, CTRL_MachineID machine_id, DMN_Handle process, U64 vaddr, U64 limit, U64 element_size, U64 endt_us) +{ + CTRL_ProcessMemorySlice result = ctrl_query_cached_data_from_process_vaddr_range(arena, machine_id, process, r1u64(vaddr, vaddr+limit), endt_us); + U64 element_count = result.data.size/element_size; + for(U64 element_idx = 0; element_idx < element_count; element_idx += 1) + { + B32 element_is_zero = 1; + for(U64 element_byte_idx = 0; element_byte_idx < element_size; element_byte_idx += 1) + { + if(result.data.str[element_idx*element_size + element_byte_idx] != 0) + { + element_is_zero = 0; + break; + } + } + if(element_is_zero) + { + result.data.size = element_idx*element_size; + break; + } + } + return result; +} + +internal B32 +ctrl_read_cached_process_memory(CTRL_MachineID machine_id, DMN_Handle process, Rng1U64 range, B32 *is_stale_out, void *out, U64 endt_us) +{ + Temp scratch = scratch_begin(0, 0); + U64 needed_size = dim_1u64(range); + CTRL_ProcessMemorySlice slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, machine_id, process, range, endt_us); + B32 good = (slice.data.size >= needed_size && !slice.any_byte_bad); + if(good) + { + MemoryCopy(out, slice.data.str, needed_size); + } + if(slice.stale && is_stale_out) + { + *is_stale_out = 1; + } + scratch_end(scratch); + return good; +} + +//- rjf: process memory writing + +internal B32 +ctrl_process_write(CTRL_MachineID machine_id, DMN_Handle process, Rng1U64 range, void *src) +{ + ProfBeginFunction(); + B32 result = dmn_process_write(process, range, src); + + //- rjf: success -> wait for cache updates, for small regions - prefer relatively seamless + // writes within calling frame's "view" of the memory, at the expense of a small amount of + // time. + if(result) + { + Temp scratch = scratch_begin(0, 0); + U64 endt_us = os_now_microseconds()+5000; + + //- rjf: gather tasks for all affected cached regions + typedef struct Task Task; + struct Task + { + Task *next; + CTRL_MachineID machine_id; + DMN_Handle process; + Rng1U64 range; + }; + Task *first_task = 0; + Task *last_task = 0; + CTRL_ProcessMemoryCache *cache = &ctrl_state->process_memory_cache; + for(U64 slot_idx = 0; slot_idx < cache->slots_count; slot_idx += 1) + { + U64 stripe_idx = slot_idx%cache->stripes_count; + CTRL_ProcessMemoryCacheSlot *slot = &cache->slots[slot_idx]; + CTRL_ProcessMemoryCacheStripe *stripe = &cache->stripes[stripe_idx]; + OS_MutexScopeW(stripe->rw_mutex) + { + for(CTRL_ProcessMemoryCacheNode *proc_n = slot->first; proc_n != 0; proc_n = proc_n->next) + { + for(U64 range_hash_idx = 0; range_hash_idx < proc_n->range_hash_slots_count; range_hash_idx += 1) + { + CTRL_ProcessMemoryRangeHashSlot *range_slot = &proc_n->range_hash_slots[range_hash_idx]; + for(CTRL_ProcessMemoryRangeHashNode *n = range_slot->first; n != 0; n = n->next) + { + Rng1U64 intersection_w_range = intersect_1u64(range, n->vaddr_range); + if(dim_1u64(intersection_w_range) != 0 && dim_1u64(n->vaddr_range) <= KB(64)) + { + Task *task = push_array(scratch.arena, Task, 1); + task->machine_id = proc_n->machine_id; + task->process = proc_n->process; + task->range = n->vaddr_range; + SLLQueuePush(first_task, last_task, task); + } + } + } + } + } + } + + //- rjf: for all tasks, wait for up-to-date results + for(Task *task = first_task; task != 0; task = task->next) + { + Temp temp = temp_begin(scratch.arena); + ctrl_query_cached_data_from_process_vaddr_range(temp.arena, task->machine_id, task->process, task->range, endt_us); + temp_end(temp); + } + + scratch_end(scratch); + } + + ProfEnd(); + return result; +} + +//////////////////////////////// +//~ rjf: Thread Register Functions + +//- rjf: thread register cache reading + +internal void * +ctrl_query_cached_reg_block_from_thread(Arena *arena, CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle thread) +{ + CTRL_ThreadRegCache *cache = &ctrl_state->thread_reg_cache; + CTRL_Entity *thread_entity = ctrl_entity_from_machine_id_handle(store, machine_id, thread); + Architecture arch = thread_entity->arch; + U64 reg_block_size = regs_block_size_from_architecture(arch); + U64 hash = ctrl_hash_from_machine_id_handle(machine_id, thread); + U64 slot_idx = hash%cache->slots_count; + U64 stripe_idx = slot_idx%cache->stripes_count; + CTRL_ThreadRegCacheSlot *slot = &cache->slots[slot_idx]; + CTRL_ThreadRegCacheStripe *stripe = &cache->stripes[stripe_idx]; + void *result = push_array(arena, U8, reg_block_size); + OS_MutexScopeR(stripe->rw_mutex) + { + // rjf: find existing node + CTRL_ThreadRegCacheNode *node = 0; + for(CTRL_ThreadRegCacheNode *n = slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->thread, thread)) + { + node = n; + break; + } + } + + // rjf: allocate existing node + if(!node) + { + OS_MutexScopeRWPromote(stripe->rw_mutex) + { + for(CTRL_ThreadRegCacheNode *n = slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->thread, thread)) + { + node = n; + break; + } + } + if(!node) + { + node = push_array(stripe->arena, CTRL_ThreadRegCacheNode, 1); + DLLPushBack(slot->first, slot->last, node); + node->machine_id = machine_id; + node->thread = thread; + node->block_size = reg_block_size; + node->block = push_array(stripe->arena, U8, reg_block_size); + } + } + for(CTRL_ThreadRegCacheNode *n = slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->thread, thread)) + { + node = n; + break; + } + } + } + + // rjf: copy from node + if(node) + { + U64 current_reg_gen = dmn_reg_gen(); + B32 need_stale = 1; + if(node->reg_gen != current_reg_gen && dmn_thread_read_reg_block(thread, result)) + { + OS_MutexScopeRWPromote(stripe->rw_mutex) + { + for(CTRL_ThreadRegCacheNode *n = slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->thread, thread)) + { + node = n; + break; + } + } + if(node != 0) + { + need_stale = 0; + node->reg_gen = current_reg_gen; + MemoryCopy(node->block, result, reg_block_size); + } + } + } + if(need_stale) + { + MemoryCopy(result, node->block, reg_block_size); + } + } + } + return result; +} + +internal U64 +ctrl_query_cached_tls_root_vaddr_from_thread(CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle thread) +{ + U64 result = dmn_tls_root_vaddr_from_thread(thread); + return result; +} + +internal U64 +ctrl_query_cached_rip_from_thread(CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle thread) +{ + Temp scratch = scratch_begin(0, 0); + CTRL_Entity *thread_entity = ctrl_entity_from_machine_id_handle(store, machine_id, thread); + Architecture arch = thread_entity->arch; + void *block = ctrl_query_cached_reg_block_from_thread(scratch.arena, store, machine_id, thread); + U64 result = regs_rip_from_arch_block(arch, block); + scratch_end(scratch); + return result; +} + +internal U64 +ctrl_query_cached_rsp_from_thread(CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle thread) +{ + Temp scratch = scratch_begin(0, 0); + CTRL_Entity *thread_entity = ctrl_entity_from_machine_id_handle(store, machine_id, thread); + Architecture arch = thread_entity->arch; + void *block = ctrl_query_cached_reg_block_from_thread(scratch.arena, store, machine_id, thread); + U64 result = regs_rsp_from_arch_block(arch, block); + scratch_end(scratch); + return result; +} + +//- rjf: thread register writing + +internal B32 +ctrl_thread_write_reg_block(CTRL_MachineID machine_id, DMN_Handle thread, void *block) +{ + B32 good = dmn_thread_write_reg_block(thread, block); + return good; +} + +//////////////////////////////// +//~ rjf: Module Image Info Functions + +//- rjf: cache lookups + +internal PE_IntelPdata * +ctrl_intel_pdata_from_module_voff(Arena *arena, CTRL_MachineID machine_id, DMN_Handle module_handle, U64 voff) +{ + PE_IntelPdata *first_pdata = 0; + { + U64 hash = ctrl_hash_from_machine_id_handle(machine_id, module_handle); + U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; + U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; + CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; + CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; + OS_MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->module, module_handle)) + { + PE_IntelPdata *pdatas = n->pdatas; + U64 pdatas_count = n->pdatas_count; + if(n->pdatas_count != 0 && voff >= n->pdatas[0].voff_first) + { + // NOTE(rjf): + // + // binary search: + // find max index s.t. pdata_array[index].voff_first <= voff + // we assume (i < j) -> (pdata_array[i].voff_first < pdata_array[j].voff_first) + U64 index = pdatas_count; + U64 min = 0; + U64 opl = pdatas_count; + for(;;) + { + U64 mid = (min + opl)/2; + PE_IntelPdata *pdata = pdatas + mid; + if(voff < pdata->voff_first) + { + opl = mid; + } + else if(pdata->voff_first < voff) + { + min = mid; + } + else + { + index = mid; + break; + } + if(min + 1 >= opl) + { + index = min; + break; + } + } + + // rjf: if we are in range fill result + { + PE_IntelPdata *pdata = pdatas + index; + if(pdata->voff_first <= voff && voff < pdata->voff_one_past_last) + { + first_pdata = push_array(arena, PE_IntelPdata, 1); + MemoryCopyStruct(first_pdata, pdata); + } + } + } + break; + } + } + } + return first_pdata; +} + +internal U64 +ctrl_entry_point_voff_from_module(CTRL_MachineID machine_id, DMN_Handle module_handle) +{ + U64 result = 0; + U64 hash = ctrl_hash_from_machine_id_handle(machine_id, module_handle); + U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; + U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; + CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; + CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; + OS_MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->module, module_handle)) + { + result = n->entry_point_voff; + break; + } + } + return result; +} + +internal Rng1U64 +ctrl_tls_vaddr_range_from_module(CTRL_MachineID machine_id, DMN_Handle module_handle) +{ + Rng1U64 result = {0}; + U64 hash = ctrl_hash_from_machine_id_handle(machine_id, module_handle); + U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; + U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; + CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; + CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; + OS_MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->module, module_handle)) + { + result = n->tls_vaddr_range; + break; + } + } + return result; +} + +internal String8 +ctrl_initial_debug_info_path_from_module(Arena *arena, CTRL_MachineID machine_id, DMN_Handle module_handle) +{ + String8 result = {0}; + U64 hash = ctrl_hash_from_machine_id_handle(machine_id, module_handle); + U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; + U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; + CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; + CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; + OS_MutexScopeR(stripe->rw_mutex) for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->module, module_handle)) + { + result = push_str8_copy(arena, n->initial_debug_info_path); + break; + } + } + return result; +} + +//////////////////////////////// +//~ rjf: Unwinding Functions + +//- rjf: unwind deep copier + +internal CTRL_Unwind +ctrl_unwind_deep_copy(Arena *arena, Architecture arch, CTRL_Unwind *src) +{ + CTRL_Unwind dst = {0}; + { + dst.flags = src->flags; + dst.frames.count = src->frames.count; + dst.frames.v = push_array(arena, CTRL_UnwindFrame, dst.frames.count); + MemoryCopy(dst.frames.v, src->frames.v, sizeof(dst.frames.v[0])*dst.frames.count); + U64 block_size = regs_block_size_from_architecture(arch); + for(U64 idx = 0; idx < dst.frames.count; idx += 1) + { + dst.frames.v[idx].regs = push_array_no_zero(arena, U8, block_size); + MemoryCopy(dst.frames.v[idx].regs, src->frames.v[idx].regs, block_size); + } + } + return dst; +} + +//- rjf: [x64] + +internal REGS_Reg64 * +ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(REGS_RegBlockX64 *regs, PE_UnwindGprRegX64 gpr_reg) +{ + local_persist REGS_Reg64 dummy = {0}; + REGS_Reg64 *result = &dummy; + switch(gpr_reg) + { + case PE_UnwindGprRegX64_RAX:{result = ®s->rax;}break; + case PE_UnwindGprRegX64_RCX:{result = ®s->rcx;}break; + case PE_UnwindGprRegX64_RDX:{result = ®s->rdx;}break; + case PE_UnwindGprRegX64_RBX:{result = ®s->rbx;}break; + case PE_UnwindGprRegX64_RSP:{result = ®s->rsp;}break; + case PE_UnwindGprRegX64_RBP:{result = ®s->rbp;}break; + case PE_UnwindGprRegX64_RSI:{result = ®s->rsi;}break; + case PE_UnwindGprRegX64_RDI:{result = ®s->rdi;}break; + case PE_UnwindGprRegX64_R8 :{result = ®s->r8 ;}break; + case PE_UnwindGprRegX64_R9 :{result = ®s->r9 ;}break; + case PE_UnwindGprRegX64_R10:{result = ®s->r10;}break; + case PE_UnwindGprRegX64_R11:{result = ®s->r11;}break; + case PE_UnwindGprRegX64_R12:{result = ®s->r12;}break; + case PE_UnwindGprRegX64_R13:{result = ®s->r13;}break; + case PE_UnwindGprRegX64_R14:{result = ®s->r14;}break; + case PE_UnwindGprRegX64_R15:{result = ®s->r15;}break; + } + return result; +} + +internal CTRL_UnwindStepResult +ctrl_unwind_step__pe_x64(CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle process_handle, DMN_Handle module_handle, REGS_RegBlockX64 *regs, U64 endt_us) +{ + B32 is_stale = 0; + B32 is_good = 1; + Temp scratch = scratch_begin(0, 0); + + ////////////////////////////// + //- rjf: unpack parameters + // + CTRL_Entity *module = ctrl_entity_from_machine_id_handle(store, machine_id, module_handle); + CTRL_Entity *process = ctrl_entity_from_machine_id_handle(store, machine_id, process_handle); + U64 rip_voff = regs->rip.u64 - module->vaddr_range.min; + + ////////////////////////////// + //- rjf: rip_voff -> first pdata + // + PE_IntelPdata *first_pdata = ctrl_intel_pdata_from_module_voff(scratch.arena, machine_id, module_handle, rip_voff); + + ////////////////////////////// + //- rjf: pdata -> detect if in epilog + // + B32 has_pdata_and_in_epilog = 0; + if(first_pdata) ProfScope("pdata -> detect if in epilog") + { + // NOTE(allen): There are restrictions placed on how an epilog is allowed + // to be formed (https://docs.microsoft.com/en-us/cpp/build/prolog-and-epilog?view=msvc-160) + // Here we interpret machine code directly according to the rules + // given there to determine if the code we're looking at looks like an epilog. + + //- rjf: set up parsing state + B32 is_epilog = 0; + B32 keep_parsing = 1; + U64 read_vaddr = regs->rip.u64; + U64 read_vaddr_opl = read_vaddr + 256; + + //- rjf: check first instruction + { + B32 inst_good = 0; + U8 inst[4] = {0}; + if(read_vaddr + sizeof(inst) <= read_vaddr_opl) + { + inst_good = ctrl_read_cached_process_memory(machine_id, process->handle, r1u64(read_vaddr, read_vaddr+sizeof(inst)), &is_stale, inst, endt_us); + inst_good = inst_good && !is_stale; + } + if(!inst_good) + { + keep_parsing = 0; + } + else if((inst[0] & 0xF8) == 0x48) + { + switch(inst[1]) + { + // rjf: add $nnnn,%rsp + case 0x81: + { + if(inst[0] == 0x48 && inst[2] == 0xC4) + { + read_vaddr += 7; + } + else + { + keep_parsing = 0; + } + }break; + + // rjf: add $n,%rsp + case 0x83: + { + if(inst[0] == 0x48 && inst[2] == 0xC4) + { + read_vaddr += 4; + } + else + { + keep_parsing = 0; + } + }break; + + // rjf: lea n(reg),%rsp + case 0x8D: + { + if((inst[0] & 0x06) == 0 && + ((inst[2] >> 3) & 0x07) == 0x04 && + (inst[2] & 0x07) != 0x04) + { + U8 imm_size = (inst[2] >> 6); + + // rjf: 1-byte immediate + if(imm_size == 1) + { + read_vaddr += 4; + } + + // rjf: 4-byte immediate + else if(imm_size == 2) + { + read_vaddr += 7; + } + + // rjf: other case + else + { + keep_parsing = 0; + } + } + else + { + keep_parsing = 0; + } + }break; + } + } + } + + //- rjf: continue parsing instructions + for(;keep_parsing;) + { + // rjf: read next instruction byte + B32 inst_byte_good = 0; + U8 inst_byte = 0; + if(read_vaddr + sizeof(inst_byte) <= read_vaddr_opl) + { + inst_byte_good = ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &inst_byte, endt_us); + } + if(!inst_byte_good || is_stale) + { + keep_parsing = 0; + } + + // rjf: when (... I don't know ...) rely on the next byte + B32 check_inst_byte_good = inst_byte_good; + U64 check_vaddr = read_vaddr; + U8 check_inst_byte = inst_byte; + if(inst_byte_good && (inst_byte & 0xF0) == 0x40) + { + check_vaddr = read_vaddr + 1; + if(read_vaddr + sizeof(check_inst_byte) <= read_vaddr_opl) + { + check_inst_byte_good = ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &check_inst_byte, endt_us); + } + if(!check_inst_byte_good || is_stale) + { + keep_parsing = 0; + } + } + + // rjf: check instruction byte + if(check_inst_byte_good) + { + switch(check_inst_byte) + { + // rjf: pop + case 0x58:case 0x59:case 0x5A:case 0x5B: + case 0x5C:case 0x5D:case 0x5E:case 0x5F: + { + read_vaddr = check_vaddr + 1; + }break; + + // rjf: ret + case 0xC2: + case 0xC3: + { + is_epilog = 1; + keep_parsing = 0; + }break; + + // rjf: jmp nnnn + case 0xE9: + { + U64 imm_vaddr = check_vaddr + 1; + S32 imm = 0; + B32 imm_good = 0; + if(read_vaddr + sizeof(imm) <= read_vaddr_opl) + { + imm_good = ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &imm, endt_us); + } + if(!imm_good || is_stale) + { + keep_parsing = 0; + } + if(imm_good) + { + U64 next_vaddr = (U64)(imm_vaddr + sizeof(imm) + imm); + U64 next_voff = next_vaddr - module->vaddr_range.min; // TODO(rjf): verify that this offset is from module base vaddr, not section + if(!(first_pdata->voff_first <= next_voff && next_voff < first_pdata->voff_one_past_last)) + { + keep_parsing = 0; + } + else + { + read_vaddr = next_vaddr; + } + } + // TODO(allen): why isn't this just the end of the epilog? + }break; + + // rjf: rep; ret (for amd64 prediction bug) + case 0xF3: + { + U8 next_inst_byte = 0; + B32 next_inst_byte_good = 0; + if(read_vaddr + sizeof(next_inst_byte) <= read_vaddr_opl) + { + next_inst_byte_good = ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &next_inst_byte, endt_us); + } + if(next_inst_byte_good) + { + is_epilog = (next_inst_byte == 0xC3); + } + keep_parsing = 0; + }break; + + default:{keep_parsing = 0;}break; + } + } + } + has_pdata_and_in_epilog = is_epilog; + } + + ////////////////////////////// + //- rjf: pdata & in epilog -> epilog unwind + // + if(first_pdata && has_pdata_and_in_epilog) ProfScope("pdata & in epilog -> epilog unwind") + { + U64 read_vaddr = regs->rip.u64; + for(B32 keep_parsing = 1;keep_parsing != 0;) + { + //- rjf: assume no more parsing after this instruction + keep_parsing = 0; + + //- rjf: read next instruction byte + U8 inst_byte = 0; + is_good = is_good && ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &inst_byte, endt_us); + is_good = is_good && !is_stale; + read_vaddr += 1; + + //- rjf: extract rex from instruction byte + U8 rex = 0; + if((inst_byte & 0xF0) == 0x40) + { + rex = inst_byte & 0xF; // rex prefix + is_good = is_good && ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &inst_byte, endt_us); + is_good = is_good && !is_stale; + read_vaddr += 1; + } + + //- rjf: parse remainder of instruction + switch(inst_byte) + { + // rjf: pop + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + { + // rjf: read value at rsp + U64 sp = regs->rsp.u64; + U64 value = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, sp, &is_stale, &value, endt_us) || + is_stale) + { + is_good = 0; + break; + } + + // rjf: modify registers + PE_UnwindGprRegX64 gpr_reg = (inst_byte - 0x58) + (rex & 1)*8; + REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, gpr_reg); + reg->u64 = value; + regs->rsp.u64 = sp + 8; + + // rjf: not a final instruction, so keep mparsing + keep_parsing = 1; + }break; + + // rjf: add $nnnn,%rsp + case 0x81: + { + // rjf: skip one byte (we already know what it is in this scenario) + read_vaddr += 1; + + // rjf: read the 4-byte immediate + S32 imm = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &imm, endt_us) || + is_stale) + { + is_good = 0; + break; + } + read_vaddr += 4; + + // rjf: update stack pointer + regs->rsp.u64 = (U64)(regs->rsp.u64 + imm); + + // rjf: not a final instruction; keep parsing + keep_parsing = 1; + }break; + + // rjf: add $n,%rsp + case 0x83: + { + // rjf: skip one byte (we already know what it is in this scenario) + read_vaddr += 1; + + // rjf: read the 4-byte immediate + S8 imm = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &imm, endt_us) || + is_stale) + { + is_good = 0; + break; + } + read_vaddr += 1; + + // rjf: update stack pointer + regs->rsp.u64 = (U64)(regs->rsp.u64 + imm); + + // rjf: not a final instruction; keep parsing + keep_parsing = 1; + }break; + + // rjf: lea imm8/imm32,$rsp + case 0x8D: + { + // rjf: read source register + U8 modrm = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &modrm, endt_us) || + is_stale) + { + is_good = 0; + break; + } + read_vaddr += 1; + PE_UnwindGprRegX64 gpr_reg = (modrm & 7) + (rex & 1)*8; + REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, gpr_reg); + U64 reg_value = reg->u64; + + // rjf: read immediate + S32 imm = 0; + { + // rjf: read 1-byte immediate + if((modrm >> 6) == 1) + { + S8 imm8 = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &imm8, endt_us) || + is_stale) + { + is_good = 0; + break; + } + read_vaddr += 1; + imm = (S32)imm8; + } + + // rjf: read 4-byte immediate + else + { + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &imm, endt_us) || + is_stale) + { + is_good = 0; + break; + } + read_vaddr += 4; + } + } + + // rjf: update stack pointer + regs->rsp.u64 = (U64)(reg_value + imm); + + // rjf: not a final instruction; keep parsing + keep_parsing = 1; + }break; + + // rjf: ret $nn + case 0xC2: + { + // rjf: read new ip + U64 sp = regs->rsp.u64; + U64 new_ip = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, sp, &is_stale, &new_ip, endt_us) || + is_stale) + { + is_good = 0; + break; + } + + // rjf: read 2-byte immediate & advance stack pointer + U16 imm = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, read_vaddr, &is_stale, &imm, endt_us) || + is_stale) + { + is_good = 0; + break; + } + U64 new_sp = sp + 8 + imm; + + // rjf: commit registers + regs->rip.u64 = new_ip; + regs->rsp.u64 = new_sp; + }break; + + // rjf: ret / rep; ret + case 0xF3: + { + // Assert(!"Hit me!"); + }break; + case 0xC3: + { + // rjf: read new ip + U64 sp = regs->rsp.u64; + U64 new_ip = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, sp, &is_stale, &new_ip, endt_us) || + is_stale) + { + is_good = 0; + break; + } + + // rjf: advance stack pointer + U64 new_sp = sp + 8; + + // rjf: commit registers + regs->rip.u64 = new_ip; + regs->rsp.u64 = new_sp; + }break; + + // rjf: jmp nnnn + case 0xE9: + { + // Assert(!"Hit Me"); + // TODO(allen): general idea: read the immediate, move the ip, leave the sp, done + // we don't have any cases to exercise this right now. no guess implementation! + }break; + + // rjf: Sjmp n + case 0xEB: + { + // Assert(!"Hit Me"); + // TODO(allen): general idea: read the immediate, move the ip, leave the sp, done + // we don't have any cases to exercise this right now. no guess implementation! + }break; + } + } + } + + ////////////////////////////// + //- rjf: pdata & not in epilog -> xdata unwind + // + B32 xdata_unwind_did_machframe = 0; + if(first_pdata && !has_pdata_and_in_epilog) ProfScope("pdata & not in epilog -> xdata unwind") + { + //- rjf: get frame reg + B32 bad_frame_reg_info = 0; + REGS_Reg64 *frame_reg = 0; + U64 frame_off = 0; + { + U64 unwind_info_off = first_pdata->voff_unwind_info; + PE_UnwindInfo unwind_info = {0}; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, module->vaddr_range.min+unwind_info_off, &is_stale, &unwind_info, endt_us) || + is_stale) + { + is_good = 0; + } + U32 frame_reg_id = PE_UNWIND_INFO_REG_FROM_FRAME(unwind_info.frame); + U64 frame_off_val = PE_UNWIND_INFO_OFF_FROM_FRAME(unwind_info.frame); + if(frame_reg_id != 0) + { + frame_reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, frame_reg_id); + bad_frame_reg_info = (frame_reg == 0); // NOTE(rjf): frame_reg should never be 0 at this point, in valid exe + } + frame_off = frame_off_val; + } + + //- rjf: iterate pdatas, apply opcodes + PE_IntelPdata *last_pdata = 0; + PE_IntelPdata *pdata = first_pdata; + if(!bad_frame_reg_info) for(B32 keep_parsing = 1; keep_parsing && pdata != last_pdata;) + { + //- rjf: unpack unwind info & codes + B32 good_unwind_info = 1; + U64 unwind_info_off = pdata->voff_unwind_info; + PE_UnwindInfo unwind_info = {0}; + good_unwind_info = good_unwind_info && ctrl_read_cached_process_memory_struct(machine_id, process->handle, module->vaddr_range.min+unwind_info_off, &is_stale, &unwind_info, endt_us); + PE_UnwindCode *unwind_codes = push_array(scratch.arena, PE_UnwindCode, unwind_info.codes_num); + good_unwind_info = good_unwind_info && ctrl_read_cached_process_memory(machine_id, process->handle, r1u64(module->vaddr_range.min+unwind_info_off+sizeof(unwind_info), + module->vaddr_range.min+unwind_info_off+sizeof(unwind_info)+sizeof(PE_UnwindCode)*unwind_info.codes_num), + &is_stale, unwind_codes, endt_us); + good_unwind_info = good_unwind_info && !is_stale; + + //- rjf: bad unwind info -> abort + if(!good_unwind_info) + { + is_good = 0; + break; + } + + //- rjf: unpack frame base + U64 frame_base = regs->rsp.u64; + if(frame_reg != 0) + { + U64 raw_frame_base = frame_reg->u64; + U64 adjusted_frame_base = raw_frame_base - frame_off*16; + if(adjusted_frame_base < raw_frame_base) + { + frame_base = adjusted_frame_base; + } + } + + //- rjf: apply opcodes + PE_UnwindCode *code_ptr = unwind_codes; + PE_UnwindCode *code_opl = unwind_codes + unwind_info.codes_num; + for(PE_UnwindCode *next_code_ptr = 0; code_ptr < code_opl; code_ptr = next_code_ptr) + { + // rjf: unpack opcode info + U32 op_code = PE_UNWIND_OPCODE_FROM_FLAGS(code_ptr->flags); + U32 op_info = PE_UNWIND_INFO_FROM_FLAGS(code_ptr->flags); + U32 slot_count = pe_slot_count_from_unwind_op_code(op_code); + if(op_code == PE_UnwindOpCode_ALLOC_LARGE && op_info == 1) + { + slot_count += 1; + } + + // rjf: detect bad slot counts + if(slot_count == 0 || code_ptr+slot_count > code_opl) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: set next op code pointer + next_code_ptr = code_ptr + slot_count; + + // rjf: interpret this op code + U64 code_voff = pdata->voff_first + code_ptr->off_in_prolog; + if(code_voff <= rip_voff) + { + switch(op_code) + { + case PE_UnwindOpCode_PUSH_NONVOL: + { + // rjf: read value from stack pointer + U64 rsp = regs->rsp.u64; + U64 value = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, rsp, &is_stale, &value, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: advance stack ptr + U64 new_rsp = rsp + 8; + + // rjf: commit registers + REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, op_info); + reg->u64 = value; + regs->rsp.u64 = new_rsp; + }break; + + case PE_UnwindOpCode_ALLOC_LARGE: + { + // rjf: read alloc size + U64 size = 0; + if(op_info == 0) + { + size = code_ptr[1].u16*8; + } + else if(op_info == 1) + { + size = code_ptr[1].u16 + ((U32)code_ptr[2].u16 << 16); + } + else + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: advance stack pointer + U64 rsp = regs->rsp.u64; + U64 new_rsp = rsp + size; + + // rjf: advance stack pointer + regs->rsp.u64 = new_rsp; + }break; + + case PE_UnwindOpCode_ALLOC_SMALL: + { + // rjf: advance stack pointer + regs->rsp.u64 += op_info*8 + 8; + }break; + + case PE_UnwindOpCode_SET_FPREG: + { + // rjf: put stack pointer back to the frame base + regs->rsp.u64 = frame_base; + }break; + + case PE_UnwindOpCode_SAVE_NONVOL: + { + // rjf: read value from frame base + U64 off = code_ptr[1].u16*8; + U64 addr = frame_base + off; + U64 value = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, addr, &is_stale, &value, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: commit to register + REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, op_info); + reg->u64 = value; + }break; + + case PE_UnwindOpCode_SAVE_NONVOL_FAR: + { + // rjf: read value from frame base + U64 off = code_ptr[1].u16 + ((U32)code_ptr[2].u16 << 16); + U64 addr = frame_base + off; + U64 value = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, addr, &is_stale, &value, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: commit to register + REGS_Reg64 *reg = ctrl_unwind_reg_from_pe_gpr_reg__pe_x64(regs, op_info); + reg->u64 = value; + }break; + + case PE_UnwindOpCode_EPILOG: + { + keep_parsing = 0; + is_good = 0; + }break; + + case PE_UnwindOpCode_SPARE_CODE: + { + // TODO(rjf): ??? + keep_parsing = 0; + is_good = 0; + }break; + + case PE_UnwindOpCode_SAVE_XMM128: + { + // rjf: read new register values + U8 buf[16]; + U64 off = code_ptr[1].u16*16; + U64 addr = frame_base + off; + if(!ctrl_read_cached_process_memory(machine_id, process->handle, r1u64(addr, addr+sizeof(buf)), &is_stale, buf, endt_us)) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: commit to register + void *xmm_reg = (®s->ymm0) + op_info; + MemoryCopy(xmm_reg, buf, sizeof(buf)); + }break; + + case PE_UnwindOpCode_SAVE_XMM128_FAR: + { + // rjf: read new register values + U8 buf[16]; + U64 off = code_ptr[1].u16 + ((U32)code_ptr[2].u16 << 16); + U64 addr = frame_base + off; + if(!ctrl_read_cached_process_memory(machine_id, process->handle, r1u64(addr, addr+16), &is_stale, buf, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: commit to register + void *xmm_reg = (®s->ymm0) + op_info; + MemoryCopy(xmm_reg, buf, sizeof(buf)); + }break; + + case PE_UnwindOpCode_PUSH_MACHFRAME: + { + // NOTE(rjf): this was found by stepping through kernel code after an exception was + // thrown, encountered in the exception_stepping_tests (after the throw) in mule_main + if(op_info > 1) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: read values + U64 sp_og = regs->rsp.u64; + U64 sp_adj = sp_og; + if(op_info == 1) + { + sp_adj += 8; + } + U64 ip_value = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, sp_adj, &is_stale, &ip_value, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + U64 sp_after_ip = sp_adj + 8; + U16 ss_value = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, sp_after_ip, &is_stale, &ss_value, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + U64 sp_after_ss = sp_after_ip + 8; + U64 rflags_value = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, sp_after_ss, &is_stale, &rflags_value, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + U64 sp_after_rflags = sp_after_ss + 8; + U64 sp_value = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, sp_after_rflags, &is_stale, &sp_value, endt_us) || + is_stale) + { + keep_parsing = 0; + is_good = 0; + break; + } + + // rjf: commit registers + regs->rip.u64 = ip_value; + regs->ss.u16 = ss_value; + regs->rflags.u64 = rflags_value; + regs->rsp.u64 = sp_value; + + // rjf: mark machine frame + xdata_unwind_did_machframe = 1; + }break; + } + } + } + + //- rjf: iterate to next pdata + if(keep_parsing) + { + U32 flags = PE_UNWIND_INFO_FLAGS_FROM_HDR(unwind_info.header); + if(!(flags & PE_UnwindInfoFlag_CHAINED)) + { + break; + } + U64 code_count_rounded = AlignPow2(unwind_info.codes_num, sizeof(PE_UnwindCode)); + U64 code_size = code_count_rounded*sizeof(PE_UnwindCode); + U64 chained_pdata_off = unwind_info_off + sizeof(PE_UnwindInfo) + code_size; + last_pdata = pdata; + pdata = push_array(scratch.arena, PE_IntelPdata, 1); + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, module->vaddr_range.min+chained_pdata_off, &is_stale, pdata, endt_us) || + is_stale) + { + is_good = 0; + break; + } + } + } + } + + ////////////////////////////// + //- rjf: no pdata, or didn't do machframe in xdata unwind -> unwind by reading stack pointer + // + if(!first_pdata || (!has_pdata_and_in_epilog && !xdata_unwind_did_machframe)) ProfScope("no pdata, or didn't do machframe in xdata unwind -> unwind by reading stack pointer") + { + // rjf: read rip from stack pointer + U64 rsp = regs->rsp.u64; + U64 new_rip = 0; + if(!ctrl_read_cached_process_memory_struct(machine_id, process->handle, rsp, &is_stale, &new_rip, endt_us) || + is_stale) + { + is_good = 0; + } + + // rjf: commit registers + if(is_good) + { + U64 new_rsp = rsp + 8; + regs->rip.u64 = new_rip; + regs->rsp.u64 = new_rsp; + } + } + + ////////////////////////////// + //- rjf: fill & return + // + scratch_end(scratch); + CTRL_UnwindStepResult result = {0}; + if(!is_good) {result.flags |= CTRL_UnwindFlag_Error;} + if(is_stale) {result.flags |= CTRL_UnwindFlag_Stale;} + return result; +} + +//- rjf: abstracted unwind step + +internal CTRL_UnwindStepResult +ctrl_unwind_step(CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle process, DMN_Handle module, Architecture arch, void *reg_block, U64 endt_us) +{ + CTRL_UnwindStepResult result = {0}; + switch(arch) + { + default:{}break; + case Architecture_x64: + { + result = ctrl_unwind_step__pe_x64(store, machine_id, process, module, (REGS_RegBlockX64 *)reg_block, endt_us); + }break; + } + return result; +} + +//- rjf: abstracted full unwind + +internal CTRL_Unwind +ctrl_unwind_from_thread(Arena *arena, CTRL_EntityStore *store, CTRL_MachineID machine_id, DMN_Handle thread, U64 endt_us) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(&arena, 1); + CTRL_Unwind unwind = {0}; + unwind.flags |= CTRL_UnwindFlag_Error; + + //- rjf: unpack args + CTRL_Entity *thread_entity = ctrl_entity_from_machine_id_handle(store, machine_id, thread); + CTRL_Entity *process_entity = thread_entity->parent; + Architecture arch = thread_entity->arch; + U64 arch_reg_block_size = regs_block_size_from_architecture(arch); + + //- rjf: grab initial register block + void *regs_block = ctrl_query_cached_reg_block_from_thread(scratch.arena, store, machine_id, thread); + B32 regs_block_good = (arch != Architecture_Null && regs_block != 0); + + //- rjf: loop & unwind + CTRL_UnwindFrameNode *first_frame_node = 0; + CTRL_UnwindFrameNode *last_frame_node = 0; + U64 frame_node_count = 0; + if(regs_block_good) + { + unwind.flags = 0; + for(;;) + { + // rjf: regs -> rip*module + U64 rip = regs_rip_from_arch_block(arch, regs_block); + DMN_Handle module = {0}; + for(CTRL_Entity *m = process_entity->first; m != &ctrl_entity_nil; m = m->next) + { + if(m->kind == CTRL_EntityKind_Module && contains_1u64(m->vaddr_range, rip)) + { + module = m->handle; + break; + } + } + + // rjf: cancel on 0 rip + if(rip == 0) + { + break; + } + + // rjf: valid step -> push frame + CTRL_UnwindFrameNode *frame_node = push_array(scratch.arena, CTRL_UnwindFrameNode, 1); + CTRL_UnwindFrame *frame = &frame_node->v; + frame->regs = push_array_no_zero(arena, U8, arch_reg_block_size); + MemoryCopy(frame->regs, regs_block, arch_reg_block_size); + DLLPushBack(first_frame_node, last_frame_node, frame_node); + frame_node_count += 1; + + // rjf: unwind one step + CTRL_UnwindStepResult step = ctrl_unwind_step(store, machine_id, process_entity->handle, module, arch, regs_block, endt_us); + unwind.flags |= step.flags; + if(step.flags & CTRL_UnwindFlag_Error || + regs_rsp_from_arch_block(arch, regs_block) == 0 || + regs_rip_from_arch_block(arch, regs_block) == 0 || + regs_rip_from_arch_block(arch, regs_block) == rip) + { + break; + } + } + } + + //- rjf: bake frames list into result array + { + unwind.frames.count = frame_node_count; + unwind.frames.v = push_array(arena, CTRL_UnwindFrame, unwind.frames.count); + U64 idx = 0; + for(CTRL_UnwindFrameNode *n = first_frame_node; n != 0; n = n->next, idx += 1) + { + unwind.frames.v[idx] = n->v; + } + } + + scratch_end(scratch); + ProfEnd(); + return unwind; +} + +//////////////////////////////// +//~ rjf: Halting All Attached Processes + +internal void +ctrl_halt(void) +{ + dmn_halt(0, 0); +} + +//////////////////////////////// +//~ rjf: Shared Accessor Functions + +//- rjf: run generation counter + +internal U64 +ctrl_run_gen(void) +{ + U64 result = dmn_run_gen(); + return result; +} + +internal U64 +ctrl_mem_gen(void) +{ + U64 result = dmn_mem_gen(); + return result; +} + +internal U64 +ctrl_reg_gen(void) +{ + U64 result = dmn_reg_gen(); + return result; +} + +//- rjf: name -> register/alias hash tables, for eval + +internal EVAL_String2NumMap * +ctrl_string2reg_from_arch(Architecture arch) +{ + return &ctrl_state->arch_string2reg_tables[arch]; +} + +internal EVAL_String2NumMap * +ctrl_string2alias_from_arch(Architecture arch) +{ + return &ctrl_state->arch_string2alias_tables[arch]; +} + +//////////////////////////////// +//~ rjf: Control-Thread Functions + +//- rjf: user -> control thread communication + +internal B32 +ctrl_u2c_push_msgs(CTRL_MsgList *msgs, U64 endt_us) +{ + Temp scratch = scratch_begin(0, 0); + String8 msgs_srlzed_baked = ctrl_serialized_string_from_msg_list(scratch.arena, msgs); + B32 good = 0; + OS_MutexScope(ctrl_state->u2c_ring_mutex) for(;;) + { + U64 unconsumed_size = (ctrl_state->u2c_ring_write_pos-ctrl_state->u2c_ring_read_pos); + U64 available_size = ctrl_state->u2c_ring_size-unconsumed_size; + if(available_size >= sizeof(U64) + msgs_srlzed_baked.size) + { + ctrl_state->u2c_ring_write_pos += ring_write_struct(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_write_pos, &msgs_srlzed_baked.size); + ctrl_state->u2c_ring_write_pos += ring_write(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_write_pos, msgs_srlzed_baked.str, msgs_srlzed_baked.size); + ctrl_state->u2c_ring_write_pos += 7; + ctrl_state->u2c_ring_write_pos -= ctrl_state->u2c_ring_write_pos%8; + good = 1; + break; + } + if(os_now_microseconds() >= endt_us) + { + break; + } + os_condition_variable_wait(ctrl_state->u2c_ring_cv, ctrl_state->u2c_ring_mutex, endt_us); + } + if(good) + { + os_condition_variable_broadcast(ctrl_state->u2c_ring_cv); + } + scratch_end(scratch); + return good; +} + +internal CTRL_MsgList +ctrl_u2c_pop_msgs(Arena *arena) +{ + Temp scratch = scratch_begin(&arena, 1); + String8 msgs_srlzed_baked = {0}; + OS_MutexScope(ctrl_state->u2c_ring_mutex) for(;;) + { + U64 unconsumed_size = (ctrl_state->u2c_ring_write_pos-ctrl_state->u2c_ring_read_pos); + if(unconsumed_size >= sizeof(U64)) + { + U64 size_to_decode = 0; + ctrl_state->u2c_ring_read_pos += ring_read_struct(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_read_pos, &size_to_decode); + msgs_srlzed_baked.size = size_to_decode; + msgs_srlzed_baked.str = push_array_no_zero(scratch.arena, U8, msgs_srlzed_baked.size); + ctrl_state->u2c_ring_read_pos += ring_read(ctrl_state->u2c_ring_base, ctrl_state->u2c_ring_size, ctrl_state->u2c_ring_read_pos, msgs_srlzed_baked.str, size_to_decode); + ctrl_state->u2c_ring_read_pos += 7; + ctrl_state->u2c_ring_read_pos -= ctrl_state->u2c_ring_read_pos%8; + break; + } + os_condition_variable_wait(ctrl_state->u2c_ring_cv, ctrl_state->u2c_ring_mutex, max_U64); + } + os_condition_variable_broadcast(ctrl_state->u2c_ring_cv); + CTRL_MsgList msgs = ctrl_msg_list_from_serialized_string(arena, msgs_srlzed_baked); + scratch_end(scratch); + return msgs; +} + +//- rjf: control -> user thread communication + +internal void +ctrl_c2u_push_events(CTRL_EventList *events) +{ + if(events->count != 0) ProfScope("ctrl_c2u_push_events") + { + ctrl_entity_store_apply_events(ctrl_state->ctrl_thread_entity_store, events); + for(CTRL_EventNode *n = events->first; n != 0; n = n ->next) + { + Temp scratch = scratch_begin(0, 0); + String8 event_srlzed = ctrl_serialized_string_from_event(scratch.arena, &n->v, ctrl_state->c2u_ring_size-sizeof(U64)); + OS_MutexScope(ctrl_state->c2u_ring_mutex) for(;;) + { + U64 unconsumed_size = (ctrl_state->c2u_ring_write_pos-ctrl_state->c2u_ring_read_pos); + U64 available_size = ctrl_state->c2u_ring_size-unconsumed_size; + if(available_size >= sizeof(U64) + event_srlzed.size) + { + ctrl_state->c2u_ring_write_pos += ring_write_struct(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_write_pos, &event_srlzed.size); + ctrl_state->c2u_ring_write_pos += ring_write(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_write_pos, event_srlzed.str, event_srlzed.size); + ctrl_state->c2u_ring_write_pos += 7; + ctrl_state->c2u_ring_write_pos -= ctrl_state->c2u_ring_write_pos%8; + break; + } + os_condition_variable_wait(ctrl_state->c2u_ring_cv, ctrl_state->c2u_ring_mutex, os_now_microseconds()+100); + } + os_condition_variable_broadcast(ctrl_state->c2u_ring_cv); + if(ctrl_state->wakeup_hook != 0) + { + ctrl_state->wakeup_hook(); + } + scratch_end(scratch); + } + } +} + +internal CTRL_EventList +ctrl_c2u_pop_events(Arena *arena) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(&arena, 1); + CTRL_EventList events = {0}; + OS_MutexScope(ctrl_state->c2u_ring_mutex) for(;;) + { + U64 unconsumed_size = (ctrl_state->c2u_ring_write_pos-ctrl_state->c2u_ring_read_pos); + if(unconsumed_size >= sizeof(U64)) + { + U64 size_to_decode = 0; + ctrl_state->c2u_ring_read_pos += ring_read_struct(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_read_pos, &size_to_decode); + String8 event_srlzed = {0}; + event_srlzed.size = size_to_decode; + event_srlzed.str = push_array_no_zero(scratch.arena, U8, event_srlzed.size); + ctrl_state->c2u_ring_read_pos += ring_read(ctrl_state->c2u_ring_base, ctrl_state->c2u_ring_size, ctrl_state->c2u_ring_read_pos, event_srlzed.str, event_srlzed.size); + ctrl_state->c2u_ring_read_pos += 7; + ctrl_state->c2u_ring_read_pos -= ctrl_state->c2u_ring_read_pos%8; + CTRL_Event *new_event = ctrl_event_list_push(arena, &events); + *new_event = ctrl_event_from_serialized_string(arena, event_srlzed); + } + else + { + break; + } + } + os_condition_variable_broadcast(ctrl_state->c2u_ring_cv); + scratch_end(scratch); + ProfEnd(); + return events; +} + +//- rjf: entry point + +internal void +ctrl_thread__entry_point(void *p) +{ + ThreadNameF("[ctrl] thread"); + ProfBeginFunction(); + DMN_CtrlCtx *ctrl_ctx = dmn_ctrl_begin(); + log_select(ctrl_state->ctrl_thread_log); + + //- rjf: loop + Temp scratch = scratch_begin(0, 0); + for(;;) + { + temp_end(scratch); + log_scope_begin(); + + //- rjf: get next messages + CTRL_MsgList msgs = ctrl_u2c_pop_msgs(scratch.arena); + + //- rjf: process messages + DMN_CtrlExclusiveAccessScope + { + B32 done = 0; + for(CTRL_MsgNode *msg_n = msgs.first; msg_n != 0 && done == 0; msg_n = msg_n->next) + { + CTRL_Msg *msg = &msg_n->v; + { + log_infof("user2ctrl_msg:{kind:\"%S\"}\n", ctrl_string_from_msg_kind(msg->kind)); + } + MemoryCopyArray(ctrl_state->exception_code_filters, msg->exception_code_filters); + switch(msg->kind) + { + case CTRL_MsgKind_Null: + case CTRL_MsgKind_COUNT:{}break; + + //- rjf: target operations + case CTRL_MsgKind_Launch: {ctrl_thread__launch (ctrl_ctx, msg);}break; + case CTRL_MsgKind_Attach: {ctrl_thread__attach (ctrl_ctx, msg);}break; + case CTRL_MsgKind_Kill: {ctrl_thread__kill (ctrl_ctx, msg);}break; + case CTRL_MsgKind_Detach: {ctrl_thread__detach (ctrl_ctx, msg);}break; + case CTRL_MsgKind_Run: {ctrl_thread__run (ctrl_ctx, msg); done = 1;}break; + case CTRL_MsgKind_SingleStep: {ctrl_thread__single_step (ctrl_ctx, msg); done = 1;}break; + + //- rjf: configuration + case CTRL_MsgKind_SetUserEntryPoints: + { + arena_clear(ctrl_state->user_entry_point_arena); + MemoryZeroStruct(&ctrl_state->user_entry_points); + for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) + { + str8_list_push(ctrl_state->user_entry_point_arena, &ctrl_state->user_entry_points, n->string); + } + }break; + case CTRL_MsgKind_SetModuleDebugInfoPath: + { + String8 path = msg->path; + CTRL_Entity *module = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, msg->machine_id, msg->entity); + CTRL_Entity *debug_info_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); + DI_Key old_dbgi_key = {debug_info_path->string, debug_info_path->timestamp}; + di_close(&old_dbgi_key); + ctrl_entity_equip_string(ctrl_state->ctrl_thread_entity_store, debug_info_path, path); + U64 new_dbgi_timestamp = os_properties_from_file_path(path).modified; + debug_info_path->timestamp = new_dbgi_timestamp; + DI_Key new_dbgi_key = {debug_info_path->string, new_dbgi_timestamp}; + di_open(&new_dbgi_key); + CTRL_EventList evts = {0}; + CTRL_Event *evt = ctrl_event_list_push(scratch.arena, &evts); + evt->kind = CTRL_EventKind_ModuleDebugInfoPathChange; + evt->machine_id = msg->machine_id; + evt->entity = msg->entity; + evt->string = path; + evt->timestamp = new_dbgi_timestamp; + ctrl_c2u_push_events(&evts); + }break; + } + } + } + + //- rjf: gather & output logs + LogScopeResult log = log_scope_end(scratch.arena); + ctrl_thread__flush_info_log(log.strings[LogMsgKind_Info]); + if(log.strings[LogMsgKind_UserError].size != 0) + { + CTRL_EventList evts = {0}; + CTRL_Event *evt = ctrl_event_list_push(scratch.arena, &evts); + evt->kind = CTRL_EventKind_Error; + evt->string = log.strings[LogMsgKind_UserError]; + ctrl_c2u_push_events(&evts); + } + } + + scratch_end(scratch); + ProfEnd(); +} + +//- rjf: breakpoint resolution + +internal void +ctrl_thread__append_resolved_module_user_bp_traps(Arena *arena, CTRL_MachineID machine_id, DMN_Handle process, DMN_Handle module, CTRL_UserBreakpointList *user_bps, DMN_TrapChunkList *traps_out) +{ + Temp scratch = scratch_begin(&arena, 1); + DI_Scope *di_scope = di_scope_open(); + CTRL_Entity *module_entity = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, machine_id, module); + CTRL_Entity *debug_info_path_entity = ctrl_entity_child_from_kind(module_entity, CTRL_EntityKind_DebugInfoPath); + DI_Key dbgi_key = {debug_info_path_entity->string, debug_info_path_entity->timestamp}; + RDI_Parsed *rdi = di_rdi_from_key(di_scope, &dbgi_key, max_U64); + U64 base_vaddr = module_entity->vaddr_range.min; + for(CTRL_UserBreakpointNode *n = user_bps->first; n != 0; n = n->next) + { + CTRL_UserBreakpoint *bp = &n->v; + switch(bp->kind) + { + default:{}break; + + //- rjf: file:line-based breakpoints + case CTRL_UserBreakpointKind_FileNameAndLineColNumber: + { + // rjf: unpack & normalize + TxtPt pt = bp->pt; + String8 filename = bp->string; + String8 filename_normalized = push_str8_copy(scratch.arena, filename); + for(U64 idx = 0; idx < filename_normalized.size; idx += 1) + { + filename_normalized.str[idx] = char_to_lower(filename_normalized.str[idx]); + filename_normalized.str[idx] = char_to_correct_slash(filename_normalized.str[idx]); + } + + // rjf: filename -> src_id + U32 src_id = 0; + { + RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_NormalSourcePaths); + if(mapptr != 0) + { + RDI_ParsedNameMap map = {0}; + rdi_parsed_from_name_map(rdi, mapptr, &map); + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, filename_normalized.str, filename_normalized.size); + if(node != 0) + { + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + src_id = ids[0]; + } + } + } + } + + // rjf: src_id * pt -> push + { + RDI_SourceFile *src = rdi_element_from_name_idx(rdi, SourceFiles, src_id); + RDI_SourceLineMap *src_line_map = rdi_element_from_name_idx(rdi, SourceLineMaps, src->source_line_map_idx); + RDI_ParsedSourceLineMap line_map = {0}; + rdi_parsed_from_source_line_map(rdi, src_line_map, &line_map); + U32 voff_count = 0; + U64 *voffs = rdi_line_voffs_from_num(&line_map, pt.line, &voff_count); + for(U32 i = 0; i < voff_count; i += 1) + { + U64 vaddr = voffs[i] + base_vaddr; + DMN_Trap trap = {process, vaddr, (U64)bp}; + dmn_trap_chunk_list_push(arena, traps_out, 256, &trap); + } + } + }break; + + //- rjf: symbol:voff-based breakpoints + case CTRL_UserBreakpointKind_SymbolNameAndOffset: + { + String8 symbol_name = bp->string; + U64 voff = bp->u64; + RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_Procedures); + RDI_ParsedNameMap map = {0}; + rdi_parsed_from_name_map(rdi, mapptr, &map); + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, symbol_name.str, symbol_name.size); + if(node != 0) + { + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + for(U32 match_i = 0; match_i < id_count; match_i += 1) + { + RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, ids[match_i]); + U64 proc_voff = rdi_first_voff_from_procedure(rdi, procedure); + U64 proc_vaddr = proc_voff + base_vaddr; + DMN_Trap trap = {process, proc_vaddr + voff, (U64)bp}; + dmn_trap_chunk_list_push(arena, traps_out, 256, &trap); + } + } + }break; + } + } + di_scope_close(di_scope); + scratch_end(scratch); +} + +internal void +ctrl_thread__append_resolved_process_user_bp_traps(Arena *arena, CTRL_MachineID machine_id, DMN_Handle process, CTRL_UserBreakpointList *user_bps, DMN_TrapChunkList *traps_out) +{ + for(CTRL_UserBreakpointNode *n = user_bps->first; n != 0; n = n->next) + { + CTRL_UserBreakpoint *bp = &n->v; + if(bp->kind == CTRL_UserBreakpointKind_VirtualAddress) + { + DMN_Trap trap = {process, bp->u64, (U64)bp}; + dmn_trap_chunk_list_push(arena, traps_out, 256, &trap); + } + } +} + +//- rjf: module lifetime open/close work + +internal void +ctrl_thread__module_open(CTRL_MachineID machine_id, DMN_Handle process, DMN_Handle module, Rng1U64 vaddr_range, String8 path) +{ + ////////////////////////////// + //- rjf: parse module image info + // + Arena *arena = arena_alloc(); + PE_IntelPdata *pdatas = 0; + U64 pdatas_count = 0; + U64 entry_point_voff = 0; + Rng1U64 tls_vaddr_range = {0}; + String8 builtin_debug_info_path = {0}; + ProfScope("unpack relevant PE info") + { + B32 is_valid = 1; + + //- rjf: read DOS header + PE_DosHeader dos_header = {0}; + if(is_valid) + { + if(!dmn_process_read_struct(process, vaddr_range.min, &dos_header) || + dos_header.magic != PE_DOS_MAGIC) + { + is_valid = 0; + } + } + + //- rjf: read PE magic + U32 pe_magic = 0; + if(is_valid) + { + if(!dmn_process_read_struct(process, vaddr_range.min + dos_header.coff_file_offset, &pe_magic) || + pe_magic != PE_MAGIC) + { + is_valid = 0; + } + } + + //- rjf: read COFF header + U64 coff_header_off = dos_header.coff_file_offset + sizeof(pe_magic); + COFF_Header coff_header = {0}; + if(is_valid) + { + if(!dmn_process_read_struct(process, vaddr_range.min + coff_header_off, &coff_header)) + { + is_valid = 0; + } + } + + //- rjf: unpack range of optional extension header + U32 opt_ext_size = coff_header.optional_header_size; + Rng1U64 opt_ext_off_range = r1u64(coff_header_off + sizeof(coff_header), + coff_header_off + sizeof(coff_header) + opt_ext_size); + + //- rjf: read optional header + U16 optional_magic = 0; + U64 image_base = 0; + U64 entry_point = 0; + U32 data_dir_count = 0; + U64 virt_section_align = 0; + U64 file_section_align = 0; + Rng1U64 *data_dir_franges = 0; + if(opt_ext_size > 0) + { + // rjf: read magic number + U16 opt_ext_magic = 0; + dmn_process_read_struct(process, vaddr_range.min + opt_ext_off_range.min, &opt_ext_magic); + + // rjf: read info + U32 reported_data_dir_offset = 0; + U32 reported_data_dir_count = 0; + switch(opt_ext_magic) + { + case PE_PE32_MAGIC: + { + PE_OptionalHeader32 pe_optional = {0}; + dmn_process_read_struct(process, vaddr_range.min + opt_ext_off_range.min, &pe_optional); + image_base = pe_optional.image_base; + entry_point = pe_optional.entry_point_va; + virt_section_align = pe_optional.section_alignment; + file_section_align = pe_optional.file_alignment; + reported_data_dir_offset = sizeof(pe_optional); + reported_data_dir_count = pe_optional.data_dir_count; + }break; + case PE_PE32PLUS_MAGIC: + { + PE_OptionalHeader32Plus pe_optional = {0}; + dmn_process_read_struct(process, vaddr_range.min + opt_ext_off_range.min, &pe_optional); + image_base = pe_optional.image_base; + entry_point = pe_optional.entry_point_va; + virt_section_align = pe_optional.section_alignment; + file_section_align = pe_optional.file_alignment; + reported_data_dir_offset = sizeof(pe_optional); + reported_data_dir_count = pe_optional.data_dir_count; + }break; + } + + // rjf: find number of data directories + U32 data_dir_max = (opt_ext_size - reported_data_dir_offset) / sizeof(PE_DataDirectory); + data_dir_count = ClampTop(reported_data_dir_count, data_dir_max); + + // rjf: grab pdatas from exceptions section + if(data_dir_count > PE_DataDirectoryIndex_EXCEPTIONS) + { + PE_DataDirectory dir = {0}; + dmn_process_read_struct(process, vaddr_range.min + opt_ext_off_range.min + reported_data_dir_offset + sizeof(PE_DataDirectory)*PE_DataDirectoryIndex_EXCEPTIONS, &dir); + Rng1U64 pdatas_voff_range = r1u64((U64)dir.virt_off, (U64)dir.virt_off + (U64)dir.virt_size); + pdatas_count = dim_1u64(pdatas_voff_range)/sizeof(PE_IntelPdata); + pdatas = push_array(arena, PE_IntelPdata, pdatas_count); + dmn_process_read(process, r1u64(vaddr_range.min + pdatas_voff_range.min, vaddr_range.min + pdatas_voff_range.max), pdatas); + } + + // rjf: extract tls header + PE_TLSHeader64 tls_header = {0}; + if(data_dir_count > PE_DataDirectoryIndex_TLS) + { + PE_DataDirectory dir = {0}; + dmn_process_read_struct(process, vaddr_range.min + opt_ext_off_range.min + reported_data_dir_offset + sizeof(PE_DataDirectory)*PE_DataDirectoryIndex_TLS, &dir); + Rng1U64 tls_voff_range = r1u64((U64)dir.virt_off, (U64)dir.virt_off + (U64)dir.virt_size); + switch(coff_header.machine) + { + default:{}break; + case COFF_MachineType_X86: + { + PE_TLSHeader32 tls_header32 = {0}; + dmn_process_read_struct(process, vaddr_range.min + tls_voff_range.min, &tls_header32); + tls_header.raw_data_start = (U64)tls_header32.raw_data_start; + tls_header.raw_data_end = (U64)tls_header32.raw_data_end; + tls_header.index_address = (U64)tls_header32.index_address; + tls_header.callbacks_address = (U64)tls_header32.callbacks_address; + tls_header.zero_fill_size = (U64)tls_header32.zero_fill_size; + tls_header.characteristics = (U64)tls_header32.characteristics; + }break; + case COFF_MachineType_X64: + { + dmn_process_read_struct(process, vaddr_range.min + tls_voff_range.min, &tls_header); + }break; + } + } + + // rjf: grab entry point vaddr + entry_point_voff = entry_point; + + // rjf: calculate TLS vaddr range + tls_vaddr_range = r1u64(tls_header.index_address, tls_header.index_address+sizeof(U32)); + + // rjf: grab data about debug info + U32 dbg_time = 0; + U32 dbg_age = 0; + OS_Guid dbg_guid = {0}; + if(data_dir_count > PE_DataDirectoryIndex_DEBUG) + { + // rjf: read data dir + PE_DataDirectory dir = {0}; + dmn_process_read_struct(process, vaddr_range.min + opt_ext_off_range.min + reported_data_dir_offset + sizeof(PE_DataDirectory)*PE_DataDirectoryIndex_DEBUG, &dir); + + // rjf: read debug directory + PE_DebugDirectory dbg_data = {0}; + dmn_process_read_struct(process, vaddr_range.min+(U64)dir.virt_off, &dbg_data); + + // rjf: extract external file info from codeview header + if(dbg_data.type == PE_DebugDirectoryType_CODEVIEW) + { + U64 dbg_path_off = 0; + U64 dbg_path_size = 0; + U64 cv_offset = dbg_data.voff; + U32 cv_magic = 0; + dmn_process_read_struct(process, vaddr_range.min+cv_offset, &cv_magic); + switch(cv_magic) + { + default:break; + case PE_CODEVIEW_PDB20_MAGIC: + { + PE_CvHeaderPDB20 cv = {0}; + dmn_process_read_struct(process, vaddr_range.min+cv_offset, &cv); + dbg_time = cv.time; + dbg_age = cv.age; + dbg_path_off = cv_offset + sizeof(cv); + }break; + case PE_CODEVIEW_PDB70_MAGIC: + { + PE_CvHeaderPDB70 cv = {0}; + dmn_process_read_struct(process, vaddr_range.min+cv_offset, &cv); + dbg_guid = cv.guid; + dbg_age = cv.age; + dbg_path_off = cv_offset + sizeof(cv); + }break; + } + if(dbg_path_off > 0) + { + Temp scratch = scratch_begin(0, 0); + String8List parts = {0}; + for(U64 off = dbg_path_off;; off += 256) + { + U8 bytes[256] = {0}; + dmn_process_read(process, r1u64(vaddr_range.min+off, vaddr_range.min+off+sizeof(bytes)), bytes); + U64 size = cstring8_length(&bytes[0]); + String8 part = str8(bytes, size); + str8_list_push(scratch.arena, &parts, part); + if(size < sizeof(bytes)) + { + break; + } + } + builtin_debug_info_path = str8_list_join(arena, &parts, 0); + scratch_end(scratch); + } + } + } + } + } + + ////////////////////////////// + //- rjf: pick default initial debug info path + // + String8 initial_debug_info_path = builtin_debug_info_path; + { + Temp scratch = scratch_begin(0, 0); + String8 exe_folder = str8_chop_last_slash(path); + String8 builtin_debug_info_path__absolute = builtin_debug_info_path; + String8 builtin_debug_info_path__relative = push_str8f(scratch.arena, "%S/%S", exe_folder, builtin_debug_info_path); + String8 dbg_path_candidates[] = + { + /* inferred (treated as relative): */ builtin_debug_info_path__relative, + /* inferred (treated as absolute): */ builtin_debug_info_path__absolute, + /* "foo.exe" -> "foo.pdb" */ push_str8f(scratch.arena, "%S.pdb", str8_chop_last_dot(path)), + /* "foo.exe" -> "foo.exe.pdb" */ push_str8f(scratch.arena, "%S.pdb", path), + }; + for(U64 idx = 0; idx < ArrayCount(dbg_path_candidates); idx += 1) + { + FileProperties props = os_properties_from_file_path(dbg_path_candidates[idx]); + if(props.modified != 0 && props.size != 0) + { + initial_debug_info_path = push_str8_copy(arena, dbg_path_candidates[idx]); + break; + } + } + scratch_end(scratch); + } + + ////////////////////////////// + //- rjf: insert info into cache + // + { + U64 hash = ctrl_hash_from_machine_id_handle(machine_id, module); + U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; + U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; + CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; + CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; + OS_MutexScopeW(stripe->rw_mutex) + { + CTRL_ModuleImageInfoCacheNode *node = 0; + for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->module, module)) + { + node = n; + break; + } + } + if(!node) + { + node = push_array(arena, CTRL_ModuleImageInfoCacheNode, 1); + DLLPushBack(slot->first, slot->last, node); + node->machine_id = machine_id; + node->module = module; + node->arena = arena; + node->pdatas = pdatas; + node->pdatas_count = pdatas_count; + node->entry_point_voff = entry_point_voff; + node->initial_debug_info_path = initial_debug_info_path; + } + } + } +} + +internal void +ctrl_thread__module_close(CTRL_MachineID machine_id, DMN_Handle module) +{ + ////////////////////////////// + //- rjf: evict module image info from cache + // + { + U64 hash = ctrl_hash_from_machine_id_handle(machine_id, module); + U64 slot_idx = hash%ctrl_state->module_image_info_cache.slots_count; + U64 stripe_idx = slot_idx%ctrl_state->module_image_info_cache.stripes_count; + CTRL_ModuleImageInfoCacheSlot *slot = &ctrl_state->module_image_info_cache.slots[slot_idx]; + CTRL_ModuleImageInfoCacheStripe *stripe = &ctrl_state->module_image_info_cache.stripes[stripe_idx]; + OS_MutexScopeW(stripe->rw_mutex) + { + CTRL_ModuleImageInfoCacheNode *node = 0; + for(CTRL_ModuleImageInfoCacheNode *n = slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->module, module)) + { + node = n; + break; + } + } + if(node) + { + DLLRemove(slot->first, slot->last, node); + arena_release(node->arena); + } + } + } +} + +//- rjf: attached process running/event gathering + +internal DMN_Event * +ctrl_thread__next_dmn_event(Arena *arena, DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg, DMN_RunCtrls *run_ctrls, CTRL_Spoof *spoof) +{ + ProfBeginFunction(); + DMN_Event *event = push_array(arena, DMN_Event, 1); + Temp scratch = scratch_begin(&arena, 1); + + //- rjf: loop -> try to get event, run, repeat + U64 spoof_old_ip_value = 0; + ProfScope("loop -> try to get event, run, repeat") for(B32 got_event = 0; got_event == 0;) + { + //- rjf: get next event + ProfScope("get next event") + { + // rjf: grab first event + DMN_EventNode *next_event_node = ctrl_state->first_dmn_event_node; + + // rjf: log event + if(next_event_node != 0) + { + DMN_Event *ev = &next_event_node->v; + LogInfoNamedBlockF("dmn_event") + { + log_infof("kind: %S\n", dmn_event_kind_string_table[ev->kind]); + log_infof("exception_kind: %S\n", dmn_exception_kind_string_table[ev->exception_kind]); + log_infof("process: [%I64u]\n", ev->process.u64[0]); + log_infof("thread: [%I64u]\n", ev->thread.u64[0]); + log_infof("module: [%I64u]\n", ev->module.u64[0]); + log_infof("arch: %S\n", string_from_architecture(ev->arch)); + log_infof("address: 0x%I64x\n", ev->address); + log_infof("string: \"%S\"\n", ev->string); + log_infof("ip_vaddr: 0x%I64x\n", ev->instruction_pointer); + } + } + + // rjf: determine if we should filter + B32 should_filter_event = 0; + if(next_event_node != 0) + { + DMN_Event *ev = &next_event_node->v; + switch(ev->kind) + { + default:{}break; + case DMN_EventKind_Exception: + { + // NOTE(rjf): first chance exceptions -> try ignoring + should_filter_event = (ev->exception_repeated == 0 && (spoof == 0 || ev->instruction_pointer != spoof->new_ip_value)); + + // rjf: exception code -> kind + CTRL_ExceptionCodeKind code_kind = CTRL_ExceptionCodeKind_Null; + if(should_filter_event) + { + for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)0; k < CTRL_ExceptionCodeKind_COUNT; k = (CTRL_ExceptionCodeKind)(k+1)) + { + if(ctrl_exception_code_kind_code_table[k] == ev->code) + { + code_kind = k; + break; + } + } + } + + // rjf: exception code kind -> shouldn't stop? if so, do not filter + if(should_filter_event) + { + B32 shouldnt_filter = !!(ctrl_state->exception_code_filters[code_kind/64] & (1ull<<(code_kind%64))); + if(should_filter_event && shouldnt_filter) + { + should_filter_event = 0; + } + } + + // rjf: special case: be gracious with ASan modules or symbols if + // they do their cute little 0xc0000005 exception trick... + if(!should_filter_event && ev->code == 0xc0000005 && + (spoof == 0 || ev->instruction_pointer != spoof->new_ip_value)) + { + DI_Scope *di_scope = di_scope_open(); + CTRL_Entity *process = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, CTRL_MachineID_Local, ev->process); + CTRL_Entity *module = &ctrl_entity_nil; + for(CTRL_Entity *child = process->first; child != &ctrl_entity_nil; child = child->next) + { + if(child->kind == CTRL_EntityKind_Module) + { + module = child; + break; + } + } + if(module != &ctrl_entity_nil) + { + // rjf: determine base address of asan shadow space + U64 asan_shadow_base_vaddr = 0; + B32 asan_shadow_variable_exists_but_is_zero = 0; + CTRL_Entity *dbg_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); + DI_Key dbgi_key = {dbg_path->string, dbg_path->timestamp}; + RDI_Parsed *rdi = di_rdi_from_key(di_scope, &dbgi_key, max_U64); + RDI_NameMap *unparsed_map = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_GlobalVariables); + { + RDI_ParsedNameMap map = {0}; + rdi_parsed_from_name_map(rdi, unparsed_map, &map); + String8 name = str8_lit("__asan_shadow_memory_dynamic_address"); + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); + if(node != 0) + { + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + RDI_GlobalVariable *global_var = rdi_element_from_name_idx(rdi, GlobalVariables, ids[0]); + U64 global_var_voff = global_var->voff; + U64 global_var_vaddr = global_var->voff + module->vaddr_range.min; + Architecture arch = process->arch; + U64 addr_size = bit_size_from_arch(arch)/8; + dmn_process_read(ev->process, r1u64(global_var_vaddr, global_var_vaddr+addr_size), &asan_shadow_base_vaddr); + asan_shadow_variable_exists_but_is_zero = (asan_shadow_base_vaddr == 0); + } + } + } + + // rjf: determine if this was a read/write to the shadow space + B32 violation_in_shadow_space = 0; + if(asan_shadow_base_vaddr != 0) + { + U64 asan_shadow_space_size = TB(128)/8; + if(asan_shadow_base_vaddr <= ev->address && ev->address < asan_shadow_base_vaddr+asan_shadow_space_size) + { + violation_in_shadow_space = 1; + } + } + + // rjf: filter event if this violation occurred in asan's shadow space + if(violation_in_shadow_space || asan_shadow_variable_exists_but_is_zero) + { + should_filter_event = 1; + } + } + + di_scope_close(di_scope); + } + }break; + } + } + + // rjf: good event & unfiltered? -> pop from queue & grab as result + if(next_event_node != 0 && !should_filter_event) + { + got_event = 1; + SLLQueuePop(ctrl_state->first_dmn_event_node, ctrl_state->last_dmn_event_node); + MemoryCopyStruct(event, &next_event_node->v); + event->string = push_str8_copy(arena, event->string); + run_ctrls->ignore_previous_exception = 1; + } + + // rjf: good event but filtered? pop from queue + if(next_event_node != 0 && should_filter_event) + { + SLLQueuePop(ctrl_state->first_dmn_event_node, ctrl_state->last_dmn_event_node); + run_ctrls->ignore_previous_exception = 0; + } + } + + //- rjf: no event -> dmn_ctrl_run for a new one + if(got_event == 0) ProfScope("no event -> dmn_ctrl_run for a new one") + { + // rjf: prep spoof + B32 do_spoof = (spoof != 0 && dmn_handle_match(run_ctrls->single_step_thread, dmn_handle_zero())); + U64 size_of_spoof = 0; + if(do_spoof) ProfScope("prep spoof") + { + CTRL_Entity *spoof_process = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, CTRL_MachineID_Local, spoof->process); + Architecture arch = spoof_process->arch; + size_of_spoof = bit_size_from_arch(arch)/8; + dmn_process_read(spoof_process->handle, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof_old_ip_value); + } + + // rjf: set spoof + if(do_spoof) ProfScope("set spoof") + { + dmn_process_write(spoof->process, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof->new_ip_value); + } + + // rjf: run for new events + ProfScope("run for new events") + { + LogInfoNamedBlockF("dmn_ctrl_run") + { + log_infof("single_step_thread: [0x%I64x]\n", run_ctrls->single_step_thread); + log_infof("ignore_previous_exception: %i\n", !!run_ctrls->ignore_previous_exception); + log_infof("run_entities_are_unfrozen: %i\n", !!run_ctrls->run_entities_are_unfrozen); + log_infof("run_entities_are_processes: %i\n", !!run_ctrls->run_entities_are_processes); + log_infof("run_entity_count: %I64u\n", run_ctrls->run_entity_count); + LogInfoNamedBlockF("run_entities") for(U64 idx = 0; idx < run_ctrls->run_entity_count; idx += 1) + { + log_infof("[0x%I64x]\n", run_ctrls->run_entities[idx]); + } + log_infof("trap_count: %I64u\n", run_ctrls->traps.trap_count); + LogInfoNamedBlockF("traps") for(DMN_TrapChunkNode *n = run_ctrls->traps.first; n != 0; n = n->next) + { + for(U64 idx = 0; idx < n->count; idx += 1) + { + log_infof("{process:[0x%I64x], vaddr:0x%I64x, id:0x%I64x}\n", n->v[idx].process.u64[0], n->v[idx].vaddr, n->v[idx].id); + } + } + } + DMN_EventList events = dmn_ctrl_run(scratch.arena, ctrl_ctx, run_ctrls); + for(DMN_EventNode *src_n = events.first; src_n != 0; src_n = src_n->next) + { + DMN_EventNode *dst_n = ctrl_state->free_dmn_event_node; + if(dst_n != 0) + { + SLLStackPop(ctrl_state->free_dmn_event_node); + } + else + { + dst_n = push_array(ctrl_state->dmn_event_arena, DMN_EventNode, 1); + } + MemoryCopyStruct(&dst_n->v, &src_n->v); + dst_n->v.string = push_str8_copy(ctrl_state->dmn_event_arena, dst_n->v.string); + SLLQueuePush(ctrl_state->first_dmn_event_node, ctrl_state->last_dmn_event_node, dst_n); + } + } + + // rjf: unset spoof + if(do_spoof) ProfScope("unset spoof") + { + dmn_process_write(spoof->process, r1u64(spoof->vaddr, spoof->vaddr+size_of_spoof), &spoof_old_ip_value); + } + } + } + + //- rjf: irrespective of what event came back, we should ALWAYS check the + // spoof's thread and see if it hit the spoof address, because we may have + // simply been sent other debug events first + if(spoof != 0) + { + CTRL_Entity *thread = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, CTRL_MachineID_Local, spoof->thread); + Architecture arch = thread->arch; + void *regs_block = push_array(scratch.arena, U8, regs_block_size_from_architecture(arch)); + dmn_thread_read_reg_block(spoof->thread, regs_block); + U64 spoof_thread_rip = regs_rip_from_arch_block(arch, regs_block); + if(spoof_thread_rip == spoof->new_ip_value) + { + regs_arch_block_write_rip(arch, regs_block, spoof_old_ip_value); + ctrl_thread_write_reg_block(CTRL_MachineID_Local, spoof->thread, regs_block); + } + } + + //- rjf: push ctrl events associated with this demon event + CTRL_EventList evts = {0}; + ProfScope("push ctrl events associated with this demon event") switch(event->kind) + { + default:{}break; + case DMN_EventKind_CreateProcess: + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_NewProc; + out_evt->msg_id = msg->msg_id; + out_evt->machine_id = CTRL_MachineID_Local; + out_evt->entity = event->process; + out_evt->arch = event->arch; + out_evt->entity_id = event->code; + ctrl_state->process_counter += 1; + }break; + case DMN_EventKind_CreateThread: + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_NewThread; + out_evt->msg_id = msg->msg_id; + out_evt->machine_id = CTRL_MachineID_Local; + out_evt->entity = event->thread; + out_evt->parent = event->process; + out_evt->arch = event->arch; + out_evt->entity_id = event->code; + out_evt->stack_base = dmn_stack_base_vaddr_from_thread(event->thread); + out_evt->tls_root = dmn_tls_root_vaddr_from_thread(event->thread); + out_evt->rip_vaddr = event->instruction_pointer; + out_evt->string = event->string; + }break; + case DMN_EventKind_LoadModule: + { + CTRL_Event *out_evt1 = ctrl_event_list_push(scratch.arena, &evts); + String8 module_path = event->string; + U64 exe_timestamp = os_properties_from_file_path(module_path).modified; + ctrl_thread__module_open(CTRL_MachineID_Local, event->process, event->module, r1u64(event->address, event->address+event->size), module_path); + out_evt1->kind = CTRL_EventKind_NewModule; + out_evt1->msg_id = msg->msg_id; + out_evt1->machine_id = CTRL_MachineID_Local; + out_evt1->entity = event->module; + out_evt1->parent = event->process; + out_evt1->arch = event->arch; + out_evt1->entity_id = event->code; + out_evt1->vaddr_rng = r1u64(event->address, event->address+event->size); + out_evt1->rip_vaddr = event->address; + out_evt1->timestamp = exe_timestamp; + out_evt1->string = module_path; + CTRL_Event *out_evt2 = ctrl_event_list_push(scratch.arena, &evts); + String8 initial_debug_info_path = ctrl_initial_debug_info_path_from_module(scratch.arena, CTRL_MachineID_Local, event->module); + U64 debug_info_timestamp = os_properties_from_file_path(initial_debug_info_path).modified; + out_evt2->kind = CTRL_EventKind_ModuleDebugInfoPathChange; + out_evt2->msg_id = msg->msg_id; + out_evt2->machine_id = CTRL_MachineID_Local; + out_evt2->entity = event->module; + out_evt2->parent = event->process; + out_evt2->timestamp = debug_info_timestamp; + out_evt2->string = initial_debug_info_path; + DI_Key initial_dbgi_key = {initial_debug_info_path, debug_info_timestamp}; + di_open(&initial_dbgi_key); + }break; + case DMN_EventKind_ExitProcess: + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_EndProc; + out_evt->msg_id = msg->msg_id; + out_evt->machine_id = CTRL_MachineID_Local; + out_evt->entity = event->process; + out_evt->u64_code = event->code; + ctrl_state->process_counter -= 1; + }break; + case DMN_EventKind_ExitThread: + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_EndThread; + out_evt->msg_id = msg->msg_id; + out_evt->machine_id = CTRL_MachineID_Local; + out_evt->entity = event->thread; + out_evt->entity_id = event->code; + }break; + case DMN_EventKind_UnloadModule: + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + String8 module_path = event->string; + ctrl_thread__module_close(CTRL_MachineID_Local, event->module); + out_evt->kind = CTRL_EventKind_EndModule; + out_evt->msg_id = msg->msg_id; + out_evt->machine_id = CTRL_MachineID_Local; + out_evt->entity = event->module; + out_evt->string = module_path; + CTRL_Entity *module_ent = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, CTRL_MachineID_Local, event->module); + CTRL_Entity *debug_info_path_ent = ctrl_entity_child_from_kind(module_ent, CTRL_EntityKind_DebugInfoPath); + if(debug_info_path_ent != &ctrl_entity_nil) + { + DI_Key dbgi_key = {debug_info_path_ent->string, debug_info_path_ent->timestamp}; + di_close(&dbgi_key); + } + }break; + case DMN_EventKind_DebugString: + { + U64 num_strings = (event->string.size + ctrl_state->c2u_ring_max_string_size-1) / ctrl_state->c2u_ring_max_string_size; + for(U64 string_idx = 0; string_idx < num_strings; string_idx += 1) + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_DebugString; + out_evt->msg_id = msg->msg_id; + out_evt->machine_id = CTRL_MachineID_Local; + out_evt->entity = event->thread; + out_evt->parent = event->process; + out_evt->string = str8_substr(event->string, r1u64(string_idx*ctrl_state->c2u_ring_max_string_size, (string_idx+1)*ctrl_state->c2u_ring_max_string_size)); + } + }break; + case DMN_EventKind_SetThreadName: + { + CTRL_Event *out_evt = ctrl_event_list_push(scratch.arena, &evts); + out_evt->kind = CTRL_EventKind_ThreadName; + out_evt->msg_id = msg->msg_id; + out_evt->machine_id = CTRL_MachineID_Local; + out_evt->entity = event->thread; + out_evt->parent = event->process; + out_evt->string = event->string; + out_evt->entity_id = event->code; + }break; + } + ctrl_c2u_push_events(&evts); + + //- rjf: clear process memory cache, if we've just started a lone process + if(event->kind == DMN_EventKind_CreateProcess && ctrl_state->process_counter == 1) + { + CTRL_ProcessMemoryCache *cache = &ctrl_state->process_memory_cache; + for(U64 slot_idx = 0; slot_idx < cache->slots_count; slot_idx += 1) + { + U64 stripe_idx = slot_idx%cache->stripes_count; + CTRL_ProcessMemoryCacheSlot *slot = &cache->slots[slot_idx]; + CTRL_ProcessMemoryCacheStripe *stripe = &cache->stripes[stripe_idx]; + OS_MutexScopeW(stripe->rw_mutex) + { + for(CTRL_ProcessMemoryCacheNode *n = slot->first, *next = 0; n != 0; n = next) + { + next = n->next; + arena_clear(n->arena); + } + } + MemoryZeroStruct(slot); + } + } + + //- rjf: out of queued up demon events -> clear event arena + if(ctrl_state->first_dmn_event_node == 0) + { + ctrl_state->free_dmn_event_node = 0; + arena_clear(ctrl_state->dmn_event_arena); + } + + scratch_end(scratch); + ProfEnd(); + return(event); +} + +//- rjf: eval helpers + +internal B32 +ctrl_eval_memory_read(void *u, void *out, U64 addr, U64 size) +{ + DMN_Handle process = *(DMN_Handle *)u; + U64 read_size = dmn_process_read(process, r1u64(addr, addr+size), out); + B32 result = (read_size == size); + return result; +} + +//- rjf: log flusher + +internal void +ctrl_thread__flush_info_log(String8 string) +{ + os_append_data_to_file_path(ctrl_state->ctrl_thread_log_path, string); +} + +internal void +ctrl_thread__end_and_flush_info_log(void) +{ + Temp scratch = scratch_begin(0, 0); + LogScopeResult log = log_scope_end(scratch.arena); + ctrl_thread__flush_info_log(log.strings[LogMsgKind_Info]); + scratch_end(scratch); +} + +//- rjf: msg kind implementations + +internal void +ctrl_thread__launch(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) +{ + //- rjf: launch + OS_ProcessLaunchParams params = {0}; + { + params.cmd_line = msg->cmd_line_string_list; + params.path = msg->path; + params.env = msg->env_string_list; + params.inherit_env = msg->env_inherit; + } + U32 id = dmn_ctrl_launch(ctrl_ctx, ¶ms); + + //- rjf: record (id -> entry points), so that we know custom entry points for this PID + for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) + { + String8 string = n->string; + CTRL_Entity *entry = ctrl_entity_alloc(ctrl_state->ctrl_thread_entity_store, ctrl_state->ctrl_thread_entity_store->root, CTRL_EntityKind_EntryPoint, Architecture_Null, 0, dmn_handle_zero(), (U64)id); + ctrl_entity_equip_string(ctrl_state->ctrl_thread_entity_store, entry, string); + } +} + +internal void +ctrl_thread__attach(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + + //- rjf: attach + B32 attach_successful = dmn_ctrl_attach(ctrl_ctx, msg->entity_id); + + //- rjf: run to handshake + if(attach_successful) + { + DMN_Handle unfrozen_process = {0}; + DMN_RunCtrls run_ctrls = {0}; + run_ctrls.run_entities_are_unfrozen = 1; + run_ctrls.run_entities_are_processes = 1; + for(B32 done = 0; done == 0;) + { + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); + switch(event->kind) + { + default:{}break; + case DMN_EventKind_CreateProcess: + { + unfrozen_process = event->process; + run_ctrls.run_entities = &unfrozen_process; + run_ctrls.run_entity_count = 1; + }break; + case DMN_EventKind_Halt: + case DMN_EventKind_Exception: + case DMN_EventKind_Error: + case DMN_EventKind_HandshakeComplete: + { + done = 1; + }break; + } + } + } + + //- rjf: record stop + { + CTRL_EventList evts = {0}; + CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); + event->kind = CTRL_EventKind_Stopped; + event->cause = CTRL_EventCause_Finished; + event->machine_id = CTRL_MachineID_Local; + event->msg_id = msg->msg_id; + event->entity_id = !!attach_successful * msg->entity_id; + ctrl_c2u_push_events(&evts); + } + + scratch_end(scratch); + ProfEnd(); +} + +internal void +ctrl_thread__kill(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DMN_Handle process = msg->entity; + U32 exit_code = msg->exit_code; + + //- rjf: send kill + B32 kill_worked = dmn_ctrl_kill(ctrl_ctx, process, exit_code); + + //- rjf: wait for process to be dead + if(kill_worked) + { + DMN_RunCtrls run_ctrls = {0}; + run_ctrls.run_entities_are_unfrozen = 1; + run_ctrls.run_entities_are_processes = 1; + run_ctrls.run_entities = &process; + run_ctrls.run_entity_count = 1; + for(B32 done = 0; done == 0;) + { + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); + if(event->kind == DMN_EventKind_ExitProcess && dmn_handle_match(event->process, process)) + { + done = 1; + } + if(event->kind == DMN_EventKind_Halt) + { + done = 1; + } + } + } + + //- rjf: record stop + { + CTRL_EventList evts = {0}; + CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); + event->kind = CTRL_EventKind_Stopped; + event->cause = CTRL_EventCause_Finished; + event->machine_id = CTRL_MachineID_Local; + event->msg_id = msg->msg_id; + if(kill_worked) + { + event->entity = msg->entity; + } + ctrl_c2u_push_events(&evts); + } + + scratch_end(scratch); + ProfEnd(); +} + +internal void +ctrl_thread__detach(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DMN_Handle process = msg->entity; + + //- rjf: detach + B32 detach_worked = dmn_ctrl_detach(ctrl_ctx, process); + + //- rjf: wait for process to be dead + if(detach_worked) + { + DMN_RunCtrls run_ctrls = {0}; + run_ctrls.run_entities_are_unfrozen = 1; + run_ctrls.run_entities_are_processes = 1; + run_ctrls.run_entities = &process; + run_ctrls.run_entity_count = 1; + for(B32 done = 0; done == 0;) + { + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); + if(event->kind == DMN_EventKind_ExitProcess && dmn_handle_match(event->process, process)) + { + done = 1; + } + if(event->kind == DMN_EventKind_Halt) + { + done = 1; + } + } + } + + //- rjf: record stop + { + CTRL_EventList evts = {0}; + CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); + event->kind = CTRL_EventKind_Stopped; + event->cause = CTRL_EventCause_Finished; + event->machine_id = CTRL_MachineID_Local; + event->msg_id = msg->msg_id; + if(detach_worked) + { + event->entity = msg->entity; + } + ctrl_c2u_push_events(&evts); + } + + scratch_end(scratch); + ProfEnd(); +} + +internal void +ctrl_thread__run(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DI_Scope *di_scope = di_scope_open(); + DMN_Event *stop_event = 0; + CTRL_EventCause stop_cause = CTRL_EventCause_Null; + DMN_Handle target_thread = msg->entity; + DMN_Handle target_process = msg->parent; + CTRL_Entity *target_process_entity = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, msg->machine_id, target_process); + U64 spoof_ip_vaddr = 911; + log_infof("ctrl_thread__run:\n{\n"); + + ////////////////////////////// + //- rjf: gather all initial breakpoints + // + DMN_TrapChunkList user_traps = {0}; + for(CTRL_Entity *machine = ctrl_state->ctrl_thread_entity_store->root->first; + machine != &ctrl_entity_nil; + machine = machine->next) + { + if(machine->kind != CTRL_EntityKind_Machine) { continue; } + for(CTRL_Entity *process = machine->first; process != &ctrl_entity_nil; process = process->next) + { + if(process->kind != CTRL_EntityKind_Process) { continue; } + + // rjf: resolve module-dependent user bps + for(CTRL_Entity *module = process->first; module != &ctrl_entity_nil; module = module->next) + { + if(module->kind != CTRL_EntityKind_Module) { continue; } + ctrl_thread__append_resolved_module_user_bp_traps(scratch.arena, machine->machine_id, process->handle, module->handle, &msg->user_bps, &user_traps); + } + + // rjf: push virtual-address user breakpoints per-process + ctrl_thread__append_resolved_process_user_bp_traps(scratch.arena, machine->machine_id, process->handle, &msg->user_bps, &user_traps); + } + } + + ////////////////////////////// + //- rjf: read initial stack-pointer-check value + // + // This MUST happen before any threads move, including single-stepping stuck + // threads, because otherwise, their stack pointer may change, if single-stepping + // causes e.g. entrance into a function via a call instruction. + // + U64 sp_check_value = dmn_rsp_from_thread(target_thread); + log_infof("sp_check_value := 0x%I64x\n", sp_check_value); + + ////////////////////////////// + //- rjf: single step "stuck threads" + // + // "Stuck threads" are threads that are already on a User BP and would hit + // it immediately if resumed with all User BPs enabled. To get them "unstuck" + // we just need to single step them to get them off their current instruction. + // + // This only applies to threads OTHER THAN the target thread. If the target + // thread is on a user breakpoint, then we need to let trap net logic run, + // which may include features put on a trap net trap at the same address as + // the user breakpoint. + // + B32 target_thread_is_on_user_bp_and_trap_net_trap = 0; + if(stop_event == 0) + { + // rjf: gather stuck threads + DMN_HandleList stuck_threads = {0}; + for(CTRL_Entity *machine = ctrl_state->ctrl_thread_entity_store->root->first; + machine != &ctrl_entity_nil; + machine = machine->next) + { + if(machine->kind != CTRL_EntityKind_Machine) { continue; } + for(CTRL_Entity *process = machine->first; process != &ctrl_entity_nil; process = process->next) + { + if(process->kind != CTRL_EntityKind_Process) { continue; } + for(CTRL_Entity *thread = process->first; thread != &ctrl_entity_nil; thread = thread->next) + { + U64 rip = dmn_rip_from_thread(thread->handle); + + // rjf: determine if thread is frozen + B32 thread_is_frozen = !msg->freeze_state_is_frozen; + for(CTRL_MachineIDHandlePairNode *n = msg->freeze_state_threads.first; n != 0; n = n->next) + { + if(dmn_handle_match(n->v.handle, thread->handle)) + { + thread_is_frozen ^= 1; + break; + } + } + + // rjf: not frozen? -> check if stuck & gather if so + if(thread_is_frozen == 0) + { + for(DMN_TrapChunkNode *n = user_traps.first; n != 0; n = n->next) + { + B32 is_on_user_bp = 0; + for(DMN_Trap *trap_ptr = n->v; trap_ptr < n->v+n->count; trap_ptr += 1) + { + if(dmn_handle_match(trap_ptr->process, process->handle) && trap_ptr->vaddr == rip) + { + is_on_user_bp = 1; + } + } + + B32 is_on_net_trap = 0; + for(CTRL_TrapNode *n = msg->traps.first; n != 0; n = n->next) + { + if(n->v.vaddr == rip) + { + is_on_net_trap = 1; + } + } + + if(is_on_user_bp && (!is_on_net_trap || !dmn_handle_match(thread->handle, target_thread))) + { + dmn_handle_list_push(scratch.arena, &stuck_threads, thread->handle); + } + + if(is_on_user_bp && is_on_net_trap && dmn_handle_match(thread->handle, target_thread)) + { + target_thread_is_on_user_bp_and_trap_net_trap = 1; + } + } + } + } + } + } + + // rjf: actually step stuck threads + for(DMN_HandleNode *node = stuck_threads.first; + node != 0; + node = node->next) + { + DMN_Handle thread = node->v; + U64 thread_pre_rip = dmn_rip_from_thread(thread); + U64 thread_post_rip = thread_pre_rip; + for(B32 done = 0; !done;) + { + log_infof("single_step_stuck_thread([0x%I64x])\n", thread.u64[0]); + DMN_RunCtrls run_ctrls = {0}; + run_ctrls.run_entities_are_unfrozen = 1; + run_ctrls.run_entities = &thread; + run_ctrls.run_entity_count = 1; + if(thread_post_rip == thread_pre_rip) + { + run_ctrls.single_step_thread = thread; + } + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); + thread_post_rip = dmn_rip_from_thread(thread); + switch(event->kind) + { + default:{}break; + case DMN_EventKind_Error: stop_cause = CTRL_EventCause_Error; goto stop; + case DMN_EventKind_Exception: stop_cause = CTRL_EventCause_InterruptedByException; goto stop; + case DMN_EventKind_Trap: stop_cause = CTRL_EventCause_InterruptedByTrap; goto stop; + case DMN_EventKind_Halt: stop_cause = CTRL_EventCause_InterruptedByHalt; goto stop; + stop:; + { + stop_event = event; + done = 1; + }break; + case DMN_EventKind_SingleStep: + { + done = dmn_handle_match(node->v, event->thread); + }break; + } + } + } + } + + ////////////////////////////// + //- rjf: resolve trap net + // + DMN_TrapChunkList trap_net_traps = {0}; + for(CTRL_TrapNode *node = msg->traps.first; + node != 0; + node = node->next) + { + DMN_Trap trap = {target_process, node->v.vaddr}; + dmn_trap_chunk_list_push(scratch.arena, &trap_net_traps, 256, &trap); + } + + ////////////////////////////// + //- rjf: join user breakpoints and trap net traps + // + DMN_TrapChunkList joined_traps = {0}; + { + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &user_traps); + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &trap_net_traps); + } + + ////////////////////////////// + //- rjf: record start + // + if(stop_event == 0) + { + CTRL_EventList evts = {0}; + CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); + event->kind = CTRL_EventKind_Started; + ctrl_c2u_push_events(&evts); + } + + ////////////////////////////// + //- rjf: run loop + // + if(stop_event == 0) + { + B32 spoof_mode = 0; + CTRL_Spoof spoof = {0}; + DMN_TrapChunkList entry_traps = {0}; + for(;;) + { + ////////////////////////// + //- rjf: choose low level traps + // + DMN_TrapChunkList *trap_list = &joined_traps; + if(spoof_mode) + { + trap_list = &user_traps; + } + + ////////////////////////// + //- rjf: choose spoof + // + CTRL_Spoof *run_spoof = 0; + if(spoof_mode) + { + run_spoof = &spoof; + } + + ////////////////////////// + //- rjf: setup run controls + // + DMN_RunCtrls run_ctrls = {0}; + run_ctrls.ignore_previous_exception = 1; + run_ctrls.run_entity_count = msg->freeze_state_threads.count; + run_ctrls.run_entities = push_array(scratch.arena, DMN_Handle, run_ctrls.run_entity_count); + run_ctrls.run_entities_are_unfrozen = !msg->freeze_state_is_frozen; + { + U64 idx = 0; + for(CTRL_MachineIDHandlePairNode *n = msg->freeze_state_threads.first; n != 0; n = n->next) + { + run_ctrls.run_entities[idx] = n->v.handle; + idx += 1; + } + } + run_ctrls.traps = *trap_list; + + ////////////////////////// + //- rjf: get next run-related event + // + log_infof("get_next_event:\n{\n"); + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, run_spoof); + log_infof("}\n\n"); + + ////////////////////////// + //- rjf: determine event handling + // + B32 launch_done_first_module = 0; + B32 hard_stop = 0; + CTRL_EventCause hard_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); + B32 use_stepping_logic = 0; + switch(event->kind) + { + default:{}break; + case DMN_EventKind_Error: + case DMN_EventKind_Halt: + case DMN_EventKind_SingleStep: + case DMN_EventKind_Trap: + { + hard_stop = 1; + log_infof("step_rule: unexpected -> hard_stop\n"); + }break; + case DMN_EventKind_Exception: + case DMN_EventKind_Breakpoint: + { + use_stepping_logic = 1; + log_infof("step_rule: exception/breakpoint -> stepping_logic\n"); + }break; + case DMN_EventKind_CreateProcess: + { + DMN_TrapChunkList new_traps = {0}; + ctrl_thread__append_resolved_process_user_bp_traps(scratch.arena, CTRL_MachineID_Local, event->process, &msg->user_bps, &new_traps); + log_infof("step_rule: create_process -> resolve traps\n"); + log_infof("new_traps:\n{\n"); + for(DMN_TrapChunkNode *n = new_traps.first; n != 0; n = n->next) + { + for(U64 idx = 0; idx < n->count; idx += 1) + { + DMN_Trap *trap = &n->v[idx]; + log_infof("{process:[0x%I64x], vaddr:0x%I64x}\n", trap->process.u64[0], trap->vaddr); + } + } + log_infof("}\n\n"); + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &new_traps); + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &user_traps, &new_traps); + }break; + case DMN_EventKind_LoadModule: + { + DMN_TrapChunkList new_traps = {0}; + ctrl_thread__append_resolved_module_user_bp_traps(scratch.arena, CTRL_MachineID_Local, event->process, event->module, &msg->user_bps, &new_traps); + log_infof("step_rule: load_module -> resolve traps\n"); + log_infof("new_traps:\n{\n"); + for(DMN_TrapChunkNode *n = new_traps.first; n != 0; n = n->next) + { + for(U64 idx = 0; idx < n->count; idx += 1) + { + DMN_Trap *trap = &n->v[idx]; + log_infof("{process:[0x%I64x], vaddr:0x%I64x}\n", trap->process.u64[0], trap->vaddr); + } + } + log_infof("}\n\n"); + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &new_traps); + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &user_traps, &new_traps); + }break; + } + + ////////////////////////// + //- rjf: on launches, detect entry points, place traps + // + if(msg->run_flags & CTRL_RunFlag_StopOnEntryPoint && !launch_done_first_module && event->kind == DMN_EventKind_HandshakeComplete) + { + launch_done_first_module = 1; + + //- rjf: unpack process/module info + CTRL_Entity *process = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, CTRL_MachineID_Local, event->process); + CTRL_Entity *module = &ctrl_entity_nil; + for(CTRL_Entity *child = process->first; child != &ctrl_entity_nil; child = child->next) + { + if(child->kind == CTRL_EntityKind_Module) + { + module = child; + break; + } + } + U64 module_base_vaddr = module->vaddr_range.min; + CTRL_Entity *dbg_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); + DI_Key dbgi_key = {dbg_path->string, dbg_path->timestamp}; + RDI_Parsed *rdi = di_rdi_from_key(di_scope, &dbgi_key, max_U64); + RDI_NameMap *unparsed_map = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_Procedures); + RDI_ParsedNameMap map = {0}; + rdi_parsed_from_name_map(rdi, unparsed_map, &map); + + //- rjf: add traps for user-specified entry points on this message, if specified + B32 entries_found = 0; + if(!entries_found) + { + for(String8Node *n = msg->entry_points.first; n != 0; n = n->next) + { + U32 procedure_id = 0; + { + String8 name = n->string; + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + procedure_id = ids[0]; + } + } + RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); + U64 voff = rdi_first_voff_from_procedure(rdi, procedure); + if(voff != 0) + { + entries_found = 1; + DMN_Trap trap = {process->handle, module_base_vaddr + voff}; + dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); + } + } + } + + //- rjf: add traps for PID-correllated entry points + if(!entries_found) + { + for(CTRL_Entity *e = ctrl_state->ctrl_thread_entity_store->root->first; e != &ctrl_entity_nil; e = e->next) + { + if(e->id == process->id) + { + U32 procedure_id = 0; + { + String8 name = e->string; + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + procedure_id = ids[0]; + } + } + RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); + U64 voff = rdi_first_voff_from_procedure(rdi, procedure); + if(voff != 0) + { + entries_found = 1; + DMN_Trap trap = {process->handle, module_base_vaddr + voff}; + dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); + } + } + } + } + + //- rjf: add traps for all custom user entry points + if(!entries_found) + { + for(String8Node *n = ctrl_state->user_entry_points.first; n != 0; n = n->next) + { + U32 procedure_id = 0; + { + String8 name = n->string; + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + procedure_id = ids[0]; + } + } + RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); + U64 voff = rdi_first_voff_from_procedure(rdi, procedure); + if(voff != 0) + { + DMN_Trap trap = {process->handle, module_base_vaddr + voff}; + dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); + break; + } + } + } + + //- rjf: add traps for all high-level entry points + if(!entries_found) + { + String8 hi_entry_points[] = + { + str8_lit("WinMain"), + str8_lit("wWinMain"), + str8_lit("main"), + str8_lit("wmain"), + }; + for(U64 idx = 0; idx < ArrayCount(hi_entry_points); idx += 1) + { + U32 procedure_id = 0; + { + String8 name = hi_entry_points[idx]; + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + procedure_id = ids[0]; + } + } + RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); + U64 voff = rdi_first_voff_from_procedure(rdi, procedure); + if(voff != 0) + { + entries_found = 1; + DMN_Trap trap = {process->handle, module_base_vaddr + voff}; + dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); + } + } + } + + //- rjf: add trap for PE header entry + if(!entries_found) + { + U64 voff = ctrl_entry_point_voff_from_module(CTRL_MachineID_Local, module->handle); + if(voff != 0) + { + DMN_Trap trap = {process->handle, module_base_vaddr + voff}; + dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); + } + } + + //- rjf: add traps for all low-level entry points + if(!entries_found) + { + String8 lo_entry_points[] = + { + str8_lit("WinMainCRTStartup"), + str8_lit("wWinMainCRTStartup"), + str8_lit("mainCRTStartup"), + str8_lit("wmainCRTStartup"), + }; + for(U64 idx = 0; idx < ArrayCount(lo_entry_points); idx += 1) + { + U32 procedure_id = 0; + { + String8 name = lo_entry_points[idx]; + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, name.str, name.size); + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + procedure_id = ids[0]; + } + } + RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_id); + U64 voff = rdi_first_voff_from_procedure(rdi, procedure); + if(voff != 0) + { + entries_found = 1; + DMN_Trap trap = {process->handle, module_base_vaddr + voff}; + dmn_trap_chunk_list_push(scratch.arena, &entry_traps, 256, &trap); + } + } + } + + //- rjf: no entry point found -> done + if(entry_traps.trap_count == 0) + { + hard_stop = 1; + } + + //- rjf: found entry points -> add to joined traps + dmn_trap_chunk_list_concat_shallow_copy(scratch.arena, &joined_traps, &entry_traps); + } + + ////////////////////////// + //- rjf: unpack info about thread attached to event + // + CTRL_Entity *thread = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, CTRL_MachineID_Local, event->thread); + Architecture arch = thread->arch; + U64 thread_rip_vaddr = dmn_rip_from_thread(event->thread); + CTRL_Entity *module = &ctrl_entity_nil; + { + CTRL_Entity *process = ctrl_entity_from_machine_id_handle(ctrl_state->ctrl_thread_entity_store, CTRL_MachineID_Local, event->process); + for(CTRL_Entity *m = process->first; m != &ctrl_entity_nil; m = m->next) + { + if(m->kind == CTRL_EntityKind_Module && contains_1u64(m->vaddr_range, thread_rip_vaddr)) + { + module = m; + break; + } + } + } + + ////////////////////////// + //- rjf: extract module-dependent info + // + CTRL_Entity *dbg_path = ctrl_entity_child_from_kind(module, CTRL_EntityKind_DebugInfoPath); + U64 thread_rip_voff = thread_rip_vaddr - module->vaddr_range.min; + + ////////////////////////// + //- rjf: stepping logic + // + //{ + + ////////////////////////// + //- rjf: handle if hitting a spoof + // + B32 exception_stop = 0; + B32 hit_spoof = 0; + if(!hard_stop && use_stepping_logic && event->kind == DMN_EventKind_Exception) + { + if(spoof_mode && + dmn_handle_match(target_process, event->process) && + dmn_handle_match(target_thread, event->thread) && + spoof.new_ip_value == event->instruction_pointer) + { + hit_spoof = 1; + log_infof("hit_spoof\n"); + } + else + { + exception_stop = 1; + use_stepping_logic = 0; + } + } + + //- rjf: handle spoof hit + if(hit_spoof) + { + log_infof("exit_spoof_mode\n"); + + // rjf: clear spoof mode + spoof_mode = 0; + MemoryZeroStruct(&spoof); + + // rjf: skip remainder of handling + use_stepping_logic = 0; + } + + //- rjf: for breakpoint events, gather bp info + B32 hit_entry = 0; + B32 hit_user_bp = 0; + B32 hit_trap_net_bp = 0; + B32 hit_conditional_bp_but_filtered = 0; + CTRL_TrapFlags hit_trap_flags = 0; + if(!hard_stop && use_stepping_logic && event->kind == DMN_EventKind_Breakpoint) + ProfScope("for breakpoint events, gather bp info") + { + Temp temp = temp_begin(scratch.arena); + String8List conditions = {0}; + + // rjf: entry breakpoints + for(DMN_TrapChunkNode *n = entry_traps.first; n != 0; n = n->next) + { + DMN_Trap *trap = n->v; + DMN_Trap *opl = n->v + n->count; + for(;trap < opl; trap += 1) + { + if(dmn_handle_match(trap->process, event->process) && trap->vaddr == event->instruction_pointer) + { + hit_entry = 1; + } + } + } + + // rjf: user breakpoints + for(DMN_TrapChunkNode *n = user_traps.first; n != 0; n = n->next) + { + DMN_Trap *trap = n->v; + DMN_Trap *opl = n->v + n->count; + for(;trap < opl; trap += 1) + { + if(dmn_handle_match(trap->process, event->process) && + trap->vaddr == event->instruction_pointer && + (!dmn_handle_match(event->thread, target_thread) || !target_thread_is_on_user_bp_and_trap_net_trap)) + { + CTRL_UserBreakpoint *user_bp = (CTRL_UserBreakpoint *)trap->id; + hit_user_bp = 1; + if(user_bp != 0 && user_bp->condition.size != 0) + { + str8_list_push(temp.arena, &conditions, user_bp->condition); + } + } + } + } + + // rjf: evaluate hit stop conditions + if(conditions.node_count != 0) ProfScope("evaluate hit stop conditions") + { + DI_Key dbgi_key = {dbg_path->string, dbg_path->timestamp}; + RDI_Parsed *rdi = di_rdi_from_key(di_scope, &dbgi_key, max_U64); + for(String8Node *condition_n = conditions.first; condition_n != 0; condition_n = condition_n->next) + { + ProfBegin("compile expression"); + String8 string = condition_n->string; + EVAL_ParseCtx parse_ctx = zero_struct; + { + parse_ctx.arch = arch; + parse_ctx.ip_voff = thread_rip_voff; + parse_ctx.rdi = rdi; + parse_ctx.type_graph = tg_graph_begin(bit_size_from_arch(arch)/8, 256); + parse_ctx.regs_map = ctrl_string2reg_from_arch(arch); + parse_ctx.reg_alias_map = ctrl_string2alias_from_arch(arch); + parse_ctx.locals_map = eval_push_locals_map_from_rdi_voff(temp.arena, rdi, thread_rip_voff); + parse_ctx.member_map = eval_push_member_map_from_rdi_voff(temp.arena, rdi, thread_rip_voff); + } + EVAL_TokenArray tokens = eval_token_array_from_text(temp.arena, string); + EVAL_ParseResult parse = eval_parse_expr_from_text_tokens(temp.arena, &parse_ctx, string, &tokens); + EVAL_ErrorList errors = parse.errors; + B32 parse_has_expr = (parse.expr != &eval_expr_nil); + B32 parse_is_type = (parse_has_expr && parse.expr->kind == EVAL_ExprKind_TypeIdent); + EVAL_IRTreeAndType ir_tree_and_type = {&eval_irtree_nil}; + if(parse_has_expr && errors.count == 0) + { + ir_tree_and_type = eval_irtree_and_type_from_expr(temp.arena, parse_ctx.type_graph, rdi, &eval_string2expr_map_nil, parse.expr, &errors); + } + EVAL_OpList op_list = {0}; + if(parse_has_expr && ir_tree_and_type.tree != &eval_irtree_nil) + { + eval_oplist_from_irtree(scratch.arena, ir_tree_and_type.tree, &op_list); + } + String8 bytecode = {0}; + if(parse_has_expr && parse_is_type == 0 && op_list.encoded_size != 0) + { + bytecode = eval_bytecode_from_oplist(scratch.arena, &op_list); + } + ProfEnd(); + EVAL_Result eval = {0}; + if(bytecode.size != 0) ProfScope("evaluate expression") + { + U64 module_base = module->vaddr_range.min; + U64 tls_base = dmn_tls_root_vaddr_from_thread(event->thread); + EVAL_Machine machine = {0}; + machine.u = &event->process; + machine.arch = arch; + machine.memory_read = ctrl_eval_memory_read; + machine.reg_size = regs_block_size_from_architecture(arch); + machine.reg_data = push_array(scratch.arena, U8, machine.reg_size); + dmn_thread_read_reg_block(event->thread, machine.reg_data); + machine.module_base = &module_base; + machine.tls_base = &tls_base; + eval = eval_interpret(&machine, bytecode); + } + if(eval.code == EVAL_ResultCode_Good && eval.value.u64 == 0) + { + hit_user_bp = 0; + hit_conditional_bp_but_filtered = 1; + log_infof("conditional_breakpoint_hit: 'condition eval'd to 0, and so filtered'\n"); + } + else + { + hit_user_bp = 1; + hit_conditional_bp_but_filtered = 0; + log_infof("conditional_breakpoint_hit: 'conditional eval'd to nonzero, hit'\n"); + break; + } + } + } + + // rjf: gather trap net hits + ProfScope("gather trap net hits") + { + if(!hit_user_bp && dmn_handle_match(event->process, target_process)) + { + for(CTRL_TrapNode *node = msg->traps.first; + node != 0; + node = node->next) + { + if(node->v.vaddr == event->instruction_pointer) + { + hit_trap_net_bp = 1; + hit_trap_flags |= node->v.flags; + } + } + } + } + + log_infof("user_breakpoint_hit: %i\n", hit_user_bp); + log_infof("entry_point_hit: %i\n", hit_entry); + temp_end(temp); + } + + //- rjf: hit conditional user bp but filtered -> single step + B32 cond_bp_single_step_stop = 0; + CTRL_EventCause cond_bp_single_step_stop_cause = CTRL_EventCause_Null; + if(hit_conditional_bp_but_filtered) LogInfoNamedBlockF("conditional_bp_hit_single_step") + { + DMN_Handle thread = event->thread; + U64 thread_pre_rip = dmn_rip_from_thread(thread); + U64 thread_post_rip = thread_pre_rip; + for(B32 single_step_done = 0; !single_step_done;) + { + DMN_RunCtrls single_step_ctrls = {0}; + single_step_ctrls.run_entities_are_unfrozen = 1; + single_step_ctrls.run_entities = &thread; + single_step_ctrls.run_entity_count = 1; + if(thread_post_rip == thread_pre_rip) + { + single_step_ctrls.single_step_thread = thread; + } + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &single_step_ctrls, 0); + thread_post_rip = dmn_rip_from_thread(thread); + switch(event->kind) + { + default:{}break; + case DMN_EventKind_Error: + case DMN_EventKind_Exception: + case DMN_EventKind_Halt: + case DMN_EventKind_Trap: + { + cond_bp_single_step_stop = 1; + single_step_done = 1; + use_stepping_logic = 0; + cond_bp_single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); + }break; + case DMN_EventKind_SingleStep: + { + single_step_done = dmn_handle_match(event->thread, thread); + cond_bp_single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); + }break; + } + } + } + + //- rjf: hit entry points on *any thread* cause a stop, if this msg says as such + B32 entry_stop = 0; + if(msg->run_flags & CTRL_RunFlag_StopOnEntryPoint && hit_entry) + { + entry_stop = 1; + use_stepping_logic = 0; + } + + //- rjf: user breakpoints on *any thread* cause a stop + B32 user_bp_stop = 0; + if(!hard_stop && use_stepping_logic && hit_user_bp) + { + user_bp_stop = 1; + use_stepping_logic = 0; + } + + //- rjf: trap net on off-target threads are ignored + B32 step_past_trap_net = 0; + if(!hard_stop && use_stepping_logic && hit_trap_net_bp) + { + if(!dmn_handle_match(event->thread, target_thread)) + { + step_past_trap_net = 1; + use_stepping_logic = 0; + } + } + + //- rjf: trap net on on-target threads trigger trap net logic + B32 use_trap_net_logic = 0; + if(!hard_stop && use_stepping_logic && hit_trap_net_bp) + { + if(dmn_handle_match(event->thread, target_thread)) + { + use_trap_net_logic = 1; + } + } + + //- rjf: trap net logic: stack pointer check + B32 stack_pointer_matches = 0; + if(use_trap_net_logic) + { + U64 sp = dmn_rsp_from_thread(target_thread); + stack_pointer_matches = (sp == sp_check_value); + } + + //- rjf: trap net logic: single step after hit + B32 single_step_stop = 0; + CTRL_EventCause single_step_stop_cause = CTRL_EventCause_Null; + if(!hard_stop && use_trap_net_logic) + { + if(hit_trap_flags & CTRL_TrapFlag_SingleStepAfterHit) LogInfoNamedBlockF("trap_net__single_step_after_hit") + { + U64 thread_pre_rip = dmn_rip_from_thread(target_thread); + U64 thread_post_rip = thread_pre_rip; + for(B32 single_step_done = 0; single_step_done == 0;) + { + DMN_RunCtrls single_step_ctrls = {0}; + single_step_ctrls.run_entities_are_unfrozen = 1; + single_step_ctrls.run_entities = &target_thread; + single_step_ctrls.run_entity_count = 1; + if(thread_post_rip == thread_pre_rip) + { + single_step_ctrls.single_step_thread = target_thread; + } + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &single_step_ctrls, 0); + thread_post_rip = dmn_rip_from_thread(target_thread); + switch(event->kind) + { + default:{}break; + case DMN_EventKind_Error: + case DMN_EventKind_Exception: + case DMN_EventKind_Halt: + case DMN_EventKind_Trap: + { + single_step_stop = 1; + single_step_done = 1; + use_stepping_logic = 0; + use_trap_net_logic = 0; + single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); + }break; + case DMN_EventKind_SingleStep: + { + single_step_done = dmn_handle_match(event->thread, target_thread);; + single_step_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); + }break; + } + } + } + } + + //- rjf: trap net logic: begin spoof mode + B32 begin_spoof_mode = 0; + if(!hard_stop && use_trap_net_logic) + { + if(hit_trap_flags & CTRL_TrapFlag_BeginSpoofMode) LogInfoNamedBlockF("trap_net__begin_spoof_mode") + { + // rjf: setup spoof mode + begin_spoof_mode = 1; + U64 spoof_sp = dmn_rsp_from_thread(target_thread); + spoof_mode = 1; + spoof.process = target_process; + spoof.thread = target_thread; + spoof.vaddr = spoof_sp; + spoof.new_ip_value = spoof_ip_vaddr; + log_infof("spoof:{process:[0x%I64x], thread:[0x%I64x], vaddr:0x%I64x, new_ip_value:0x%I64x}\n", spoof.process.u64[0], spoof.thread.u64[0], spoof.vaddr, spoof.new_ip_value); + } + } + + //- rjf: trap net logic: save stack pointer + B32 save_stack_pointer = 0; + if(!hard_stop && use_trap_net_logic) + { + if(hit_trap_flags & CTRL_TrapFlag_SaveStackPointer) + { + if(stack_pointer_matches) LogInfoNamedBlockF("trap_net__save_sp") + { + save_stack_pointer = 1; + sp_check_value = dmn_rsp_from_thread(target_thread); + log_infof("sp_check_value = 0x%I64x\n", sp_check_value); + } + } + } + + //- rjf: trap net logic: end stepping + B32 trap_net_stop = 0; + if(!hard_stop && use_trap_net_logic) + { + if(hit_trap_flags & CTRL_TrapFlag_EndStepping) LogInfoNamedBlockF("trap_net__end_step") + { + if((hit_trap_flags & CTRL_TrapFlag_IgnoreStackPointerCheck) || + stack_pointer_matches) + { + trap_net_stop = 1; + use_trap_net_logic = 0; + } + } + } + + //} + // + //- rjf: stepping logic + //////////////////////////////// + + //- rjf: handle step past trap net + B32 step_past_trap_net_stop = 0; + CTRL_EventCause step_past_trap_net_stop_cause = CTRL_EventCause_Null; + if(step_past_trap_net) LogInfoNamedBlockF("trap_net__single_step_past_trap_net") + { + DMN_Handle thread = event->thread; + U64 thread_pre_rip = dmn_rip_from_thread(thread); + U64 thread_post_rip = thread_pre_rip; + for(B32 single_step_done = 0; single_step_done == 0;) + { + DMN_RunCtrls single_step_ctrls = {0}; + single_step_ctrls.run_entities_are_unfrozen = 1; + single_step_ctrls.run_entities = &thread; + single_step_ctrls.run_entity_count = 1; + if(thread_post_rip == thread_pre_rip) + { + single_step_ctrls.single_step_thread = thread; + } + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &single_step_ctrls, 0); + thread_post_rip = dmn_rip_from_thread(thread); + switch(event->kind) + { + default:{}break; + case DMN_EventKind_Error: + case DMN_EventKind_Exception: + case DMN_EventKind_Halt: + case DMN_EventKind_Trap: + { + step_past_trap_net_stop = 1; + single_step_done = 1; + step_past_trap_net_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); + }break; + case DMN_EventKind_SingleStep: + { + single_step_done = dmn_handle_match(event->thread, thread); + step_past_trap_net_stop_cause = ctrl_event_cause_from_dmn_event_kind(event->kind); + }break; + } + } + } + + //- rjf: loop exit condition + CTRL_EventCause stage_stop_cause = CTRL_EventCause_Null; + if(hard_stop) + { + stage_stop_cause = hard_stop_cause; + } + else if(cond_bp_single_step_stop) + { + stage_stop_cause = cond_bp_single_step_stop_cause; + } + else if(single_step_stop) + { + stage_stop_cause = single_step_stop_cause; + } + else if(step_past_trap_net_stop) + { + stage_stop_cause = step_past_trap_net_stop_cause; + } + else if(exception_stop) + { + stage_stop_cause = CTRL_EventCause_InterruptedByException; + } + else if(user_bp_stop) + { + stage_stop_cause = CTRL_EventCause_UserBreakpoint; + } + else if(entry_stop) + { + stage_stop_cause = CTRL_EventCause_EntryPoint; + } + else if(trap_net_stop) + { + stage_stop_cause = CTRL_EventCause_Finished; + } + log_infof("stop_cause: %i\n", stage_stop_cause); + if(stage_stop_cause != CTRL_EventCause_Null) + { + stop_event = event; + stop_cause = stage_stop_cause; + break; + } + } + } + + ////////////////////////////// + //- rjf: record stop + // + if(stop_event != 0) + { + CTRL_EventList evts = {0}; + CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); + event->kind = CTRL_EventKind_Stopped; + event->cause = stop_cause; + event->machine_id = CTRL_MachineID_Local; + event->entity = stop_event->thread; + event->parent = stop_event->process; + event->exception_code = stop_event->code; + event->vaddr_rng = r1u64(stop_event->address, stop_event->address); + event->rip_vaddr = stop_event->instruction_pointer; + ctrl_c2u_push_events(&evts); + } + + log_infof("}\n\n"); + di_scope_close(di_scope); + scratch_end(scratch); + ProfEnd(); +} + +internal void +ctrl_thread__single_step(DMN_CtrlCtx *ctrl_ctx, CTRL_Msg *msg) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + + //- rjf: record start + { + CTRL_EventList evts = {0}; + CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); + event->kind = CTRL_EventKind_Started; + ctrl_c2u_push_events(&evts); + } + + //- rjf: single step + DMN_Event *stop_event = 0; + CTRL_EventCause stop_cause = CTRL_EventCause_Null; + { + DMN_Handle thread = msg->entity; + U64 thread_pre_rip = dmn_rip_from_thread(thread); + U64 thread_post_rip = thread_pre_rip; + for(B32 done = 0; done == 0;) + { + DMN_RunCtrls run_ctrls = {0}; + run_ctrls.run_entities_are_unfrozen = 1; + run_ctrls.run_entities = &thread; + run_ctrls.run_entity_count = 1; + if(thread_post_rip == thread_pre_rip) + { + run_ctrls.single_step_thread = msg->entity; + } + DMN_Event *event = ctrl_thread__next_dmn_event(scratch.arena, ctrl_ctx, msg, &run_ctrls, 0); + thread_post_rip = dmn_rip_from_thread(msg->entity); + switch(event->kind) + { + default:{}break; + case DMN_EventKind_Error: {stop_cause = CTRL_EventCause_Error;}goto end_single_step; + case DMN_EventKind_Exception: {stop_cause = CTRL_EventCause_InterruptedByException;}goto end_single_step; + case DMN_EventKind_Halt: {stop_cause = CTRL_EventCause_InterruptedByHalt;}goto end_single_step; + case DMN_EventKind_Trap: {stop_cause = CTRL_EventCause_InterruptedByTrap;}goto end_single_step; + case DMN_EventKind_Breakpoint: {stop_cause = CTRL_EventCause_UserBreakpoint;}goto end_single_step; + case DMN_EventKind_SingleStep: {stop_cause = CTRL_EventCause_Finished;}goto end_single_step; + end_single_step: + { + stop_event = event; + done = 1; + }break; + } + } + } + + //- rjf: record stop + if(stop_event != 0) + { + CTRL_EventList evts = {0}; + CTRL_Event *event = ctrl_event_list_push(scratch.arena, &evts); + event->kind = CTRL_EventKind_Stopped; + event->cause = stop_cause; + event->machine_id = CTRL_MachineID_Local; + event->entity = stop_event->thread; + event->parent = stop_event->process; + event->exception_code = stop_event->code; + event->vaddr_rng = r1u64(stop_event->address, stop_event->address); + event->rip_vaddr = stop_event->instruction_pointer; + ctrl_c2u_push_events(&evts); + } + + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Memory-Stream-Thread-Only Functions + +//- rjf: user -> memory stream communication + +internal B32 +ctrl_u2ms_enqueue_req(CTRL_MachineID machine_id, DMN_Handle process, Rng1U64 vaddr_range, B32 zero_terminated, U64 endt_us) +{ + B32 good = 0; + OS_MutexScope(ctrl_state->u2ms_ring_mutex) for(;;) + { + U64 unconsumed_size = ctrl_state->u2ms_ring_write_pos-ctrl_state->u2ms_ring_read_pos; + U64 available_size = ctrl_state->u2ms_ring_size-unconsumed_size; + if(available_size >= sizeof(machine_id)+sizeof(process)+sizeof(vaddr_range)) + { + good = 1; + ctrl_state->u2ms_ring_write_pos += ring_write_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_write_pos, &machine_id); + ctrl_state->u2ms_ring_write_pos += ring_write_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_write_pos, &process); + ctrl_state->u2ms_ring_write_pos += ring_write_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_write_pos, &vaddr_range); + ctrl_state->u2ms_ring_write_pos += ring_write_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_write_pos, &zero_terminated); + break; + } + if(os_now_microseconds() >= endt_us) {break;} + os_condition_variable_wait(ctrl_state->u2ms_ring_cv, ctrl_state->u2ms_ring_mutex, endt_us); + } + os_condition_variable_broadcast(ctrl_state->u2ms_ring_cv); + return good; +} + +internal void +ctrl_u2ms_dequeue_req(CTRL_MachineID *out_machine_id, DMN_Handle *out_process, Rng1U64 *out_vaddr_range, B32 *out_zero_terminated) +{ + OS_MutexScope(ctrl_state->u2ms_ring_mutex) for(;;) + { + U64 unconsumed_size = ctrl_state->u2ms_ring_write_pos-ctrl_state->u2ms_ring_read_pos; + if(unconsumed_size >= sizeof(*out_machine_id)+sizeof(*out_process)+sizeof(*out_vaddr_range)) + { + ctrl_state->u2ms_ring_read_pos += ring_read_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_read_pos, out_machine_id); + ctrl_state->u2ms_ring_read_pos += ring_read_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_read_pos, out_process); + ctrl_state->u2ms_ring_read_pos += ring_read_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_read_pos, out_vaddr_range); + ctrl_state->u2ms_ring_read_pos += ring_read_struct(ctrl_state->u2ms_ring_base, ctrl_state->u2ms_ring_size, ctrl_state->u2ms_ring_read_pos, out_zero_terminated); + break; + } + os_condition_variable_wait(ctrl_state->u2ms_ring_cv, ctrl_state->u2ms_ring_mutex, max_U64); + } + os_condition_variable_broadcast(ctrl_state->u2ms_ring_cv); +} + +//- rjf: entry point + +internal void +ctrl_mem_stream_thread__entry_point(void *p) +{ + CTRL_ProcessMemoryCache *cache = &ctrl_state->process_memory_cache; + for(;;) + { + //- rjf: unpack next request + CTRL_MachineID machine_id = 0; + DMN_Handle process = {0}; + Rng1U64 vaddr_range = {0}; + B32 zero_terminated = 0; + ctrl_u2ms_dequeue_req(&machine_id, &process, &vaddr_range, &zero_terminated); + U128 key = ctrl_calc_hash_store_key_from_process_vaddr_range(machine_id, process, vaddr_range, zero_terminated); + + //- rjf: unpack process memory cache key + U64 process_hash = ctrl_hash_from_string(str8_struct(&process)); + U64 process_slot_idx = process_hash%cache->slots_count; + U64 process_stripe_idx = process_slot_idx%cache->stripes_count; + CTRL_ProcessMemoryCacheSlot *process_slot = &cache->slots[process_slot_idx]; + CTRL_ProcessMemoryCacheStripe *process_stripe = &cache->stripes[process_stripe_idx]; + + //- rjf: unpack address range hash cache key + U64 range_hash = ctrl_hash_from_string(str8_struct(&vaddr_range)); + + //- rjf: take task + B32 got_task = 0; + U64 preexisting_mem_gen = 0; + U128 preexisting_hash = {0}; + Rng1U64 vaddr_range_clamped = {0}; + OS_MutexScopeW(process_stripe->rw_mutex) + { + for(CTRL_ProcessMemoryCacheNode *n = process_slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->process, process)) + { + U64 range_slot_idx = range_hash%n->range_hash_slots_count; + CTRL_ProcessMemoryRangeHashSlot *range_slot = &n->range_hash_slots[range_slot_idx]; + for(CTRL_ProcessMemoryRangeHashNode *range_n = range_slot->first; range_n != 0; range_n = range_n->next) + { + if(MemoryMatchStruct(&range_n->vaddr_range, &vaddr_range) && range_n->zero_terminated == zero_terminated) + { + got_task = !ins_atomic_u32_eval_cond_assign(&range_n->is_taken, 1, 0); + preexisting_mem_gen = range_n->mem_gen; + preexisting_hash = range_n->hash; + vaddr_range_clamped = range_n->vaddr_range_clamped; + goto take_task__break_all; + } + } + } + } + take_task__break_all:; + } + + //- rjf: task was taken -> read memory + U64 range_size = 0; + Arena *range_arena = 0; + void *range_base = 0; + U64 zero_terminated_size = 0; + U64 pre_read_mem_gen = dmn_mem_gen(); + U64 post_read_mem_gen = 0; + if(got_task && pre_read_mem_gen != preexisting_mem_gen) + { + range_size = dim_1u64(vaddr_range_clamped); + U64 arena_size = AlignPow2(range_size + ARENA_HEADER_SIZE, os_get_system_info()->page_size); + range_arena = arena_alloc(.reserve_size = range_size+ARENA_HEADER_SIZE, .commit_size = range_size+ARENA_HEADER_SIZE); + if(range_arena == 0) + { + range_size = 0; + } + else + { + range_base = push_array_no_zero(range_arena, U8, range_size); + U64 bytes_read = 0; + U64 retry_count = 0; + for(Rng1U64 vaddr_range_clamped_retry = vaddr_range_clamped; retry_count < 64; retry_count += 1) + { + bytes_read = dmn_process_read(process, vaddr_range_clamped_retry, range_base); + if(bytes_read == 0 && vaddr_range_clamped_retry.max > vaddr_range_clamped_retry.min) + { + U64 diff = (vaddr_range_clamped_retry.max-vaddr_range_clamped_retry.min)/2; + vaddr_range_clamped_retry.max -= diff; + if(diff == 0) + { + break; + } + } + else + { + break; + } + } + if(bytes_read == 0) + { + arena_release(range_arena); + range_base = 0; + range_size = 0; + range_arena = 0; + } + else if(bytes_read < range_size) + { + MemoryZero((U8 *)range_base + bytes_read, range_size-bytes_read); + } + zero_terminated_size = range_size; + if(zero_terminated) + { + for(U64 idx = 0; idx < bytes_read; idx += 1) + { + if(((U8 *)range_base)[idx] == 0) + { + zero_terminated_size = idx; + break; + } + } + } + } + post_read_mem_gen = dmn_mem_gen(); + } + + //- rjf: read successful -> submit to hash store + U128 hash = {0}; + if(got_task && range_base != 0) + { + hash = hs_submit_data(key, &range_arena, str8((U8*)range_base, zero_terminated_size)); + } + + //- rjf: commit hash to cache + if(got_task) OS_MutexScopeW(process_stripe->rw_mutex) + { + for(CTRL_ProcessMemoryCacheNode *n = process_slot->first; n != 0; n = n->next) + { + if(n->machine_id == machine_id && dmn_handle_match(n->process, process)) + { + U64 range_slot_idx = range_hash%n->range_hash_slots_count; + CTRL_ProcessMemoryRangeHashSlot *range_slot = &n->range_hash_slots[range_slot_idx]; + for(CTRL_ProcessMemoryRangeHashNode *range_n = range_slot->first; range_n != 0; range_n = range_n->next) + { + if(MemoryMatchStruct(&range_n->vaddr_range, &vaddr_range) && range_n->zero_terminated == zero_terminated) + { + if(!u128_match(u128_zero(), hash)) + { + range_n->hash = hash; + } + if(!u128_match(u128_zero(), hash)) + { + range_n->mem_gen = post_read_mem_gen; + } + ins_atomic_u32_eval_assign(&range_n->is_taken, 0); + goto commit__break_all; + } + } + } + } + commit__break_all:; + } + + //- rjf: broadcast changes + os_condition_variable_broadcast(process_stripe->cv); + } +} diff --git a/src/dasm_cache/dasm_cache.c b/src/dasm_cache/dasm_cache.c index a0f46255..8d36fecf 100644 --- a/src/dasm_cache/dasm_cache.c +++ b/src/dasm_cache/dasm_cache.c @@ -1,738 +1,738 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Third Party Includes - -#include "third_party/udis86/config.h" -#include "third_party/udis86/udis86.h" -#include "third_party/udis86/libudis86/decode.c" -#include "third_party/udis86/libudis86/itab.c" -#include "third_party/udis86/libudis86/syn-att.c" -#include "third_party/udis86/libudis86/syn-intel.c" -#include "third_party/udis86/libudis86/syn.c" -#include "third_party/udis86/libudis86/udis86.c" - -//////////////////////////////// -//~ rjf: Parameter Type Functions - -internal B32 -dasm_params_match(DASM_Params *a, DASM_Params *b) -{ - B32 result = (a->vaddr == b->vaddr && - a->arch == b->arch && - a->style_flags == b->style_flags && - a->syntax == b->syntax && - a->base_vaddr == b->base_vaddr && - di_key_match(&a->dbgi_key, &b->dbgi_key)); - return result; -} - -//////////////////////////////// -//~ rjf: Instruction Type Functions - -internal void -dasm_inst_chunk_list_push(Arena *arena, DASM_InstChunkList *list, U64 cap, DASM_Inst *inst) -{ - DASM_InstChunkNode *node = list->last; - if(node == 0 || node->count >= node->cap) - { - node = push_array(arena, DASM_InstChunkNode, 1); - node->v = push_array_no_zero(arena, DASM_Inst, cap); - node->cap = cap; - SLLQueuePush(list->first, list->last, node); - list->node_count += 1; - } - MemoryCopyStruct(&node->v[node->count], inst); - node->count += 1; - list->inst_count += 1; -} - -internal DASM_InstArray -dasm_inst_array_from_chunk_list(Arena *arena, DASM_InstChunkList *list) -{ - DASM_InstArray array = {0}; - array.count = list->inst_count; - array.v = push_array_no_zero(arena, DASM_Inst, array.count); - U64 idx = 0; - for(DASM_InstChunkNode *n = list->first; n != 0; n = n->next) - { - MemoryCopy(array.v+idx, n->v, sizeof(DASM_Inst)*n->count); - idx += n->count; - } - return array; -} - -internal U64 -dasm_inst_array_idx_from_code_off__linear_scan(DASM_InstArray *array, U64 off) -{ - U64 result = 0; - for(U64 idx = 0; idx < array->count; idx += 1) - { - U64 next_off = (idx+1 < array->count ? array->v[idx+1].code_off : max_U64); - if(array->v[idx].code_off <= off && off < next_off) - { - result = idx; - if(!(array->v[idx].flags & DASM_InstFlag_Decorative)) - { - break; - } - } - } - return result; -} - -internal U64 -dasm_inst_array_code_off_from_idx(DASM_InstArray *array, U64 idx) -{ - U64 off = 0; - if(idx < array->count) - { - off = array->v[idx].code_off; - } - return off; -} - -//////////////////////////////// -//~ rjf: Main Layer Initialization - -internal void -dasm_init(void) -{ - Arena *arena = arena_alloc(); - dasm_shared = push_array(arena, DASM_Shared, 1); - dasm_shared->arena = arena; - dasm_shared->slots_count = 1024; - dasm_shared->stripes_count = Min(dasm_shared->slots_count, os_logical_core_count()); - dasm_shared->slots = push_array(arena, DASM_Slot, dasm_shared->slots_count); - dasm_shared->stripes = push_array(arena, DASM_Stripe, dasm_shared->stripes_count); - for(U64 idx = 0; idx < dasm_shared->stripes_count; idx += 1) - { - dasm_shared->stripes[idx].arena = arena_alloc(); - dasm_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); - dasm_shared->stripes[idx].cv = os_condition_variable_alloc(); - } - dasm_shared->u2p_ring_size = KB(64); - dasm_shared->u2p_ring_base = push_array_no_zero(arena, U8, dasm_shared->u2p_ring_size); - dasm_shared->u2p_ring_cv = os_condition_variable_alloc(); - dasm_shared->u2p_ring_mutex = os_mutex_alloc(); - dasm_shared->parse_thread_count = 1; - dasm_shared->parse_threads = push_array(arena, OS_Handle, dasm_shared->parse_thread_count); - for(U64 idx = 0; idx < dasm_shared->parse_thread_count; idx += 1) - { - dasm_shared->parse_threads[idx] = os_launch_thread(dasm_parse_thread__entry_point, (void *)idx, 0); - } - dasm_shared->evictor_detector_thread = os_launch_thread(dasm_evictor_detector_thread__entry_point, 0, 0); -} - -//////////////////////////////// -//~ rjf: User Clock - -internal void -dasm_user_clock_tick(void) -{ - ins_atomic_u64_inc_eval(&dasm_shared->user_clock_idx); -} - -internal U64 -dasm_user_clock_idx(void) -{ - U64 idx = ins_atomic_u64_eval(&dasm_shared->user_clock_idx); - return idx; -} - -//////////////////////////////// -//~ rjf: Scoped Access - -internal DASM_Scope * -dasm_scope_open(void) -{ - if(dasm_tctx == 0) - { - Arena *arena = arena_alloc(); - dasm_tctx = push_array(arena, DASM_TCTX, 1); - dasm_tctx->arena = arena; - } - U64 base_pos = arena_pos(dasm_tctx->arena); - DASM_Scope *scope = push_array(dasm_tctx->arena, DASM_Scope, 1); - scope->base_pos = base_pos; - return scope; -} - -internal void -dasm_scope_close(DASM_Scope *scope) -{ - for(DASM_Touch *t = scope->top_touch, *next = 0; t != 0; t = next) - { - next = t->next; - U64 slot_idx = t->hash.u64[1]%dasm_shared->slots_count; - U64 stripe_idx = slot_idx%dasm_shared->stripes_count; - DASM_Slot *slot = &dasm_shared->slots[slot_idx]; - DASM_Stripe *stripe = &dasm_shared->stripes[stripe_idx]; - OS_MutexScopeR(stripe->rw_mutex) - { - for(DASM_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(t->hash, n->hash) && dasm_params_match(&t->params, &n->params)) - { - ins_atomic_u64_dec_eval(&n->scope_ref_count); - break; - } - } - } - } - arena_pop_to(dasm_tctx->arena, scope->base_pos); -} - -internal void -dasm_scope_touch_node__stripe_r_guarded(DASM_Scope *scope, DASM_Node *node) -{ - DASM_Touch *touch = push_array(dasm_tctx->arena, DASM_Touch, 1); - ins_atomic_u64_inc_eval(&node->scope_ref_count); - ins_atomic_u64_eval_assign(&node->last_time_touched_us, os_now_microseconds()); - ins_atomic_u64_eval_assign(&node->last_user_clock_idx_touched, dasm_user_clock_idx()); - touch->hash = node->hash; - MemoryCopyStruct(&touch->params, &node->params); - touch->params.dbgi_key = di_key_copy(dasm_tctx->arena, &touch->params.dbgi_key); - SLLStackPush(scope->top_touch, touch); -} - -//////////////////////////////// -//~ rjf: Cache Lookups - -internal DASM_Info -dasm_info_from_hash_params(DASM_Scope *scope, U128 hash, DASM_Params *params) -{ - DASM_Info info = {0}; - if(!u128_match(hash, u128_zero())) - { - U64 slot_idx = hash.u64[1]%dasm_shared->slots_count; - U64 stripe_idx = slot_idx%dasm_shared->stripes_count; - DASM_Slot *slot = &dasm_shared->slots[slot_idx]; - DASM_Stripe *stripe = &dasm_shared->stripes[stripe_idx]; - B32 found = 0; - OS_MutexScopeR(stripe->rw_mutex) - { - for(DASM_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(hash, n->hash) && dasm_params_match(params, &n->params)) - { - MemoryCopyStruct(&info, &n->info); - found = 1; - dasm_scope_touch_node__stripe_r_guarded(scope, n); - break; - } - } - } - B32 node_is_new = 0; - if(!found) - { - OS_MutexScopeW(stripe->rw_mutex) - { - DASM_Node *node = 0; - for(DASM_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(hash, n->hash) && dasm_params_match(params, &n->params)) - { - node = n; - break; - } - } - if(node == 0) - { - LogInfoNamedBlockF("dasm_new_node") - { - log_infof("hash: [0x%I64x 0x%I64x]\n", hash.u64[0], hash.u64[1]); - log_infof("vaddr: 0x%I64x\n", params->vaddr); - log_infof("arch: %S\n", string_from_architecture(params->arch)); - log_infof("style_flags: 0x%x\n", params->style_flags); - log_infof("syntax: %i\n", params->syntax); - log_infof("base_vaddr: 0x%I64x\n", params->base_vaddr); - log_infof("dbgi_key: [%S 0x%I64x]\n", params->dbgi_key.path, params->dbgi_key.min_timestamp); - } - node = stripe->free_node; - if(node) - { - SLLStackPop(stripe->free_node); - } - else - { - node = push_array_no_zero(stripe->arena, DASM_Node, 1); - } - MemoryZeroStruct(node); - DLLPushBack(slot->first, slot->last, node); - node->hash = hash; - MemoryCopyStruct(&node->params, params); - // TODO(rjf): need to make this releasable - currently all exe_paths just leak - node->params.dbgi_key = di_key_copy(stripe->arena, &node->params.dbgi_key); - node_is_new = 1; - } - } - } - if(node_is_new) - { - dasm_u2p_enqueue_req(hash, params, max_U64); - } - } - return info; -} - -internal DASM_Info -dasm_info_from_key_params(DASM_Scope *scope, U128 key, DASM_Params *params, U128 *hash_out) -{ - DASM_Info result = {0}; - for(U64 rewind_idx = 0; rewind_idx < 2; rewind_idx += 1) - { - U128 hash = hs_hash_from_key(key, rewind_idx); - result = dasm_info_from_hash_params(scope, hash, params); - if(result.insts.count != 0) - { - if(hash_out) - { - *hash_out = hash; - } - break; - } - } - return result; -} - -//////////////////////////////// -//~ rjf: Parse Threads - -internal B32 -dasm_u2p_enqueue_req(U128 hash, DASM_Params *params, U64 endt_us) -{ - B32 good = 0; - OS_MutexScope(dasm_shared->u2p_ring_mutex) for(;;) - { - U64 unconsumed_size = dasm_shared->u2p_ring_write_pos - dasm_shared->u2p_ring_read_pos; - U64 available_size = dasm_shared->u2p_ring_size - unconsumed_size; - if(available_size >= sizeof(hash)+sizeof(U64)+sizeof(Architecture)+sizeof(DASM_StyleFlags)+sizeof(DASM_Syntax)+sizeof(U64)+sizeof(U64)+params->dbgi_key.path.size+sizeof(U64)) - { - good = 1; - dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, &hash); - dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, ¶ms->vaddr); - dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, ¶ms->arch); - dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, ¶ms->style_flags); - dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, ¶ms->syntax); - dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, ¶ms->base_vaddr); - dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, ¶ms->dbgi_key.path.size); - dasm_shared->u2p_ring_write_pos += ring_write(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, params->dbgi_key.path.str, params->dbgi_key.path.size); - dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, ¶ms->dbgi_key.min_timestamp); - dasm_shared->u2p_ring_write_pos += 7; - dasm_shared->u2p_ring_write_pos -= dasm_shared->u2p_ring_write_pos%8; - break; - } - if(os_now_microseconds() >= endt_us) - { - break; - } - os_condition_variable_wait(dasm_shared->u2p_ring_cv, dasm_shared->u2p_ring_mutex, endt_us); - } - if(good) - { - os_condition_variable_broadcast(dasm_shared->u2p_ring_cv); - } - return good; -} - -internal void -dasm_u2p_dequeue_req(Arena *arena, U128 *hash_out, DASM_Params *params_out) -{ - OS_MutexScope(dasm_shared->u2p_ring_mutex) for(;;) - { - U64 unconsumed_size = dasm_shared->u2p_ring_write_pos - dasm_shared->u2p_ring_read_pos; - if(unconsumed_size >= sizeof(*hash_out)+sizeof(U64)+sizeof(Architecture)+sizeof(DASM_StyleFlags)+sizeof(DASM_Syntax)+sizeof(U64)+sizeof(U64)+sizeof(U64)) - { - dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, hash_out); - dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, ¶ms_out->vaddr); - dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, ¶ms_out->arch); - dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, ¶ms_out->style_flags); - dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, ¶ms_out->syntax); - dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, ¶ms_out->base_vaddr); - dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, ¶ms_out->dbgi_key.path.size); - params_out->dbgi_key.path.str = push_array(arena, U8, params_out->dbgi_key.path.size); - dasm_shared->u2p_ring_read_pos += ring_read(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, params_out->dbgi_key.path.str, params_out->dbgi_key.path.size); - dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, ¶ms_out->dbgi_key.min_timestamp); - dasm_shared->u2p_ring_read_pos += 7; - dasm_shared->u2p_ring_read_pos -= dasm_shared->u2p_ring_read_pos%8; - break; - } - os_condition_variable_wait(dasm_shared->u2p_ring_cv, dasm_shared->u2p_ring_mutex, max_U64); - } - os_condition_variable_broadcast(dasm_shared->u2p_ring_cv); -} - -internal void -dasm_parse_thread__entry_point(void *p) -{ - ThreadNameF("[dasm] parse thread #%I64u", (U64)p); - for(;;) - { - Temp scratch = scratch_begin(0, 0); - - //- rjf: get next request - U128 hash = {0}; - DASM_Params params = {0}; - dasm_u2p_dequeue_req(scratch.arena, &hash, ¶ms); - U64 change_gen = fs_change_gen(); - HS_Scope *hs_scope = hs_scope_open(); - DI_Scope *di_scope = di_scope_open(); - TXT_Scope *txt_scope = txt_scope_open(); - - //- rjf: unpack hash - U64 slot_idx = hash.u64[1]%dasm_shared->slots_count; - U64 stripe_idx = slot_idx%dasm_shared->stripes_count; - DASM_Slot *slot = &dasm_shared->slots[slot_idx]; - DASM_Stripe *stripe = &dasm_shared->stripes[stripe_idx]; - - //- rjf: take task - B32 got_task = 0; - OS_MutexScopeR(stripe->rw_mutex) - { - for(DASM_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(n->hash, hash) && dasm_params_match(&n->params, ¶ms)) - { - got_task = !ins_atomic_u32_eval_cond_assign(&n->is_working, 1, 0); - break; - } - } - } - - //- rjf: get dbg info - RDI_Parsed *rdi = &di_rdi_parsed_nil; - if(got_task && params.dbgi_key.path.size != 0) - { - rdi = di_rdi_from_key(di_scope, ¶ms.dbgi_key, max_U64); - } - - //- rjf: hash -> data - String8 data = {0}; - if(got_task) - { - data = hs_data_from_hash(hs_scope, hash); - } - - //- rjf: data * arch * addr * dbg -> decode artifacts - DASM_InstChunkList inst_list = {0}; - String8List inst_strings = {0}; - if(got_task) - { - switch(params.arch) - { - default:{}break; - - //- rjf: x86/x64 decoding - case Architecture_x64: - case Architecture_x86: - { - // rjf: grab context - struct ud udc; - ud_init(&udc); - ud_set_mode(&udc, bit_size_from_arch(params.arch)); - ud_set_pc(&udc, params.vaddr); - ud_set_input_buffer(&udc, data.str, data.size); - ud_set_vendor(&udc, UD_VENDOR_ANY); - ud_set_syntax(&udc, params.syntax == DASM_Syntax_Intel ? UD_SYN_INTEL : UD_SYN_ATT); - - // rjf: disassemble - RDI_SourceFile *last_file = &rdi_nil_element_union.source_file; - RDI_Line *last_line = 0; - for(U64 off = 0; off < data.size;) - { - // rjf: disassemble one instruction - U64 size = ud_disassemble(&udc); - if(size == 0) - { - break; - } - - // rjf: analyze - struct ud_operand *first_op = (struct ud_operand *)ud_insn_opr(&udc, 0); - U64 rel_voff = (first_op != 0 && first_op->type == UD_OP_JIMM) ? ud_syn_rel_target(&udc, first_op) : 0; - U64 jump_dst_vaddr = rel_voff; - - // rjf: push strings derived from voff -> line info - if(params.style_flags & (DASM_StyleFlag_SourceFilesNames|DASM_StyleFlag_SourceLines)) - { - if(rdi != &di_rdi_parsed_nil) - { - U64 voff = (params.vaddr+off) - params.base_vaddr; - U32 unit_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_UnitVMap, voff); - RDI_Unit *unit = rdi_element_from_name_idx(rdi, Units, unit_idx); - RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, unit->line_table_idx); - RDI_ParsedLineTable unit_line_info = {0}; - rdi_parsed_from_line_table(rdi, line_table, &unit_line_info); - U64 line_info_idx = rdi_line_info_idx_from_voff(&unit_line_info, voff); - if(line_info_idx < unit_line_info.count) - { - RDI_Line *line = &unit_line_info.lines[line_info_idx]; - RDI_SourceFile *file = rdi_element_from_name_idx(rdi, SourceFiles, line->file_idx); - String8 file_normalized_full_path = {0}; - file_normalized_full_path.str = rdi_string_from_idx(rdi, file->normal_full_path_string_idx, &file_normalized_full_path.size); - if(file != last_file) - { - if(params.style_flags & DASM_StyleFlag_SourceFilesNames && - file->normal_full_path_string_idx != 0 && file_normalized_full_path.size != 0) - { - String8 inst_string = push_str8f(scratch.arena, "> %S", file_normalized_full_path); - DASM_Inst inst = {u32_from_u64_saturate(off), DASM_InstFlag_Decorative, 0, r1u64(inst_strings.total_size + inst_strings.node_count, - inst_strings.total_size + inst_strings.node_count + inst_string.size)}; - dasm_inst_chunk_list_push(scratch.arena, &inst_list, 1024, &inst); - str8_list_push(scratch.arena, &inst_strings, inst_string); - } - if(params.style_flags & DASM_StyleFlag_SourceFilesNames && file->normal_full_path_string_idx == 0) - { - String8 inst_string = str8_lit(">"); - DASM_Inst inst = {u32_from_u64_saturate(off), DASM_InstFlag_Decorative, 0, r1u64(inst_strings.total_size + inst_strings.node_count, - inst_strings.total_size + inst_strings.node_count + inst_string.size)}; - dasm_inst_chunk_list_push(scratch.arena, &inst_list, 1024, &inst); - str8_list_push(scratch.arena, &inst_strings, inst_string); - } - last_file = file; - } - if(line && line != last_line && file->normal_full_path_string_idx != 0 && - params.style_flags & DASM_StyleFlag_SourceLines && - file_normalized_full_path.size != 0) - { - FileProperties props = os_properties_from_file_path(file_normalized_full_path); - if(props.modified != 0) - { - // TODO(rjf): need redirection path - this may map to a different path on the local machine, - // need frontend to communicate path remapping info to this layer - U128 key = fs_key_from_path(file_normalized_full_path); - TXT_LangKind lang_kind = txt_lang_kind_from_extension(file_normalized_full_path); - U64 endt_us = max_U64; - U128 hash = {0}; - TXT_TextInfo text_info = {0}; - for(;os_now_microseconds() <= endt_us;) - { - text_info = txt_text_info_from_key_lang(txt_scope, key, lang_kind, &hash); - if(!u128_match(hash, u128_zero())) - { - break; - } - } - if(0 < line->line_num && line->line_num < text_info.lines_count) - { - String8 data = hs_data_from_hash(hs_scope, hash); - String8 line_text = str8_skip_chop_whitespace(str8_substr(data, text_info.lines_ranges[line->line_num-1])); - if(line_text.size != 0) - { - String8 inst_string = push_str8f(scratch.arena, "> %S", line_text); - DASM_Inst inst = {u32_from_u64_saturate(off), DASM_InstFlag_Decorative, 0, r1u64(inst_strings.total_size + inst_strings.node_count, - inst_strings.total_size + inst_strings.node_count + inst_string.size)}; - dasm_inst_chunk_list_push(scratch.arena, &inst_list, 1024, &inst); - str8_list_push(scratch.arena, &inst_strings, inst_string); - } - } - } - last_line = line; - } - } - } - } - - // rjf: push - String8 addr_part = {0}; - if(params.style_flags & DASM_StyleFlag_Addresses) - { - addr_part = push_str8f(scratch.arena, "%s0x%016I64x ", rdi != &di_rdi_parsed_nil ? " " : "", params.vaddr+off); - } - String8 code_bytes_part = {0}; - if(params.style_flags & DASM_StyleFlag_CodeBytes) - { - String8List code_bytes_strings = {0}; - str8_list_push(scratch.arena, &code_bytes_strings, str8_lit("{")); - for(U64 byte_idx = 0; byte_idx < size || byte_idx < 16; byte_idx += 1) - { - if(byte_idx < size) - { - str8_list_pushf(scratch.arena, &code_bytes_strings, "%02x%s ", (U32)data.str[off+byte_idx], byte_idx == size-1 ? "}" : ""); - } - else if(byte_idx < 8) - { - str8_list_push(scratch.arena, &code_bytes_strings, str8_lit(" ")); - } - } - str8_list_push(scratch.arena, &code_bytes_strings, str8_lit(" ")); - code_bytes_part = str8_list_join(scratch.arena, &code_bytes_strings, 0); - } - String8 symbol_part = {0}; - if(jump_dst_vaddr != 0 && rdi != &di_rdi_parsed_nil && params.style_flags & DASM_StyleFlag_SymbolNames) - { - RDI_U32 scope_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_ScopeVMap, jump_dst_vaddr-params.base_vaddr); - if(scope_idx != 0) - { - RDI_Scope *scope = rdi_element_from_name_idx(rdi, Scopes, scope_idx); - RDI_U32 procedure_idx = scope->proc_idx; - RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_idx); - String8 procedure_name = {0}; - procedure_name.str = rdi_string_from_idx(rdi, procedure->name_string_idx, &procedure_name.size); - if(procedure_name.size != 0) - { - symbol_part = push_str8f(scratch.arena, " (%S)", procedure_name); - } - } - } - String8 inst_string = push_str8f(scratch.arena, "%S%S%s%S", addr_part, code_bytes_part, udc.asm_buf, symbol_part); - DASM_Inst inst = {u32_from_u64_saturate(off), 0, rel_voff, r1u64(inst_strings.total_size + inst_strings.node_count, - inst_strings.total_size + inst_strings.node_count + inst_string.size)}; - dasm_inst_chunk_list_push(scratch.arena, &inst_list, 1024, &inst); - str8_list_push(scratch.arena, &inst_strings, inst_string); - - // rjf: increment - off += size; - } - }break; - } - } - - //- rjf: artifacts -> value bundle - Arena *info_arena = 0; - DASM_Info info = {0}; - if(got_task) - { - //- rjf: produce joined text - Arena *text_arena = arena_alloc(); - StringJoin text_join = {0}; - text_join.sep = str8_lit("\n"); - String8 text = str8_list_join(text_arena, &inst_strings, &text_join); - - //- rjf: produce unique key for this disassembly's text - U128 text_key = {0}; - { - U64 hash_data[] = - { - hash.u64[0], - hash.u64[1], - params.vaddr, - (U64)params.arch, - (U64)params.style_flags, - (U64)params.syntax, - (U64)rdi, - 0x4d534144, - }; - text_key = hs_hash_from_data(str8((U8 *)hash_data, sizeof(hash_data))); - } - - //- rjf: submit text data to hash store - U128 text_hash = hs_submit_data(text_key, &text_arena, text); - - //- rjf: produce value bundle - info_arena = arena_alloc(); - info.text_key = text_key; - info.insts = dasm_inst_array_from_chunk_list(info_arena, &inst_list); - } - - //- rjf: commit results to cache - if(got_task) OS_MutexScopeW(stripe->rw_mutex) - { - for(DASM_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(n->hash, hash) && dasm_params_match(&n->params, ¶ms)) - { - n->info_arena = info_arena; - MemoryCopyStruct(&n->info, &info); - if(rdi != &di_rdi_parsed_nil && params.style_flags & (DASM_StyleFlag_SourceLines|DASM_StyleFlag_SourceFilesNames)) - { - n->change_gen = change_gen; - } - else - { - n->change_gen = 0; - } - ins_atomic_u32_eval_assign(&n->is_working, 0); - ins_atomic_u64_inc_eval(&n->load_count); - break; - } - } - } - - txt_scope_close(txt_scope); - di_scope_close(di_scope); - hs_scope_close(hs_scope); - scratch_end(scratch); - } -} - -//////////////////////////////// -//~ rjf: Evictor/Detector Thread - -internal void -dasm_evictor_detector_thread__entry_point(void *p) -{ - ThreadNameF("[dasm] evictor/detector thread"); - for(;;) - { - U64 change_gen = fs_change_gen(); - U64 check_time_us = os_now_microseconds(); - U64 check_time_user_clocks = dasm_user_clock_idx(); - U64 evict_threshold_us = 10*1000000; - U64 retry_threshold_us = 1*1000000; - U64 evict_threshold_user_clocks = 10; - U64 retry_threshold_user_clocks = 10; - for(U64 slot_idx = 0; slot_idx < dasm_shared->slots_count; slot_idx += 1) - { - U64 stripe_idx = slot_idx%dasm_shared->stripes_count; - DASM_Slot *slot = &dasm_shared->slots[slot_idx]; - DASM_Stripe *stripe = &dasm_shared->stripes[stripe_idx]; - B32 slot_has_work = 0; - OS_MutexScopeR(stripe->rw_mutex) - { - for(DASM_Node *n = slot->first; n != 0; n = n->next) - { - if(n->scope_ref_count == 0 && - n->last_time_touched_us+evict_threshold_us <= check_time_us && - n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && - n->load_count != 0 && - n->is_working == 0) - { - slot_has_work = 1; - break; - } - if(n->change_gen != 0 && n->change_gen != change_gen && - n->last_time_requested_us+retry_threshold_us <= check_time_us && - n->last_user_clock_idx_requested+retry_threshold_user_clocks <= check_time_user_clocks) - { - slot_has_work = 1; - break; - } - } - } - if(slot_has_work) OS_MutexScopeW(stripe->rw_mutex) - { - for(DASM_Node *n = slot->first, *next = 0; n != 0; n = next) - { - next = n->next; - if(n->scope_ref_count == 0 && - n->last_time_touched_us+evict_threshold_us <= check_time_us && - n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && - n->load_count != 0 && - n->is_working == 0) - { - DLLRemove(slot->first, slot->last, n); - if(n->info_arena != 0) - { - arena_release(n->info_arena); - } - SLLStackPush(stripe->free_node, n); - } - if(n->change_gen != 0 && n->change_gen != change_gen && - n->last_time_requested_us+retry_threshold_us <= check_time_us && - n->last_user_clock_idx_requested+retry_threshold_user_clocks <= check_time_user_clocks) - { - if(dasm_u2p_enqueue_req(n->hash, &n->params, max_U64)) - { - n->last_time_requested_us = os_now_microseconds(); - n->last_user_clock_idx_requested = check_time_user_clocks; - } - } - } - } - } - os_sleep_milliseconds(100); - } -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Third Party Includes + +#include "third_party/udis86/config.h" +#include "third_party/udis86/udis86.h" +#include "third_party/udis86/libudis86/decode.c" +#include "third_party/udis86/libudis86/itab.c" +#include "third_party/udis86/libudis86/syn-att.c" +#include "third_party/udis86/libudis86/syn-intel.c" +#include "third_party/udis86/libudis86/syn.c" +#include "third_party/udis86/libudis86/udis86.c" + +//////////////////////////////// +//~ rjf: Parameter Type Functions + +internal B32 +dasm_params_match(DASM_Params *a, DASM_Params *b) +{ + B32 result = (a->vaddr == b->vaddr && + a->arch == b->arch && + a->style_flags == b->style_flags && + a->syntax == b->syntax && + a->base_vaddr == b->base_vaddr && + di_key_match(&a->dbgi_key, &b->dbgi_key)); + return result; +} + +//////////////////////////////// +//~ rjf: Instruction Type Functions + +internal void +dasm_inst_chunk_list_push(Arena *arena, DASM_InstChunkList *list, U64 cap, DASM_Inst *inst) +{ + DASM_InstChunkNode *node = list->last; + if(node == 0 || node->count >= node->cap) + { + node = push_array(arena, DASM_InstChunkNode, 1); + node->v = push_array_no_zero(arena, DASM_Inst, cap); + node->cap = cap; + SLLQueuePush(list->first, list->last, node); + list->node_count += 1; + } + MemoryCopyStruct(&node->v[node->count], inst); + node->count += 1; + list->inst_count += 1; +} + +internal DASM_InstArray +dasm_inst_array_from_chunk_list(Arena *arena, DASM_InstChunkList *list) +{ + DASM_InstArray array = {0}; + array.count = list->inst_count; + array.v = push_array_no_zero(arena, DASM_Inst, array.count); + U64 idx = 0; + for(DASM_InstChunkNode *n = list->first; n != 0; n = n->next) + { + MemoryCopy(array.v+idx, n->v, sizeof(DASM_Inst)*n->count); + idx += n->count; + } + return array; +} + +internal U64 +dasm_inst_array_idx_from_code_off__linear_scan(DASM_InstArray *array, U64 off) +{ + U64 result = 0; + for(U64 idx = 0; idx < array->count; idx += 1) + { + U64 next_off = (idx+1 < array->count ? array->v[idx+1].code_off : max_U64); + if(array->v[idx].code_off <= off && off < next_off) + { + result = idx; + if(!(array->v[idx].flags & DASM_InstFlag_Decorative)) + { + break; + } + } + } + return result; +} + +internal U64 +dasm_inst_array_code_off_from_idx(DASM_InstArray *array, U64 idx) +{ + U64 off = 0; + if(idx < array->count) + { + off = array->v[idx].code_off; + } + return off; +} + +//////////////////////////////// +//~ rjf: Main Layer Initialization + +internal void +dasm_init(void) +{ + Arena *arena = arena_alloc(); + dasm_shared = push_array(arena, DASM_Shared, 1); + dasm_shared->arena = arena; + dasm_shared->slots_count = 1024; + dasm_shared->stripes_count = Min(dasm_shared->slots_count, os_get_system_info()->logical_processor_count); + dasm_shared->slots = push_array(arena, DASM_Slot, dasm_shared->slots_count); + dasm_shared->stripes = push_array(arena, DASM_Stripe, dasm_shared->stripes_count); + for(U64 idx = 0; idx < dasm_shared->stripes_count; idx += 1) + { + dasm_shared->stripes[idx].arena = arena_alloc(); + dasm_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); + dasm_shared->stripes[idx].cv = os_condition_variable_alloc(); + } + dasm_shared->u2p_ring_size = KB(64); + dasm_shared->u2p_ring_base = push_array_no_zero(arena, U8, dasm_shared->u2p_ring_size); + dasm_shared->u2p_ring_cv = os_condition_variable_alloc(); + dasm_shared->u2p_ring_mutex = os_mutex_alloc(); + dasm_shared->parse_thread_count = 1; + dasm_shared->parse_threads = push_array(arena, OS_Handle, dasm_shared->parse_thread_count); + for(U64 idx = 0; idx < dasm_shared->parse_thread_count; idx += 1) + { + dasm_shared->parse_threads[idx] = os_thread_launch(dasm_parse_thread__entry_point, (void *)idx, 0); + } + dasm_shared->evictor_detector_thread = os_thread_launch(dasm_evictor_detector_thread__entry_point, 0, 0); +} + +//////////////////////////////// +//~ rjf: User Clock + +internal void +dasm_user_clock_tick(void) +{ + ins_atomic_u64_inc_eval(&dasm_shared->user_clock_idx); +} + +internal U64 +dasm_user_clock_idx(void) +{ + U64 idx = ins_atomic_u64_eval(&dasm_shared->user_clock_idx); + return idx; +} + +//////////////////////////////// +//~ rjf: Scoped Access + +internal DASM_Scope * +dasm_scope_open(void) +{ + if(dasm_tctx == 0) + { + Arena *arena = arena_alloc(); + dasm_tctx = push_array(arena, DASM_TCTX, 1); + dasm_tctx->arena = arena; + } + U64 base_pos = arena_pos(dasm_tctx->arena); + DASM_Scope *scope = push_array(dasm_tctx->arena, DASM_Scope, 1); + scope->base_pos = base_pos; + return scope; +} + +internal void +dasm_scope_close(DASM_Scope *scope) +{ + for(DASM_Touch *t = scope->top_touch, *next = 0; t != 0; t = next) + { + next = t->next; + U64 slot_idx = t->hash.u64[1]%dasm_shared->slots_count; + U64 stripe_idx = slot_idx%dasm_shared->stripes_count; + DASM_Slot *slot = &dasm_shared->slots[slot_idx]; + DASM_Stripe *stripe = &dasm_shared->stripes[stripe_idx]; + OS_MutexScopeR(stripe->rw_mutex) + { + for(DASM_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(t->hash, n->hash) && dasm_params_match(&t->params, &n->params)) + { + ins_atomic_u64_dec_eval(&n->scope_ref_count); + break; + } + } + } + } + arena_pop_to(dasm_tctx->arena, scope->base_pos); +} + +internal void +dasm_scope_touch_node__stripe_r_guarded(DASM_Scope *scope, DASM_Node *node) +{ + DASM_Touch *touch = push_array(dasm_tctx->arena, DASM_Touch, 1); + ins_atomic_u64_inc_eval(&node->scope_ref_count); + ins_atomic_u64_eval_assign(&node->last_time_touched_us, os_now_microseconds()); + ins_atomic_u64_eval_assign(&node->last_user_clock_idx_touched, dasm_user_clock_idx()); + touch->hash = node->hash; + MemoryCopyStruct(&touch->params, &node->params); + touch->params.dbgi_key = di_key_copy(dasm_tctx->arena, &touch->params.dbgi_key); + SLLStackPush(scope->top_touch, touch); +} + +//////////////////////////////// +//~ rjf: Cache Lookups + +internal DASM_Info +dasm_info_from_hash_params(DASM_Scope *scope, U128 hash, DASM_Params *params) +{ + DASM_Info info = {0}; + if(!u128_match(hash, u128_zero())) + { + U64 slot_idx = hash.u64[1]%dasm_shared->slots_count; + U64 stripe_idx = slot_idx%dasm_shared->stripes_count; + DASM_Slot *slot = &dasm_shared->slots[slot_idx]; + DASM_Stripe *stripe = &dasm_shared->stripes[stripe_idx]; + B32 found = 0; + OS_MutexScopeR(stripe->rw_mutex) + { + for(DASM_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(hash, n->hash) && dasm_params_match(params, &n->params)) + { + MemoryCopyStruct(&info, &n->info); + found = 1; + dasm_scope_touch_node__stripe_r_guarded(scope, n); + break; + } + } + } + B32 node_is_new = 0; + if(!found) + { + OS_MutexScopeW(stripe->rw_mutex) + { + DASM_Node *node = 0; + for(DASM_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(hash, n->hash) && dasm_params_match(params, &n->params)) + { + node = n; + break; + } + } + if(node == 0) + { + LogInfoNamedBlockF("dasm_new_node") + { + log_infof("hash: [0x%I64x 0x%I64x]\n", hash.u64[0], hash.u64[1]); + log_infof("vaddr: 0x%I64x\n", params->vaddr); + log_infof("arch: %S\n", string_from_architecture(params->arch)); + log_infof("style_flags: 0x%x\n", params->style_flags); + log_infof("syntax: %i\n", params->syntax); + log_infof("base_vaddr: 0x%I64x\n", params->base_vaddr); + log_infof("dbgi_key: [%S 0x%I64x]\n", params->dbgi_key.path, params->dbgi_key.min_timestamp); + } + node = stripe->free_node; + if(node) + { + SLLStackPop(stripe->free_node); + } + else + { + node = push_array_no_zero(stripe->arena, DASM_Node, 1); + } + MemoryZeroStruct(node); + DLLPushBack(slot->first, slot->last, node); + node->hash = hash; + MemoryCopyStruct(&node->params, params); + // TODO(rjf): need to make this releasable - currently all exe_paths just leak + node->params.dbgi_key = di_key_copy(stripe->arena, &node->params.dbgi_key); + node_is_new = 1; + } + } + } + if(node_is_new) + { + dasm_u2p_enqueue_req(hash, params, max_U64); + } + } + return info; +} + +internal DASM_Info +dasm_info_from_key_params(DASM_Scope *scope, U128 key, DASM_Params *params, U128 *hash_out) +{ + DASM_Info result = {0}; + for(U64 rewind_idx = 0; rewind_idx < 2; rewind_idx += 1) + { + U128 hash = hs_hash_from_key(key, rewind_idx); + result = dasm_info_from_hash_params(scope, hash, params); + if(result.insts.count != 0) + { + if(hash_out) + { + *hash_out = hash; + } + break; + } + } + return result; +} + +//////////////////////////////// +//~ rjf: Parse Threads + +internal B32 +dasm_u2p_enqueue_req(U128 hash, DASM_Params *params, U64 endt_us) +{ + B32 good = 0; + OS_MutexScope(dasm_shared->u2p_ring_mutex) for(;;) + { + U64 unconsumed_size = dasm_shared->u2p_ring_write_pos - dasm_shared->u2p_ring_read_pos; + U64 available_size = dasm_shared->u2p_ring_size - unconsumed_size; + if(available_size >= sizeof(hash)+sizeof(U64)+sizeof(Architecture)+sizeof(DASM_StyleFlags)+sizeof(DASM_Syntax)+sizeof(U64)+sizeof(U64)+params->dbgi_key.path.size+sizeof(U64)) + { + good = 1; + dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, &hash); + dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, ¶ms->vaddr); + dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, ¶ms->arch); + dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, ¶ms->style_flags); + dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, ¶ms->syntax); + dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, ¶ms->base_vaddr); + dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, ¶ms->dbgi_key.path.size); + dasm_shared->u2p_ring_write_pos += ring_write(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, params->dbgi_key.path.str, params->dbgi_key.path.size); + dasm_shared->u2p_ring_write_pos += ring_write_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_write_pos, ¶ms->dbgi_key.min_timestamp); + dasm_shared->u2p_ring_write_pos += 7; + dasm_shared->u2p_ring_write_pos -= dasm_shared->u2p_ring_write_pos%8; + break; + } + if(os_now_microseconds() >= endt_us) + { + break; + } + os_condition_variable_wait(dasm_shared->u2p_ring_cv, dasm_shared->u2p_ring_mutex, endt_us); + } + if(good) + { + os_condition_variable_broadcast(dasm_shared->u2p_ring_cv); + } + return good; +} + +internal void +dasm_u2p_dequeue_req(Arena *arena, U128 *hash_out, DASM_Params *params_out) +{ + OS_MutexScope(dasm_shared->u2p_ring_mutex) for(;;) + { + U64 unconsumed_size = dasm_shared->u2p_ring_write_pos - dasm_shared->u2p_ring_read_pos; + if(unconsumed_size >= sizeof(*hash_out)+sizeof(U64)+sizeof(Architecture)+sizeof(DASM_StyleFlags)+sizeof(DASM_Syntax)+sizeof(U64)+sizeof(U64)+sizeof(U64)) + { + dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, hash_out); + dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, ¶ms_out->vaddr); + dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, ¶ms_out->arch); + dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, ¶ms_out->style_flags); + dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, ¶ms_out->syntax); + dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, ¶ms_out->base_vaddr); + dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, ¶ms_out->dbgi_key.path.size); + params_out->dbgi_key.path.str = push_array(arena, U8, params_out->dbgi_key.path.size); + dasm_shared->u2p_ring_read_pos += ring_read(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, params_out->dbgi_key.path.str, params_out->dbgi_key.path.size); + dasm_shared->u2p_ring_read_pos += ring_read_struct(dasm_shared->u2p_ring_base, dasm_shared->u2p_ring_size, dasm_shared->u2p_ring_read_pos, ¶ms_out->dbgi_key.min_timestamp); + dasm_shared->u2p_ring_read_pos += 7; + dasm_shared->u2p_ring_read_pos -= dasm_shared->u2p_ring_read_pos%8; + break; + } + os_condition_variable_wait(dasm_shared->u2p_ring_cv, dasm_shared->u2p_ring_mutex, max_U64); + } + os_condition_variable_broadcast(dasm_shared->u2p_ring_cv); +} + +internal void +dasm_parse_thread__entry_point(void *p) +{ + ThreadNameF("[dasm] parse thread #%I64u", (U64)p); + for(;;) + { + Temp scratch = scratch_begin(0, 0); + + //- rjf: get next request + U128 hash = {0}; + DASM_Params params = {0}; + dasm_u2p_dequeue_req(scratch.arena, &hash, ¶ms); + U64 change_gen = fs_change_gen(); + HS_Scope *hs_scope = hs_scope_open(); + DI_Scope *di_scope = di_scope_open(); + TXT_Scope *txt_scope = txt_scope_open(); + + //- rjf: unpack hash + U64 slot_idx = hash.u64[1]%dasm_shared->slots_count; + U64 stripe_idx = slot_idx%dasm_shared->stripes_count; + DASM_Slot *slot = &dasm_shared->slots[slot_idx]; + DASM_Stripe *stripe = &dasm_shared->stripes[stripe_idx]; + + //- rjf: take task + B32 got_task = 0; + OS_MutexScopeR(stripe->rw_mutex) + { + for(DASM_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(n->hash, hash) && dasm_params_match(&n->params, ¶ms)) + { + got_task = !ins_atomic_u32_eval_cond_assign(&n->is_working, 1, 0); + break; + } + } + } + + //- rjf: get dbg info + RDI_Parsed *rdi = &di_rdi_parsed_nil; + if(got_task && params.dbgi_key.path.size != 0) + { + rdi = di_rdi_from_key(di_scope, ¶ms.dbgi_key, max_U64); + } + + //- rjf: hash -> data + String8 data = {0}; + if(got_task) + { + data = hs_data_from_hash(hs_scope, hash); + } + + //- rjf: data * arch * addr * dbg -> decode artifacts + DASM_InstChunkList inst_list = {0}; + String8List inst_strings = {0}; + if(got_task) + { + switch(params.arch) + { + default:{}break; + + //- rjf: x86/x64 decoding + case Architecture_x64: + case Architecture_x86: + { + // rjf: grab context + struct ud udc; + ud_init(&udc); + ud_set_mode(&udc, bit_size_from_arch(params.arch)); + ud_set_pc(&udc, params.vaddr); + ud_set_input_buffer(&udc, data.str, data.size); + ud_set_vendor(&udc, UD_VENDOR_ANY); + ud_set_syntax(&udc, params.syntax == DASM_Syntax_Intel ? UD_SYN_INTEL : UD_SYN_ATT); + + // rjf: disassemble + RDI_SourceFile *last_file = &rdi_nil_element_union.source_file; + RDI_Line *last_line = 0; + for(U64 off = 0; off < data.size;) + { + // rjf: disassemble one instruction + U64 size = ud_disassemble(&udc); + if(size == 0) + { + break; + } + + // rjf: analyze + struct ud_operand *first_op = (struct ud_operand *)ud_insn_opr(&udc, 0); + U64 rel_voff = (first_op != 0 && first_op->type == UD_OP_JIMM) ? ud_syn_rel_target(&udc, first_op) : 0; + U64 jump_dst_vaddr = rel_voff; + + // rjf: push strings derived from voff -> line info + if(params.style_flags & (DASM_StyleFlag_SourceFilesNames|DASM_StyleFlag_SourceLines)) + { + if(rdi != &di_rdi_parsed_nil) + { + U64 voff = (params.vaddr+off) - params.base_vaddr; + U32 unit_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_UnitVMap, voff); + RDI_Unit *unit = rdi_element_from_name_idx(rdi, Units, unit_idx); + RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, unit->line_table_idx); + RDI_ParsedLineTable unit_line_info = {0}; + rdi_parsed_from_line_table(rdi, line_table, &unit_line_info); + U64 line_info_idx = rdi_line_info_idx_from_voff(&unit_line_info, voff); + if(line_info_idx < unit_line_info.count) + { + RDI_Line *line = &unit_line_info.lines[line_info_idx]; + RDI_SourceFile *file = rdi_element_from_name_idx(rdi, SourceFiles, line->file_idx); + String8 file_normalized_full_path = {0}; + file_normalized_full_path.str = rdi_string_from_idx(rdi, file->normal_full_path_string_idx, &file_normalized_full_path.size); + if(file != last_file) + { + if(params.style_flags & DASM_StyleFlag_SourceFilesNames && + file->normal_full_path_string_idx != 0 && file_normalized_full_path.size != 0) + { + String8 inst_string = push_str8f(scratch.arena, "> %S", file_normalized_full_path); + DASM_Inst inst = {u32_from_u64_saturate(off), DASM_InstFlag_Decorative, 0, r1u64(inst_strings.total_size + inst_strings.node_count, + inst_strings.total_size + inst_strings.node_count + inst_string.size)}; + dasm_inst_chunk_list_push(scratch.arena, &inst_list, 1024, &inst); + str8_list_push(scratch.arena, &inst_strings, inst_string); + } + if(params.style_flags & DASM_StyleFlag_SourceFilesNames && file->normal_full_path_string_idx == 0) + { + String8 inst_string = str8_lit(">"); + DASM_Inst inst = {u32_from_u64_saturate(off), DASM_InstFlag_Decorative, 0, r1u64(inst_strings.total_size + inst_strings.node_count, + inst_strings.total_size + inst_strings.node_count + inst_string.size)}; + dasm_inst_chunk_list_push(scratch.arena, &inst_list, 1024, &inst); + str8_list_push(scratch.arena, &inst_strings, inst_string); + } + last_file = file; + } + if(line && line != last_line && file->normal_full_path_string_idx != 0 && + params.style_flags & DASM_StyleFlag_SourceLines && + file_normalized_full_path.size != 0) + { + FileProperties props = os_properties_from_file_path(file_normalized_full_path); + if(props.modified != 0) + { + // TODO(rjf): need redirection path - this may map to a different path on the local machine, + // need frontend to communicate path remapping info to this layer + U128 key = fs_key_from_path(file_normalized_full_path); + TXT_LangKind lang_kind = txt_lang_kind_from_extension(file_normalized_full_path); + U64 endt_us = max_U64; + U128 hash = {0}; + TXT_TextInfo text_info = {0}; + for(;os_now_microseconds() <= endt_us;) + { + text_info = txt_text_info_from_key_lang(txt_scope, key, lang_kind, &hash); + if(!u128_match(hash, u128_zero())) + { + break; + } + } + if(0 < line->line_num && line->line_num < text_info.lines_count) + { + String8 data = hs_data_from_hash(hs_scope, hash); + String8 line_text = str8_skip_chop_whitespace(str8_substr(data, text_info.lines_ranges[line->line_num-1])); + if(line_text.size != 0) + { + String8 inst_string = push_str8f(scratch.arena, "> %S", line_text); + DASM_Inst inst = {u32_from_u64_saturate(off), DASM_InstFlag_Decorative, 0, r1u64(inst_strings.total_size + inst_strings.node_count, + inst_strings.total_size + inst_strings.node_count + inst_string.size)}; + dasm_inst_chunk_list_push(scratch.arena, &inst_list, 1024, &inst); + str8_list_push(scratch.arena, &inst_strings, inst_string); + } + } + } + last_line = line; + } + } + } + } + + // rjf: push + String8 addr_part = {0}; + if(params.style_flags & DASM_StyleFlag_Addresses) + { + addr_part = push_str8f(scratch.arena, "%s0x%016I64x ", rdi != &di_rdi_parsed_nil ? " " : "", params.vaddr+off); + } + String8 code_bytes_part = {0}; + if(params.style_flags & DASM_StyleFlag_CodeBytes) + { + String8List code_bytes_strings = {0}; + str8_list_push(scratch.arena, &code_bytes_strings, str8_lit("{")); + for(U64 byte_idx = 0; byte_idx < size || byte_idx < 16; byte_idx += 1) + { + if(byte_idx < size) + { + str8_list_pushf(scratch.arena, &code_bytes_strings, "%02x%s ", (U32)data.str[off+byte_idx], byte_idx == size-1 ? "}" : ""); + } + else if(byte_idx < 8) + { + str8_list_push(scratch.arena, &code_bytes_strings, str8_lit(" ")); + } + } + str8_list_push(scratch.arena, &code_bytes_strings, str8_lit(" ")); + code_bytes_part = str8_list_join(scratch.arena, &code_bytes_strings, 0); + } + String8 symbol_part = {0}; + if(jump_dst_vaddr != 0 && rdi != &di_rdi_parsed_nil && params.style_flags & DASM_StyleFlag_SymbolNames) + { + RDI_U32 scope_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_ScopeVMap, jump_dst_vaddr-params.base_vaddr); + if(scope_idx != 0) + { + RDI_Scope *scope = rdi_element_from_name_idx(rdi, Scopes, scope_idx); + RDI_U32 procedure_idx = scope->proc_idx; + RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, procedure_idx); + String8 procedure_name = {0}; + procedure_name.str = rdi_string_from_idx(rdi, procedure->name_string_idx, &procedure_name.size); + if(procedure_name.size != 0) + { + symbol_part = push_str8f(scratch.arena, " (%S)", procedure_name); + } + } + } + String8 inst_string = push_str8f(scratch.arena, "%S%S%s%S", addr_part, code_bytes_part, udc.asm_buf, symbol_part); + DASM_Inst inst = {u32_from_u64_saturate(off), 0, rel_voff, r1u64(inst_strings.total_size + inst_strings.node_count, + inst_strings.total_size + inst_strings.node_count + inst_string.size)}; + dasm_inst_chunk_list_push(scratch.arena, &inst_list, 1024, &inst); + str8_list_push(scratch.arena, &inst_strings, inst_string); + + // rjf: increment + off += size; + } + }break; + } + } + + //- rjf: artifacts -> value bundle + Arena *info_arena = 0; + DASM_Info info = {0}; + if(got_task) + { + //- rjf: produce joined text + Arena *text_arena = arena_alloc(); + StringJoin text_join = {0}; + text_join.sep = str8_lit("\n"); + String8 text = str8_list_join(text_arena, &inst_strings, &text_join); + + //- rjf: produce unique key for this disassembly's text + U128 text_key = {0}; + { + U64 hash_data[] = + { + hash.u64[0], + hash.u64[1], + params.vaddr, + (U64)params.arch, + (U64)params.style_flags, + (U64)params.syntax, + (U64)rdi, + 0x4d534144, + }; + text_key = hs_hash_from_data(str8((U8 *)hash_data, sizeof(hash_data))); + } + + //- rjf: submit text data to hash store + U128 text_hash = hs_submit_data(text_key, &text_arena, text); + + //- rjf: produce value bundle + info_arena = arena_alloc(); + info.text_key = text_key; + info.insts = dasm_inst_array_from_chunk_list(info_arena, &inst_list); + } + + //- rjf: commit results to cache + if(got_task) OS_MutexScopeW(stripe->rw_mutex) + { + for(DASM_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(n->hash, hash) && dasm_params_match(&n->params, ¶ms)) + { + n->info_arena = info_arena; + MemoryCopyStruct(&n->info, &info); + if(rdi != &di_rdi_parsed_nil && params.style_flags & (DASM_StyleFlag_SourceLines|DASM_StyleFlag_SourceFilesNames)) + { + n->change_gen = change_gen; + } + else + { + n->change_gen = 0; + } + ins_atomic_u32_eval_assign(&n->is_working, 0); + ins_atomic_u64_inc_eval(&n->load_count); + break; + } + } + } + + txt_scope_close(txt_scope); + di_scope_close(di_scope); + hs_scope_close(hs_scope); + scratch_end(scratch); + } +} + +//////////////////////////////// +//~ rjf: Evictor/Detector Thread + +internal void +dasm_evictor_detector_thread__entry_point(void *p) +{ + ThreadNameF("[dasm] evictor/detector thread"); + for(;;) + { + U64 change_gen = fs_change_gen(); + U64 check_time_us = os_now_microseconds(); + U64 check_time_user_clocks = dasm_user_clock_idx(); + U64 evict_threshold_us = 10*1000000; + U64 retry_threshold_us = 1*1000000; + U64 evict_threshold_user_clocks = 10; + U64 retry_threshold_user_clocks = 10; + for(U64 slot_idx = 0; slot_idx < dasm_shared->slots_count; slot_idx += 1) + { + U64 stripe_idx = slot_idx%dasm_shared->stripes_count; + DASM_Slot *slot = &dasm_shared->slots[slot_idx]; + DASM_Stripe *stripe = &dasm_shared->stripes[stripe_idx]; + B32 slot_has_work = 0; + OS_MutexScopeR(stripe->rw_mutex) + { + for(DASM_Node *n = slot->first; n != 0; n = n->next) + { + if(n->scope_ref_count == 0 && + n->last_time_touched_us+evict_threshold_us <= check_time_us && + n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && + n->load_count != 0 && + n->is_working == 0) + { + slot_has_work = 1; + break; + } + if(n->change_gen != 0 && n->change_gen != change_gen && + n->last_time_requested_us+retry_threshold_us <= check_time_us && + n->last_user_clock_idx_requested+retry_threshold_user_clocks <= check_time_user_clocks) + { + slot_has_work = 1; + break; + } + } + } + if(slot_has_work) OS_MutexScopeW(stripe->rw_mutex) + { + for(DASM_Node *n = slot->first, *next = 0; n != 0; n = next) + { + next = n->next; + if(n->scope_ref_count == 0 && + n->last_time_touched_us+evict_threshold_us <= check_time_us && + n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && + n->load_count != 0 && + n->is_working == 0) + { + DLLRemove(slot->first, slot->last, n); + if(n->info_arena != 0) + { + arena_release(n->info_arena); + } + SLLStackPush(stripe->free_node, n); + } + if(n->change_gen != 0 && n->change_gen != change_gen && + n->last_time_requested_us+retry_threshold_us <= check_time_us && + n->last_user_clock_idx_requested+retry_threshold_user_clocks <= check_time_user_clocks) + { + if(dasm_u2p_enqueue_req(n->hash, &n->params, max_U64)) + { + n->last_time_requested_us = os_now_microseconds(); + n->last_user_clock_idx_requested = check_time_user_clocks; + } + } + } + } + } + os_sleep_milliseconds(100); + } +} diff --git a/src/dbgi/dbgi.c b/src/dbgi/dbgi.c index 340d67fe..5c087c2b 100644 --- a/src/dbgi/dbgi.c +++ b/src/dbgi/dbgi.c @@ -1,883 +1,883 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Basic Helpers - -internal U64 -di_hash_from_string(String8 string, StringMatchFlags match_flags) -{ - U64 result = 5381; - for(U64 i = 0; i < string.size; i += 1) - { - result = ((result << 5) + result) + ((match_flags & StringMatchFlag_CaseInsensitive) ? char_to_lower(string.str[i]) : string.str[i]); - } - return result; -} - -internal U64 -di_hash_from_key(DI_Key *k) -{ - U64 hash = di_hash_from_string(k->path, StringMatchFlag_CaseInsensitive); - return hash; -} - -internal DI_Key -di_key_zero(void) -{ - DI_Key key = {0}; - return key; -} - -internal B32 -di_key_match(DI_Key *a, DI_Key *b) -{ - return (str8_match(a->path, b->path, StringMatchFlag_CaseInsensitive) && a->min_timestamp == b->min_timestamp); -} - -internal DI_Key -di_key_copy(Arena *arena, DI_Key *src) -{ - DI_Key dst = {0}; - MemoryCopyStruct(&dst, src); - dst.path = push_str8_copy(arena, src->path); - return dst; -} - -internal DI_Key -di_normalized_key_from_key(Arena *arena, DI_Key *src) -{ - DI_Key dst = {path_normalized_from_string(arena, src->path), src->min_timestamp}; - return dst; -} - -internal void -di_key_list_push(Arena *arena, DI_KeyList *list, DI_Key *key) -{ - DI_KeyNode *n = push_array(arena, DI_KeyNode, 1); - MemoryCopyStruct(&n->v, key); - SLLQueuePush(list->first, list->last, n); - list->count += 1; -} - -internal DI_KeyArray -di_key_array_from_list(Arena *arena, DI_KeyList *list) -{ - DI_KeyArray array = {0}; - array.count = list->count; - array.v = push_array_no_zero(arena, DI_Key, array.count); - U64 idx = 0; - for(DI_KeyNode *n = list->first; n != 0; n = n->next, idx += 1) - { - MemoryCopyStruct(&array.v[idx], &n->v); - } - return array; -} - -//////////////////////////////// -//~ rjf: Main Layer Initialization - -internal void -di_init(void) -{ - Arena *arena = arena_alloc(); - di_shared = push_array(arena, DI_Shared, 1); - di_shared->arena = arena; - di_shared->slots_count = 1024; - di_shared->slots = push_array(arena, DI_Slot, di_shared->slots_count); - di_shared->stripes_count = Min(di_shared->slots_count, os_logical_core_count()); - di_shared->stripes = push_array(arena, DI_Stripe, di_shared->stripes_count); - for(U64 idx = 0; idx < di_shared->stripes_count; idx += 1) - { - di_shared->stripes[idx].arena = arena_alloc(); - di_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); - di_shared->stripes[idx].cv = os_condition_variable_alloc(); - } - di_shared->u2p_ring_mutex = os_mutex_alloc(); - di_shared->u2p_ring_cv = os_condition_variable_alloc(); - di_shared->u2p_ring_size = KB(64); - di_shared->u2p_ring_base = push_array_no_zero(arena, U8, di_shared->u2p_ring_size); - di_shared->p2u_ring_mutex = os_mutex_alloc(); - di_shared->p2u_ring_cv = os_condition_variable_alloc(); - di_shared->p2u_ring_size = KB(64); - di_shared->p2u_ring_base = push_array_no_zero(arena, U8, di_shared->p2u_ring_size); - di_shared->parse_thread_count = Max(2, os_logical_core_count()/2); - di_shared->parse_threads = push_array(arena, OS_Handle, di_shared->parse_thread_count); - for(U64 idx = 0; idx < di_shared->parse_thread_count; idx += 1) - { - di_shared->parse_threads[idx] = os_launch_thread(di_parse_thread__entry_point, (void *)idx, 0); - } -} - -//////////////////////////////// -//~ rjf: Scope Functions - -internal DI_Scope * -di_scope_open(void) -{ - if(di_tctx == 0) - { - Arena *arena = arena_alloc(); - di_tctx = push_array(arena, DI_TCTX, 1); - di_tctx->arena = arena; - } - DI_Scope *scope = di_tctx->free_scope; - if(scope != 0) - { - SLLStackPop(di_tctx->free_scope); - } - else - { - scope = push_array_no_zero(di_tctx->arena, DI_Scope, 1); - } - MemoryZeroStruct(scope); - return scope; -} - -internal void -di_scope_close(DI_Scope *scope) -{ - for(DI_Touch *t = scope->first_touch, *next = 0; t != 0; t = next) - { - next = t->next; - SLLStackPush(di_tctx->free_touch, t); - if(t->node != 0) - { - ins_atomic_u64_dec_eval(&t->node->touch_count); - } - } - SLLStackPush(di_tctx->free_scope, scope); -} - -internal void -di_scope_touch_node__stripe_mutex_r_guarded(DI_Scope *scope, DI_Node *node) -{ - if(node != 0) - { - ins_atomic_u64_inc_eval(&node->touch_count); - } - DI_Touch *touch = di_tctx->free_touch; - if(touch != 0) - { - SLLStackPop(di_tctx->free_touch); - } - else - { - touch = push_array_no_zero(di_tctx->arena, DI_Touch, 1); - } - MemoryZeroStruct(touch); - SLLQueuePush(scope->first_touch, scope->last_touch, touch); - touch->node = node; -} - -//////////////////////////////// -//~ rjf: Per-Slot Functions - -internal DI_Node * -di_node_from_key_slot__stripe_mutex_r_guarded(DI_Slot *slot, DI_Key *key) -{ - DI_Node *node = 0; - StringMatchFlags match_flags = path_match_flags_from_os(operating_system_from_context()); - U64 most_recent_timestamp = max_U64; - for(DI_Node *n = slot->first; n != 0; n = n->next) - { - if(str8_match(n->key.path, key->path, match_flags) && - key->min_timestamp <= n->key.min_timestamp && - (n->key.min_timestamp - key->min_timestamp) <= most_recent_timestamp) - { - node = n; - most_recent_timestamp = (n->key.min_timestamp - key->min_timestamp); - } - } - return node; -} - -//////////////////////////////// -//~ rjf: Per-Stripe Functions - -internal U64 -di_string_bucket_idx_from_string_size(U64 size) -{ - U64 size_rounded = u64_up_to_pow2(size+1); - size_rounded = ClampBot((1<<4), size_rounded); - U64 bucket_idx = 0; - switch(size_rounded) - { - case 1<<4: {bucket_idx = 0;}break; - case 1<<5: {bucket_idx = 1;}break; - case 1<<6: {bucket_idx = 2;}break; - case 1<<7: {bucket_idx = 3;}break; - case 1<<8: {bucket_idx = 4;}break; - case 1<<9: {bucket_idx = 5;}break; - case 1<<10:{bucket_idx = 6;}break; - default:{bucket_idx = ArrayCount(((DI_Stripe *)0)->free_string_chunks)-1;}break; - } - return bucket_idx; -} - -internal String8 -di_string_alloc__stripe_mutex_w_guarded(DI_Stripe *stripe, String8 string) -{ - if(string.size == 0) {return str8_zero();} - U64 bucket_idx = di_string_bucket_idx_from_string_size(string.size); - DI_StringChunkNode *node = stripe->free_string_chunks[bucket_idx]; - - // rjf: pull from bucket free list - if(node != 0) - { - if(bucket_idx == ArrayCount(stripe->free_string_chunks)-1) - { - node = 0; - DI_StringChunkNode *prev = 0; - for(DI_StringChunkNode *n = stripe->free_string_chunks[bucket_idx]; - n != 0; - prev = n, n = n->next) - { - if(n->size >= string.size+1) - { - if(prev == 0) - { - stripe->free_string_chunks[bucket_idx] = n->next; - } - else - { - prev->next = n->next; - } - node = n; - break; - } - } - } - else - { - SLLStackPop(stripe->free_string_chunks[bucket_idx]); - } - } - - // rjf: no found node -> allocate new - if(node == 0) - { - U64 chunk_size = 0; - if(bucket_idx < ArrayCount(stripe->free_string_chunks)-1) - { - chunk_size = 1<<(bucket_idx+4); - } - else - { - chunk_size = u64_up_to_pow2(string.size); - } - U8 *chunk_memory = push_array(stripe->arena, U8, chunk_size); - node = (DI_StringChunkNode *)chunk_memory; - } - - // rjf: fill string & return - String8 allocated_string = str8((U8 *)node, string.size); - MemoryCopy((U8 *)node, string.str, string.size); - return allocated_string; -} - -internal void -di_string_release__stripe_mutex_w_guarded(DI_Stripe *stripe, String8 string) -{ - if(string.size == 0) {return;} - U64 bucket_idx = di_string_bucket_idx_from_string_size(string.size); - DI_StringChunkNode *node = (DI_StringChunkNode *)string.str; - node->size = u64_up_to_pow2(string.size); - SLLStackPush(stripe->free_string_chunks[bucket_idx], node); -} - -//////////////////////////////// -//~ rjf: Key Opening/Closing - -internal void -di_open(DI_Key *key) -{ - Temp scratch = scratch_begin(0, 0); - if(key->path.size != 0) - { - DI_Key key_normalized = di_normalized_key_from_key(scratch.arena, key); - U64 hash = di_hash_from_key(&key_normalized); - U64 slot_idx = hash%di_shared->slots_count; - U64 stripe_idx = slot_idx%di_shared->stripes_count; - DI_Slot *slot = &di_shared->slots[slot_idx]; - DI_Stripe *stripe = &di_shared->stripes[stripe_idx]; - log_infof("open_debug_info: {\"%S\", 0x%I64x}\n", key_normalized.path, key_normalized.min_timestamp); - OS_MutexScopeW(stripe->rw_mutex) - { - //- rjf: find existing node - DI_Node *node = di_node_from_key_slot__stripe_mutex_r_guarded(slot, &key_normalized); - - //- rjf: allocate node if none exists; insert into slot - if(node == 0) - { - U64 current_timestamp = os_properties_from_file_path(key_normalized.path).modified; - if(current_timestamp == 0) - { - current_timestamp = key_normalized.min_timestamp; - } - node = stripe->free_node; - if(node != 0) - { - SLLStackPop(stripe->free_node); - } - else - { - node = push_array_no_zero(stripe->arena, DI_Node, 1); - } - MemoryZeroStruct(node); - DLLPushBack(slot->first, slot->last, node); - String8 path_stored = di_string_alloc__stripe_mutex_w_guarded(stripe, key_normalized.path); - node->key.path = path_stored; - node->key.min_timestamp = current_timestamp; - } - - //- rjf: increment node reference count - if(node != 0) - { - node->ref_count += 1; - if(node->ref_count == 1) - { - di_u2p_enqueue_key(&key_normalized, max_U64); - } - } - } - } - scratch_end(scratch); -} - -internal void -di_close(DI_Key *key) -{ - Temp scratch = scratch_begin(0, 0); - if(key->path.size != 0) - { - DI_Key key_normalized = di_normalized_key_from_key(scratch.arena, key); - U64 hash = di_hash_from_key(&key_normalized); - U64 slot_idx = hash%di_shared->slots_count; - U64 stripe_idx = slot_idx%di_shared->stripes_count; - DI_Slot *slot = &di_shared->slots[slot_idx]; - DI_Stripe *stripe = &di_shared->stripes[stripe_idx]; - log_infof("close_debug_info: {\"%S\", 0x%I64x}\n", key_normalized.path, key_normalized.min_timestamp); - OS_MutexScopeW(stripe->rw_mutex) - { - //- rjf: find existing node - DI_Node *node = di_node_from_key_slot__stripe_mutex_r_guarded(slot, &key_normalized); - - //- rjf: node exists -> decrement reference count; release - if(node != 0) - { - node->ref_count -= 1; - if(node->ref_count == 0) for(;;) - { - //- rjf: wait for touch count to go to 0 - if(ins_atomic_u64_eval(&node->touch_count) != 0) - { - os_rw_mutex_drop_w(stripe->rw_mutex); - for(U64 start_t = os_now_microseconds(); os_now_microseconds() <= start_t + 250;); - os_rw_mutex_take_w(stripe->rw_mutex); - } - - //- rjf: release - if(node->ref_count == 0 && ins_atomic_u64_eval(&node->touch_count) == 0) - { - di_string_release__stripe_mutex_w_guarded(stripe, node->key.path); - if(node->file_base != 0) - { - os_file_map_view_close(node->file_map, node->file_base); - } - if(!os_handle_match(node->file_map, os_handle_zero())) - { - os_file_map_close(node->file_map); - } - if(!os_handle_match(node->file, os_handle_zero())) - { - os_file_close(node->file); - } - if(node->arena != 0) - { - arena_release(node->arena); - } - DLLRemove(slot->first, slot->last, node); - SLLStackPush(stripe->free_node, node); - break; - } - } - } - } - } - scratch_end(scratch); -} - -//////////////////////////////// -//~ rjf: Cache Lookups - -internal RDI_Parsed * -di_rdi_from_key(DI_Scope *scope, DI_Key *key, U64 endt_us) -{ - RDI_Parsed *result = &di_rdi_parsed_nil; - if(key->path.size != 0) - { - Temp scratch = scratch_begin(0, 0); - DI_Key key_normalized = di_normalized_key_from_key(scratch.arena, key); - U64 hash = di_hash_from_key(&key_normalized); - U64 slot_idx = hash%di_shared->slots_count; - U64 stripe_idx = slot_idx%di_shared->stripes_count; - DI_Slot *slot = &di_shared->slots[slot_idx]; - DI_Stripe *stripe = &di_shared->stripes[stripe_idx]; - OS_MutexScopeR(stripe->rw_mutex) for(;;) - { - //- rjf: find existing node - DI_Node *node = di_node_from_key_slot__stripe_mutex_r_guarded(slot, &key_normalized); - - //- rjf: no node? this path is not opened - if(node == 0) - { - break; - } - - //- rjf: parse done -> touch, grab result - if(node != 0 && node->parse_done) - { - di_scope_touch_node__stripe_mutex_r_guarded(scope, node); - result = &node->rdi; - break; - } - - //- rjf: parse not done, not working, asked a while ago -> ask for parse - B32 sent = 0; - if(node != 0 && !node->parse_done && !node->is_working && ins_atomic_u64_eval(&node->last_time_requested_us)+1000000last_time_requested_us, os_now_microseconds()); - } - } - - //- rjf: time expired -> break - if(os_now_microseconds() >= endt_us) - { - break; - } - - //- rjf: wait on this stripe - { - os_condition_variable_wait_rw_r(stripe->cv, stripe->rw_mutex, endt_us); - } - } - scratch_end(scratch); - } - return result; -} - -//////////////////////////////// -//~ rjf: Parse Threads - -internal B32 -di_u2p_enqueue_key(DI_Key *key, U64 endt_us) -{ - B32 sent = 0; - OS_MutexScope(di_shared->u2p_ring_mutex) for(;;) - { - U64 unconsumed_size = di_shared->u2p_ring_write_pos - di_shared->u2p_ring_read_pos; - U64 available_size = di_shared->u2p_ring_size - unconsumed_size; - if(available_size >= sizeof(key->path.size) + key->path.size + sizeof(key->min_timestamp)) - { - di_shared->u2p_ring_write_pos += ring_write_struct(di_shared->u2p_ring_base, di_shared->u2p_ring_size, di_shared->u2p_ring_write_pos, &key->path.size); - di_shared->u2p_ring_write_pos += ring_write(di_shared->u2p_ring_base, di_shared->u2p_ring_size, di_shared->u2p_ring_write_pos, key->path.str, key->path.size); - di_shared->u2p_ring_write_pos += ring_write_struct(di_shared->u2p_ring_base, di_shared->u2p_ring_size, di_shared->u2p_ring_write_pos, &key->min_timestamp); - di_shared->u2p_ring_write_pos += 7; - di_shared->u2p_ring_write_pos -= di_shared->u2p_ring_write_pos%8; - sent = 1; - break; - } - if(os_now_microseconds() >= endt_us) - { - break; - } - os_condition_variable_wait(di_shared->u2p_ring_cv, di_shared->u2p_ring_mutex, endt_us); - } - if(sent) - { - os_condition_variable_broadcast(di_shared->u2p_ring_cv); - } - return sent; -} - -internal void -di_u2p_dequeue_key(Arena *arena, DI_Key *out_key) -{ - OS_MutexScope(di_shared->u2p_ring_mutex) for(;;) - { - U64 unconsumed_size = di_shared->u2p_ring_write_pos - di_shared->u2p_ring_read_pos; - if(unconsumed_size >= sizeof(out_key->path.size) + sizeof(out_key->min_timestamp)) - { - di_shared->u2p_ring_read_pos += ring_read_struct(di_shared->u2p_ring_base, di_shared->u2p_ring_size, di_shared->u2p_ring_read_pos, &out_key->path.size); - out_key->path.str = push_array(arena, U8, out_key->path.size); - di_shared->u2p_ring_read_pos += ring_read(di_shared->u2p_ring_base, di_shared->u2p_ring_size, di_shared->u2p_ring_read_pos, out_key->path.str, out_key->path.size); - di_shared->u2p_ring_read_pos += ring_read_struct(di_shared->u2p_ring_base, di_shared->u2p_ring_size, di_shared->u2p_ring_read_pos, &out_key->min_timestamp); - di_shared->u2p_ring_read_pos += 7; - di_shared->u2p_ring_read_pos -= di_shared->u2p_ring_read_pos%8; - break; - } - os_condition_variable_wait(di_shared->u2p_ring_cv, di_shared->u2p_ring_mutex, max_U64); - } - os_condition_variable_broadcast(di_shared->u2p_ring_cv); -} - -internal void -di_p2u_push_event(DI_Event *event) -{ - OS_MutexScope(di_shared->p2u_ring_mutex) for(;;) - { - U64 unconsumed_size = (di_shared->p2u_ring_write_pos-di_shared->p2u_ring_read_pos); - U64 available_size = di_shared->p2u_ring_size-unconsumed_size; - U64 needed_size = sizeof(DI_EventKind) + sizeof(U64) + event->string.size; - if(available_size >= needed_size) - { - di_shared->p2u_ring_write_pos += ring_write_struct(di_shared->p2u_ring_base, di_shared->p2u_ring_size, di_shared->p2u_ring_write_pos, &event->kind); - di_shared->p2u_ring_write_pos += ring_write_struct(di_shared->p2u_ring_base, di_shared->p2u_ring_size, di_shared->p2u_ring_write_pos, &event->string.size); - di_shared->p2u_ring_write_pos += ring_write(di_shared->p2u_ring_base, di_shared->p2u_ring_size, di_shared->p2u_ring_write_pos, event->string.str, event->string.size); - di_shared->p2u_ring_write_pos += 7; - di_shared->p2u_ring_write_pos -= di_shared->p2u_ring_write_pos%8; - break; - } - os_condition_variable_wait(di_shared->p2u_ring_cv, di_shared->p2u_ring_mutex, max_U64); - } - os_condition_variable_broadcast(di_shared->p2u_ring_cv); -} - -internal DI_EventList -di_p2u_pop_events(Arena *arena, U64 endt_us) -{ - DI_EventList events = {0}; - OS_MutexScope(di_shared->p2u_ring_mutex) for(;;) - { - U64 unconsumed_size = (di_shared->p2u_ring_write_pos-di_shared->p2u_ring_read_pos); - if(unconsumed_size >= sizeof(DI_EventKind) + sizeof(U64)) - { - DI_EventNode *n = push_array(arena, DI_EventNode, 1); - SLLQueuePush(events.first, events.last, n); - events.count += 1; - di_shared->p2u_ring_read_pos += ring_read_struct(di_shared->p2u_ring_base, di_shared->p2u_ring_size, di_shared->p2u_ring_read_pos, &n->v.kind); - di_shared->p2u_ring_read_pos += ring_read_struct(di_shared->p2u_ring_base, di_shared->p2u_ring_size, di_shared->p2u_ring_read_pos, &n->v.string.size); - n->v.string.str = push_array_no_zero(arena, U8, n->v.string.size); - di_shared->p2u_ring_read_pos += ring_read(di_shared->p2u_ring_base, di_shared->p2u_ring_size, di_shared->p2u_ring_read_pos, n->v.string.str, n->v.string.size); - di_shared->p2u_ring_read_pos += 7; - di_shared->p2u_ring_read_pos -= di_shared->p2u_ring_read_pos%8; - } - else if(os_now_microseconds() >= endt_us) - { - break; - } - os_condition_variable_wait(di_shared->p2u_ring_cv, di_shared->p2u_ring_mutex, endt_us); - } - os_condition_variable_broadcast(di_shared->p2u_ring_cv); - return events; -} - -internal void -di_parse_thread__entry_point(void *p) -{ - ThreadNameF("[di] parse #%I64u", (U64)p); - for(;;) - { - Temp scratch = scratch_begin(0, 0); - - //////////////////////////// - //- rjf: grab next key - // - DI_Key key = {0}; - di_u2p_dequeue_key(scratch.arena, &key); - String8 og_path = key.path; - U64 min_timestamp = key.min_timestamp; - - //////////////////////////// - //- rjf: unpack key - // - U64 hash = di_hash_from_string(og_path, StringMatchFlag_CaseInsensitive); - U64 slot_idx = hash%di_shared->slots_count; - U64 stripe_idx = slot_idx%di_shared->stripes_count; - DI_Slot *slot = &di_shared->slots[slot_idx]; - DI_Stripe *stripe = &di_shared->stripes[stripe_idx]; - - //////////////////////////// - //- rjf: take task - // - B32 got_task = 0; - OS_MutexScopeR(stripe->rw_mutex) - { - DI_Node *node = di_node_from_key_slot__stripe_mutex_r_guarded(slot, &key); - if(node != 0) - { - got_task = !ins_atomic_u64_eval_cond_assign(&node->is_working, 1, 0); - } - } - - //////////////////////////// - //- rjf: got task -> open O.G. file (may or may not be RDI) - // - B32 og_format_is_known = 0; - B32 og_is_pe = 0; - B32 og_is_pdb = 0; - B32 og_is_elf = 0; - B32 og_is_rdi = 0; - FileProperties og_props = {0}; - if(got_task) ProfScope("analyze %.*s", str8_varg(og_path)) - { - OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead, og_path); - OS_Handle file_map = os_file_map_open(OS_AccessFlag_Read, file); - FileProperties props = og_props = os_properties_from_file(file); - void *base = os_file_map_view_open(file_map, OS_AccessFlag_Read, r1u64(0, props.size)); - String8 data = str8((U8 *)base, props.size); - if(!og_format_is_known) - { - String8 msf20_magic = str8_lit("Microsoft C/C++ program database 2.00\r\n\x1aJG\0\0"); - String8 msf70_magic = str8_lit("Microsoft C/C++ MSF 7.00\r\n\032DS\0\0"); - String8 msfxx_magic = str8_lit("Microsoft C/C++"); - if((data.size >= msf20_magic.size && str8_match(data, msf20_magic, StringMatchFlag_RightSideSloppy)) || - (data.size >= msf70_magic.size && str8_match(data, msf70_magic, StringMatchFlag_RightSideSloppy)) || - (data.size >= msfxx_magic.size && str8_match(data, msfxx_magic, StringMatchFlag_RightSideSloppy))) - { - og_format_is_known = 1; - og_is_pdb = 1; - } - } - if(!og_format_is_known) - { - if(data.size >= 8 && *(U64 *)data.str == RDI_MAGIC_CONSTANT) - { - og_format_is_known = 1; - og_is_rdi = 1; - } - } - if(!og_format_is_known) - { - if(data.size >= 4 && - data.str[0] == 0x7f && - data.str[1] == 'E' && - data.str[2] == 'L' && - data.str[3] == 'F') - { - og_format_is_known = 1; - og_is_elf = 1; - } - } - if(!og_format_is_known) - { - if(data.size >= 2 && *(U16 *)data.str == 0x5a4d) - { - og_format_is_known = 1; - og_is_pe = 1; - } - } - os_file_map_view_close(file_map, base); - os_file_map_close(file_map); - os_file_close(file); - } - - //////////////////////////// - //- rjf: given O.G. path & analysis, determine RDI path - // - String8 rdi_path = {0}; - if(got_task) - { - if(og_is_rdi) - { - rdi_path = og_path; - } - else if(og_format_is_known && og_is_pdb) - { - rdi_path = push_str8f(scratch.arena, "%S.rdi", str8_chop_last_dot(og_path)); - } - } - - //////////////////////////// - //- rjf: check if rdi file is up-to-date - // - B32 rdi_file_is_up_to_date = 0; - if(got_task) - { - if(rdi_path.size != 0) ProfScope("check %.*s is up-to-date", str8_varg(rdi_path)) - { - FileProperties props = os_properties_from_file_path(rdi_path); - rdi_file_is_up_to_date = (props.modified > og_props.modified); - } - } - - //////////////////////////// - //- rjf: if raddbg file is up to date based on timestamp, check the - // encoding generation number & size, to see if we need to regenerate it - // regardless - // - if(got_task && rdi_file_is_up_to_date) ProfScope("check %.*s version matches our's", str8_varg(rdi_path)) - { - OS_Handle file = {0}; - OS_Handle file_map = {0}; - FileProperties file_props = {0}; - void *file_base = 0; - file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead, rdi_path); - file_map = os_file_map_open(OS_AccessFlag_Read, file); - file_props = os_properties_from_file(file); - file_base = os_file_map_view_open(file_map, OS_AccessFlag_Read, r1u64(0, file_props.size)); - if(sizeof(RDI_Header) <= file_props.size) - { - RDI_Header *header = (RDI_Header*)file_base; - if(header->encoding_version != RDI_ENCODING_VERSION) - { - rdi_file_is_up_to_date = 0; - } - } - else - { - rdi_file_is_up_to_date = 0; - } - os_file_map_view_close(file_map, file_base); - os_file_map_close(file_map); - os_file_close(file); - } - - //////////////////////////// - //- rjf: heuristically choose compression settings - // - B32 should_compress = 0; -#if 0 - if(og_dbg_props.size > MB(64)) - { - should_compress = 1; - } -#endif - - //////////////////////////// - //- rjf: rdi file not up-to-date? we need to generate it - // - if(got_task && !rdi_file_is_up_to_date) ProfScope("generate %.*s", str8_varg(rdi_path)) - { - if(og_is_pdb) - { - //- rjf: push conversion task begin event - { - DI_Event event = {DI_EventKind_ConversionStarted}; - event.string = rdi_path; - di_p2u_push_event(&event); - } - - //- rjf: kick off process - OS_Handle process = {0}; - { - OS_LaunchOptions opts = {0}; - opts.path = os_string_from_system_path(scratch.arena, OS_SystemPath_Binary); - opts.inherit_env = 1; - opts.consoleless = 1; - str8_list_pushf(scratch.arena, &opts.cmd_line, "raddbg"); - str8_list_pushf(scratch.arena, &opts.cmd_line, "--convert"); - str8_list_pushf(scratch.arena, &opts.cmd_line, "--quiet"); - if(should_compress) - { - str8_list_pushf(scratch.arena, &opts.cmd_line, "--compress"); - } - //str8_list_pushf(scratch.arena, &opts.cmd_line, "--capture"); - str8_list_pushf(scratch.arena, &opts.cmd_line, "--pdb:%S", og_path); - str8_list_pushf(scratch.arena, &opts.cmd_line, "--out:%S", rdi_path); - os_launch_process(&opts, &process); - } - - //- rjf: wait for process to complete - { - U64 start_wait_t = os_now_microseconds(); - for(;;) - { - B32 wait_done = os_process_wait(process, os_now_microseconds()+1000); - if(wait_done) - { - rdi_file_is_up_to_date = 1; - break; - } - } - } - - //- rjf: push conversion task end event - { - DI_Event event = {DI_EventKind_ConversionEnded}; - event.string = rdi_path; - di_p2u_push_event(&event); - } - } - else - { - // NOTE(rjf): we cannot convert from this O.G. debug info format right now. - //- rjf: push conversion task failure event - { - DI_Event event = {DI_EventKind_ConversionFailureUnsupportedFormat}; - event.string = rdi_path; - di_p2u_push_event(&event); - } - } - } - - //////////////////////////// - //- rjf: got task -> open file - // - OS_Handle file = {0}; - OS_Handle file_map = {0}; - FileProperties file_props = {0}; - void *file_base = 0; - if(got_task) - { - file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead|OS_AccessFlag_ShareWrite, rdi_path); - file_map = os_file_map_open(OS_AccessFlag_Read, file); - file_props = os_properties_from_file(file); - file_base = os_file_map_view_open(file_map, OS_AccessFlag_Read, r1u64(0, file_props.size)); - } - - //////////////////////////// - //- rjf: do initial parse of rdi - // - RDI_Parsed rdi_parsed_maybe_compressed = di_rdi_parsed_nil; - if(got_task) - { - RDI_ParseStatus parse_status = rdi_parse((U8 *)file_base, file_props.size, &rdi_parsed_maybe_compressed); - (void)parse_status; - } - - //////////////////////////// - //- rjf: decompress & re-parse, if necessary - // - Arena *rdi_parsed_arena = 0; - RDI_Parsed rdi_parsed = rdi_parsed_maybe_compressed; - if(got_task) - { - U64 decompressed_size = rdi_decompressed_size_from_parsed(&rdi_parsed_maybe_compressed); - if(decompressed_size > file_props.size) - { - rdi_parsed_arena = arena_alloc(); - U8 *decompressed_data = push_array_no_zero(rdi_parsed_arena, U8, decompressed_size); - rdi_decompress_parsed(decompressed_data, decompressed_size, &rdi_parsed_maybe_compressed); - RDI_ParseStatus parse_status = rdi_parse(decompressed_data, decompressed_size, &rdi_parsed); - (void)parse_status; - } - } - - //////////////////////////// - //- rjf: commit parsed info to cache - // - if(got_task) OS_MutexScopeW(stripe->rw_mutex) - { - DI_Node *node = di_node_from_key_slot__stripe_mutex_r_guarded(slot, &key); - if(node != 0) - { - node->is_working = 0; - node->file = file; - node->file_map = file_map; - node->file_base = file_base; - node->file_props = file_props; - node->arena = rdi_parsed_arena; - node->rdi = rdi_parsed; - node->parse_done = 1; - } - } - os_condition_variable_broadcast(stripe->cv); - - scratch_end(scratch); - } -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Basic Helpers + +internal U64 +di_hash_from_string(String8 string, StringMatchFlags match_flags) +{ + U64 result = 5381; + for(U64 i = 0; i < string.size; i += 1) + { + result = ((result << 5) + result) + ((match_flags & StringMatchFlag_CaseInsensitive) ? char_to_lower(string.str[i]) : string.str[i]); + } + return result; +} + +internal U64 +di_hash_from_key(DI_Key *k) +{ + U64 hash = di_hash_from_string(k->path, StringMatchFlag_CaseInsensitive); + return hash; +} + +internal DI_Key +di_key_zero(void) +{ + DI_Key key = {0}; + return key; +} + +internal B32 +di_key_match(DI_Key *a, DI_Key *b) +{ + return (str8_match(a->path, b->path, StringMatchFlag_CaseInsensitive) && a->min_timestamp == b->min_timestamp); +} + +internal DI_Key +di_key_copy(Arena *arena, DI_Key *src) +{ + DI_Key dst = {0}; + MemoryCopyStruct(&dst, src); + dst.path = push_str8_copy(arena, src->path); + return dst; +} + +internal DI_Key +di_normalized_key_from_key(Arena *arena, DI_Key *src) +{ + DI_Key dst = {path_normalized_from_string(arena, src->path), src->min_timestamp}; + return dst; +} + +internal void +di_key_list_push(Arena *arena, DI_KeyList *list, DI_Key *key) +{ + DI_KeyNode *n = push_array(arena, DI_KeyNode, 1); + MemoryCopyStruct(&n->v, key); + SLLQueuePush(list->first, list->last, n); + list->count += 1; +} + +internal DI_KeyArray +di_key_array_from_list(Arena *arena, DI_KeyList *list) +{ + DI_KeyArray array = {0}; + array.count = list->count; + array.v = push_array_no_zero(arena, DI_Key, array.count); + U64 idx = 0; + for(DI_KeyNode *n = list->first; n != 0; n = n->next, idx += 1) + { + MemoryCopyStruct(&array.v[idx], &n->v); + } + return array; +} + +//////////////////////////////// +//~ rjf: Main Layer Initialization + +internal void +di_init(void) +{ + Arena *arena = arena_alloc(); + di_shared = push_array(arena, DI_Shared, 1); + di_shared->arena = arena; + di_shared->slots_count = 1024; + di_shared->slots = push_array(arena, DI_Slot, di_shared->slots_count); + di_shared->stripes_count = Min(di_shared->slots_count, os_get_system_info()->logical_processor_count); + di_shared->stripes = push_array(arena, DI_Stripe, di_shared->stripes_count); + for(U64 idx = 0; idx < di_shared->stripes_count; idx += 1) + { + di_shared->stripes[idx].arena = arena_alloc(); + di_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); + di_shared->stripes[idx].cv = os_condition_variable_alloc(); + } + di_shared->u2p_ring_mutex = os_mutex_alloc(); + di_shared->u2p_ring_cv = os_condition_variable_alloc(); + di_shared->u2p_ring_size = KB(64); + di_shared->u2p_ring_base = push_array_no_zero(arena, U8, di_shared->u2p_ring_size); + di_shared->p2u_ring_mutex = os_mutex_alloc(); + di_shared->p2u_ring_cv = os_condition_variable_alloc(); + di_shared->p2u_ring_size = KB(64); + di_shared->p2u_ring_base = push_array_no_zero(arena, U8, di_shared->p2u_ring_size); + di_shared->parse_thread_count = Max(2, os_get_system_info()->logical_processor_count/2); + di_shared->parse_threads = push_array(arena, OS_Handle, di_shared->parse_thread_count); + for(U64 idx = 0; idx < di_shared->parse_thread_count; idx += 1) + { + di_shared->parse_threads[idx] = os_thread_launch(di_parse_thread__entry_point, (void *)idx, 0); + } +} + +//////////////////////////////// +//~ rjf: Scope Functions + +internal DI_Scope * +di_scope_open(void) +{ + if(di_tctx == 0) + { + Arena *arena = arena_alloc(); + di_tctx = push_array(arena, DI_TCTX, 1); + di_tctx->arena = arena; + } + DI_Scope *scope = di_tctx->free_scope; + if(scope != 0) + { + SLLStackPop(di_tctx->free_scope); + } + else + { + scope = push_array_no_zero(di_tctx->arena, DI_Scope, 1); + } + MemoryZeroStruct(scope); + return scope; +} + +internal void +di_scope_close(DI_Scope *scope) +{ + for(DI_Touch *t = scope->first_touch, *next = 0; t != 0; t = next) + { + next = t->next; + SLLStackPush(di_tctx->free_touch, t); + if(t->node != 0) + { + ins_atomic_u64_dec_eval(&t->node->touch_count); + } + } + SLLStackPush(di_tctx->free_scope, scope); +} + +internal void +di_scope_touch_node__stripe_mutex_r_guarded(DI_Scope *scope, DI_Node *node) +{ + if(node != 0) + { + ins_atomic_u64_inc_eval(&node->touch_count); + } + DI_Touch *touch = di_tctx->free_touch; + if(touch != 0) + { + SLLStackPop(di_tctx->free_touch); + } + else + { + touch = push_array_no_zero(di_tctx->arena, DI_Touch, 1); + } + MemoryZeroStruct(touch); + SLLQueuePush(scope->first_touch, scope->last_touch, touch); + touch->node = node; +} + +//////////////////////////////// +//~ rjf: Per-Slot Functions + +internal DI_Node * +di_node_from_key_slot__stripe_mutex_r_guarded(DI_Slot *slot, DI_Key *key) +{ + DI_Node *node = 0; + StringMatchFlags match_flags = path_match_flags_from_os(operating_system_from_context()); + U64 most_recent_timestamp = max_U64; + for(DI_Node *n = slot->first; n != 0; n = n->next) + { + if(str8_match(n->key.path, key->path, match_flags) && + key->min_timestamp <= n->key.min_timestamp && + (n->key.min_timestamp - key->min_timestamp) <= most_recent_timestamp) + { + node = n; + most_recent_timestamp = (n->key.min_timestamp - key->min_timestamp); + } + } + return node; +} + +//////////////////////////////// +//~ rjf: Per-Stripe Functions + +internal U64 +di_string_bucket_idx_from_string_size(U64 size) +{ + U64 size_rounded = u64_up_to_pow2(size+1); + size_rounded = ClampBot((1<<4), size_rounded); + U64 bucket_idx = 0; + switch(size_rounded) + { + case 1<<4: {bucket_idx = 0;}break; + case 1<<5: {bucket_idx = 1;}break; + case 1<<6: {bucket_idx = 2;}break; + case 1<<7: {bucket_idx = 3;}break; + case 1<<8: {bucket_idx = 4;}break; + case 1<<9: {bucket_idx = 5;}break; + case 1<<10:{bucket_idx = 6;}break; + default:{bucket_idx = ArrayCount(((DI_Stripe *)0)->free_string_chunks)-1;}break; + } + return bucket_idx; +} + +internal String8 +di_string_alloc__stripe_mutex_w_guarded(DI_Stripe *stripe, String8 string) +{ + if(string.size == 0) {return str8_zero();} + U64 bucket_idx = di_string_bucket_idx_from_string_size(string.size); + DI_StringChunkNode *node = stripe->free_string_chunks[bucket_idx]; + + // rjf: pull from bucket free list + if(node != 0) + { + if(bucket_idx == ArrayCount(stripe->free_string_chunks)-1) + { + node = 0; + DI_StringChunkNode *prev = 0; + for(DI_StringChunkNode *n = stripe->free_string_chunks[bucket_idx]; + n != 0; + prev = n, n = n->next) + { + if(n->size >= string.size+1) + { + if(prev == 0) + { + stripe->free_string_chunks[bucket_idx] = n->next; + } + else + { + prev->next = n->next; + } + node = n; + break; + } + } + } + else + { + SLLStackPop(stripe->free_string_chunks[bucket_idx]); + } + } + + // rjf: no found node -> allocate new + if(node == 0) + { + U64 chunk_size = 0; + if(bucket_idx < ArrayCount(stripe->free_string_chunks)-1) + { + chunk_size = 1<<(bucket_idx+4); + } + else + { + chunk_size = u64_up_to_pow2(string.size); + } + U8 *chunk_memory = push_array(stripe->arena, U8, chunk_size); + node = (DI_StringChunkNode *)chunk_memory; + } + + // rjf: fill string & return + String8 allocated_string = str8((U8 *)node, string.size); + MemoryCopy((U8 *)node, string.str, string.size); + return allocated_string; +} + +internal void +di_string_release__stripe_mutex_w_guarded(DI_Stripe *stripe, String8 string) +{ + if(string.size == 0) {return;} + U64 bucket_idx = di_string_bucket_idx_from_string_size(string.size); + DI_StringChunkNode *node = (DI_StringChunkNode *)string.str; + node->size = u64_up_to_pow2(string.size); + SLLStackPush(stripe->free_string_chunks[bucket_idx], node); +} + +//////////////////////////////// +//~ rjf: Key Opening/Closing + +internal void +di_open(DI_Key *key) +{ + Temp scratch = scratch_begin(0, 0); + if(key->path.size != 0) + { + DI_Key key_normalized = di_normalized_key_from_key(scratch.arena, key); + U64 hash = di_hash_from_key(&key_normalized); + U64 slot_idx = hash%di_shared->slots_count; + U64 stripe_idx = slot_idx%di_shared->stripes_count; + DI_Slot *slot = &di_shared->slots[slot_idx]; + DI_Stripe *stripe = &di_shared->stripes[stripe_idx]; + log_infof("open_debug_info: {\"%S\", 0x%I64x}\n", key_normalized.path, key_normalized.min_timestamp); + OS_MutexScopeW(stripe->rw_mutex) + { + //- rjf: find existing node + DI_Node *node = di_node_from_key_slot__stripe_mutex_r_guarded(slot, &key_normalized); + + //- rjf: allocate node if none exists; insert into slot + if(node == 0) + { + U64 current_timestamp = os_properties_from_file_path(key_normalized.path).modified; + if(current_timestamp == 0) + { + current_timestamp = key_normalized.min_timestamp; + } + node = stripe->free_node; + if(node != 0) + { + SLLStackPop(stripe->free_node); + } + else + { + node = push_array_no_zero(stripe->arena, DI_Node, 1); + } + MemoryZeroStruct(node); + DLLPushBack(slot->first, slot->last, node); + String8 path_stored = di_string_alloc__stripe_mutex_w_guarded(stripe, key_normalized.path); + node->key.path = path_stored; + node->key.min_timestamp = current_timestamp; + } + + //- rjf: increment node reference count + if(node != 0) + { + node->ref_count += 1; + if(node->ref_count == 1) + { + di_u2p_enqueue_key(&key_normalized, max_U64); + } + } + } + } + scratch_end(scratch); +} + +internal void +di_close(DI_Key *key) +{ + Temp scratch = scratch_begin(0, 0); + if(key->path.size != 0) + { + DI_Key key_normalized = di_normalized_key_from_key(scratch.arena, key); + U64 hash = di_hash_from_key(&key_normalized); + U64 slot_idx = hash%di_shared->slots_count; + U64 stripe_idx = slot_idx%di_shared->stripes_count; + DI_Slot *slot = &di_shared->slots[slot_idx]; + DI_Stripe *stripe = &di_shared->stripes[stripe_idx]; + log_infof("close_debug_info: {\"%S\", 0x%I64x}\n", key_normalized.path, key_normalized.min_timestamp); + OS_MutexScopeW(stripe->rw_mutex) + { + //- rjf: find existing node + DI_Node *node = di_node_from_key_slot__stripe_mutex_r_guarded(slot, &key_normalized); + + //- rjf: node exists -> decrement reference count; release + if(node != 0) + { + node->ref_count -= 1; + if(node->ref_count == 0) for(;;) + { + //- rjf: wait for touch count to go to 0 + if(ins_atomic_u64_eval(&node->touch_count) != 0) + { + os_rw_mutex_drop_w(stripe->rw_mutex); + for(U64 start_t = os_now_microseconds(); os_now_microseconds() <= start_t + 250;); + os_rw_mutex_take_w(stripe->rw_mutex); + } + + //- rjf: release + if(node->ref_count == 0 && ins_atomic_u64_eval(&node->touch_count) == 0) + { + di_string_release__stripe_mutex_w_guarded(stripe, node->key.path); + if(node->file_base != 0) + { + os_file_map_view_close(node->file_map, node->file_base); + } + if(!os_handle_match(node->file_map, os_handle_zero())) + { + os_file_map_close(node->file_map); + } + if(!os_handle_match(node->file, os_handle_zero())) + { + os_file_close(node->file); + } + if(node->arena != 0) + { + arena_release(node->arena); + } + DLLRemove(slot->first, slot->last, node); + SLLStackPush(stripe->free_node, node); + break; + } + } + } + } + } + scratch_end(scratch); +} + +//////////////////////////////// +//~ rjf: Cache Lookups + +internal RDI_Parsed * +di_rdi_from_key(DI_Scope *scope, DI_Key *key, U64 endt_us) +{ + RDI_Parsed *result = &di_rdi_parsed_nil; + if(key->path.size != 0) + { + Temp scratch = scratch_begin(0, 0); + DI_Key key_normalized = di_normalized_key_from_key(scratch.arena, key); + U64 hash = di_hash_from_key(&key_normalized); + U64 slot_idx = hash%di_shared->slots_count; + U64 stripe_idx = slot_idx%di_shared->stripes_count; + DI_Slot *slot = &di_shared->slots[slot_idx]; + DI_Stripe *stripe = &di_shared->stripes[stripe_idx]; + OS_MutexScopeR(stripe->rw_mutex) for(;;) + { + //- rjf: find existing node + DI_Node *node = di_node_from_key_slot__stripe_mutex_r_guarded(slot, &key_normalized); + + //- rjf: no node? this path is not opened + if(node == 0) + { + break; + } + + //- rjf: parse done -> touch, grab result + if(node != 0 && node->parse_done) + { + di_scope_touch_node__stripe_mutex_r_guarded(scope, node); + result = &node->rdi; + break; + } + + //- rjf: parse not done, not working, asked a while ago -> ask for parse + B32 sent = 0; + if(node != 0 && !node->parse_done && !node->is_working && ins_atomic_u64_eval(&node->last_time_requested_us)+1000000last_time_requested_us, os_now_microseconds()); + } + } + + //- rjf: time expired -> break + if(os_now_microseconds() >= endt_us) + { + break; + } + + //- rjf: wait on this stripe + { + os_condition_variable_wait_rw_r(stripe->cv, stripe->rw_mutex, endt_us); + } + } + scratch_end(scratch); + } + return result; +} + +//////////////////////////////// +//~ rjf: Parse Threads + +internal B32 +di_u2p_enqueue_key(DI_Key *key, U64 endt_us) +{ + B32 sent = 0; + OS_MutexScope(di_shared->u2p_ring_mutex) for(;;) + { + U64 unconsumed_size = di_shared->u2p_ring_write_pos - di_shared->u2p_ring_read_pos; + U64 available_size = di_shared->u2p_ring_size - unconsumed_size; + if(available_size >= sizeof(key->path.size) + key->path.size + sizeof(key->min_timestamp)) + { + di_shared->u2p_ring_write_pos += ring_write_struct(di_shared->u2p_ring_base, di_shared->u2p_ring_size, di_shared->u2p_ring_write_pos, &key->path.size); + di_shared->u2p_ring_write_pos += ring_write(di_shared->u2p_ring_base, di_shared->u2p_ring_size, di_shared->u2p_ring_write_pos, key->path.str, key->path.size); + di_shared->u2p_ring_write_pos += ring_write_struct(di_shared->u2p_ring_base, di_shared->u2p_ring_size, di_shared->u2p_ring_write_pos, &key->min_timestamp); + di_shared->u2p_ring_write_pos += 7; + di_shared->u2p_ring_write_pos -= di_shared->u2p_ring_write_pos%8; + sent = 1; + break; + } + if(os_now_microseconds() >= endt_us) + { + break; + } + os_condition_variable_wait(di_shared->u2p_ring_cv, di_shared->u2p_ring_mutex, endt_us); + } + if(sent) + { + os_condition_variable_broadcast(di_shared->u2p_ring_cv); + } + return sent; +} + +internal void +di_u2p_dequeue_key(Arena *arena, DI_Key *out_key) +{ + OS_MutexScope(di_shared->u2p_ring_mutex) for(;;) + { + U64 unconsumed_size = di_shared->u2p_ring_write_pos - di_shared->u2p_ring_read_pos; + if(unconsumed_size >= sizeof(out_key->path.size) + sizeof(out_key->min_timestamp)) + { + di_shared->u2p_ring_read_pos += ring_read_struct(di_shared->u2p_ring_base, di_shared->u2p_ring_size, di_shared->u2p_ring_read_pos, &out_key->path.size); + out_key->path.str = push_array(arena, U8, out_key->path.size); + di_shared->u2p_ring_read_pos += ring_read(di_shared->u2p_ring_base, di_shared->u2p_ring_size, di_shared->u2p_ring_read_pos, out_key->path.str, out_key->path.size); + di_shared->u2p_ring_read_pos += ring_read_struct(di_shared->u2p_ring_base, di_shared->u2p_ring_size, di_shared->u2p_ring_read_pos, &out_key->min_timestamp); + di_shared->u2p_ring_read_pos += 7; + di_shared->u2p_ring_read_pos -= di_shared->u2p_ring_read_pos%8; + break; + } + os_condition_variable_wait(di_shared->u2p_ring_cv, di_shared->u2p_ring_mutex, max_U64); + } + os_condition_variable_broadcast(di_shared->u2p_ring_cv); +} + +internal void +di_p2u_push_event(DI_Event *event) +{ + OS_MutexScope(di_shared->p2u_ring_mutex) for(;;) + { + U64 unconsumed_size = (di_shared->p2u_ring_write_pos-di_shared->p2u_ring_read_pos); + U64 available_size = di_shared->p2u_ring_size-unconsumed_size; + U64 needed_size = sizeof(DI_EventKind) + sizeof(U64) + event->string.size; + if(available_size >= needed_size) + { + di_shared->p2u_ring_write_pos += ring_write_struct(di_shared->p2u_ring_base, di_shared->p2u_ring_size, di_shared->p2u_ring_write_pos, &event->kind); + di_shared->p2u_ring_write_pos += ring_write_struct(di_shared->p2u_ring_base, di_shared->p2u_ring_size, di_shared->p2u_ring_write_pos, &event->string.size); + di_shared->p2u_ring_write_pos += ring_write(di_shared->p2u_ring_base, di_shared->p2u_ring_size, di_shared->p2u_ring_write_pos, event->string.str, event->string.size); + di_shared->p2u_ring_write_pos += 7; + di_shared->p2u_ring_write_pos -= di_shared->p2u_ring_write_pos%8; + break; + } + os_condition_variable_wait(di_shared->p2u_ring_cv, di_shared->p2u_ring_mutex, max_U64); + } + os_condition_variable_broadcast(di_shared->p2u_ring_cv); +} + +internal DI_EventList +di_p2u_pop_events(Arena *arena, U64 endt_us) +{ + DI_EventList events = {0}; + OS_MutexScope(di_shared->p2u_ring_mutex) for(;;) + { + U64 unconsumed_size = (di_shared->p2u_ring_write_pos-di_shared->p2u_ring_read_pos); + if(unconsumed_size >= sizeof(DI_EventKind) + sizeof(U64)) + { + DI_EventNode *n = push_array(arena, DI_EventNode, 1); + SLLQueuePush(events.first, events.last, n); + events.count += 1; + di_shared->p2u_ring_read_pos += ring_read_struct(di_shared->p2u_ring_base, di_shared->p2u_ring_size, di_shared->p2u_ring_read_pos, &n->v.kind); + di_shared->p2u_ring_read_pos += ring_read_struct(di_shared->p2u_ring_base, di_shared->p2u_ring_size, di_shared->p2u_ring_read_pos, &n->v.string.size); + n->v.string.str = push_array_no_zero(arena, U8, n->v.string.size); + di_shared->p2u_ring_read_pos += ring_read(di_shared->p2u_ring_base, di_shared->p2u_ring_size, di_shared->p2u_ring_read_pos, n->v.string.str, n->v.string.size); + di_shared->p2u_ring_read_pos += 7; + di_shared->p2u_ring_read_pos -= di_shared->p2u_ring_read_pos%8; + } + else if(os_now_microseconds() >= endt_us) + { + break; + } + os_condition_variable_wait(di_shared->p2u_ring_cv, di_shared->p2u_ring_mutex, endt_us); + } + os_condition_variable_broadcast(di_shared->p2u_ring_cv); + return events; +} + +internal void +di_parse_thread__entry_point(void *p) +{ + ThreadNameF("[di] parse #%I64u", (U64)p); + for(;;) + { + Temp scratch = scratch_begin(0, 0); + + //////////////////////////// + //- rjf: grab next key + // + DI_Key key = {0}; + di_u2p_dequeue_key(scratch.arena, &key); + String8 og_path = key.path; + U64 min_timestamp = key.min_timestamp; + + //////////////////////////// + //- rjf: unpack key + // + U64 hash = di_hash_from_string(og_path, StringMatchFlag_CaseInsensitive); + U64 slot_idx = hash%di_shared->slots_count; + U64 stripe_idx = slot_idx%di_shared->stripes_count; + DI_Slot *slot = &di_shared->slots[slot_idx]; + DI_Stripe *stripe = &di_shared->stripes[stripe_idx]; + + //////////////////////////// + //- rjf: take task + // + B32 got_task = 0; + OS_MutexScopeR(stripe->rw_mutex) + { + DI_Node *node = di_node_from_key_slot__stripe_mutex_r_guarded(slot, &key); + if(node != 0) + { + got_task = !ins_atomic_u64_eval_cond_assign(&node->is_working, 1, 0); + } + } + + //////////////////////////// + //- rjf: got task -> open O.G. file (may or may not be RDI) + // + B32 og_format_is_known = 0; + B32 og_is_pe = 0; + B32 og_is_pdb = 0; + B32 og_is_elf = 0; + B32 og_is_rdi = 0; + FileProperties og_props = {0}; + if(got_task) ProfScope("analyze %.*s", str8_varg(og_path)) + { + OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead, og_path); + OS_Handle file_map = os_file_map_open(OS_AccessFlag_Read, file); + FileProperties props = og_props = os_properties_from_file(file); + void *base = os_file_map_view_open(file_map, OS_AccessFlag_Read, r1u64(0, props.size)); + String8 data = str8((U8 *)base, props.size); + if(!og_format_is_known) + { + String8 msf20_magic = str8_lit("Microsoft C/C++ program database 2.00\r\n\x1aJG\0\0"); + String8 msf70_magic = str8_lit("Microsoft C/C++ MSF 7.00\r\n\032DS\0\0"); + String8 msfxx_magic = str8_lit("Microsoft C/C++"); + if((data.size >= msf20_magic.size && str8_match(data, msf20_magic, StringMatchFlag_RightSideSloppy)) || + (data.size >= msf70_magic.size && str8_match(data, msf70_magic, StringMatchFlag_RightSideSloppy)) || + (data.size >= msfxx_magic.size && str8_match(data, msfxx_magic, StringMatchFlag_RightSideSloppy))) + { + og_format_is_known = 1; + og_is_pdb = 1; + } + } + if(!og_format_is_known) + { + if(data.size >= 8 && *(U64 *)data.str == RDI_MAGIC_CONSTANT) + { + og_format_is_known = 1; + og_is_rdi = 1; + } + } + if(!og_format_is_known) + { + if(data.size >= 4 && + data.str[0] == 0x7f && + data.str[1] == 'E' && + data.str[2] == 'L' && + data.str[3] == 'F') + { + og_format_is_known = 1; + og_is_elf = 1; + } + } + if(!og_format_is_known) + { + if(data.size >= 2 && *(U16 *)data.str == 0x5a4d) + { + og_format_is_known = 1; + og_is_pe = 1; + } + } + os_file_map_view_close(file_map, base); + os_file_map_close(file_map); + os_file_close(file); + } + + //////////////////////////// + //- rjf: given O.G. path & analysis, determine RDI path + // + String8 rdi_path = {0}; + if(got_task) + { + if(og_is_rdi) + { + rdi_path = og_path; + } + else if(og_format_is_known && og_is_pdb) + { + rdi_path = push_str8f(scratch.arena, "%S.rdi", str8_chop_last_dot(og_path)); + } + } + + //////////////////////////// + //- rjf: check if rdi file is up-to-date + // + B32 rdi_file_is_up_to_date = 0; + if(got_task) + { + if(rdi_path.size != 0) ProfScope("check %.*s is up-to-date", str8_varg(rdi_path)) + { + FileProperties props = os_properties_from_file_path(rdi_path); + rdi_file_is_up_to_date = (props.modified > og_props.modified); + } + } + + //////////////////////////// + //- rjf: if raddbg file is up to date based on timestamp, check the + // encoding generation number & size, to see if we need to regenerate it + // regardless + // + if(got_task && rdi_file_is_up_to_date) ProfScope("check %.*s version matches our's", str8_varg(rdi_path)) + { + OS_Handle file = {0}; + OS_Handle file_map = {0}; + FileProperties file_props = {0}; + void *file_base = 0; + file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead, rdi_path); + file_map = os_file_map_open(OS_AccessFlag_Read, file); + file_props = os_properties_from_file(file); + file_base = os_file_map_view_open(file_map, OS_AccessFlag_Read, r1u64(0, file_props.size)); + if(sizeof(RDI_Header) <= file_props.size) + { + RDI_Header *header = (RDI_Header*)file_base; + if(header->encoding_version != RDI_ENCODING_VERSION) + { + rdi_file_is_up_to_date = 0; + } + } + else + { + rdi_file_is_up_to_date = 0; + } + os_file_map_view_close(file_map, file_base); + os_file_map_close(file_map); + os_file_close(file); + } + + //////////////////////////// + //- rjf: heuristically choose compression settings + // + B32 should_compress = 0; +#if 0 + if(og_dbg_props.size > MB(64)) + { + should_compress = 1; + } +#endif + + //////////////////////////// + //- rjf: rdi file not up-to-date? we need to generate it + // + if(got_task && !rdi_file_is_up_to_date) ProfScope("generate %.*s", str8_varg(rdi_path)) + { + if(og_is_pdb) + { + //- rjf: push conversion task begin event + { + DI_Event event = {DI_EventKind_ConversionStarted}; + event.string = rdi_path; + di_p2u_push_event(&event); + } + + //- rjf: kick off process + OS_Handle process = {0}; + { + OS_ProcessLaunchParams params = {0}; + params.path = os_get_process_info()->binary_path; + params.inherit_env = 1; + params.consoleless = 1; + str8_list_pushf(scratch.arena, ¶ms.cmd_line, "raddbg"); + str8_list_pushf(scratch.arena, ¶ms.cmd_line, "--convert"); + str8_list_pushf(scratch.arena, ¶ms.cmd_line, "--quiet"); + if(should_compress) + { + str8_list_pushf(scratch.arena, ¶ms.cmd_line, "--compress"); + } + // str8_list_pushf(scratch.arena, ¶ms.cmd_line, "--capture"); + str8_list_pushf(scratch.arena, ¶ms.cmd_line, "--pdb:%S", og_path); + str8_list_pushf(scratch.arena, ¶ms.cmd_line, "--out:%S", rdi_path); + process = os_process_launch(¶ms); + } + + //- rjf: wait for process to complete + { + U64 start_wait_t = os_now_microseconds(); + for(;;) + { + B32 wait_done = os_process_join(process, os_now_microseconds()+1000); + if(wait_done) + { + rdi_file_is_up_to_date = 1; + break; + } + } + } + + //- rjf: push conversion task end event + { + DI_Event event = {DI_EventKind_ConversionEnded}; + event.string = rdi_path; + di_p2u_push_event(&event); + } + } + else + { + // NOTE(rjf): we cannot convert from this O.G. debug info format right now. + //- rjf: push conversion task failure event + { + DI_Event event = {DI_EventKind_ConversionFailureUnsupportedFormat}; + event.string = rdi_path; + di_p2u_push_event(&event); + } + } + } + + //////////////////////////// + //- rjf: got task -> open file + // + OS_Handle file = {0}; + OS_Handle file_map = {0}; + FileProperties file_props = {0}; + void *file_base = 0; + if(got_task) + { + file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead|OS_AccessFlag_ShareWrite, rdi_path); + file_map = os_file_map_open(OS_AccessFlag_Read, file); + file_props = os_properties_from_file(file); + file_base = os_file_map_view_open(file_map, OS_AccessFlag_Read, r1u64(0, file_props.size)); + } + + //////////////////////////// + //- rjf: do initial parse of rdi + // + RDI_Parsed rdi_parsed_maybe_compressed = di_rdi_parsed_nil; + if(got_task) + { + RDI_ParseStatus parse_status = rdi_parse((U8 *)file_base, file_props.size, &rdi_parsed_maybe_compressed); + (void)parse_status; + } + + //////////////////////////// + //- rjf: decompress & re-parse, if necessary + // + Arena *rdi_parsed_arena = 0; + RDI_Parsed rdi_parsed = rdi_parsed_maybe_compressed; + if(got_task) + { + U64 decompressed_size = rdi_decompressed_size_from_parsed(&rdi_parsed_maybe_compressed); + if(decompressed_size > file_props.size) + { + rdi_parsed_arena = arena_alloc(); + U8 *decompressed_data = push_array_no_zero(rdi_parsed_arena, U8, decompressed_size); + rdi_decompress_parsed(decompressed_data, decompressed_size, &rdi_parsed_maybe_compressed); + RDI_ParseStatus parse_status = rdi_parse(decompressed_data, decompressed_size, &rdi_parsed); + (void)parse_status; + } + } + + //////////////////////////// + //- rjf: commit parsed info to cache + // + if(got_task) OS_MutexScopeW(stripe->rw_mutex) + { + DI_Node *node = di_node_from_key_slot__stripe_mutex_r_guarded(slot, &key); + if(node != 0) + { + node->is_working = 0; + node->file = file; + node->file_map = file_map; + node->file_base = file_base; + node->file_props = file_props; + node->arena = rdi_parsed_arena; + node->rdi = rdi_parsed; + node->parse_done = 1; + } + } + os_condition_variable_broadcast(stripe->cv); + + scratch_end(scratch); + } +} diff --git a/src/demon/demon_core.h b/src/demon/demon_core.h index 53866179..523e6e82 100644 --- a/src/demon/demon_core.h +++ b/src/demon/demon_core.h @@ -1,242 +1,242 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef DEMON_CORE_H -#define DEMON_CORE_H - -//////////////////////////////// -//~ rjf: Control-Thread-Only Context -// -// An instance of this struct must ONLY be returned by dmn_ctrl_begin, and only -// used by the thread which called it. All APIs which can ONLY run on the -// control thread, which blocks to control & receive events, will take this -// parameter. All other APIs can be called from any thread. - -typedef struct DMN_CtrlCtx DMN_CtrlCtx; -struct DMN_CtrlCtx -{ - U64 u64[1]; -}; - -//////////////////////////////// -//~ rjf: Handle Types - -typedef union DMN_Handle DMN_Handle; -union DMN_Handle -{ - U32 u32[2]; - U64 u64[1]; -}; - -typedef struct DMN_HandleNode DMN_HandleNode; -struct DMN_HandleNode -{ - DMN_HandleNode *next; - DMN_Handle v; -}; - -typedef struct DMN_HandleList DMN_HandleList; -struct DMN_HandleList -{ - DMN_HandleNode *first; - DMN_HandleNode *last; - U64 count; -}; - -typedef struct DMN_HandleArray DMN_HandleArray; -struct DMN_HandleArray -{ - DMN_Handle *handles; - U64 count; -}; - -//////////////////////////////// -//~ rjf: Generated Code - -#include "generated/demon.meta.h" - -//////////////////////////////// -//~ rjf: Event Types - -typedef struct DMN_Event DMN_Event; -struct DMN_Event -{ - DMN_EventKind kind; - DMN_ErrorKind error_kind; - DMN_MemoryEventKind memory_kind; - DMN_ExceptionKind exception_kind; - DMN_Handle process; - DMN_Handle thread; - DMN_Handle module; - Architecture arch; - U64 address; - U64 size; - String8 string; - U32 code; // code gives pid & tid on CreateProcess and CreateThread (respectfully) - U32 flags; - S32 signo; - S32 sigcode; - U64 instruction_pointer; - U64 stack_pointer; - U64 user_data; - B32 exception_repeated; -}; - -typedef struct DMN_EventNode DMN_EventNode; -struct DMN_EventNode -{ - DMN_EventNode *next; - DMN_Event v; -}; - -typedef struct DMN_EventList DMN_EventList; -struct DMN_EventList -{ - DMN_EventNode *first; - DMN_EventNode *last; - U64 count; -}; - -//////////////////////////////// -//~ rjf: Run Control Types - -typedef struct DMN_Trap DMN_Trap; -struct DMN_Trap -{ - DMN_Handle process; - U64 vaddr; - U64 id; -}; - -typedef struct DMN_TrapChunkNode DMN_TrapChunkNode; -struct DMN_TrapChunkNode -{ - DMN_TrapChunkNode *next; - DMN_Trap *v; - U64 cap; - U64 count; -}; - -typedef struct DMN_TrapChunkList DMN_TrapChunkList; -struct DMN_TrapChunkList -{ - DMN_TrapChunkNode *first; - DMN_TrapChunkNode *last; - U64 node_count; - U64 trap_count; -}; - -typedef struct DMN_RunCtrls DMN_RunCtrls; -struct DMN_RunCtrls -{ - DMN_Handle single_step_thread; - B8 ignore_previous_exception; - B8 run_entities_are_unfrozen; - B8 run_entities_are_processes; - DMN_Handle *run_entities; - U64 run_entity_count; - DMN_TrapChunkList traps; -}; - -//////////////////////////////// -//~ rjf: System Process Listing Types - -typedef struct DMN_ProcessIter DMN_ProcessIter; -struct DMN_ProcessIter -{ - U64 v[2]; -}; - -typedef struct DMN_ProcessInfo DMN_ProcessInfo; -struct DMN_ProcessInfo -{ - String8 name; - U32 pid; -}; - -//////////////////////////////// -//~ rjf: Basic Type Functions (Helpers, Implemented Once) - -//- rjf: handles -internal DMN_Handle dmn_handle_zero(void); -internal B32 dmn_handle_match(DMN_Handle a, DMN_Handle b); - -//- rjf: trap chunk lists -internal void dmn_trap_chunk_list_push(Arena *arena, DMN_TrapChunkList *list, U64 cap, DMN_Trap *trap); -internal void dmn_trap_chunk_list_concat_in_place(DMN_TrapChunkList *dst, DMN_TrapChunkList *to_push); -internal void dmn_trap_chunk_list_concat_shallow_copy(Arena *arena, DMN_TrapChunkList *dst, DMN_TrapChunkList *to_push); - -//- rjf: handle lists -internal void dmn_handle_list_push(Arena *arena, DMN_HandleList *list, DMN_Handle handle); -internal DMN_HandleArray dmn_handle_array_from_list(Arena *arena, DMN_HandleList *list); -internal DMN_HandleArray dmn_handle_array_copy(Arena *arena, DMN_HandleArray *src); - -//- rjf: event list building -internal DMN_Event *dmn_event_list_push(Arena *arena, DMN_EventList *list); - -//////////////////////////////// -//~ rjf: Thread Reading Helper Functions (Helpers, Implemented Once) - -internal U64 dmn_rip_from_thread(DMN_Handle thread); -internal U64 dmn_rsp_from_thread(DMN_Handle thread); - -//////////////////////////////// -//~ rjf: @dmn_os_hooks Main Layer Initialization (Implemented Per-OS) - -internal void dmn_init(void); - -//////////////////////////////// -//~ rjf: @dmn_os_hooks Blocking Control Thread Operations (Implemented Per-OS) - -internal DMN_CtrlCtx *dmn_ctrl_begin(void); -internal void dmn_ctrl_exclusive_access_begin(void); -internal void dmn_ctrl_exclusive_access_end(void); -#define DMN_CtrlExclusiveAccessScope DeferLoop(dmn_ctrl_exclusive_access_begin(), dmn_ctrl_exclusive_access_end()) -internal U32 dmn_ctrl_launch(DMN_CtrlCtx *ctx, OS_LaunchOptions *options); -internal B32 dmn_ctrl_attach(DMN_CtrlCtx *ctx, U32 pid); -internal B32 dmn_ctrl_kill(DMN_CtrlCtx *ctx, DMN_Handle process, U32 exit_code); -internal B32 dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process); -internal DMN_EventList dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls); - -//////////////////////////////// -//~ rjf: @dmn_os_hooks Halting (Implemented Per-OS) - -internal void dmn_halt(U64 code, U64 user_data); - -//////////////////////////////// -//~ rjf: @dmn_os_hooks Introspection Functions (Implemented Per-OS) - -//- rjf: run/memory/register counters -internal U64 dmn_run_gen(void); -internal U64 dmn_mem_gen(void); -internal U64 dmn_reg_gen(void); - -//- rjf: non-blocking-control-thread access barriers -internal B32 dmn_access_open(void); -internal void dmn_access_close(void); -#define DMN_AccessScope DeferLoopChecked(dmn_access_open(), dmn_access_close()) - -//- rjf: processes -internal U64 dmn_process_memory_reserve(DMN_Handle process, U64 vaddr, U64 size); -internal void dmn_process_memory_commit(DMN_Handle process, U64 vaddr, U64 size); -internal void dmn_process_memory_decommit(DMN_Handle process, U64 vaddr, U64 size); -internal void dmn_process_memory_release(DMN_Handle process, U64 vaddr, U64 size); -internal void dmn_process_memory_protect(DMN_Handle process, U64 vaddr, U64 size, OS_AccessFlags flags); -internal U64 dmn_process_read(DMN_Handle process, Rng1U64 range, void *dst); -internal B32 dmn_process_write(DMN_Handle process, Rng1U64 range, void *src); -#define dmn_process_read_struct(process, vaddr, ptr) dmn_process_read((process), r1u64((vaddr), (vaddr)+(sizeof(*ptr))), ptr) -#define dmn_process_write_struct(process, vaddr, ptr) dmn_process_write((process), r1u64((vaddr), (vaddr)+(sizeof(*ptr))), ptr) - -//- rjf: threads -internal Architecture dmn_arch_from_thread(DMN_Handle handle); -internal U64 dmn_stack_base_vaddr_from_thread(DMN_Handle handle); -internal U64 dmn_tls_root_vaddr_from_thread(DMN_Handle handle); -internal B32 dmn_thread_read_reg_block(DMN_Handle handle, void *reg_block); -internal B32 dmn_thread_write_reg_block(DMN_Handle handle, void *reg_block); - -//- rjf: system process listing -internal void dmn_process_iter_begin(DMN_ProcessIter *iter); -internal B32 dmn_process_iter_next(Arena *arena, DMN_ProcessIter *iter, DMN_ProcessInfo *info_out); -internal void dmn_process_iter_end(DMN_ProcessIter *iter); - -#endif // DEMON_CORE_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef DEMON_CORE_H +#define DEMON_CORE_H + +//////////////////////////////// +//~ rjf: Control-Thread-Only Context +// +// An instance of this struct must ONLY be returned by dmn_ctrl_begin, and only +// used by the thread which called it. All APIs which can ONLY run on the +// control thread, which blocks to control & receive events, will take this +// parameter. All other APIs can be called from any thread. + +typedef struct DMN_CtrlCtx DMN_CtrlCtx; +struct DMN_CtrlCtx +{ + U64 u64[1]; +}; + +//////////////////////////////// +//~ rjf: Handle Types + +typedef union DMN_Handle DMN_Handle; +union DMN_Handle +{ + U32 u32[2]; + U64 u64[1]; +}; + +typedef struct DMN_HandleNode DMN_HandleNode; +struct DMN_HandleNode +{ + DMN_HandleNode *next; + DMN_Handle v; +}; + +typedef struct DMN_HandleList DMN_HandleList; +struct DMN_HandleList +{ + DMN_HandleNode *first; + DMN_HandleNode *last; + U64 count; +}; + +typedef struct DMN_HandleArray DMN_HandleArray; +struct DMN_HandleArray +{ + DMN_Handle *handles; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Generated Code + +#include "generated/demon.meta.h" + +//////////////////////////////// +//~ rjf: Event Types + +typedef struct DMN_Event DMN_Event; +struct DMN_Event +{ + DMN_EventKind kind; + DMN_ErrorKind error_kind; + DMN_MemoryEventKind memory_kind; + DMN_ExceptionKind exception_kind; + DMN_Handle process; + DMN_Handle thread; + DMN_Handle module; + Architecture arch; + U64 address; + U64 size; + String8 string; + U32 code; // code gives pid & tid on CreateProcess and CreateThread (respectfully) + U32 flags; + S32 signo; + S32 sigcode; + U64 instruction_pointer; + U64 stack_pointer; + U64 user_data; + B32 exception_repeated; +}; + +typedef struct DMN_EventNode DMN_EventNode; +struct DMN_EventNode +{ + DMN_EventNode *next; + DMN_Event v; +}; + +typedef struct DMN_EventList DMN_EventList; +struct DMN_EventList +{ + DMN_EventNode *first; + DMN_EventNode *last; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Run Control Types + +typedef struct DMN_Trap DMN_Trap; +struct DMN_Trap +{ + DMN_Handle process; + U64 vaddr; + U64 id; +}; + +typedef struct DMN_TrapChunkNode DMN_TrapChunkNode; +struct DMN_TrapChunkNode +{ + DMN_TrapChunkNode *next; + DMN_Trap *v; + U64 cap; + U64 count; +}; + +typedef struct DMN_TrapChunkList DMN_TrapChunkList; +struct DMN_TrapChunkList +{ + DMN_TrapChunkNode *first; + DMN_TrapChunkNode *last; + U64 node_count; + U64 trap_count; +}; + +typedef struct DMN_RunCtrls DMN_RunCtrls; +struct DMN_RunCtrls +{ + DMN_Handle single_step_thread; + B8 ignore_previous_exception; + B8 run_entities_are_unfrozen; + B8 run_entities_are_processes; + DMN_Handle *run_entities; + U64 run_entity_count; + DMN_TrapChunkList traps; +}; + +//////////////////////////////// +//~ rjf: System Process Listing Types + +typedef struct DMN_ProcessIter DMN_ProcessIter; +struct DMN_ProcessIter +{ + U64 v[2]; +}; + +typedef struct DMN_ProcessInfo DMN_ProcessInfo; +struct DMN_ProcessInfo +{ + String8 name; + U32 pid; +}; + +//////////////////////////////// +//~ rjf: Basic Type Functions (Helpers, Implemented Once) + +//- rjf: handles +internal DMN_Handle dmn_handle_zero(void); +internal B32 dmn_handle_match(DMN_Handle a, DMN_Handle b); + +//- rjf: trap chunk lists +internal void dmn_trap_chunk_list_push(Arena *arena, DMN_TrapChunkList *list, U64 cap, DMN_Trap *trap); +internal void dmn_trap_chunk_list_concat_in_place(DMN_TrapChunkList *dst, DMN_TrapChunkList *to_push); +internal void dmn_trap_chunk_list_concat_shallow_copy(Arena *arena, DMN_TrapChunkList *dst, DMN_TrapChunkList *to_push); + +//- rjf: handle lists +internal void dmn_handle_list_push(Arena *arena, DMN_HandleList *list, DMN_Handle handle); +internal DMN_HandleArray dmn_handle_array_from_list(Arena *arena, DMN_HandleList *list); +internal DMN_HandleArray dmn_handle_array_copy(Arena *arena, DMN_HandleArray *src); + +//- rjf: event list building +internal DMN_Event *dmn_event_list_push(Arena *arena, DMN_EventList *list); + +//////////////////////////////// +//~ rjf: Thread Reading Helper Functions (Helpers, Implemented Once) + +internal U64 dmn_rip_from_thread(DMN_Handle thread); +internal U64 dmn_rsp_from_thread(DMN_Handle thread); + +//////////////////////////////// +//~ rjf: @dmn_os_hooks Main Layer Initialization (Implemented Per-OS) + +internal void dmn_init(void); + +//////////////////////////////// +//~ rjf: @dmn_os_hooks Blocking Control Thread Operations (Implemented Per-OS) + +internal DMN_CtrlCtx *dmn_ctrl_begin(void); +internal void dmn_ctrl_exclusive_access_begin(void); +internal void dmn_ctrl_exclusive_access_end(void); +#define DMN_CtrlExclusiveAccessScope DeferLoop(dmn_ctrl_exclusive_access_begin(), dmn_ctrl_exclusive_access_end()) +internal U32 dmn_ctrl_launch(DMN_CtrlCtx *ctx, OS_ProcessLaunchParams *params); +internal B32 dmn_ctrl_attach(DMN_CtrlCtx *ctx, U32 pid); +internal B32 dmn_ctrl_kill(DMN_CtrlCtx *ctx, DMN_Handle process, U32 exit_code); +internal B32 dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process); +internal DMN_EventList dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls); + +//////////////////////////////// +//~ rjf: @dmn_os_hooks Halting (Implemented Per-OS) + +internal void dmn_halt(U64 code, U64 user_data); + +//////////////////////////////// +//~ rjf: @dmn_os_hooks Introspection Functions (Implemented Per-OS) + +//- rjf: run/memory/register counters +internal U64 dmn_run_gen(void); +internal U64 dmn_mem_gen(void); +internal U64 dmn_reg_gen(void); + +//- rjf: non-blocking-control-thread access barriers +internal B32 dmn_access_open(void); +internal void dmn_access_close(void); +#define DMN_AccessScope DeferLoopChecked(dmn_access_open(), dmn_access_close()) + +//- rjf: processes +internal U64 dmn_process_memory_reserve(DMN_Handle process, U64 vaddr, U64 size); +internal void dmn_process_memory_commit(DMN_Handle process, U64 vaddr, U64 size); +internal void dmn_process_memory_decommit(DMN_Handle process, U64 vaddr, U64 size); +internal void dmn_process_memory_release(DMN_Handle process, U64 vaddr, U64 size); +internal void dmn_process_memory_protect(DMN_Handle process, U64 vaddr, U64 size, OS_AccessFlags flags); +internal U64 dmn_process_read(DMN_Handle process, Rng1U64 range, void *dst); +internal B32 dmn_process_write(DMN_Handle process, Rng1U64 range, void *src); +#define dmn_process_read_struct(process, vaddr, ptr) dmn_process_read((process), r1u64((vaddr), (vaddr)+(sizeof(*ptr))), ptr) +#define dmn_process_write_struct(process, vaddr, ptr) dmn_process_write((process), r1u64((vaddr), (vaddr)+(sizeof(*ptr))), ptr) + +//- rjf: threads +internal Architecture dmn_arch_from_thread(DMN_Handle handle); +internal U64 dmn_stack_base_vaddr_from_thread(DMN_Handle handle); +internal U64 dmn_tls_root_vaddr_from_thread(DMN_Handle handle); +internal B32 dmn_thread_read_reg_block(DMN_Handle handle, void *reg_block); +internal B32 dmn_thread_write_reg_block(DMN_Handle handle, void *reg_block); + +//- rjf: system process listing +internal void dmn_process_iter_begin(DMN_ProcessIter *iter); +internal B32 dmn_process_iter_next(Arena *arena, DMN_ProcessIter *iter, DMN_ProcessInfo *info_out); +internal void dmn_process_iter_end(DMN_ProcessIter *iter); + +#endif // DEMON_CORE_H diff --git a/src/demon/linux/demon_os_linux.c b/src/demon/linux/demon_os_linux.c index 0c92f461..01a1fb70 100644 --- a/src/demon/linux/demon_os_linux.c +++ b/src/demon/linux/demon_os_linux.c @@ -1,2106 +1,2106 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -// TODO(allen): run controls: ignore_previous_exception - -//////////////////////////////// -//~ allen: Elf Parsing Code - -#include "syms/syms_elf_inc.c" - -//////////////////////////////// -//~ rjf: Globals - -global B32 demon_lnx_already_has_halt_injection = false; -global U64 demon_lnx_halt_code = 0; -global U64 demon_lnx_halt_user_data = 0; - -global B32 demon_lnx_new_process_pending = false; - -global Arena *demon_lnx_event_arena = 0; -global DEMON_EventList demon_lnx_queued_events = {0}; - -global U32 demon_lnx_ptrace_options = (PTRACE_O_TRACEEXIT| - PTRACE_O_EXITKILL| - PTRACE_O_TRACEFORK| - PTRACE_O_TRACEVFORK| - PTRACE_O_TRACECLONE); - -//////////////////////////////// -//~ rjf: Helpers - -internal DEMON_LNX_ThreadExt* -demon_lnx_thread_ext(DEMON_Entity *entity){ - DEMON_LNX_ThreadExt *result = (DEMON_LNX_ThreadExt*)&entity->ext; - return(result); -} - -internal B32 -demon_lnx_attach_pid(Arena *arena, pid_t pid, DEMON_LNX_AttachNode **new_node){ - B32 result = false; - - int attach_result = ptrace(PTRACE_ATTACH, pid, 0, 0); - if (attach_result == -1){ - // TODO(allen): attach denied - } - else{ - // return a new attachment node as soon as the ptrace exists. we use these nodes - // for cleanup on failure *and* for initializing on success. either way we need - // to see all new attachments whether or not they fully initialized correctly. - DEMON_LNX_AttachNode *proc_attachment = push_array_no_zero(arena, DEMON_LNX_AttachNode, 1); - proc_attachment->next = 0; - proc_attachment->pid = pid; - *new_node = proc_attachment; - - int status = 0; - pid_t wait_id = waitpid(pid, &status, __WALL); - // NOTE(allen): if wait_id != pid we don't know what that means; study that case before - // deciding how error handling around it works. - if (wait_id == pid){ - int setoptions_result = ptrace(PTRACE_SETOPTIONS, pid, 0, PtrFromInt(demon_lnx_ptrace_options)); - if (setoptions_result == -1){ - // TODO(allen): setup failed - } - else{ - result = true; - } - } - } - - return(result); -} - -internal String8 -demon_lnx_executable_path_from_pid(Arena *arena, pid_t pid){ - // get symbolic path - Temp scratch = scratch_begin(&arena, 1); - String8 exe_symbol_path = push_str8f(scratch.arena, "/proc/%d/exe", pid); - - // try to read the link for a bit - Temp restore_point = temp_begin(arena); - B32 got_final_result = false; - U8 *buffer = 0; - int size = 0; - S64 cap = PATH_MAX; - for (S64 r = 0; r < 4; cap *= 2, r += 1){ - temp_end(restore_point); - buffer = push_array_no_zero(arena, U8, cap); - size = readlink((char*)exe_symbol_path.str, (char*)buffer, cap); - if (size < cap){ - got_final_result = true; - break; - } - } - - // finalize result - String8 result = {0}; - if (!got_final_result || size == -1){ - temp_end(restore_point); - } - else{ - arena_put_back(arena, (cap - size - 1)); - result = str8(buffer, size + 1); - } - - scratch_end(scratch); - return(result); -} - -internal int -demon_lnx_open_memory_fd_for_pid(pid_t pid){ - Temp scratch = scratch_begin(0, 0); - String8 memory_path = push_str8f(scratch.arena, "/proc/%i/mem", pid); - int result = open((char*)memory_path.str, O_RDWR); - scratch_end(scratch); - return(result); -} - -internal Architecture -demon_lnx_arch_from_pid(pid_t pid){ - Temp scratch = scratch_begin(0, 0); - Architecture result = Architecture_Null; - - // exe path - String8 exe_path = demon_lnx_executable_path_from_pid(scratch.arena, pid); - - // handle to exe - int exe_fd = -1; - if (exe_path.size != 0){ - exe_fd = open((char*)exe_path.str, O_RDONLY); - } - - // elf identification - B32 is_elf = false; - U8 e_ident[SYMS_ElfIdentifier_NIDENT] = {0}; - if (exe_fd >= 0){ - if (pread(exe_fd, e_ident, sizeof(e_ident), 0) == sizeof(e_ident)){ - is_elf = (e_ident[SYMS_ElfIdentifier_MAG0] == 0x7f && - e_ident[SYMS_ElfIdentifier_MAG1] == 'E' && - e_ident[SYMS_ElfIdentifier_MAG2] == 'L' && - e_ident[SYMS_ElfIdentifier_MAG3] == 'F'); - } - } - - // elf class - U8 elf_class = 0; - if (is_elf){ - elf_class = e_ident[SYMS_ElfIdentifier_CLASS]; - } - - // exe header data - SYMS_ElfEhdr64 ehdr = {0}; - switch (elf_class){ - case 1: - { - SYMS_ElfEhdr32 ehdr32 = {0}; - if (pread(exe_fd, &ehdr32, sizeof(ehdr32), 0) == sizeof(ehdr32)){ - ehdr = syms_elf_ehdr64_from_ehdr32(ehdr32); - } - }break; - - case 2: - { - pread(exe_fd, &ehdr, sizeof(ehdr), 0); - }break; - } - - // determine machine type - switch (ehdr.e_machine){ - case SYMS_ElfMachineKind_386: - { - result = Architecture_x86; - }break; - - case SYMS_ElfMachineKind_ARM: - { - result = Architecture_arm32; - }break; - - case SYMS_ElfMachineKind_X86_64: - { - result = Architecture_x64; - }break; - - case SYMS_ElfMachineKind_AARCH64: - { - result = Architecture_arm64; - }break; - } - - scratch_end(scratch); - return(result); -} - -internal DEMON_LNX_ProcessAux -demon_lnx_aux_from_pid(pid_t pid, Architecture arch){ - DEMON_LNX_ProcessAux result = {0}; - B32 addr_32bit = (arch == Architecture_x86 || arch == Architecture_arm32); - - // open aux data - Temp scratch = scratch_begin(0, 0); - String8 auxv_symbol_path = push_str8f(scratch.arena, "/proc/%d/auxv", pid); - int aux_fd = open((char*)auxv_symbol_path.str, O_RDONLY); - - // scan aux data - if (aux_fd >= 0){ - for (;;){ - result.filled = true; - - // read next aux - U64 type = 0; - U64 val = 0; - if (addr_32bit){ - SYMS_ElfAuxv32 aux; - if (read(aux_fd, &aux, sizeof(aux)) != sizeof(aux)){ - goto brkloop; - } - type = aux.a_type; - val = aux.a_val; - } - else{ - SYMS_ElfAuxv64 aux; - if (read(aux_fd, &aux, sizeof(aux)) != sizeof(aux)){ - goto brkloop; - } - type = aux.a_type; - val = aux.a_val; - } - - // place value in result - switch (type){ - default:break; - case SYMS_ElfAuxType_NULL: goto brkloop; break; - case SYMS_ElfAuxType_PHNUM: result.phnum = val; break; - case SYMS_ElfAuxType_PHENT: result.phent = val; break; - case SYMS_ElfAuxType_PHDR: result.phdr = val; break; - case SYMS_ElfAuxType_EXECFN: result.execfn = val; break; - } - } - brkloop:; - - close(aux_fd); - } - - scratch_end(scratch); - return(result); -} - -internal DEMON_LNX_PhdrInfo -demon_lnx_phdr_info_from_memory(int memory_fd, B32 is_32bit, U64 phvaddr, U64 phentsize, U64 phcount){ - DEMON_LNX_PhdrInfo result = {0}; - result.range.min = max_U64; - - // how much phdr will we read? - U64 phdr_size_expected = (is_32bit?sizeof(SYMS_ElfPhdr32):sizeof(SYMS_ElfPhdr64)); - U64 phdr_stride = (phentsize?phentsize:phdr_size_expected); - U64 phdr_read_size = ClampTop(phdr_stride, phdr_size_expected); - - // scan table - U64 va = phvaddr; - for (U64 i = 0; i < phcount; i += 1, va += phdr_stride){ - - // get type and range - SYMS_ElfPKind p_type = 0; - U64 p_vaddr = 0; - U64 p_memsz = 0; - - if (is_32bit){ - SYMS_ElfPhdr32 phdr32 = {0}; - demon_lnx_read_memory(memory_fd, &phdr32, va, phdr_read_size); - p_type = phdr32.p_type; - p_vaddr = phdr32.p_vaddr; - p_memsz = phdr32.p_memsz; - } - else{ - SYMS_ElfPhdr64 phdr64 = {0}; - demon_lnx_read_memory(memory_fd, &phdr64, va, phdr_read_size); - p_type = phdr64.p_type; - p_vaddr = phdr64.p_vaddr; - p_memsz = phdr64.p_memsz; - } - - // save useful info - switch (p_type){ - case SYMS_ElfPKind_Dynamic: - { - result.dynamic = p_vaddr; - }break; - case SYMS_ElfPKind_Load: - { - U64 min = p_vaddr; - U64 max = p_vaddr + p_memsz; - result.range.min = Min(result.range.min, min); - result.range.max = Max(result.range.max, max); - }break; - } - } - - return(result); -} - -internal DEMON_LNX_ModuleNode* -demon_lnx_module_list_from_process(Arena *arena, DEMON_Entity *process){ - Architecture arch = (Architecture)process->arch; - B32 is_32bit = (arch == Architecture_x86 || arch == Architecture_arm32); - int memory_fd = (int)process->ext_u64; - - // aux from pid - DEMON_LNX_ProcessAux aux = demon_lnx_aux_from_pid((pid_t)process->id, arch); - - // extract info from program headers - DEMON_LNX_PhdrInfo phdr_info = demon_lnx_phdr_info_from_memory(memory_fd, is_32bit, - aux.phdr, aux.phent, aux.phnum); - - // linkmap first from memory space & dyn address - U64 first_linkmap_va = 0; - if (phdr_info.dynamic != 0){ - U64 off = phdr_info.dynamic; - for (;;){ - SYMS_ElfDyn64 dyn = {0}; - if (is_32bit){ - SYMS_ElfDyn32 dyn32 = {0}; - demon_lnx_read_memory(memory_fd, &dyn32, off, sizeof(dyn32)); - dyn.tag = dyn32.tag; - dyn.val = dyn32.val; - off += sizeof(dyn32); - } - else{ - demon_lnx_read_memory(memory_fd, &dyn, off, sizeof(dyn)); - off += sizeof(dyn); - } - - if (dyn.tag == SYMS_ElfDynTag_NULL){ - break; - } - - if (dyn.tag == SYMS_ElfDynTag_PLTGOT){ - // True for x86 and x64 - // vas[0] virtual address of .dynamic - // vas[2] callback for resolving function address of relocation and if successful jumps to it. - // - // Code that sets up PLTGOT is in glibc/sysdeps/x86_64/dl_machine.h -> elf_machine_runtime_setup - U64 vas_off = dyn.val; - U64 vas[3] = {0}; - demon_lnx_read_memory(memory_fd, vas, vas_off, sizeof(vas)); - first_linkmap_va = vas[1]; - break; - } - } - } - - // setup output list - DEMON_LNX_ModuleNode *first = 0; - DEMON_LNX_ModuleNode *last = 0; - - // main module - { - DEMON_LNX_ModuleNode *node = push_array(arena, DEMON_LNX_ModuleNode, 1); - SLLQueuePush(first, last, node); - node->vaddr = phdr_info.range.min; - node->size = phdr_info.range.max - phdr_info.range.min; - node->name = aux.execfn; - } - - // iterate link maps - if (first_linkmap_va != 0){ - U64 linkmap_va = first_linkmap_va; - - for (;;){ - SYMS_ElfLinkMap64 linkmap = {0}; - if (is_32bit){ - // TOOD(nick): endian awarness - SYMS_ElfLinkMap32 linkmap32 = {0}; - demon_lnx_read_memory(memory_fd, &linkmap32, linkmap_va, sizeof(linkmap32)); - linkmap.base = linkmap32.base; - linkmap.name = linkmap32.name; - linkmap.ld = linkmap32.ld; - linkmap.next = linkmap32.next; - } - else{ - demon_lnx_read_memory(memory_fd, &linkmap, linkmap_va, sizeof(linkmap)); - } - - if (linkmap.base != 0){ - // find phdrs for this module - SYMS_U64 phvaddr = 0; - SYMS_U64 phentsize = 0; - SYMS_U64 phcount = 0; - - if (is_32bit){ - SYMS_ElfEhdr32 ehdr = {0}; - demon_lnx_read_memory(memory_fd, &ehdr, linkmap.base, sizeof(ehdr)); - phvaddr = ehdr.e_phoff + linkmap.base; - phentsize = ehdr.e_phentsize; - phcount = ehdr.e_phnum; - } - else{ - SYMS_ElfEhdr64 ehdr = {0}; - demon_lnx_read_memory(memory_fd, &ehdr, linkmap.base, sizeof(ehdr)); - phvaddr = ehdr.e_phoff + linkmap.base; - phentsize = ehdr.e_phentsize; - phcount = ehdr.e_phnum; - } - - // extract info from phdrs - DEMON_LNX_PhdrInfo module_phdr_info = demon_lnx_phdr_info_from_memory(memory_fd, is_32bit, - phvaddr, phentsize, phcount); - - // save module node - DEMON_LNX_ModuleNode *node = push_array(arena, DEMON_LNX_ModuleNode, 1); - SLLQueuePush(first, last, node); - node->vaddr = linkmap.base; - node->size = module_phdr_info.range.max - module_phdr_info.range.min; - node->name = linkmap.name; - } - - linkmap_va = linkmap.next; - if (linkmap_va == 0){ - break; - } - } - } - - return(first); -} - -internal U64 -demon_lnx_read_memory(int memory_fd, void *dst, U64 src, U64 size){ - U64 bytes_read = 0; - U8 *ptr = (U8*)dst; - U8 *opl = ptr + size; - U64 cursor = src; - for (;ptr < opl;){ - size_t to_read = (size_t)(opl - ptr); - ssize_t actual_read = pread(memory_fd, ptr, to_read, cursor); - if (actual_read == -1){ - break; - } - ptr += actual_read; - cursor += actual_read; - bytes_read += actual_read; - } - return(bytes_read); -} - -internal B32 -demon_lnx_write_memory(int memory_fd, U64 dst, void *src, U64 size){ - B32 result = true; - U8 *ptr = (U8*)src; - U8 *opl = ptr + size; - U64 cursor = dst; - for (;ptr < opl;){ - size_t to_write = (size_t)(opl - ptr); - ssize_t actual_write = pwrite(memory_fd, ptr, to_write, cursor); - if (actual_write == -1){ - result = false; - break; - } - ptr += actual_write; - cursor += actual_write; - } - return(result); -} - -internal String8 -demon_lnx_read_memory_str(Arena *arena, int memory_fd, U64 address){ - // TODO(allen): this could be done better with a demon_lnx_read_memory - // that returns a read amount instead of a success/fail. - - // scan piece by piece - Temp scratch = scratch_begin(&arena, 1); - String8List list = {0}; - - U64 max_cap = 256; - U64 cap = max_cap; - U64 read_p = address; - for (;;){ - U8 *block = push_array(scratch.arena, U8, cap); - for (;cap > 0;){ - if (demon_lnx_read_memory(memory_fd, block, read_p, cap)){ - break; - } - cap /= 2; - } - read_p += cap; - - U64 block_opl = 0; - for (;block_opl < cap; block_opl += 1){ - if (block[block_opl] == 0){ - break; - } - } - - if (block_opl > 0){ - str8_list_push(scratch.arena, &list, str8(block, block_opl)); - } - - if (block_opl < cap || cap == 0){ - break; - } - } - - // assemble results - String8 result = str8_list_join(arena, &list, 0); - scratch_end(scratch); - return(result); -} - -internal void -demon_lnx_regs_x64_from_usr_regs_x64(SYMS_RegX64 *dst, DEMON_LNX_UserRegsX64 *src){ - dst->rax.u64 = src->rax; - dst->rcx.u64 = src->rcx; - dst->rdx.u64 = src->rdx; - dst->rbx.u64 = src->rbx; - dst->rsp.u64 = src->rsp; - dst->rbp.u64 = src->rbp; - dst->rsi.u64 = src->rsi; - dst->rdi.u64 = src->rdi; - dst->r8.u64 = src->r8; - dst->r9.u64 = src->r9; - dst->r10.u64 = src->r10; - dst->r11.u64 = src->r11; - dst->r12.u64 = src->r12; - dst->r13.u64 = src->r13; - dst->r14.u64 = src->r14; - dst->r15.u64 = src->r15; - dst->cs.u16 = src->cs; - dst->ds.u16 = src->ds; - dst->es.u16 = src->es; - dst->fs.u16 = src->fs; - dst->gs.u16 = src->gs; - dst->ss.u16 = src->ss; - dst->fsbase.u64 = src->fsbase; - dst->gsbase.u64 = src->gsbase; - dst->rip.u64 = src->rip; - dst->rflags.u64 = src->rflags; -} - -internal void -demon_lnx_usr_regs_x64_from_regs_x64(DEMON_LNX_UserRegsX64 *dst, SYMS_RegX64 *src){ - dst->rax = src->rax.u64; - dst->rcx = src->rcx.u64; - dst->rdx = src->rdx.u64; - dst->rbx = src->rbx.u64; - dst->rsp = src->rsp.u64; - dst->rbp = src->rbp.u64; - dst->rsi = src->rsi.u64; - dst->rdi = src->rdi.u64; - dst->r8 = src->r8.u64; - dst->r9 = src->r9.u64; - dst->r10 = src->r10.u64; - dst->r11 = src->r11.u64; - dst->r12 = src->r12.u64; - dst->r13 = src->r13.u64; - dst->r14 = src->r14.u64; - dst->r15 = src->r15.u64; - dst->cs = src->cs.u16; - dst->ds = src->ds.u16; - dst->es = src->es.u16; - dst->fs = src->fs.u16; - dst->gs = src->gs.u16; - dst->ss = src->ss.u16; - dst->fsbase = src->fsbase.u64; - dst->gsbase = src->gsbase.u64; - dst->rip = src->rip.u64; - dst->rflags = src->rflags.u64; -} - -//////////////////////////////// - -internal String8 -demon_lnx_read_int_string(Arena *arena, int fd, int radix){ - String8 integer = str8(0,0); - - int to_read = 0; - int to_seek = 0; - for (;;){ - char b = 0; - if (read(fd, &b, sizeof(b)) == 0){ - break; - } - to_seek += 1; - if ( ! char_is_digit(b, radix)){ - break; - } - to_read += 1; - } - - if (lseek(fd, -to_seek, SEEK_CUR) != -1) { - char *buf = push_array_no_zero(arena, char, to_read + 1); - read(fd, buf, to_read); - buf[to_read] = '\0'; - integer = str8((U8*)buf, (U64)to_read); - } - - return(integer); -} - -internal U64 -demon_lnx_read_u64(int fd, int radix){ - Temp scratch = scratch_begin(0, 0); - String8 integer = demon_lnx_read_int_string(scratch.arena, fd, radix); - U64 result = u64_from_str8(integer, radix); - scratch_end(scratch); - return(result); -} - -internal S64 -demon_lnx_read_s64(int fd, int radix){ - Temp scratch = scratch_begin(0, 0); - String8 integer = demon_lnx_read_int_string(scratch.arena, fd, radix); - S64 result = s64_from_str8(integer, radix); - scratch_end(scratch); - return(result); -} - -internal B32 -demon_lnx_read_expect(int fd, char expect){ - char got = 0; - read(fd, &got, sizeof(got)); - B32 result = (got == expect); - if (!result){ - lseek(fd, -1, SEEK_CUR); - } - return(result); -} - -internal int -demon_lnx_read_whitespace(int fd){ - int whitespace_size = 0; - for (;;){ - if (!demon_lnx_read_expect(fd, ' ')){ - if (!demon_lnx_read_expect(fd, '\t')){ - break; - } - } - whitespace_size += 1; - } - return whitespace_size; -} - -internal String8 -demon_lnx_read_string(Arena *arena, int fd){ - String8 result = str8(0,0); - - int to_read = 0; - int to_seek = 0; - for (;;){ - char b = 0; - if (read(fd, &b, sizeof(b)) == 0) { - break; - } - to_seek += 1; - if (b == '\0' || b == '\n'){ - break; - } - to_read += 1; - } - - if (to_seek > 0 && lseek(fd, -to_seek, SEEK_CUR) != -1){ - char *buf = push_array_no_zero(arena, char, to_read + 1); - read(fd, buf, to_read); - buf[to_read] = '\0'; - result = str8((U8*)buf, to_read); - } - - return(result); -} - -internal int -demon_lnx_open_maps(pid_t pid){ - Temp scratch = scratch_begin(0, 0); - String8 path = push_str8f(scratch.arena, "/proc/%d/maps", pid); - int maps = open((char*)path.str, O_RDONLY); - scratch_end(scratch); - return(maps); -} - -internal B32 -demon_lnx_next_map(Arena *arena, int maps, DEMON_LNX_MapsEntry *entry_out){ - B32 is_parsed = false; - MemoryZeroStruct(entry_out); - do{ - U64 address_lo = 0; - U64 address_hi = 0; - DEMON_LNX_PermFlags perms = 0; - U64 offset = 0; - U64 dev_major = 0; - U64 dev_minor = 0; - U64 inode = 0; - String8 pathname = str8(0,0); - - // address range - address_lo = demon_lnx_read_u64(maps, 16); - if (!demon_lnx_read_expect(maps, '-')){ - break; - } - address_hi = demon_lnx_read_u64(maps, 16); - if (demon_lnx_read_whitespace(maps) == 0){ - break; - } - - // permission flags - char b; - if (read(maps, &b, sizeof(b)) == 0){ - break; - } - if (b=='r'){ - perms |= DEMON_LNX_PermFlags_Read; - } - if (read(maps, &b, sizeof(b)) == 0){ - break; - } - if (b=='w'){ - perms |= DEMON_LNX_PermFlags_Write; - } - if (read(maps, &b, sizeof(b)) == 0){ - break; - } - if (b=='x'){ - perms |= DEMON_LNX_PermFlags_Exec; - } - if (read(maps, &b, sizeof(b)) == 0){ - break; - } - if (b == 'p'){ - perms |= DEMON_LNX_PermFlags_Private; - } - if (demon_lnx_read_whitespace(maps) == 0){ - break; - } - - // offset - offset = demon_lnx_read_u64(maps, 16); - if (demon_lnx_read_whitespace(maps) == 0){ - break; - } - - // dev - dev_major = demon_lnx_read_u64(maps, 10); - if (!demon_lnx_read_expect(maps, ':')){ - break; - } - dev_minor = demon_lnx_read_u64(maps, 10); - if (demon_lnx_read_whitespace(maps) == 0){ - break; - } - - // inode - inode = demon_lnx_read_u64(maps, 10); - if (demon_lnx_read_whitespace(maps) == 10){ - break; - } - - // pathname - pathname = demon_lnx_read_string(arena, maps); - - // emit entry if en - b = 0; - read(maps, &b, sizeof(b)); - if (b != '\n' && b != '\0') { - break; - } - - // fill result - entry_out->address_lo = address_lo; - entry_out->address_hi = address_hi; - entry_out->perms = perms; - entry_out->offset = offset; - entry_out->dev_major = (U32)dev_major; - entry_out->dev_minor = (U32)dev_minor; - entry_out->inode = inode; - entry_out->pathname = pathname; - entry_out->type = DEMON_LNX_MapsEntryType_Null; - entry_out->stack_tid = 0; - - if (str8_match(pathname, str8_lit("/"), StringMatchFlag_RightSideSloppy)){ - entry_out->type = DEMON_LNX_MapsEntryType_Path; - } else if (str8_match(pathname, str8_lit("[heap]"), 0)){ - entry_out->type = DEMON_LNX_MapsEntryType_Heap; - } else if (str8_match(pathname, str8_lit("[stack]"), 0)){ - entry_out->type = DEMON_LNX_MapsEntryType_Stack; - } else if (str8_match(pathname, str8_lit("[stack:"), StringMatchFlag_RightSideSloppy)){ - entry_out->type = DEMON_LNX_MapsEntryType_Stack; - String8 tid = str8_substr(pathname, r1u64(7, pathname.size - 8)); - entry_out->stack_tid = (pid_t)u64_from_str8(tid, 10); - } - - is_parsed = true; - }while(0); - return(is_parsed); -} - -//////////////////////////////// -//~ rjf: @demon_os_hooks Main Layer Initialization - -internal void -demon_os_init(void){ - demon_lnx_event_arena = arena_alloc(); -} - -//////////////////////////////// -//~ rjf: @demon_os_hooks Running/Halting - -internal DEMON_EventList -demon_os_run(Arena *arena, DEMON_OS_RunCtrls *controls){ - DEMON_EventList result = {0}; - - if (demon_ent_root == 0){ - demon_push_event(arena, &result, DEMON_EventKind_NotInitialized); - } - else if (demon_ent_root->first == 0 && !demon_lnx_new_process_pending){ - demon_push_event(arena, &result, DEMON_EventKind_NotAttached); - } - else{ - Temp scratch = scratch_begin(&arena, 1); - - // use queued events if there are any - if (demon_lnx_queued_events.first != 0){ - // copy event queue - for (DEMON_Event *node = demon_lnx_queued_events.first; - node != 0; - node = node->next){ - DEMON_Event *copy = push_array_no_zero(arena, DEMON_Event, 1); - MemoryCopyStruct(copy, node); - SLLQueuePush(result.first, result.last, copy); - } - result.count = demon_lnx_queued_events.count; - - // zero stored queue - MemoryZeroStruct(&demon_lnx_queued_events); - arena_clear(demon_lnx_event_arena); - } - - // get the single step thread (if any) - DEMON_Entity *single_step_thread = controls->single_step_thread; - - // do setup - B32 did_setup = false; - U8 *trap_swap_bytes = 0; - - if (result.first == 0){ - // TODO(allen): per-Architecture implementation of single steps - // set single step bit - if (single_step_thread != 0){ - switch (single_step_thread->arch){ - case Architecture_x86: - { - // TODO(allen): possibly buggy - SYMS_RegX86 regs = {0}; - demon_os_read_regs_x86(single_step_thread, ®s); - regs.eflags.u32 |= 0x100; - demon_os_write_regs_x86(single_step_thread, ®s); - }break; - - case Architecture_x64: - { - // TODO(allen): possibly buggy - SYMS_RegX64 regs = {0}; - demon_os_read_regs_x64(single_step_thread, ®s); - regs.rflags.u64 |= 0x100; - demon_os_write_regs_x64(single_step_thread, ®s); - }break; - } - } - - // TODO(allen): per-Architecture implementation of traps - trap_swap_bytes = push_array_no_zero(scratch.arena, U8, controls->trap_count); - - { - DEMON_OS_Trap *trap = controls->traps; - for (U64 i = 0; i < controls->trap_count; i += 1, trap += 1){ - if (demon_os_read_memory(trap->process, trap_swap_bytes + i, trap->address, 1)){ - U8 int3 = 0xCC; - demon_os_write_memory(trap->process, trap->address, &int3, 1); - } - else{ - trap_swap_bytes[i] = 0xCC; - } - } - } - - did_setup = true; - } - - // do run - B32 did_run = false; - if (did_setup){ - // continue non-frozen threads - DEMON_LNX_EntityNode *resume_threads = 0; - for (DEMON_Entity *process = demon_ent_root->first; - process != 0; - process = process->next){ - if (process->kind == DEMON_EntityKind_Process){ - - // determine if this process is frozen - B32 process_is_frozen = false; - if (controls->run_entities_are_processes){ - for (U64 i = 0; i < controls->run_entity_count; i += 1){ - if (controls->run_entities[i] == process){ - process_is_frozen = true; - break; - } - } - } - - for (DEMON_Entity *thread = process->first; - thread != 0; - thread = thread->next){ - if (thread->kind == DEMON_EntityKind_Thread){ - // determine if this thread is frozen - B32 is_frozen = false; - - if (controls->single_step_thread != 0 && - controls->single_step_thread != thread){ - is_frozen = true; - } - else{ - - if (controls->run_entities_are_processes){ - is_frozen = process_is_frozen; - } - else{ - for (U64 i = 0; i < controls->run_entity_count; i += 1){ - if (controls->run_entities[i] == thread){ - is_frozen = true; - break; - } - } - } - - if (controls->run_entities_are_unfrozen){ - is_frozen = !is_frozen; - } - } - - // continue if not frozen - if (!is_frozen){ - errno = 0; - ptrace(PTRACE_CONT, (pid_t)thread->id, 0, 0); - DEMON_LNX_EntityNode *thread_node = push_array_no_zero(scratch.arena, DEMON_LNX_EntityNode, 1); - SLLStackPush(resume_threads, thread_node); - thread_node->entity = thread; - } - } - } - } - } - - // get next stop - wait_for_stop: - B32 did_dummy_stop = false; - int status = 0; - pid_t wait_id = waitpid(-1, &status, __WALL); - - // increment demon time - demon_time += 1; - - // handle devent - DEMON_Entity *thread = demon_ent_map_entity_from_id(DEMON_EntityKind_Thread, wait_id); - if (thread == 0){ - if (wait_id >= 0){ - // TODO(allen): this isn't a great situation! From what I can tell there's no - // options that I am super happy with for going from unknown tid -> pid. - // We can parse it out of /proc//status; but I don't want to do that until - // I'm forced to, because it seems like this shouldn't happen if the ptrace - // API works correctly and we don't have any bugs in our demon entity system. - } - } - else{ - B32 thread_exit = false; - U64 exit_code = 0; - - DEMON_Entity *process = thread->parent; - // NOTE(allen): hitting this assert should never ever be possible, if our entities - // are wired up correctly. it doesn't matter what ptrace or waitpid are doing. - Assert(process != 0); - - // read register info - U64 instruction_pointer = 0; - union{ SYMS_RegX86 x86; SYMS_RegX64 x64; } regs = {0}; - - switch (thread->arch){ - case Architecture_x86: - { - demon_os_read_regs_x86(thread, ®s.x86); - instruction_pointer = regs.x86.eip.u32; - }break; - - case Architecture_x64: - { - demon_os_read_regs_x64(thread, ®s.x64); - instruction_pointer = regs.x64.rip.u64; - }break; - } - - // check stop status - if (WIFEXITED(status)){ - thread_exit = true; - } - if (WIFSIGNALED(status)){ - exit_code = WTERMSIG(status); - thread_exit = true; - } - - // extra event list - DEMON_EventList stop_events = {0}; - - if (WIFSTOPPED(status)){ - switch (WSTOPSIG(status)){ - case SIGTRAP: - { - switch (status >> 8){ - case (SIGTRAP | (PTRACE_EVENT_EXIT << 8)): - { - // TODO(allen): (not sure actually, study this part) - thread_exit = true; - }break; - - case (SIGTRAP | (PTRACE_EVENT_CLONE << 8)): - { - // new thread coming - unsigned long new_tid = 0; - int get_message_result = ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_tid); - if (get_message_result == -1){ - // TODO(allen): this isn't right, time to give up on getting this process. - // this will likely lead to getting unrecognized wait_id s later. So we need - // this stuff in the log to make sense of it still. - } - else{ - // thread entity - DEMON_Entity *new_thread = demon_ent_new(process, DEMON_EntityKind_Thread, new_tid); - demon_thread_count += 1; - DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(new_thread); - thread_ext->expecting_dummy_sigstop = true; - - // thread event - DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_CreateThread); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(new_thread); - } - }break; - - case (SIGTRAP | (PTRACE_EVENT_FORK << 8)): - case (SIGTRAP | (PTRACE_EVENT_VFORK << 8)): - { - // new process coming - unsigned long new_pid = 0; - int get_message_result = ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_pid); - if (get_message_result == -1){ - // TODO(allen): this isn't right, time to give up on getting this process. - // this will likely lead to getting unrecognized wait_id s later. So we need - // this stuff in the log to make sense of it still. - } - else{ - Architecture arch = demon_lnx_arch_from_pid(new_pid); - - // process entity - DEMON_Entity *new_process = demon_ent_new(demon_ent_root, DEMON_EntityKind_Process, new_pid); - new_process->arch = arch; - new_process->ext_u64 = demon_lnx_open_memory_fd_for_pid(new_pid); - - demon_lnx_new_process_pending = false; - - // thread entity - DEMON_Entity *new_thread = demon_ent_new(new_process, DEMON_EntityKind_Thread, new_pid); - demon_thread_count += 1; - DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(new_thread); - thread_ext->expecting_dummy_sigstop = true; - - // process event - { - DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_CreateProcess); - e->process = demon_ent_handle_from_ptr(new_process); - } - - // thread event - { - DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_CreateThread); - e->process = demon_ent_handle_from_ptr(new_process); - e->thread = demon_ent_handle_from_ptr(new_thread); - } - } - }break; - - default: - { - // check single step - DEMON_EventKind e_kind = DEMON_EventKind_Trap; - if (thread == single_step_thread){ - e_kind = DEMON_EventKind_SingleStep; - } - - // check bp - if (e_kind == DEMON_EventKind_Trap){ - DEMON_OS_Trap *trap = controls->traps; - for (U64 i = 0; i < controls->trap_count; i += 1, trap += 1){ - if (trap->process == process && trap->address == instruction_pointer - 1){ - e_kind = DEMON_EventKind_Breakpoint; - break; - } - } - } - - // adjust ip after breakpoint - if (e_kind == DEMON_EventKind_Breakpoint){ - // TODO(allen): possibly buggy - switch (thread->arch){ - case Architecture_x86: - { - instruction_pointer -= 1; - regs.x86.eip.u32 = instruction_pointer; - demon_os_write_regs_x86(thread, ®s.x86); - }break; - - case Architecture_x64: - { - instruction_pointer -= 1; - regs.x64.rip.u64 = instruction_pointer; - demon_os_write_regs_x64(thread, ®s.x64); - }break; - } - } - - // event - DEMON_Event *e = demon_push_event(arena, &stop_events, e_kind); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(thread); - e->instruction_pointer = instruction_pointer; - }break; - } - }break; - - case SIGSTOP: - { - // TODO(allen): we need to figure out how we want to tell apart: - // SIGSTOP All-Stop, SIGSTOP Halt, SIGSTOP "User" - // what we're doing right now == big-time race conditions - - DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(thread); - - if (thread_ext->expecting_dummy_sigstop){ - thread_ext->expecting_dummy_sigstop = false; - did_dummy_stop = true; - } - else if (demon_lnx_already_has_halt_injection){ - DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_Halt); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(thread); - e->instruction_pointer = instruction_pointer; - } - else{ - // TODO(allen): a signal we don't want to mess with (except to record that it happened maybe) - // we should "hand it back" - } - }break; - - default: - { -#if 0 - // these are a little special. the program cannot continue after these - // unless the user first does something to change the state (move the IP, change a variable, w/e) - case SIGABRT:case SIGFPE:case SIGSEGV: -#endif - - // event - DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_Exception); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(thread); - e->instruction_pointer = instruction_pointer; - e->signo = WSTOPSIG(status); - }break; - } - } - - // entity cleanup - if (thread_exit){ - if (thread->id == process->id){ - // generate events for threads & modules - for (DEMON_Entity *entity = process->first; - entity != 0; - entity = entity->next){ - if (entity->kind == DEMON_EntityKind_Thread){ - DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_ExitThread); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(entity); - } - else{ - DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_UnloadModule); - e->process = demon_ent_handle_from_ptr(process); - e->module = demon_ent_handle_from_ptr(entity); - } - } - - // exit event - DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_ExitProcess); - e->process = demon_ent_handle_from_ptr(process); - e->code = exit_code; - - // free entity - demon_ent_release_root_and_children(process); - } - else{ - // exit event - DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_ExitThread); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(thread); - e->code = exit_code; - - // free entity - demon_ent_release_root_and_children(thread); - } - } - - // update all module lists (for each process ...) - DEMON_EventList module_change_events = {0}; - - for (DEMON_Entity *proc_node = demon_ent_root->first; - proc_node != 0; - proc_node = proc_node->next){ - DEMON_LNX_ModuleNode *first_module = demon_lnx_module_list_from_process(scratch.arena, proc_node); - - DEMON_LNX_EntityNode *first_unloaded = 0; - DEMON_LNX_EntityNode *last_unloaded = 0; - - // compute the delta (mark known modules, save list of unloaded modules) - for (DEMON_Entity *entity = proc_node->first; - entity != 0; - entity = entity->next){ - if (entity->kind == DEMON_EntityKind_Module){ - U64 base = entity->id; - U64 name = entity->ext_u64; - B32 still_exists = false; - for (DEMON_LNX_ModuleNode *module_node = first_module; - module_node != 0; - module_node = module_node->next){ - if (module_node->vaddr == base && module_node->name == name){ - module_node->already_known = true; - still_exists = true; - break; - } - } - if (!still_exists){ - DEMON_LNX_EntityNode *node = push_array_no_zero(scratch.arena, DEMON_LNX_EntityNode, 1); - SLLQueuePush(first_unloaded, last_unloaded, node); - node->entity = entity; - } - } - } - - // handle unloads - for (DEMON_LNX_EntityNode *unloaded_node = first_unloaded; - unloaded_node != 0; - unloaded_node = unloaded_node->next){ - DEMON_Entity *module = unloaded_node->entity; - - // event - { - DEMON_Event *e = demon_push_event(arena, &module_change_events, DEMON_EventKind_UnloadModule); - e->process = demon_ent_handle_from_ptr(proc_node); - e->module = demon_ent_handle_from_ptr(module); - } - - // free entity - demon_ent_release_root_and_children(module); - } - - // handle loads - for (DEMON_LNX_ModuleNode *module_node = first_module; - module_node != 0; - module_node = module_node->next){ - if (!module_node->already_known){ - // entity - DEMON_Entity *module = demon_ent_new(proc_node, DEMON_EntityKind_Module, module_node->vaddr); - demon_module_count += 1; - module->ext_u64 = module_node->name; - - // event - { - DEMON_Event *e = demon_push_event(arena, &module_change_events, DEMON_EventKind_LoadModule); - e->process = demon_ent_handle_from_ptr(proc_node); - e->module = demon_ent_handle_from_ptr(module); - e->address = module_node->vaddr; - e->size = module_node->size; - } - } - } - } - - // concat the events list (with module changes first) - result.count = module_change_events.count + stop_events.count; - result.first = module_change_events.first; - result.last = module_change_events.last; - if (stop_events.first != 0){ - if (result.first != 0){ - result.last->next = stop_events.first; - result.last = stop_events.last; - } - else{ - result.first = stop_events.first; - result.last = stop_events.last; - } - } - } - - // do we have a reason to keep going? - B32 skip_this_stop = false; - if (did_dummy_stop && result.count == 0){ - skip_this_stop = true; - } - - // ignore this stop, resume and wait again - if (skip_this_stop){ - if (wait_id != 0){ - ptrace(PTRACE_CONT, (pid_t)wait_id, 0, 0); - } - goto wait_for_stop; - } - - // stop all running threads - for (DEMON_LNX_EntityNode *node = resume_threads; - node != 0; - node = node->next){ - DEMON_Entity *thread = node->entity; - pid_t thread_id = (pid_t)thread->id; - if (thread_id != wait_id){ - union sigval sv = {0}; - sigqueue(thread_id, SIGSTOP, sv); - - DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(thread); - thread_ext->expecting_dummy_sigstop = true; - } - } - - did_run = true; - } - - // cleanup - if (did_run){ - // TODO(allen): per-Architecture - // unset traps - { - DEMON_OS_Trap *trap = controls->traps; - for (U64 i = 0; i < controls->trap_count; i += 1, trap += 1){ - U8 og_byte = trap_swap_bytes[i]; - if (og_byte != 0xCC){ - demon_os_write_memory(trap->process, trap->address, &og_byte, 1); - } - } - } - - // TODO(allen): per-Architecture - // unset single step bit - // the single step bit is automatically unset whenever we single step - // but if *something else* happened, it will still be there ready to - // confound us later; so here we're just being sure it's taken out. - if (single_step_thread != 0){ - // TODO(allen): possibly buggy - switch (single_step_thread->arch){ - case Architecture_x86: - { - SYMS_RegX86 regs = {0}; - demon_os_read_regs_x86(single_step_thread, ®s); - regs.eflags.u32 &= ~0x100; - demon_os_write_regs_x86(single_step_thread, ®s); - }break; - - case Architecture_x64: - { - SYMS_RegX64 regs = {0}; - demon_os_read_regs_x64(single_step_thread, ®s); - regs.rflags.u64 &= ~0x100; - demon_os_write_regs_x64(single_step_thread, ®s); - }break; - } - } - } - - scratch_end(scratch); - } - - return(result); -} - -internal void -demon_os_halt(U64 code, U64 user_data){ - if (demon_ent_root != 0 && !demon_lnx_already_has_halt_injection){ - DEMON_Entity *process = demon_ent_root->first; - if (process != 0){ - demon_lnx_already_has_halt_injection = true; - demon_lnx_halt_code = code; - demon_lnx_halt_user_data = user_data; - union sigval sv = {0}; - if (sigqueue(process->id, SIGSTOP, sv) == -1){ - demon_lnx_already_has_halt_injection = false; - } - } - } -} - -// NOTE(allen): siginfo hint from old code: -#if 0 -{ - switch (siginfo.si_code){ - // SI_KERNEL (hit int3; 0xCC) - case 0x80: - { - // TODO(allen): breakpoint event - }break; - - // TRAP_UNK, TRAP_HWBKPT, TRAP_BRKPT, TRAP_TRACE - case 0x5: case 0x4: case 0x1: case 0x2: - { - // TODO(allen): breakpoint event (?) - }break; - - case 0x3: case 0x0: - { - // TODO(allen): do nothing I guess? - }break; - } -} -#endif - -//////////////////////////////// -//~ rjf: @demon_os_hooks Target Process Launching/Attaching/Killing/Detaching/Halting - -internal U32 -demon_os_launch_process(OS_LaunchOptions *options){ - U32 result = 0; - Temp scratch = scratch_begin(0, 0); - - // arrange options - char *binary = 0; - char **args = 0; - if (options->cmd_line.node_count > 0){ - args = push_array_no_zero(scratch.arena, char*, options->cmd_line.node_count + 1); - char **arg_ptr = args; - for (String8Node *node = options->cmd_line.first; - node != 0; - node = node->next, arg_ptr += 1){ - String8 string = push_str8_copy(scratch.arena, node->string); - *arg_ptr = (char*)string.str; - } - *arg_ptr = 0; - binary = args[0]; - } - - char *path = 0; - { - String8 string = push_str8_copy(scratch.arena, options->path); - path = (char*)string.str; - } - - char **env = 0; - if (options->env.node_count > 0){ - env = push_array_no_zero(scratch.arena, char*, options->env.node_count + 1); - char **env_ptr = env; - for (String8Node *node = options->env.first; - node != 0; - node = node->next, env_ptr += 1){ - String8 string = push_str8_copy(scratch.arena, node->string); - *env_ptr = (char*)string.str; - } - *env_ptr = 0; - } - - // fork - if (binary != 0){ - pid_t pid = fork(); - if (pid == -1){ - // TODO(allen): fork error - } - else if (pid == 0){ - // NOTE(allen): child process - int ptrace_result = ptrace(PTRACE_TRACEME, 0, 0, 0); - if (ptrace_result != -1){ - int chdir_result = chdir(path); - if (chdir_result != -1){ - execve(binary, args, env); - } - } - // failed to init fully; abort so the parent can clean up the child - abort(); - } - else{ - // NOTE(allen): parent process - - // wait for child - int status = 0; - pid_t wait_id = waitpid(pid, &status, __WALL); - - // determine child launch status - enum{ - LaunchCode_Null, - LaunchCode_FailBeforePtrace, - LaunchCode_FailAfterPtrace, - LaunchCode_Success, - }; - U32 launch_result = LaunchCode_Null; - // NOTE(allen): if wait_id != pid we don't know what that means; study that case before - // deciding how error handling around it works. - if (wait_id == pid){ - if (WIFSTOPPED(status)){ - if (WSTOPSIG(status) == SIGTRAP){ - launch_result = LaunchCode_Success; - } - else{ - launch_result = LaunchCode_FailAfterPtrace; - } - } - else{ - launch_result = LaunchCode_FailBeforePtrace; - } - } - - // handle launch result - switch (launch_result){ - default: - { - // TODO(allen): error that we do not understand - }break; - - case LaunchCode_FailBeforePtrace: - { - // TODO(allen): child ptrace init failed - }break; - - case LaunchCode_FailAfterPtrace: - { - // need to specifically pull the exit status out of the child - // or it will sit around as a zombie forever since it is ptraced. - B32 cleanup_good = false; - int detach_result = ptrace(PTRACE_DETACH, pid, 0, (void*)SIGCONT); - if (detach_result != -1){ - int status_cleanup = 0; - pid_t wait_id_cleanup = waitpid(pid, &status_cleanup, __WALL); - if (wait_id_cleanup == pid){ - cleanup_good = true; - } - } - if (cleanup_good){ - // TODO(allen): child init failed - } - else{ - // TODO(allen): child init failed; something went wrong and a process may have leaked - } - }break; - - case LaunchCode_Success: - { - int setoptions_result = ptrace(PTRACE_SETOPTIONS, pid, 0, PtrFromInt(demon_lnx_ptrace_options)); - if (setoptions_result == -1){ - // TODO(allen): ptrace setup failed; need to kill the child and clean it up - } - else{ - result = pid; - - Architecture arch = demon_lnx_arch_from_pid(pid); - - // process entity - DEMON_Entity *process = demon_ent_new(demon_ent_root, DEMON_EntityKind_Process, pid); - demon_proc_count += 1; - process->arch = arch; - process->ext_u64 = demon_lnx_open_memory_fd_for_pid(pid); - - // thread entity - DEMON_Entity *thread = demon_ent_new(process, DEMON_EntityKind_Thread, pid); - demon_thread_count += 1; - - // process event - { - DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, - DEMON_EventKind_CreateProcess); - e->process = demon_ent_handle_from_ptr(process); - } - - // thread event - { - DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, - DEMON_EventKind_CreateThread); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(thread); - } - - // get module list - DEMON_LNX_ModuleNode *module_list = demon_lnx_module_list_from_process(scratch.arena, process); - - // for each module ... - for (DEMON_LNX_ModuleNode *node = module_list; - node != 0; - node = node->next){ - // module entity - DEMON_Entity *module = demon_ent_new(process, DEMON_EntityKind_Module, node->vaddr); - demon_module_count += 1; - module->ext_u64 = node->name; - - // event - { - DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, - DEMON_EventKind_LoadModule); - e->process = demon_ent_handle_from_ptr(process); - e->module = demon_ent_handle_from_ptr(module); - e->address = node->vaddr; - e->size = node->size; - } - } - - // handshake event - { - DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, - DEMON_EventKind_HandshakeComplete); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(thread); - } - } - }break; - } - } - } - - scratch_end(scratch); - return(result); -} - -internal B32 -demon_os_attach_process(U32 pid){ - B32 result = false; - - Temp scratch = scratch_begin(0, 0); - DEMON_LNX_AttachNode *attachments = 0; - DEMON_LNX_AttachNode *the_process = 0; - - // TODO(allen): double check that this logic only lets us - // "attach" when pid is the id of the main thread of a process. - - // attach this process - B32 attached_proc = false; - if (kill(pid, 0) == -1){ - // TODO(allen): process does not exist - } - else{ - attached_proc = demon_lnx_attach_pid(scratch.arena, pid, &the_process); - if (the_process != 0){ - SLLStackPush(attachments, the_process); - } - } - - // open thread list - if (attached_proc){ - String8 threads_path = push_str8f(scratch.arena, "/proc/%d/task", pid); - DIR *proc_dir = opendir((char*)threads_path.str); - if (proc_dir == 0){ - // TODO(allen): could not read proc threads somehow; no good! - } - else{ - - // attach all threads - B32 attached_all_threads = true; - for (;;){ - struct dirent *entry = readdir(proc_dir); - if (entry == 0){ - break; - } - - String8 name = str8_cstring(entry->d_name); - if (str8_is_integer(name, 10)){ - pid_t tid = u64_from_str8(name, 10); - if (tid != pid){ - DEMON_LNX_AttachNode *new_attachment = 0; - B32 attached_this_thread = demon_lnx_attach_pid(scratch.arena, tid, &new_attachment); - if (new_attachment != 0){ - SLLStackPush(attachments, new_attachment); - } - if (!attached_this_thread){ - attached_all_threads = false; - break; - } - } - } - } - closedir(proc_dir); - - if (attached_all_threads){ - result = true; - } - } - } - - // initialize new entities on success - if (result){ - Architecture arch = demon_lnx_arch_from_pid(the_process->pid); - - // process entity - DEMON_Entity *process = demon_ent_new(demon_ent_root, DEMON_EntityKind_Process, the_process->pid); - demon_proc_count += 1; - process->arch = arch; - process->ext_u64 = demon_lnx_open_memory_fd_for_pid(the_process->pid); - - // process event - { - DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, - DEMON_EventKind_CreateProcess); - e->process = demon_ent_handle_from_ptr(process); - } - - // TODO(allen): happens on windows here? - - for (DEMON_LNX_AttachNode *node = attachments; - node != 0; - node = node->next){ - DEMON_Entity *thread = demon_ent_new(process, DEMON_EntityKind_Thread, node->pid); - demon_thread_count += 1; - - // thread event - { - DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, - DEMON_EventKind_CreateThread); - e->process = demon_ent_handle_from_ptr(process); - e->thread = demon_ent_handle_from_ptr(thread); - } - } - - // TODO(allen): sync modules in process - } - - // cleanup on failure - else{ - for (DEMON_LNX_AttachNode *node = attachments; - node != 0; - node = node->next){ - ptrace(PTRACE_DETACH, node->pid, 0, (void*)SIGCONT); - } - } - - scratch_end(scratch); - return(result); -} - -internal B32 -demon_os_kill_process(DEMON_Entity *process, U32 exit_code){ - B32 result = false; - if (process != 0){ - if (kill(process->id, SIGKILL) != -1){ - result = true; - } - } - return(result); -} - -internal B32 -demon_os_detach_process(DEMON_Entity *process){ - B32 result = false; - if (process != 0){ - int detach_result = ptrace(PTRACE_DETACH, process->id, 0, 0); - result = (detach_result != -1); - } - return(0); -} - -//////////////////////////////// -//~ rjf: @demon_os_hooks Entity Functions - -//- rjf: cleanup - -internal void -demon_os_entity_cleanup(DEMON_Entity *entity) -{ - // NOTE(rjf): no-op -} - -//- rjf: introspection - -internal String8 -demon_os_full_path_from_module(Arena *arena, DEMON_Entity *module){ - DEMON_Entity *process = module->parent; - int memory_fd = (int)process->ext_u64; - U64 name_va = module->ext_u64; - String8 result = demon_lnx_read_memory_str(arena, memory_fd, name_va); - return(result); -} - -internal U64 -demon_os_stack_base_vaddr_from_thread(DEMON_Entity *thread){ - Temp scratch = scratch_begin(0, 0); - - U64 stack_base = 0; - - DEMON_Entity *process = thread->parent; - - // id for main thread is zero - B32 is_main_thread = (thread->id == process->id); - pid_t match_tid = is_main_thread ? 0 : thread->id; - - // open /proc/$pid/maps - int maps = demon_lnx_open_maps(process->id); - - // look for entry with stack markings and matching thread id - for (;;){ - DEMON_LNX_MapsEntry e; - Temp temp = temp_begin(scratch.arena); - if (!demon_lnx_next_map(temp.arena, maps, &e)){ - break; - } - if (e.type == DEMON_LNX_MapsEntryType_Stack && e.stack_tid == match_tid){ - stack_base = e.address_lo; - break; - } - temp_end(temp); - } - - scratch_end(scratch); - return(stack_base); -} - -internal U64 -demon_os_tls_root_vaddr_from_thread(DEMON_Entity *thread){ - U64 result = 0; - switch (thread->arch){ - case Architecture_x64: - case Architecture_x86: - { - U32 fsbase = 0; - pid_t tid = (pid_t)thread->id; - if (ptrace(PT_GETFSBASE, tid, (void*)&fsbase, 0) != -1){ - result = (U64)fsbase; - } - if (thread->arch == Architecture_x64){ - result += 8; - } - else{ - result += 4; - } - }break; - } - return(result); -} - -//- rjf: target process memory allocation/protection - -internal U64 -demon_os_reserve_memory(DEMON_Entity *process, U64 size){ - U64 result = 0; - NotImplemented; - return(result); -} - -internal void -demon_os_set_memory_protect_flags(DEMON_Entity *process, U64 page_vaddr, U64 size, DEMON_MemoryProtectFlags flags){ - NotImplemented; -} - -internal void -demon_os_release_memory(DEMON_Entity *process, U64 vaddr, U64 size){ - NotImplemented; -} - -//- rjf: target process memory reading/writing - -internal U64 -demon_os_read_memory(DEMON_Entity *process, void *dst, U64 src_address, U64 size){ - int memory_fd = (int)process->ext_u64; - U64 result = demon_lnx_read_memory(memory_fd, dst, src_address, size); - return(result); -} - -internal B32 -demon_os_write_memory(DEMON_Entity *process, U64 dst_address, void *src, U64 size){ - int memory_fd = (int)process->ext_u64; - B32 result = demon_lnx_write_memory(memory_fd, dst_address, src, size); - return(result); -} - -//- rjf: thread registers reading/writing - -internal B32 -demon_os_read_regs_x86(DEMON_Entity *thread, SYMS_RegX86 *dst){ - B32 result = false; - NotImplemented; - return(result); -} - -internal B32 -demon_os_write_regs_x86(DEMON_Entity *thread, SYMS_RegX86 *src){ - B32 result = false; - NotImplemented; - return(result); -} - -internal B32 -demon_os_read_regs_x64(DEMON_Entity *thread, SYMS_RegX64 *dst){ - pid_t tid = (pid_t)thread->id; - - // gpr - B32 got_gpr = false; - DEMON_LNX_UserX64 ctx = {0}; - struct iovec iov_gpr = {0}; - iov_gpr.iov_len = sizeof(ctx); - iov_gpr.iov_base = &ctx; - if (ptrace(PTRACE_GETREGSET, tid, (void*)NT_PRSTATUS, &iov_gpr) != -1){ - demon_lnx_regs_x64_from_usr_regs_x64(dst, &ctx.regs); - got_gpr = true; - } - - // fpr - B32 got_fpr = false; - if (got_gpr){ - B32 got_xsave = false; - { - U8 xsave_buffer[KB(4)]; - struct iovec iov_xsave = {0}; - iov_xsave.iov_len = sizeof(xsave_buffer); - iov_xsave.iov_base = xsave_buffer; - if (ptrace(PTRACE_GETREGSET, tid, (void*)NT_X86_XSTATE, &iov_xsave) != -1){ - SYMS_XSave *xsave = (SYMS_XSave*)xsave_buffer; - syms_x64_regs__set_full_regs_from_xsave_legacy(dst, &xsave->legacy); - - // TODO(allen): this is a lie; ymm can technically move around - // we need some more low-level-assembly-fu to do this hardcore. - B32 has_ymm_registers = ((xsave->header.xstate_bv & 4) != 0); - if (has_ymm_registers){ - syms_x64_regs__set_full_regs_from_xsave_avx_extension(dst, xsave->ymmh); - } - - got_xsave = true; - } - } - - B32 got_fxsave = false; - if (!got_xsave){ - SYMS_XSaveLegacy fxsave = {0}; - struct iovec iov_fxsave = {0}; - iov_fxsave.iov_len = sizeof(fxsave); - iov_fxsave.iov_base = &fxsave; - if (ptrace(PTRACE_GETREGSET, tid, (void*)NT_FPREGSET, &iov_fxsave) != -1){ - syms_x64_regs__set_full_regs_from_xsave_legacy(dst, &fxsave); - got_fxsave = true; - } - } - - if (got_xsave || got_fxsave){ - got_fpr = true; - } - } - - // debug - B32 got_debug = false; - if (got_fpr){ - got_debug = true; - SYMS_Reg32 *dr_d = &dst->dr0; - for (U32 i = 0; i < 8; i += 1, dr_d += 1){ - if (i != 4 && i != 5){ - U64 offset = OffsetOf(DEMON_LNX_UserX64, u_debugreg[i]); - errno = 0; - int peek_result = ptrace(PTRACE_PEEKUSER, tid, PtrFromInt(offset), 0); - if (errno == 0){ - dr_d->u32 = (U32)peek_result; - } - else{ - got_debug = false; - } - } - } - } - - // got everything - B32 result = got_debug; - return(result); -} - -internal B32 -demon_os_write_regs_x64(DEMON_Entity *thread, SYMS_RegX64 *src){ - pid_t tid = (pid_t)thread->id; - - // gpr - DEMON_LNX_UserX64 ctx = {0}; - demon_lnx_usr_regs_x64_from_regs_x64(&ctx.regs, src); - - struct iovec iov_gpr = {0}; - iov_gpr.iov_base = &ctx; - iov_gpr.iov_len = sizeof(ctx); - int gpr_result = ptrace(PTRACE_SETREGSET, tid, (void*)NT_PRSTATUS, &iov_gpr); - B32 gpr_success = (gpr_result != -1); - - // fpr - int xsave_result = 0; - int fxsave_result = 0; - - { - U8 xsave_buffer[KB(4)] = {0}; - SYMS_XSave *xsave = (SYMS_XSave*)xsave_buffer; - syms_x64_regs__set_xsave_legacy_from_full_regs(&xsave->legacy, src); - - xsave->header.xstate_bv = 7; - - // TODO(allen): this is a lie; ymm can technically move around - // we need some more low-level-assembly-fu to do this hardcore. - syms_x64_regs__set_xsave_avx_extension_from_full_regs(xsave->ymmh, src); - - { - struct iovec iov_xsave = {0}; - iov_xsave.iov_base = &xsave; - iov_xsave.iov_len = sizeof(xsave); - xsave_result = ptrace(PTRACE_SETREGSET, tid, (void*)NT_X86_XSTATE, &iov_xsave); - } - - if (xsave_result == -1){ - struct iovec iov_fxsave = {0}; - iov_fxsave.iov_base = &xsave->legacy; - iov_fxsave.iov_len = sizeof(xsave->legacy); - fxsave_result = ptrace(PTRACE_SETREGSET, tid, (void*)NT_FPREGSET, &iov_fxsave); - } - } - - B32 fpr_success = (xsave_result != -1 || fxsave_result != -1); - - // debug - B32 dr_success = true; - { - SYMS_Reg32 *dr_s = &src->dr0; - for (U32 i = 0; i < 8; i += 1, dr_s += 1){ - if (i != 4 && i != 5){ - U64 offset = OffsetOf(DEMON_LNX_UserX64, u_debugreg[i]); - errno = 0; - int poke_result = ptrace(PTRACE_POKEUSER, tid, PtrFromInt(offset), dr_s->u32); - if (poke_result == -1){ - dr_success = false; - } - } - } - } - - // assemble result - B32 result = (gpr_success && fpr_success && dr_success); - - return(result); -} - -//////////////////////////////// -//~ rjf: @demon_os_hooks Process Listing - -internal void -demon_os_proc_iter_begin(DEMON_ProcessIter *iter){ - DIR *dir = opendir("/proc"); - MemoryZeroStruct(iter); - iter->v[0] = IntFromPtr(dir); -} - -internal B32 -demon_os_proc_iter_next(Arena *arena, DEMON_ProcessIter *iter, DEMON_ProcessInfo *info_out){ - // scan for a process id - B32 got_pid = false; - String8 pid_string = {0}; - - DIR *dir = (DIR*)PtrFromInt(iter->v[0]); - if (dir != 0 && iter->v[1] == 0){ - for (;;){ - struct dirent *d = readdir(dir); - if (d == 0){ - break; - } - - // check file name is integer - String8 file_name = str8_cstring((char*)d->d_name); - B32 is_integer = str8_is_integer(file_name, 10); - - // break on integers (which represent processes) - if (is_integer){ - got_pid = true; - pid_string = file_name; - break; - } - } - } - - // mark iterator dead if nothing found - if (!got_pid){ - iter->v[1] = 1; - } - - // if got process id convert pid -> process info - B32 result = false; - if (got_pid){ - // determine the name we will report - pid_t pid = u64_from_str8(pid_string, 10); - String8 name = demon_lnx_executable_path_from_pid(arena, pid); - if (name.size == 0){ - name = str8_lit(""); - } - - // finish conversion - info_out->name = name; - info_out->pid = pid; - result = true; - } - - return(result); -} - -internal void -demon_os_proc_iter_end(DEMON_ProcessIter *iter){ - DIR *dir = (DIR*)PtrFromInt(iter->v[0]); - if (dir != 0){ - closedir(dir); - } - MemoryZeroStruct(iter); -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +// TODO(allen): run controls: ignore_previous_exception + +//////////////////////////////// +//~ allen: Elf Parsing Code + +#include "syms/syms_elf_inc.c" + +//////////////////////////////// +//~ rjf: Globals + +global B32 demon_lnx_already_has_halt_injection = false; +global U64 demon_lnx_halt_code = 0; +global U64 demon_lnx_halt_user_data = 0; + +global B32 demon_lnx_new_process_pending = false; + +global Arena *demon_lnx_event_arena = 0; +global DEMON_EventList demon_lnx_queued_events = {0}; + +global U32 demon_lnx_ptrace_options = (PTRACE_O_TRACEEXIT| + PTRACE_O_EXITKILL| + PTRACE_O_TRACEFORK| + PTRACE_O_TRACEVFORK| + PTRACE_O_TRACECLONE); + +//////////////////////////////// +//~ rjf: Helpers + +internal DEMON_LNX_ThreadExt* +demon_lnx_thread_ext(DEMON_Entity *entity){ + DEMON_LNX_ThreadExt *result = (DEMON_LNX_ThreadExt*)&entity->ext; + return(result); +} + +internal B32 +demon_lnx_attach_pid(Arena *arena, pid_t pid, DEMON_LNX_AttachNode **new_node){ + B32 result = false; + + int attach_result = ptrace(PTRACE_ATTACH, pid, 0, 0); + if (attach_result == -1){ + // TODO(allen): attach denied + } + else{ + // return a new attachment node as soon as the ptrace exists. we use these nodes + // for cleanup on failure *and* for initializing on success. either way we need + // to see all new attachments whether or not they fully initialized correctly. + DEMON_LNX_AttachNode *proc_attachment = push_array_no_zero(arena, DEMON_LNX_AttachNode, 1); + proc_attachment->next = 0; + proc_attachment->pid = pid; + *new_node = proc_attachment; + + int status = 0; + pid_t wait_id = waitpid(pid, &status, __WALL); + // NOTE(allen): if wait_id != pid we don't know what that means; study that case before + // deciding how error handling around it works. + if (wait_id == pid){ + int setoptions_result = ptrace(PTRACE_SETOPTIONS, pid, 0, PtrFromInt(demon_lnx_ptrace_options)); + if (setoptions_result == -1){ + // TODO(allen): setup failed + } + else{ + result = true; + } + } + } + + return(result); +} + +internal String8 +demon_lnx_executable_path_from_pid(Arena *arena, pid_t pid){ + // get symbolic path + Temp scratch = scratch_begin(&arena, 1); + String8 exe_symbol_path = push_str8f(scratch.arena, "/proc/%d/exe", pid); + + // try to read the link for a bit + Temp restore_point = temp_begin(arena); + B32 got_final_result = false; + U8 *buffer = 0; + int size = 0; + S64 cap = PATH_MAX; + for (S64 r = 0; r < 4; cap *= 2, r += 1){ + temp_end(restore_point); + buffer = push_array_no_zero(arena, U8, cap); + size = readlink((char*)exe_symbol_path.str, (char*)buffer, cap); + if (size < cap){ + got_final_result = true; + break; + } + } + + // finalize result + String8 result = {0}; + if (!got_final_result || size == -1){ + temp_end(restore_point); + } + else{ + arena_pop(arena, (cap - size - 1)); + result = str8(buffer, size + 1); + } + + scratch_end(scratch); + return(result); +} + +internal int +demon_lnx_open_memory_fd_for_pid(pid_t pid){ + Temp scratch = scratch_begin(0, 0); + String8 memory_path = push_str8f(scratch.arena, "/proc/%i/mem", pid); + int result = open((char*)memory_path.str, O_RDWR); + scratch_end(scratch); + return(result); +} + +internal Architecture +demon_lnx_arch_from_pid(pid_t pid){ + Temp scratch = scratch_begin(0, 0); + Architecture result = Architecture_Null; + + // exe path + String8 exe_path = demon_lnx_executable_path_from_pid(scratch.arena, pid); + + // handle to exe + int exe_fd = -1; + if (exe_path.size != 0){ + exe_fd = open((char*)exe_path.str, O_RDONLY); + } + + // elf identification + B32 is_elf = false; + U8 e_ident[SYMS_ElfIdentifier_NIDENT] = {0}; + if (exe_fd >= 0){ + if (pread(exe_fd, e_ident, sizeof(e_ident), 0) == sizeof(e_ident)){ + is_elf = (e_ident[SYMS_ElfIdentifier_MAG0] == 0x7f && + e_ident[SYMS_ElfIdentifier_MAG1] == 'E' && + e_ident[SYMS_ElfIdentifier_MAG2] == 'L' && + e_ident[SYMS_ElfIdentifier_MAG3] == 'F'); + } + } + + // elf class + U8 elf_class = 0; + if (is_elf){ + elf_class = e_ident[SYMS_ElfIdentifier_CLASS]; + } + + // exe header data + SYMS_ElfEhdr64 ehdr = {0}; + switch (elf_class){ + case 1: + { + SYMS_ElfEhdr32 ehdr32 = {0}; + if (pread(exe_fd, &ehdr32, sizeof(ehdr32), 0) == sizeof(ehdr32)){ + ehdr = syms_elf_ehdr64_from_ehdr32(ehdr32); + } + }break; + + case 2: + { + pread(exe_fd, &ehdr, sizeof(ehdr), 0); + }break; + } + + // determine machine type + switch (ehdr.e_machine){ + case SYMS_ElfMachineKind_386: + { + result = Architecture_x86; + }break; + + case SYMS_ElfMachineKind_ARM: + { + result = Architecture_arm32; + }break; + + case SYMS_ElfMachineKind_X86_64: + { + result = Architecture_x64; + }break; + + case SYMS_ElfMachineKind_AARCH64: + { + result = Architecture_arm64; + }break; + } + + scratch_end(scratch); + return(result); +} + +internal DEMON_LNX_ProcessAux +demon_lnx_aux_from_pid(pid_t pid, Architecture arch){ + DEMON_LNX_ProcessAux result = {0}; + B32 addr_32bit = (arch == Architecture_x86 || arch == Architecture_arm32); + + // open aux data + Temp scratch = scratch_begin(0, 0); + String8 auxv_symbol_path = push_str8f(scratch.arena, "/proc/%d/auxv", pid); + int aux_fd = open((char*)auxv_symbol_path.str, O_RDONLY); + + // scan aux data + if (aux_fd >= 0){ + for (;;){ + result.filled = true; + + // read next aux + U64 type = 0; + U64 val = 0; + if (addr_32bit){ + SYMS_ElfAuxv32 aux; + if (read(aux_fd, &aux, sizeof(aux)) != sizeof(aux)){ + goto brkloop; + } + type = aux.a_type; + val = aux.a_val; + } + else{ + SYMS_ElfAuxv64 aux; + if (read(aux_fd, &aux, sizeof(aux)) != sizeof(aux)){ + goto brkloop; + } + type = aux.a_type; + val = aux.a_val; + } + + // place value in result + switch (type){ + default:break; + case SYMS_ElfAuxType_NULL: goto brkloop; break; + case SYMS_ElfAuxType_PHNUM: result.phnum = val; break; + case SYMS_ElfAuxType_PHENT: result.phent = val; break; + case SYMS_ElfAuxType_PHDR: result.phdr = val; break; + case SYMS_ElfAuxType_EXECFN: result.execfn = val; break; + } + } + brkloop:; + + close(aux_fd); + } + + scratch_end(scratch); + return(result); +} + +internal DEMON_LNX_PhdrInfo +demon_lnx_phdr_info_from_memory(int memory_fd, B32 is_32bit, U64 phvaddr, U64 phentsize, U64 phcount){ + DEMON_LNX_PhdrInfo result = {0}; + result.range.min = max_U64; + + // how much phdr will we read? + U64 phdr_size_expected = (is_32bit?sizeof(SYMS_ElfPhdr32):sizeof(SYMS_ElfPhdr64)); + U64 phdr_stride = (phentsize?phentsize:phdr_size_expected); + U64 phdr_read_size = ClampTop(phdr_stride, phdr_size_expected); + + // scan table + U64 va = phvaddr; + for (U64 i = 0; i < phcount; i += 1, va += phdr_stride){ + + // get type and range + SYMS_ElfPKind p_type = 0; + U64 p_vaddr = 0; + U64 p_memsz = 0; + + if (is_32bit){ + SYMS_ElfPhdr32 phdr32 = {0}; + demon_lnx_read_memory(memory_fd, &phdr32, va, phdr_read_size); + p_type = phdr32.p_type; + p_vaddr = phdr32.p_vaddr; + p_memsz = phdr32.p_memsz; + } + else{ + SYMS_ElfPhdr64 phdr64 = {0}; + demon_lnx_read_memory(memory_fd, &phdr64, va, phdr_read_size); + p_type = phdr64.p_type; + p_vaddr = phdr64.p_vaddr; + p_memsz = phdr64.p_memsz; + } + + // save useful info + switch (p_type){ + case SYMS_ElfPKind_Dynamic: + { + result.dynamic = p_vaddr; + }break; + case SYMS_ElfPKind_Load: + { + U64 min = p_vaddr; + U64 max = p_vaddr + p_memsz; + result.range.min = Min(result.range.min, min); + result.range.max = Max(result.range.max, max); + }break; + } + } + + return(result); +} + +internal DEMON_LNX_ModuleNode* +demon_lnx_module_list_from_process(Arena *arena, DEMON_Entity *process){ + Architecture arch = (Architecture)process->arch; + B32 is_32bit = (arch == Architecture_x86 || arch == Architecture_arm32); + int memory_fd = (int)process->ext_u64; + + // aux from pid + DEMON_LNX_ProcessAux aux = demon_lnx_aux_from_pid((pid_t)process->id, arch); + + // extract info from program headers + DEMON_LNX_PhdrInfo phdr_info = demon_lnx_phdr_info_from_memory(memory_fd, is_32bit, + aux.phdr, aux.phent, aux.phnum); + + // linkmap first from memory space & dyn address + U64 first_linkmap_va = 0; + if (phdr_info.dynamic != 0){ + U64 off = phdr_info.dynamic; + for (;;){ + SYMS_ElfDyn64 dyn = {0}; + if (is_32bit){ + SYMS_ElfDyn32 dyn32 = {0}; + demon_lnx_read_memory(memory_fd, &dyn32, off, sizeof(dyn32)); + dyn.tag = dyn32.tag; + dyn.val = dyn32.val; + off += sizeof(dyn32); + } + else{ + demon_lnx_read_memory(memory_fd, &dyn, off, sizeof(dyn)); + off += sizeof(dyn); + } + + if (dyn.tag == SYMS_ElfDynTag_NULL){ + break; + } + + if (dyn.tag == SYMS_ElfDynTag_PLTGOT){ + // True for x86 and x64 + // vas[0] virtual address of .dynamic + // vas[2] callback for resolving function address of relocation and if successful jumps to it. + // + // Code that sets up PLTGOT is in glibc/sysdeps/x86_64/dl_machine.h -> elf_machine_runtime_setup + U64 vas_off = dyn.val; + U64 vas[3] = {0}; + demon_lnx_read_memory(memory_fd, vas, vas_off, sizeof(vas)); + first_linkmap_va = vas[1]; + break; + } + } + } + + // setup output list + DEMON_LNX_ModuleNode *first = 0; + DEMON_LNX_ModuleNode *last = 0; + + // main module + { + DEMON_LNX_ModuleNode *node = push_array(arena, DEMON_LNX_ModuleNode, 1); + SLLQueuePush(first, last, node); + node->vaddr = phdr_info.range.min; + node->size = phdr_info.range.max - phdr_info.range.min; + node->name = aux.execfn; + } + + // iterate link maps + if (first_linkmap_va != 0){ + U64 linkmap_va = first_linkmap_va; + + for (;;){ + SYMS_ElfLinkMap64 linkmap = {0}; + if (is_32bit){ + // TOOD(nick): endian awarness + SYMS_ElfLinkMap32 linkmap32 = {0}; + demon_lnx_read_memory(memory_fd, &linkmap32, linkmap_va, sizeof(linkmap32)); + linkmap.base = linkmap32.base; + linkmap.name = linkmap32.name; + linkmap.ld = linkmap32.ld; + linkmap.next = linkmap32.next; + } + else{ + demon_lnx_read_memory(memory_fd, &linkmap, linkmap_va, sizeof(linkmap)); + } + + if (linkmap.base != 0){ + // find phdrs for this module + SYMS_U64 phvaddr = 0; + SYMS_U64 phentsize = 0; + SYMS_U64 phcount = 0; + + if (is_32bit){ + SYMS_ElfEhdr32 ehdr = {0}; + demon_lnx_read_memory(memory_fd, &ehdr, linkmap.base, sizeof(ehdr)); + phvaddr = ehdr.e_phoff + linkmap.base; + phentsize = ehdr.e_phentsize; + phcount = ehdr.e_phnum; + } + else{ + SYMS_ElfEhdr64 ehdr = {0}; + demon_lnx_read_memory(memory_fd, &ehdr, linkmap.base, sizeof(ehdr)); + phvaddr = ehdr.e_phoff + linkmap.base; + phentsize = ehdr.e_phentsize; + phcount = ehdr.e_phnum; + } + + // extract info from phdrs + DEMON_LNX_PhdrInfo module_phdr_info = demon_lnx_phdr_info_from_memory(memory_fd, is_32bit, + phvaddr, phentsize, phcount); + + // save module node + DEMON_LNX_ModuleNode *node = push_array(arena, DEMON_LNX_ModuleNode, 1); + SLLQueuePush(first, last, node); + node->vaddr = linkmap.base; + node->size = module_phdr_info.range.max - module_phdr_info.range.min; + node->name = linkmap.name; + } + + linkmap_va = linkmap.next; + if (linkmap_va == 0){ + break; + } + } + } + + return(first); +} + +internal U64 +demon_lnx_read_memory(int memory_fd, void *dst, U64 src, U64 size){ + U64 bytes_read = 0; + U8 *ptr = (U8*)dst; + U8 *opl = ptr + size; + U64 cursor = src; + for (;ptr < opl;){ + size_t to_read = (size_t)(opl - ptr); + ssize_t actual_read = pread(memory_fd, ptr, to_read, cursor); + if (actual_read == -1){ + break; + } + ptr += actual_read; + cursor += actual_read; + bytes_read += actual_read; + } + return(bytes_read); +} + +internal B32 +demon_lnx_write_memory(int memory_fd, U64 dst, void *src, U64 size){ + B32 result = true; + U8 *ptr = (U8*)src; + U8 *opl = ptr + size; + U64 cursor = dst; + for (;ptr < opl;){ + size_t to_write = (size_t)(opl - ptr); + ssize_t actual_write = pwrite(memory_fd, ptr, to_write, cursor); + if (actual_write == -1){ + result = false; + break; + } + ptr += actual_write; + cursor += actual_write; + } + return(result); +} + +internal String8 +demon_lnx_read_memory_str(Arena *arena, int memory_fd, U64 address){ + // TODO(allen): this could be done better with a demon_lnx_read_memory + // that returns a read amount instead of a success/fail. + + // scan piece by piece + Temp scratch = scratch_begin(&arena, 1); + String8List list = {0}; + + U64 max_cap = 256; + U64 cap = max_cap; + U64 read_p = address; + for (;;){ + U8 *block = push_array(scratch.arena, U8, cap); + for (;cap > 0;){ + if (demon_lnx_read_memory(memory_fd, block, read_p, cap)){ + break; + } + cap /= 2; + } + read_p += cap; + + U64 block_opl = 0; + for (;block_opl < cap; block_opl += 1){ + if (block[block_opl] == 0){ + break; + } + } + + if (block_opl > 0){ + str8_list_push(scratch.arena, &list, str8(block, block_opl)); + } + + if (block_opl < cap || cap == 0){ + break; + } + } + + // assemble results + String8 result = str8_list_join(arena, &list, 0); + scratch_end(scratch); + return(result); +} + +internal void +demon_lnx_regs_x64_from_usr_regs_x64(SYMS_RegX64 *dst, DEMON_LNX_UserRegsX64 *src){ + dst->rax.u64 = src->rax; + dst->rcx.u64 = src->rcx; + dst->rdx.u64 = src->rdx; + dst->rbx.u64 = src->rbx; + dst->rsp.u64 = src->rsp; + dst->rbp.u64 = src->rbp; + dst->rsi.u64 = src->rsi; + dst->rdi.u64 = src->rdi; + dst->r8.u64 = src->r8; + dst->r9.u64 = src->r9; + dst->r10.u64 = src->r10; + dst->r11.u64 = src->r11; + dst->r12.u64 = src->r12; + dst->r13.u64 = src->r13; + dst->r14.u64 = src->r14; + dst->r15.u64 = src->r15; + dst->cs.u16 = src->cs; + dst->ds.u16 = src->ds; + dst->es.u16 = src->es; + dst->fs.u16 = src->fs; + dst->gs.u16 = src->gs; + dst->ss.u16 = src->ss; + dst->fsbase.u64 = src->fsbase; + dst->gsbase.u64 = src->gsbase; + dst->rip.u64 = src->rip; + dst->rflags.u64 = src->rflags; +} + +internal void +demon_lnx_usr_regs_x64_from_regs_x64(DEMON_LNX_UserRegsX64 *dst, SYMS_RegX64 *src){ + dst->rax = src->rax.u64; + dst->rcx = src->rcx.u64; + dst->rdx = src->rdx.u64; + dst->rbx = src->rbx.u64; + dst->rsp = src->rsp.u64; + dst->rbp = src->rbp.u64; + dst->rsi = src->rsi.u64; + dst->rdi = src->rdi.u64; + dst->r8 = src->r8.u64; + dst->r9 = src->r9.u64; + dst->r10 = src->r10.u64; + dst->r11 = src->r11.u64; + dst->r12 = src->r12.u64; + dst->r13 = src->r13.u64; + dst->r14 = src->r14.u64; + dst->r15 = src->r15.u64; + dst->cs = src->cs.u16; + dst->ds = src->ds.u16; + dst->es = src->es.u16; + dst->fs = src->fs.u16; + dst->gs = src->gs.u16; + dst->ss = src->ss.u16; + dst->fsbase = src->fsbase.u64; + dst->gsbase = src->gsbase.u64; + dst->rip = src->rip.u64; + dst->rflags = src->rflags.u64; +} + +//////////////////////////////// + +internal String8 +demon_lnx_read_int_string(Arena *arena, int fd, int radix){ + String8 integer = str8(0,0); + + int to_read = 0; + int to_seek = 0; + for (;;){ + char b = 0; + if (read(fd, &b, sizeof(b)) == 0){ + break; + } + to_seek += 1; + if ( ! char_is_digit(b, radix)){ + break; + } + to_read += 1; + } + + if (lseek(fd, -to_seek, SEEK_CUR) != -1) { + char *buf = push_array_no_zero(arena, char, to_read + 1); + read(fd, buf, to_read); + buf[to_read] = '\0'; + integer = str8((U8*)buf, (U64)to_read); + } + + return(integer); +} + +internal U64 +demon_lnx_read_u64(int fd, int radix){ + Temp scratch = scratch_begin(0, 0); + String8 integer = demon_lnx_read_int_string(scratch.arena, fd, radix); + U64 result = u64_from_str8(integer, radix); + scratch_end(scratch); + return(result); +} + +internal S64 +demon_lnx_read_s64(int fd, int radix){ + Temp scratch = scratch_begin(0, 0); + String8 integer = demon_lnx_read_int_string(scratch.arena, fd, radix); + S64 result = s64_from_str8(integer, radix); + scratch_end(scratch); + return(result); +} + +internal B32 +demon_lnx_read_expect(int fd, char expect){ + char got = 0; + read(fd, &got, sizeof(got)); + B32 result = (got == expect); + if (!result){ + lseek(fd, -1, SEEK_CUR); + } + return(result); +} + +internal int +demon_lnx_read_whitespace(int fd){ + int whitespace_size = 0; + for (;;){ + if (!demon_lnx_read_expect(fd, ' ')){ + if (!demon_lnx_read_expect(fd, '\t')){ + break; + } + } + whitespace_size += 1; + } + return whitespace_size; +} + +internal String8 +demon_lnx_read_string(Arena *arena, int fd){ + String8 result = str8(0,0); + + int to_read = 0; + int to_seek = 0; + for (;;){ + char b = 0; + if (read(fd, &b, sizeof(b)) == 0) { + break; + } + to_seek += 1; + if (b == '\0' || b == '\n'){ + break; + } + to_read += 1; + } + + if (to_seek > 0 && lseek(fd, -to_seek, SEEK_CUR) != -1){ + char *buf = push_array_no_zero(arena, char, to_read + 1); + read(fd, buf, to_read); + buf[to_read] = '\0'; + result = str8((U8*)buf, to_read); + } + + return(result); +} + +internal int +demon_lnx_open_maps(pid_t pid){ + Temp scratch = scratch_begin(0, 0); + String8 path = push_str8f(scratch.arena, "/proc/%d/maps", pid); + int maps = open((char*)path.str, O_RDONLY); + scratch_end(scratch); + return(maps); +} + +internal B32 +demon_lnx_next_map(Arena *arena, int maps, DEMON_LNX_MapsEntry *entry_out){ + B32 is_parsed = false; + MemoryZeroStruct(entry_out); + do{ + U64 address_lo = 0; + U64 address_hi = 0; + DEMON_LNX_PermFlags perms = 0; + U64 offset = 0; + U64 dev_major = 0; + U64 dev_minor = 0; + U64 inode = 0; + String8 pathname = str8(0,0); + + // address range + address_lo = demon_lnx_read_u64(maps, 16); + if (!demon_lnx_read_expect(maps, '-')){ + break; + } + address_hi = demon_lnx_read_u64(maps, 16); + if (demon_lnx_read_whitespace(maps) == 0){ + break; + } + + // permission flags + char b; + if (read(maps, &b, sizeof(b)) == 0){ + break; + } + if (b=='r'){ + perms |= DEMON_LNX_PermFlags_Read; + } + if (read(maps, &b, sizeof(b)) == 0){ + break; + } + if (b=='w'){ + perms |= DEMON_LNX_PermFlags_Write; + } + if (read(maps, &b, sizeof(b)) == 0){ + break; + } + if (b=='x'){ + perms |= DEMON_LNX_PermFlags_Exec; + } + if (read(maps, &b, sizeof(b)) == 0){ + break; + } + if (b == 'p'){ + perms |= DEMON_LNX_PermFlags_Private; + } + if (demon_lnx_read_whitespace(maps) == 0){ + break; + } + + // offset + offset = demon_lnx_read_u64(maps, 16); + if (demon_lnx_read_whitespace(maps) == 0){ + break; + } + + // dev + dev_major = demon_lnx_read_u64(maps, 10); + if (!demon_lnx_read_expect(maps, ':')){ + break; + } + dev_minor = demon_lnx_read_u64(maps, 10); + if (demon_lnx_read_whitespace(maps) == 0){ + break; + } + + // inode + inode = demon_lnx_read_u64(maps, 10); + if (demon_lnx_read_whitespace(maps) == 10){ + break; + } + + // pathname + pathname = demon_lnx_read_string(arena, maps); + + // emit entry if en + b = 0; + read(maps, &b, sizeof(b)); + if (b != '\n' && b != '\0') { + break; + } + + // fill result + entry_out->address_lo = address_lo; + entry_out->address_hi = address_hi; + entry_out->perms = perms; + entry_out->offset = offset; + entry_out->dev_major = (U32)dev_major; + entry_out->dev_minor = (U32)dev_minor; + entry_out->inode = inode; + entry_out->pathname = pathname; + entry_out->type = DEMON_LNX_MapsEntryType_Null; + entry_out->stack_tid = 0; + + if (str8_match(pathname, str8_lit("/"), StringMatchFlag_RightSideSloppy)){ + entry_out->type = DEMON_LNX_MapsEntryType_Path; + } else if (str8_match(pathname, str8_lit("[heap]"), 0)){ + entry_out->type = DEMON_LNX_MapsEntryType_Heap; + } else if (str8_match(pathname, str8_lit("[stack]"), 0)){ + entry_out->type = DEMON_LNX_MapsEntryType_Stack; + } else if (str8_match(pathname, str8_lit("[stack:"), StringMatchFlag_RightSideSloppy)){ + entry_out->type = DEMON_LNX_MapsEntryType_Stack; + String8 tid = str8_substr(pathname, r1u64(7, pathname.size - 8)); + entry_out->stack_tid = (pid_t)u64_from_str8(tid, 10); + } + + is_parsed = true; + }while(0); + return(is_parsed); +} + +//////////////////////////////// +//~ rjf: @demon_os_hooks Main Layer Initialization + +internal void +demon_os_init(void){ + demon_lnx_event_arena = arena_alloc(); +} + +//////////////////////////////// +//~ rjf: @demon_os_hooks Running/Halting + +internal DEMON_EventList +demon_os_run(Arena *arena, DEMON_OS_RunCtrls *controls){ + DEMON_EventList result = {0}; + + if (demon_ent_root == 0){ + demon_push_event(arena, &result, DEMON_EventKind_NotInitialized); + } + else if (demon_ent_root->first == 0 && !demon_lnx_new_process_pending){ + demon_push_event(arena, &result, DEMON_EventKind_NotAttached); + } + else{ + Temp scratch = scratch_begin(&arena, 1); + + // use queued events if there are any + if (demon_lnx_queued_events.first != 0){ + // copy event queue + for (DEMON_Event *node = demon_lnx_queued_events.first; + node != 0; + node = node->next){ + DEMON_Event *copy = push_array_no_zero(arena, DEMON_Event, 1); + MemoryCopyStruct(copy, node); + SLLQueuePush(result.first, result.last, copy); + } + result.count = demon_lnx_queued_events.count; + + // zero stored queue + MemoryZeroStruct(&demon_lnx_queued_events); + arena_clear(demon_lnx_event_arena); + } + + // get the single step thread (if any) + DEMON_Entity *single_step_thread = controls->single_step_thread; + + // do setup + B32 did_setup = false; + U8 *trap_swap_bytes = 0; + + if (result.first == 0){ + // TODO(allen): per-Architecture implementation of single steps + // set single step bit + if (single_step_thread != 0){ + switch (single_step_thread->arch){ + case Architecture_x86: + { + // TODO(allen): possibly buggy + SYMS_RegX86 regs = {0}; + demon_os_read_regs_x86(single_step_thread, ®s); + regs.eflags.u32 |= 0x100; + demon_os_write_regs_x86(single_step_thread, ®s); + }break; + + case Architecture_x64: + { + // TODO(allen): possibly buggy + SYMS_RegX64 regs = {0}; + demon_os_read_regs_x64(single_step_thread, ®s); + regs.rflags.u64 |= 0x100; + demon_os_write_regs_x64(single_step_thread, ®s); + }break; + } + } + + // TODO(allen): per-Architecture implementation of traps + trap_swap_bytes = push_array_no_zero(scratch.arena, U8, controls->trap_count); + + { + DEMON_OS_Trap *trap = controls->traps; + for (U64 i = 0; i < controls->trap_count; i += 1, trap += 1){ + if (demon_os_read_memory(trap->process, trap_swap_bytes + i, trap->address, 1)){ + U8 int3 = 0xCC; + demon_os_write_memory(trap->process, trap->address, &int3, 1); + } + else{ + trap_swap_bytes[i] = 0xCC; + } + } + } + + did_setup = true; + } + + // do run + B32 did_run = false; + if (did_setup){ + // continue non-frozen threads + DEMON_LNX_EntityNode *resume_threads = 0; + for (DEMON_Entity *process = demon_ent_root->first; + process != 0; + process = process->next){ + if (process->kind == DEMON_EntityKind_Process){ + + // determine if this process is frozen + B32 process_is_frozen = false; + if (controls->run_entities_are_processes){ + for (U64 i = 0; i < controls->run_entity_count; i += 1){ + if (controls->run_entities[i] == process){ + process_is_frozen = true; + break; + } + } + } + + for (DEMON_Entity *thread = process->first; + thread != 0; + thread = thread->next){ + if (thread->kind == DEMON_EntityKind_Thread){ + // determine if this thread is frozen + B32 is_frozen = false; + + if (controls->single_step_thread != 0 && + controls->single_step_thread != thread){ + is_frozen = true; + } + else{ + + if (controls->run_entities_are_processes){ + is_frozen = process_is_frozen; + } + else{ + for (U64 i = 0; i < controls->run_entity_count; i += 1){ + if (controls->run_entities[i] == thread){ + is_frozen = true; + break; + } + } + } + + if (controls->run_entities_are_unfrozen){ + is_frozen = !is_frozen; + } + } + + // continue if not frozen + if (!is_frozen){ + errno = 0; + ptrace(PTRACE_CONT, (pid_t)thread->id, 0, 0); + DEMON_LNX_EntityNode *thread_node = push_array_no_zero(scratch.arena, DEMON_LNX_EntityNode, 1); + SLLStackPush(resume_threads, thread_node); + thread_node->entity = thread; + } + } + } + } + } + + // get next stop + wait_for_stop: + B32 did_dummy_stop = false; + int status = 0; + pid_t wait_id = waitpid(-1, &status, __WALL); + + // increment demon time + demon_time += 1; + + // handle devent + DEMON_Entity *thread = demon_ent_map_entity_from_id(DEMON_EntityKind_Thread, wait_id); + if (thread == 0){ + if (wait_id >= 0){ + // TODO(allen): this isn't a great situation! From what I can tell there's no + // options that I am super happy with for going from unknown tid -> pid. + // We can parse it out of /proc//status; but I don't want to do that until + // I'm forced to, because it seems like this shouldn't happen if the ptrace + // API works correctly and we don't have any bugs in our demon entity system. + } + } + else{ + B32 thread_exit = false; + U64 exit_code = 0; + + DEMON_Entity *process = thread->parent; + // NOTE(allen): hitting this assert should never ever be possible, if our entities + // are wired up correctly. it doesn't matter what ptrace or waitpid are doing. + Assert(process != 0); + + // read register info + U64 instruction_pointer = 0; + union{ SYMS_RegX86 x86; SYMS_RegX64 x64; } regs = {0}; + + switch (thread->arch){ + case Architecture_x86: + { + demon_os_read_regs_x86(thread, ®s.x86); + instruction_pointer = regs.x86.eip.u32; + }break; + + case Architecture_x64: + { + demon_os_read_regs_x64(thread, ®s.x64); + instruction_pointer = regs.x64.rip.u64; + }break; + } + + // check stop status + if (WIFEXITED(status)){ + thread_exit = true; + } + if (WIFSIGNALED(status)){ + exit_code = WTERMSIG(status); + thread_exit = true; + } + + // extra event list + DEMON_EventList stop_events = {0}; + + if (WIFSTOPPED(status)){ + switch (WSTOPSIG(status)){ + case SIGTRAP: + { + switch (status >> 8){ + case (SIGTRAP | (PTRACE_EVENT_EXIT << 8)): + { + // TODO(allen): (not sure actually, study this part) + thread_exit = true; + }break; + + case (SIGTRAP | (PTRACE_EVENT_CLONE << 8)): + { + // new thread coming + unsigned long new_tid = 0; + int get_message_result = ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_tid); + if (get_message_result == -1){ + // TODO(allen): this isn't right, time to give up on getting this process. + // this will likely lead to getting unrecognized wait_id s later. So we need + // this stuff in the log to make sense of it still. + } + else{ + // thread entity + DEMON_Entity *new_thread = demon_ent_new(process, DEMON_EntityKind_Thread, new_tid); + demon_thread_count += 1; + DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(new_thread); + thread_ext->expecting_dummy_sigstop = true; + + // thread event + DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_CreateThread); + e->process = demon_ent_handle_from_ptr(process); + e->thread = demon_ent_handle_from_ptr(new_thread); + } + }break; + + case (SIGTRAP | (PTRACE_EVENT_FORK << 8)): + case (SIGTRAP | (PTRACE_EVENT_VFORK << 8)): + { + // new process coming + unsigned long new_pid = 0; + int get_message_result = ptrace(PTRACE_GETEVENTMSG, wait_id, 0, &new_pid); + if (get_message_result == -1){ + // TODO(allen): this isn't right, time to give up on getting this process. + // this will likely lead to getting unrecognized wait_id s later. So we need + // this stuff in the log to make sense of it still. + } + else{ + Architecture arch = demon_lnx_arch_from_pid(new_pid); + + // process entity + DEMON_Entity *new_process = demon_ent_new(demon_ent_root, DEMON_EntityKind_Process, new_pid); + new_process->arch = arch; + new_process->ext_u64 = demon_lnx_open_memory_fd_for_pid(new_pid); + + demon_lnx_new_process_pending = false; + + // thread entity + DEMON_Entity *new_thread = demon_ent_new(new_process, DEMON_EntityKind_Thread, new_pid); + demon_thread_count += 1; + DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(new_thread); + thread_ext->expecting_dummy_sigstop = true; + + // process event + { + DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_CreateProcess); + e->process = demon_ent_handle_from_ptr(new_process); + } + + // thread event + { + DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_CreateThread); + e->process = demon_ent_handle_from_ptr(new_process); + e->thread = demon_ent_handle_from_ptr(new_thread); + } + } + }break; + + default: + { + // check single step + DEMON_EventKind e_kind = DEMON_EventKind_Trap; + if (thread == single_step_thread){ + e_kind = DEMON_EventKind_SingleStep; + } + + // check bp + if (e_kind == DEMON_EventKind_Trap){ + DEMON_OS_Trap *trap = controls->traps; + for (U64 i = 0; i < controls->trap_count; i += 1, trap += 1){ + if (trap->process == process && trap->address == instruction_pointer - 1){ + e_kind = DEMON_EventKind_Breakpoint; + break; + } + } + } + + // adjust ip after breakpoint + if (e_kind == DEMON_EventKind_Breakpoint){ + // TODO(allen): possibly buggy + switch (thread->arch){ + case Architecture_x86: + { + instruction_pointer -= 1; + regs.x86.eip.u32 = instruction_pointer; + demon_os_write_regs_x86(thread, ®s.x86); + }break; + + case Architecture_x64: + { + instruction_pointer -= 1; + regs.x64.rip.u64 = instruction_pointer; + demon_os_write_regs_x64(thread, ®s.x64); + }break; + } + } + + // event + DEMON_Event *e = demon_push_event(arena, &stop_events, e_kind); + e->process = demon_ent_handle_from_ptr(process); + e->thread = demon_ent_handle_from_ptr(thread); + e->instruction_pointer = instruction_pointer; + }break; + } + }break; + + case SIGSTOP: + { + // TODO(allen): we need to figure out how we want to tell apart: + // SIGSTOP All-Stop, SIGSTOP Halt, SIGSTOP "User" + // what we're doing right now == big-time race conditions + + DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(thread); + + if (thread_ext->expecting_dummy_sigstop){ + thread_ext->expecting_dummy_sigstop = false; + did_dummy_stop = true; + } + else if (demon_lnx_already_has_halt_injection){ + DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_Halt); + e->process = demon_ent_handle_from_ptr(process); + e->thread = demon_ent_handle_from_ptr(thread); + e->instruction_pointer = instruction_pointer; + } + else{ + // TODO(allen): a signal we don't want to mess with (except to record that it happened maybe) + // we should "hand it back" + } + }break; + + default: + { +#if 0 + // these are a little special. the program cannot continue after these + // unless the user first does something to change the state (move the IP, change a variable, w/e) + case SIGABRT:case SIGFPE:case SIGSEGV: +#endif + + // event + DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_Exception); + e->process = demon_ent_handle_from_ptr(process); + e->thread = demon_ent_handle_from_ptr(thread); + e->instruction_pointer = instruction_pointer; + e->signo = WSTOPSIG(status); + }break; + } + } + + // entity cleanup + if (thread_exit){ + if (thread->id == process->id){ + // generate events for threads & modules + for (DEMON_Entity *entity = process->first; + entity != 0; + entity = entity->next){ + if (entity->kind == DEMON_EntityKind_Thread){ + DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_ExitThread); + e->process = demon_ent_handle_from_ptr(process); + e->thread = demon_ent_handle_from_ptr(entity); + } + else{ + DEMON_Event *e = demon_push_event(arena, &result, DEMON_EventKind_UnloadModule); + e->process = demon_ent_handle_from_ptr(process); + e->module = demon_ent_handle_from_ptr(entity); + } + } + + // exit event + DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_ExitProcess); + e->process = demon_ent_handle_from_ptr(process); + e->code = exit_code; + + // free entity + demon_ent_release_root_and_children(process); + } + else{ + // exit event + DEMON_Event *e = demon_push_event(arena, &stop_events, DEMON_EventKind_ExitThread); + e->process = demon_ent_handle_from_ptr(process); + e->thread = demon_ent_handle_from_ptr(thread); + e->code = exit_code; + + // free entity + demon_ent_release_root_and_children(thread); + } + } + + // update all module lists (for each process ...) + DEMON_EventList module_change_events = {0}; + + for (DEMON_Entity *proc_node = demon_ent_root->first; + proc_node != 0; + proc_node = proc_node->next){ + DEMON_LNX_ModuleNode *first_module = demon_lnx_module_list_from_process(scratch.arena, proc_node); + + DEMON_LNX_EntityNode *first_unloaded = 0; + DEMON_LNX_EntityNode *last_unloaded = 0; + + // compute the delta (mark known modules, save list of unloaded modules) + for (DEMON_Entity *entity = proc_node->first; + entity != 0; + entity = entity->next){ + if (entity->kind == DEMON_EntityKind_Module){ + U64 base = entity->id; + U64 name = entity->ext_u64; + B32 still_exists = false; + for (DEMON_LNX_ModuleNode *module_node = first_module; + module_node != 0; + module_node = module_node->next){ + if (module_node->vaddr == base && module_node->name == name){ + module_node->already_known = true; + still_exists = true; + break; + } + } + if (!still_exists){ + DEMON_LNX_EntityNode *node = push_array_no_zero(scratch.arena, DEMON_LNX_EntityNode, 1); + SLLQueuePush(first_unloaded, last_unloaded, node); + node->entity = entity; + } + } + } + + // handle unloads + for (DEMON_LNX_EntityNode *unloaded_node = first_unloaded; + unloaded_node != 0; + unloaded_node = unloaded_node->next){ + DEMON_Entity *module = unloaded_node->entity; + + // event + { + DEMON_Event *e = demon_push_event(arena, &module_change_events, DEMON_EventKind_UnloadModule); + e->process = demon_ent_handle_from_ptr(proc_node); + e->module = demon_ent_handle_from_ptr(module); + } + + // free entity + demon_ent_release_root_and_children(module); + } + + // handle loads + for (DEMON_LNX_ModuleNode *module_node = first_module; + module_node != 0; + module_node = module_node->next){ + if (!module_node->already_known){ + // entity + DEMON_Entity *module = demon_ent_new(proc_node, DEMON_EntityKind_Module, module_node->vaddr); + demon_module_count += 1; + module->ext_u64 = module_node->name; + + // event + { + DEMON_Event *e = demon_push_event(arena, &module_change_events, DEMON_EventKind_LoadModule); + e->process = demon_ent_handle_from_ptr(proc_node); + e->module = demon_ent_handle_from_ptr(module); + e->address = module_node->vaddr; + e->size = module_node->size; + } + } + } + } + + // concat the events list (with module changes first) + result.count = module_change_events.count + stop_events.count; + result.first = module_change_events.first; + result.last = module_change_events.last; + if (stop_events.first != 0){ + if (result.first != 0){ + result.last->next = stop_events.first; + result.last = stop_events.last; + } + else{ + result.first = stop_events.first; + result.last = stop_events.last; + } + } + } + + // do we have a reason to keep going? + B32 skip_this_stop = false; + if (did_dummy_stop && result.count == 0){ + skip_this_stop = true; + } + + // ignore this stop, resume and wait again + if (skip_this_stop){ + if (wait_id != 0){ + ptrace(PTRACE_CONT, (pid_t)wait_id, 0, 0); + } + goto wait_for_stop; + } + + // stop all running threads + for (DEMON_LNX_EntityNode *node = resume_threads; + node != 0; + node = node->next){ + DEMON_Entity *thread = node->entity; + pid_t thread_id = (pid_t)thread->id; + if (thread_id != wait_id){ + union sigval sv = {0}; + sigqueue(thread_id, SIGSTOP, sv); + + DEMON_LNX_ThreadExt *thread_ext = demon_lnx_thread_ext(thread); + thread_ext->expecting_dummy_sigstop = true; + } + } + + did_run = true; + } + + // cleanup + if (did_run){ + // TODO(allen): per-Architecture + // unset traps + { + DEMON_OS_Trap *trap = controls->traps; + for (U64 i = 0; i < controls->trap_count; i += 1, trap += 1){ + U8 og_byte = trap_swap_bytes[i]; + if (og_byte != 0xCC){ + demon_os_write_memory(trap->process, trap->address, &og_byte, 1); + } + } + } + + // TODO(allen): per-Architecture + // unset single step bit + // the single step bit is automatically unset whenever we single step + // but if *something else* happened, it will still be there ready to + // confound us later; so here we're just being sure it's taken out. + if (single_step_thread != 0){ + // TODO(allen): possibly buggy + switch (single_step_thread->arch){ + case Architecture_x86: + { + SYMS_RegX86 regs = {0}; + demon_os_read_regs_x86(single_step_thread, ®s); + regs.eflags.u32 &= ~0x100; + demon_os_write_regs_x86(single_step_thread, ®s); + }break; + + case Architecture_x64: + { + SYMS_RegX64 regs = {0}; + demon_os_read_regs_x64(single_step_thread, ®s); + regs.rflags.u64 &= ~0x100; + demon_os_write_regs_x64(single_step_thread, ®s); + }break; + } + } + } + + scratch_end(scratch); + } + + return(result); +} + +internal void +demon_os_halt(U64 code, U64 user_data){ + if (demon_ent_root != 0 && !demon_lnx_already_has_halt_injection){ + DEMON_Entity *process = demon_ent_root->first; + if (process != 0){ + demon_lnx_already_has_halt_injection = true; + demon_lnx_halt_code = code; + demon_lnx_halt_user_data = user_data; + union sigval sv = {0}; + if (sigqueue(process->id, SIGSTOP, sv) == -1){ + demon_lnx_already_has_halt_injection = false; + } + } + } +} + +// NOTE(allen): siginfo hint from old code: +#if 0 +{ + switch (siginfo.si_code){ + // SI_KERNEL (hit int3; 0xCC) + case 0x80: + { + // TODO(allen): breakpoint event + }break; + + // TRAP_UNK, TRAP_HWBKPT, TRAP_BRKPT, TRAP_TRACE + case 0x5: case 0x4: case 0x1: case 0x2: + { + // TODO(allen): breakpoint event (?) + }break; + + case 0x3: case 0x0: + { + // TODO(allen): do nothing I guess? + }break; + } +} +#endif + +//////////////////////////////// +//~ rjf: @demon_os_hooks Target Process Launching/Attaching/Killing/Detaching/Halting + +internal U32 +demon_os_launch_process(OS_LaunchOptions *options){ + U32 result = 0; + Temp scratch = scratch_begin(0, 0); + + // arrange options + char *binary = 0; + char **args = 0; + if (options->cmd_line.node_count > 0){ + args = push_array_no_zero(scratch.arena, char*, options->cmd_line.node_count + 1); + char **arg_ptr = args; + for (String8Node *node = options->cmd_line.first; + node != 0; + node = node->next, arg_ptr += 1){ + String8 string = push_str8_copy(scratch.arena, node->string); + *arg_ptr = (char*)string.str; + } + *arg_ptr = 0; + binary = args[0]; + } + + char *path = 0; + { + String8 string = push_str8_copy(scratch.arena, options->path); + path = (char*)string.str; + } + + char **env = 0; + if (options->env.node_count > 0){ + env = push_array_no_zero(scratch.arena, char*, options->env.node_count + 1); + char **env_ptr = env; + for (String8Node *node = options->env.first; + node != 0; + node = node->next, env_ptr += 1){ + String8 string = push_str8_copy(scratch.arena, node->string); + *env_ptr = (char*)string.str; + } + *env_ptr = 0; + } + + // fork + if (binary != 0){ + pid_t pid = fork(); + if (pid == -1){ + // TODO(allen): fork error + } + else if (pid == 0){ + // NOTE(allen): child process + int ptrace_result = ptrace(PTRACE_TRACEME, 0, 0, 0); + if (ptrace_result != -1){ + int chdir_result = chdir(path); + if (chdir_result != -1){ + execve(binary, args, env); + } + } + // failed to init fully; abort so the parent can clean up the child + abort(); + } + else{ + // NOTE(allen): parent process + + // wait for child + int status = 0; + pid_t wait_id = waitpid(pid, &status, __WALL); + + // determine child launch status + enum{ + LaunchCode_Null, + LaunchCode_FailBeforePtrace, + LaunchCode_FailAfterPtrace, + LaunchCode_Success, + }; + U32 launch_result = LaunchCode_Null; + // NOTE(allen): if wait_id != pid we don't know what that means; study that case before + // deciding how error handling around it works. + if (wait_id == pid){ + if (WIFSTOPPED(status)){ + if (WSTOPSIG(status) == SIGTRAP){ + launch_result = LaunchCode_Success; + } + else{ + launch_result = LaunchCode_FailAfterPtrace; + } + } + else{ + launch_result = LaunchCode_FailBeforePtrace; + } + } + + // handle launch result + switch (launch_result){ + default: + { + // TODO(allen): error that we do not understand + }break; + + case LaunchCode_FailBeforePtrace: + { + // TODO(allen): child ptrace init failed + }break; + + case LaunchCode_FailAfterPtrace: + { + // need to specifically pull the exit status out of the child + // or it will sit around as a zombie forever since it is ptraced. + B32 cleanup_good = false; + int detach_result = ptrace(PTRACE_DETACH, pid, 0, (void*)SIGCONT); + if (detach_result != -1){ + int status_cleanup = 0; + pid_t wait_id_cleanup = waitpid(pid, &status_cleanup, __WALL); + if (wait_id_cleanup == pid){ + cleanup_good = true; + } + } + if (cleanup_good){ + // TODO(allen): child init failed + } + else{ + // TODO(allen): child init failed; something went wrong and a process may have leaked + } + }break; + + case LaunchCode_Success: + { + int setoptions_result = ptrace(PTRACE_SETOPTIONS, pid, 0, PtrFromInt(demon_lnx_ptrace_options)); + if (setoptions_result == -1){ + // TODO(allen): ptrace setup failed; need to kill the child and clean it up + } + else{ + result = pid; + + Architecture arch = demon_lnx_arch_from_pid(pid); + + // process entity + DEMON_Entity *process = demon_ent_new(demon_ent_root, DEMON_EntityKind_Process, pid); + demon_proc_count += 1; + process->arch = arch; + process->ext_u64 = demon_lnx_open_memory_fd_for_pid(pid); + + // thread entity + DEMON_Entity *thread = demon_ent_new(process, DEMON_EntityKind_Thread, pid); + demon_thread_count += 1; + + // process event + { + DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, + DEMON_EventKind_CreateProcess); + e->process = demon_ent_handle_from_ptr(process); + } + + // thread event + { + DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, + DEMON_EventKind_CreateThread); + e->process = demon_ent_handle_from_ptr(process); + e->thread = demon_ent_handle_from_ptr(thread); + } + + // get module list + DEMON_LNX_ModuleNode *module_list = demon_lnx_module_list_from_process(scratch.arena, process); + + // for each module ... + for (DEMON_LNX_ModuleNode *node = module_list; + node != 0; + node = node->next){ + // module entity + DEMON_Entity *module = demon_ent_new(process, DEMON_EntityKind_Module, node->vaddr); + demon_module_count += 1; + module->ext_u64 = node->name; + + // event + { + DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, + DEMON_EventKind_LoadModule); + e->process = demon_ent_handle_from_ptr(process); + e->module = demon_ent_handle_from_ptr(module); + e->address = node->vaddr; + e->size = node->size; + } + } + + // handshake event + { + DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, + DEMON_EventKind_HandshakeComplete); + e->process = demon_ent_handle_from_ptr(process); + e->thread = demon_ent_handle_from_ptr(thread); + } + } + }break; + } + } + } + + scratch_end(scratch); + return(result); +} + +internal B32 +demon_os_attach_process(U32 pid){ + B32 result = false; + + Temp scratch = scratch_begin(0, 0); + DEMON_LNX_AttachNode *attachments = 0; + DEMON_LNX_AttachNode *the_process = 0; + + // TODO(allen): double check that this logic only lets us + // "attach" when pid is the id of the main thread of a process. + + // attach this process + B32 attached_proc = false; + if (kill(pid, 0) == -1){ + // TODO(allen): process does not exist + } + else{ + attached_proc = demon_lnx_attach_pid(scratch.arena, pid, &the_process); + if (the_process != 0){ + SLLStackPush(attachments, the_process); + } + } + + // open thread list + if (attached_proc){ + String8 threads_path = push_str8f(scratch.arena, "/proc/%d/task", pid); + DIR *proc_dir = opendir((char*)threads_path.str); + if (proc_dir == 0){ + // TODO(allen): could not read proc threads somehow; no good! + } + else{ + + // attach all threads + B32 attached_all_threads = true; + for (;;){ + struct dirent *entry = readdir(proc_dir); + if (entry == 0){ + break; + } + + String8 name = str8_cstring(entry->d_name); + if (str8_is_integer(name, 10)){ + pid_t tid = u64_from_str8(name, 10); + if (tid != pid){ + DEMON_LNX_AttachNode *new_attachment = 0; + B32 attached_this_thread = demon_lnx_attach_pid(scratch.arena, tid, &new_attachment); + if (new_attachment != 0){ + SLLStackPush(attachments, new_attachment); + } + if (!attached_this_thread){ + attached_all_threads = false; + break; + } + } + } + } + closedir(proc_dir); + + if (attached_all_threads){ + result = true; + } + } + } + + // initialize new entities on success + if (result){ + Architecture arch = demon_lnx_arch_from_pid(the_process->pid); + + // process entity + DEMON_Entity *process = demon_ent_new(demon_ent_root, DEMON_EntityKind_Process, the_process->pid); + demon_proc_count += 1; + process->arch = arch; + process->ext_u64 = demon_lnx_open_memory_fd_for_pid(the_process->pid); + + // process event + { + DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, + DEMON_EventKind_CreateProcess); + e->process = demon_ent_handle_from_ptr(process); + } + + // TODO(allen): happens on windows here? + + for (DEMON_LNX_AttachNode *node = attachments; + node != 0; + node = node->next){ + DEMON_Entity *thread = demon_ent_new(process, DEMON_EntityKind_Thread, node->pid); + demon_thread_count += 1; + + // thread event + { + DEMON_Event *e = demon_push_event(demon_lnx_event_arena, &demon_lnx_queued_events, + DEMON_EventKind_CreateThread); + e->process = demon_ent_handle_from_ptr(process); + e->thread = demon_ent_handle_from_ptr(thread); + } + } + + // TODO(allen): sync modules in process + } + + // cleanup on failure + else{ + for (DEMON_LNX_AttachNode *node = attachments; + node != 0; + node = node->next){ + ptrace(PTRACE_DETACH, node->pid, 0, (void*)SIGCONT); + } + } + + scratch_end(scratch); + return(result); +} + +internal B32 +demon_os_kill_process(DEMON_Entity *process, U32 exit_code){ + B32 result = false; + if (process != 0){ + if (kill(process->id, SIGKILL) != -1){ + result = true; + } + } + return(result); +} + +internal B32 +demon_os_detach_process(DEMON_Entity *process){ + B32 result = false; + if (process != 0){ + int detach_result = ptrace(PTRACE_DETACH, process->id, 0, 0); + result = (detach_result != -1); + } + return(0); +} + +//////////////////////////////// +//~ rjf: @demon_os_hooks Entity Functions + +//- rjf: cleanup + +internal void +demon_os_entity_cleanup(DEMON_Entity *entity) +{ + // NOTE(rjf): no-op +} + +//- rjf: introspection + +internal String8 +demon_os_full_path_from_module(Arena *arena, DEMON_Entity *module){ + DEMON_Entity *process = module->parent; + int memory_fd = (int)process->ext_u64; + U64 name_va = module->ext_u64; + String8 result = demon_lnx_read_memory_str(arena, memory_fd, name_va); + return(result); +} + +internal U64 +demon_os_stack_base_vaddr_from_thread(DEMON_Entity *thread){ + Temp scratch = scratch_begin(0, 0); + + U64 stack_base = 0; + + DEMON_Entity *process = thread->parent; + + // id for main thread is zero + B32 is_main_thread = (thread->id == process->id); + pid_t match_tid = is_main_thread ? 0 : thread->id; + + // open /proc/$pid/maps + int maps = demon_lnx_open_maps(process->id); + + // look for entry with stack markings and matching thread id + for (;;){ + DEMON_LNX_MapsEntry e; + Temp temp = temp_begin(scratch.arena); + if (!demon_lnx_next_map(temp.arena, maps, &e)){ + break; + } + if (e.type == DEMON_LNX_MapsEntryType_Stack && e.stack_tid == match_tid){ + stack_base = e.address_lo; + break; + } + temp_end(temp); + } + + scratch_end(scratch); + return(stack_base); +} + +internal U64 +demon_os_tls_root_vaddr_from_thread(DEMON_Entity *thread){ + U64 result = 0; + switch (thread->arch){ + case Architecture_x64: + case Architecture_x86: + { + U32 fsbase = 0; + pid_t tid = (pid_t)thread->id; + if (ptrace(PT_GETFSBASE, tid, (void*)&fsbase, 0) != -1){ + result = (U64)fsbase; + } + if (thread->arch == Architecture_x64){ + result += 8; + } + else{ + result += 4; + } + }break; + } + return(result); +} + +//- rjf: target process memory allocation/protection + +internal U64 +demon_os_reserve_memory(DEMON_Entity *process, U64 size){ + U64 result = 0; + NotImplemented; + return(result); +} + +internal void +demon_os_set_memory_protect_flags(DEMON_Entity *process, U64 page_vaddr, U64 size, DEMON_MemoryProtectFlags flags){ + NotImplemented; +} + +internal void +demon_os_release_memory(DEMON_Entity *process, U64 vaddr, U64 size){ + NotImplemented; +} + +//- rjf: target process memory reading/writing + +internal U64 +demon_os_read_memory(DEMON_Entity *process, void *dst, U64 src_address, U64 size){ + int memory_fd = (int)process->ext_u64; + U64 result = demon_lnx_read_memory(memory_fd, dst, src_address, size); + return(result); +} + +internal B32 +demon_os_write_memory(DEMON_Entity *process, U64 dst_address, void *src, U64 size){ + int memory_fd = (int)process->ext_u64; + B32 result = demon_lnx_write_memory(memory_fd, dst_address, src, size); + return(result); +} + +//- rjf: thread registers reading/writing + +internal B32 +demon_os_read_regs_x86(DEMON_Entity *thread, SYMS_RegX86 *dst){ + B32 result = false; + NotImplemented; + return(result); +} + +internal B32 +demon_os_write_regs_x86(DEMON_Entity *thread, SYMS_RegX86 *src){ + B32 result = false; + NotImplemented; + return(result); +} + +internal B32 +demon_os_read_regs_x64(DEMON_Entity *thread, SYMS_RegX64 *dst){ + pid_t tid = (pid_t)thread->id; + + // gpr + B32 got_gpr = false; + DEMON_LNX_UserX64 ctx = {0}; + struct iovec iov_gpr = {0}; + iov_gpr.iov_len = sizeof(ctx); + iov_gpr.iov_base = &ctx; + if (ptrace(PTRACE_GETREGSET, tid, (void*)NT_PRSTATUS, &iov_gpr) != -1){ + demon_lnx_regs_x64_from_usr_regs_x64(dst, &ctx.regs); + got_gpr = true; + } + + // fpr + B32 got_fpr = false; + if (got_gpr){ + B32 got_xsave = false; + { + U8 xsave_buffer[KB(4)]; + struct iovec iov_xsave = {0}; + iov_xsave.iov_len = sizeof(xsave_buffer); + iov_xsave.iov_base = xsave_buffer; + if (ptrace(PTRACE_GETREGSET, tid, (void*)NT_X86_XSTATE, &iov_xsave) != -1){ + SYMS_XSave *xsave = (SYMS_XSave*)xsave_buffer; + syms_x64_regs__set_full_regs_from_xsave_legacy(dst, &xsave->legacy); + + // TODO(allen): this is a lie; ymm can technically move around + // we need some more low-level-assembly-fu to do this hardcore. + B32 has_ymm_registers = ((xsave->header.xstate_bv & 4) != 0); + if (has_ymm_registers){ + syms_x64_regs__set_full_regs_from_xsave_avx_extension(dst, xsave->ymmh); + } + + got_xsave = true; + } + } + + B32 got_fxsave = false; + if (!got_xsave){ + SYMS_XSaveLegacy fxsave = {0}; + struct iovec iov_fxsave = {0}; + iov_fxsave.iov_len = sizeof(fxsave); + iov_fxsave.iov_base = &fxsave; + if (ptrace(PTRACE_GETREGSET, tid, (void*)NT_FPREGSET, &iov_fxsave) != -1){ + syms_x64_regs__set_full_regs_from_xsave_legacy(dst, &fxsave); + got_fxsave = true; + } + } + + if (got_xsave || got_fxsave){ + got_fpr = true; + } + } + + // debug + B32 got_debug = false; + if (got_fpr){ + got_debug = true; + SYMS_Reg32 *dr_d = &dst->dr0; + for (U32 i = 0; i < 8; i += 1, dr_d += 1){ + if (i != 4 && i != 5){ + U64 offset = OffsetOf(DEMON_LNX_UserX64, u_debugreg[i]); + errno = 0; + int peek_result = ptrace(PTRACE_PEEKUSER, tid, PtrFromInt(offset), 0); + if (errno == 0){ + dr_d->u32 = (U32)peek_result; + } + else{ + got_debug = false; + } + } + } + } + + // got everything + B32 result = got_debug; + return(result); +} + +internal B32 +demon_os_write_regs_x64(DEMON_Entity *thread, SYMS_RegX64 *src){ + pid_t tid = (pid_t)thread->id; + + // gpr + DEMON_LNX_UserX64 ctx = {0}; + demon_lnx_usr_regs_x64_from_regs_x64(&ctx.regs, src); + + struct iovec iov_gpr = {0}; + iov_gpr.iov_base = &ctx; + iov_gpr.iov_len = sizeof(ctx); + int gpr_result = ptrace(PTRACE_SETREGSET, tid, (void*)NT_PRSTATUS, &iov_gpr); + B32 gpr_success = (gpr_result != -1); + + // fpr + int xsave_result = 0; + int fxsave_result = 0; + + { + U8 xsave_buffer[KB(4)] = {0}; + SYMS_XSave *xsave = (SYMS_XSave*)xsave_buffer; + syms_x64_regs__set_xsave_legacy_from_full_regs(&xsave->legacy, src); + + xsave->header.xstate_bv = 7; + + // TODO(allen): this is a lie; ymm can technically move around + // we need some more low-level-assembly-fu to do this hardcore. + syms_x64_regs__set_xsave_avx_extension_from_full_regs(xsave->ymmh, src); + + { + struct iovec iov_xsave = {0}; + iov_xsave.iov_base = &xsave; + iov_xsave.iov_len = sizeof(xsave); + xsave_result = ptrace(PTRACE_SETREGSET, tid, (void*)NT_X86_XSTATE, &iov_xsave); + } + + if (xsave_result == -1){ + struct iovec iov_fxsave = {0}; + iov_fxsave.iov_base = &xsave->legacy; + iov_fxsave.iov_len = sizeof(xsave->legacy); + fxsave_result = ptrace(PTRACE_SETREGSET, tid, (void*)NT_FPREGSET, &iov_fxsave); + } + } + + B32 fpr_success = (xsave_result != -1 || fxsave_result != -1); + + // debug + B32 dr_success = true; + { + SYMS_Reg32 *dr_s = &src->dr0; + for (U32 i = 0; i < 8; i += 1, dr_s += 1){ + if (i != 4 && i != 5){ + U64 offset = OffsetOf(DEMON_LNX_UserX64, u_debugreg[i]); + errno = 0; + int poke_result = ptrace(PTRACE_POKEUSER, tid, PtrFromInt(offset), dr_s->u32); + if (poke_result == -1){ + dr_success = false; + } + } + } + } + + // assemble result + B32 result = (gpr_success && fpr_success && dr_success); + + return(result); +} + +//////////////////////////////// +//~ rjf: @demon_os_hooks Process Listing + +internal void +demon_os_proc_iter_begin(DEMON_ProcessIter *iter){ + DIR *dir = opendir("/proc"); + MemoryZeroStruct(iter); + iter->v[0] = IntFromPtr(dir); +} + +internal B32 +demon_os_proc_iter_next(Arena *arena, DEMON_ProcessIter *iter, DEMON_ProcessInfo *info_out){ + // scan for a process id + B32 got_pid = false; + String8 pid_string = {0}; + + DIR *dir = (DIR*)PtrFromInt(iter->v[0]); + if (dir != 0 && iter->v[1] == 0){ + for (;;){ + struct dirent *d = readdir(dir); + if (d == 0){ + break; + } + + // check file name is integer + String8 file_name = str8_cstring((char*)d->d_name); + B32 is_integer = str8_is_integer(file_name, 10); + + // break on integers (which represent processes) + if (is_integer){ + got_pid = true; + pid_string = file_name; + break; + } + } + } + + // mark iterator dead if nothing found + if (!got_pid){ + iter->v[1] = 1; + } + + // if got process id convert pid -> process info + B32 result = false; + if (got_pid){ + // determine the name we will report + pid_t pid = u64_from_str8(pid_string, 10); + String8 name = demon_lnx_executable_path_from_pid(arena, pid); + if (name.size == 0){ + name = str8_lit(""); + } + + // finish conversion + info_out->name = name; + info_out->pid = pid; + result = true; + } + + return(result); +} + +internal void +demon_os_proc_iter_end(DEMON_ProcessIter *iter){ + DIR *dir = (DIR*)PtrFromInt(iter->v[0]); + if (dir != 0){ + closedir(dir); + } + MemoryZeroStruct(iter); +} diff --git a/src/demon/win32/demon_core_win32.c b/src/demon/win32/demon_core_win32.c index 39ac97bb..b8098bff 100644 --- a/src/demon/win32/demon_core_win32.c +++ b/src/demon/win32/demon_core_win32.c @@ -1,2835 +1,2835 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Basic Helpers - -internal U64 -dmn_w32_hash_from_string(String8 string) -{ - U64 result = 5381; - for(U64 i = 0; i < string.size; i += 1) - { - result = ((result << 5) + result) + string.str[i]; - } - return result; -} - -internal U64 -dmn_w32_hash_from_id(U64 id) -{ - return dmn_w32_hash_from_string(str8_struct(&id)); -} - -//////////////////////////////// -//~ rjf: Entity Helpers - -//- rjf: entity <-> handle - -internal DMN_Handle -dmn_w32_handle_from_entity(DMN_W32_Entity *entity) -{ - U32 idx = (U32)(entity - dmn_w32_shared->entities_base); - U32 gen = entity->gen; - DMN_Handle handle = {idx, gen}; - return handle; -} - -internal DMN_W32_Entity * -dmn_w32_entity_from_handle(DMN_Handle handle) -{ - U32 idx = handle.u32[0]; - U32 gen = handle.u32[1]; - DMN_W32_Entity *entity = dmn_w32_shared->entities_base + idx; - if(entity->gen != gen) - { - entity = &dmn_w32_entity_nil; - } - return entity; -} - -//- rjf: entity allocation/deallocation - -internal DMN_W32_Entity * -dmn_w32_entity_alloc(DMN_W32_Entity *parent, DMN_W32_EntityKind kind, U64 id) -{ - // rjf: allocate - DMN_W32_Entity *e = dmn_w32_shared->entities_first_free; - { - if(e != 0) - { - SLLStackPop(dmn_w32_shared->entities_first_free); - } - else - { - e = push_array_no_zero(dmn_w32_shared->entities_arena, DMN_W32_Entity, 1); - dmn_w32_shared->entities_count += 1; - } - U32 gen = e->gen; - MemoryZeroStruct(e); - e->gen = gen+1; - } - - // rjf: fill - { - e->kind = kind; - e->id = id; - e->parent = parent; - e->next = e->prev = e->first = e->last = &dmn_w32_entity_nil; - if(parent != &dmn_w32_entity_nil) - { - DLLPushBack_NPZ(&dmn_w32_entity_nil, parent->first, parent->last, e, next, prev); - } - } - - // rjf: insert into id -> entity map - if(id != 0) - { - U64 hash = dmn_w32_hash_from_id(id); - U64 slot_idx = hash%dmn_w32_shared->entities_id_hash_slots_count; - DMN_W32_EntityIDHashSlot *slot = &dmn_w32_shared->entities_id_hash_slots[slot_idx]; - DMN_W32_EntityIDHashNode *node = 0; - for(DMN_W32_EntityIDHashNode *n = slot->first; n != 0; n = n->next) - { - if(n->id == id) - { - node = n; - break; - } - } - if(node == 0) - { - node = dmn_w32_shared->entities_id_hash_node_free; - if(node != 0) - { - SLLStackPop(dmn_w32_shared->entities_id_hash_node_free); - } - else - { - node = push_array(dmn_w32_shared->arena, DMN_W32_EntityIDHashNode, 1); - } - DLLPushBack(slot->first, slot->last, node); - } - node->id = id; - node->entity = e; - } - - return e; -} - -internal void -dmn_w32_entity_release(DMN_W32_Entity *entity) -{ - // rjf: unhook root - if(entity->parent != &dmn_w32_entity_nil) - { - DLLRemove_NPZ(&dmn_w32_entity_nil, entity->parent->first, entity->parent->last, entity, next, prev); - } - - // rjf: walk every entity in this tree, free each - if(entity != &dmn_w32_entity_nil) - { - Temp scratch = scratch_begin(0, 0); - typedef struct Task Task; - struct Task - { - Task *next; - DMN_W32_Entity *e; - }; - Task start_task = {0, entity}; - Task *first_task = &start_task; - Task *last_task = &start_task; - for(Task *t = first_task; t != 0; t = t->next) - { - for(DMN_W32_Entity *child = t->e->first; child != &dmn_w32_entity_nil; child = child->next) - { - Task *t = push_array(scratch.arena, Task, 1); - t->e = child; - SLLQueuePush(first_task, last_task, t); - } - - // rjf: free entity - SLLStackPush(dmn_w32_shared->entities_first_free, t->e); - t->e->gen += 1; - if(t->e->kind == DMN_W32_EntityKind_Module) - { - CloseHandle(t->e->handle); - } - - // rjf: remove from id -> entity map - if(t->e->id != 0) - { - U64 hash = dmn_w32_hash_from_id(t->e->id); - U64 slot_idx = hash%dmn_w32_shared->entities_id_hash_slots_count; - DMN_W32_EntityIDHashSlot *slot = &dmn_w32_shared->entities_id_hash_slots[slot_idx]; - DMN_W32_EntityIDHashNode *node = 0; - for(DMN_W32_EntityIDHashNode *n = slot->first; n != 0; n = n->next) - { - if(n->id == t->e->id && n->entity == t->e) - { - DLLRemove(slot->first, slot->last, n); - SLLStackPush(dmn_w32_shared->entities_id_hash_node_free, n); - break; - } - } - } - } - scratch_end(scratch); - } -} - -//- rjf: kind*id -> entity - -internal DMN_W32_Entity * -dmn_w32_entity_from_kind_id(DMN_W32_EntityKind kind, U64 id) -{ - DMN_W32_Entity *result = &dmn_w32_entity_nil; - U64 hash = dmn_w32_hash_from_id(id); - U64 slot_idx = hash%dmn_w32_shared->entities_id_hash_slots_count; - DMN_W32_EntityIDHashSlot *slot = &dmn_w32_shared->entities_id_hash_slots[slot_idx]; - DMN_W32_EntityIDHashNode *node = 0; - for(DMN_W32_EntityIDHashNode *n = slot->first; n != 0; n = n->next) - { - if(n->entity->kind == kind && n->id == id) - { - node = n; - break; - } - } - if(node != 0) - { - result = node->entity; - } - return result; -} - -//////////////////////////////// -//~ rjf: Module Info Extraction - -internal String8 -dmn_w32_full_path_from_module(Arena *arena, DMN_W32_Entity *module) -{ - Temp scratch = scratch_begin(&arena, 1); - - //- rjf: extract path from module - String16 path16 = {0}; - String8 path8 = {0}; - { - // rjf: handle -> full path - if(module->handle != 0) - { - DWORD cap16 = GetFinalPathNameByHandleW(module->handle, 0, 0, VOLUME_NAME_DOS); - U16 *buffer16 = push_array_no_zero(scratch.arena, U16, cap16); - DWORD size16 = GetFinalPathNameByHandleW(module->handle, (WCHAR*)buffer16, cap16, VOLUME_NAME_DOS); - path16 = str16(buffer16, size16); - } - - // rjf: fallback (main module only): process -> full path - if(path16.size == 0 && module->module.is_main) - { - DMN_W32_Entity *process = module->parent; - DWORD size = KB(4); - U16 *buf = push_array_no_zero(scratch.arena, U16, size); - if(QueryFullProcessImageNameW(process->handle, 0, (WCHAR*)buf, &size)) - { - path16 = str16(buf, size); - } - } - - // rjf: fallback (any module - no guarantee): address_of_name -> full path - if(path16.size == 0 && module->module.address_of_name_pointer != 0) - { - DMN_W32_Entity *process = module->parent; - U64 ptr_size = bit_size_from_arch(process->arch)/8; - U64 name_pointer = 0; - if(dmn_w32_process_read(process->handle, r1u64(module->module.address_of_name_pointer, module->module.address_of_name_pointer+ptr_size), &name_pointer)) - { - if(name_pointer != 0) - { - if(module->module.name_is_unicode) - { - path16 = dmn_w32_read_memory_str16(scratch.arena, process->handle, name_pointer); - } - else - { - path8 = dmn_w32_read_memory_str(scratch.arena, process->handle, name_pointer); - } - } - } - } - } - - // rjf: produce finalized result - String8 result = {0}; - { - if(path16.size > 0) - { - // rjf: skip the extended path thing if necessary - if(path16.size >= 4 && - path16.str[0] == L'\\' && - path16.str[1] == L'\\' && - path16.str[2] == L'?' && - path16.str[3] == L'\\') - { - path16.size -= 4; - path16.str += 4; - } - - // rjf: convert to UTF-8 - result = str8_from_16(arena, path16); - } - else - { - // rjf: skip the extended path thing if necessary - if (path8.size >= 4 && - path8.str[0] == L'\\' && - path8.str[1] == L'\\' && - path8.str[2] == L'?' && - path8.str[3] == L'\\') - { - path8.size -= 4; - path8.str += 4; - } - - // rjf: copy to output arena - result = push_str8_copy(arena, path8); - } - } - - scratch_end(scratch); - return result; -} - -//////////////////////////////// -//~ rjf: Win32-Level Process/Thread Reads/Writes - -//- rjf: processes - -internal U64 -dmn_w32_process_read(HANDLE process, Rng1U64 range, void *dst) -{ - U64 bytes_read = 0; - U8 *ptr = (U8*)dst; - U8 *opl = ptr + dim_1u64(range); - U64 cursor = range.min; - for(;ptr < opl;) - { - SIZE_T to_read = (SIZE_T)(opl - ptr); - SIZE_T actual_read = 0; - if(!ReadProcessMemory(process, (LPCVOID)cursor, ptr, to_read, &actual_read)) - { - bytes_read += actual_read; - break; - } - ptr += actual_read; - cursor += actual_read; - bytes_read += actual_read; - } - return bytes_read; -} - -internal B32 -dmn_w32_process_write(HANDLE process, Rng1U64 range, void *src) -{ - B32 result = 1; - U8 *ptr = (U8*)src; - U8 *opl = ptr + dim_1u64(range); - U64 cursor = range.min; - for(;ptr < opl;) - { - SIZE_T to_write = (SIZE_T)(opl - ptr); - SIZE_T actual_write = 0; - if(!WriteProcessMemory(process, (LPVOID)cursor, ptr, to_write, &actual_write)) - { - result = 0; - break; - } - ptr += actual_write; - cursor += actual_write; - } - ins_atomic_u64_inc_eval(&dmn_w32_shared->mem_gen); - return result; -} - -internal String8 -dmn_w32_read_memory_str(Arena *arena, HANDLE process_handle, U64 address) -{ - // TODO(rjf): @rewrite - // - // OLD: this could be done better with a demon_w32_read_memory - // that returns a read amount instead of a success/fail. - // - // (dmn_w32_process_read now does this, so we can switch to it) - - // scan piece by piece - Temp scratch = scratch_begin(&arena, 1); - String8List list = {0}; - - U64 max_cap = 256; - U64 cap = max_cap; - U64 read_p = address; - for (;;){ - U8 *block = push_array(scratch.arena, U8, cap); - for (;cap > 0;){ - if (dmn_w32_process_read(process_handle, r1u64(read_p, read_p+cap), block)){ - break; - } - cap /= 2; - } - read_p += cap; - - U64 block_opl = 0; - for (;block_opl < cap; block_opl += 1){ - if (block[block_opl] == 0){ - break; - } - } - - if (block_opl > 0){ - str8_list_push(scratch.arena, &list, str8(block, block_opl)); - } - - if (block_opl < cap || cap == 0){ - break; - } - } - - // assemble results - String8 result = str8_list_join(arena, &list, 0); - scratch_end(scratch); - return(result); -} - -internal String16 -dmn_w32_read_memory_str16(Arena *arena, HANDLE process_handle, U64 address) -{ - // TODO(rjf): @rewrite - // - // OLD: this could be done better with a demon_w32_read_memory - // that returns a read amount instead of a success/fail. - // - // (dmn_w32_process_read now does this, so we can switch to it) - - // scan piece by piece - Temp scratch = scratch_begin(&arena, 1); - String8List list = {0}; - - U64 max_cap = 256; - U64 cap = max_cap; - U64 read_p = address; - for (;;){ - U8 *block = push_array(scratch.arena, U8, cap); - for (;cap > 1;){ - if (dmn_w32_process_read(process_handle, r1u64(read_p, read_p+cap), block)){ - break; - } - cap /= 2; - } - read_p += cap; - - U16 *block16 = (U16*)block; - (void)block16; - U64 block_opl = 0; - for (;block_opl < cap; block_opl += 2){ - if (*(U16*)(block + block_opl) == 0){ - break; - } - } - - if (block_opl > 0){ - str8_list_push(scratch.arena, &list, str8(block, block_opl)); - } - - if (block_opl < cap || cap == 0){ - break; - } - } - - // assemble results - String8 joined = str8_list_join(arena, &list, 0); - String16 result = {(U16*)joined.str, joined.size/2}; - scratch_end(scratch); - return(result); -} - -internal DMN_W32_ImageInfo -dmn_w32_image_info_from_process_base_vaddr(HANDLE process, U64 base_vaddr) -{ - // rjf: find PE offset - U32 pe_offset = 0; - { - U64 dos_magic_off = base_vaddr; - U16 dos_magic = 0; - dmn_w32_process_read_struct(process, dos_magic_off, &dos_magic); - if(dos_magic == PE_DOS_MAGIC) - { - U64 pe_offset_off = base_vaddr + OffsetOf(PE_DosHeader, coff_file_offset); - dmn_w32_process_read_struct(process, pe_offset_off, &pe_offset); - } - } - - // rjf: get COFF header - B32 got_coff_header = 0; - U64 coff_header_off = 0; - COFF_Header coff_header = {0}; - if(pe_offset > 0) - { - U64 pe_magic_off = base_vaddr + pe_offset; - U32 pe_magic = 0; - dmn_w32_process_read_struct(process, pe_magic_off, &pe_magic); - if(pe_magic == PE_MAGIC) - { - coff_header_off = pe_magic_off + sizeof(pe_magic); - if(dmn_w32_process_read_struct(process, coff_header_off, &coff_header)) - { - got_coff_header = 1; - } - } - } - - // rjf: get arch and size - DMN_W32_ImageInfo result = zero_struct; - if(got_coff_header) - { - U64 optional_size_off = 0; - Architecture arch = Architecture_Null; - switch(coff_header.machine) - { - case COFF_MachineType_X86: - { - arch = Architecture_x86; - optional_size_off = OffsetOf(PE_OptionalHeader32, sizeof_image); - }break; - case COFF_MachineType_X64: - { - arch = Architecture_x64; - optional_size_off = OffsetOf(PE_OptionalHeader32Plus, sizeof_image); - }break; - default: - {}break; - } - if(arch != Architecture_Null) - { - U64 optional_off = coff_header_off + sizeof(coff_header); - U32 size = 0; - if(dmn_w32_process_read_struct(process, optional_off+optional_size_off, &size) >= sizeof(size)) - { - result.arch = arch; - result.size = size; - } - } - } - - return result; -} - -//- rjf: threads - -internal U16 -dmn_w32_real_tag_word_from_xsave(XSAVE_FORMAT *fxsave) -{ - U16 result = 0; - U32 top = (fxsave->StatusWord >> 11) & 7; - for(U32 fpr = 0; fpr < 8; fpr += 1) - { - U32 tag = 3; - if(fxsave->TagWord & (1 << fpr)) - { - U32 st = (fpr - top)&7; - - REGS_Reg80 *fp = (REGS_Reg80*)&fxsave->FloatRegisters[st*16]; - U16 exponent = fp->sign1_exp15 & bitmask15; - U64 integer_part = fp->int1_frac63 >> 63; - U64 fraction_part = fp->int1_frac63 & bitmask63; - - // tag: 0 - normal; 1 - zero; 2 - special - tag = 2; - if(exponent == 0) - { - if(integer_part == 0 && fraction_part == 0) - { - tag = 1; - } - } - else if(exponent != bitmask15 && integer_part != 0) - { - tag = 0; - } - } - result |= tag << (2 * fpr); - } - return result; -} - -internal U16 -dmn_w32_xsave_tag_word_from_real_tag_word(U16 ftw) -{ - U16 compact = 0; - for(U32 fpr = 0; fpr < 8; fpr++) - { - U32 tag = (ftw >> (fpr * 2)) & 3; - if(tag != 3) - { - compact |= (1 << fpr); - } - } - return compact; -} - -internal B32 -dmn_w32_thread_read_reg_block(Architecture arch, HANDLE thread, void *reg_block) -{ - B32 result = 0; - ProfBeginFunction(); - switch(arch) - { - //////////////////////////// - //- rjf: unimplemented win32/arch combos - // - case Architecture_Null: - case Architecture_COUNT: - {}break; - case Architecture_arm64: - case Architecture_arm32: - {NotImplemented;}break; - - //////////////////////////// - //- rjf: x86 - // - case Architecture_x86: - { - REGS_RegBlockX86 *dst = (REGS_RegBlockX86 *)reg_block; - - //- rjf: get thread context - WOW64_CONTEXT ctx = {0}; - ctx.ContextFlags = DMN_W32_CTX_X86_ALL; - if(!Wow64GetThreadContext(thread, (WOW64_CONTEXT *)&ctx)) - { - break; - } - result = 1; - - //- rjf: convert WOW64_CONTEXT -> REGS_RegBlockX86 - XSAVE_FORMAT *fxsave = (XSAVE_FORMAT *)ctx.ExtendedRegisters; - dst->eax.u32 = ctx.Eax; - dst->ebx.u32 = ctx.Ebx; - dst->ecx.u32 = ctx.Ecx; - dst->edx.u32 = ctx.Edx; - dst->esi.u32 = ctx.Esi; - dst->edi.u32 = ctx.Edi; - dst->esp.u32 = ctx.Esp; - dst->ebp.u32 = ctx.Ebp; - dst->eip.u32 = ctx.Eip; - dst->cs.u16 = ctx.SegCs; - dst->ds.u16 = ctx.SegDs; - dst->es.u16 = ctx.SegEs; - dst->fs.u16 = ctx.SegFs; - dst->gs.u16 = ctx.SegGs; - dst->ss.u16 = ctx.SegSs; - dst->dr0.u32 = ctx.Dr0; - dst->dr1.u32 = ctx.Dr1; - dst->dr2.u32 = ctx.Dr2; - dst->dr3.u32 = ctx.Dr3; - dst->dr6.u32 = ctx.Dr6; - dst->dr7.u32 = ctx.Dr7; - // NOTE(rjf): this bit is "supposed to always be 1", according to old info. - // may need to be investigated. - dst->eflags.u32 = ctx.EFlags | 0x2; - dst->fcw.u16 = fxsave->ControlWord; - dst->fsw.u16 = fxsave->StatusWord; - dst->ftw.u16 = dmn_w32_real_tag_word_from_xsave(fxsave); - dst->fop.u16 = fxsave->ErrorOpcode; - dst->fip.u32 = fxsave->ErrorOffset; - dst->fcs.u16 = fxsave->ErrorSelector; - dst->fdp.u32 = fxsave->DataOffset; - dst->fds.u16 = fxsave->DataSelector; - dst->mxcsr.u32 = fxsave->MxCsr; - dst->mxcsr_mask.u32 = fxsave->MxCsr_Mask; - { - M128A *float_s = fxsave->FloatRegisters; - REGS_Reg80 *float_d = &dst->fpr0; - for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1) - { - MemoryCopy(float_d, float_s, sizeof(*float_d)); - } - } - { - M128A *xmm_s = fxsave->XmmRegisters; - REGS_Reg256 *xmm_d = &dst->ymm0; - for(U32 n = 0; n < 8; n += 1, xmm_s += 1, xmm_d += 1) - { - MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_s)); - } - } - - //- rjf: read FS/GS base - WOW64_LDT_ENTRY ldt = {0}; - if(Wow64GetThreadSelectorEntry(thread, ctx.SegFs, &ldt)) - { - U32 base = (ldt.BaseLow) | (ldt.HighWord.Bytes.BaseMid << 16) | (ldt.HighWord.Bytes.BaseHi << 24); - dst->fsbase.u32 = base; - } - if(Wow64GetThreadSelectorEntry(thread, ctx.SegGs, &ldt)) - { - U32 base = (ldt.BaseLow) | (ldt.HighWord.Bytes.BaseMid << 16) | (ldt.HighWord.Bytes.BaseHi << 24); - dst->gsbase.u32 = base; - } - }break; - - //////////////////////////// - //- rjf: x64 - // - case Architecture_x64: - { - Temp scratch = scratch_begin(0, 0); - REGS_RegBlockX64 *dst = (REGS_RegBlockX64 *)reg_block; - - //- rjf: unpack info about available features - U32 feature_mask = GetEnabledXStateFeatures(); - B32 avx_enabled = !!(feature_mask & XSTATE_MASK_AVX); - - //- rjf: set up context - CONTEXT *ctx = 0; - U32 ctx_flags = DMN_W32_CTX_X64_ALL; - if(avx_enabled) - { - ctx_flags |= DMN_W32_CTX_INTEL_XSTATE; - } - DWORD size = 0; - InitializeContext(0, ctx_flags, 0, &size); - if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) - { - void *ctx_memory = push_array(scratch.arena, U8, size); - if(!InitializeContext(ctx_memory, ctx_flags, &ctx, &size)) - { - ctx = 0; - } - } - - //- rjf: unpack features available on this context - B32 avx_available = 0; - if(ctx != 0) - { - if(avx_enabled) - { - SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX); - } - DWORD64 xstate_flags = 0; - if(GetXStateFeaturesMask(ctx, &xstate_flags)) - { - avx_available = !!(xstate_flags & XSTATE_MASK_AVX); - } - } - - //- rjf: get thread context - if(!GetThreadContext(thread, ctx)) - { - ctx = 0; - } - - //- rjf: bad context -> abort - if(ctx == 0) - { - break; - } - result = 1; - - //- rjf: convert context -> REGS_RegBlockX64 - XSAVE_FORMAT *xsave = &ctx->FltSave; - dst->rax.u64 = ctx->Rax; - dst->rcx.u64 = ctx->Rcx; - dst->rdx.u64 = ctx->Rdx; - dst->rbx.u64 = ctx->Rbx; - dst->rsp.u64 = ctx->Rsp; - dst->rbp.u64 = ctx->Rbp; - dst->rsi.u64 = ctx->Rsi; - dst->rdi.u64 = ctx->Rdi; - dst->r8.u64 = ctx->R8; - dst->r9.u64 = ctx->R9; - dst->r10.u64 = ctx->R10; - dst->r11.u64 = ctx->R11; - dst->r12.u64 = ctx->R12; - dst->r13.u64 = ctx->R13; - dst->r14.u64 = ctx->R14; - dst->r15.u64 = ctx->R15; - dst->rip.u64 = ctx->Rip; - dst->cs.u16 = ctx->SegCs; - dst->ds.u16 = ctx->SegDs; - dst->es.u16 = ctx->SegEs; - dst->fs.u16 = ctx->SegFs; - dst->gs.u16 = ctx->SegGs; - dst->ss.u16 = ctx->SegSs; - dst->dr0.u32 = ctx->Dr0; - dst->dr1.u32 = ctx->Dr1; - dst->dr2.u32 = ctx->Dr2; - dst->dr3.u32 = ctx->Dr3; - dst->dr6.u32 = ctx->Dr6; - dst->dr7.u32 = ctx->Dr7; - // NOTE(rjf): this bit is "supposed to always be 1", according to old info. - // may need to be investigated. - dst->rflags.u64 = ctx->EFlags | 0x2; - dst->fcw.u16 = xsave->ControlWord; - dst->fsw.u16 = xsave->StatusWord; - dst->ftw.u16 = dmn_w32_real_tag_word_from_xsave(xsave); - dst->fop.u16 = xsave->ErrorOpcode; - dst->fcs.u16 = xsave->ErrorSelector; - dst->fds.u16 = xsave->DataSelector; - dst->fip.u32 = xsave->ErrorOffset; - dst->fdp.u32 = xsave->DataOffset; - dst->mxcsr.u32 = xsave->MxCsr; - dst->mxcsr_mask.u32 = xsave->MxCsr_Mask; - { - M128A *float_s = xsave->FloatRegisters; - REGS_Reg80 *float_d = &dst->fpr0; - for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1) - { - MemoryCopy(float_d, float_s, sizeof(*float_d)); - } - } - if(!avx_available) - { - M128A *xmm_s = xsave->XmmRegisters; - REGS_Reg256 *xmm_d = &dst->ymm0; - for(U32 n = 0; n < 16; n += 1, xmm_s += 1, xmm_d += 1) - { - MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_s)); - } - } - if(avx_available) - { - DWORD part0_length = 0; - M128A *part0 = (M128A*)LocateXStateFeature(ctx, XSTATE_LEGACY_SSE, &part0_length); - DWORD part1_length = 0; - M128A *part1 = (M128A*)LocateXStateFeature(ctx, XSTATE_AVX, &part1_length); - Assert(part0_length == part1_length); - DWORD count = part0_length/sizeof(part0[0]); - count = ClampTop(count, 16); - REGS_Reg256 *ymm_d = &dst->ymm0; - for (DWORD i = 0; i < count; i += 1, part0 += 1, part1 += 1, ymm_d += 1) - { - // TODO(rjf): confirm ordering of writes - ymm_d->u64[3] = part0->Low; - ymm_d->u64[2] = part0->High; - ymm_d->u64[1] = part1->Low; - ymm_d->u64[0] = part1->High; - } - } - - scratch_end(scratch); - }break; - } - ProfEnd(); - return result; -} - -internal B32 -dmn_w32_thread_write_reg_block(Architecture arch, HANDLE thread, void *reg_block) -{ - B32 result = 0; - ProfBeginFunction(); - switch(arch) - { - //////////////////////////// - //- rjf: unimplemented win32/arch combos - // - case Architecture_Null: - case Architecture_COUNT: - {}break; - case Architecture_arm64: - case Architecture_arm32: - {NotImplemented;}break; - - //////////////////////////// - //- rjf: x86 - // - case Architecture_x86: - { - REGS_RegBlockX86 *src = (REGS_RegBlockX86 *)reg_block; - - //- rjf: convert REGS_RegBlockX86 -> WOW64_CONTEXT - WOW64_CONTEXT ctx = {0}; - XSAVE_FORMAT *fxsave = (XSAVE_FORMAT*)ctx.ExtendedRegisters; - ctx.ContextFlags = DMN_W32_CTX_X86_ALL; - ctx.Eax = src->eax.u32; - ctx.Ebx = src->ebx.u32; - ctx.Ecx = src->ecx.u32; - ctx.Edx = src->edx.u32; - ctx.Esi = src->esi.u32; - ctx.Edi = src->edi.u32; - ctx.Esp = src->esp.u32; - ctx.Ebp = src->ebp.u32; - ctx.Eip = src->eip.u32; - ctx.SegCs = src->cs.u16; - ctx.SegDs = src->ds.u16; - ctx.SegEs = src->es.u16; - ctx.SegFs = src->fs.u16; - ctx.SegGs = src->gs.u16; - ctx.SegSs = src->ss.u16; - ctx.Dr0 = src->dr0.u32; - ctx.Dr1 = src->dr1.u32; - ctx.Dr2 = src->dr2.u32; - ctx.Dr3 = src->dr3.u32; - ctx.Dr6 = src->dr6.u32; - ctx.Dr7 = src->dr7.u32; - ctx.EFlags = src->eflags.u32; - fxsave->ControlWord = src->fcw.u16; - fxsave->StatusWord = src->fsw.u16; - fxsave->TagWord = dmn_w32_xsave_tag_word_from_real_tag_word(src->ftw.u16); - fxsave->ErrorOpcode = src->fop.u16; - fxsave->ErrorSelector = src->fcs.u16; - fxsave->DataSelector = src->fds.u16; - fxsave->ErrorOffset = src->fip.u32; - fxsave->DataOffset = src->fdp.u32; - fxsave->MxCsr = src->mxcsr.u32 & src->mxcsr_mask.u32; - fxsave->MxCsr_Mask = src->mxcsr_mask.u32; - { - M128A *float_d = fxsave->FloatRegisters; - REGS_Reg80 *float_s = &src->fpr0; - for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1) - { - MemoryCopy(float_d, float_s, 10); - } - } - { - M128A *xmm_d = fxsave->XmmRegisters; - REGS_Reg256 *xmm_s = &src->ymm0; - for(U32 n = 0; n < 8; n += 1, xmm_d += 1, xmm_s += 1) - { - MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_d)); - } - } - - //- rjf: set thread context - B32 result = 0; - if(Wow64SetThreadContext(thread, &ctx)) - { - result = 1; - } - }break; - - //////////////////////////// - //- rjf: x64 - // - case Architecture_x64: - { - Temp scratch = scratch_begin(0, 0); - REGS_RegBlockX64 *src = (REGS_RegBlockX64 *)reg_block; - - //- rjf: unpack info about available features - U32 feature_mask = GetEnabledXStateFeatures(); - B32 avx_enabled = !!(feature_mask & XSTATE_MASK_AVX); - - //- rjf: set up context - CONTEXT *ctx = 0; - U32 ctx_flags = DMN_W32_CTX_X64_ALL; - if(avx_enabled) - { - ctx_flags |= DMN_W32_CTX_INTEL_XSTATE; - } - DWORD size = 0; - InitializeContext(0, ctx_flags, 0, &size); - if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) - { - void *ctx_memory = push_array(scratch.arena, U8, size); - if(!InitializeContext(ctx_memory, ctx_flags, &ctx, &size)) - { - ctx = 0; - } - } - - //- rjf: unpack features available on this context - B32 avx_available = 0; - if(ctx != 0) - { - if(avx_enabled) - { - SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX); - } - DWORD64 xstate_flags = 0; - if(GetXStateFeaturesMask(ctx, &xstate_flags)) - { - avx_available = !!(xstate_flags & XSTATE_MASK_AVX); - } - } - - //- rjf: get thread context - if(!GetThreadContext(thread, ctx)) - { - ctx = 0; - } - - //- rjf: bad context -> abort - if(ctx == 0) - { - DWORD error = GetLastError(); - break; - } - - //- rjf: convert REGS_RegBlockX64 -> CONTEXT - XSAVE_FORMAT *fxsave = &ctx->FltSave; - ctx->ContextFlags = ctx_flags; - ctx->MxCsr = src->mxcsr.u32 & src->mxcsr_mask.u32; - ctx->Rax = src->rax.u64; - ctx->Rcx = src->rcx.u64; - ctx->Rdx = src->rdx.u64; - ctx->Rbx = src->rbx.u64; - ctx->Rsp = src->rsp.u64; - ctx->Rbp = src->rbp.u64; - ctx->Rsi = src->rsi.u64; - ctx->Rdi = src->rdi.u64; - ctx->R8 = src->r8.u64; - ctx->R9 = src->r9.u64; - ctx->R10 = src->r10.u64; - ctx->R11 = src->r11.u64; - ctx->R12 = src->r12.u64; - ctx->R13 = src->r13.u64; - ctx->R14 = src->r14.u64; - ctx->R15 = src->r15.u64; - ctx->Rip = src->rip.u64; - ctx->SegCs = src->cs.u16; - ctx->SegDs = src->ds.u16; - ctx->SegEs = src->es.u16; - ctx->SegFs = src->fs.u16; - ctx->SegGs = src->gs.u16; - ctx->SegSs = src->ss.u16; - ctx->Dr0 = src->dr0.u32; - ctx->Dr1 = src->dr1.u32; - ctx->Dr2 = src->dr2.u32; - ctx->Dr3 = src->dr3.u32; - ctx->Dr6 = src->dr6.u32; - ctx->Dr7 = src->dr7.u32; - ctx->EFlags = src->rflags.u64; - fxsave->ControlWord = src->fcw.u16; - fxsave->StatusWord = src->fsw.u16; - fxsave->TagWord = dmn_w32_xsave_tag_word_from_real_tag_word(src->ftw.u16); - fxsave->ErrorOpcode = src->fop.u16; - fxsave->ErrorSelector = src->fcs.u16; - fxsave->DataSelector = src->fds.u16; - fxsave->ErrorOffset = src->fip.u32; - fxsave->DataOffset = src->fdp.u32; - { - M128A *float_d = fxsave->FloatRegisters; - REGS_Reg80 *float_s = &src->fpr0; - for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1) - { - MemoryCopy(float_d, float_s, 10); - } - } - if(!avx_available) - { - M128A *xmm_d = fxsave->XmmRegisters; - REGS_Reg256 *xmm_s = &src->ymm0; - for(U32 n = 0; n < 8; n += 1, xmm_d += 1, xmm_s += 1) - { - MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_d)); - } - } - if(avx_available) - { - DWORD part0_length = 0; - M128A *part0 = (M128A*)LocateXStateFeature(ctx, XSTATE_LEGACY_SSE, &part0_length); - DWORD part1_length = 0; - M128A *part1 = (M128A*)LocateXStateFeature(ctx, XSTATE_AVX, &part1_length); - Assert(part0_length == part1_length); - DWORD count = part0_length/sizeof(part0[0]); - count = ClampTop(count, 16); - REGS_Reg256 *ymm_d = &src->ymm0; - for(DWORD i = 0; i < count; i += 1, part0 += 1, part1 += 1, ymm_d += 1) - { - // TODO(allen): Are we writing these out in the right order? Seems weird right? - part0->Low = ymm_d->u64[3]; - part0->High = ymm_d->u64[2]; - part1->Low = ymm_d->u64[1]; - part1->High = ymm_d->u64[0]; - } - } - - //- rjf: set thread context - if(SetThreadContext(thread, ctx)) - { - result = 1; - } - scratch_end(scratch); - }break; - } - ins_atomic_u64_inc_eval(&dmn_w32_shared->reg_gen); - ProfEnd(); - return result; -} - -//- rjf: remote thread injection - -internal DWORD -dmn_w32_inject_thread(HANDLE process, U64 start_address) -{ - LPTHREAD_START_ROUTINE start = (LPTHREAD_START_ROUTINE)start_address; - DWORD thread_id = 0; - HANDLE thread = CreateRemoteThread(process, 0, 0, start, 0, 0, &thread_id); - if(thread != 0) - { - CloseHandle(thread); - } - return thread_id; -} - -//////////////////////////////// -//~ rjf: @dmn_os_hooks Main Layer Initialization (Implemented Per-OS) - -internal void -dmn_init(void) -{ - Arena *arena = arena_alloc(); - dmn_w32_shared = push_array(arena, DMN_W32_Shared, 1); - dmn_w32_shared->arena = arena; - dmn_w32_shared->access_mutex = os_mutex_alloc(); - dmn_w32_shared->detach_arena = arena_alloc(); - dmn_w32_shared->entities_arena = arena_alloc__sized(GB(8), KB(64)); - dmn_w32_shared->entities_base = dmn_w32_entity_alloc(&dmn_w32_entity_nil, DMN_W32_EntityKind_Root, 0); - dmn_w32_shared->entities_id_hash_slots_count = 4096; - dmn_w32_shared->entities_id_hash_slots = push_array(arena, DMN_W32_EntityIDHashSlot, dmn_w32_shared->entities_id_hash_slots_count); - - // rjf: load Windows 10+ GetThreadDescription API - { - dmn_w32_GetThreadDescription = (DMN_W32_GetThreadDescriptionFunctionType *)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "GetThreadDescription"); - } - - // rjf: setup environment variables - { - WCHAR *this_proc_env = GetEnvironmentStringsW(); - U64 start_idx = 0; - for(U64 idx = 0;; idx += 1) - { - if(this_proc_env[idx] == 0) - { - if(start_idx == idx) - { - break; - } - else - { - String16 string16 = str16((U16 *)this_proc_env + start_idx, idx - start_idx); - String8 string = str8_from_16(dmn_w32_shared->arena, string16); - str8_list_push(dmn_w32_shared->arena, &dmn_w32_shared->env_strings, string); - start_idx = idx+1; - } - } - } - } -} - -//////////////////////////////// -//~ rjf: @dmn_os_hooks Blocking Control Thread Operations (Implemented Per-OS) - -internal DMN_CtrlCtx * -dmn_ctrl_begin(void) -{ - DMN_CtrlCtx *ctx = (DMN_CtrlCtx *)1; - dmn_w32_ctrl_thread = 1; - return ctx; -} - -internal void -dmn_ctrl_exclusive_access_begin(void) -{ - OS_MutexScope(dmn_w32_shared->access_mutex) - { - dmn_w32_shared->access_run_state = 1; - } -} - -internal void -dmn_ctrl_exclusive_access_end(void) -{ - OS_MutexScope(dmn_w32_shared->access_mutex) - { - dmn_w32_shared->access_run_state = 0; - } -} - -internal U32 -dmn_ctrl_launch(DMN_CtrlCtx *ctx, OS_LaunchOptions *options) -{ - Temp scratch = scratch_begin(0, 0); - U32 result = 0; - DMN_AccessScope - { - //- rjf: produce exe / arguments string - String8 cmd = {0}; - if(options->cmd_line.first != 0) - { - String8List args = {0}; - String8 exe_path = options->cmd_line.first->string; - str8_list_pushf(scratch.arena, &args, "\"%S\"", exe_path); - for(String8Node *n = options->cmd_line.first->next; n != 0; n = n->next) - { - str8_list_push(scratch.arena, &args, n->string); - } - StringJoin join_params = {0}; - join_params.sep = str8_lit(" "); - cmd = str8_list_join(scratch.arena, &args, &join_params); - } - - //- rjf: produce environment strings - String8 env = {0}; - { - String8List all_opts = options->env; - if(options->inherit_env != 0) - { - MemoryZeroStruct(&all_opts); - str8_list_push(scratch.arena, &all_opts, str8_lit("_NO_DEBUG_HEAP=1")); - for(String8Node *n = options->env.first; n != 0; n = n->next) - { - str8_list_push(scratch.arena, &all_opts, n->string); - } - for(String8Node *n = dmn_w32_shared->env_strings.first; n != 0; n = n->next) - { - str8_list_push(scratch.arena, &all_opts, n->string); - } - } - StringJoin join_params2 = {0}; - join_params2.sep = str8_lit("\0"); - join_params2.post = str8_lit("\0"); - env = str8_list_join(scratch.arena, &all_opts, &join_params2); - } - - //- rjf: produce utf-16 strings - String16 cmd16 = str16_from_8(scratch.arena, cmd); - String16 dir16 = str16_from_8(scratch.arena, options->path); - String16 env16 = str16_from_8(scratch.arena, env); - - //- rjf: launch - DWORD access_flags = CREATE_UNICODE_ENVIRONMENT|DEBUG_PROCESS; - STARTUPINFOW startup_info = {sizeof(startup_info)}; - PROCESS_INFORMATION process_info = {0}; - AllocConsole(); - if(CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, 1, access_flags, (WCHAR*)env16.str, (WCHAR*)dir16.str, &startup_info, &process_info)) - { - // check if we are 32-bit app, and just close it immediately - BOOL is_wow = 0; - IsWow64Process(process_info.hProcess, &is_wow); - if(is_wow) - { - log_user_errorf("Only 64-bit applications can be debugged currently."); - DebugActiveProcessStop(process_info.dwProcessId); - TerminateProcess(process_info.hProcess,0xffffffff); - } - else - { - result = process_info.dwProcessId; - dmn_w32_shared->new_process_pending = 1; - } - CloseHandle(process_info.hProcess); - CloseHandle(process_info.hThread); - } - else - { - MessageBox(0, "Error starting process.", "Process error", MB_OK|MB_ICONSTOP); - } - FreeConsole(); - - //- rjf: eliminate all handles which have stuck around from the AllocConsole - { - SetStdHandle(STD_INPUT_HANDLE, 0); - SetStdHandle(STD_OUTPUT_HANDLE, 0); - SetStdHandle(STD_ERROR_HANDLE, 0); - } - } - scratch_end(scratch); - return result; -} - -internal B32 -dmn_ctrl_attach(DMN_CtrlCtx *ctx, U32 pid) -{ - B32 result = 0; - DMN_AccessScope if(DebugActiveProcess((DWORD)pid)) - { - result = 1; - dmn_w32_shared->new_process_pending = 1; - -#if 0 - // TODO(rjf): JIT debugging info - { - typedef struct JIT_DEBUG_INFO JIT_DEBUG_INFO; - struct JIT_DEBUG_INFO - { - DWORD dwSize; - DWORD dwProcessorArchitecture; - DWORD dwThreadID; - DWORD dwReserved0; - ULONG64 lpExceptionAddress; - ULONG64 lpExceptionRecord; - ULONG64 lpContextRecord; - }; - } -#endif - } - return result; -} - -internal B32 -dmn_ctrl_kill(DMN_CtrlCtx *ctx, DMN_Handle process, U32 exit_code) -{ - B32 result = 0; - DMN_AccessScope - { - DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); - if(TerminateProcess(process_entity->handle, exit_code)) - { - result = 1; - } - } - return result; -} - -internal B32 -dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process) -{ - B32 result = 0; - DMN_AccessScope - { - DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); - - // rjf: resume threads - for(DMN_W32_Entity *child = process_entity->first; - child != &dmn_w32_entity_nil; - child = child->next) - { - if(child->kind == DMN_W32_EntityKind_Thread) - { - DWORD resume_result = ResumeThread(child->handle); - (void)resume_result; - } - } - - // rjf: detach - { - DWORD pid = (DWORD)process_entity->id; - if(DebugActiveProcessStop(pid)) - { - result = 1; - } - } - - // rjf: push into list of processes to generate events for later - if(result != 0) - { - dmn_handle_list_push(dmn_w32_shared->detach_arena, &dmn_w32_shared->detach_processes, process); - } - } - return result; -} - -internal DMN_EventList -dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) -{ - DMN_EventList events = {0}; - dmn_access_open(); - - ////////////////////////////// - //- rjf: determine event generation path - // - typedef enum DMN_W32_EventGenPath - { - DMN_W32_EventGenPath_NotAttached, - DMN_W32_EventGenPath_Run, - DMN_W32_EventGenPath_DetachProcesses, - } - DMN_W32_EventGenPath; - DMN_W32_EventGenPath event_gen_path = DMN_W32_EventGenPath_Run; - if(dmn_w32_shared->detach_processes.first != 0) - { - event_gen_path = DMN_W32_EventGenPath_DetachProcesses; - } - else - { - B32 any_processes_live = dmn_w32_shared->new_process_pending; - if(!any_processes_live) - { - for(DMN_W32_Entity *process = dmn_w32_shared->entities_base->first; - process != &dmn_w32_entity_nil; - process = process->next) - { - if(process->kind == DMN_W32_EntityKind_Process) - { - any_processes_live = 1; - break; - } - } - } - if(!any_processes_live) - { - event_gen_path = DMN_W32_EventGenPath_NotAttached; - } - } - - ////////////////////////////// - //- rjf: produce debug events - // - switch(event_gen_path) - { - //////////////////////////// - //- rjf: produce not-attached error events - // - case DMN_W32_EventGenPath_NotAttached: - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_Error; - e->error_kind = DMN_ErrorKind_NotAttached; - }break; - - //////////////////////////// - //- rjf: produce debug events from regular running - // - case DMN_W32_EventGenPath_Run: - { - Temp scratch = scratch_begin(&arena, 1); - - ////////////////////////// - //- rjf: get single step thread's context (x64 single-step-set fast path) - // - CONTEXT *single_step_thread_ctx = 0; - if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) - { - DMN_W32_Entity *thread = dmn_w32_entity_from_handle(ctrls->single_step_thread); - Architecture arch = thread->arch; - switch(arch) - { - default:{}break; - case Architecture_x64: - { - U32 ctx_flags = DMN_W32_CTX_X64|DMN_W32_CTX_INTEL_CONTROL; - DWORD size = 0; - InitializeContext(0, ctx_flags, 0, &size); - if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) - { - void *ctx_memory = push_array(scratch.arena, U8, size); - if(!InitializeContext(ctx_memory, ctx_flags, &single_step_thread_ctx, &size)) - { - single_step_thread_ctx = 0; - } - } - }break; - } - } - - ////////////////////////// - //- rjf: set single step bit - // - if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) ProfScope("set single step bit") - { - DMN_W32_Entity *thread = dmn_w32_entity_from_handle(ctrls->single_step_thread); - Architecture arch = thread->arch; - switch(arch) - { - //- rjf: unimplemented win32/arch combos - case Architecture_Null: - case Architecture_COUNT: - {}break; - case Architecture_arm64: - case Architecture_arm32: - {NotImplemented;}break; - - //- rjf: x86 - case Architecture_x86: - { - REGS_RegBlockX86 regs = {0}; - dmn_thread_read_reg_block(ctrls->single_step_thread, ®s); - regs.eflags.u32 |= 0x100; - dmn_thread_write_reg_block(ctrls->single_step_thread, ®s); - }break; - - //- rjf: x64 - case Architecture_x64: - { - if(!GetThreadContext(thread->handle, single_step_thread_ctx)) - { - single_step_thread_ctx = 0; - } - if(single_step_thread_ctx != 0) - { - U64 rflags = single_step_thread_ctx->EFlags|0x2; - U64 new_rflags = rflags | 0x100; - single_step_thread_ctx->EFlags = new_rflags; - SetThreadContext(thread->handle, single_step_thread_ctx); - ins_atomic_u64_inc_eval(&dmn_w32_shared->reg_gen); - } - }break; - } - } - - ////////////////////////// - //- rjf: write all traps into memory - // - U8 *trap_swap_bytes = push_array_no_zero(scratch.arena, U8, ctrls->traps.trap_count); - ProfScope("write all traps into memory") - { - U64 trap_idx = 0; - for(DMN_TrapChunkNode *n = ctrls->traps.first; n != 0; n = n->next) - { - for(U64 n_idx = 0; n_idx < n->count; n_idx += 1, trap_idx += 1) - { - DMN_Trap *trap = n->v+n_idx; - trap_swap_bytes[trap_idx] = 0xCC; - dmn_process_read(trap->process, r1u64(trap->vaddr, trap->vaddr+1), trap_swap_bytes+trap_idx); - U8 int3 = 0xCC; - dmn_process_write(trap->process, r1u64(trap->vaddr, trap->vaddr+1), &int3); - } - } - } - - ////////////////////////// - //- rjf: produce list of threads which will run - // - DMN_W32_EntityNode *first_run_thread = 0; - DMN_W32_EntityNode *last_run_thread = 0; - ProfScope("produce list of threads which will run") - { - //- rjf: scan all processes - for(DMN_W32_Entity *process = dmn_w32_shared->entities_base->first; - process != &dmn_w32_entity_nil; - process = process->next) - { - if(process->kind != DMN_W32_EntityKind_Process) {continue;} - - //- rjf: determine if this process is frozen - B32 process_is_frozen = 0; - if(ctrls->run_entities_are_processes) - { - for(U64 idx = 0; idx < ctrls->run_entity_count; idx += 1) - { - if(dmn_handle_match(ctrls->run_entities[idx], dmn_w32_handle_from_entity(process))) - { - process_is_frozen = 1; - break; - } - } - } - - //- rjf: scan all threads in this process - for(DMN_W32_Entity *thread = process->first; - thread != &dmn_w32_entity_nil; - thread = thread->next) - { - if(thread->kind != DMN_W32_EntityKind_Thread) {continue;} - - //- rjf: determine if this thread is frozen - B32 is_frozen = 0; - { - // rjf: single-step? freeze if not the single-step thread. - if(!dmn_handle_match(dmn_handle_zero(), ctrls->single_step_thread)) - { - is_frozen = !dmn_handle_match(dmn_w32_handle_from_entity(thread), ctrls->single_step_thread); - } - - // rjf: not single-stepping? determine based on run controls freezing info - else - { - if(ctrls->run_entities_are_processes) - { - is_frozen = process_is_frozen; - } - else for(U64 idx = 0; idx < ctrls->run_entity_count; idx += 1) - { - if(dmn_handle_match(ctrls->run_entities[idx], dmn_w32_handle_from_entity(thread))) - { - is_frozen = 1; - break; - } - } - if(ctrls->run_entities_are_unfrozen) - { - is_frozen ^= 1; - } - } - } - - //- rjf: disregard all other rules if this is the halter thread - if(dmn_w32_shared->halter_tid == thread->id) - { - is_frozen = 0; - } - - //- rjf: add to list - if(!is_frozen) - { - DMN_W32_EntityNode *n = push_array(scratch.arena, DMN_W32_EntityNode, 1); - n->v = thread; - SLLQueuePush(first_run_thread, last_run_thread, n); - } - } - } - } - - ////////////////////////// - //- rjf: resume threads which will run - // - ProfScope("resume threads which will run") - { - for(DMN_W32_EntityNode *n = first_run_thread; n != 0; n = n->next) - { - DMN_W32_Entity *thread = n->v; - DWORD resume_result = ResumeThread(thread->handle); - switch(resume_result) - { - case 0xffffffffu: - { - // TODO(rjf): error - unknown cause. need to do GetLastError, FormatMessage - }break; - default: - { - DWORD desired_counter = 0; - DWORD current_counter = resume_result - 1; - if(current_counter != desired_counter) - { - // NOTE(rjf): Warning. The user has manually suspended this thread, - // so even though from Demon's perspective it thinks this thread - // should run, it will not, because the user has manually called - // SuspendThread or used CREATE_SUSPENDED or whatever. - } - }break; - } - } - } - - ////////////////////////// - //- rjf: loop, consume win32 debug events until we produce the relevant demon events - // - U64 begin_time = os_now_microseconds(); - String8List debug_strings = {0}; - DMN_Event *debug_strings_event = 0; - for(B32 keep_going = 1; keep_going;) - { - keep_going = 0; - - //////////////////////// - //- rjf: choose win32 resume code - // - DWORD resume_code = DBG_CONTINUE; - { - if(dmn_w32_shared->exception_not_handled && !ctrls->ignore_previous_exception) - { - log_infof("using DBG_EXCEPTION_NOT_HANDLED\n"); - resume_code = DBG_EXCEPTION_NOT_HANDLED; - } - else - { - log_infof("using DBG_CONTINUE\n"); - } - dmn_w32_shared->exception_not_handled = 0; - } - - //////////////////////// - //- rjf: inform windows that we're resuming, run, & obtain next debug event - // - DEBUG_EVENT evt = {0}; - B32 evt_good = 0; - ProfScope("inform windows that we're resuming, run, & obtain next debug event") - { - B32 resume_good = 1; - if(dmn_w32_shared->resume_needed) - { - dmn_w32_shared->resume_needed = 0; - resume_good = !!ContinueDebugEvent(dmn_w32_shared->resume_pid, dmn_w32_shared->resume_tid, resume_code); - dmn_w32_shared->resume_needed = 0; - dmn_w32_shared->resume_tid = 0; - dmn_w32_shared->resume_pid = 0; - } - if(resume_good) - { - evt_good = !!WaitForDebugEvent(&evt, 100); - if(evt_good) - { - dmn_w32_shared->resume_needed = 1; - dmn_w32_shared->resume_pid = evt.dwProcessId; - dmn_w32_shared->resume_tid = evt.dwThreadId; - } - else - { - keep_going = 1; - } - ins_atomic_u64_inc_eval(&dmn_w32_shared->run_gen); - ins_atomic_u64_inc_eval(&dmn_w32_shared->mem_gen); - ins_atomic_u64_inc_eval(&dmn_w32_shared->reg_gen); - } - } - - //////////////////////// - //- rjf: process the new event - // - if(evt_good) ProfScope("process the new event") - { - switch(evt.dwDebugEventCode) - { - ////////////////////// - //- rjf: process was created - // - case CREATE_PROCESS_DEBUG_EVENT: - { - // rjf: zero out "process pending" state - dmn_w32_shared->new_process_pending = 0; - - // rjf: unpack event - HANDLE process_handle = evt.u.CreateProcessInfo.hProcess; - HANDLE thread_handle = evt.u.CreateProcessInfo.hThread; - HANDLE module_handle = evt.u.CreateProcessInfo.hFile; - U64 tls_base = (U64)evt.u.CreateProcessInfo.lpThreadLocalBase; - U64 module_base = (U64)evt.u.CreateProcessInfo.lpBaseOfImage; - U64 module_name_vaddr = (U64)evt.u.CreateProcessInfo.lpImageName; - B32 module_name_is_unicode = (evt.u.CreateProcessInfo.fUnicode != 0); - DMN_W32_ImageInfo image_info = dmn_w32_image_info_from_process_base_vaddr(process_handle, module_base); - - // rjf: create entities (thread/module are implied for initial - they are not reported by win32) - DMN_W32_Entity *process = dmn_w32_entity_alloc(dmn_w32_shared->entities_base, DMN_W32_EntityKind_Process, evt.dwProcessId); - DMN_W32_Entity *thread = dmn_w32_entity_alloc(process, DMN_W32_EntityKind_Thread, evt.dwThreadId); - DMN_W32_Entity *module = dmn_w32_entity_alloc(process, DMN_W32_EntityKind_Module, module_base); - { - process->handle = process_handle; - process->arch = image_info.arch; - thread->handle = thread_handle; - thread->arch = image_info.arch; - thread->thread.thread_local_base = tls_base; - module->handle = module_handle; - module->module.vaddr_range = r1u64(module_base, image_info.size); - module->module.is_main = 1; - module->module.address_of_name_pointer = module_name_vaddr; - module->module.name_is_unicode = module_name_is_unicode; - } - - // rjf: put thread into suspended state, so it matches expected initial state - SuspendThread(thread_handle); - - // rjf: set up per-process injected code (to run halter threads on & - // generate debug events) - { - U8 injection_code[DMN_W32_INJECTED_CODE_SIZE]; - MemorySet(injection_code, 0xCC, DMN_W32_INJECTED_CODE_SIZE); - injection_code[0] = 0xC3; - U64 injection_size = DMN_W32_INJECTED_CODE_SIZE + sizeof(DMN_W32_InjectedBreak); - U64 injection_address = (U64)VirtualAllocEx(process_handle, 0, injection_size, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE); - dmn_w32_process_write(process_handle, r1u64(injection_address, injection_address+sizeof(injection_code)), injection_code); - process->proc.injection_address = injection_address; - } - - // rjf: generate events - { - // rjf: create process - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_CreateProcess; - e->process = dmn_w32_handle_from_entity(process); - e->arch = image_info.arch; - e->code = evt.dwProcessId; - } - - // rjf: create thread - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_CreateThread; - e->process = dmn_w32_handle_from_entity(process); - e->thread = dmn_w32_handle_from_entity(thread); - e->arch = image_info.arch; - e->code = evt.dwThreadId; - } - - // rjf: load module - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_LoadModule; - e->process = dmn_w32_handle_from_entity(process); - e->module = dmn_w32_handle_from_entity(module); - e->arch = image_info.arch; - e->address = module_base; - e->size = image_info.size; - e->string = dmn_w32_full_path_from_module(arena, module); - } - } - }break; - - ////////////////////// - //- rjf: process exited - // - case EXIT_PROCESS_DEBUG_EVENT: - { - DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId); - - // rjf: generate events for children - for(DMN_W32_Entity *child = process->first; child != &dmn_w32_entity_nil; child = child->next) - { - switch(child->kind) - { - default:{}break; - case DMN_W32_EntityKind_Thread: - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_ExitThread; - e->process = dmn_w32_handle_from_entity(process); - e->thread = dmn_w32_handle_from_entity(child); - }break; - case DMN_W32_EntityKind_Module: - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_UnloadModule; - e->process = dmn_w32_handle_from_entity(process); - e->module = dmn_w32_handle_from_entity(child); - e->string = dmn_w32_full_path_from_module(arena, child); - }break; - } - } - - // rjf: generate event for process - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_ExitProcess; - e->process = dmn_w32_handle_from_entity(process); - e->code = evt.u.ExitProcess.dwExitCode; - } - - // rjf: release entity storage - dmn_w32_entity_release(process); - - // rjf: detach - DebugActiveProcessStop(evt.dwProcessId); - }break; - - ////////////////////// - //- rjf: thread was created - // - case CREATE_THREAD_DEBUG_EVENT: - { - DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId); - - // rjf: create thread entity - DMN_W32_Entity *thread = dmn_w32_entity_alloc(process, DMN_W32_EntityKind_Thread, evt.dwThreadId); - { - thread->handle = evt.u.CreateThread.hThread; - thread->arch = process->arch; - thread->thread.thread_local_base = (U64)evt.u.CreateThread.lpThreadLocalBase; - } - - // rjf: suspend thread immediately upon creation, to match with expected suspension state - DWORD sus_result = SuspendThread(thread->handle); - (void)sus_result; - - // rjf: unpack thread name - String8 thread_name = {0}; - if(dmn_w32_GetThreadDescription != 0) - { - WCHAR *thread_name_w = 0; - HRESULT hr = dmn_w32_GetThreadDescription(thread->handle, &thread_name_w); - if(SUCCEEDED(hr)) - { - thread_name = str8_from_16(arena, str16_cstring((U16 *)thread_name_w)); - LocalFree(thread_name_w); - } - } - - // rjf: determine if this is a "halter thread" - the threads we spawn to halt processes - B32 is_halter = (evt.dwThreadId == dmn_w32_shared->halter_tid); - - // rjf: generate events for non-halter threads - if(!is_halter) - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_CreateThread; - e->process = dmn_w32_handle_from_entity(process); - e->thread = dmn_w32_handle_from_entity(thread); - e->arch = thread->arch; - e->code = evt.dwThreadId; - e->string = thread_name; - } - }break; - - ////////////////////// - //- rjf: thread exited - // - case EXIT_THREAD_DEBUG_EVENT: - { - DMN_W32_Entity *thread = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Thread, evt.dwThreadId); - DMN_W32_Entity *process = thread->parent; - - // rjf: determine if this is the halter thread - B32 is_halter = (evt.dwThreadId == dmn_w32_shared->halter_tid); - - // rjf: generate a halt event if this thread is the halter - if(is_halter) - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_Halt; - dmn_w32_shared->halter_process = dmn_handle_zero(); - dmn_w32_shared->halter_tid = 0; - } - - // rjf: if this thread is *not* the halter, then generate a regular exit-thread event - if(!is_halter) - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_ExitThread; - e->process = dmn_w32_handle_from_entity(process); - e->thread = dmn_w32_handle_from_entity(thread); - e->code = evt.u.ExitThread.dwExitCode; - } - - // rjf: release entity storage - dmn_w32_entity_release(thread); - }break; - - ////////////////////// - //- rjf: DLL was loaded - // - case LOAD_DLL_DEBUG_EVENT: - { - DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId); - - // rjf: extract image info - U64 module_base = (U64)evt.u.LoadDll.lpBaseOfDll; - DMN_W32_ImageInfo image_info = dmn_w32_image_info_from_process_base_vaddr(process->handle, module_base); - - // rjf: create module entity - DMN_W32_Entity *module = dmn_w32_entity_alloc(process, DMN_W32_EntityKind_Module, module_base); - { - module->handle = evt.u.LoadDll.hFile; - module->arch = image_info.arch; - module->module.vaddr_range = r1u64(module_base, module_base+image_info.size); - module->module.address_of_name_pointer = (U64)evt.u.LoadDll.lpImageName; - module->module.name_is_unicode = (evt.u.LoadDll.fUnicode != 0); - } - - // rjf: generate event - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_LoadModule; - e->process = dmn_w32_handle_from_entity(process); - e->module = dmn_w32_handle_from_entity(module); - e->arch = module->arch; - e->address = module_base; - e->size = image_info.size; - e->string = dmn_w32_full_path_from_module(arena, module); - } - }break; - - ////////////////////// - //- rjf: DLL was unloaded - // - case UNLOAD_DLL_DEBUG_EVENT: - { - U64 module_base = (U64)evt.u.UnloadDll.lpBaseOfDll; - DMN_W32_Entity *module = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Module, module_base); - DMN_W32_Entity *process = module->parent; - - // rjf: generate event - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_UnloadModule; - e->process = dmn_w32_handle_from_entity(process); - e->module = dmn_w32_handle_from_entity(module); - e->string = dmn_w32_full_path_from_module(arena, module); - } - - // rjf: release entity storage - dmn_w32_entity_release(module); - }break; - - ////////////////////// - //- rjf: exception was hit - // - case EXCEPTION_DEBUG_EVENT: - { - //- NOTE(rjf): Notes on multithreaded breakpoint events - // (2021/11/1): - // - // When many threads are simultaneously running, multiple threads - // may hit a trap "at the same time". When this happens there will be - // multiple events in an internal queue that we cannot see. If there - // is another event in the queue we will not see it until we call - // ContinueDebugEvent again, in a subsequent call to demon_os_run. - // - // When we get a trap event, the instruction pointer stored - // in the event will have the address of the int 3 instruction that - // was hit. Our RIP register, however, will be one byte past that. - // So, to get the behavior we want, we need to set the RIP register - // back to the address of the int 3. - // - // To deal with the fact that we may get breakpoint events later that - // were actually from this run what we do is: - // - // #1. If we get a trap event, and it corresponds to a user submitted - // trap, then we treat it is a breakpoint event. - // #2. If we get a trap event, and it does NOT correspond to a user - // trap in this call: - // #A. If the actual unmodified instruction byte is NOT an int 3, - // then this is a queued event from a previous run that is no - // longer applicable and we skip it. - // #B. If the actual unmodified instruction is an int 3, then this - // becomes a trap event and we do not reset RIP. - - //- NOTE(rjf): Further notes on MULTITHREADED STEPPING ACCESS VIOLATION - // EVENTS! @rjf @rjf @rjf - // (2024/05/29): - // - // Just adding another comment here to document that the above long - // comment went completely unnoticed by me during a pass over demon, - // and I had removed the proper rollback stuff here without reading - // the above comment. So this comment just serves to make that - // original comment even heftier. - - //- NOTE(rjf): The exception record struct has a 32-bit version and a - // 64-bit version. We only currently handle the 64-bit version. - - //- rjf: unpack - DMN_W32_Entity *thread = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Thread, evt.dwThreadId); - DMN_W32_Entity *process = thread->parent; - EXCEPTION_DEBUG_INFO *edi = &evt.u.Exception; - EXCEPTION_RECORD *exception = &edi->ExceptionRecord; - U64 instruction_pointer = (U64)exception->ExceptionAddress; - - //- rjf: determine if this is the first breakpoint in a process - // (breakpoint notifying us that the debugger is attached) - B32 first_bp = 0; - if(!process->proc.did_first_bp && exception->ExceptionCode == DMN_W32_EXCEPTION_BREAKPOINT) - { - process->proc.did_first_bp = 1; - first_bp = 1; - } - - //- rjf: determine if this exception is a trap - B32 is_trap = (!first_bp && - (exception->ExceptionCode == DMN_W32_EXCEPTION_BREAKPOINT || - exception->ExceptionCode == DMN_W32_EXCEPTION_STACK_BUFFER_OVERRUN)); - - //- rjf: check if this trap is a usage-code-specified trap or something else - B32 hit_user_trap = 0; - if(is_trap) - { - for(DMN_TrapChunkNode *n = ctrls->traps.first; n != 0; n = n->next) - { - for(U64 idx = 0; idx < n->count; idx += 1) - { - if(dmn_handle_match(n->v[idx].process, dmn_w32_handle_from_entity(process)) && n->v[idx].vaddr == instruction_pointer) - { - hit_user_trap = 1; - break; - } - } - } - } - - //- rjf: check if trap is explicit in the actual code memory - B32 hit_explicit_trap = 0; - if(is_trap && !hit_user_trap) - { - U8 instruction_byte = 0; - if(dmn_w32_process_read_struct(process->handle, instruction_pointer, &instruction_byte)) - { - hit_explicit_trap = (instruction_byte == 0xCC || instruction_byte == 0xCD); - } - } - - //- rjf: determine whether to roll back instruction pointer - B32 should_do_rollback = (hit_user_trap || (is_trap && !hit_explicit_trap)); - - //- rjf: roll back thread's instruction pointer - if(should_do_rollback) ProfScope("roll back thread's instruction pointer") - { - switch(thread->arch) - { - //- rjf: default, general path - default: - { - Temp temp = temp_begin(scratch.arena); - U64 regs_block_size = regs_block_size_from_architecture(thread->arch); - void *regs_block = push_array(scratch.arena, U8, regs_block_size); - if(dmn_w32_thread_read_reg_block(thread->arch, thread->handle, regs_block)) - { - regs_arch_block_write_rip(thread->arch, regs_block, instruction_pointer); - dmn_w32_thread_write_reg_block(thread->arch, thread->handle, regs_block); - } - temp_end(temp); - }break; - - //- rjf: x64 (fastpath) - case Architecture_x64: - { - CONTEXT *ctx = 0; - U32 ctx_flags = DMN_W32_CTX_X64|DMN_W32_CTX_INTEL_CONTROL; - DWORD size = 0; - InitializeContext(0, ctx_flags, 0, &size); - if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) - { - void *ctx_memory = push_array(scratch.arena, U8, size); - if(!InitializeContext(ctx_memory, ctx_flags, &ctx, &size)) - { - ctx = 0; - } - } - if(!GetThreadContext(thread->handle, ctx)) - { - ctx = 0; - } - if(ctx != 0) - { - U64 rip = ctx->Rip; - U64 new_rip = instruction_pointer; - ctx->Rip = new_rip; - SetThreadContext(thread->handle, ctx); - ins_atomic_u64_inc_eval(&dmn_w32_shared->reg_gen); - } - }break; - } - } - - //- rjf: not a user trap, not an explicit trap, then it's a trap that - // this thread hit previously but has since skipped - B32 hit_previous_trap = (is_trap && !hit_user_trap && !hit_explicit_trap); - - //- rjf: determine whether to skip this event - B32 skip_event = (hit_previous_trap); - - //- rjf: generate event - if(!skip_event) - { - // rjf: fill top-level info - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_Exception; - e->process = dmn_w32_handle_from_entity(process); - e->thread = dmn_w32_handle_from_entity(thread); - e->code = exception->ExceptionCode; - e->flags = exception->ExceptionFlags; - e->instruction_pointer = (U64)exception->ExceptionAddress; - - //- rjf: fill according to exception code - switch(exception->ExceptionCode) - { - //- rjf: fill breakpoint event info - case DMN_W32_EXCEPTION_BREAKPOINT: - { - DMN_EventKind report_event_kind = DMN_EventKind_Trap; - if(first_bp) - { - report_event_kind = DMN_EventKind_HandshakeComplete; - } - else if(hit_user_trap) - { - report_event_kind = DMN_EventKind_Breakpoint; - } - e->kind = report_event_kind; - }break; - - //- rjf: fill stack buffer overrun event info - case DMN_W32_EXCEPTION_STACK_BUFFER_OVERRUN: - { - e->kind = DMN_EventKind_Trap; - }break; - - //- rjf: fill single-step event info - case DMN_W32_EXCEPTION_SINGLE_STEP: - { - e->kind = DMN_EventKind_SingleStep; - }break; - - //- rjf: fill throw info - case DMN_W32_EXCEPTION_THROW: - { - U64 exception_sp = 0; - U64 exception_ip = 0; - if(exception->NumberParameters >= 3) - { - exception_sp = (U64)exception->ExceptionInformation[1]; - exception_ip = (U64)exception->ExceptionInformation[2]; - } - e->stack_pointer = exception_sp; - e->exception_kind = DMN_ExceptionKind_CppThrow; - e->exception_repeated = (edi->dwFirstChance == 0); - dmn_w32_shared->exception_not_handled = (edi->dwFirstChance != 0); - }break; - - //- rjf: fill access violation info - case DMN_W32_EXCEPTION_ACCESS_VIOLATION: - case DMN_W32_EXCEPTION_IN_PAGE_ERROR: - { - U64 exception_address = 0; - DMN_ExceptionKind exception_kind = DMN_ExceptionKind_Null; - if(exception->NumberParameters >= 2) - { - switch(exception->ExceptionInformation[0]) - { - case 0: exception_kind = DMN_ExceptionKind_MemoryRead; break; - case 1: exception_kind = DMN_ExceptionKind_MemoryWrite; break; - case 8: exception_kind = DMN_ExceptionKind_MemoryExecute; break; - } - exception_address = exception->ExceptionInformation[1]; - } - e->address = exception_address; - e->exception_kind = exception_kind; - e->exception_repeated = (edi->dwFirstChance == 0); - dmn_w32_shared->exception_not_handled = (edi->dwFirstChance != 0); - }break; - - //- rjf: fill set-thread-name info - case DMN_W32_EXCEPTION_SET_THREAD_NAME: - if(exception->NumberParameters >= 2) - { - U64 thread_name_address = exception->ExceptionInformation[1]; - DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId); - String8List thread_name_strings = {0}; - { - U64 read_addr = thread_name_address; - U64 total_string_size = 0; - for(;total_string_size < KB(4);) - { - U8 *buffer = push_array(scratch.arena, U8, 256); - B32 good_read = dmn_w32_process_read(process->handle, r1u64(read_addr, read_addr+256), buffer); - if(good_read) - { - U64 size = 256; - for(U64 idx = 0; idx < 256; idx += 1) - { - if(buffer[idx] == 0) - { - size = idx; - break; - } - } - String8 string_part = str8(buffer, size); - str8_list_push(scratch.arena, &thread_name_strings, string_part); - total_string_size += size; - read_addr += size; - if(size < 256) - { - break; - } - } - else - { - break; - } - } - } - e->kind = DMN_EventKind_SetThreadName; - e->string = str8_list_join(arena, &thread_name_strings, 0); - if(exception->NumberParameters > 2) - { - e->code = exception->ExceptionInformation[2]; - } - }break; - - //- rjf: unhandled exception case - default: - { - e->exception_repeated = (edi->dwFirstChance == 0); - dmn_w32_shared->exception_not_handled = (edi->dwFirstChance != 0); - }break; - } - } - }break; - - ////////////////////// - //- rjf: output debug string was gathered - // - case OUTPUT_DEBUG_STRING_EVENT: - { - // rjf: unpack event - DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId); - DMN_W32_Entity *thread = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Thread, evt.dwThreadId); - U64 string_address = (U64)evt.u.DebugString.lpDebugStringData; - U64 string_size = (U64)evt.u.DebugString.nDebugStringLength; - - // rjf: read memory - U8 *buffer = push_array_no_zero(scratch.arena, U8, string_size + 1); - dmn_w32_process_read(process->handle, r1u64(string_address, string_address+string_size), buffer); - buffer[string_size] = 0; - - // rjf: extract into string - String8 debug_string = str8(buffer, string_size); - if(debug_string.size != 0 && buffer[string_size-1] == 0) - { - debug_string.size -= 1; - } - - // rjf: make debug string event - debug_strings_event = dmn_event_list_push(arena, &events); - debug_strings_event->kind = DMN_EventKind_DebugString; - - // rjf: push into debug strings - str8_list_push(scratch.arena, &debug_strings, debug_string); - keep_going = 1; - - // rjf: exit loop, given sufficient amount of text - if(debug_strings.total_size >= KB(4)) - { - keep_going = 0; - } - }break; - - ////////////////////// - //- rjf: a "rip event" - a "system debugging error". - // - case RIP_EVENT: - { - DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId); - DMN_W32_Entity *thread = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Thread, evt.dwThreadId); - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_Exception; - e->process = dmn_w32_handle_from_entity(process); - e->thread = dmn_w32_handle_from_entity(thread); - }break; - - ////////////////////// - //- rjf: default case - some kind of debugging event that we don't currently consume. - // - default: - { - NoOp; - }break; - } - } - - //////////////////////// - //- rjf: exit loop after a little while, so we keep pumping e.g. debug strings - // - if(os_now_microseconds() >= begin_time+100000) - { - keep_going = 0; - } - } - - //////////////////////// - //- rjf: send out event for any remaining debug strings - // - if(debug_strings.total_size != 0 && debug_strings_event != 0) - { - String8 debug_strings_joined = str8_list_join(arena, &debug_strings, 0); - debug_strings_event->string = debug_strings_joined; - } - - //////////////////////// - //- rjf: suspend threads which ran - // - ProfScope("suspend threads which ran") - { - for(DMN_W32_EntityNode *n = first_run_thread; n != 0; n = n->next) - { - DMN_W32_Entity *thread = n->v; - DWORD suspend_result = SuspendThread(thread->handle); - switch(suspend_result) - { - case 0xffffffffu: - { - // TODO(rjf): error - unknown cause. need to do do GetLastError, FormatMessage - // - // NOTE(rjf): this can happen when the event is EXIT_THREAD_DEBUG_EVENT - // or EXIT_PROCESS_DEBUG_EVENT. after such an event, SuspendThread - // gives error code 5 (access denied). this has no adverse effects, but - // if we want to start reporting errors we should take care to avoid - // calling SuspendThread in that case. - }break; - default: - { - DWORD desired_counter = 1; - DWORD current_counter = suspend_result + 1; - if(current_counter != desired_counter) - { - // NOTE(rjf): Warning. We've suspended to something higher than 1. - // In this case, it means the user probably created the thread in - // a suspended state, or they called SuspendThread. - } - }break; - } - } - } - - //- rjf: gather new thread-names - ProfScope("gather new thread names") if(dmn_w32_GetThreadDescription != 0) - { - for(DMN_W32_Entity *process = dmn_w32_shared->entities_base->first; - process != &dmn_w32_entity_nil; - process = process->next) - { - if(process->kind != DMN_W32_EntityKind_Process) { continue; } - for(DMN_W32_Entity *thread = process->first; - thread != &dmn_w32_entity_nil; - thread = thread->next) - { - if(thread->kind != DMN_W32_EntityKind_Thread) { continue; } - if(thread->thread.last_name_hash == 0 || - thread->thread.name_gather_time_us+1000000 <= os_now_microseconds()) - { - String8 name = {0}; - { - WCHAR *thread_name_w = 0; - HRESULT hr = dmn_w32_GetThreadDescription(thread->handle, &thread_name_w); - if(SUCCEEDED(hr)) - { - name = str8_from_16(scratch.arena, str16_cstring((U16 *)thread_name_w)); - LocalFree(thread_name_w); - } - } - U64 name_hash = dmn_w32_hash_from_string(name); - if(name.size != 0 && name_hash != thread->thread.last_name_hash) - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_SetThreadName; - e->process = dmn_w32_handle_from_entity(process); - e->thread = dmn_w32_handle_from_entity(thread); - e->string = push_str8_copy(arena, name); - } - thread->thread.name_gather_time_us = os_now_microseconds(); - thread->thread.last_name_hash = name_hash; - } - } - } - } - - ////////////////////////// - //- rjf: restore original memory at trap locations - // - ProfScope("restore original memory at trap locations") - { - U64 trap_idx = 0; - for(DMN_TrapChunkNode *n = ctrls->traps.first; n != 0; n = n->next) - { - for(U64 n_idx = 0; n_idx < n->count; n_idx += 1, trap_idx += 1) - { - DMN_Trap *trap = n->v+n_idx; - U8 og_byte = trap_swap_bytes[trap_idx]; - if(og_byte != 0xCC) - { - dmn_process_write(trap->process, r1u64(trap->vaddr, trap->vaddr+1), &og_byte); - } - } - } - } - - ////////////////////////// - //- rjf: unset single step bit - // - if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) ProfScope("unset single step bit") - { - DMN_W32_Entity *thread = dmn_w32_entity_from_handle(ctrls->single_step_thread); - Architecture arch = thread->arch; - switch(arch) - { - //- rjf: unimplemented win32/arch combos - case Architecture_Null: - case Architecture_COUNT: - {}break; - case Architecture_arm64: - case Architecture_arm32: - {NotImplemented;}break; - - //- rjf: x86/64 - case Architecture_x86: - { - REGS_RegBlockX86 regs = {0}; - dmn_thread_read_reg_block(ctrls->single_step_thread, ®s); - regs.eflags.u32 &= ~0x100; - dmn_thread_write_reg_block(ctrls->single_step_thread, ®s); - }break; - case Architecture_x64: - { - if(!GetThreadContext(thread->handle, single_step_thread_ctx)) - { - single_step_thread_ctx = 0; - } - if(ctx != 0) - { - U64 rflags = single_step_thread_ctx->EFlags|0x2; - U64 new_rflags = rflags & ~0x100; - single_step_thread_ctx->EFlags = new_rflags; - SetThreadContext(thread->handle, single_step_thread_ctx); - ins_atomic_u64_inc_eval(&dmn_w32_shared->reg_gen); - } - }break; - } - } - - scratch_end(scratch); - }break; - - //////////////////////////// - //- rjf: produce debug events from queued up detached processes - // - case DMN_W32_EventGenPath_DetachProcesses: - { - for(DMN_HandleNode *n = dmn_w32_shared->detach_processes.first; n != 0; n = n->next) - { - DMN_W32_Entity *process = dmn_w32_entity_from_handle(n->v); - - // rjf: push exit thread events - for(DMN_W32_Entity *child = process->first; child != &dmn_w32_entity_nil; child = child->next) - { - if(child->kind == DMN_W32_EntityKind_Thread) - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_ExitThread; - e->process = dmn_w32_handle_from_entity(process); - e->thread = dmn_w32_handle_from_entity(child); - } - } - - // rjf: push unload module events - for(DMN_W32_Entity *child = process->first; child != &dmn_w32_entity_nil; child = child->next) - { - if(child->kind == DMN_W32_EntityKind_Module) - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_UnloadModule; - e->process = dmn_w32_handle_from_entity(process); - e->module = dmn_w32_handle_from_entity(child); - e->string = dmn_w32_full_path_from_module(arena, child); - } - } - - // rjf: push exit process event - { - DMN_Event *e = dmn_event_list_push(arena, &events); - e->kind = DMN_EventKind_ExitProcess; - e->process = dmn_w32_handle_from_entity(process); - } - - // rjf: free process - dmn_w32_entity_release(process); - } - - // rjf: reset queued up detached processes - MemoryZeroStruct(&dmn_w32_shared->detach_processes); - arena_clear(dmn_w32_shared->detach_arena); - }break; - } - - dmn_access_close(); - return events; -} - -//////////////////////////////// -//~ rjf: @dmn_os_hooks Halting (Implemented Per-OS) - -internal void -dmn_halt(U64 code, U64 user_data) -{ - if(dmn_handle_match(dmn_handle_zero(), dmn_w32_shared->halter_process)) - { - DMN_W32_Entity *process = &dmn_w32_entity_nil; - for(DMN_W32_Entity *entity = dmn_w32_shared->entities_base->first; - entity != &dmn_w32_entity_nil; - entity = entity->next) - { - if(entity->kind == DMN_W32_EntityKind_Process) - { - process = entity; - break; - } - } - if(process != &dmn_w32_entity_nil) - { - dmn_w32_shared->halter_process = dmn_w32_handle_from_entity(process); - DMN_W32_InjectedBreak injection = {code, user_data}; - U64 data_injection_address = process->proc.injection_address + DMN_W32_INJECTED_CODE_SIZE; - dmn_w32_process_write_struct(process->handle, data_injection_address, &injection); - dmn_w32_shared->halter_tid = dmn_w32_inject_thread(process->handle, process->proc.injection_address); - } - } -} - -//////////////////////////////// -//~ rjf: @dmn_os_hooks Introspection Functions (Implemented Per-OS) - -//- rjf: run/memory/register counters - -internal U64 -dmn_run_gen(void) -{ - U64 result = ins_atomic_u64_eval(&dmn_w32_shared->run_gen); - return result; -} - -internal U64 -dmn_mem_gen(void) -{ - U64 result = ins_atomic_u64_eval(&dmn_w32_shared->mem_gen); - return result; -} - -internal U64 -dmn_reg_gen(void) -{ - U64 result = ins_atomic_u64_eval(&dmn_w32_shared->reg_gen); - return result; -} - -//- rjf: non-blocking-control-thread access barriers - -internal B32 -dmn_access_open(void) -{ - B32 result = 0; - if(dmn_w32_ctrl_thread) - { - result = 1; - } - else - { - os_mutex_take(dmn_w32_shared->access_mutex); - result = !dmn_w32_shared->access_run_state; - } - return result; -} - -internal void -dmn_access_close(void) -{ - if(!dmn_w32_ctrl_thread) - { - os_mutex_drop(dmn_w32_shared->access_mutex); - } -} - -//- rjf: processes - -internal U64 -dmn_process_memory_reserve(DMN_Handle process, U64 vaddr, U64 size) -{ - U64 result = 0; - DMN_AccessScope - { - DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); - result = (U64)VirtualAllocEx(process_entity->handle, (void *)vaddr, size, MEM_RESERVE, PAGE_READWRITE); - if(result == 0) - { - result = (U64)VirtualAllocEx(process_entity->handle, 0, size, MEM_RESERVE, PAGE_READWRITE); - } - } - return result; -} - -internal void -dmn_process_memory_commit(DMN_Handle process, U64 vaddr, U64 size) -{ - DMN_AccessScope - { - DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); - (U64)VirtualAllocEx(process_entity->handle, (void *)vaddr, size, MEM_COMMIT, PAGE_READWRITE); - } -} - -internal void -dmn_process_memory_decommit(DMN_Handle process, U64 vaddr, U64 size) -{ - DMN_AccessScope - { - DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); - VirtualFreeEx(process_entity->handle, (void *)vaddr, size, MEM_DECOMMIT); - } -} - -internal void -dmn_process_memory_release(DMN_Handle process, U64 vaddr, U64 size) -{ - DMN_AccessScope - { - DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); - VirtualFreeEx(process_entity->handle, (void *)vaddr, 0, MEM_RELEASE); - } -} - -internal void -dmn_process_memory_protect(DMN_Handle process, U64 vaddr, U64 size, OS_AccessFlags flags) -{ - DMN_AccessScope - { - DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); - DWORD old_flags = 0; - DWORD new_flags = PAGE_NOACCESS; - switch(flags) - { - default:{}break; - case OS_AccessFlag_Execute:{new_flags = PAGE_EXECUTE;}break; - case OS_AccessFlag_Execute|OS_AccessFlag_Read:{new_flags = PAGE_EXECUTE_READ;}break; - case OS_AccessFlag_Execute|OS_AccessFlag_Read|OS_AccessFlag_Write:{new_flags = PAGE_EXECUTE_READWRITE;}break; - case OS_AccessFlag_Read:{new_flags = PAGE_READONLY;}break; - case OS_AccessFlag_Read|OS_AccessFlag_Write:{new_flags = PAGE_READWRITE;}break; - } - VirtualProtectEx(process_entity->handle, (void *)vaddr, size, new_flags, &old_flags); - } -} - -internal U64 -dmn_process_read(DMN_Handle process, Rng1U64 range, void *dst) -{ - U64 result = 0; - DMN_AccessScope - { - DMN_W32_Entity *entity = dmn_w32_entity_from_handle(process); - result = dmn_w32_process_read(entity->handle, range, dst); - } - return result; -} - -internal B32 -dmn_process_write(DMN_Handle process, Rng1U64 range, void *src) -{ - B32 result = 0; - DMN_AccessScope - { - DMN_W32_Entity *entity = dmn_w32_entity_from_handle(process); - result = dmn_w32_process_write(entity->handle, range, src); - } - return result; -} - -//- rjf: threads - -internal Architecture -dmn_arch_from_thread(DMN_Handle handle) -{ - Architecture arch = Architecture_Null; - DMN_AccessScope - { - DMN_W32_Entity *entity = dmn_w32_entity_from_handle(handle); - arch = entity->arch; - } - return arch; -} - -internal U64 -dmn_stack_base_vaddr_from_thread(DMN_Handle handle) -{ - U64 result = 0; - DMN_AccessScope - { - DMN_W32_Entity *thread = dmn_w32_entity_from_handle(handle); - if(thread->kind == DMN_W32_EntityKind_Thread) - { - DMN_W32_Entity *process = thread->parent; - U64 tlb = thread->thread.thread_local_base; - switch(thread->arch) - { - case Architecture_Null: - case Architecture_COUNT: - {}break; - case Architecture_arm64: - case Architecture_arm32: - {NotImplemented;}break; - case Architecture_x64: - { - U64 stack_base_addr = tlb + 0x8; - dmn_w32_process_read(process->handle, r1u64(stack_base_addr, stack_base_addr+8), &result); - }break; - case Architecture_x86: - { - U64 stack_base_addr = tlb + 0x4; - dmn_w32_process_read(process->handle, r1u64(stack_base_addr, stack_base_addr+4), &result); - }break; - } - } - } - return result; -} - -internal U64 -dmn_tls_root_vaddr_from_thread(DMN_Handle handle) -{ - U64 result = 0; - DMN_AccessScope - { - DMN_W32_Entity *entity = dmn_w32_entity_from_handle(handle); - if(entity->kind == DMN_W32_EntityKind_Thread) - { - result = entity->thread.thread_local_base; - switch(entity->arch) - { - case Architecture_Null: - case Architecture_COUNT: - {}break; - case Architecture_arm64: - case Architecture_arm32: - {NotImplemented;}break; - case Architecture_x64: - { - result += 88; - }break; - case Architecture_x86: - { - result += 44; - }break; - } - } - } - return result; -} - -internal B32 -dmn_thread_read_reg_block(DMN_Handle handle, void *reg_block) -{ - B32 result = 0; - DMN_AccessScope - { - DMN_W32_Entity *thread = dmn_w32_entity_from_handle(handle); - result = dmn_w32_thread_read_reg_block(thread->arch, thread->handle, reg_block); - } - return result; -} - -internal B32 -dmn_thread_write_reg_block(DMN_Handle handle, void *reg_block) -{ - B32 result = 0; - DMN_AccessScope - { - DMN_W32_Entity *thread = dmn_w32_entity_from_handle(handle); - result = dmn_w32_thread_write_reg_block(thread->arch, thread->handle, reg_block); - } - return result; -} - -//- rjf: system process listing - -internal void -dmn_process_iter_begin(DMN_ProcessIter *iter) -{ - MemoryZeroStruct(iter); - iter->v[0] = (U64)CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); -} - -internal B32 -dmn_process_iter_next(Arena *arena, DMN_ProcessIter *iter, DMN_ProcessInfo *info_out) -{ - B32 result = 0; - - //- rjf: get the next process entry - PROCESSENTRY32W process_entry = {sizeof(process_entry)}; - HANDLE snapshot = (HANDLE)iter->v[0]; - if(iter->v[1] == 0) - { - if(Process32FirstW(snapshot, &process_entry)) - { - result = 1; - } - } - else - { - if(Process32NextW(snapshot, &process_entry)) - { - result = 1; - } - } - - //- rjf: increment counter - iter->v[1] += 1; - - //- rjf: convert to process info - if(result) - { - info_out->name = str8_from_16(arena, str16_cstring((U16*)process_entry.szExeFile)); - info_out->pid = (U32)process_entry.th32ProcessID; - } - - return result; -} - -internal void -dmn_process_iter_end(DMN_ProcessIter *iter) -{ - CloseHandle((HANDLE)iter->v[0]); - MemoryZeroStruct(iter); -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Basic Helpers + +internal U64 +dmn_w32_hash_from_string(String8 string) +{ + U64 result = 5381; + for(U64 i = 0; i < string.size; i += 1) + { + result = ((result << 5) + result) + string.str[i]; + } + return result; +} + +internal U64 +dmn_w32_hash_from_id(U64 id) +{ + return dmn_w32_hash_from_string(str8_struct(&id)); +} + +//////////////////////////////// +//~ rjf: Entity Helpers + +//- rjf: entity <-> handle + +internal DMN_Handle +dmn_w32_handle_from_entity(DMN_W32_Entity *entity) +{ + U32 idx = (U32)(entity - dmn_w32_shared->entities_base); + U32 gen = entity->gen; + DMN_Handle handle = {idx, gen}; + return handle; +} + +internal DMN_W32_Entity * +dmn_w32_entity_from_handle(DMN_Handle handle) +{ + U32 idx = handle.u32[0]; + U32 gen = handle.u32[1]; + DMN_W32_Entity *entity = dmn_w32_shared->entities_base + idx; + if(entity->gen != gen) + { + entity = &dmn_w32_entity_nil; + } + return entity; +} + +//- rjf: entity allocation/deallocation + +internal DMN_W32_Entity * +dmn_w32_entity_alloc(DMN_W32_Entity *parent, DMN_W32_EntityKind kind, U64 id) +{ + // rjf: allocate + DMN_W32_Entity *e = dmn_w32_shared->entities_first_free; + { + if(e != 0) + { + SLLStackPop(dmn_w32_shared->entities_first_free); + } + else + { + e = push_array_no_zero(dmn_w32_shared->entities_arena, DMN_W32_Entity, 1); + dmn_w32_shared->entities_count += 1; + } + U32 gen = e->gen; + MemoryZeroStruct(e); + e->gen = gen+1; + } + + // rjf: fill + { + e->kind = kind; + e->id = id; + e->parent = parent; + e->next = e->prev = e->first = e->last = &dmn_w32_entity_nil; + if(parent != &dmn_w32_entity_nil) + { + DLLPushBack_NPZ(&dmn_w32_entity_nil, parent->first, parent->last, e, next, prev); + } + } + + // rjf: insert into id -> entity map + if(id != 0) + { + U64 hash = dmn_w32_hash_from_id(id); + U64 slot_idx = hash%dmn_w32_shared->entities_id_hash_slots_count; + DMN_W32_EntityIDHashSlot *slot = &dmn_w32_shared->entities_id_hash_slots[slot_idx]; + DMN_W32_EntityIDHashNode *node = 0; + for(DMN_W32_EntityIDHashNode *n = slot->first; n != 0; n = n->next) + { + if(n->id == id) + { + node = n; + break; + } + } + if(node == 0) + { + node = dmn_w32_shared->entities_id_hash_node_free; + if(node != 0) + { + SLLStackPop(dmn_w32_shared->entities_id_hash_node_free); + } + else + { + node = push_array(dmn_w32_shared->arena, DMN_W32_EntityIDHashNode, 1); + } + DLLPushBack(slot->first, slot->last, node); + } + node->id = id; + node->entity = e; + } + + return e; +} + +internal void +dmn_w32_entity_release(DMN_W32_Entity *entity) +{ + // rjf: unhook root + if(entity->parent != &dmn_w32_entity_nil) + { + DLLRemove_NPZ(&dmn_w32_entity_nil, entity->parent->first, entity->parent->last, entity, next, prev); + } + + // rjf: walk every entity in this tree, free each + if(entity != &dmn_w32_entity_nil) + { + Temp scratch = scratch_begin(0, 0); + typedef struct Task Task; + struct Task + { + Task *next; + DMN_W32_Entity *e; + }; + Task start_task = {0, entity}; + Task *first_task = &start_task; + Task *last_task = &start_task; + for(Task *t = first_task; t != 0; t = t->next) + { + for(DMN_W32_Entity *child = t->e->first; child != &dmn_w32_entity_nil; child = child->next) + { + Task *t = push_array(scratch.arena, Task, 1); + t->e = child; + SLLQueuePush(first_task, last_task, t); + } + + // rjf: free entity + SLLStackPush(dmn_w32_shared->entities_first_free, t->e); + t->e->gen += 1; + if(t->e->kind == DMN_W32_EntityKind_Module) + { + CloseHandle(t->e->handle); + } + + // rjf: remove from id -> entity map + if(t->e->id != 0) + { + U64 hash = dmn_w32_hash_from_id(t->e->id); + U64 slot_idx = hash%dmn_w32_shared->entities_id_hash_slots_count; + DMN_W32_EntityIDHashSlot *slot = &dmn_w32_shared->entities_id_hash_slots[slot_idx]; + DMN_W32_EntityIDHashNode *node = 0; + for(DMN_W32_EntityIDHashNode *n = slot->first; n != 0; n = n->next) + { + if(n->id == t->e->id && n->entity == t->e) + { + DLLRemove(slot->first, slot->last, n); + SLLStackPush(dmn_w32_shared->entities_id_hash_node_free, n); + break; + } + } + } + } + scratch_end(scratch); + } +} + +//- rjf: kind*id -> entity + +internal DMN_W32_Entity * +dmn_w32_entity_from_kind_id(DMN_W32_EntityKind kind, U64 id) +{ + DMN_W32_Entity *result = &dmn_w32_entity_nil; + U64 hash = dmn_w32_hash_from_id(id); + U64 slot_idx = hash%dmn_w32_shared->entities_id_hash_slots_count; + DMN_W32_EntityIDHashSlot *slot = &dmn_w32_shared->entities_id_hash_slots[slot_idx]; + DMN_W32_EntityIDHashNode *node = 0; + for(DMN_W32_EntityIDHashNode *n = slot->first; n != 0; n = n->next) + { + if(n->entity->kind == kind && n->id == id) + { + node = n; + break; + } + } + if(node != 0) + { + result = node->entity; + } + return result; +} + +//////////////////////////////// +//~ rjf: Module Info Extraction + +internal String8 +dmn_w32_full_path_from_module(Arena *arena, DMN_W32_Entity *module) +{ + Temp scratch = scratch_begin(&arena, 1); + + //- rjf: extract path from module + String16 path16 = {0}; + String8 path8 = {0}; + { + // rjf: handle -> full path + if(module->handle != 0) + { + DWORD cap16 = GetFinalPathNameByHandleW(module->handle, 0, 0, VOLUME_NAME_DOS); + U16 *buffer16 = push_array_no_zero(scratch.arena, U16, cap16); + DWORD size16 = GetFinalPathNameByHandleW(module->handle, (WCHAR*)buffer16, cap16, VOLUME_NAME_DOS); + path16 = str16(buffer16, size16); + } + + // rjf: fallback (main module only): process -> full path + if(path16.size == 0 && module->module.is_main) + { + DMN_W32_Entity *process = module->parent; + DWORD size = KB(4); + U16 *buf = push_array_no_zero(scratch.arena, U16, size); + if(QueryFullProcessImageNameW(process->handle, 0, (WCHAR*)buf, &size)) + { + path16 = str16(buf, size); + } + } + + // rjf: fallback (any module - no guarantee): address_of_name -> full path + if(path16.size == 0 && module->module.address_of_name_pointer != 0) + { + DMN_W32_Entity *process = module->parent; + U64 ptr_size = bit_size_from_arch(process->arch)/8; + U64 name_pointer = 0; + if(dmn_w32_process_read(process->handle, r1u64(module->module.address_of_name_pointer, module->module.address_of_name_pointer+ptr_size), &name_pointer)) + { + if(name_pointer != 0) + { + if(module->module.name_is_unicode) + { + path16 = dmn_w32_read_memory_str16(scratch.arena, process->handle, name_pointer); + } + else + { + path8 = dmn_w32_read_memory_str(scratch.arena, process->handle, name_pointer); + } + } + } + } + } + + // rjf: produce finalized result + String8 result = {0}; + { + if(path16.size > 0) + { + // rjf: skip the extended path thing if necessary + if(path16.size >= 4 && + path16.str[0] == L'\\' && + path16.str[1] == L'\\' && + path16.str[2] == L'?' && + path16.str[3] == L'\\') + { + path16.size -= 4; + path16.str += 4; + } + + // rjf: convert to UTF-8 + result = str8_from_16(arena, path16); + } + else + { + // rjf: skip the extended path thing if necessary + if (path8.size >= 4 && + path8.str[0] == L'\\' && + path8.str[1] == L'\\' && + path8.str[2] == L'?' && + path8.str[3] == L'\\') + { + path8.size -= 4; + path8.str += 4; + } + + // rjf: copy to output arena + result = push_str8_copy(arena, path8); + } + } + + scratch_end(scratch); + return result; +} + +//////////////////////////////// +//~ rjf: Win32-Level Process/Thread Reads/Writes + +//- rjf: processes + +internal U64 +dmn_w32_process_read(HANDLE process, Rng1U64 range, void *dst) +{ + U64 bytes_read = 0; + U8 *ptr = (U8*)dst; + U8 *opl = ptr + dim_1u64(range); + U64 cursor = range.min; + for(;ptr < opl;) + { + SIZE_T to_read = (SIZE_T)(opl - ptr); + SIZE_T actual_read = 0; + if(!ReadProcessMemory(process, (LPCVOID)cursor, ptr, to_read, &actual_read)) + { + bytes_read += actual_read; + break; + } + ptr += actual_read; + cursor += actual_read; + bytes_read += actual_read; + } + return bytes_read; +} + +internal B32 +dmn_w32_process_write(HANDLE process, Rng1U64 range, void *src) +{ + B32 result = 1; + U8 *ptr = (U8*)src; + U8 *opl = ptr + dim_1u64(range); + U64 cursor = range.min; + for(;ptr < opl;) + { + SIZE_T to_write = (SIZE_T)(opl - ptr); + SIZE_T actual_write = 0; + if(!WriteProcessMemory(process, (LPVOID)cursor, ptr, to_write, &actual_write)) + { + result = 0; + break; + } + ptr += actual_write; + cursor += actual_write; + } + ins_atomic_u64_inc_eval(&dmn_w32_shared->mem_gen); + return result; +} + +internal String8 +dmn_w32_read_memory_str(Arena *arena, HANDLE process_handle, U64 address) +{ + // TODO(rjf): @rewrite + // + // OLD: this could be done better with a demon_w32_read_memory + // that returns a read amount instead of a success/fail. + // + // (dmn_w32_process_read now does this, so we can switch to it) + + // scan piece by piece + Temp scratch = scratch_begin(&arena, 1); + String8List list = {0}; + + U64 max_cap = 256; + U64 cap = max_cap; + U64 read_p = address; + for (;;){ + U8 *block = push_array(scratch.arena, U8, cap); + for (;cap > 0;){ + if (dmn_w32_process_read(process_handle, r1u64(read_p, read_p+cap), block)){ + break; + } + cap /= 2; + } + read_p += cap; + + U64 block_opl = 0; + for (;block_opl < cap; block_opl += 1){ + if (block[block_opl] == 0){ + break; + } + } + + if (block_opl > 0){ + str8_list_push(scratch.arena, &list, str8(block, block_opl)); + } + + if (block_opl < cap || cap == 0){ + break; + } + } + + // assemble results + String8 result = str8_list_join(arena, &list, 0); + scratch_end(scratch); + return(result); +} + +internal String16 +dmn_w32_read_memory_str16(Arena *arena, HANDLE process_handle, U64 address) +{ + // TODO(rjf): @rewrite + // + // OLD: this could be done better with a demon_w32_read_memory + // that returns a read amount instead of a success/fail. + // + // (dmn_w32_process_read now does this, so we can switch to it) + + // scan piece by piece + Temp scratch = scratch_begin(&arena, 1); + String8List list = {0}; + + U64 max_cap = 256; + U64 cap = max_cap; + U64 read_p = address; + for (;;){ + U8 *block = push_array(scratch.arena, U8, cap); + for (;cap > 1;){ + if (dmn_w32_process_read(process_handle, r1u64(read_p, read_p+cap), block)){ + break; + } + cap /= 2; + } + read_p += cap; + + U16 *block16 = (U16*)block; + (void)block16; + U64 block_opl = 0; + for (;block_opl < cap; block_opl += 2){ + if (*(U16*)(block + block_opl) == 0){ + break; + } + } + + if (block_opl > 0){ + str8_list_push(scratch.arena, &list, str8(block, block_opl)); + } + + if (block_opl < cap || cap == 0){ + break; + } + } + + // assemble results + String8 joined = str8_list_join(arena, &list, 0); + String16 result = {(U16*)joined.str, joined.size/2}; + scratch_end(scratch); + return(result); +} + +internal DMN_W32_ImageInfo +dmn_w32_image_info_from_process_base_vaddr(HANDLE process, U64 base_vaddr) +{ + // rjf: find PE offset + U32 pe_offset = 0; + { + U64 dos_magic_off = base_vaddr; + U16 dos_magic = 0; + dmn_w32_process_read_struct(process, dos_magic_off, &dos_magic); + if(dos_magic == PE_DOS_MAGIC) + { + U64 pe_offset_off = base_vaddr + OffsetOf(PE_DosHeader, coff_file_offset); + dmn_w32_process_read_struct(process, pe_offset_off, &pe_offset); + } + } + + // rjf: get COFF header + B32 got_coff_header = 0; + U64 coff_header_off = 0; + COFF_Header coff_header = {0}; + if(pe_offset > 0) + { + U64 pe_magic_off = base_vaddr + pe_offset; + U32 pe_magic = 0; + dmn_w32_process_read_struct(process, pe_magic_off, &pe_magic); + if(pe_magic == PE_MAGIC) + { + coff_header_off = pe_magic_off + sizeof(pe_magic); + if(dmn_w32_process_read_struct(process, coff_header_off, &coff_header)) + { + got_coff_header = 1; + } + } + } + + // rjf: get arch and size + DMN_W32_ImageInfo result = zero_struct; + if(got_coff_header) + { + U64 optional_size_off = 0; + Architecture arch = Architecture_Null; + switch(coff_header.machine) + { + case COFF_MachineType_X86: + { + arch = Architecture_x86; + optional_size_off = OffsetOf(PE_OptionalHeader32, sizeof_image); + }break; + case COFF_MachineType_X64: + { + arch = Architecture_x64; + optional_size_off = OffsetOf(PE_OptionalHeader32Plus, sizeof_image); + }break; + default: + {}break; + } + if(arch != Architecture_Null) + { + U64 optional_off = coff_header_off + sizeof(coff_header); + U32 size = 0; + if(dmn_w32_process_read_struct(process, optional_off+optional_size_off, &size) >= sizeof(size)) + { + result.arch = arch; + result.size = size; + } + } + } + + return result; +} + +//- rjf: threads + +internal U16 +dmn_w32_real_tag_word_from_xsave(XSAVE_FORMAT *fxsave) +{ + U16 result = 0; + U32 top = (fxsave->StatusWord >> 11) & 7; + for(U32 fpr = 0; fpr < 8; fpr += 1) + { + U32 tag = 3; + if(fxsave->TagWord & (1 << fpr)) + { + U32 st = (fpr - top)&7; + + REGS_Reg80 *fp = (REGS_Reg80*)&fxsave->FloatRegisters[st*16]; + U16 exponent = fp->sign1_exp15 & bitmask15; + U64 integer_part = fp->int1_frac63 >> 63; + U64 fraction_part = fp->int1_frac63 & bitmask63; + + // tag: 0 - normal; 1 - zero; 2 - special + tag = 2; + if(exponent == 0) + { + if(integer_part == 0 && fraction_part == 0) + { + tag = 1; + } + } + else if(exponent != bitmask15 && integer_part != 0) + { + tag = 0; + } + } + result |= tag << (2 * fpr); + } + return result; +} + +internal U16 +dmn_w32_xsave_tag_word_from_real_tag_word(U16 ftw) +{ + U16 compact = 0; + for(U32 fpr = 0; fpr < 8; fpr++) + { + U32 tag = (ftw >> (fpr * 2)) & 3; + if(tag != 3) + { + compact |= (1 << fpr); + } + } + return compact; +} + +internal B32 +dmn_w32_thread_read_reg_block(Architecture arch, HANDLE thread, void *reg_block) +{ + B32 result = 0; + ProfBeginFunction(); + switch(arch) + { + //////////////////////////// + //- rjf: unimplemented win32/arch combos + // + case Architecture_Null: + case Architecture_COUNT: + {}break; + case Architecture_arm64: + case Architecture_arm32: + {NotImplemented;}break; + + //////////////////////////// + //- rjf: x86 + // + case Architecture_x86: + { + REGS_RegBlockX86 *dst = (REGS_RegBlockX86 *)reg_block; + + //- rjf: get thread context + WOW64_CONTEXT ctx = {0}; + ctx.ContextFlags = DMN_W32_CTX_X86_ALL; + if(!Wow64GetThreadContext(thread, (WOW64_CONTEXT *)&ctx)) + { + break; + } + result = 1; + + //- rjf: convert WOW64_CONTEXT -> REGS_RegBlockX86 + XSAVE_FORMAT *fxsave = (XSAVE_FORMAT *)ctx.ExtendedRegisters; + dst->eax.u32 = ctx.Eax; + dst->ebx.u32 = ctx.Ebx; + dst->ecx.u32 = ctx.Ecx; + dst->edx.u32 = ctx.Edx; + dst->esi.u32 = ctx.Esi; + dst->edi.u32 = ctx.Edi; + dst->esp.u32 = ctx.Esp; + dst->ebp.u32 = ctx.Ebp; + dst->eip.u32 = ctx.Eip; + dst->cs.u16 = ctx.SegCs; + dst->ds.u16 = ctx.SegDs; + dst->es.u16 = ctx.SegEs; + dst->fs.u16 = ctx.SegFs; + dst->gs.u16 = ctx.SegGs; + dst->ss.u16 = ctx.SegSs; + dst->dr0.u32 = ctx.Dr0; + dst->dr1.u32 = ctx.Dr1; + dst->dr2.u32 = ctx.Dr2; + dst->dr3.u32 = ctx.Dr3; + dst->dr6.u32 = ctx.Dr6; + dst->dr7.u32 = ctx.Dr7; + // NOTE(rjf): this bit is "supposed to always be 1", according to old info. + // may need to be investigated. + dst->eflags.u32 = ctx.EFlags | 0x2; + dst->fcw.u16 = fxsave->ControlWord; + dst->fsw.u16 = fxsave->StatusWord; + dst->ftw.u16 = dmn_w32_real_tag_word_from_xsave(fxsave); + dst->fop.u16 = fxsave->ErrorOpcode; + dst->fip.u32 = fxsave->ErrorOffset; + dst->fcs.u16 = fxsave->ErrorSelector; + dst->fdp.u32 = fxsave->DataOffset; + dst->fds.u16 = fxsave->DataSelector; + dst->mxcsr.u32 = fxsave->MxCsr; + dst->mxcsr_mask.u32 = fxsave->MxCsr_Mask; + { + M128A *float_s = fxsave->FloatRegisters; + REGS_Reg80 *float_d = &dst->fpr0; + for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1) + { + MemoryCopy(float_d, float_s, sizeof(*float_d)); + } + } + { + M128A *xmm_s = fxsave->XmmRegisters; + REGS_Reg256 *xmm_d = &dst->ymm0; + for(U32 n = 0; n < 8; n += 1, xmm_s += 1, xmm_d += 1) + { + MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_s)); + } + } + + //- rjf: read FS/GS base + WOW64_LDT_ENTRY ldt = {0}; + if(Wow64GetThreadSelectorEntry(thread, ctx.SegFs, &ldt)) + { + U32 base = (ldt.BaseLow) | (ldt.HighWord.Bytes.BaseMid << 16) | (ldt.HighWord.Bytes.BaseHi << 24); + dst->fsbase.u32 = base; + } + if(Wow64GetThreadSelectorEntry(thread, ctx.SegGs, &ldt)) + { + U32 base = (ldt.BaseLow) | (ldt.HighWord.Bytes.BaseMid << 16) | (ldt.HighWord.Bytes.BaseHi << 24); + dst->gsbase.u32 = base; + } + }break; + + //////////////////////////// + //- rjf: x64 + // + case Architecture_x64: + { + Temp scratch = scratch_begin(0, 0); + REGS_RegBlockX64 *dst = (REGS_RegBlockX64 *)reg_block; + + //- rjf: unpack info about available features + U32 feature_mask = GetEnabledXStateFeatures(); + B32 avx_enabled = !!(feature_mask & XSTATE_MASK_AVX); + + //- rjf: set up context + CONTEXT *ctx = 0; + U32 ctx_flags = DMN_W32_CTX_X64_ALL; + if(avx_enabled) + { + ctx_flags |= DMN_W32_CTX_INTEL_XSTATE; + } + DWORD size = 0; + InitializeContext(0, ctx_flags, 0, &size); + if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + void *ctx_memory = push_array(scratch.arena, U8, size); + if(!InitializeContext(ctx_memory, ctx_flags, &ctx, &size)) + { + ctx = 0; + } + } + + //- rjf: unpack features available on this context + B32 avx_available = 0; + if(ctx != 0) + { + if(avx_enabled) + { + SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX); + } + DWORD64 xstate_flags = 0; + if(GetXStateFeaturesMask(ctx, &xstate_flags)) + { + avx_available = !!(xstate_flags & XSTATE_MASK_AVX); + } + } + + //- rjf: get thread context + if(!GetThreadContext(thread, ctx)) + { + ctx = 0; + } + + //- rjf: bad context -> abort + if(ctx == 0) + { + break; + } + result = 1; + + //- rjf: convert context -> REGS_RegBlockX64 + XSAVE_FORMAT *xsave = &ctx->FltSave; + dst->rax.u64 = ctx->Rax; + dst->rcx.u64 = ctx->Rcx; + dst->rdx.u64 = ctx->Rdx; + dst->rbx.u64 = ctx->Rbx; + dst->rsp.u64 = ctx->Rsp; + dst->rbp.u64 = ctx->Rbp; + dst->rsi.u64 = ctx->Rsi; + dst->rdi.u64 = ctx->Rdi; + dst->r8.u64 = ctx->R8; + dst->r9.u64 = ctx->R9; + dst->r10.u64 = ctx->R10; + dst->r11.u64 = ctx->R11; + dst->r12.u64 = ctx->R12; + dst->r13.u64 = ctx->R13; + dst->r14.u64 = ctx->R14; + dst->r15.u64 = ctx->R15; + dst->rip.u64 = ctx->Rip; + dst->cs.u16 = ctx->SegCs; + dst->ds.u16 = ctx->SegDs; + dst->es.u16 = ctx->SegEs; + dst->fs.u16 = ctx->SegFs; + dst->gs.u16 = ctx->SegGs; + dst->ss.u16 = ctx->SegSs; + dst->dr0.u32 = ctx->Dr0; + dst->dr1.u32 = ctx->Dr1; + dst->dr2.u32 = ctx->Dr2; + dst->dr3.u32 = ctx->Dr3; + dst->dr6.u32 = ctx->Dr6; + dst->dr7.u32 = ctx->Dr7; + // NOTE(rjf): this bit is "supposed to always be 1", according to old info. + // may need to be investigated. + dst->rflags.u64 = ctx->EFlags | 0x2; + dst->fcw.u16 = xsave->ControlWord; + dst->fsw.u16 = xsave->StatusWord; + dst->ftw.u16 = dmn_w32_real_tag_word_from_xsave(xsave); + dst->fop.u16 = xsave->ErrorOpcode; + dst->fcs.u16 = xsave->ErrorSelector; + dst->fds.u16 = xsave->DataSelector; + dst->fip.u32 = xsave->ErrorOffset; + dst->fdp.u32 = xsave->DataOffset; + dst->mxcsr.u32 = xsave->MxCsr; + dst->mxcsr_mask.u32 = xsave->MxCsr_Mask; + { + M128A *float_s = xsave->FloatRegisters; + REGS_Reg80 *float_d = &dst->fpr0; + for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1) + { + MemoryCopy(float_d, float_s, sizeof(*float_d)); + } + } + if(!avx_available) + { + M128A *xmm_s = xsave->XmmRegisters; + REGS_Reg256 *xmm_d = &dst->ymm0; + for(U32 n = 0; n < 16; n += 1, xmm_s += 1, xmm_d += 1) + { + MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_s)); + } + } + if(avx_available) + { + DWORD part0_length = 0; + M128A *part0 = (M128A*)LocateXStateFeature(ctx, XSTATE_LEGACY_SSE, &part0_length); + DWORD part1_length = 0; + M128A *part1 = (M128A*)LocateXStateFeature(ctx, XSTATE_AVX, &part1_length); + Assert(part0_length == part1_length); + DWORD count = part0_length/sizeof(part0[0]); + count = ClampTop(count, 16); + REGS_Reg256 *ymm_d = &dst->ymm0; + for (DWORD i = 0; i < count; i += 1, part0 += 1, part1 += 1, ymm_d += 1) + { + // TODO(rjf): confirm ordering of writes + ymm_d->u64[3] = part0->Low; + ymm_d->u64[2] = part0->High; + ymm_d->u64[1] = part1->Low; + ymm_d->u64[0] = part1->High; + } + } + + scratch_end(scratch); + }break; + } + ProfEnd(); + return result; +} + +internal B32 +dmn_w32_thread_write_reg_block(Architecture arch, HANDLE thread, void *reg_block) +{ + B32 result = 0; + ProfBeginFunction(); + switch(arch) + { + //////////////////////////// + //- rjf: unimplemented win32/arch combos + // + case Architecture_Null: + case Architecture_COUNT: + {}break; + case Architecture_arm64: + case Architecture_arm32: + {NotImplemented;}break; + + //////////////////////////// + //- rjf: x86 + // + case Architecture_x86: + { + REGS_RegBlockX86 *src = (REGS_RegBlockX86 *)reg_block; + + //- rjf: convert REGS_RegBlockX86 -> WOW64_CONTEXT + WOW64_CONTEXT ctx = {0}; + XSAVE_FORMAT *fxsave = (XSAVE_FORMAT*)ctx.ExtendedRegisters; + ctx.ContextFlags = DMN_W32_CTX_X86_ALL; + ctx.Eax = src->eax.u32; + ctx.Ebx = src->ebx.u32; + ctx.Ecx = src->ecx.u32; + ctx.Edx = src->edx.u32; + ctx.Esi = src->esi.u32; + ctx.Edi = src->edi.u32; + ctx.Esp = src->esp.u32; + ctx.Ebp = src->ebp.u32; + ctx.Eip = src->eip.u32; + ctx.SegCs = src->cs.u16; + ctx.SegDs = src->ds.u16; + ctx.SegEs = src->es.u16; + ctx.SegFs = src->fs.u16; + ctx.SegGs = src->gs.u16; + ctx.SegSs = src->ss.u16; + ctx.Dr0 = src->dr0.u32; + ctx.Dr1 = src->dr1.u32; + ctx.Dr2 = src->dr2.u32; + ctx.Dr3 = src->dr3.u32; + ctx.Dr6 = src->dr6.u32; + ctx.Dr7 = src->dr7.u32; + ctx.EFlags = src->eflags.u32; + fxsave->ControlWord = src->fcw.u16; + fxsave->StatusWord = src->fsw.u16; + fxsave->TagWord = dmn_w32_xsave_tag_word_from_real_tag_word(src->ftw.u16); + fxsave->ErrorOpcode = src->fop.u16; + fxsave->ErrorSelector = src->fcs.u16; + fxsave->DataSelector = src->fds.u16; + fxsave->ErrorOffset = src->fip.u32; + fxsave->DataOffset = src->fdp.u32; + fxsave->MxCsr = src->mxcsr.u32 & src->mxcsr_mask.u32; + fxsave->MxCsr_Mask = src->mxcsr_mask.u32; + { + M128A *float_d = fxsave->FloatRegisters; + REGS_Reg80 *float_s = &src->fpr0; + for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1) + { + MemoryCopy(float_d, float_s, 10); + } + } + { + M128A *xmm_d = fxsave->XmmRegisters; + REGS_Reg256 *xmm_s = &src->ymm0; + for(U32 n = 0; n < 8; n += 1, xmm_d += 1, xmm_s += 1) + { + MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_d)); + } + } + + //- rjf: set thread context + B32 result = 0; + if(Wow64SetThreadContext(thread, &ctx)) + { + result = 1; + } + }break; + + //////////////////////////// + //- rjf: x64 + // + case Architecture_x64: + { + Temp scratch = scratch_begin(0, 0); + REGS_RegBlockX64 *src = (REGS_RegBlockX64 *)reg_block; + + //- rjf: unpack info about available features + U32 feature_mask = GetEnabledXStateFeatures(); + B32 avx_enabled = !!(feature_mask & XSTATE_MASK_AVX); + + //- rjf: set up context + CONTEXT *ctx = 0; + U32 ctx_flags = DMN_W32_CTX_X64_ALL; + if(avx_enabled) + { + ctx_flags |= DMN_W32_CTX_INTEL_XSTATE; + } + DWORD size = 0; + InitializeContext(0, ctx_flags, 0, &size); + if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + void *ctx_memory = push_array(scratch.arena, U8, size); + if(!InitializeContext(ctx_memory, ctx_flags, &ctx, &size)) + { + ctx = 0; + } + } + + //- rjf: unpack features available on this context + B32 avx_available = 0; + if(ctx != 0) + { + if(avx_enabled) + { + SetXStateFeaturesMask(ctx, XSTATE_MASK_AVX); + } + DWORD64 xstate_flags = 0; + if(GetXStateFeaturesMask(ctx, &xstate_flags)) + { + avx_available = !!(xstate_flags & XSTATE_MASK_AVX); + } + } + + //- rjf: get thread context + if(!GetThreadContext(thread, ctx)) + { + ctx = 0; + } + + //- rjf: bad context -> abort + if(ctx == 0) + { + DWORD error = GetLastError(); + break; + } + + //- rjf: convert REGS_RegBlockX64 -> CONTEXT + XSAVE_FORMAT *fxsave = &ctx->FltSave; + ctx->ContextFlags = ctx_flags; + ctx->MxCsr = src->mxcsr.u32 & src->mxcsr_mask.u32; + ctx->Rax = src->rax.u64; + ctx->Rcx = src->rcx.u64; + ctx->Rdx = src->rdx.u64; + ctx->Rbx = src->rbx.u64; + ctx->Rsp = src->rsp.u64; + ctx->Rbp = src->rbp.u64; + ctx->Rsi = src->rsi.u64; + ctx->Rdi = src->rdi.u64; + ctx->R8 = src->r8.u64; + ctx->R9 = src->r9.u64; + ctx->R10 = src->r10.u64; + ctx->R11 = src->r11.u64; + ctx->R12 = src->r12.u64; + ctx->R13 = src->r13.u64; + ctx->R14 = src->r14.u64; + ctx->R15 = src->r15.u64; + ctx->Rip = src->rip.u64; + ctx->SegCs = src->cs.u16; + ctx->SegDs = src->ds.u16; + ctx->SegEs = src->es.u16; + ctx->SegFs = src->fs.u16; + ctx->SegGs = src->gs.u16; + ctx->SegSs = src->ss.u16; + ctx->Dr0 = src->dr0.u32; + ctx->Dr1 = src->dr1.u32; + ctx->Dr2 = src->dr2.u32; + ctx->Dr3 = src->dr3.u32; + ctx->Dr6 = src->dr6.u32; + ctx->Dr7 = src->dr7.u32; + ctx->EFlags = src->rflags.u64; + fxsave->ControlWord = src->fcw.u16; + fxsave->StatusWord = src->fsw.u16; + fxsave->TagWord = dmn_w32_xsave_tag_word_from_real_tag_word(src->ftw.u16); + fxsave->ErrorOpcode = src->fop.u16; + fxsave->ErrorSelector = src->fcs.u16; + fxsave->DataSelector = src->fds.u16; + fxsave->ErrorOffset = src->fip.u32; + fxsave->DataOffset = src->fdp.u32; + { + M128A *float_d = fxsave->FloatRegisters; + REGS_Reg80 *float_s = &src->fpr0; + for(U32 n = 0; n < 8; n += 1, float_s += 1, float_d += 1) + { + MemoryCopy(float_d, float_s, 10); + } + } + if(!avx_available) + { + M128A *xmm_d = fxsave->XmmRegisters; + REGS_Reg256 *xmm_s = &src->ymm0; + for(U32 n = 0; n < 8; n += 1, xmm_d += 1, xmm_s += 1) + { + MemoryCopy(xmm_d, xmm_s, sizeof(*xmm_d)); + } + } + if(avx_available) + { + DWORD part0_length = 0; + M128A *part0 = (M128A*)LocateXStateFeature(ctx, XSTATE_LEGACY_SSE, &part0_length); + DWORD part1_length = 0; + M128A *part1 = (M128A*)LocateXStateFeature(ctx, XSTATE_AVX, &part1_length); + Assert(part0_length == part1_length); + DWORD count = part0_length/sizeof(part0[0]); + count = ClampTop(count, 16); + REGS_Reg256 *ymm_d = &src->ymm0; + for(DWORD i = 0; i < count; i += 1, part0 += 1, part1 += 1, ymm_d += 1) + { + // TODO(allen): Are we writing these out in the right order? Seems weird right? + part0->Low = ymm_d->u64[3]; + part0->High = ymm_d->u64[2]; + part1->Low = ymm_d->u64[1]; + part1->High = ymm_d->u64[0]; + } + } + + //- rjf: set thread context + if(SetThreadContext(thread, ctx)) + { + result = 1; + } + scratch_end(scratch); + }break; + } + ins_atomic_u64_inc_eval(&dmn_w32_shared->reg_gen); + ProfEnd(); + return result; +} + +//- rjf: remote thread injection + +internal DWORD +dmn_w32_inject_thread(HANDLE process, U64 start_address) +{ + LPTHREAD_START_ROUTINE start = (LPTHREAD_START_ROUTINE)start_address; + DWORD thread_id = 0; + HANDLE thread = CreateRemoteThread(process, 0, 0, start, 0, 0, &thread_id); + if(thread != 0) + { + CloseHandle(thread); + } + return thread_id; +} + +//////////////////////////////// +//~ rjf: @dmn_os_hooks Main Layer Initialization (Implemented Per-OS) + +internal void +dmn_init(void) +{ + Arena *arena = arena_alloc(); + dmn_w32_shared = push_array(arena, DMN_W32_Shared, 1); + dmn_w32_shared->arena = arena; + dmn_w32_shared->access_mutex = os_mutex_alloc(); + dmn_w32_shared->detach_arena = arena_alloc(); + dmn_w32_shared->entities_arena = arena_alloc(.reserve_size = GB(8), .commit_size = KB(64)); + dmn_w32_shared->entities_base = dmn_w32_entity_alloc(&dmn_w32_entity_nil, DMN_W32_EntityKind_Root, 0); + dmn_w32_shared->entities_id_hash_slots_count = 4096; + dmn_w32_shared->entities_id_hash_slots = push_array(arena, DMN_W32_EntityIDHashSlot, dmn_w32_shared->entities_id_hash_slots_count); + + // rjf: load Windows 10+ GetThreadDescription API + { + dmn_w32_GetThreadDescription = (DMN_W32_GetThreadDescriptionFunctionType *)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "GetThreadDescription"); + } + + // rjf: setup environment variables + { + WCHAR *this_proc_env = GetEnvironmentStringsW(); + U64 start_idx = 0; + for(U64 idx = 0;; idx += 1) + { + if(this_proc_env[idx] == 0) + { + if(start_idx == idx) + { + break; + } + else + { + String16 string16 = str16((U16 *)this_proc_env + start_idx, idx - start_idx); + String8 string = str8_from_16(dmn_w32_shared->arena, string16); + str8_list_push(dmn_w32_shared->arena, &dmn_w32_shared->env_strings, string); + start_idx = idx+1; + } + } + } + } +} + +//////////////////////////////// +//~ rjf: @dmn_os_hooks Blocking Control Thread Operations (Implemented Per-OS) + +internal DMN_CtrlCtx * +dmn_ctrl_begin(void) +{ + DMN_CtrlCtx *ctx = (DMN_CtrlCtx *)1; + dmn_w32_ctrl_thread = 1; + return ctx; +} + +internal void +dmn_ctrl_exclusive_access_begin(void) +{ + OS_MutexScope(dmn_w32_shared->access_mutex) + { + dmn_w32_shared->access_run_state = 1; + } +} + +internal void +dmn_ctrl_exclusive_access_end(void) +{ + OS_MutexScope(dmn_w32_shared->access_mutex) + { + dmn_w32_shared->access_run_state = 0; + } +} + +internal U32 +dmn_ctrl_launch(DMN_CtrlCtx *ctx, OS_ProcessLaunchParams *params) +{ + Temp scratch = scratch_begin(0, 0); + U32 result = 0; + DMN_AccessScope + { + //- rjf: produce exe / arguments string + String8 cmd = {0}; + if(params->cmd_line.first != 0) + { + String8List args = {0}; + String8 exe_path = params->cmd_line.first->string; + str8_list_pushf(scratch.arena, &args, "\"%S\"", exe_path); + for(String8Node *n = params->cmd_line.first->next; n != 0; n = n->next) + { + str8_list_push(scratch.arena, &args, n->string); + } + StringJoin join_params = {0}; + join_params.sep = str8_lit(" "); + cmd = str8_list_join(scratch.arena, &args, &join_params); + } + + //- rjf: produce environment strings + String8 env = {0}; + { + String8List all_opts = params->env; + if(params->inherit_env != 0) + { + MemoryZeroStruct(&all_opts); + str8_list_push(scratch.arena, &all_opts, str8_lit("_NO_DEBUG_HEAP=1")); + for(String8Node *n = params->env.first; n != 0; n = n->next) + { + str8_list_push(scratch.arena, &all_opts, n->string); + } + for(String8Node *n = dmn_w32_shared->env_strings.first; n != 0; n = n->next) + { + str8_list_push(scratch.arena, &all_opts, n->string); + } + } + StringJoin join_params2 = {0}; + join_params2.sep = str8_lit("\0"); + join_params2.post = str8_lit("\0"); + env = str8_list_join(scratch.arena, &all_opts, &join_params2); + } + + //- rjf: produce utf-16 strings + String16 cmd16 = str16_from_8(scratch.arena, cmd); + String16 dir16 = str16_from_8(scratch.arena, params->path); + String16 env16 = str16_from_8(scratch.arena, env); + + //- rjf: launch + DWORD access_flags = CREATE_UNICODE_ENVIRONMENT|DEBUG_PROCESS; + STARTUPINFOW startup_info = {sizeof(startup_info)}; + PROCESS_INFORMATION process_info = {0}; + AllocConsole(); + if(CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, 1, access_flags, (WCHAR*)env16.str, (WCHAR*)dir16.str, &startup_info, &process_info)) + { + // check if we are 32-bit app, and just close it immediately + BOOL is_wow = 0; + IsWow64Process(process_info.hProcess, &is_wow); + if(is_wow) + { + log_user_errorf("Only 64-bit applications can be debugged currently."); + DebugActiveProcessStop(process_info.dwProcessId); + TerminateProcess(process_info.hProcess,0xffffffff); + } + else + { + result = process_info.dwProcessId; + dmn_w32_shared->new_process_pending = 1; + } + CloseHandle(process_info.hProcess); + CloseHandle(process_info.hThread); + } + else + { + MessageBox(0, "Error starting process.", "Process error", MB_OK|MB_ICONSTOP); + } + FreeConsole(); + + //- rjf: eliminate all handles which have stuck around from the AllocConsole + { + SetStdHandle(STD_INPUT_HANDLE, 0); + SetStdHandle(STD_OUTPUT_HANDLE, 0); + SetStdHandle(STD_ERROR_HANDLE, 0); + } + } + scratch_end(scratch); + return result; +} + +internal B32 +dmn_ctrl_attach(DMN_CtrlCtx *ctx, U32 pid) +{ + B32 result = 0; + DMN_AccessScope if(DebugActiveProcess((DWORD)pid)) + { + result = 1; + dmn_w32_shared->new_process_pending = 1; + +#if 0 + // TODO(rjf): JIT debugging info + { + typedef struct JIT_DEBUG_INFO JIT_DEBUG_INFO; + struct JIT_DEBUG_INFO + { + DWORD dwSize; + DWORD dwProcessorArchitecture; + DWORD dwThreadID; + DWORD dwReserved0; + ULONG64 lpExceptionAddress; + ULONG64 lpExceptionRecord; + ULONG64 lpContextRecord; + }; + } +#endif + } + return result; +} + +internal B32 +dmn_ctrl_kill(DMN_CtrlCtx *ctx, DMN_Handle process, U32 exit_code) +{ + B32 result = 0; + DMN_AccessScope + { + DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); + if(TerminateProcess(process_entity->handle, exit_code)) + { + result = 1; + } + } + return result; +} + +internal B32 +dmn_ctrl_detach(DMN_CtrlCtx *ctx, DMN_Handle process) +{ + B32 result = 0; + DMN_AccessScope + { + DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); + + // rjf: resume threads + for(DMN_W32_Entity *child = process_entity->first; + child != &dmn_w32_entity_nil; + child = child->next) + { + if(child->kind == DMN_W32_EntityKind_Thread) + { + DWORD resume_result = ResumeThread(child->handle); + (void)resume_result; + } + } + + // rjf: detach + { + DWORD pid = (DWORD)process_entity->id; + if(DebugActiveProcessStop(pid)) + { + result = 1; + } + } + + // rjf: push into list of processes to generate events for later + if(result != 0) + { + dmn_handle_list_push(dmn_w32_shared->detach_arena, &dmn_w32_shared->detach_processes, process); + } + } + return result; +} + +internal DMN_EventList +dmn_ctrl_run(Arena *arena, DMN_CtrlCtx *ctx, DMN_RunCtrls *ctrls) +{ + DMN_EventList events = {0}; + dmn_access_open(); + + ////////////////////////////// + //- rjf: determine event generation path + // + typedef enum DMN_W32_EventGenPath + { + DMN_W32_EventGenPath_NotAttached, + DMN_W32_EventGenPath_Run, + DMN_W32_EventGenPath_DetachProcesses, + } + DMN_W32_EventGenPath; + DMN_W32_EventGenPath event_gen_path = DMN_W32_EventGenPath_Run; + if(dmn_w32_shared->detach_processes.first != 0) + { + event_gen_path = DMN_W32_EventGenPath_DetachProcesses; + } + else + { + B32 any_processes_live = dmn_w32_shared->new_process_pending; + if(!any_processes_live) + { + for(DMN_W32_Entity *process = dmn_w32_shared->entities_base->first; + process != &dmn_w32_entity_nil; + process = process->next) + { + if(process->kind == DMN_W32_EntityKind_Process) + { + any_processes_live = 1; + break; + } + } + } + if(!any_processes_live) + { + event_gen_path = DMN_W32_EventGenPath_NotAttached; + } + } + + ////////////////////////////// + //- rjf: produce debug events + // + switch(event_gen_path) + { + //////////////////////////// + //- rjf: produce not-attached error events + // + case DMN_W32_EventGenPath_NotAttached: + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_Error; + e->error_kind = DMN_ErrorKind_NotAttached; + }break; + + //////////////////////////// + //- rjf: produce debug events from regular running + // + case DMN_W32_EventGenPath_Run: + { + Temp scratch = scratch_begin(&arena, 1); + + ////////////////////////// + //- rjf: get single step thread's context (x64 single-step-set fast path) + // + CONTEXT *single_step_thread_ctx = 0; + if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) + { + DMN_W32_Entity *thread = dmn_w32_entity_from_handle(ctrls->single_step_thread); + Architecture arch = thread->arch; + switch(arch) + { + default:{}break; + case Architecture_x64: + { + U32 ctx_flags = DMN_W32_CTX_X64|DMN_W32_CTX_INTEL_CONTROL; + DWORD size = 0; + InitializeContext(0, ctx_flags, 0, &size); + if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + void *ctx_memory = push_array(scratch.arena, U8, size); + if(!InitializeContext(ctx_memory, ctx_flags, &single_step_thread_ctx, &size)) + { + single_step_thread_ctx = 0; + } + } + }break; + } + } + + ////////////////////////// + //- rjf: set single step bit + // + if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) ProfScope("set single step bit") + { + DMN_W32_Entity *thread = dmn_w32_entity_from_handle(ctrls->single_step_thread); + Architecture arch = thread->arch; + switch(arch) + { + //- rjf: unimplemented win32/arch combos + case Architecture_Null: + case Architecture_COUNT: + {}break; + case Architecture_arm64: + case Architecture_arm32: + {NotImplemented;}break; + + //- rjf: x86 + case Architecture_x86: + { + REGS_RegBlockX86 regs = {0}; + dmn_thread_read_reg_block(ctrls->single_step_thread, ®s); + regs.eflags.u32 |= 0x100; + dmn_thread_write_reg_block(ctrls->single_step_thread, ®s); + }break; + + //- rjf: x64 + case Architecture_x64: + { + if(!GetThreadContext(thread->handle, single_step_thread_ctx)) + { + single_step_thread_ctx = 0; + } + if(single_step_thread_ctx != 0) + { + U64 rflags = single_step_thread_ctx->EFlags|0x2; + U64 new_rflags = rflags | 0x100; + single_step_thread_ctx->EFlags = new_rflags; + SetThreadContext(thread->handle, single_step_thread_ctx); + ins_atomic_u64_inc_eval(&dmn_w32_shared->reg_gen); + } + }break; + } + } + + ////////////////////////// + //- rjf: write all traps into memory + // + U8 *trap_swap_bytes = push_array_no_zero(scratch.arena, U8, ctrls->traps.trap_count); + ProfScope("write all traps into memory") + { + U64 trap_idx = 0; + for(DMN_TrapChunkNode *n = ctrls->traps.first; n != 0; n = n->next) + { + for(U64 n_idx = 0; n_idx < n->count; n_idx += 1, trap_idx += 1) + { + DMN_Trap *trap = n->v+n_idx; + trap_swap_bytes[trap_idx] = 0xCC; + dmn_process_read(trap->process, r1u64(trap->vaddr, trap->vaddr+1), trap_swap_bytes+trap_idx); + U8 int3 = 0xCC; + dmn_process_write(trap->process, r1u64(trap->vaddr, trap->vaddr+1), &int3); + } + } + } + + ////////////////////////// + //- rjf: produce list of threads which will run + // + DMN_W32_EntityNode *first_run_thread = 0; + DMN_W32_EntityNode *last_run_thread = 0; + ProfScope("produce list of threads which will run") + { + //- rjf: scan all processes + for(DMN_W32_Entity *process = dmn_w32_shared->entities_base->first; + process != &dmn_w32_entity_nil; + process = process->next) + { + if(process->kind != DMN_W32_EntityKind_Process) {continue;} + + //- rjf: determine if this process is frozen + B32 process_is_frozen = 0; + if(ctrls->run_entities_are_processes) + { + for(U64 idx = 0; idx < ctrls->run_entity_count; idx += 1) + { + if(dmn_handle_match(ctrls->run_entities[idx], dmn_w32_handle_from_entity(process))) + { + process_is_frozen = 1; + break; + } + } + } + + //- rjf: scan all threads in this process + for(DMN_W32_Entity *thread = process->first; + thread != &dmn_w32_entity_nil; + thread = thread->next) + { + if(thread->kind != DMN_W32_EntityKind_Thread) {continue;} + + //- rjf: determine if this thread is frozen + B32 is_frozen = 0; + { + // rjf: single-step? freeze if not the single-step thread. + if(!dmn_handle_match(dmn_handle_zero(), ctrls->single_step_thread)) + { + is_frozen = !dmn_handle_match(dmn_w32_handle_from_entity(thread), ctrls->single_step_thread); + } + + // rjf: not single-stepping? determine based on run controls freezing info + else + { + if(ctrls->run_entities_are_processes) + { + is_frozen = process_is_frozen; + } + else for(U64 idx = 0; idx < ctrls->run_entity_count; idx += 1) + { + if(dmn_handle_match(ctrls->run_entities[idx], dmn_w32_handle_from_entity(thread))) + { + is_frozen = 1; + break; + } + } + if(ctrls->run_entities_are_unfrozen) + { + is_frozen ^= 1; + } + } + } + + //- rjf: disregard all other rules if this is the halter thread + if(dmn_w32_shared->halter_tid == thread->id) + { + is_frozen = 0; + } + + //- rjf: add to list + if(!is_frozen) + { + DMN_W32_EntityNode *n = push_array(scratch.arena, DMN_W32_EntityNode, 1); + n->v = thread; + SLLQueuePush(first_run_thread, last_run_thread, n); + } + } + } + } + + ////////////////////////// + //- rjf: resume threads which will run + // + ProfScope("resume threads which will run") + { + for(DMN_W32_EntityNode *n = first_run_thread; n != 0; n = n->next) + { + DMN_W32_Entity *thread = n->v; + DWORD resume_result = ResumeThread(thread->handle); + switch(resume_result) + { + case 0xffffffffu: + { + // TODO(rjf): error - unknown cause. need to do GetLastError, FormatMessage + }break; + default: + { + DWORD desired_counter = 0; + DWORD current_counter = resume_result - 1; + if(current_counter != desired_counter) + { + // NOTE(rjf): Warning. The user has manually suspended this thread, + // so even though from Demon's perspective it thinks this thread + // should run, it will not, because the user has manually called + // SuspendThread or used CREATE_SUSPENDED or whatever. + } + }break; + } + } + } + + ////////////////////////// + //- rjf: loop, consume win32 debug events until we produce the relevant demon events + // + U64 begin_time = os_now_microseconds(); + String8List debug_strings = {0}; + DMN_Event *debug_strings_event = 0; + for(B32 keep_going = 1; keep_going;) + { + keep_going = 0; + + //////////////////////// + //- rjf: choose win32 resume code + // + DWORD resume_code = DBG_CONTINUE; + { + if(dmn_w32_shared->exception_not_handled && !ctrls->ignore_previous_exception) + { + log_infof("using DBG_EXCEPTION_NOT_HANDLED\n"); + resume_code = DBG_EXCEPTION_NOT_HANDLED; + } + else + { + log_infof("using DBG_CONTINUE\n"); + } + dmn_w32_shared->exception_not_handled = 0; + } + + //////////////////////// + //- rjf: inform windows that we're resuming, run, & obtain next debug event + // + DEBUG_EVENT evt = {0}; + B32 evt_good = 0; + ProfScope("inform windows that we're resuming, run, & obtain next debug event") + { + B32 resume_good = 1; + if(dmn_w32_shared->resume_needed) + { + dmn_w32_shared->resume_needed = 0; + resume_good = !!ContinueDebugEvent(dmn_w32_shared->resume_pid, dmn_w32_shared->resume_tid, resume_code); + dmn_w32_shared->resume_needed = 0; + dmn_w32_shared->resume_tid = 0; + dmn_w32_shared->resume_pid = 0; + } + if(resume_good) + { + evt_good = !!WaitForDebugEvent(&evt, 100); + if(evt_good) + { + dmn_w32_shared->resume_needed = 1; + dmn_w32_shared->resume_pid = evt.dwProcessId; + dmn_w32_shared->resume_tid = evt.dwThreadId; + } + else + { + keep_going = 1; + } + ins_atomic_u64_inc_eval(&dmn_w32_shared->run_gen); + ins_atomic_u64_inc_eval(&dmn_w32_shared->mem_gen); + ins_atomic_u64_inc_eval(&dmn_w32_shared->reg_gen); + } + } + + //////////////////////// + //- rjf: process the new event + // + if(evt_good) ProfScope("process the new event") + { + switch(evt.dwDebugEventCode) + { + ////////////////////// + //- rjf: process was created + // + case CREATE_PROCESS_DEBUG_EVENT: + { + // rjf: zero out "process pending" state + dmn_w32_shared->new_process_pending = 0; + + // rjf: unpack event + HANDLE process_handle = evt.u.CreateProcessInfo.hProcess; + HANDLE thread_handle = evt.u.CreateProcessInfo.hThread; + HANDLE module_handle = evt.u.CreateProcessInfo.hFile; + U64 tls_base = (U64)evt.u.CreateProcessInfo.lpThreadLocalBase; + U64 module_base = (U64)evt.u.CreateProcessInfo.lpBaseOfImage; + U64 module_name_vaddr = (U64)evt.u.CreateProcessInfo.lpImageName; + B32 module_name_is_unicode = (evt.u.CreateProcessInfo.fUnicode != 0); + DMN_W32_ImageInfo image_info = dmn_w32_image_info_from_process_base_vaddr(process_handle, module_base); + + // rjf: create entities (thread/module are implied for initial - they are not reported by win32) + DMN_W32_Entity *process = dmn_w32_entity_alloc(dmn_w32_shared->entities_base, DMN_W32_EntityKind_Process, evt.dwProcessId); + DMN_W32_Entity *thread = dmn_w32_entity_alloc(process, DMN_W32_EntityKind_Thread, evt.dwThreadId); + DMN_W32_Entity *module = dmn_w32_entity_alloc(process, DMN_W32_EntityKind_Module, module_base); + { + process->handle = process_handle; + process->arch = image_info.arch; + thread->handle = thread_handle; + thread->arch = image_info.arch; + thread->thread.thread_local_base = tls_base; + module->handle = module_handle; + module->module.vaddr_range = r1u64(module_base, image_info.size); + module->module.is_main = 1; + module->module.address_of_name_pointer = module_name_vaddr; + module->module.name_is_unicode = module_name_is_unicode; + } + + // rjf: put thread into suspended state, so it matches expected initial state + SuspendThread(thread_handle); + + // rjf: set up per-process injected code (to run halter threads on & + // generate debug events) + { + U8 injection_code[DMN_W32_INJECTED_CODE_SIZE]; + MemorySet(injection_code, 0xCC, DMN_W32_INJECTED_CODE_SIZE); + injection_code[0] = 0xC3; + U64 injection_size = DMN_W32_INJECTED_CODE_SIZE + sizeof(DMN_W32_InjectedBreak); + U64 injection_address = (U64)VirtualAllocEx(process_handle, 0, injection_size, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE); + dmn_w32_process_write(process_handle, r1u64(injection_address, injection_address+sizeof(injection_code)), injection_code); + process->proc.injection_address = injection_address; + } + + // rjf: generate events + { + // rjf: create process + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_CreateProcess; + e->process = dmn_w32_handle_from_entity(process); + e->arch = image_info.arch; + e->code = evt.dwProcessId; + } + + // rjf: create thread + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_CreateThread; + e->process = dmn_w32_handle_from_entity(process); + e->thread = dmn_w32_handle_from_entity(thread); + e->arch = image_info.arch; + e->code = evt.dwThreadId; + } + + // rjf: load module + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_LoadModule; + e->process = dmn_w32_handle_from_entity(process); + e->module = dmn_w32_handle_from_entity(module); + e->arch = image_info.arch; + e->address = module_base; + e->size = image_info.size; + e->string = dmn_w32_full_path_from_module(arena, module); + } + } + }break; + + ////////////////////// + //- rjf: process exited + // + case EXIT_PROCESS_DEBUG_EVENT: + { + DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId); + + // rjf: generate events for children + for(DMN_W32_Entity *child = process->first; child != &dmn_w32_entity_nil; child = child->next) + { + switch(child->kind) + { + default:{}break; + case DMN_W32_EntityKind_Thread: + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_ExitThread; + e->process = dmn_w32_handle_from_entity(process); + e->thread = dmn_w32_handle_from_entity(child); + }break; + case DMN_W32_EntityKind_Module: + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_UnloadModule; + e->process = dmn_w32_handle_from_entity(process); + e->module = dmn_w32_handle_from_entity(child); + e->string = dmn_w32_full_path_from_module(arena, child); + }break; + } + } + + // rjf: generate event for process + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_ExitProcess; + e->process = dmn_w32_handle_from_entity(process); + e->code = evt.u.ExitProcess.dwExitCode; + } + + // rjf: release entity storage + dmn_w32_entity_release(process); + + // rjf: detach + DebugActiveProcessStop(evt.dwProcessId); + }break; + + ////////////////////// + //- rjf: thread was created + // + case CREATE_THREAD_DEBUG_EVENT: + { + DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId); + + // rjf: create thread entity + DMN_W32_Entity *thread = dmn_w32_entity_alloc(process, DMN_W32_EntityKind_Thread, evt.dwThreadId); + { + thread->handle = evt.u.CreateThread.hThread; + thread->arch = process->arch; + thread->thread.thread_local_base = (U64)evt.u.CreateThread.lpThreadLocalBase; + } + + // rjf: suspend thread immediately upon creation, to match with expected suspension state + DWORD sus_result = SuspendThread(thread->handle); + (void)sus_result; + + // rjf: unpack thread name + String8 thread_name = {0}; + if(dmn_w32_GetThreadDescription != 0) + { + WCHAR *thread_name_w = 0; + HRESULT hr = dmn_w32_GetThreadDescription(thread->handle, &thread_name_w); + if(SUCCEEDED(hr)) + { + thread_name = str8_from_16(arena, str16_cstring((U16 *)thread_name_w)); + LocalFree(thread_name_w); + } + } + + // rjf: determine if this is a "halter thread" - the threads we spawn to halt processes + B32 is_halter = (evt.dwThreadId == dmn_w32_shared->halter_tid); + + // rjf: generate events for non-halter threads + if(!is_halter) + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_CreateThread; + e->process = dmn_w32_handle_from_entity(process); + e->thread = dmn_w32_handle_from_entity(thread); + e->arch = thread->arch; + e->code = evt.dwThreadId; + e->string = thread_name; + } + }break; + + ////////////////////// + //- rjf: thread exited + // + case EXIT_THREAD_DEBUG_EVENT: + { + DMN_W32_Entity *thread = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Thread, evt.dwThreadId); + DMN_W32_Entity *process = thread->parent; + + // rjf: determine if this is the halter thread + B32 is_halter = (evt.dwThreadId == dmn_w32_shared->halter_tid); + + // rjf: generate a halt event if this thread is the halter + if(is_halter) + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_Halt; + dmn_w32_shared->halter_process = dmn_handle_zero(); + dmn_w32_shared->halter_tid = 0; + } + + // rjf: if this thread is *not* the halter, then generate a regular exit-thread event + if(!is_halter) + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_ExitThread; + e->process = dmn_w32_handle_from_entity(process); + e->thread = dmn_w32_handle_from_entity(thread); + e->code = evt.u.ExitThread.dwExitCode; + } + + // rjf: release entity storage + dmn_w32_entity_release(thread); + }break; + + ////////////////////// + //- rjf: DLL was loaded + // + case LOAD_DLL_DEBUG_EVENT: + { + DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId); + + // rjf: extract image info + U64 module_base = (U64)evt.u.LoadDll.lpBaseOfDll; + DMN_W32_ImageInfo image_info = dmn_w32_image_info_from_process_base_vaddr(process->handle, module_base); + + // rjf: create module entity + DMN_W32_Entity *module = dmn_w32_entity_alloc(process, DMN_W32_EntityKind_Module, module_base); + { + module->handle = evt.u.LoadDll.hFile; + module->arch = image_info.arch; + module->module.vaddr_range = r1u64(module_base, module_base+image_info.size); + module->module.address_of_name_pointer = (U64)evt.u.LoadDll.lpImageName; + module->module.name_is_unicode = (evt.u.LoadDll.fUnicode != 0); + } + + // rjf: generate event + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_LoadModule; + e->process = dmn_w32_handle_from_entity(process); + e->module = dmn_w32_handle_from_entity(module); + e->arch = module->arch; + e->address = module_base; + e->size = image_info.size; + e->string = dmn_w32_full_path_from_module(arena, module); + } + }break; + + ////////////////////// + //- rjf: DLL was unloaded + // + case UNLOAD_DLL_DEBUG_EVENT: + { + U64 module_base = (U64)evt.u.UnloadDll.lpBaseOfDll; + DMN_W32_Entity *module = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Module, module_base); + DMN_W32_Entity *process = module->parent; + + // rjf: generate event + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_UnloadModule; + e->process = dmn_w32_handle_from_entity(process); + e->module = dmn_w32_handle_from_entity(module); + e->string = dmn_w32_full_path_from_module(arena, module); + } + + // rjf: release entity storage + dmn_w32_entity_release(module); + }break; + + ////////////////////// + //- rjf: exception was hit + // + case EXCEPTION_DEBUG_EVENT: + { + //- NOTE(rjf): Notes on multithreaded breakpoint events + // (2021/11/1): + // + // When many threads are simultaneously running, multiple threads + // may hit a trap "at the same time". When this happens there will be + // multiple events in an internal queue that we cannot see. If there + // is another event in the queue we will not see it until we call + // ContinueDebugEvent again, in a subsequent call to demon_os_run. + // + // When we get a trap event, the instruction pointer stored + // in the event will have the address of the int 3 instruction that + // was hit. Our RIP register, however, will be one byte past that. + // So, to get the behavior we want, we need to set the RIP register + // back to the address of the int 3. + // + // To deal with the fact that we may get breakpoint events later that + // were actually from this run what we do is: + // + // #1. If we get a trap event, and it corresponds to a user submitted + // trap, then we treat it is a breakpoint event. + // #2. If we get a trap event, and it does NOT correspond to a user + // trap in this call: + // #A. If the actual unmodified instruction byte is NOT an int 3, + // then this is a queued event from a previous run that is no + // longer applicable and we skip it. + // #B. If the actual unmodified instruction is an int 3, then this + // becomes a trap event and we do not reset RIP. + + //- NOTE(rjf): Further notes on MULTITHREADED STEPPING ACCESS VIOLATION + // EVENTS! @rjf @rjf @rjf + // (2024/05/29): + // + // Just adding another comment here to document that the above long + // comment went completely unnoticed by me during a pass over demon, + // and I had removed the proper rollback stuff here without reading + // the above comment. So this comment just serves to make that + // original comment even heftier. + + //- NOTE(rjf): The exception record struct has a 32-bit version and a + // 64-bit version. We only currently handle the 64-bit version. + + //- rjf: unpack + DMN_W32_Entity *thread = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Thread, evt.dwThreadId); + DMN_W32_Entity *process = thread->parent; + EXCEPTION_DEBUG_INFO *edi = &evt.u.Exception; + EXCEPTION_RECORD *exception = &edi->ExceptionRecord; + U64 instruction_pointer = (U64)exception->ExceptionAddress; + + //- rjf: determine if this is the first breakpoint in a process + // (breakpoint notifying us that the debugger is attached) + B32 first_bp = 0; + if(!process->proc.did_first_bp && exception->ExceptionCode == DMN_W32_EXCEPTION_BREAKPOINT) + { + process->proc.did_first_bp = 1; + first_bp = 1; + } + + //- rjf: determine if this exception is a trap + B32 is_trap = (!first_bp && + (exception->ExceptionCode == DMN_W32_EXCEPTION_BREAKPOINT || + exception->ExceptionCode == DMN_W32_EXCEPTION_STACK_BUFFER_OVERRUN)); + + //- rjf: check if this trap is a usage-code-specified trap or something else + B32 hit_user_trap = 0; + if(is_trap) + { + for(DMN_TrapChunkNode *n = ctrls->traps.first; n != 0; n = n->next) + { + for(U64 idx = 0; idx < n->count; idx += 1) + { + if(dmn_handle_match(n->v[idx].process, dmn_w32_handle_from_entity(process)) && n->v[idx].vaddr == instruction_pointer) + { + hit_user_trap = 1; + break; + } + } + } + } + + //- rjf: check if trap is explicit in the actual code memory + B32 hit_explicit_trap = 0; + if(is_trap && !hit_user_trap) + { + U8 instruction_byte = 0; + if(dmn_w32_process_read_struct(process->handle, instruction_pointer, &instruction_byte)) + { + hit_explicit_trap = (instruction_byte == 0xCC || instruction_byte == 0xCD); + } + } + + //- rjf: determine whether to roll back instruction pointer + B32 should_do_rollback = (hit_user_trap || (is_trap && !hit_explicit_trap)); + + //- rjf: roll back thread's instruction pointer + if(should_do_rollback) ProfScope("roll back thread's instruction pointer") + { + switch(thread->arch) + { + //- rjf: default, general path + default: + { + Temp temp = temp_begin(scratch.arena); + U64 regs_block_size = regs_block_size_from_architecture(thread->arch); + void *regs_block = push_array(scratch.arena, U8, regs_block_size); + if(dmn_w32_thread_read_reg_block(thread->arch, thread->handle, regs_block)) + { + regs_arch_block_write_rip(thread->arch, regs_block, instruction_pointer); + dmn_w32_thread_write_reg_block(thread->arch, thread->handle, regs_block); + } + temp_end(temp); + }break; + + //- rjf: x64 (fastpath) + case Architecture_x64: + { + CONTEXT *ctx = 0; + U32 ctx_flags = DMN_W32_CTX_X64|DMN_W32_CTX_INTEL_CONTROL; + DWORD size = 0; + InitializeContext(0, ctx_flags, 0, &size); + if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + void *ctx_memory = push_array(scratch.arena, U8, size); + if(!InitializeContext(ctx_memory, ctx_flags, &ctx, &size)) + { + ctx = 0; + } + } + if(!GetThreadContext(thread->handle, ctx)) + { + ctx = 0; + } + if(ctx != 0) + { + U64 rip = ctx->Rip; + U64 new_rip = instruction_pointer; + ctx->Rip = new_rip; + SetThreadContext(thread->handle, ctx); + ins_atomic_u64_inc_eval(&dmn_w32_shared->reg_gen); + } + }break; + } + } + + //- rjf: not a user trap, not an explicit trap, then it's a trap that + // this thread hit previously but has since skipped + B32 hit_previous_trap = (is_trap && !hit_user_trap && !hit_explicit_trap); + + //- rjf: determine whether to skip this event + B32 skip_event = (hit_previous_trap); + + //- rjf: generate event + if(!skip_event) + { + // rjf: fill top-level info + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_Exception; + e->process = dmn_w32_handle_from_entity(process); + e->thread = dmn_w32_handle_from_entity(thread); + e->code = exception->ExceptionCode; + e->flags = exception->ExceptionFlags; + e->instruction_pointer = (U64)exception->ExceptionAddress; + + //- rjf: fill according to exception code + switch(exception->ExceptionCode) + { + //- rjf: fill breakpoint event info + case DMN_W32_EXCEPTION_BREAKPOINT: + { + DMN_EventKind report_event_kind = DMN_EventKind_Trap; + if(first_bp) + { + report_event_kind = DMN_EventKind_HandshakeComplete; + } + else if(hit_user_trap) + { + report_event_kind = DMN_EventKind_Breakpoint; + } + e->kind = report_event_kind; + }break; + + //- rjf: fill stack buffer overrun event info + case DMN_W32_EXCEPTION_STACK_BUFFER_OVERRUN: + { + e->kind = DMN_EventKind_Trap; + }break; + + //- rjf: fill single-step event info + case DMN_W32_EXCEPTION_SINGLE_STEP: + { + e->kind = DMN_EventKind_SingleStep; + }break; + + //- rjf: fill throw info + case DMN_W32_EXCEPTION_THROW: + { + U64 exception_sp = 0; + U64 exception_ip = 0; + if(exception->NumberParameters >= 3) + { + exception_sp = (U64)exception->ExceptionInformation[1]; + exception_ip = (U64)exception->ExceptionInformation[2]; + } + e->stack_pointer = exception_sp; + e->exception_kind = DMN_ExceptionKind_CppThrow; + e->exception_repeated = (edi->dwFirstChance == 0); + dmn_w32_shared->exception_not_handled = (edi->dwFirstChance != 0); + }break; + + //- rjf: fill access violation info + case DMN_W32_EXCEPTION_ACCESS_VIOLATION: + case DMN_W32_EXCEPTION_IN_PAGE_ERROR: + { + U64 exception_address = 0; + DMN_ExceptionKind exception_kind = DMN_ExceptionKind_Null; + if(exception->NumberParameters >= 2) + { + switch(exception->ExceptionInformation[0]) + { + case 0: exception_kind = DMN_ExceptionKind_MemoryRead; break; + case 1: exception_kind = DMN_ExceptionKind_MemoryWrite; break; + case 8: exception_kind = DMN_ExceptionKind_MemoryExecute; break; + } + exception_address = exception->ExceptionInformation[1]; + } + e->address = exception_address; + e->exception_kind = exception_kind; + e->exception_repeated = (edi->dwFirstChance == 0); + dmn_w32_shared->exception_not_handled = (edi->dwFirstChance != 0); + }break; + + //- rjf: fill set-thread-name info + case DMN_W32_EXCEPTION_SET_THREAD_NAME: + if(exception->NumberParameters >= 2) + { + U64 thread_name_address = exception->ExceptionInformation[1]; + DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId); + String8List thread_name_strings = {0}; + { + U64 read_addr = thread_name_address; + U64 total_string_size = 0; + for(;total_string_size < KB(4);) + { + U8 *buffer = push_array(scratch.arena, U8, 256); + B32 good_read = dmn_w32_process_read(process->handle, r1u64(read_addr, read_addr+256), buffer); + if(good_read) + { + U64 size = 256; + for(U64 idx = 0; idx < 256; idx += 1) + { + if(buffer[idx] == 0) + { + size = idx; + break; + } + } + String8 string_part = str8(buffer, size); + str8_list_push(scratch.arena, &thread_name_strings, string_part); + total_string_size += size; + read_addr += size; + if(size < 256) + { + break; + } + } + else + { + break; + } + } + } + e->kind = DMN_EventKind_SetThreadName; + e->string = str8_list_join(arena, &thread_name_strings, 0); + if(exception->NumberParameters > 2) + { + e->code = exception->ExceptionInformation[2]; + } + }break; + + //- rjf: unhandled exception case + default: + { + e->exception_repeated = (edi->dwFirstChance == 0); + dmn_w32_shared->exception_not_handled = (edi->dwFirstChance != 0); + }break; + } + } + }break; + + ////////////////////// + //- rjf: output debug string was gathered + // + case OUTPUT_DEBUG_STRING_EVENT: + { + // rjf: unpack event + DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId); + DMN_W32_Entity *thread = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Thread, evt.dwThreadId); + U64 string_address = (U64)evt.u.DebugString.lpDebugStringData; + U64 string_size = (U64)evt.u.DebugString.nDebugStringLength; + + // rjf: read memory + U8 *buffer = push_array_no_zero(scratch.arena, U8, string_size + 1); + dmn_w32_process_read(process->handle, r1u64(string_address, string_address+string_size), buffer); + buffer[string_size] = 0; + + // rjf: extract into string + String8 debug_string = str8(buffer, string_size); + if(debug_string.size != 0 && buffer[string_size-1] == 0) + { + debug_string.size -= 1; + } + + // rjf: make debug string event + debug_strings_event = dmn_event_list_push(arena, &events); + debug_strings_event->kind = DMN_EventKind_DebugString; + + // rjf: push into debug strings + str8_list_push(scratch.arena, &debug_strings, debug_string); + keep_going = 1; + + // rjf: exit loop, given sufficient amount of text + if(debug_strings.total_size >= KB(4)) + { + keep_going = 0; + } + }break; + + ////////////////////// + //- rjf: a "rip event" - a "system debugging error". + // + case RIP_EVENT: + { + DMN_W32_Entity *process = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Process, evt.dwProcessId); + DMN_W32_Entity *thread = dmn_w32_entity_from_kind_id(DMN_W32_EntityKind_Thread, evt.dwThreadId); + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_Exception; + e->process = dmn_w32_handle_from_entity(process); + e->thread = dmn_w32_handle_from_entity(thread); + }break; + + ////////////////////// + //- rjf: default case - some kind of debugging event that we don't currently consume. + // + default: + { + NoOp; + }break; + } + } + + //////////////////////// + //- rjf: exit loop after a little while, so we keep pumping e.g. debug strings + // + if(os_now_microseconds() >= begin_time+100000) + { + keep_going = 0; + } + } + + //////////////////////// + //- rjf: send out event for any remaining debug strings + // + if(debug_strings.total_size != 0 && debug_strings_event != 0) + { + String8 debug_strings_joined = str8_list_join(arena, &debug_strings, 0); + debug_strings_event->string = debug_strings_joined; + } + + //////////////////////// + //- rjf: suspend threads which ran + // + ProfScope("suspend threads which ran") + { + for(DMN_W32_EntityNode *n = first_run_thread; n != 0; n = n->next) + { + DMN_W32_Entity *thread = n->v; + DWORD suspend_result = SuspendThread(thread->handle); + switch(suspend_result) + { + case 0xffffffffu: + { + // TODO(rjf): error - unknown cause. need to do do GetLastError, FormatMessage + // + // NOTE(rjf): this can happen when the event is EXIT_THREAD_DEBUG_EVENT + // or EXIT_PROCESS_DEBUG_EVENT. after such an event, SuspendThread + // gives error code 5 (access denied). this has no adverse effects, but + // if we want to start reporting errors we should take care to avoid + // calling SuspendThread in that case. + }break; + default: + { + DWORD desired_counter = 1; + DWORD current_counter = suspend_result + 1; + if(current_counter != desired_counter) + { + // NOTE(rjf): Warning. We've suspended to something higher than 1. + // In this case, it means the user probably created the thread in + // a suspended state, or they called SuspendThread. + } + }break; + } + } + } + + //- rjf: gather new thread-names + ProfScope("gather new thread names") if(dmn_w32_GetThreadDescription != 0) + { + for(DMN_W32_Entity *process = dmn_w32_shared->entities_base->first; + process != &dmn_w32_entity_nil; + process = process->next) + { + if(process->kind != DMN_W32_EntityKind_Process) { continue; } + for(DMN_W32_Entity *thread = process->first; + thread != &dmn_w32_entity_nil; + thread = thread->next) + { + if(thread->kind != DMN_W32_EntityKind_Thread) { continue; } + if(thread->thread.last_name_hash == 0 || + thread->thread.name_gather_time_us+1000000 <= os_now_microseconds()) + { + String8 name = {0}; + { + WCHAR *thread_name_w = 0; + HRESULT hr = dmn_w32_GetThreadDescription(thread->handle, &thread_name_w); + if(SUCCEEDED(hr)) + { + name = str8_from_16(scratch.arena, str16_cstring((U16 *)thread_name_w)); + LocalFree(thread_name_w); + } + } + U64 name_hash = dmn_w32_hash_from_string(name); + if(name.size != 0 && name_hash != thread->thread.last_name_hash) + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_SetThreadName; + e->process = dmn_w32_handle_from_entity(process); + e->thread = dmn_w32_handle_from_entity(thread); + e->string = push_str8_copy(arena, name); + } + thread->thread.name_gather_time_us = os_now_microseconds(); + thread->thread.last_name_hash = name_hash; + } + } + } + } + + ////////////////////////// + //- rjf: restore original memory at trap locations + // + ProfScope("restore original memory at trap locations") + { + U64 trap_idx = 0; + for(DMN_TrapChunkNode *n = ctrls->traps.first; n != 0; n = n->next) + { + for(U64 n_idx = 0; n_idx < n->count; n_idx += 1, trap_idx += 1) + { + DMN_Trap *trap = n->v+n_idx; + U8 og_byte = trap_swap_bytes[trap_idx]; + if(og_byte != 0xCC) + { + dmn_process_write(trap->process, r1u64(trap->vaddr, trap->vaddr+1), &og_byte); + } + } + } + } + + ////////////////////////// + //- rjf: unset single step bit + // + if(!dmn_handle_match(ctrls->single_step_thread, dmn_handle_zero())) ProfScope("unset single step bit") + { + DMN_W32_Entity *thread = dmn_w32_entity_from_handle(ctrls->single_step_thread); + Architecture arch = thread->arch; + switch(arch) + { + //- rjf: unimplemented win32/arch combos + case Architecture_Null: + case Architecture_COUNT: + {}break; + case Architecture_arm64: + case Architecture_arm32: + {NotImplemented;}break; + + //- rjf: x86/64 + case Architecture_x86: + { + REGS_RegBlockX86 regs = {0}; + dmn_thread_read_reg_block(ctrls->single_step_thread, ®s); + regs.eflags.u32 &= ~0x100; + dmn_thread_write_reg_block(ctrls->single_step_thread, ®s); + }break; + case Architecture_x64: + { + if(!GetThreadContext(thread->handle, single_step_thread_ctx)) + { + single_step_thread_ctx = 0; + } + if(ctx != 0) + { + U64 rflags = single_step_thread_ctx->EFlags|0x2; + U64 new_rflags = rflags & ~0x100; + single_step_thread_ctx->EFlags = new_rflags; + SetThreadContext(thread->handle, single_step_thread_ctx); + ins_atomic_u64_inc_eval(&dmn_w32_shared->reg_gen); + } + }break; + } + } + + scratch_end(scratch); + }break; + + //////////////////////////// + //- rjf: produce debug events from queued up detached processes + // + case DMN_W32_EventGenPath_DetachProcesses: + { + for(DMN_HandleNode *n = dmn_w32_shared->detach_processes.first; n != 0; n = n->next) + { + DMN_W32_Entity *process = dmn_w32_entity_from_handle(n->v); + + // rjf: push exit thread events + for(DMN_W32_Entity *child = process->first; child != &dmn_w32_entity_nil; child = child->next) + { + if(child->kind == DMN_W32_EntityKind_Thread) + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_ExitThread; + e->process = dmn_w32_handle_from_entity(process); + e->thread = dmn_w32_handle_from_entity(child); + } + } + + // rjf: push unload module events + for(DMN_W32_Entity *child = process->first; child != &dmn_w32_entity_nil; child = child->next) + { + if(child->kind == DMN_W32_EntityKind_Module) + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_UnloadModule; + e->process = dmn_w32_handle_from_entity(process); + e->module = dmn_w32_handle_from_entity(child); + e->string = dmn_w32_full_path_from_module(arena, child); + } + } + + // rjf: push exit process event + { + DMN_Event *e = dmn_event_list_push(arena, &events); + e->kind = DMN_EventKind_ExitProcess; + e->process = dmn_w32_handle_from_entity(process); + } + + // rjf: free process + dmn_w32_entity_release(process); + } + + // rjf: reset queued up detached processes + MemoryZeroStruct(&dmn_w32_shared->detach_processes); + arena_clear(dmn_w32_shared->detach_arena); + }break; + } + + dmn_access_close(); + return events; +} + +//////////////////////////////// +//~ rjf: @dmn_os_hooks Halting (Implemented Per-OS) + +internal void +dmn_halt(U64 code, U64 user_data) +{ + if(dmn_handle_match(dmn_handle_zero(), dmn_w32_shared->halter_process)) + { + DMN_W32_Entity *process = &dmn_w32_entity_nil; + for(DMN_W32_Entity *entity = dmn_w32_shared->entities_base->first; + entity != &dmn_w32_entity_nil; + entity = entity->next) + { + if(entity->kind == DMN_W32_EntityKind_Process) + { + process = entity; + break; + } + } + if(process != &dmn_w32_entity_nil) + { + dmn_w32_shared->halter_process = dmn_w32_handle_from_entity(process); + DMN_W32_InjectedBreak injection = {code, user_data}; + U64 data_injection_address = process->proc.injection_address + DMN_W32_INJECTED_CODE_SIZE; + dmn_w32_process_write_struct(process->handle, data_injection_address, &injection); + dmn_w32_shared->halter_tid = dmn_w32_inject_thread(process->handle, process->proc.injection_address); + } + } +} + +//////////////////////////////// +//~ rjf: @dmn_os_hooks Introspection Functions (Implemented Per-OS) + +//- rjf: run/memory/register counters + +internal U64 +dmn_run_gen(void) +{ + U64 result = ins_atomic_u64_eval(&dmn_w32_shared->run_gen); + return result; +} + +internal U64 +dmn_mem_gen(void) +{ + U64 result = ins_atomic_u64_eval(&dmn_w32_shared->mem_gen); + return result; +} + +internal U64 +dmn_reg_gen(void) +{ + U64 result = ins_atomic_u64_eval(&dmn_w32_shared->reg_gen); + return result; +} + +//- rjf: non-blocking-control-thread access barriers + +internal B32 +dmn_access_open(void) +{ + B32 result = 0; + if(dmn_w32_ctrl_thread) + { + result = 1; + } + else + { + os_mutex_take(dmn_w32_shared->access_mutex); + result = !dmn_w32_shared->access_run_state; + } + return result; +} + +internal void +dmn_access_close(void) +{ + if(!dmn_w32_ctrl_thread) + { + os_mutex_drop(dmn_w32_shared->access_mutex); + } +} + +//- rjf: processes + +internal U64 +dmn_process_memory_reserve(DMN_Handle process, U64 vaddr, U64 size) +{ + U64 result = 0; + DMN_AccessScope + { + DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); + result = (U64)VirtualAllocEx(process_entity->handle, (void *)vaddr, size, MEM_RESERVE, PAGE_READWRITE); + if(result == 0) + { + result = (U64)VirtualAllocEx(process_entity->handle, 0, size, MEM_RESERVE, PAGE_READWRITE); + } + } + return result; +} + +internal void +dmn_process_memory_commit(DMN_Handle process, U64 vaddr, U64 size) +{ + DMN_AccessScope + { + DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); + (U64)VirtualAllocEx(process_entity->handle, (void *)vaddr, size, MEM_COMMIT, PAGE_READWRITE); + } +} + +internal void +dmn_process_memory_decommit(DMN_Handle process, U64 vaddr, U64 size) +{ + DMN_AccessScope + { + DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); + VirtualFreeEx(process_entity->handle, (void *)vaddr, size, MEM_DECOMMIT); + } +} + +internal void +dmn_process_memory_release(DMN_Handle process, U64 vaddr, U64 size) +{ + DMN_AccessScope + { + DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); + VirtualFreeEx(process_entity->handle, (void *)vaddr, 0, MEM_RELEASE); + } +} + +internal void +dmn_process_memory_protect(DMN_Handle process, U64 vaddr, U64 size, OS_AccessFlags flags) +{ + DMN_AccessScope + { + DMN_W32_Entity *process_entity = dmn_w32_entity_from_handle(process); + DWORD old_flags = 0; + DWORD new_flags = PAGE_NOACCESS; + switch(flags) + { + default:{}break; + case OS_AccessFlag_Execute:{new_flags = PAGE_EXECUTE;}break; + case OS_AccessFlag_Execute|OS_AccessFlag_Read:{new_flags = PAGE_EXECUTE_READ;}break; + case OS_AccessFlag_Execute|OS_AccessFlag_Read|OS_AccessFlag_Write:{new_flags = PAGE_EXECUTE_READWRITE;}break; + case OS_AccessFlag_Read:{new_flags = PAGE_READONLY;}break; + case OS_AccessFlag_Read|OS_AccessFlag_Write:{new_flags = PAGE_READWRITE;}break; + } + VirtualProtectEx(process_entity->handle, (void *)vaddr, size, new_flags, &old_flags); + } +} + +internal U64 +dmn_process_read(DMN_Handle process, Rng1U64 range, void *dst) +{ + U64 result = 0; + DMN_AccessScope + { + DMN_W32_Entity *entity = dmn_w32_entity_from_handle(process); + result = dmn_w32_process_read(entity->handle, range, dst); + } + return result; +} + +internal B32 +dmn_process_write(DMN_Handle process, Rng1U64 range, void *src) +{ + B32 result = 0; + DMN_AccessScope + { + DMN_W32_Entity *entity = dmn_w32_entity_from_handle(process); + result = dmn_w32_process_write(entity->handle, range, src); + } + return result; +} + +//- rjf: threads + +internal Architecture +dmn_arch_from_thread(DMN_Handle handle) +{ + Architecture arch = Architecture_Null; + DMN_AccessScope + { + DMN_W32_Entity *entity = dmn_w32_entity_from_handle(handle); + arch = entity->arch; + } + return arch; +} + +internal U64 +dmn_stack_base_vaddr_from_thread(DMN_Handle handle) +{ + U64 result = 0; + DMN_AccessScope + { + DMN_W32_Entity *thread = dmn_w32_entity_from_handle(handle); + if(thread->kind == DMN_W32_EntityKind_Thread) + { + DMN_W32_Entity *process = thread->parent; + U64 tlb = thread->thread.thread_local_base; + switch(thread->arch) + { + case Architecture_Null: + case Architecture_COUNT: + {}break; + case Architecture_arm64: + case Architecture_arm32: + {NotImplemented;}break; + case Architecture_x64: + { + U64 stack_base_addr = tlb + 0x8; + dmn_w32_process_read(process->handle, r1u64(stack_base_addr, stack_base_addr+8), &result); + }break; + case Architecture_x86: + { + U64 stack_base_addr = tlb + 0x4; + dmn_w32_process_read(process->handle, r1u64(stack_base_addr, stack_base_addr+4), &result); + }break; + } + } + } + return result; +} + +internal U64 +dmn_tls_root_vaddr_from_thread(DMN_Handle handle) +{ + U64 result = 0; + DMN_AccessScope + { + DMN_W32_Entity *entity = dmn_w32_entity_from_handle(handle); + if(entity->kind == DMN_W32_EntityKind_Thread) + { + result = entity->thread.thread_local_base; + switch(entity->arch) + { + case Architecture_Null: + case Architecture_COUNT: + {}break; + case Architecture_arm64: + case Architecture_arm32: + {NotImplemented;}break; + case Architecture_x64: + { + result += 88; + }break; + case Architecture_x86: + { + result += 44; + }break; + } + } + } + return result; +} + +internal B32 +dmn_thread_read_reg_block(DMN_Handle handle, void *reg_block) +{ + B32 result = 0; + DMN_AccessScope + { + DMN_W32_Entity *thread = dmn_w32_entity_from_handle(handle); + result = dmn_w32_thread_read_reg_block(thread->arch, thread->handle, reg_block); + } + return result; +} + +internal B32 +dmn_thread_write_reg_block(DMN_Handle handle, void *reg_block) +{ + B32 result = 0; + DMN_AccessScope + { + DMN_W32_Entity *thread = dmn_w32_entity_from_handle(handle); + result = dmn_w32_thread_write_reg_block(thread->arch, thread->handle, reg_block); + } + return result; +} + +//- rjf: system process listing + +internal void +dmn_process_iter_begin(DMN_ProcessIter *iter) +{ + MemoryZeroStruct(iter); + iter->v[0] = (U64)CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); +} + +internal B32 +dmn_process_iter_next(Arena *arena, DMN_ProcessIter *iter, DMN_ProcessInfo *info_out) +{ + B32 result = 0; + + //- rjf: get the next process entry + PROCESSENTRY32W process_entry = {sizeof(process_entry)}; + HANDLE snapshot = (HANDLE)iter->v[0]; + if(iter->v[1] == 0) + { + if(Process32FirstW(snapshot, &process_entry)) + { + result = 1; + } + } + else + { + if(Process32NextW(snapshot, &process_entry)) + { + result = 1; + } + } + + //- rjf: increment counter + iter->v[1] += 1; + + //- rjf: convert to process info + if(result) + { + info_out->name = str8_from_16(arena, str16_cstring((U16*)process_entry.szExeFile)); + info_out->pid = (U32)process_entry.th32ProcessID; + } + + return result; +} + +internal void +dmn_process_iter_end(DMN_ProcessIter *iter) +{ + CloseHandle((HANDLE)iter->v[0]); + MemoryZeroStruct(iter); +} diff --git a/src/df/core/df_core.c b/src/df/core/df_core.c index 53e88c8d..4d6076e6 100644 --- a/src/df/core/df_core.c +++ b/src/df/core/df_core.c @@ -1,9357 +1,9312 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#undef RADDBG_LAYER_COLOR -#define RADDBG_LAYER_COLOR 0.70f, 0.50f, 0.25f - -//////////////////////////////// -//~ rjf: Generated Code - -#include "df/core/generated/df_core.meta.c" - -//////////////////////////////// -//~ rjf: Basic Helpers - -internal U64 -df_hash_from_seed_string(U64 seed, String8 string) -{ - U64 result = seed; - for(U64 i = 0; i < string.size; i += 1) - { - result = ((result << 5) + result) + string.str[i]; - } - return result; -} - -internal U64 -df_hash_from_string(String8 string) -{ - return df_hash_from_seed_string(5381, string); -} - -internal U64 -df_hash_from_seed_string__case_insensitive(U64 seed, String8 string) -{ - U64 result = seed; - for(U64 i = 0; i < string.size; i += 1) - { - result = ((result << 5) + result) + char_to_lower(string.str[i]); - } - return result; -} - -internal U64 -df_hash_from_string__case_insensitive(String8 string) -{ - return df_hash_from_seed_string__case_insensitive(5381, string); -} - -//////////////////////////////// -//~ rjf: Handles - -internal DF_Handle -df_handle_zero(void) -{ - DF_Handle result = {0}; - return result; -} - -internal B32 -df_handle_match(DF_Handle a, DF_Handle b) -{ - return (a.u64[0] == b.u64[0] && a.u64[1] == b.u64[1]); -} - -internal void -df_handle_list_push_node(DF_HandleList *list, DF_HandleNode *node) -{ - DLLPushBack(list->first, list->last, node); - list->count += 1; -} - -internal void -df_handle_list_push(Arena *arena, DF_HandleList *list, DF_Handle handle) -{ - DF_HandleNode *n = push_array(arena, DF_HandleNode, 1); - n->handle = handle; - df_handle_list_push_node(list, n); -} - -internal void -df_handle_list_remove(DF_HandleList *list, DF_HandleNode *node) -{ - DLLRemove(list->first, list->last, node); - list->count -= 1; -} - -internal DF_HandleNode * -df_handle_list_find(DF_HandleList *list, DF_Handle handle) -{ - DF_HandleNode *result = 0; - for(DF_HandleNode *n = list->first; n != 0; n = n->next) - { - if(df_handle_match(n->handle, handle)) - { - result = n; - break; - } - } - return result; -} - -internal DF_HandleList -df_push_handle_list_copy(Arena *arena, DF_HandleList list) -{ - DF_HandleList result = {0}; - for(DF_HandleNode *n = list.first; n != 0; n = n->next) - { - df_handle_list_push(arena, &result, n->handle); - } - return result; -} - -//////////////////////////////// -//~ rjf: State History Data Structure - -internal DF_StateDeltaHistory * -df_state_delta_history_alloc(void) -{ - Arena *arena = arena_alloc(); - DF_StateDeltaHistory *hist = push_array(arena, DF_StateDeltaHistory, 1); - hist->arena = arena; - for(Side side = (Side)0; side < Side_COUNT; side = (Side)(side+1)) - { - hist->side_arenas[side] = arena_alloc(); - } - return hist; -} - -internal void -df_state_delta_history_release(DF_StateDeltaHistory *hist) -{ - for(Side side = (Side)0; side < Side_COUNT; side = (Side)(side+1)) - { - arena_release(hist->side_arenas[side]); - } - arena_release(hist->arena); -} - -internal void -df_state_delta_history_push_batch(DF_StateDeltaHistory *hist, U64 *optional_gen_ptr) -{ - if(hist == 0) { return; } - if(hist->side_arenas[Side_Max] != 0) - { - arena_clear(hist->side_arenas[Side_Max]); - hist->side_tops[Side_Max] = 0; - } - DF_StateDeltaBatch *batch = push_array(hist->side_arenas[Side_Min], DF_StateDeltaBatch, 1); - SLLStackPush(hist->side_tops[Side_Min], batch); - if(optional_gen_ptr != 0) - { - batch->gen = *optional_gen_ptr; - batch->gen_vaddr = (U64)optional_gen_ptr; - } -} - -internal void -df_state_delta_history_push_delta(DF_StateDeltaHistory *hist, void *ptr, U64 size) -{ - if(hist == 0) { return; } - DF_StateDeltaBatch *batch = hist->side_tops[Side_Min]; - if(batch == 0) - { - df_state_delta_history_push_batch(hist, 0); - batch = hist->side_tops[Side_Min]; - } - DF_StateDeltaNode *n = push_array(hist->side_arenas[Side_Min], DF_StateDeltaNode, 1); - SLLQueuePush(batch->first, batch->last, n); - n->v.vaddr = (U64)ptr; - n->v.data = push_str8_copy(hist->arena, str8((U8*)ptr, size)); -} - -internal void -df_state_delta_history_wind(DF_StateDeltaHistory *hist, Side side) -{ - if(hist == 0) { return; } - DF_StateDeltaBatch *src_batch = hist->side_tops[side]; - if(src_batch != 0) - { - B32 src_batch_gen_good = (src_batch->gen_vaddr == 0 || src_batch->gen == *(U64 *)(src_batch->gen_vaddr)); - U64 pop_pos = (U64)hist->side_tops[side] - (U64)hist->side_arenas[side]; - SLLStackPop(hist->side_tops[side]); - if(src_batch_gen_good) - { - DF_StateDeltaBatch *dst_batch = push_array(hist->side_arenas[side_flip(side)], DF_StateDeltaBatch, 1); - SLLStackPush(hist->side_tops[side_flip(side)], dst_batch); - for(DF_StateDeltaNode *src_n = src_batch->first; src_n != 0; src_n = src_n->next) - { - DF_StateDelta *src_delta = &src_n->v; - DF_StateDeltaNode *dst_n = push_array(hist->side_arenas[side_flip(side)], DF_StateDeltaNode, 1); - SLLQueuePush(dst_batch->first, dst_batch->last, dst_n); - dst_n->v.vaddr = src_delta->vaddr; - dst_n->v.data = push_str8_copy(hist->side_arenas[side_flip(side)], str8((U8 *)src_delta->vaddr, src_delta->data.size)); - MemoryCopy((void *)src_delta->vaddr, src_delta->data.str, src_delta->data.size); - } - } - arena_pop_to(hist->side_arenas[side], pop_pos); - } -} - -//////////////////////////////// -//~ rjf: Sparse Tree Expansion State Data Structure - -//- rjf: keys - -internal DF_ExpandKey -df_expand_key_make(U64 parent_hash, U64 child_num) -{ - DF_ExpandKey key; - { - key.parent_hash = parent_hash; - key.child_num = child_num; - } - return key; -} - -internal DF_ExpandKey -df_expand_key_zero(void) -{ - DF_ExpandKey key = {0}; - return key; -} - -internal B32 -df_expand_key_match(DF_ExpandKey a, DF_ExpandKey b) -{ - return MemoryMatchStruct(&a, &b); -} - -internal U64 -df_hash_from_expand_key(DF_ExpandKey key) -{ - U64 data[] = - { - key.child_num, - }; - U64 hash = df_hash_from_seed_string(key.parent_hash, str8((U8 *)data, sizeof(data))); - return hash; -} - -//- rjf: table - -internal void -df_expand_tree_table_init(Arena *arena, DF_ExpandTreeTable *table, U64 slot_count) -{ - MemoryZeroStruct(table); - table->slots_count = slot_count; - table->slots = push_array(arena, DF_ExpandSlot, table->slots_count); -} - -internal DF_ExpandNode * -df_expand_node_from_key(DF_ExpandTreeTable *table, DF_ExpandKey key) -{ - U64 hash = df_hash_from_expand_key(key); - U64 slot_idx = hash%table->slots_count; - DF_ExpandSlot *slot = &table->slots[slot_idx]; - DF_ExpandNode *node = 0; - for(DF_ExpandNode *n = slot->first; n != 0; n = n->hash_next) - { - if(df_expand_key_match(n->key, key)) - { - node = n; - break; - } - } - return node; -} - -internal B32 -df_expand_key_is_set(DF_ExpandTreeTable *table, DF_ExpandKey key) -{ - DF_ExpandNode *node = df_expand_node_from_key(table, key); - return (node != 0 && node->expanded); -} - -internal void -df_expand_set_expansion(Arena *arena, DF_ExpandTreeTable *table, DF_ExpandKey parent_key, DF_ExpandKey key, B32 expanded) -{ - // rjf: map keys => nodes - DF_ExpandNode *parent_node = df_expand_node_from_key(table, parent_key); - DF_ExpandNode *node = df_expand_node_from_key(table, key); - - // rjf: make node if we don't have one, and we need one - if(node == 0 && expanded) - { - node = table->free_node; - if(node != 0) - { - table->free_node = table->free_node->next; - MemoryZeroStruct(node); - } - else - { - node = push_array(arena, DF_ExpandNode, 1); - } - - // rjf: link into table - U64 hash = df_hash_from_expand_key(key); - U64 slot = hash % table->slots_count; - DLLPushBack_NP(table->slots[slot].first, table->slots[slot].last, node, hash_next, hash_prev); - - // rjf: link into parent - if(parent_node != 0) - { - DF_ExpandNode *prev = 0; - for(DF_ExpandNode *n = parent_node->first; n != 0; n = n->next) - { - if(n->key.child_num < key.child_num) - { - prev = n; - } - else - { - break; - } - } - DLLInsert_NP(parent_node->first, parent_node->last, prev, node, next, prev); - node->parent = parent_node; - } - } - - // rjf: fill - if(node != 0) - { - node->key = key; - node->expanded = expanded; - } - - // rjf: unlink node & free if we don't need it anymore - if(expanded == 0 && node != 0 && node->first == 0) - { - // rjf: unlink from table - U64 hash = df_hash_from_expand_key(key); - U64 slot = hash % table->slots_count; - DLLRemove_NP(table->slots[slot].first, table->slots[slot].last, node, hash_next, hash_prev); - - // rjf: unlink from tree - if(parent_node != 0) - { - DLLRemove_NP(parent_node->first, parent_node->last, node, next, prev); - } - - // rjf: free - node->next = table->free_node; - table->free_node = node; - } -} - -//////////////////////////////// -//~ rjf: Config Type Functions - -internal DF_CfgNode * -df_cfg_tree_copy(Arena *arena, DF_CfgNode *src_root) -{ - DF_CfgNode *dst_root = &df_g_nil_cfg_node; - DF_CfgNode *dst_parent = dst_root; - { - DF_CfgNodeRec rec = {0}; - for(DF_CfgNode *src = src_root; src != &df_g_nil_cfg_node; src = rec.next) - { - DF_CfgNode *dst = push_array(arena, DF_CfgNode, 1); - dst->first = dst->last = dst->parent = dst->next = &df_g_nil_cfg_node; - dst->flags = src->flags; - dst->string = push_str8_copy(arena, src->string); - dst->source = src->source; - dst->parent = dst_parent; - if(dst_parent != &df_g_nil_cfg_node) - { - SLLQueuePush_NZ(&df_g_nil_cfg_node, dst_parent->first, dst_parent->last, dst, next); - } - else - { - dst_root = dst_parent = dst; - } - rec = df_cfg_node_rec__depth_first_pre(src, src_root); - if(rec.push_count != 0) - { - dst_parent = dst; - } - else for(U64 idx = 0; idx < rec.pop_count; idx += 1) - { - dst_parent = dst_parent->parent; - } - } - } - return dst_root; -} - -internal DF_CfgNodeRec -df_cfg_node_rec__depth_first_pre(DF_CfgNode *node, DF_CfgNode *root) -{ - DF_CfgNodeRec rec = {0}; - rec.next = &df_g_nil_cfg_node; - if(node->first != &df_g_nil_cfg_node) - { - rec.next = node->first; - rec.push_count = 1; - } - else for(DF_CfgNode *p = node; p != &df_g_nil_cfg_node && p != root; p = p->parent, rec.pop_count += 1) - { - if(p->next != &df_g_nil_cfg_node) - { - rec.next = p->next; - break; - } - } - return rec; -} - -internal void -df_cfg_table_push_unparsed_string(Arena *arena, DF_CfgTable *table, String8 string, DF_CfgSrc source) -{ - Temp scratch = scratch_begin(&arena, 1); - if(table->slot_count == 0) - { - table->slot_count = 64; - table->slots = push_array(arena, DF_CfgSlot, table->slot_count); - } - MD_TokenizeResult tokenize = md_tokenize_from_text(scratch.arena, string); - MD_ParseResult parse = md_parse_from_text_tokens(scratch.arena, str8_lit(""), string, tokenize.tokens); - MD_Node *md_root = parse.root; - for(MD_EachNode(tln, md_root->first)) if(tln->string.size != 0) - { - // rjf: map string -> hash*slot - String8 string = str8(tln->string.str, tln->string.size); - U64 hash = df_hash_from_string__case_insensitive(string); - U64 slot_idx = hash % table->slot_count; - DF_CfgSlot *slot = &table->slots[slot_idx]; - - // rjf: find existing value for this string - DF_CfgVal *val = 0; - for(DF_CfgVal *v = slot->first; v != 0; v = v->hash_next) - { - if(str8_match(v->string, string, StringMatchFlag_CaseInsensitive)) - { - val = v; - break; - } - } - - // rjf: create new value if needed - if(val == 0) - { - val = push_array(arena, DF_CfgVal, 1); - val->string = push_str8_copy(arena, string); - val->insertion_stamp = table->insertion_stamp_counter; - SLLStackPush_N(slot->first, val, hash_next); - SLLQueuePush_N(table->first_val, table->last_val, val, linear_next); - table->insertion_stamp_counter += 1; - } - - // rjf: deep copy tree into streamlined config structure - DF_CfgNode *dst_root = &df_g_nil_cfg_node; - { - DF_CfgNode *dst_parent = &df_g_nil_cfg_node; - for(MD_Node *src = tln, *src_next = 0; !md_node_is_nil(src); src = src_next) - { - src_next = 0; - - // rjf: copy - DF_CfgNode *dst = push_array(arena, DF_CfgNode, 1); - dst->first = dst->last = dst->parent = dst->next = &df_g_nil_cfg_node; - if(dst_parent == &df_g_nil_cfg_node) - { - dst_root = dst; - } - else - { - SLLQueuePush_NZ(&df_g_nil_cfg_node, dst_parent->first, dst_parent->last, dst, next); - dst->parent = dst_parent; - } - { - dst->flags |= !!(src->flags & MD_NodeFlag_Identifier) * DF_CfgNodeFlag_Identifier; - dst->flags |= !!(src->flags & MD_NodeFlag_Numeric) * DF_CfgNodeFlag_Numeric; - dst->flags |= !!(src->flags & MD_NodeFlag_StringLiteral) * DF_CfgNodeFlag_StringLiteral; - dst->string = push_str8_copy(arena, str8(src->string.str, src->string.size)); - dst->source = source; - } - - // rjf: grab next - if(!md_node_is_nil(src->first)) - { - src_next = src->first; - dst_parent = dst; - } - else for(MD_Node *p = src; !md_node_is_nil(p) && p != tln; p = p->parent, dst_parent = dst_parent->parent) - { - if(!md_node_is_nil(p->next)) - { - src_next = p->next; - break; - } - } - } - } - - // rjf: push tree into value - SLLQueuePush_NZ(&df_g_nil_cfg_node, val->first, val->last, dst_root, next); - } - scratch_end(scratch); -} - -internal DF_CfgTable -df_cfg_table_from_inheritance(Arena *arena, DF_CfgTable *src) -{ - DF_CfgTable dst_ = {0}; - DF_CfgTable *dst = &dst_; - { - dst->slot_count = src->slot_count; - dst->slots = push_array(arena, DF_CfgSlot, dst->slot_count); - } - for(DF_CfgVal *src_val = src->first_val; src_val != 0 && src_val != &df_g_nil_cfg_val; src_val = src_val->linear_next) - { - DF_CoreViewRuleSpec *spec = df_core_view_rule_spec_from_string(src_val->string); - if(spec->info.flags & DF_CoreViewRuleSpecInfoFlag_Inherited) - { - U64 hash = df_hash_from_string(spec->info.string); - U64 dst_slot_idx = hash%dst->slot_count; - DF_CfgSlot *dst_slot = &dst->slots[dst_slot_idx]; - DF_CfgVal *dst_val = push_array(arena, DF_CfgVal, 1); - dst_val->first = src_val->first; - dst_val->last = src_val->last; - dst_val->string = src_val->string; - dst_val->insertion_stamp = dst->insertion_stamp_counter; - SLLStackPush_N(dst_slot->first, dst_val, hash_next); - dst->insertion_stamp_counter += 1; - } - } - return dst_; -} - -internal DF_CfgTable -df_cfg_table_copy(Arena *arena, DF_CfgTable *src) -{ - DF_CfgTable result = {0}; - result.slot_count = src->slot_count; - result.slots = push_array(arena, DF_CfgSlot, result.slot_count); - MemoryCopy(result.slots, src->slots, sizeof(DF_CfgSlot)*result.slot_count); - return result; -} - -internal DF_CfgVal * -df_cfg_val_from_string(DF_CfgTable *table, String8 string) -{ - DF_CfgVal *result = &df_g_nil_cfg_val; - if(table->slot_count != 0) - { - U64 hash = df_hash_from_string__case_insensitive(string); - U64 slot_idx = hash % table->slot_count; - DF_CfgSlot *slot = &table->slots[slot_idx]; - for(DF_CfgVal *val = slot->first; val != 0; val = val->hash_next) - { - if(str8_match(val->string, string, StringMatchFlag_CaseInsensitive)) - { - result = val; - break; - } - } - } - return result; -} - -internal DF_CfgNode * -df_cfg_node_child_from_string(DF_CfgNode *node, String8 string, StringMatchFlags flags) -{ - DF_CfgNode *result = &df_g_nil_cfg_node; - for(DF_CfgNode *child = node->first; child != &df_g_nil_cfg_node; child = child->next) - { - if(str8_match(child->string, string, flags)) - { - result = child; - break; - } - } - return result; -} - -internal DF_CfgNode * -df_first_cfg_node_child_from_flags(DF_CfgNode *node, DF_CfgNodeFlags flags) -{ - DF_CfgNode *result = &df_g_nil_cfg_node; - for(DF_CfgNode *child = node->first; child != &df_g_nil_cfg_node; child = child->next) - { - if(child->flags & flags) - { - result = child; - break; - } - } - return result; -} - -internal String8 -df_string_from_cfg_node_children(Arena *arena, DF_CfgNode *node) -{ - Temp scratch = scratch_begin(&arena, 1); - String8List strs = {0}; - for(DF_CfgNode *child = node->first; child != &df_g_nil_cfg_node; child = child->next) - { - str8_list_push(scratch.arena, &strs, child->string); - } - String8 result = str8_list_join(arena, &strs, 0); - scratch_end(scratch); - return result; -} - -internal Vec4F32 -df_hsva_from_cfg_node(DF_CfgNode *node) -{ - Vec4F32 result = {0}; - DF_CfgNode *hsva = df_cfg_node_child_from_string(node, str8_lit("hsva"), StringMatchFlag_CaseInsensitive); - DF_CfgNode *rgba = df_cfg_node_child_from_string(node, str8_lit("rgba"), StringMatchFlag_CaseInsensitive); - DF_CfgNode *hsv = df_cfg_node_child_from_string(node, str8_lit("hsv"), StringMatchFlag_CaseInsensitive); - DF_CfgNode *rgb = df_cfg_node_child_from_string(node, str8_lit("rgb"), StringMatchFlag_CaseInsensitive); - if(hsva != &df_g_nil_cfg_node) - { - DF_CfgNode *hue = hsva->first; - DF_CfgNode *sat = hue->next; - DF_CfgNode *val = sat->next; - DF_CfgNode *alp = val->next; - F32 hue_f32 = (F32)f64_from_str8(hue->string); - F32 sat_f32 = (F32)f64_from_str8(sat->string); - F32 val_f32 = (F32)f64_from_str8(val->string); - F32 alp_f32 = (F32)f64_from_str8(alp->string); - result = v4f32(hue_f32, sat_f32, val_f32, alp_f32); - } - else if(hsv != &df_g_nil_cfg_node) - { - DF_CfgNode *hue = hsva->first; - DF_CfgNode *sat = hue->next; - DF_CfgNode *val = sat->next; - F32 hue_f32 = (F32)f64_from_str8(hue->string); - F32 sat_f32 = (F32)f64_from_str8(sat->string); - F32 val_f32 = (F32)f64_from_str8(val->string); - result = v4f32(hue_f32, sat_f32, val_f32, 1.f); - } - else if(rgba != &df_g_nil_cfg_node) - { - DF_CfgNode *red = rgba->first; - DF_CfgNode *grn = red->next; - DF_CfgNode *blu = grn->next; - DF_CfgNode *alp = blu->next; - F32 red_f32 = (F32)f64_from_str8(red->string); - F32 grn_f32 = (F32)f64_from_str8(grn->string); - F32 blu_f32 = (F32)f64_from_str8(blu->string); - F32 alp_f32 = (F32)f64_from_str8(alp->string); - Vec3F32 hsv = hsv_from_rgb(v3f32(red_f32, grn_f32, blu_f32)); - result = v4f32(hsv.x, hsv.y, hsv.z, alp_f32); - } - else if(rgb != &df_g_nil_cfg_node) - { - DF_CfgNode *red = rgba->first; - DF_CfgNode *grn = red->next; - DF_CfgNode *blu = grn->next; - F32 red_f32 = (F32)f64_from_str8(red->string); - F32 grn_f32 = (F32)f64_from_str8(grn->string); - F32 blu_f32 = (F32)f64_from_str8(blu->string); - Vec3F32 hsv = hsv_from_rgb(v3f32(red_f32, grn_f32, blu_f32)); - result = v4f32(hsv.x, hsv.y, hsv.z, 1.f); - } - return result; -} - -internal String8 -df_string_from_cfg_node_key(DF_CfgNode *node, String8 key, StringMatchFlags flags) -{ - DF_CfgNode *child = df_cfg_node_child_from_string(node, key, flags); - return child->first->string; -} - -//////////////////////////////// -//~ rjf: Disassembling - -#include "third_party/udis86/config.h" -#include "third_party/udis86/udis86.h" -#include "third_party/udis86/libudis86/syn.h" - -internal DF_Inst -df_single_inst_from_machine_code__x64(Arena *arena, U64 start_voff, String8 string) -{ - Architecture arch = Architecture_x64; - - //- rjf: prep ud state - struct ud ud_ctx_; - struct ud *ud_ctx = &ud_ctx_; - ud_init(ud_ctx); - ud_set_mode(ud_ctx, bit_size_from_arch(arch)); - ud_set_pc(ud_ctx, start_voff); - ud_set_input_buffer(ud_ctx, string.str, string.size); - ud_set_vendor(ud_ctx, UD_VENDOR_ANY); - ud_set_syntax(ud_ctx, UD_SYN_INTEL); - - //- rjf: disassembly + get info - U32 bytes_disassembled = ud_disassemble(ud_ctx); - struct ud_operand *first_op = (struct ud_operand *)ud_insn_opr(ud_ctx, 0); - U64 rel_voff = (first_op != 0 && first_op->type == UD_OP_JIMM) ? ud_syn_rel_target(ud_ctx, first_op) : 0; - DF_InstFlags flags = 0; - enum ud_mnemonic_code code = ud_insn_mnemonic(ud_ctx); - switch(code) - { - case UD_Icall: - { - flags |= DF_InstFlag_Call; - }break; - - /* TODO(wonchun) - case UD_Iiretd: - case UD_Iiretw: - */ - - case UD_Ija: - case UD_Ijae: - case UD_Ijb: - case UD_Ijbe: - case UD_Ijcxz: - case UD_Ijecxz: - case UD_Ijg: - case UD_Ijge: - case UD_Ijl: - case UD_Ijle: - { - flags |= DF_InstFlag_Branch; - }break; - - case UD_Ijmp: - { - flags |= DF_InstFlag_UnconditionalJump; - }break; - - case UD_Ijno: - case UD_Ijnp: - case UD_Ijns: - case UD_Ijnz: - case UD_Ijo: - case UD_Ijp: - case UD_Ijrcxz: - case UD_Ijs: - case UD_Ijz: - case UD_Iloop: - case UD_Iloope: - case UD_Iloopne: - { - flags |= DF_InstFlag_Branch; - }break; - - case UD_Iret: - case UD_Iretf: - { - flags |= DF_InstFlag_Return; - }break; - - /* TODO(wonchun) - case UD_Isyscall: - case UD_Isysenter: - case UD_Isysexit: - case UD_Isysret: - case UD_Ivmcall: - case UD_Ivmmcall: - */ - default: - { - flags |= DF_InstFlag_NonFlow; - }break; - } - - //- rjf: check for stack pointer modifications - S64 sp_delta = 0; - { - struct ud_operand *dst_op = (struct ud_operand *)ud_insn_opr(ud_ctx, 0); - struct ud_operand *src_op = (struct ud_operand *)ud_insn_opr(ud_ctx, 1); - - // rjf: direct additions/subtractions to RSP - if(dst_op && src_op && dst_op->base == UD_R_RSP && dst_op->type == UD_OP_REG) - { - flags |= DF_InstFlag_ChangesStackPointer; - // TODO(rjf): does the library report constant changes to the stack pointer - // as UD_OP_CONST too? what does UD_OP_JIMM refer to? - if(src_op->base == UD_NONE && src_op->type == UD_OP_IMM && code == UD_Isub) - { - S64 sign = -1; - sp_delta = sign * src_op->lval.sqword; - } - else if(src_op->base == UD_NONE && src_op->type == UD_OP_IMM && code == UD_Iadd) - { - S64 sign = +1; - sp_delta = sign * src_op->lval.sqword; - } - else - { - flags |= DF_InstFlag_ChangesStackPointerVariably; - } - } - - // rjf: push/pop - if(code == UD_Ipush) - { - flags |= DF_InstFlag_ChangesStackPointer; - sp_delta = -8; - } - else if(code == UD_Ipop) - { - flags |= DF_InstFlag_ChangesStackPointer; - sp_delta = +8; - } - - // rjf: mark extra flags - if(ud_ctx->pfx_rep != 0 || - ud_ctx->pfx_repe != 0 || - ud_ctx->pfx_repne != 0) - { - flags |= DF_InstFlag_Repeats; - } - } - - //- rjf: fill+return - DF_Inst inst = {0}; - inst.size = bytes_disassembled; - inst.string = push_str8_copy(arena, str8_cstring((char *)ud_insn_asm(ud_ctx))); - inst.rel_voff = rel_voff; - inst.sp_delta = sp_delta; - inst.flags = flags; - return inst; -} - -internal DF_Inst -df_single_inst_from_machine_code(Arena *arena, Architecture arch, U64 start_voff, String8 string) -{ - DF_Inst result = {0}; - switch(arch) - { - default:{}break; - case Architecture_x64: - { - result = df_single_inst_from_machine_code__x64(arena, start_voff, string); - }break; - } - return result; -} - -//////////////////////////////// -//~ rjf: Debug Info Extraction Type Pure Functions - -internal DF_LineList -df_line_list_copy(Arena *arena, DF_LineList *list) -{ - DF_LineList dst = {0}; - for(DF_LineNode *src_n = list->first; src_n != 0; src_n = src_n->next) - { - DF_LineNode *dst_n = push_array(arena, DF_LineNode, 1); - MemoryCopyStruct(dst_n, src_n); - dst_n->v.dbgi_key = di_key_copy(arena, &src_n->v.dbgi_key); - SLLQueuePush(dst.first, dst.last, dst_n); - dst.count += 1; - } - return dst; -} - -//////////////////////////////// -//~ rjf: Control Flow Analysis Functions - -internal DF_CtrlFlowInfo -df_ctrl_flow_info_from_vaddr_code__x64(Arena *arena, DF_InstFlags exit_points_mask, U64 vaddr, String8 code) -{ - Temp scratch = scratch_begin(&arena, 1); - DF_CtrlFlowInfo info = {0}; - for(U64 offset = 0; offset < code.size;) - { - DF_Inst inst = df_single_inst_from_machine_code__x64(scratch.arena, 0, str8_skip(code, offset)); - U64 inst_vaddr = vaddr+offset; - info.cumulative_sp_delta += inst.sp_delta; - offset += inst.size; - info.total_size += inst.size; - if(inst.flags & exit_points_mask) - { - DF_CtrlFlowPoint point = {0}; - point.inst_flags = inst.flags; - point.vaddr = inst_vaddr; - point.jump_dest_vaddr = 0; - point.expected_sp_delta = info.cumulative_sp_delta; - if(inst.rel_voff != 0) - { - point.jump_dest_vaddr = (U64)(point.vaddr + (S64)((S32)inst.rel_voff)); - } - DF_CtrlFlowPointNode *node = push_array(arena, DF_CtrlFlowPointNode, 1); - node->v = point; - SLLQueuePush(info.exit_points.first, info.exit_points.last, node); - info.exit_points.count += 1; - } - } - scratch_end(scratch); - return info; -} - -internal DF_CtrlFlowInfo -df_ctrl_flow_info_from_arch_vaddr_code(Arena *arena, DF_InstFlags exit_points_mask, Architecture arch, U64 vaddr, String8 code) -{ - DF_CtrlFlowInfo result = {0}; - switch(arch) - { - default:{}break; - case Architecture_x64: - { - result = df_ctrl_flow_info_from_vaddr_code__x64(arena, exit_points_mask, vaddr, code); - }break; - } - return result; -} - -//////////////////////////////// -//~ rjf: Command Type Pure Functions - -//- rjf: specs - -internal B32 -df_cmd_spec_is_nil(DF_CmdSpec *spec) -{ - return (spec == 0 || spec == &df_g_nil_cmd_spec); -} - -internal void -df_cmd_spec_list_push(Arena *arena, DF_CmdSpecList *list, DF_CmdSpec *spec) -{ - DF_CmdSpecNode *n = push_array(arena, DF_CmdSpecNode, 1); - n->spec = spec; - SLLQueuePush(list->first, list->last, n); - list->count += 1; -} - -internal DF_CmdSpecArray -df_cmd_spec_array_from_list(Arena *arena, DF_CmdSpecList list) -{ - DF_CmdSpecArray result = {0}; - result.count = list.count; - result.v = push_array(arena, DF_CmdSpec *, list.count); - U64 idx = 0; - for(DF_CmdSpecNode *n = list.first; n != 0; n = n->next, idx += 1) - { - result.v[idx] = n->spec; - } - return result; -} - -internal int -df_qsort_compare_cmd_spec__run_counter(DF_CmdSpec **a, DF_CmdSpec **b) -{ - int result = 0; - if(a[0]->run_count > b[0]->run_count) - { - result = -1; - } - else if(a[0]->run_count < b[0]->run_count) - { - result = +1; - } - return result; -} - -internal void -df_cmd_spec_array_sort_by_run_counter__in_place(DF_CmdSpecArray array) -{ - quick_sort(array.v, array.count, sizeof(DF_CmdSpec *), df_qsort_compare_cmd_spec__run_counter); -} - -internal DF_Handle -df_handle_from_cmd_spec(DF_CmdSpec *spec) -{ - DF_Handle handle = {0}; - handle.u64[0] = (U64)spec; - return handle; -} - -internal DF_CmdSpec * -df_cmd_spec_from_handle(DF_Handle handle) -{ - DF_CmdSpec *result = (DF_CmdSpec *)handle.u64[0]; - if(result == 0) - { - result = &df_g_nil_cmd_spec; - } - return result; -} - -//- rjf: string -> command parsing - -internal String8 -df_cmd_name_part_from_string(String8 string) -{ - String8 result = string; - for(U64 idx = 0; idx <= string.size; idx += 1) - { - if(idx == string.size || char_is_space(string.str[idx])) - { - result = str8_prefix(string, idx); - break; - } - } - return result; -} - -internal String8 -df_cmd_arg_part_from_string(String8 string) -{ - String8 result = str8_lit(""); - B32 found_space = 0; - for(U64 idx = 0; idx <= string.size; idx += 1) - { - if(found_space && (idx == string.size || !char_is_space(string.str[idx]))) - { - result = str8_skip(string, idx); - break; - } - else if(!found_space && (idx == string.size || char_is_space(string.str[idx]))) - { - found_space = 1; - } - } - return result; -} - -//- rjf: command parameter bundles - -internal DF_CmdParams -df_cmd_params_zero(void) -{ - DF_CmdParams p = {0}; - return p; -} - -internal void -df_cmd_params_mark_slot(DF_CmdParams *params, DF_CmdParamSlot slot) -{ - params->slot_props[slot/64] |= (1ull<<(slot%64)); -} - -internal B32 -df_cmd_params_has_slot(DF_CmdParams *params, DF_CmdParamSlot slot) -{ - return !!(params->slot_props[slot/64] & (1ull<<(slot%64))); -} - -internal String8 -df_cmd_params_apply_spec_query(Arena *arena, DF_CtrlCtx *ctrl_ctx, DF_CmdParams *params, DF_CmdSpec *spec, String8 query) -{ - String8 error = {0}; - B32 prefer_imm = 0; - switch(spec->info.query.slot) - { - default: - case DF_CmdParamSlot_String: - { - params->string = push_str8_copy(arena, query); - df_cmd_params_mark_slot(params, DF_CmdParamSlot_String); - }break; - case DF_CmdParamSlot_FilePath: - { - String8TxtPtPair pair = str8_txt_pt_pair_from_string(query); - params->file_path = push_str8_copy(arena, pair.string); - params->text_point = pair.pt; - df_cmd_params_mark_slot(params, DF_CmdParamSlot_FilePath); - df_cmd_params_mark_slot(params, DF_CmdParamSlot_TextPoint); - }break; - case DF_CmdParamSlot_TextPoint: - { - U64 v = 0; - if(try_u64_from_str8_c_rules(query, &v)) - { - params->text_point.column = 1; - params->text_point.line = v; - df_cmd_params_mark_slot(params, DF_CmdParamSlot_TextPoint); - } - else - { - error = str8_lit("Couldn't interpret as a line number."); - } - }break; - case DF_CmdParamSlot_VirtualAddr: prefer_imm = 0; goto use_numeric_eval; - case DF_CmdParamSlot_VirtualOff: prefer_imm = 0; goto use_numeric_eval; - case DF_CmdParamSlot_Index: prefer_imm = 1; goto use_numeric_eval; - case DF_CmdParamSlot_ID: prefer_imm = 1; goto use_numeric_eval; - use_numeric_eval: - { - Temp scratch = scratch_begin(&arena, 1); - DI_Scope *scope = di_scope_open(); - DF_Entity *thread = df_entity_from_handle(ctrl_ctx->thread); - U64 vaddr = df_query_cached_rip_from_thread_unwind(thread, ctrl_ctx->unwind_count); - DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); - EVAL_ParseCtx parse_ctx = df_eval_parse_ctx_from_process_vaddr(scope, process, vaddr); - DF_Eval eval = df_eval_from_string(scratch.arena, scope, ctrl_ctx, &parse_ctx, &eval_string2expr_map_nil, query); - if(eval.errors.count == 0) - { - TG_Kind eval_type_kind = tg_kind_from_key(tg_unwrapped_from_graph_rdi_key(parse_ctx.type_graph, parse_ctx.rdi, eval.type_key)); - if(eval_type_kind == TG_Kind_Ptr || eval_type_kind == TG_Kind_LRef || eval_type_kind == TG_Kind_RRef) - { - eval = df_value_mode_eval_from_eval(parse_ctx.type_graph, parse_ctx.rdi, ctrl_ctx, eval); - prefer_imm = 1; - } - U64 u64 = !prefer_imm && eval.offset ? eval.offset : eval.imm_u64; - switch(spec->info.query.slot) - { - default:{}break; - case DF_CmdParamSlot_VirtualAddr: - { - params->vaddr = u64; - df_cmd_params_mark_slot(params, DF_CmdParamSlot_VirtualAddr); - }break; - case DF_CmdParamSlot_VirtualOff: - { - params->voff = u64; - df_cmd_params_mark_slot(params, DF_CmdParamSlot_VirtualOff); - }break; - case DF_CmdParamSlot_Index: - { - params->index = u64; - df_cmd_params_mark_slot(params, DF_CmdParamSlot_Index); - }break; - case DF_CmdParamSlot_BaseUnwindIndex: - { - params->base_unwind_index = u64; - df_cmd_params_mark_slot(params, DF_CmdParamSlot_BaseUnwindIndex); - }break; - case DF_CmdParamSlot_InlineUnwindIndex: - { - params->inline_unwind_index = u64; - df_cmd_params_mark_slot(params, DF_CmdParamSlot_InlineUnwindIndex); - }break; - case DF_CmdParamSlot_ID: - { - params->id = u64; - df_cmd_params_mark_slot(params, DF_CmdParamSlot_ID); - }break; - } - } - else - { - error = push_str8f(scratch.arena, "Couldn't evaluate \"%S\" as an address", query); - } - di_scope_close(scope); - scratch_end(scratch); - }break; - } - return error; -} - -//- rjf: command lists - -internal void -df_cmd_list_push(Arena *arena, DF_CmdList *cmds, DF_CmdParams *params, DF_CmdSpec *spec) -{ - DF_CmdNode *n = push_array(arena, DF_CmdNode, 1); - n->cmd.spec = spec; - n->cmd.params = df_cmd_params_copy(arena, params); - DLLPushBack(cmds->first, cmds->last, n); - cmds->count += 1; -} - -//- rjf: string -> core layer command kind - -internal DF_CoreCmdKind -df_core_cmd_kind_from_string(String8 string) -{ - DF_CoreCmdKind result = DF_CoreCmdKind_Null; - for(U64 idx = 0; idx < ArrayCount(df_g_core_cmd_kind_spec_info_table); idx += 1) - { - if(str8_match(string, df_g_core_cmd_kind_spec_info_table[idx].string, StringMatchFlag_CaseInsensitive)) - { - result = (DF_CoreCmdKind)idx; - break; - } - } - return result; -} - -//////////////////////////////// -//~ rjf: Entity Functions - -//- rjf: nil - -internal B32 -df_entity_is_nil(DF_Entity *entity) -{ - return (entity == 0 || entity == &df_g_nil_entity); -} - -//- rjf: handle <-> entity conversions - -internal U64 -df_index_from_entity(DF_Entity *entity) -{ - return (U64)(entity - df_state->entities_base); -} - -internal DF_Handle -df_handle_from_entity(DF_Entity *entity) -{ - DF_Handle handle = df_handle_zero(); - if(!df_entity_is_nil(entity)) - { - handle.u64[0] = df_index_from_entity(entity); - handle.u64[1] = entity->generation; - } - return handle; -} - -internal DF_Entity * -df_entity_from_handle(DF_Handle handle) -{ - DF_Entity *result = df_state->entities_base + handle.u64[0]; - if(handle.u64[0] >= df_state->entities_count || result->generation != handle.u64[1]) - { - result = &df_g_nil_entity; - } - return result; -} - -internal DF_EntityList -df_entity_list_from_handle_list(Arena *arena, DF_HandleList handles) -{ - DF_EntityList result = {0}; - for(DF_HandleNode *n = handles.first; n != 0; n = n->next) - { - DF_Entity *entity = df_entity_from_handle(n->handle); - if(!df_entity_is_nil(entity)) - { - df_entity_list_push(arena, &result, entity); - } - } - return result; -} - -internal DF_HandleList -df_handle_list_from_entity_list(Arena *arena, DF_EntityList entities) -{ - DF_HandleList result = {0}; - for(DF_EntityNode *n = entities.first; n != 0; n = n->next) - { - DF_Handle handle = df_handle_from_entity(n->entity); - df_handle_list_push(arena, &result, handle); - } - return result; -} - -//- rjf: entity recursion iterators - -internal DF_EntityRec -df_entity_rec_df(DF_Entity *entity, DF_Entity *subtree_root, U64 sib_off, U64 child_off) -{ - DF_EntityRec result = {0}; - if(!df_entity_is_nil(*MemberFromOffset(DF_Entity **, entity, child_off))) - { - result.next = *MemberFromOffset(DF_Entity **, entity, child_off); - result.push_count = 1; - } - else for(DF_Entity *parent = entity; parent != subtree_root && !df_entity_is_nil(parent); parent = parent->parent) - { - if(!df_entity_is_nil(*MemberFromOffset(DF_Entity **, parent, sib_off))) - { - result.next = *MemberFromOffset(DF_Entity **, parent, sib_off); - break; - } - result.pop_count += 1; - } - return result; -} - -//- rjf: ancestor/child introspection - -internal DF_Entity * -df_entity_child_from_kind(DF_Entity *entity, DF_EntityKind kind) -{ - DF_Entity *result = &df_g_nil_entity; - for(DF_Entity *child = entity->first; !df_entity_is_nil(child); child = child->next) - { - if(!child->deleted && child->kind == kind) - { - result = child; - break; - } - } - return result; -} - -internal DF_Entity * -df_entity_ancestor_from_kind(DF_Entity *entity, DF_EntityKind kind) -{ - DF_Entity *result = &df_g_nil_entity; - for(DF_Entity *p = entity->parent; !df_entity_is_nil(p); p = p->parent) - { - if(p->kind == kind) - { - result = p; - break; - } - } - return result; -} - -internal DF_EntityList -df_push_entity_child_list_with_kind(Arena *arena, DF_Entity *entity, DF_EntityKind kind) -{ - DF_EntityList result = {0}; - for(DF_Entity *child = entity->first; !df_entity_is_nil(child); child = child->next) - { - if(!child->deleted && child->kind == kind) - { - df_entity_list_push(arena, &result, child); - } - } - return result; -} - -internal DF_Entity * -df_entity_child_from_name_and_kind(DF_Entity *parent, String8 string, DF_EntityKind kind) -{ - DF_Entity *result = &df_g_nil_entity; - for(DF_Entity *child = parent->first; !df_entity_is_nil(child); child = child->next) - { - if(!child->deleted && str8_match(child->name, string, 0) && child->kind == kind) - { - result = child; - break; - } - } - return result; -} - -//- rjf: entity list building - -internal void -df_entity_list_push(Arena *arena, DF_EntityList *list, DF_Entity *entity) -{ - DF_EntityNode *n = push_array(arena, DF_EntityNode, 1); - n->entity = entity; - SLLQueuePush(list->first, list->last, n); - list->count += 1; -} - -internal DF_EntityArray -df_entity_array_from_list(Arena *arena, DF_EntityList *list) -{ - DF_EntityArray result = {0}; - result.count = list->count; - result.v = push_array(arena, DF_Entity *, result.count); - U64 idx = 0; - for(DF_EntityNode *n = list->first; n != 0; n = n->next, idx += 1) - { - result.v[idx] = n->entity; - } - return result; -} - -//- rjf: entity fuzzy list building - -internal DF_EntityFuzzyItemArray -df_entity_fuzzy_item_array_from_entity_list_needle(Arena *arena, DF_EntityList *list, String8 needle) -{ - Temp scratch = scratch_begin(&arena, 1); - DF_EntityArray array = df_entity_array_from_list(scratch.arena, list); - DF_EntityFuzzyItemArray result = df_entity_fuzzy_item_array_from_entity_array_needle(arena, &array, needle); - return result; -} - -internal DF_EntityFuzzyItemArray -df_entity_fuzzy_item_array_from_entity_array_needle(Arena *arena, DF_EntityArray *array, String8 needle) -{ - Temp scratch = scratch_begin(&arena, 1); - DF_EntityFuzzyItemArray result = {0}; - result.count = array->count; - result.v = push_array(arena, DF_EntityFuzzyItem, result.count); - U64 result_idx = 0; - for(U64 src_idx = 0; src_idx < array->count; src_idx += 1) - { - DF_Entity *entity = array->v[src_idx]; - String8 display_string = df_display_string_from_entity(scratch.arena, entity); - FuzzyMatchRangeList matches = fuzzy_match_find(arena, needle, display_string); - if(matches.count >= matches.needle_part_count) - { - result.v[result_idx].entity = entity; - result.v[result_idx].matches = matches; - result_idx += 1; - } - else - { - String8 search_tags = df_search_tags_from_entity(scratch.arena, entity); - if(search_tags.size != 0) - { - FuzzyMatchRangeList tag_matches = fuzzy_match_find(scratch.arena, needle, search_tags); - if(tag_matches.count >= tag_matches.needle_part_count) - { - result.v[result_idx].entity = entity; - result.v[result_idx].matches = matches; - result_idx += 1; - } - } - } - } - result.count = result_idx; - scratch_end(scratch); - return result; -} - -//- rjf: full path building, from file/folder entities - -internal String8 -df_full_path_from_entity(Arena *arena, DF_Entity *entity) -{ - String8 string = {0}; - { - Temp scratch = scratch_begin(&arena, 1); - String8List strs = {0}; - for(DF_Entity *e = entity; !df_entity_is_nil(e); e = e->parent) - { - if(e->kind == DF_EntityKind_File || - e->kind == DF_EntityKind_OverrideFileLink) - { - str8_list_push_front(scratch.arena, &strs, e->name); - } - } - StringJoin join = {0}; - join.sep = str8_lit("/"); - string = str8_list_join(arena, &strs, &join); - scratch_end(scratch); - } - return string; -} - -//- rjf: display string entities, for referencing entities in ui - -internal String8 -df_display_string_from_entity(Arena *arena, DF_Entity *entity) -{ - String8 result = {0}; - switch(entity->kind) - { - default: - { - if(entity->name.size != 0) - { - result = push_str8_copy(arena, entity->name); - } - else - { - String8 kind_string = df_g_entity_kind_display_string_table[entity->kind]; - result = push_str8f(arena, "%S $%I64u", kind_string, entity->id); - } - }break; - - case DF_EntityKind_Target: - { - if(entity->name.size != 0) - { - result = push_str8_copy(arena, entity->name); - } - else - { - DF_Entity *exe = df_entity_child_from_kind(entity, DF_EntityKind_Executable); - result = push_str8_copy(arena, exe->name); - } - }break; - - case DF_EntityKind_Breakpoint: - { - if(entity->name.size != 0) - { - result = push_str8_copy(arena, entity->name); - } - else if(entity->flags & DF_EntityFlag_HasVAddr) - { - result = str8_from_u64(arena, entity->vaddr, 16, 16, 0); - } - else - { - DF_Entity *symb = df_entity_child_from_kind(entity, DF_EntityKind_EntryPointName); - DF_Entity *file = df_entity_ancestor_from_kind(entity, DF_EntityKind_File); - if(!df_entity_is_nil(symb)) - { - result = push_str8_copy(arena, symb->name); - } - else if(!df_entity_is_nil(file) && entity->flags & DF_EntityFlag_HasTextPoint) - { - result = push_str8f(arena, "%S:%I64d:%I64d", file->name, entity->text_point.line, entity->text_point.column); - } - } - }break; - - case DF_EntityKind_Process: - { - DF_Entity *main_mod_child = df_entity_child_from_kind(entity, DF_EntityKind_Module); - String8 main_mod_name = str8_skip_last_slash(main_mod_child->name); - result = push_str8f(arena, "%S%s%sPID: %i%s", - main_mod_name, - main_mod_name.size != 0 ? " " : "", - main_mod_name.size != 0 ? "(" : "", - entity->ctrl_id, - main_mod_name.size != 0 ? ")" : ""); - }break; - - case DF_EntityKind_Thread: - { - String8 name = entity->name; - if(name.size == 0) - { - DF_Entity *process = df_entity_ancestor_from_kind(entity, DF_EntityKind_Process); - DF_Entity *first_thread = df_entity_child_from_kind(process, DF_EntityKind_Thread); - if(first_thread == entity) - { - name = str8_lit("Main Thread"); - } - } - result = push_str8f(arena, "%S%s%sTID: %i%s", - name, - name.size != 0 ? " " : "", - name.size != 0 ? "(" : "", - entity->ctrl_id, - name.size != 0 ? ")" : ""); - }break; - - case DF_EntityKind_Module: - { - result = push_str8_copy(arena, str8_skip_last_slash(entity->name)); - }break; - - case DF_EntityKind_RecentProject: - { - result = push_str8_copy(arena, str8_skip_last_slash(entity->name)); - }break; - } - return result; -} - -//- rjf: extra search tag strings for fuzzy filtering entities - -internal String8 -df_search_tags_from_entity(Arena *arena, DF_Entity *entity) -{ - String8 result = {0}; - if(entity->kind == DF_EntityKind_Thread) - { - Temp scratch = scratch_begin(&arena, 1); - DF_Entity *process = df_entity_ancestor_from_kind(entity, DF_EntityKind_Process); - CTRL_Unwind unwind = df_query_cached_unwind_from_thread(entity); - String8List strings = {0}; - for(U64 frame_num = unwind.frames.count; frame_num > 0; frame_num -= 1) - { - CTRL_UnwindFrame *f = &unwind.frames.v[frame_num-1]; - U64 rip_vaddr = regs_rip_from_arch_block(entity->arch, f->regs); - DF_Entity *module = df_module_from_process_vaddr(process, rip_vaddr); - U64 rip_voff = df_voff_from_vaddr(module, rip_vaddr); - DI_Key dbgi_key = df_dbgi_key_from_module(module); - String8 procedure_name = df_symbol_name_from_dbgi_key_voff(scratch.arena, &dbgi_key, rip_voff); - if(procedure_name.size != 0) - { - str8_list_push(scratch.arena, &strings, procedure_name); - } - } - StringJoin join = {0}; - join.sep = str8_lit(","); - result = str8_list_join(arena, &strings, &join); - scratch_end(scratch); - } - return result; -} - -//- rjf: entity -> color operations - -internal Vec4F32 -df_hsva_from_entity(DF_Entity *entity) -{ - Vec4F32 result = {0}; - if(entity->flags & DF_EntityFlag_HasColor) - { - result = entity->color_hsva; - } - return result; -} - -internal Vec4F32 -df_rgba_from_entity(DF_Entity *entity) -{ - Vec4F32 result = {0}; - if(entity->flags & DF_EntityFlag_HasColor) - { - Vec3F32 hsv = v3f32(entity->color_hsva.x, entity->color_hsva.y, entity->color_hsva.z); - Vec3F32 rgb = rgb_from_hsv(hsv); - result = v4f32(rgb.x, rgb.y, rgb.z, entity->color_hsva.w); - } - return result; -} - -//////////////////////////////// -//~ rjf: Name Allocation - -internal U64 -df_name_bucket_idx_from_string_size(U64 size) -{ - U64 size_rounded = u64_up_to_pow2(size+1); - size_rounded = ClampBot((1<<4), size_rounded); - U64 bucket_idx = 0; - switch(size_rounded) - { - case 1<<4: {bucket_idx = 0;}break; - case 1<<5: {bucket_idx = 1;}break; - case 1<<6: {bucket_idx = 2;}break; - case 1<<7: {bucket_idx = 3;}break; - case 1<<8: {bucket_idx = 4;}break; - case 1<<9: {bucket_idx = 5;}break; - case 1<<10:{bucket_idx = 6;}break; - default:{bucket_idx = ArrayCount(df_state->free_name_chunks)-1;}break; - } - return bucket_idx; -} - -internal String8 -df_name_alloc(DF_StateDeltaHistory *hist, String8 string) -{ - if(string.size == 0) {return str8_zero();} - U64 bucket_idx = df_name_bucket_idx_from_string_size(string.size); - DF_NameChunkNode *node = df_state->free_name_chunks[bucket_idx]; - - // rjf: pull from bucket free list - if(node != 0) - { - if(bucket_idx == ArrayCount(df_state->free_name_chunks)-1) - { - node = 0; - DF_NameChunkNode *prev = 0; - for(DF_NameChunkNode *n = df_state->free_name_chunks[bucket_idx]; - n != 0; - prev = n, n = n->next) - { - if(n->size >= string.size+1) - { - if(prev == 0) - { - df_state->free_name_chunks[bucket_idx] = n->next; - } - else - { - prev->next = n->next; - } - node = n; - break; - } - } - } - else - { - SLLStackPop(df_state->free_name_chunks[bucket_idx]); - } - } - - // rjf: no found node -> allocate new - if(node == 0) - { - U64 chunk_size = 0; - if(bucket_idx < ArrayCount(df_state->free_name_chunks)-1) - { - chunk_size = 1<<(bucket_idx+4); - } - else - { - chunk_size = u64_up_to_pow2(string.size); - } - U8 *chunk_memory = push_array(df_state->arena, U8, chunk_size); - node = (DF_NameChunkNode *)chunk_memory; - } - - // rjf: fill string & return - String8 allocated_string = str8((U8 *)node, string.size); - MemoryCopy((U8 *)node, string.str, string.size); - return allocated_string; -} - -internal void -df_name_release(DF_StateDeltaHistory *hist, String8 string) -{ - if(string.size == 0) {return;} - U64 bucket_idx = df_name_bucket_idx_from_string_size(string.size); - DF_NameChunkNode *node = (DF_NameChunkNode *)string.str; - node->size = u64_up_to_pow2(string.size); - SLLStackPush(df_state->free_name_chunks[bucket_idx], node); -} - -//////////////////////////////// -//~ rjf: Entity State Functions - -//- rjf: entity mutation notification codepath - -internal void -df_entity_notify_mutation(DF_Entity *entity) -{ - for(DF_Entity *e = entity; !df_entity_is_nil(e); e = e->parent) - { - DF_EntityKindFlags flags = df_g_entity_kind_flags_table[entity->kind]; - if(e == entity && flags & DF_EntityKindFlag_LeafMutationProjectConfig) - { - DF_CmdParams p = {0}; - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_WriteProjectData)); - } - if(e == entity && flags & DF_EntityKindFlag_LeafMutationSoftHalt && df_ctrl_targets_running()) - { - df_state->entities_mut_soft_halt = 1; - } - if(e == entity && flags & DF_EntityKindFlag_LeafMutationDebugInfoMap) - { - df_state->entities_mut_dbg_info_map = 1; - } - if(flags & DF_EntityKindFlag_TreeMutationSoftHalt && df_ctrl_targets_running()) - { - df_state->entities_mut_soft_halt = 1; - } - if(flags & DF_EntityKindFlag_TreeMutationDebugInfoMap) - { - df_state->entities_mut_dbg_info_map = 1; - } - } -} - -//- rjf: entity allocation + tree forming - -internal DF_Entity * -df_entity_alloc(DF_StateDeltaHistory *hist, DF_Entity *parent, DF_EntityKind kind) -{ - B32 user_defined_lifetime = !!(df_g_entity_kind_flags_table[kind] & DF_EntityKindFlag_UserDefinedLifetime); - U64 free_list_idx = !!user_defined_lifetime; - if(df_entity_is_nil(parent)) { parent = df_state->entities_root; } - - // rjf: empty free list -> push new - if(!df_state->entities_free[free_list_idx]) - { - DF_Entity *entity = push_array(df_state->entities_arena, DF_Entity, 1); - df_state->entities_count += 1; - df_state->entities_free_count += 1; - SLLStackPush(df_state->entities_free[free_list_idx], entity); - } - - // rjf: user-defined lifetimes -> push record of df_state info - if(user_defined_lifetime) - { - df_state_delta_history_push_struct_delta(hist, &df_state->entities_root); - df_state_delta_history_push_struct_delta(hist, &df_state->entities_free_count); - df_state_delta_history_push_struct_delta(hist, &df_state->entities_active_count); - df_state_delta_history_push_struct_delta(hist, &df_state->entities_free[free_list_idx]); - df_state_delta_history_push_struct_delta(hist, &df_state->kind_alloc_gens[kind]); - } - - // rjf: pop new entity off free-list - DF_Entity *entity = df_state->entities_free[free_list_idx]; - SLLStackPop(df_state->entities_free[free_list_idx]); - df_state->entities_free_count -= 1; - df_state->entities_active_count += 1; - - // rjf: user-defined lifetimes -> push records of initial entity data - if(user_defined_lifetime) - { - df_state_delta_history_push_struct_delta(hist, &entity->next); - df_state_delta_history_push_struct_delta(hist, &entity->prev); - df_state_delta_history_push_struct_delta(hist, &entity->first); - df_state_delta_history_push_struct_delta(hist, &entity->last); - df_state_delta_history_push_struct_delta(hist, &entity->parent); - df_state_delta_history_push_struct_delta(hist, &entity->generation); - df_state_delta_history_push_struct_delta(hist, &entity->id); - df_state_delta_history_push_struct_delta(hist, &entity->kind); - if(!df_entity_is_nil(parent)) - { - df_state_delta_history_push_struct_delta(hist, &parent->first); - df_state_delta_history_push_struct_delta(hist, &parent->last); - } - if(!df_entity_is_nil(parent->last)) - { - df_state_delta_history_push_struct_delta(hist, &parent->last->next); - } - } - - // rjf: zero entity - { - U64 generation = entity->generation; - MemoryZeroStruct(entity); - entity->generation = generation; - } - - // rjf: set up alloc'd entity links - entity->first = entity->last = entity->next = entity->prev = entity->parent = &df_g_nil_entity; - entity->parent = parent; - - // rjf: stitch up parent links - if(df_entity_is_nil(parent)) - { - df_state->entities_root = entity; - } - else - { - DLLPushBack_NPZ(&df_g_nil_entity, parent->first, parent->last, entity, next, prev); - } - - // rjf: fill out metadata - entity->kind = kind; - df_state->entities_id_gen += 1; - entity->id = df_state->entities_id_gen; - entity->generation += 1; - entity->alloc_time_us = os_now_microseconds(); - - // rjf: dirtify caches - df_state->kind_alloc_gens[kind] += 1; - df_entity_notify_mutation(entity); - - // rjf: log - LogInfoNamedBlockF("new_entity") - { - log_infof("kind: \"%S\"\n", df_g_entity_kind_display_string_table[kind]); - log_infof("id: $0x%I64x\n", entity->id); - } - - return entity; -} - -internal void -df_entity_mark_for_deletion(DF_Entity *entity) -{ - if(!df_entity_is_nil(entity)) - { - entity->flags |= DF_EntityFlag_MarkedForDeletion; - df_entity_notify_mutation(entity); - } -} - -internal void -df_entity_release(DF_StateDeltaHistory *hist, DF_Entity *entity) -{ - Temp scratch = scratch_begin(0, 0); - - // rjf: unpack - U64 free_list_idx = !!(df_g_entity_kind_flags_table[entity->kind] & DF_EntityKindFlag_UserDefinedLifetime); - - // rjf: record pre-deletion entity state - df_state_delta_history_push_struct_delta(hist, &df_state->entities_free_count); - df_state_delta_history_push_struct_delta(hist, &df_state->entities_active_count); - - // rjf: release whole tree - typedef struct Task Task; - struct Task - { - Task *next; - DF_Entity *e; - }; - Task start_task = {0, entity}; - Task *first_task = &start_task; - Task *last_task = &start_task; - for(Task *task = first_task; task != 0; task = task->next) - { - for(DF_Entity *child = task->e->first; !df_entity_is_nil(child); child = child->next) - { - Task *t = push_array(scratch.arena, Task, 1); - t->e = child; - SLLQueuePush(first_task, last_task, t); - } - LogInfoNamedBlockF("end_entity") - { - String8 name = df_display_string_from_entity(scratch.arena, task->e); - log_infof("kind: \"%S\"\n", df_g_entity_kind_display_string_table[task->e->kind]); - log_infof("id: $0x%I64x\n", task->e->id); - log_infof("display_string: \"%S\"\n", name); - } - df_state_delta_history_push_struct_delta(hist, &task->e->first); - df_state_delta_history_push_struct_delta(hist, &task->e->last); - df_state_delta_history_push_struct_delta(hist, &task->e->next); - df_state_delta_history_push_struct_delta(hist, &task->e->prev); - df_state_delta_history_push_struct_delta(hist, &task->e->parent); - df_state_delta_history_push_struct_delta(hist, &df_state->kind_alloc_gens[task->e->kind]); - df_state_delta_history_push_struct_delta(hist, &df_state->entities_free[free_list_idx]); - df_set_thread_freeze_state(task->e, 0); - SLLStackPush(df_state->entities_free[free_list_idx], task->e); - df_state->entities_free_count += 1; - df_state->entities_active_count -= 1; - task->e->generation += 1; - if(task->e->name.size != 0) - { - df_name_release(hist, task->e->name); - } - df_state->kind_alloc_gens[task->e->kind] += 1; - } - - scratch_end(scratch); -} - -internal void -df_entity_change_parent(DF_StateDeltaHistory *hist, DF_Entity *entity, DF_Entity *old_parent, DF_Entity *new_parent) -{ - Assert(entity->parent == old_parent); - - // rjf: push delta records - if(hist != 0) - { - if(!df_entity_is_nil(old_parent)) - { - df_state_delta_history_push_struct_delta(df_state->hist, &old_parent->first); - df_state_delta_history_push_struct_delta(df_state->hist, &old_parent->last); - } - if(!df_entity_is_nil(new_parent)) - { - df_state_delta_history_push_struct_delta(df_state->hist, &new_parent->first); - df_state_delta_history_push_struct_delta(df_state->hist, &new_parent->last); - } - if(!df_entity_is_nil(entity->prev)) - { - df_state_delta_history_push_struct_delta(df_state->hist, &entity->prev->next); - } - if(!df_entity_is_nil(entity->next)) - { - df_state_delta_history_push_struct_delta(df_state->hist, &entity->next->prev); - } - df_state_delta_history_push_struct_delta(df_state->hist, &entity->next); - df_state_delta_history_push_struct_delta(df_state->hist, &entity->prev); - df_state_delta_history_push_struct_delta(df_state->hist, &entity->parent); - } - - // rjf: fix up links - if(!df_entity_is_nil(old_parent)) - { - DLLRemove_NPZ(&df_g_nil_entity, old_parent->first, old_parent->last, entity, next, prev); - } - if(!df_entity_is_nil(new_parent)) - { - DLLPushBack_NPZ(&df_g_nil_entity, new_parent->first, new_parent->last, entity, next, prev); - } - entity->parent = new_parent; - - // rjf: notify - df_entity_notify_mutation(entity); - df_entity_notify_mutation(new_parent); - df_entity_notify_mutation(old_parent); -} - -//- rjf: entity simple equipment - -internal void -df_entity_equip_txt_pt(DF_Entity *entity, TxtPt point) -{ - df_require_entity_nonnil(entity, return); - entity->text_point = point; - entity->flags |= DF_EntityFlag_HasTextPoint; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_txt_pt_alt(DF_Entity *entity, TxtPt point) -{ - df_require_entity_nonnil(entity, return); - entity->text_point_alt = point; - entity->flags |= DF_EntityFlag_HasTextPointAlt; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_entity_handle(DF_Entity *entity, DF_Handle handle) -{ - df_require_entity_nonnil(entity, return); - entity->entity_handle = handle; - entity->flags |= DF_EntityFlag_HasEntityHandle; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_b32(DF_Entity *entity, B32 b32) -{ - df_require_entity_nonnil(entity, return); - entity->b32 = b32; - entity->flags |= DF_EntityFlag_HasB32; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_u64(DF_Entity *entity, U64 u64) -{ - df_require_entity_nonnil(entity, return); - entity->u64 = u64; - entity->flags |= DF_EntityFlag_HasU64; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_rng1u64(DF_Entity *entity, Rng1U64 range) -{ - df_require_entity_nonnil(entity, return); - entity->rng1u64 = range; - entity->flags |= DF_EntityFlag_HasRng1U64; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_color_rgba(DF_Entity *entity, Vec4F32 rgba) -{ - df_require_entity_nonnil(entity, return); - Vec3F32 rgb = v3f32(rgba.x, rgba.y, rgba.z); - Vec3F32 hsv = hsv_from_rgb(rgb); - Vec4F32 hsva = v4f32(hsv.x, hsv.y, hsv.z, rgba.w); - df_entity_equip_color_hsva(entity, hsva); -} - -internal void -df_entity_equip_color_hsva(DF_Entity *entity, Vec4F32 hsva) -{ - df_require_entity_nonnil(entity, return); - entity->color_hsva = hsva; - entity->flags |= DF_EntityFlag_HasColor; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_death_timer(DF_Entity *entity, F32 seconds_til_death) -{ - df_require_entity_nonnil(entity, return); - entity->flags |= DF_EntityFlag_DiesWithTime; - entity->life_left = seconds_til_death; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_cfg_src(DF_Entity *entity, DF_CfgSrc cfg_src) -{ - df_require_entity_nonnil(entity, return); - entity->cfg_src = cfg_src; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_timestamp(DF_Entity *entity, U64 timestamp) -{ - df_require_entity_nonnil(entity, return); - entity->timestamp = timestamp; - df_entity_notify_mutation(entity); -} - -//- rjf: control layer correllation equipment - -internal void -df_entity_equip_ctrl_machine_id(DF_Entity *entity, CTRL_MachineID machine_id) -{ - df_require_entity_nonnil(entity, return); - entity->ctrl_machine_id = machine_id; - entity->flags |= DF_EntityFlag_HasCtrlMachineID; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_ctrl_handle(DF_Entity *entity, DMN_Handle handle) -{ - df_require_entity_nonnil(entity, return); - entity->ctrl_handle = handle; - entity->flags |= DF_EntityFlag_HasCtrlHandle; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_arch(DF_Entity *entity, Architecture arch) -{ - df_require_entity_nonnil(entity, return); - entity->arch = arch; - entity->flags |= DF_EntityFlag_HasArch; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_ctrl_id(DF_Entity *entity, U32 id) -{ - df_require_entity_nonnil(entity, return); - entity->ctrl_id = id; - entity->flags |= DF_EntityFlag_HasCtrlID; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_stack_base(DF_Entity *entity, U64 stack_base) -{ - df_require_entity_nonnil(entity, return); - entity->stack_base = stack_base; - entity->flags |= DF_EntityFlag_HasStackBase; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_tls_root(DF_Entity *entity, U64 tls_root) -{ - df_require_entity_nonnil(entity, return); - entity->tls_root = tls_root; - entity->flags |= DF_EntityFlag_HasTLSRoot; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_vaddr_rng(DF_Entity *entity, Rng1U64 range) -{ - df_require_entity_nonnil(entity, return); - entity->vaddr_rng = range; - entity->flags |= DF_EntityFlag_HasVAddrRng; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_vaddr(DF_Entity *entity, U64 vaddr) -{ - df_require_entity_nonnil(entity, return); - entity->vaddr = vaddr; - entity->flags |= DF_EntityFlag_HasVAddr; - df_entity_notify_mutation(entity); -} - -//- rjf: name equipment - -internal void -df_entity_equip_name(DF_StateDeltaHistory *hist, DF_Entity *entity, String8 name) -{ - df_require_entity_nonnil(entity, return); - if(entity->name.size != 0) - { - df_name_release(hist, entity->name); - } - if(name.size != 0) - { - entity->name = df_name_alloc(hist, name); - } - else - { - entity->name = str8_zero(); - } - entity->name_generation += 1; - df_entity_notify_mutation(entity); -} - -internal void -df_entity_equip_namef(DF_StateDeltaHistory *hist, DF_Entity *entity, char *fmt, ...) -{ - Temp scratch = scratch_begin(0, 0); - va_list args; - va_start(args, fmt); - String8 string = push_str8fv(scratch.arena, fmt, args); - va_end(args); - df_entity_equip_name(hist, entity, string); - scratch_end(scratch); -} - -//- rjf: opening folders/files & maintaining the entity model of the filesystem - -internal DF_Entity * -df_entity_from_path(String8 path, DF_EntityFromPathFlags flags) -{ - Temp scratch = scratch_begin(0, 0); - PathStyle path_style = PathStyle_Relative; - String8List path_parts = path_normalized_list_from_string(scratch.arena, path, &path_style); - StringMatchFlags path_match_flags = path_match_flags_from_os(operating_system_from_context()); - - //- rjf: pass 1: open parts, ignore overrides - DF_Entity *file_no_override = &df_g_nil_entity; - { - DF_Entity *parent = df_entity_root(); - for(String8Node *path_part_n = path_parts.first; - path_part_n != 0; - path_part_n = path_part_n->next) - { - // rjf: find next child - DF_Entity *next_parent = &df_g_nil_entity; - for(DF_Entity *child = parent->first; !df_entity_is_nil(child); child = child->next) - { - B32 name_matches = str8_match(child->name, path_part_n->string, path_match_flags); - if(name_matches && child->kind == DF_EntityKind_File) - { - next_parent = child; - break; - } - } - - // rjf: no next -> allocate one - if(df_entity_is_nil(next_parent)) - { - if(flags & DF_EntityFromPathFlag_OpenAsNeeded) - { - String8 parent_path = df_full_path_from_entity(scratch.arena, parent); - String8 path = push_str8f(scratch.arena, "%S%s%S", parent_path, parent_path.size != 0 ? "/" : "", path_part_n->string); - FileProperties file_properties = os_properties_from_file_path(path); - if(file_properties.created != 0 || flags & DF_EntityFromPathFlag_OpenMissing) - { - next_parent = df_entity_alloc(0, parent, DF_EntityKind_File); - df_entity_equip_name(0, next_parent, path_part_n->string); - next_parent->timestamp = file_properties.modified; - next_parent->flags |= DF_EntityFlag_IsFolder * !!(file_properties.flags & FilePropertyFlag_IsFolder); - next_parent->flags |= DF_EntityFlag_IsMissing * !!(file_properties.created == 0); - if(path_part_n->next != 0) - { - next_parent->flags |= DF_EntityFlag_IsFolder; - } - } - } - else - { - parent = &df_g_nil_entity; - break; - } - } - - // rjf: next parent -> follow it - parent = next_parent; - } - file_no_override = (parent != df_entity_root() ? parent : &df_g_nil_entity); - } - - //- rjf: pass 2: follow overrides - DF_Entity *file_overrides_applied = &df_g_nil_entity; - if(flags & DF_EntityFromPathFlag_AllowOverrides) - { - DF_Entity *parent = df_entity_root(); - for(String8Node *path_part_n = path_parts.first; - path_part_n != 0; - path_part_n = path_part_n->next) - { - // rjf: find next child - DF_Entity *next_parent = &df_g_nil_entity; - for(DF_Entity *child = parent->first; !df_entity_is_nil(child); child = child->next) - { - B32 name_matches = str8_match(child->name, path_part_n->string, path_match_flags); - if(name_matches && child->kind == DF_EntityKind_File) - { - next_parent = child; - } - if(name_matches && child->kind == DF_EntityKind_OverrideFileLink) - { - next_parent = df_entity_from_handle(child->entity_handle); - break; - } - } - - // rjf: no next -> allocate one - if(df_entity_is_nil(next_parent)) - { - if(flags & DF_EntityFromPathFlag_OpenAsNeeded) - { - String8 parent_path = df_full_path_from_entity(scratch.arena, parent); - String8 path = push_str8f(scratch.arena, "%S%s%S", parent_path, parent_path.size != 0 ? "/" : "", path_part_n->string); - FileProperties file_properties = os_properties_from_file_path(path); - if(file_properties.created != 0 || flags & DF_EntityFromPathFlag_OpenMissing) - { - next_parent = df_entity_alloc(0, parent, DF_EntityKind_File); - df_entity_equip_name(0, next_parent, path_part_n->string); - next_parent->timestamp = file_properties.modified; - next_parent->flags |= DF_EntityFlag_IsFolder * !!(file_properties.flags & FilePropertyFlag_IsFolder); - next_parent->flags |= DF_EntityFlag_IsMissing * !!(file_properties.created == 0); - if(path_part_n->next != 0) - { - next_parent->flags |= DF_EntityFlag_IsFolder; - } - } - } - else - { - parent = &df_g_nil_entity; - break; - } - } - - // rjf: next parent -> follow it - parent = next_parent; - } - file_overrides_applied = (parent != df_entity_root() ? parent : &df_g_nil_entity);; - } - - //- rjf: pick & return result - DF_Entity *result = (flags & DF_EntityFromPathFlag_AllowOverrides) ? file_overrides_applied : file_no_override; - if(flags & DF_EntityFromPathFlag_AllowOverrides && - result == file_overrides_applied && - result->flags & DF_EntityFlag_IsMissing) - { - result = file_no_override; - } - - scratch_end(scratch); - return result; -} - -internal DF_EntityList -df_possible_overrides_from_entity(Arena *arena, DF_Entity *entity) -{ - Temp scratch = scratch_begin(&arena, 1); - StringMatchFlags path_match_flags = path_match_flags_from_os(operating_system_from_context()); - DF_EntityList result = {0}; - df_entity_list_push(arena, &result, entity); - { - DF_EntityList links = df_query_cached_entity_list_with_kind(DF_EntityKind_OverrideFileLink); - String8List p_chain_names_to_entity = {0}; - for(DF_Entity *p = entity; - !df_entity_is_nil(p); - str8_list_push_front(scratch.arena, &p_chain_names_to_entity, p->name), p = p->parent) - { - // rjf: gather all links which would redirect to this chain - DF_EntityList links_going_to_p = {0}; - for(DF_EntityNode *n = links.first; n != 0; n = n->next) - { - DF_Entity *link_src = n->entity; - DF_Entity *link_dst = df_entity_from_handle(link_src->entity_handle); - if(link_dst == p) - { - df_entity_list_push(scratch.arena, &links_going_to_p, link_src); - } - } - - // rjf: for each link, gather possible overrides - for(DF_EntityNode *n = links_going_to_p.first; n != 0; n = n->next) - { - DF_Entity *link_src = n->entity; - DF_Entity *link_src_parent = link_src->parent; - - // rjf: find the sibling that this link overrides - DF_Entity *link_overridden_sibling = &df_g_nil_entity; - for(DF_Entity *child = link_src_parent->first; - !df_entity_is_nil(child); - child = child->next) - { - B32 name_matches = str8_match(child->name, link_src->name, path_match_flags); - if(name_matches && child->kind == DF_EntityKind_File) - { - link_overridden_sibling = child; - break; - } - } - - // rjf: descend tree if needed, by the chain names, find override - DF_Entity *override = link_overridden_sibling; - if(!df_entity_is_nil(override)) - { - DF_Entity *parent = override; - for(String8Node *path_part_n = p_chain_names_to_entity.first; - path_part_n != 0; - path_part_n = path_part_n->next) - { - // rjf: find next child - DF_Entity *next_parent = &df_g_nil_entity; - for(DF_Entity *child = parent->first; !df_entity_is_nil(child); child = child->next) - { - B32 name_matches = str8_match(child->name, path_part_n->string, path_match_flags); - if(name_matches && child->kind == DF_EntityKind_File) - { - next_parent = child; - break; - } - } - - // rjf: no next -> allocate one - if(df_entity_is_nil(next_parent)) - { - next_parent = df_entity_alloc(0, parent, DF_EntityKind_File); - df_entity_equip_name(0, next_parent, path_part_n->string); - String8 path = df_full_path_from_entity(scratch.arena, next_parent); - FileProperties file_properties = os_properties_from_file_path(path); - next_parent->timestamp = file_properties.modified; - next_parent->flags |= DF_EntityFlag_IsFolder * !!(file_properties.flags & FilePropertyFlag_IsFolder); - next_parent->flags |= DF_EntityFlag_IsMissing * !!(file_properties.created == 0); - } - - // rjf: next parent -> follow it - parent = next_parent; - } - override = parent; - } - - // rjf: valid override -> push - if(!df_entity_is_nil(override)) - { - df_entity_list_push(arena, &result, override); - } - } - } - } - scratch_end(scratch); - return result; -} - -//- rjf: top-level state queries - -internal DF_Entity * -df_entity_root(void) -{ - return df_state->entities_root; -} - -internal DF_EntityList -df_push_entity_list_with_kind(Arena *arena, DF_EntityKind kind) -{ - ProfBeginFunction(); - DF_EntityList result = {0}; - for(DF_Entity *entity = df_state->entities_root; - !df_entity_is_nil(entity); - entity = df_entity_rec_df_pre(entity, &df_g_nil_entity).next) - { - if(!entity->deleted && entity->kind == kind) - { - df_entity_list_push(arena, &result, entity); - } - } - ProfEnd(); - return result; -} - -internal DF_Entity * -df_entity_from_id(DF_EntityID id) -{ - DF_Entity *result = &df_g_nil_entity; - for(DF_Entity *e = df_entity_root(); - !df_entity_is_nil(e); - e = df_entity_rec_df_pre(e, &df_g_nil_entity).next) - { - if(e->id == id) - { - result = e; - break; - } - } - return result; -} - -internal DF_Entity * -df_machine_entity_from_machine_id(CTRL_MachineID machine_id) -{ - DF_Entity *result = &df_g_nil_entity; - for(DF_Entity *e = df_entity_root(); - !df_entity_is_nil(e); - e = df_entity_rec_df_pre(e, &df_g_nil_entity).next) - { - if(e->kind == DF_EntityKind_Machine && e->ctrl_machine_id == machine_id) - { - result = e; - break; - } - } - if(df_entity_is_nil(result)) - { - result = df_entity_alloc(0, df_entity_root(), DF_EntityKind_Machine); - df_entity_equip_ctrl_machine_id(result, machine_id); - } - return result; -} - -internal DF_Entity * -df_entity_from_ctrl_handle(CTRL_MachineID machine_id, DMN_Handle handle) -{ - DF_Entity *result = &df_g_nil_entity; - if(handle.u64[0] != 0) - { - for(DF_Entity *e = df_entity_root(); - !df_entity_is_nil(e); - e = df_entity_rec_df_pre(e, &df_g_nil_entity).next) - { - if(e->flags & DF_EntityFlag_HasCtrlMachineID && - e->flags & DF_EntityFlag_HasCtrlHandle && - e->ctrl_machine_id == machine_id && - MemoryMatchStruct(&e->ctrl_handle, &handle)) - { - result = e; - break; - } - } - } - return result; -} - -internal DF_Entity * -df_entity_from_ctrl_id(CTRL_MachineID machine_id, U32 id) -{ - DF_Entity *result = &df_g_nil_entity; - if(id != 0) - { - for(DF_Entity *e = df_entity_root(); - !df_entity_is_nil(e); - e = df_entity_rec_df_pre(e, &df_g_nil_entity).next) - { - if(e->flags & DF_EntityFlag_HasCtrlMachineID && - e->flags & DF_EntityFlag_HasCtrlID && - e->ctrl_machine_id == machine_id && - e->ctrl_id == id) - { - result = e; - break; - } - } - } - return result; -} - -internal DF_Entity * -df_entity_from_name_and_kind(String8 string, DF_EntityKind kind) -{ - DF_Entity *result = &df_g_nil_entity; - DF_EntityList all_of_this_kind = df_query_cached_entity_list_with_kind(kind); - for(DF_EntityNode *n = all_of_this_kind.first; n != 0; n = n->next) - { - if(str8_match(n->entity->name, string, 0)) - { - result = n->entity; - break; - } - } - return result; -} - -internal DF_Entity * -df_entity_from_u64_and_kind(U64 u64, DF_EntityKind kind) -{ - DF_Entity *result = &df_g_nil_entity; - DF_EntityList all_of_this_kind = df_query_cached_entity_list_with_kind(kind); - for(DF_EntityNode *n = all_of_this_kind.first; n != 0; n = n->next) - { - if(n->entity->u64 == u64) - { - result = n->entity; - break; - } - } - return result; -} - -//- rjf: entity freezing state - -internal void -df_set_thread_freeze_state(DF_Entity *thread, B32 frozen) -{ - DF_Handle thread_handle = df_handle_from_entity(thread); - DF_HandleNode *already_frozen_node = df_handle_list_find(&df_state->frozen_threads, thread_handle); - B32 is_frozen = !!already_frozen_node; - B32 should_be_frozen = frozen; - - // rjf: not frozen => frozen - if(!is_frozen && should_be_frozen) - { - DF_HandleNode *node = df_state->free_handle_node; - if(node) - { - SLLStackPop(df_state->free_handle_node); - } - else - { - node = push_array(df_state->arena, DF_HandleNode, 1); - } - node->handle = thread_handle; - df_handle_list_push_node(&df_state->frozen_threads, node); - df_state->entities_mut_soft_halt = 1; - } - - // rjf: frozen => not frozen - if(is_frozen && !should_be_frozen) - { - df_state->entities_mut_soft_halt = 1; - df_handle_list_remove(&df_state->frozen_threads, already_frozen_node); - SLLStackPush(df_state->free_handle_node, already_frozen_node); - } - - df_entity_notify_mutation(thread); -} - -internal B32 -df_entity_is_frozen(DF_Entity *entity) -{ - B32 is_frozen = !df_entity_is_nil(entity); - for(DF_Entity *e = entity; !df_entity_is_nil(e); e = df_entity_rec_df_pre(e, entity).next) - { - if(e->kind == DF_EntityKind_Thread) - { - B32 thread_is_frozen = !!df_handle_list_find(&df_state->frozen_threads, df_handle_from_entity(e)); - if(!thread_is_frozen) - { - is_frozen = 0; - break; - } - } - } - return is_frozen; -} - -//////////////////////////////// -//~ rjf: Command Stateful Functions - -internal void -df_register_cmd_specs(DF_CmdSpecInfoArray specs) -{ - U64 registrar_idx = df_state->total_registrar_count; - df_state->total_registrar_count += 1; - for(U64 idx = 0; idx < specs.count; idx += 1) - { - // rjf: extract info from array slot - DF_CmdSpecInfo *info = &specs.v[idx]; - - // rjf: skip empties - if(info->string.size == 0) - { - continue; - } - - // rjf: determine hash/slot - U64 hash = df_hash_from_string(info->string); - U64 slot = hash % df_state->cmd_spec_table_size; - - // rjf: allocate node & push - DF_CmdSpec *spec = push_array(df_state->arena, DF_CmdSpec, 1); - SLLStackPush_N(df_state->cmd_spec_table[slot], spec, hash_next); - - // rjf: fill node - DF_CmdSpecInfo *info_copy = &spec->info; - info_copy->string = push_str8_copy(df_state->arena, info->string); - info_copy->description = push_str8_copy(df_state->arena, info->description); - info_copy->search_tags = push_str8_copy(df_state->arena, info->search_tags); - info_copy->display_name = push_str8_copy(df_state->arena, info->display_name); - info_copy->flags = info->flags; - info_copy->query = info->query; - info_copy->canonical_icon_kind = info->canonical_icon_kind; - spec->registrar_index = registrar_idx; - spec->ordering_index = idx; - } -} - -internal DF_CmdSpec * -df_cmd_spec_from_string(String8 string) -{ - DF_CmdSpec *result = &df_g_nil_cmd_spec; - { - U64 hash = df_hash_from_string(string); - U64 slot = hash%df_state->cmd_spec_table_size; - for(DF_CmdSpec *n = df_state->cmd_spec_table[slot]; n != 0; n = n->hash_next) - { - if(str8_match(n->info.string, string, 0)) - { - result = n; - break; - } - } - } - return result; -} - -internal DF_CmdSpec * -df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind core_cmd_kind) -{ - String8 string = df_g_core_cmd_kind_spec_info_table[core_cmd_kind].string; - DF_CmdSpec *result = df_cmd_spec_from_string(string); - return result; -} - -internal void -df_cmd_spec_counter_inc(DF_CmdSpec *spec) -{ - if(!df_cmd_spec_is_nil(spec)) - { - spec->run_count += 1; - } -} - -internal DF_CmdSpecList -df_push_cmd_spec_list(Arena *arena) -{ - DF_CmdSpecList list = {0}; - for(U64 idx = 0; idx < df_state->cmd_spec_table_size; idx += 1) - { - for(DF_CmdSpec *spec = df_state->cmd_spec_table[idx]; spec != 0; spec = spec->hash_next) - { - df_cmd_spec_list_push(arena, &list, spec); - } - } - return list; -} - -//////////////////////////////// -//~ rjf: View Rule Spec Stateful Functions - -internal void -df_register_core_view_rule_specs(DF_CoreViewRuleSpecInfoArray specs) -{ - for(U64 idx = 0; idx < specs.count; idx += 1) - { - // rjf: extract info from array slot - DF_CoreViewRuleSpecInfo *info = &specs.v[idx]; - - // rjf: skip empties - if(info->string.size == 0) - { - continue; - } - - // rjf: determine hash/slot - U64 hash = df_hash_from_string(info->string); - U64 slot_idx = hash%df_state->view_rule_spec_table_size; - - // rjf: allocate node & push - DF_CoreViewRuleSpec *spec = push_array(df_state->arena, DF_CoreViewRuleSpec, 1); - SLLStackPush_N(df_state->view_rule_spec_table[slot_idx], spec, hash_next); - - // rjf: fill node - DF_CoreViewRuleSpecInfo *info_copy = &spec->info; - MemoryCopyStruct(info_copy, info); - info_copy->string = push_str8_copy(df_state->arena, info->string); - info_copy->display_string = push_str8_copy(df_state->arena, info->display_string); - info_copy->description = push_str8_copy(df_state->arena, info->description); - } -} - -internal DF_CoreViewRuleSpec * -df_core_view_rule_spec_from_string(String8 string) -{ - DF_CoreViewRuleSpec *spec = &df_g_nil_core_view_rule_spec; - { - U64 hash = df_hash_from_string(string); - U64 slot_idx = hash%df_state->view_rule_spec_table_size; - for(DF_CoreViewRuleSpec *s = df_state->view_rule_spec_table[slot_idx]; s != 0; s = s->hash_next) - { - if(str8_match(string, s->info.string, 0)) - { - spec = s; - break; - } - } - } - return spec; -} - -//////////////////////////////// -//~ rjf: Stepping "Trap Net" Builders - -// NOTE(rjf): Stepping Algorithm Overview (2024/01/17) -// -// The basic idea behind all stepping algorithms in the debugger are setting up -// a "trap net". A "trap net" is just a collection of high-level traps that are -// meant to "catch" a thread after letting it run. This trap net is submitted -// when the debugger frontend sends a "run" command (it is just empty if doing -// an actual 'run' or 'continue'). The debugger control thread then uses this -// trap net to program a state machine, to appropriately respond to a variety -// of debug events which it is passed from the OS. -// -// These are "high-level traps" because they can have specific behavioral info -// attached to them. These are encoded via the `CTRL_TrapFlags` type, which -// allow expression of the following behaviors: -// -// - end-stepping: when this trap is hit, it will end the stepping operation, -// and the target will not continue. -// - ignore-stack-pointer-check: when a trap in the trap net is hit, it will -// by-default be ignored if the thread's stack pointer has changed. this -// flag disables that behavior, for when the stack pointer is expected to -// change (e.g. step-out). -// - single-step-after-hit: when a trap with this flag is hit, the debugger -// will immediately single-step the thread which hit it. -// - save-stack-pointer: when a trap with this flag is hit, it will rewrite -// the stack pointer which is used to compare against, when deciding -// whether or not to filter a trap (based on stack pointer changes). -// - begin-spoof-mode: this enables "spoof mode". "spoof mode" is a special -// mode that disables the trap net entirely, and lets the thread run -// freely - but it catches the thread not with a trap, but a false return -// address. the debugger will overwrite a specific return address on the -// stack. this address will be overwritten with an address which does NOT -// point to a valid page, such that when the thread returns out of a -// particular call frame, the debugger will receive a debug event, at -// which point it can move the thread back to the correct return address, -// and resume with the trap net enabled. this is used in "step over" -// operations, because it avoids target <-> debugger "roundtrips" (e.g. -// target being stopped, debugger being called with debug events, then -// target resumes when debugger's control thread is done running) for -// recursions. (it doesn't make a difference with non-recursive calls, -// but the debugger can't detect the difference). -// -// Each stepping command prepares its trap net differently. -// -// --- Instruction Step Into -------------------------------------------------- -// In this case, no trap net is prepared, and only a low-level single-step is -// performed. -// -// --- Instruction Step Over -------------------------------------------------- -// To build a trap net for an instruction-level step-over, the next instruction -// at the thread's current instruction pointer is decoded. If it is a call -// instruction, or if it is a repeating instruction, then a trap with the -// 'end-stepping' behavior is placed at the instruction immediately following -// the 'call' instruction. -// -// --- Line Step Into --------------------------------------------------------- -// For a source-line step-into, the thread's instruction pointer is first used -// to look up into the debug info's line info, to find the machine code in the -// thread's current source line. Every instruction in this range is decoded. -// Traps are then built in the following way: -// -// - 'call' instruction -> if can decode call destination address, place -// "end-stepping | ignore-stack-pointer-check" trap at destination. if -// can't, "end-stepping | single-step-after | ignore-stack-pointer-check" -// trap at call. -// - 'jmp' (both unconditional & conditional) -> if can decode jump destination -// address, AND if jump leaves the line, place "end-stepping | ignore- -// stack-pointer-check" trap at destination. if can't, "end-stepping | -// single-step-after | ignore-stack-pointer-check" trap at jmp. if jump -// stays within the line, do nothing. -// - 'return' -> place "end-stepping | single-step-after" trap at return inst. -// - "end-stepping" trap is placed at the first address after the line, to -// catch all steps which simply proceed linearly through the instruction -// stream. -// -// --- Line Step Over --------------------------------------------------------- -// For a source-line step-over, the thread's instruction pointer is first used -// to look up into the debug info's line info, to find the machine code in the -// thread's current source line. Every instruction in this range is decoded. -// Traps are then built in the following way: -// -// - 'call' instruction -> place "single-step-after | begin-spoof-mode" trap at -// call instruction. -// - 'jmp' (both unconditional & conditional) -> if can decode jump destination -// address, AND if jump leaves the line, place "end-stepping" trap at -// destination. if can't, "end-stepping | single-step-after" trap at jmp. -// if jump stays within the line, do nothing. -// - 'return' -> place "end-stepping | single-step-after" trap at return inst. -// - "end-stepping" trap is placed at the first address after the line, to -// catch all steps which simply proceed linearly through the instruction -// stream. -// - for any instructions which may change the stack pointer, traps are placed -// at them with the "save-stack-pointer | single-step-after" behaviors. - -internal CTRL_TrapList -df_trap_net_from_thread__step_over_inst(Arena *arena, DF_Entity *thread) -{ - Temp scratch = scratch_begin(&arena, 1); - CTRL_TrapList result = {0}; - - // rjf: thread => unpacked info - DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); - Architecture arch = df_architecture_from_entity(thread); - U64 ip_vaddr = ctrl_query_cached_rip_from_thread(df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); - - // rjf: ip => machine code - String8 machine_code = {0}; - { - Rng1U64 rng = r1u64(ip_vaddr, ip_vaddr+max_instruction_size_from_arch(arch)); - CTRL_ProcessMemorySlice machine_code_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, rng, os_now_microseconds()+5000); - machine_code = machine_code_slice.data; - } - - // rjf: build traps if machine code was read successfully - if(machine_code.size != 0) - { - // rjf: decode instruction - DF_Inst inst = df_single_inst_from_machine_code(scratch.arena, arch, ip_vaddr, machine_code); - - // rjf: call => run until call returns - if(inst.flags & DF_InstFlag_Call || inst.flags & DF_InstFlag_Repeats) - { - CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, ip_vaddr+inst.size}; - ctrl_trap_list_push(arena, &result, &trap); - } - } - - scratch_end(scratch); - return result; -} - -internal CTRL_TrapList -df_trap_net_from_thread__step_over_line(Arena *arena, DF_Entity *thread) -{ - Temp scratch = scratch_begin(&arena, 1); - log_infof("step_over_line:\n{\n"); - CTRL_TrapList result = {0}; - - // rjf: thread => info - DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); - DF_Entity *module = df_module_from_thread(thread); - DI_Key dbgi_key = df_dbgi_key_from_module(module); - Architecture arch = df_architecture_from_entity(thread); - U64 ip_vaddr = ctrl_query_cached_rip_from_thread(df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); - log_infof("ip_vaddr: 0x%I64x\n", ip_vaddr); - log_infof("dbgi_key: {%S, 0x%I64x}\n", dbgi_key.path, dbgi_key.min_timestamp); - - // rjf: ip => line vaddr range - Rng1U64 line_vaddr_rng = {0}; - { - U64 ip_voff = df_voff_from_vaddr(module, ip_vaddr); - DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, ip_voff); - Rng1U64 line_voff_rng = {0}; - if(lines.first != 0) - { - line_voff_rng = lines.first->v.voff_range; - line_vaddr_rng = df_vaddr_range_from_voff_range(module, line_voff_rng); - DF_Entity *file = df_entity_from_handle(lines.first->v.file); - log_infof("line: {%S:%I64i}\n", file->name, lines.first->v.pt.line); - } - log_infof("voff_range: {0x%I64x, 0x%I64x}\n", line_voff_rng.min, line_voff_rng.max); - log_infof("vaddr_range: {0x%I64x, 0x%I64x}\n", line_vaddr_rng.min, line_vaddr_rng.max); - } - - // rjf: opl line_vaddr_rng -> 0xf00f00 or 0xfeefee? => include in line vaddr range - // - // MSVC exports line info at these line numbers when /JMC (Just My Code) debugging - // is enabled. This is enabled by default normally. - { - U64 opl_line_voff_rng = df_voff_from_vaddr(module, line_vaddr_rng.max); - DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, opl_line_voff_rng); - if(lines.first != 0 && (lines.first->v.pt.line == 0xf00f00 || lines.first->v.pt.line == 0xfeefee)) - { - line_vaddr_rng.max = df_vaddr_from_voff(module, lines.first->v.voff_range.max); - } - } - - // rjf: line vaddr range => did we find anything successfully? - B32 good_line_info = (line_vaddr_rng.max != 0); - - // rjf: line vaddr range => line's machine code - String8 machine_code = {0}; - if(good_line_info) - { - CTRL_ProcessMemorySlice machine_code_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, line_vaddr_rng, os_now_microseconds()+50000); - machine_code = machine_code_slice.data; - LogInfoNamedBlockF("machine_code_slice") - { - log_infof("stale: %i\n", machine_code_slice.stale); - log_infof("any_byte_bad: %i\n", machine_code_slice.any_byte_bad); - log_infof("any_byte_changed: %i\n", machine_code_slice.any_byte_changed); - log_infof("bytes:\n[\n"); - for(U64 idx = 0; idx < machine_code_slice.data.size; idx += 1) - { - log_infof("0x%x,", machine_code_slice.data.str[idx]); - if(idx%16 == 15 || idx+1 == machine_code_slice.data.size) - { - log_infof("\n"); - } - } - log_infof("]\n"); - } - } - - // rjf: machine code => ctrl flow analysis - DF_CtrlFlowInfo ctrl_flow_info = {0}; - if(good_line_info) - { - ctrl_flow_info = df_ctrl_flow_info_from_arch_vaddr_code(scratch.arena, - DF_InstFlag_Call| - DF_InstFlag_Branch| - DF_InstFlag_UnconditionalJump| - DF_InstFlag_ChangesStackPointer| - DF_InstFlag_Return, - arch, - line_vaddr_rng.min, - machine_code); - LogInfoNamedBlockF("ctrl_flow_info") - { - log_infof("flags: %x\n", ctrl_flow_info.flags); - LogInfoNamedBlockF("exit_points") for(DF_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) - { - log_infof("{vaddr:0x%I64x, jump_dest_vaddr:0x%I64x, expected_sp_delta:0x%I64x, inst_flags:%x}\n", n->v.vaddr, n->v.jump_dest_vaddr, n->v.expected_sp_delta, n->v.inst_flags); - } - } - } - - // rjf: push traps for all exit points - if(good_line_info) for(DF_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) - { - DF_CtrlFlowPoint *point = &n->v; - CTRL_TrapFlags flags = 0; - B32 add = 1; - U64 trap_addr = point->vaddr; - - // rjf: branches/jumps/returns => single-step & end, OR trap @ destination. - if(point->inst_flags & (DF_InstFlag_Branch| - DF_InstFlag_UnconditionalJump| - DF_InstFlag_Return)) - { - flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_EndStepping); - - // rjf: omit if this jump stays inside of this line - if(contains_1u64(line_vaddr_rng, point->jump_dest_vaddr)) - { - add = 0; - } - - // rjf: trap @ destination, if we can - we can avoid a single-step this way. - if(point->jump_dest_vaddr != 0) - { - trap_addr = point->jump_dest_vaddr; - flags &= ~CTRL_TrapFlag_SingleStepAfterHit; - } - - } - - // rjf: call => place spoof at return spot in stack, single-step after hitting - else if(point->inst_flags & DF_InstFlag_Call) - { - flags |= (CTRL_TrapFlag_BeginSpoofMode|CTRL_TrapFlag_SingleStepAfterHit); - } - - // rjf: instruction changes stack pointer => save off the stack pointer, single-step over, keep stepping - else if(point->inst_flags & DF_InstFlag_ChangesStackPointer) - { - flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_SaveStackPointer); - } - - // rjf: add if appropriate - if(add) - { - CTRL_Trap trap = {flags, trap_addr}; - ctrl_trap_list_push(arena, &result, &trap); - } - } - - // rjf: push trap for natural linear flow - if(good_line_info) - { - CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, line_vaddr_rng.max}; - ctrl_trap_list_push(arena, &result, &trap); - } - - // rjf: log - LogInfoNamedBlockF("traps") for(CTRL_TrapNode *n = result.first; n != 0; n = n->next) - { - log_infof("{flags:0x%x, vaddr:0x%I64x}\n", n->v.flags, n->v.vaddr); - } - - scratch_end(scratch); - log_infof("}\n\n"); - return result; -} - -internal CTRL_TrapList -df_trap_net_from_thread__step_into_line(Arena *arena, DF_Entity *thread) -{ - Temp scratch = scratch_begin(&arena, 1); - CTRL_TrapList result = {0}; - - // rjf: thread => info - DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); - DF_Entity *module = df_module_from_thread(thread); - DI_Key dbgi_key = df_dbgi_key_from_module(module); - Architecture arch = df_architecture_from_entity(thread); - U64 ip_vaddr = ctrl_query_cached_rip_from_thread(df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); - - // rjf: ip => line vaddr range - Rng1U64 line_vaddr_rng = {0}; - { - U64 ip_voff = df_voff_from_vaddr(module, ip_vaddr); - DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, ip_voff); - Rng1U64 line_voff_rng = {0}; - if(lines.first != 0) - { - line_voff_rng = lines.first->v.voff_range; - line_vaddr_rng = df_vaddr_range_from_voff_range(module, line_voff_rng); - } - } - - // rjf: opl line_vaddr_rng -> 0xf00f00 or 0xfeefee? => include in line vaddr range - // - // MSVC exports line info at these line numbers when /JMC (Just My Code) debugging - // is enabled. This is enabled by default normally. - { - U64 opl_line_voff_rng = df_voff_from_vaddr(module, line_vaddr_rng.max); - DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, opl_line_voff_rng); - if(lines.first != 0 && (lines.first->v.pt.line == 0xf00f00 || lines.first->v.pt.line == 0xfeefee)) - { - line_vaddr_rng.max = df_vaddr_from_voff(module, lines.first->v.voff_range.max); - } - } - - // rjf: line vaddr range => did we find anything successfully? - B32 good_line_info = (line_vaddr_rng.max != 0); - - // rjf: line vaddr range => line's machine code - String8 machine_code = {0}; - if(good_line_info) - { - CTRL_ProcessMemorySlice machine_code_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, line_vaddr_rng, os_now_microseconds()+5000); - machine_code = machine_code_slice.data; - } - - // rjf: machine code => ctrl flow analysis - DF_CtrlFlowInfo ctrl_flow_info = {0}; - if(good_line_info) - { - ctrl_flow_info = df_ctrl_flow_info_from_arch_vaddr_code(scratch.arena, - DF_InstFlag_Call| - DF_InstFlag_Branch| - DF_InstFlag_UnconditionalJump| - DF_InstFlag_ChangesStackPointer| - DF_InstFlag_Return, - arch, - line_vaddr_rng.min, - machine_code); - } - - // rjf: push traps for all exit points - if(good_line_info) for(DF_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) - { - DF_CtrlFlowPoint *point = &n->v; - CTRL_TrapFlags flags = 0; - B32 add = 1; - U64 trap_addr = point->vaddr; - - // rjf: branches/jumps/returns => single-step & end, OR trap @ destination. - if(point->inst_flags & (DF_InstFlag_Call| - DF_InstFlag_Branch| - DF_InstFlag_UnconditionalJump| - DF_InstFlag_Return)) - { - flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_EndStepping|CTRL_TrapFlag_IgnoreStackPointerCheck); - - // rjf: omit if this jump stays inside of this line - if(contains_1u64(line_vaddr_rng, point->jump_dest_vaddr)) - { - add = 0; - } - - // rjf: trap @ destination, if we can - we can avoid a single-step this way. - if(point->jump_dest_vaddr != 0) - { - trap_addr = point->jump_dest_vaddr; - flags &= ~CTRL_TrapFlag_SingleStepAfterHit; - } - } - - // rjf: instruction changes stack pointer => save off the stack pointer, single-step over, keep stepping - else if(point->inst_flags & DF_InstFlag_ChangesStackPointer) - { - flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_SaveStackPointer); - } - - // rjf: add if appropriate - if(add) - { - CTRL_Trap trap = {flags, trap_addr}; - ctrl_trap_list_push(arena, &result, &trap); - } - } - - // rjf: push trap for natural linear flow - if(good_line_info) - { - CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, line_vaddr_rng.max}; - ctrl_trap_list_push(arena, &result, &trap); - } - - scratch_end(scratch); - return result; -} - -//////////////////////////////// -//~ rjf: Modules & Debug Info Mappings - -//- rjf: module <=> debug info keys - -internal DI_Key -df_dbgi_key_from_module(DF_Entity *module) -{ - DF_Entity *debug_info_path = df_entity_child_from_kind(module, DF_EntityKind_DebugInfoPath); - DI_Key key = {debug_info_path->name, debug_info_path->timestamp}; - return key; -} - -internal DF_EntityList -df_modules_from_dbgi_key(Arena *arena, DI_Key *dbgi_key) -{ - DF_EntityList list = {0}; - DF_EntityList all_modules = df_query_cached_entity_list_with_kind(DF_EntityKind_Module); - for(DF_EntityNode *n = all_modules.first; n != 0; n = n->next) - { - DF_Entity *module = n->entity; - DI_Key module_dbgi_key = df_dbgi_key_from_module(module); - if(di_key_match(&module_dbgi_key, dbgi_key)) - { - df_entity_list_push(arena, &list, module); - } - } - return list; -} - -//- rjf: voff <=> vaddr - -internal U64 -df_base_vaddr_from_module(DF_Entity *module) -{ - U64 module_base_vaddr = module->vaddr; - return module_base_vaddr; -} - -internal U64 -df_voff_from_vaddr(DF_Entity *module, U64 vaddr) -{ - U64 module_base_vaddr = df_base_vaddr_from_module(module); - U64 voff = vaddr - module_base_vaddr; - return voff; -} - -internal U64 -df_vaddr_from_voff(DF_Entity *module, U64 voff) -{ - U64 module_base_vaddr = df_base_vaddr_from_module(module); - U64 vaddr = voff + module_base_vaddr; - return vaddr; -} - -internal Rng1U64 -df_voff_range_from_vaddr_range(DF_Entity *module, Rng1U64 vaddr_rng) -{ - U64 rng_size = dim_1u64(vaddr_rng); - Rng1U64 voff_rng = {0}; - voff_rng.min = df_voff_from_vaddr(module, vaddr_rng.min); - voff_rng.max = voff_rng.min + rng_size; - return voff_rng; -} - -internal Rng1U64 -df_vaddr_range_from_voff_range(DF_Entity *module, Rng1U64 voff_rng) -{ - U64 rng_size = dim_1u64(voff_rng); - Rng1U64 vaddr_rng = {0}; - vaddr_rng.min = df_vaddr_from_voff(module, voff_rng.min); - vaddr_rng.max = vaddr_rng.min + rng_size; - return vaddr_rng; -} - -//////////////////////////////// -//~ rjf: Debug Info Lookups - -//- rjf: symbol lookups - -internal String8 -df_symbol_name_from_dbgi_key_voff(Arena *arena, DI_Key *dbgi_key, U64 voff) -{ - String8 result = {0}; - { - Temp scratch = scratch_begin(&arena, 1); - DI_Scope *scope = di_scope_open(); - RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); - if(result.size == 0) - { - U64 scope_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_ScopeVMap, voff); - RDI_Scope *scope = rdi_element_from_name_idx(rdi, Scopes, scope_idx); - U64 proc_idx = scope->proc_idx; - RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, proc_idx); - U64 name_size = 0; - U8 *name_ptr = rdi_string_from_idx(rdi, procedure->name_string_idx, &name_size); - result = push_str8_copy(arena, str8(name_ptr, name_size)); - } - if(result.size == 0) - { - U64 global_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_GlobalVMap, voff); - RDI_GlobalVariable *global_var = rdi_element_from_name_idx(rdi, GlobalVariables, global_idx); - U64 name_size = 0; - U8 *name_ptr = rdi_string_from_idx(rdi, global_var->name_string_idx, &name_size); - result = push_str8_copy(arena, str8(name_ptr, name_size)); - } - di_scope_close(scope); - scratch_end(scratch); - } - return result; -} - -internal String8 -df_symbol_name_from_process_vaddr(Arena *arena, DF_Entity *process, U64 vaddr) -{ - String8 result = {0}; - { - DF_Entity *module = df_module_from_process_vaddr(process, vaddr); - DI_Key dbgi_key = df_dbgi_key_from_module(module); - U64 voff = df_voff_from_vaddr(module, vaddr); - result = df_symbol_name_from_dbgi_key_voff(arena, &dbgi_key, voff); - } - return result; -} - -//- rjf: symbol -> voff lookups - -internal U64 -df_voff_from_dbgi_key_symbol_name(DI_Key *dbgi_key, String8 symbol_name) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DI_Scope *scope = di_scope_open(); - U64 result = 0; - { - RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); - RDI_NameMapKind name_map_kinds[] = - { - RDI_NameMapKind_GlobalVariables, - RDI_NameMapKind_Procedures, - }; - if(rdi != &di_rdi_parsed_nil) - { - for(U64 name_map_kind_idx = 0; - name_map_kind_idx < ArrayCount(name_map_kinds); - name_map_kind_idx += 1) - { - RDI_NameMapKind name_map_kind = name_map_kinds[name_map_kind_idx]; - RDI_NameMap *name_map = rdi_element_from_name_idx(rdi, NameMaps, name_map_kind); - RDI_ParsedNameMap parsed_name_map = {0}; - rdi_parsed_from_name_map(rdi, name_map, &parsed_name_map); - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &parsed_name_map, symbol_name.str, symbol_name.size); - - // rjf: node -> num - U64 entity_num = 0; - if(node != 0) - { - switch(node->match_count) - { - case 1: - { - entity_num = node->match_idx_or_idx_run_first + 1; - }break; - default: - { - U32 num = 0; - U32 *run = rdi_matches_from_map_node(rdi, node, &num); - if(num != 0) - { - entity_num = run[0]+1; - } - }break; - } - } - - // rjf: num -> voff - U64 voff = 0; - if(entity_num != 0) switch(name_map_kind) - { - default:{}break; - case RDI_NameMapKind_GlobalVariables: - { - RDI_GlobalVariable *global_var = rdi_element_from_name_idx(rdi, GlobalVariables, entity_num-1); - voff = global_var->voff; - }break; - case RDI_NameMapKind_Procedures: - { - RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, entity_num-1); - RDI_Scope *scope = rdi_element_from_name_idx(rdi, Scopes, procedure->root_scope_idx); - voff = *rdi_element_from_name_idx(rdi, ScopeVOffData, scope->voff_range_first); - }break; - } - - // rjf: nonzero voff -> break - if(voff != 0) - { - result = voff; - break; - } - } - } - } - di_scope_close(scope); - scratch_end(scratch); - ProfEnd(); - return result; -} - -internal U64 -df_type_num_from_dbgi_key_name(DI_Key *dbgi_key, String8 name) -{ - ProfBeginFunction(); - DI_Scope *scope = di_scope_open(); - U64 result = 0; - { - RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); - RDI_NameMap *name_map = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_Types); - RDI_ParsedNameMap parsed_name_map = {0}; - rdi_parsed_from_name_map(rdi, name_map, &parsed_name_map); - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &parsed_name_map, name.str, name.size); - U64 entity_num = 0; - if(node != 0) - { - switch(node->match_count) - { - case 1: - { - entity_num = node->match_idx_or_idx_run_first + 1; - }break; - default: - { - U32 num = 0; - U32 *run = rdi_matches_from_map_node(rdi, node, &num); - if(num != 0) - { - entity_num = run[0]+1; - } - }break; - } - } - result = entity_num; - } - di_scope_close(scope); - ProfEnd(); - return result; -} - -//- rjf: voff -> line info - -internal DF_LineList -df_lines_from_dbgi_key_voff(Arena *arena, DI_Key *dbgi_key, U64 voff) -{ - Temp scratch = scratch_begin(&arena, 1); - DI_Scope *scope = di_scope_open(); - RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); - DF_LineList result = {0}; - { - //- rjf: gather line tables - typedef struct LineTableNode LineTableNode; - struct LineTableNode - { - LineTableNode *next; - RDI_ParsedLineTable parsed_line_table; - }; - LineTableNode start_line_table = {0}; - RDI_Unit *unit = rdi_unit_from_voff(rdi, voff); - RDI_LineTable *unit_line_table = rdi_line_table_from_unit(rdi, unit); - rdi_parsed_from_line_table(rdi, unit_line_table, &start_line_table.parsed_line_table); - LineTableNode *top_line_table = &start_line_table; - RDI_Scope *scope = rdi_scope_from_voff(rdi, voff); - { - for(RDI_Scope *s = scope; - s->inline_site_idx != 0; - s = rdi_element_from_name_idx(rdi, Scopes, s->parent_scope_idx)) - { - RDI_InlineSite *inline_site = rdi_element_from_name_idx(rdi, InlineSites, s->inline_site_idx); - if(inline_site->line_table_idx != 0) - { - LineTableNode *n = push_array(scratch.arena, LineTableNode, 1); - SLLStackPush(top_line_table, n); - RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, inline_site->line_table_idx); - rdi_parsed_from_line_table(rdi, line_table, &n->parsed_line_table); - } - } - } - - //- rjf: gather lines in each line table - Rng1U64 shallowest_voff_range = {0}; - for(LineTableNode *n = top_line_table; n != 0; n = n->next) - { - RDI_ParsedLineTable parsed_line_table = n->parsed_line_table; - U64 line_info_idx = rdi_line_info_idx_from_voff(&parsed_line_table, voff); - if(line_info_idx < parsed_line_table.count) - { - RDI_Line *line = &parsed_line_table.lines[line_info_idx]; - RDI_Column *column = (line_info_idx < parsed_line_table.col_count) ? &parsed_line_table.cols[line_info_idx] : 0; - RDI_SourceFile *file = rdi_element_from_name_idx(rdi, SourceFiles, line->file_idx); - String8 file_normalized_full_path = {0}; - file_normalized_full_path.str = rdi_string_from_idx(rdi, file->normal_full_path_string_idx, &file_normalized_full_path.size); - DF_LineNode *n = push_array(arena, DF_LineNode, 1); - SLLQueuePush(result.first, result.last, n); - result.count += 1; - if(line->file_idx != 0 && file_normalized_full_path.size != 0) - { - n->v.file = df_handle_from_entity(df_entity_from_path(file_normalized_full_path, DF_EntityFromPathFlag_All)); - } - n->v.pt = txt_pt(line->line_num, column ? column->col_first : 1); - n->v.voff_range = r1u64(parsed_line_table.voffs[line_info_idx], parsed_line_table.voffs[line_info_idx+1]); - n->v.dbgi_key = *dbgi_key; - shallowest_voff_range = n->v.voff_range; - } - } - - //- rjf: clamp all lines from all tables by shallowest (most unwound) range - for(DF_LineNode *n = result.first; n != 0; n = n->next) - { - n->v.voff_range = intersect_1u64(n->v.voff_range, shallowest_voff_range); - } - } - di_scope_close(scope); - scratch_end(scratch); - return result; -} - -//- rjf: file:line -> line info - -internal DF_LineListArray -df_lines_array_from_file_line_range(Arena *arena, DF_Entity *file, Rng1S64 line_num_range) -{ - DF_LineListArray array = {0}; - { - array.count = dim_1s64(line_num_range)+1; - array.v = push_array(arena, DF_LineList, array.count); - } - Temp scratch = scratch_begin(&arena, 1); - DI_Scope *scope = di_scope_open(); - DI_KeyList dbgi_keys = df_push_active_dbgi_key_list(scratch.arena); - DF_EntityList overrides = df_possible_overrides_from_entity(scratch.arena, file); - for(DF_EntityNode *override_n = overrides.first; - override_n != 0; - override_n = override_n->next) - { - DF_Entity *override = override_n->entity; - String8 file_path = df_full_path_from_entity(scratch.arena, override); - String8 file_path_normalized = lower_from_str8(scratch.arena, file_path); - for(DI_KeyNode *dbgi_key_n = dbgi_keys.first; - dbgi_key_n != 0; - dbgi_key_n = dbgi_key_n->next) - { - // rjf: binary -> rdi - DI_Key key = dbgi_key_n->v; - RDI_Parsed *rdi = di_rdi_from_key(scope, &key, 0); - - // rjf: file_path_normalized * rdi -> src_id - B32 good_src_id = 0; - U32 src_id = 0; - if(rdi != &di_rdi_parsed_nil) - { - RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_NormalSourcePaths); - RDI_ParsedNameMap map = {0}; - rdi_parsed_from_name_map(rdi, mapptr, &map); - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, file_path_normalized.str, file_path_normalized.size); - if(node != 0) - { - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - good_src_id = 1; - src_id = ids[0]; - } - } - } - - // rjf: good src-id -> look up line info for visible range - if(good_src_id) - { - RDI_SourceFile *src = rdi_element_from_name_idx(rdi, SourceFiles, src_id); - RDI_SourceLineMap *src_line_map = rdi_element_from_name_idx(rdi, SourceLineMaps, src->source_line_map_idx); - RDI_ParsedSourceLineMap line_map = {0}; - rdi_parsed_from_source_line_map(rdi, src_line_map, &line_map); - U64 line_idx = 0; - for(S64 line_num = line_num_range.min; - line_num <= line_num_range.max; - line_num += 1, line_idx += 1) - { - DF_LineList *list = &array.v[line_idx]; - U32 voff_count = 0; - U64 *voffs = rdi_line_voffs_from_num(&line_map, u32_from_u64_saturate((U64)line_num), &voff_count); - for(U64 idx = 0; idx < voff_count; idx += 1) - { - U64 base_voff = voffs[idx]; - U64 unit_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_UnitVMap, base_voff); - RDI_Unit *unit = rdi_element_from_name_idx(rdi, Units, unit_idx); - RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, unit->line_table_idx); - RDI_ParsedLineTable unit_line_info = {0}; - rdi_parsed_from_line_table(rdi, line_table, &unit_line_info); - U64 line_info_idx = rdi_line_info_idx_from_voff(&unit_line_info, base_voff); - if(unit_line_info.voffs != 0) - { - Rng1U64 range = r1u64(base_voff, unit_line_info.voffs[line_info_idx+1]); - S64 actual_line = (S64)unit_line_info.lines[line_info_idx].line_num; - DF_LineNode *n = push_array(arena, DF_LineNode, 1); - n->v.voff_range = range; - n->v.pt.line = (S64)actual_line; - n->v.pt.column = 1; - n->v.dbgi_key = key; - SLLQueuePush(list->first, list->last, n); - list->count += 1; - } - } - } - } - - // rjf: good src id -> push to relevant dbgi keys - if(good_src_id) - { - di_key_list_push(arena, &array.dbgi_keys, &key); - } - } - } - di_scope_close(scope); - scratch_end(scratch); - return array; -} - -internal DF_LineList -df_lines_from_file_line_num(Arena *arena, DF_Entity *file, S64 line_num) -{ - DF_LineListArray array = df_lines_array_from_file_line_range(arena, file, r1s64(line_num, line_num+1)); - DF_LineList list = {0}; - if(array.count != 0) - { - list = array.v[0]; - } - return list; -} - -//- rjf: src -> voff lookups - -internal DF_TextLineSrc2DasmInfoListArray -df_text_line_src2dasm_info_list_array_from_src_line_range(Arena *arena, DF_Entity *file, Rng1S64 line_num_range) -{ - DF_TextLineSrc2DasmInfoListArray src2dasm_array = {0}; - { - src2dasm_array.count = dim_1s64(line_num_range)+1; - src2dasm_array.v = push_array(arena, DF_TextLineSrc2DasmInfoList, src2dasm_array.count); - } - Temp scratch = scratch_begin(&arena, 1); - DI_Scope *scope = di_scope_open(); - DI_KeyList dbgi_keys = df_push_active_dbgi_key_list(scratch.arena); - DF_EntityList overrides = df_possible_overrides_from_entity(scratch.arena, file); - for(DF_EntityNode *override_n = overrides.first; - override_n != 0; - override_n = override_n->next) - { - DF_Entity *override = override_n->entity; - String8 file_path = df_full_path_from_entity(scratch.arena, override); - String8 file_path_normalized = lower_from_str8(scratch.arena, file_path); - for(DI_KeyNode *dbgi_key_n = dbgi_keys.first; - dbgi_key_n != 0; - dbgi_key_n = dbgi_key_n->next) - { - // rjf: binary -> rdi - DI_Key key = dbgi_key_n->v; - RDI_Parsed *rdi = di_rdi_from_key(scope, &key, 0); - - // rjf: file_path_normalized * rdi -> src_id - B32 good_src_id = 0; - U32 src_id = 0; - if(rdi != &di_rdi_parsed_nil) - { - RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_NormalSourcePaths); - RDI_ParsedNameMap map = {0}; - rdi_parsed_from_name_map(rdi, mapptr, &map); - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, file_path_normalized.str, file_path_normalized.size); - if(node != 0) - { - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - good_src_id = 1; - src_id = ids[0]; - } - } - } - - // rjf: good src-id -> look up line info for visible range - if(good_src_id) - { - RDI_SourceFile *src = rdi_element_from_name_idx(rdi, SourceFiles, src_id); - RDI_SourceLineMap *src_line_map = rdi_element_from_name_idx(rdi, SourceLineMaps, src->source_line_map_idx); - RDI_ParsedSourceLineMap line_map = {0}; - rdi_parsed_from_source_line_map(rdi, src_line_map, &line_map); - U64 line_idx = 0; - for(S64 line_num = line_num_range.min; - line_num <= line_num_range.max; - line_num += 1, line_idx += 1) - { - DF_TextLineSrc2DasmInfoList *src2dasm_list = &src2dasm_array.v[line_idx]; - U32 voff_count = 0; - U64 *voffs = rdi_line_voffs_from_num(&line_map, u32_from_u64_saturate((U64)line_num), &voff_count); - for(U64 idx = 0; idx < voff_count; idx += 1) - { - U64 base_voff = voffs[idx]; - U64 unit_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_UnitVMap, base_voff); - RDI_Unit *unit = rdi_element_from_name_idx(rdi, Units, unit_idx); - RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, unit->line_table_idx); - RDI_ParsedLineTable unit_line_info = {0}; - rdi_parsed_from_line_table(rdi, line_table, &unit_line_info); - U64 line_info_idx = rdi_line_info_idx_from_voff(&unit_line_info, base_voff); - if(unit_line_info.voffs != 0) - { - Rng1U64 range = r1u64(base_voff, unit_line_info.voffs[line_info_idx+1]); - S64 actual_line = (S64)unit_line_info.lines[line_info_idx].line_num; - DF_TextLineSrc2DasmInfoNode *src2dasm_n = push_array(arena, DF_TextLineSrc2DasmInfoNode, 1); - src2dasm_n->v.voff_range = range; - src2dasm_n->v.remap_line = (S64)actual_line; - src2dasm_n->v.dbgi_key = key; - SLLQueuePush(src2dasm_list->first, src2dasm_list->last, src2dasm_n); - src2dasm_list->count += 1; - } - } - } - } - - // rjf: good src id -> push to relevant dbgi keys - if(good_src_id) - { - di_key_list_push(arena, &src2dasm_array.dbgi_keys, &key); - } - } - } - di_scope_close(scope); - scratch_end(scratch); - return src2dasm_array; -} - -//////////////////////////////// -//~ rjf: Process/Thread/Module Info Lookups - -internal DF_Entity * -df_module_from_process_vaddr(DF_Entity *process, U64 vaddr) -{ - ProfBeginFunction(); - DF_Entity *module = &df_g_nil_entity; - for(DF_Entity *child = process->first; !df_entity_is_nil(child); child = child->next) - { - if(child->kind == DF_EntityKind_Module && contains_1u64(child->vaddr_rng, vaddr)) - { - module = child; - break; - } - } - ProfEnd(); - return module; -} - -internal DF_Entity * -df_module_from_thread(DF_Entity *thread) -{ - DF_Entity *process = thread->parent; - U64 rip = df_query_cached_rip_from_thread(thread); - return df_module_from_process_vaddr(process, rip); -} - -internal U64 -df_tls_base_vaddr_from_process_root_rip(DF_Entity *process, U64 root_vaddr, U64 rip_vaddr) -{ - ProfBeginFunction(); - U64 base_vaddr = 0; - Temp scratch = scratch_begin(0, 0); - if(!df_ctrl_targets_running()) - { - //- rjf: unpack module info - DF_Entity *module = df_module_from_process_vaddr(process, rip_vaddr); - Rng1U64 tls_vaddr_range = ctrl_tls_vaddr_range_from_module(module->ctrl_machine_id, module->ctrl_handle); - U64 addr_size = bit_size_from_arch(process->arch)/8; - - //- rjf: read module's TLS index - U64 tls_index = 0; - if(addr_size != 0) - { - CTRL_ProcessMemorySlice tls_index_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, tls_vaddr_range, 0); - if(tls_index_slice.data.size >= addr_size) - { - tls_index = *(U64 *)tls_index_slice.data.str; - } - } - - //- rjf: PE path - if(addr_size != 0) - { - U64 thread_info_addr = root_vaddr; - U64 tls_addr_off = tls_index*addr_size; - U64 tls_addr_array = 0; - CTRL_ProcessMemorySlice tls_addr_array_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, r1u64(thread_info_addr, thread_info_addr+addr_size), 0); - String8 tls_addr_array_data = tls_addr_array_slice.data; - if(tls_addr_array_data.size >= 8) - { - MemoryCopy(&tls_addr_array, tls_addr_array_data.str, sizeof(U64)); - } - CTRL_ProcessMemorySlice result_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, r1u64(tls_addr_array + tls_addr_off, tls_addr_array + tls_addr_off + addr_size), 0); - String8 result_data = result_slice.data; - if(result_data.size >= 8) - { - MemoryCopy(&base_vaddr, result_data.str, sizeof(U64)); - } - } - - //- rjf: non-PE path (not implemented) -#if 0 - if(!bin_is_pe) - { - // TODO(rjf): not supported. old code from the prototype that Nick had sketched out: - // TODO(nick): This code works only if the linked c runtime library is glibc. - // Implement CRT detection here. - - U64 dtv_addr = UINT64_MAX; - demon_read_memory(process->demon_handle, &dtv_addr, thread_info_addr, addr_size); - - /* - union delta_thread_vector - { - size_t counter; - struct - { - void *value; - void *to_free; - } pointer; - }; - */ - - U64 dtv_size = 16; - U64 dtv_count = 0; - demon_read_memory(process->demon_handle, &dtv_count, dtv_addr - dtv_size, addr_size); - - if (tls_index > 0 && tls_index < dtv_count) - { - demon_read_memory(process->demon_handle, &result, dtv_addr + dtv_size*tls_index, addr_size); - } - } -#endif - } - scratch_end(scratch); - ProfEnd(); - return base_vaddr; -} - -internal Architecture -df_architecture_from_entity(DF_Entity *entity) -{ - return entity->arch; -} - -internal EVAL_String2NumMap * -df_push_locals_map_from_dbgi_key_voff(Arena *arena, DI_Scope *scope, DI_Key *dbgi_key, U64 voff) -{ - RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); - EVAL_String2NumMap *result = eval_push_locals_map_from_rdi_voff(arena, rdi, voff); - return result; -} - -internal EVAL_String2NumMap * -df_push_member_map_from_dbgi_key_voff(Arena *arena, DI_Scope *scope, DI_Key *dbgi_key, U64 voff) -{ - RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); - EVAL_String2NumMap *result = eval_push_member_map_from_rdi_voff(arena, rdi, voff); - return result; -} - -internal B32 -df_set_thread_rip(DF_Entity *thread, U64 vaddr) -{ - Temp scratch = scratch_begin(0, 0); - void *block = ctrl_query_cached_reg_block_from_thread(scratch.arena, df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); - regs_arch_block_write_rip(thread->arch, block, vaddr); - B32 result = ctrl_thread_write_reg_block(thread->ctrl_machine_id, thread->ctrl_handle, block); - - // rjf: early mutation of unwind cache for immediate frontend effect - if(result) - { - DF_UnwindCache *cache = &df_state->unwind_cache; - if(cache->slots_count != 0) - { - DF_Handle thread_handle = df_handle_from_entity(thread); - U64 hash = df_hash_from_string(str8_struct(&thread_handle)); - U64 slot_idx = hash%cache->slots_count; - DF_UnwindCacheSlot *slot = &cache->slots[slot_idx]; - for(DF_UnwindCacheNode *n = slot->first; n != 0; n = n->next) - { - if(df_handle_match(n->thread, thread_handle) && n->unwind.frames.count != 0) - { - regs_arch_block_write_rip(thread->arch, n->unwind.frames.v[0].regs, vaddr); - break; - } - } - } - } - - scratch_end(scratch); - return result; -} - -internal DF_Entity * -df_module_from_thread_candidates(DF_Entity *thread, DF_EntityList *candidates) -{ - DF_Entity *src_module = df_module_from_thread(thread); - DF_Entity *module = &df_g_nil_entity; - DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); - for(DF_EntityNode *n = candidates->first; n != 0; n = n->next) - { - DF_Entity *candidate_module = n->entity; - DF_Entity *candidate_process = df_entity_ancestor_from_kind(candidate_module, DF_EntityKind_Process); - if(candidate_process == process) - { - module = candidate_module; - } - if(candidate_module == src_module) - { - break; - } - } - return module; -} - -internal DF_Unwind -df_unwind_from_ctrl_unwind(Arena *arena, DI_Scope *di_scope, DF_Entity *process, CTRL_Unwind *base_unwind) -{ - Temp scratch = scratch_begin(&arena, 1); - DF_UnwindFrameList rich_frames_list = {0}; - Architecture arch = df_architecture_from_entity(process); - for(U64 base_frame_idx = 0; base_frame_idx < base_unwind->frames.count; base_frame_idx += 1) - { - CTRL_UnwindFrame *base_frame = &base_unwind->frames.v[base_frame_idx]; - U64 rip_vaddr = regs_rip_from_arch_block(arch, base_frame->regs); - DF_Entity *module = df_module_from_process_vaddr(process, rip_vaddr); - U64 rip_voff = df_voff_from_vaddr(module, rip_vaddr); - DI_Key dbgi_key = df_dbgi_key_from_module(module); - RDI_Parsed *rdi = di_rdi_from_key(di_scope, &dbgi_key, 0); - RDI_Scope *scope = rdi_scope_from_voff(rdi, rip_voff); - - // rjf: add rich frames for inlines - U64 inline_unwind_idx = 0; - for(RDI_Scope *s = scope; s->inline_site_idx != 0; s = rdi_element_from_name_idx(rdi, Scopes, s->parent_scope_idx)) - { - RDI_InlineSite *site = rdi_element_from_name_idx(rdi, InlineSites, s->inline_site_idx); - DF_UnwindFrameNode *n = push_array(scratch.arena, DF_UnwindFrameNode, 1); - SLLQueuePush(rich_frames_list.first, rich_frames_list.last, n); - rich_frames_list.count += 1; - n->v.regs = base_frame->regs; - n->v.rdi = rdi; - n->v.procedure = 0; - n->v.inline_site = site; - n->v.base_unwind_idx = base_frame_idx; - n->v.inline_unwind_idx = inline_unwind_idx; - inline_unwind_idx += 1; - } - - // rjf: add frame for concrete frame - DF_UnwindFrameNode *n = push_array(scratch.arena, DF_UnwindFrameNode, 1); - SLLQueuePush(rich_frames_list.first, rich_frames_list.last, n); - rich_frames_list.count += 1; - n->v.regs = base_frame->regs; - n->v.rdi = rdi; - n->v.procedure = rdi_element_from_name_idx(rdi, Procedures, scope->proc_idx); - n->v.inline_site = 0; - n->v.base_unwind_idx = base_frame_idx; - n->v.inline_unwind_idx = inline_unwind_idx; - inline_unwind_idx = 0; - } - DF_Unwind result = {0}; - { - result.frames.count = rich_frames_list.count; - result.frames.v = push_array(arena, DF_UnwindFrame, result.frames.count); - U64 idx = 0; - for(DF_UnwindFrameNode *n = rich_frames_list.first; n != 0; n = n->next, idx += 1) - { - MemoryCopyStruct(&result.frames.v[idx], &n->v); - } - } - scratch_end(scratch); - return result; -} - -internal DF_UnwindFrame * -df_frame_from_unwind_idxs(DF_Unwind *unwind, U64 base_unwind_idx, U64 inline_unwind_idx) -{ - DF_UnwindFrame *f = 0; - for(U64 idx = 0; idx < unwind->frames.count; idx += 1) - { - if(unwind->frames.v[idx].base_unwind_idx == base_unwind_idx) - { - f = &unwind->frames.v[idx]; - if(unwind->frames.v[idx].inline_unwind_idx == inline_unwind_idx) - { - break; - } - } - } - return f; -} - -//////////////////////////////// -//~ rjf: Entity -> Log Entities - -internal DF_Entity * -df_log_from_entity(DF_Entity *entity) -{ - Temp scratch = scratch_begin(0, 0); - String8 log_name = {0}; - switch(entity->kind) - { - default: - { - log_name = push_str8f(scratch.arena, "id_%I64u", entity->id); - }break; - case DF_EntityKind_Root: - { - U32 session_pid = os_get_pid(); - log_name = push_str8f(scratch.arena, "session_%i", session_pid); - }break; - case DF_EntityKind_Machine: - { - log_name = push_str8f(scratch.arena, "machine_%I64u", entity->id); - }break; - case DF_EntityKind_Process: - { - log_name = push_str8f(scratch.arena, "pid_%i", entity->ctrl_id); - }break; - case DF_EntityKind_Thread: - { - log_name = push_str8f(scratch.arena, "tid_%i", entity->ctrl_id); - }break; - } - String8 user_program_data_path = os_string_from_system_path(scratch.arena, OS_SystemPath_UserProgramData); - String8 user_data_folder = push_str8f(scratch.arena, "%S/%S", user_program_data_path, str8_lit("raddbg/logs")); - String8 log_path = push_str8f(scratch.arena, "%S/log%s%S.txt", user_data_folder, log_name.size != 0 ? "_" : "", log_name); - DF_Entity *log = df_entity_from_path(log_path, DF_EntityFromPathFlag_OpenAsNeeded|DF_EntityFromPathFlag_OpenMissing); - log->flags |= DF_EntityFlag_Output; - scratch_end(scratch); - return log; -} - -//////////////////////////////// -//~ rjf: Target Controls - -//- rjf: control message dispatching - -internal void -df_push_ctrl_msg(CTRL_Msg *msg) -{ - CTRL_Msg *dst = ctrl_msg_list_push(df_state->ctrl_msg_arena, &df_state->ctrl_msgs); - ctrl_msg_deep_copy(df_state->ctrl_msg_arena, dst, msg); - if(df_state->ctrl_soft_halt_issued == 0 && df_ctrl_targets_running()) - { - df_state->ctrl_soft_halt_issued = 1; - ctrl_halt(); - } -} - -//- rjf: control thread running - -internal void -df_ctrl_run(DF_RunKind run, DF_Entity *run_thread, CTRL_RunFlags flags, CTRL_TrapList *run_traps) -{ - Temp scratch = scratch_begin(0, 0); - - // rjf: build run message - CTRL_Msg msg = {(run == DF_RunKind_Run || run == DF_RunKind_Step) ? CTRL_MsgKind_Run : CTRL_MsgKind_SingleStep}; - { - DF_EntityList user_bps = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); - DF_Entity *process = df_entity_ancestor_from_kind(run_thread, DF_EntityKind_Process); - msg.run_flags = flags; - msg.machine_id = run_thread->ctrl_machine_id; - msg.entity = run_thread->ctrl_handle; - msg.parent = process->ctrl_handle; - MemoryCopyArray(msg.exception_code_filters, df_state->ctrl_exception_code_filters); - if(run_traps != 0) - { - MemoryCopyStruct(&msg.traps, run_traps); - } - for(DF_EntityNode *user_bp_n = user_bps.first; - user_bp_n != 0; - user_bp_n = user_bp_n->next) - { - // rjf: unpack user breakpoint entity - DF_Entity *user_bp = user_bp_n->entity; - if(user_bp->b32 == 0) - { - continue; - } - DF_Entity *file = df_entity_ancestor_from_kind(user_bp, DF_EntityKind_File); - DF_Entity *symb = df_entity_child_from_kind(user_bp, DF_EntityKind_EntryPointName); - DF_EntityList overrides = df_possible_overrides_from_entity(scratch.arena, file); - for(DF_EntityNode *override_n = overrides.first; override_n != 0; override_n = override_n->next) - { - DF_Entity *override = override_n->entity; - DF_Entity *condition_child = df_entity_child_from_kind(user_bp, DF_EntityKind_Condition); - String8 condition = condition_child->name; - - // rjf: generate user breakpoint info depending on breakpoint placement - CTRL_UserBreakpointKind ctrl_user_bp_kind = CTRL_UserBreakpointKind_FileNameAndLineColNumber; - String8 ctrl_user_bp_string = {0}; - TxtPt ctrl_user_bp_pt = {0}; - U64 ctrl_user_bp_u64 = 0; - { - if(user_bp->flags & DF_EntityFlag_HasTextPoint) - { - ctrl_user_bp_kind = CTRL_UserBreakpointKind_FileNameAndLineColNumber; - ctrl_user_bp_string = df_full_path_from_entity(scratch.arena, override); - ctrl_user_bp_pt = user_bp->text_point; - } - else if(user_bp->flags & DF_EntityFlag_HasVAddr) - { - ctrl_user_bp_kind = CTRL_UserBreakpointKind_VirtualAddress; - ctrl_user_bp_u64 = user_bp->vaddr; - } - else if(!df_entity_is_nil(symb)) - { - ctrl_user_bp_kind = CTRL_UserBreakpointKind_SymbolNameAndOffset; - ctrl_user_bp_string = symb->name; - } - } - - // rjf: push user breakpoint to list - { - CTRL_UserBreakpoint ctrl_user_bp = {ctrl_user_bp_kind}; - ctrl_user_bp.string = ctrl_user_bp_string; - ctrl_user_bp.pt = ctrl_user_bp_pt; - ctrl_user_bp.u64 = ctrl_user_bp_u64; - ctrl_user_bp.condition = condition; - ctrl_user_breakpoint_list_push(scratch.arena, &msg.user_bps, &ctrl_user_bp); - } - } - } - for(DF_HandleNode *n = df_state->frozen_threads.first; n != 0; n = n->next) - { - DF_Entity *thread = df_entity_from_handle(n->handle); - if(!df_entity_is_nil(thread)) - { - CTRL_MachineIDHandlePair pair = {thread->ctrl_machine_id, thread->ctrl_handle}; - ctrl_machine_id_handle_pair_list_push(scratch.arena, &msg.freeze_state_threads, &pair); - } - } - msg.freeze_state_is_frozen = 1; - } - - // rjf: push msg - df_push_ctrl_msg(&msg); - - // rjf: copy run traps to scratch (needed, if the caller can pass `df_state->ctrl_last_run_traps`) - CTRL_TrapList run_traps_copy = {0}; - if(run_traps != 0) - { - run_traps_copy = ctrl_trap_list_copy(scratch.arena, run_traps); - } - - // rjf: store last run info - arena_clear(df_state->ctrl_last_run_arena); - df_state->ctrl_last_run_kind = run; - df_state->ctrl_last_run_frame_idx = df_frame_index(); - df_state->ctrl_last_run_thread = df_handle_from_entity(run_thread); - df_state->ctrl_last_run_flags = flags; - df_state->ctrl_last_run_traps = ctrl_trap_list_copy(df_state->ctrl_last_run_arena, &run_traps_copy); - df_state->ctrl_is_running = 1; - - // rjf: set control context to top unwind - df_state->ctrl_ctx.unwind_count = 0; - df_state->ctrl_ctx.inline_unwind_count = 0; - - scratch_end(scratch); -} - -//- rjf: stopped info from the control thread - -internal CTRL_Event -df_ctrl_last_stop_event(void) -{ - return df_state->ctrl_last_stop_event; -} - -//////////////////////////////// -//~ rjf: Evaluation - -internal B32 -df_eval_memory_read(void *u, void *out, U64 addr, U64 size) -{ - DF_Entity *process = (DF_Entity *)u; - Assert(process->kind == DF_EntityKind_Process); - Temp scratch = scratch_begin(0, 0); - B32 result = 0; - CTRL_ProcessMemorySlice slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, r1u64(addr, addr+size), 0); - String8 data = slice.data; - if(data.size == size) - { - result = 1; - MemoryCopy(out, data.str, data.size); - } - scratch_end(scratch); - return result; -} - -internal EVAL_ParseCtx -df_eval_parse_ctx_from_process_vaddr(DI_Scope *scope, DF_Entity *process, U64 vaddr) -{ - Temp scratch = scratch_begin(0, 0); - - //- rjf: extract info - DF_Entity *module = df_module_from_process_vaddr(process, vaddr); - U64 voff = df_voff_from_vaddr(module, vaddr); - DI_Key dbgi_key = df_dbgi_key_from_module(module); - RDI_Parsed *rdi = di_rdi_from_key(scope, &dbgi_key, 0); - Architecture arch = df_architecture_from_entity(process); - EVAL_String2NumMap *reg_map = ctrl_string2reg_from_arch(arch); - EVAL_String2NumMap *reg_alias_map = ctrl_string2alias_from_arch(arch); - EVAL_String2NumMap *locals_map = df_query_cached_locals_map_from_dbgi_key_voff(&dbgi_key, voff); - EVAL_String2NumMap *member_map = df_query_cached_member_map_from_dbgi_key_voff(&dbgi_key, voff); - - //- rjf: build ctx - EVAL_ParseCtx ctx = zero_struct; - { - ctx.arch = arch; - ctx.ip_voff = voff; - ctx.rdi = rdi; - ctx.type_graph = tg_graph_begin(bit_size_from_arch(arch)/8, 256); - ctx.regs_map = reg_map; - ctx.reg_alias_map = reg_alias_map; - ctx.locals_map = locals_map; - ctx.member_map = member_map; - } - scratch_end(scratch); - return ctx; -} - -internal EVAL_ParseCtx -df_eval_parse_ctx_from_src_loc(DI_Scope *scope, DF_Entity *file, TxtPt pt) -{ - Temp scratch = scratch_begin(0, 0); - EVAL_ParseCtx ctx = zero_struct; - DI_KeyList dbgi_keys = df_push_active_dbgi_key_list(scratch.arena); - DF_TextLineSrc2DasmInfoList src2dasm_list = {0}; - - //- rjf: search for line info in all binaries for this file:pt - DF_EntityList overrides = df_possible_overrides_from_entity(scratch.arena, file); - for(DF_EntityNode *override_n = overrides.first; - override_n != 0; - override_n = override_n->next) - { - DF_Entity *override = override_n->entity; - String8 file_path = df_full_path_from_entity(scratch.arena, override); - String8 file_path_normalized = lower_from_str8(scratch.arena, file_path); - for(DI_KeyNode *dbgi_key_n = dbgi_keys.first; - dbgi_key_n != 0; - dbgi_key_n = dbgi_key_n->next) - { - // rjf: key -> rdi - DI_Key key = dbgi_key_n->v; - RDI_Parsed *rdi = di_rdi_from_key(scope, &key, 0); - - // rjf: file_path_normalized * rdi -> src_id - B32 good_src_id = 0; - U32 src_id = 0; - { - RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_NormalSourcePaths); - RDI_ParsedNameMap map = {0}; - rdi_parsed_from_name_map(rdi, mapptr, &map); - RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, file_path_normalized.str, file_path_normalized.size); - if(node != 0) - { - U32 id_count = 0; - U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); - if(id_count > 0) - { - good_src_id = 1; - src_id = ids[0]; - } - } - } - - // rjf: good src-id -> look up line info for visible range - if(good_src_id) - { - RDI_SourceFile *src = rdi_element_from_name_idx(rdi, SourceFiles, src_id); - RDI_SourceLineMap *src_line_map = rdi_element_from_name_idx(rdi, SourceLineMaps, src->source_line_map_idx); - RDI_ParsedSourceLineMap line_map = {0}; - rdi_parsed_from_source_line_map(rdi, src_line_map, &line_map); - U32 voff_count = 0; - U64 *voffs = rdi_line_voffs_from_num(&line_map, (U32)pt.line, &voff_count); - for(U64 idx = 0; idx < voff_count; idx += 1) - { - U64 base_voff = voffs[idx]; - U64 unit_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_UnitVMap, base_voff); - RDI_Unit *unit = rdi_element_from_name_idx(rdi, Units, unit_idx); - RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, unit->line_table_idx); - RDI_ParsedLineTable unit_line_info = {0}; - rdi_parsed_from_line_table(rdi, line_table, &unit_line_info); - U64 line_info_idx = rdi_line_info_idx_from_voff(&unit_line_info, base_voff); - Rng1U64 range = r1u64(base_voff, unit_line_info.voffs[line_info_idx+1]); - S64 actual_line = (S64)unit_line_info.lines[line_info_idx].line_num; - DF_TextLineSrc2DasmInfoNode *src2dasm_n = push_array(scratch.arena, DF_TextLineSrc2DasmInfoNode, 1); - src2dasm_n->v.voff_range = range; - src2dasm_n->v.remap_line = (S64)actual_line; - src2dasm_n->v.dbgi_key = key; - SLLQueuePush(src2dasm_list.first, src2dasm_list.last, src2dasm_n); - src2dasm_list.count += 1; - } - } - } - } - - //- rjf: try to form ctx from line info - B32 good_ctx = 0; - if(src2dasm_list.count != 0) - { - for(DF_TextLineSrc2DasmInfoNode *n = src2dasm_list.first; n != 0; n = n->next) - { - DF_TextLineSrc2DasmInfo *src2dasm = &n->v; - DF_EntityList modules = df_modules_from_dbgi_key(scratch.arena, &src2dasm->dbgi_key); - if(modules.count != 0) - { - DF_Entity *module = modules.first->entity; - DF_Entity *process = df_entity_ancestor_from_kind(module, DF_EntityKind_Process); - U64 voff = src2dasm->voff_range.min; - U64 vaddr = df_vaddr_from_voff(module, voff); - ctx = df_eval_parse_ctx_from_process_vaddr(scope, process, vaddr); - good_ctx = 1; - break; - } - } - } - - //- rjf: bad ctx -> reset with graceful defaults - if(good_ctx == 0) - { - ctx.rdi = &di_rdi_parsed_nil; - ctx.type_graph = tg_graph_begin(8, 256); - ctx.regs_map = &eval_string2num_map_nil; - ctx.regs_map = &eval_string2num_map_nil; - ctx.reg_alias_map = &eval_string2num_map_nil; - ctx.locals_map = &eval_string2num_map_nil; - ctx.member_map = &eval_string2num_map_nil; - } - - scratch_end(scratch); - return ctx; -} - -internal DF_Eval -df_eval_from_string(Arena *arena, DI_Scope *scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, String8 string) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(&arena, 1); - - //- rjf: unpack arguments - DF_Entity *thread = df_entity_from_handle(ctrl_ctx->thread); - U64 tls_root_vaddr = ctrl_query_cached_tls_root_vaddr_from_thread(df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); - DF_Entity *process = thread->parent; - U64 unwind_count = ctrl_ctx->unwind_count; - CTRL_Unwind unwind = df_query_cached_unwind_from_thread(thread); - Architecture arch = df_architecture_from_entity(thread); - U64 reg_size = regs_block_size_from_architecture(arch); - U64 thread_unwind_ip_vaddr = 0; - void *thread_unwind_regs_block = push_array(scratch.arena, U8, reg_size); - if(unwind.frames.count != 0) - { - thread_unwind_regs_block = unwind.frames.v[unwind_count%unwind.frames.count].regs; - thread_unwind_ip_vaddr = regs_rip_from_arch_block(arch, thread_unwind_regs_block); - } - - //- rjf: unpack module info & produce eval machine - DF_Entity *module = df_module_from_process_vaddr(process, thread_unwind_ip_vaddr); - U64 module_base = df_base_vaddr_from_module(module); - U64 tls_base = df_query_cached_tls_base_vaddr_from_process_root_rip(process, tls_root_vaddr, thread_unwind_ip_vaddr); - EVAL_Machine machine = {0}; - machine.u = (void *)thread->parent; - machine.arch = arch; - machine.memory_read = df_eval_memory_read; - machine.reg_data = thread_unwind_regs_block; - machine.reg_size = reg_size; - machine.module_base = &module_base; - machine.tls_base = &tls_base; - - //- rjf: lex & parse - EVAL_TokenArray tokens = eval_token_array_from_text(arena, string); - EVAL_ParseResult parse = eval_parse_expr_from_text_tokens(arena, parse_ctx, string, &tokens); - EVAL_ErrorList errors = parse.errors; - B32 parse_has_expr = (parse.expr != &eval_expr_nil); - B32 parse_is_type = (parse_has_expr && parse.expr->kind == EVAL_ExprKind_TypeIdent); - - //- rjf: produce IR tree & type - EVAL_IRTreeAndType ir_tree_and_type = {&eval_irtree_nil}; - if(parse_has_expr && errors.count == 0) - { - ir_tree_and_type = eval_irtree_and_type_from_expr(arena, parse_ctx->type_graph, parse_ctx->rdi, macro_map, parse.expr, &errors); - } - - //- rjf: get list of ops - EVAL_OpList op_list = {0}; - if(parse_has_expr && ir_tree_and_type.tree != &eval_irtree_nil) - { - eval_oplist_from_irtree(arena, ir_tree_and_type.tree, &op_list); - } - - //- rjf: get bytecode string - String8 bytecode = {0}; - if(parse_has_expr && parse_is_type == 0 && op_list.encoded_size != 0) - { - bytecode = eval_bytecode_from_oplist(arena, &op_list); - } - - //- rjf: evaluate - EVAL_Result eval = {0}; - if(bytecode.size != 0) - { - eval = eval_interpret(&machine, bytecode); - } - - //- rjf: fill result - DF_Eval result = zero_struct; - { - result.type_key = ir_tree_and_type.type_key; - result.mode = ir_tree_and_type.mode; - switch(result.mode) - { - default: - case EVAL_EvalMode_Value: - { - MemoryCopyArray(result.imm_u128, eval.value.u128); - }break; - case EVAL_EvalMode_Addr: - { - result.offset = eval.value.u64; - }break; - case EVAL_EvalMode_Reg: - { - U64 reg_off = (eval.value.u64 & 0x0000ffff) >> 0; - U64 reg_size = (eval.value.u64 & 0xffff0000) >> 16; - result.offset = reg_off; - (void)reg_size; - }break; - } - result.errors = errors; - if(EVAL_ResultCode_Good < eval.code && eval.code < EVAL_ResultCode_COUNT) - { - eval_error(arena, &result.errors, EVAL_ErrorKind_InterpretationError, 0, eval_result_code_display_strings[eval.code]); - } - } - - //- rjf: apply dynamic type overrides - if(parse.expr != 0 && parse.expr->kind != EVAL_ExprKind_Cast) - { - result = df_dynamically_typed_eval_from_eval(parse_ctx->type_graph, parse_ctx->rdi, ctrl_ctx, result); - } - - //- rjf: try to resolve basic integral values into symbols - if((result.mode == EVAL_EvalMode_Value || result.mode == EVAL_EvalMode_Reg) && parse.expr->kind != EVAL_ExprKind_Cast && - (tg_key_match(result.type_key, tg_key_basic(TG_Kind_S64)) || - tg_key_match(result.type_key, tg_key_basic(TG_Kind_U64)) || - tg_key_match(result.type_key, tg_key_basic(TG_Kind_S32)) || - tg_key_match(result.type_key, tg_key_basic(TG_Kind_U32)))) - { - U64 vaddr = result.imm_u64; - DF_Entity *module = df_module_from_process_vaddr(process, vaddr); - DI_Key dbgi_key = df_dbgi_key_from_module(module); - U64 voff = df_voff_from_vaddr(module, vaddr); - String8 symbol_name = df_symbol_name_from_dbgi_key_voff(scratch.arena, &dbgi_key, voff); - if(symbol_name.size != 0) - { - result.type_key = tg_cons_type_make(parse_ctx->type_graph, TG_Kind_Ptr, tg_key_basic(TG_Kind_Void), 0); - } - } - - scratch_end(scratch); - ProfEnd(); - return result; -} - -internal DF_Eval -df_value_mode_eval_from_eval(TG_Graph *graph, RDI_Parsed *rdi, DF_CtrlCtx *ctrl_ctx, DF_Eval eval) -{ - ProfBeginFunction(); - DF_Entity *thread = df_entity_from_handle(ctrl_ctx->thread); - DF_Entity *process = thread->parent; - switch(eval.mode) - { - //- rjf: no work to be done. already in value mode - default: - case EVAL_EvalMode_Value:{}break; - - //- rjf: address => resolve into value, if leaf - case EVAL_EvalMode_Addr: - { - TG_Key type_key = eval.type_key; - TG_Kind type_kind = tg_kind_from_key(type_key); - U64 type_byte_size = tg_byte_size_from_graph_rdi_key(graph, rdi, type_key); - if(!tg_key_match(type_key, tg_key_zero()) && type_byte_size <= sizeof(U64)*2) - { - Temp scratch = scratch_begin(0, 0); - Rng1U64 vaddr_range = r1u64(eval.offset, eval.offset + type_byte_size); - if(dim_1u64(vaddr_range) == type_byte_size) - { - CTRL_ProcessMemorySlice slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, vaddr_range, 0); - String8 data = slice.data; - MemoryZeroArray(eval.imm_u128); - MemoryCopy(eval.imm_u128, data.str, Min(data.size, sizeof(U64)*2)); - eval.mode = EVAL_EvalMode_Value; - - // rjf: mask&shift, for bitfields - if(type_kind == TG_Kind_Bitfield && type_byte_size <= sizeof(U64)) - { - TG_Type *type = tg_type_from_graph_rdi_key(scratch.arena, graph, rdi, type_key); - U64 valid_bits_mask = 0; - for(U64 idx = 0; idx < type->count; idx += 1) - { - valid_bits_mask |= (1<> type->off; - eval.imm_u64 = eval.imm_u64 & valid_bits_mask; - eval.type_key = type->direct_type_key; - } - - // rjf: manually sign-extend - switch(type_kind) - { - default: break; - case TG_Kind_S8: {eval.imm_s64 = (S64)*((S8 *)&eval.imm_u64);}break; - case TG_Kind_S16: {eval.imm_s64 = (S64)*((S16 *)&eval.imm_u64);}break; - case TG_Kind_S32: {eval.imm_s64 = (S64)*((S32 *)&eval.imm_u64);}break; - } - } - scratch_end(scratch); - } - }break; - - //- rjf: register => resolve into value - case EVAL_EvalMode_Reg: - { - TG_Key type_key = eval.type_key; - U64 type_byte_size = tg_byte_size_from_graph_rdi_key(graph, rdi, type_key); - U64 reg_off = eval.offset; - CTRL_Unwind unwind = df_query_cached_unwind_from_thread(thread); - if(unwind.frames.count != 0) - { - CTRL_UnwindFrame *frame = &unwind.frames.v[ctrl_ctx->unwind_count%unwind.frames.count]; - MemoryCopy(&eval.imm_u128[0], ((U8 *)frame->regs + reg_off), Min(type_byte_size, sizeof(U64)*2)); - } - eval.mode = EVAL_EvalMode_Value; - }break; - } - - ProfEnd(); - return eval; -} - -internal DF_Eval -df_dynamically_typed_eval_from_eval(TG_Graph *graph, RDI_Parsed *rdi, DF_CtrlCtx *ctrl_ctx, DF_Eval eval) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DF_Entity *thread = df_entity_from_handle(ctrl_ctx->thread); - Architecture arch = df_architecture_from_entity(thread); - DF_Entity *process = thread->parent; - U64 unwind_count = ctrl_ctx->unwind_count; - U64 thread_rip_vaddr = df_query_cached_rip_from_thread_unwind(thread, unwind_count); - DF_Entity *module = df_module_from_process_vaddr(process, thread_rip_vaddr); - TG_Key type_key = eval.type_key; - TG_Kind type_kind = tg_kind_from_key(type_key); - if(type_kind == TG_Kind_Ptr) - { - TG_Key ptee_type_key = tg_unwrapped_direct_from_graph_rdi_key(graph, rdi, type_key); - TG_Kind ptee_type_kind = tg_kind_from_key(ptee_type_key); - if(ptee_type_kind == TG_Kind_Struct || ptee_type_kind == TG_Kind_Class) - { - TG_Type *ptee_type = tg_type_from_graph_rdi_key(scratch.arena, graph, rdi, ptee_type_key); - B32 has_vtable = 0; - for(U64 idx = 0; idx < ptee_type->count; idx += 1) - { - if(ptee_type->members[idx].kind == TG_MemberKind_VirtualMethod) - { - has_vtable = 1; - break; - } - } - if(has_vtable) - { - U64 ptr_vaddr = eval.offset; - U64 addr_size = bit_size_from_arch(arch)/8; - CTRL_ProcessMemorySlice ptr_value_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, - r1u64(ptr_vaddr, ptr_vaddr+addr_size), 0); - String8 ptr_value_memory = ptr_value_slice.data; - if(ptr_value_memory.size >= addr_size) - { - U64 class_base_vaddr = 0; - MemoryCopy(&class_base_vaddr, ptr_value_memory.str, addr_size); - CTRL_ProcessMemorySlice vtable_base_ptr_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, - r1u64(class_base_vaddr, class_base_vaddr+addr_size), 0); - String8 vtable_base_ptr_memory = vtable_base_ptr_slice.data; - if(vtable_base_ptr_memory.size >= addr_size) - { - U64 vtable_vaddr = 0; - MemoryCopy(&vtable_vaddr, vtable_base_ptr_memory.str, addr_size); - U64 vtable_voff = df_voff_from_vaddr(module, vtable_vaddr); - U64 global_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_GlobalVMap, vtable_voff); - RDI_GlobalVariable *global_var = rdi_element_from_name_idx(rdi, GlobalVariables, global_idx); - if(global_var->link_flags & RDI_LinkFlag_TypeScoped) - { - RDI_UDT *udt = rdi_element_from_name_idx(rdi, UDTs, global_var->container_idx); - RDI_TypeNode *type = rdi_element_from_name_idx(rdi, TypeNodes, udt->self_type_idx); - TG_Key derived_type_key = tg_key_ext(tg_kind_from_rdi_type_kind(type->kind), (U64)udt->self_type_idx); - TG_Key ptr_to_derived_type_key = tg_cons_type_make(graph, TG_Kind_Ptr, derived_type_key, 0); - eval.type_key = ptr_to_derived_type_key; - } - } - } - } - } - } - scratch_end(scratch); - ProfEnd(); - return eval; -} - -internal DF_Eval -df_eval_from_eval_cfg_table(Arena *arena, DI_Scope *scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_Eval eval, DF_CfgTable *cfg) -{ - ProfBeginFunction(); - - //- rjf: apply view rules - for(DF_CfgVal *val = cfg->first_val; val != 0 && val != &df_g_nil_cfg_val; val = val->linear_next) - { - DF_CoreViewRuleSpec *spec = df_core_view_rule_spec_from_string(val->string); - if(spec->info.flags & DF_CoreViewRuleSpecInfoFlag_EvalResolution) - { - eval = spec->info.eval_resolution(arena, scope, ctrl_ctx, parse_ctx, macro_map, eval, val); - goto end_resolve; - } - } - end_resolve:; - ProfEnd(); - return eval; -} - -//////////////////////////////// -//~ rjf: Evaluation Views - -#if !defined(BLAKE2_H) -#define HAVE_SSE2 -#include "third_party/blake2/blake2.h" -#include "third_party/blake2/blake2b.c" -#endif - -internal DF_EvalViewKey -df_eval_view_key_make(U64 v0, U64 v1) -{ - DF_EvalViewKey v = {v0, v1}; - return v; -} - -internal DF_EvalViewKey -df_eval_view_key_from_string(String8 string) -{ - DF_EvalViewKey key = {0}; - blake2b((U8 *)&key.u64[0], sizeof(key), string.str, string.size, 0, 0); - return key; -} - -internal DF_EvalViewKey -df_eval_view_key_from_stringf(char *fmt, ...) -{ - Temp scratch = scratch_begin(0, 0); - va_list args; - va_start(args, fmt); - String8 string = push_str8fv(scratch.arena, fmt, args); - va_end(args); - DF_EvalViewKey key = df_eval_view_key_from_string(string); - scratch_end(scratch); - return key; -} - -internal B32 -df_eval_view_key_match(DF_EvalViewKey a, DF_EvalViewKey b) -{ - return MemoryMatchStruct(&a, &b); -} - -internal DF_EvalView * -df_eval_view_from_key(DF_EvalViewKey key) -{ - DF_EvalView *eval_view = &df_g_nil_eval_view; - { - U64 slot_idx = key.u64[1]%df_state->eval_view_cache.slots_count; - DF_EvalViewSlot *slot = &df_state->eval_view_cache.slots[slot_idx]; - for(DF_EvalView *v = slot->first; v != &df_g_nil_eval_view && v != 0; v = v->hash_next) - { - if(df_eval_view_key_match(key, v->key)) - { - eval_view = v; - break; - } - } - if(eval_view == &df_g_nil_eval_view) - { - eval_view = push_array(df_state->arena, DF_EvalView, 1); - DLLPushBack_NPZ(&df_g_nil_eval_view, slot->first, slot->last, eval_view, hash_next, hash_prev); - eval_view->key = key; - eval_view->arena = arena_alloc(); - df_expand_tree_table_init(eval_view->arena, &eval_view->expand_tree_table, 256); - eval_view->view_rule_table.slot_count = 64; - eval_view->view_rule_table.slots = push_array(eval_view->arena, DF_EvalViewRuleCacheSlot, eval_view->view_rule_table.slot_count); - } - } - return eval_view; -} - -//- rjf: key -> view rules - -internal void -df_eval_view_set_key_rule(DF_EvalView *eval_view, DF_ExpandKey key, String8 view_rule_string) -{ - //- rjf: key -> hash * slot idx * slot - String8 key_string = str8_struct(&key); - U64 hash = df_hash_from_string(key_string); - U64 slot_idx = hash%eval_view->view_rule_table.slot_count; - DF_EvalViewRuleCacheSlot *slot = &eval_view->view_rule_table.slots[slot_idx]; - - //- rjf: slot -> existing node - DF_EvalViewRuleCacheNode *existing_node = 0; - for(DF_EvalViewRuleCacheNode *n = slot->first; n != 0; n = n->hash_next) - { - if(df_expand_key_match(n->key, key)) - { - existing_node = n; - break; - } - } - - //- rjf: existing node * new node -> node - DF_EvalViewRuleCacheNode *node = existing_node; - if(node == 0) - { - node = push_array(eval_view->arena, DF_EvalViewRuleCacheNode, 1); - DLLPushBack_NP(slot->first, slot->last, node, hash_next, hash_prev); - node->key = key; - node->buffer_cap = 512; - node->buffer = push_array(eval_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); - } -} - -internal String8 -df_eval_view_rule_from_key(DF_EvalView *eval_view, DF_ExpandKey key) -{ - String8 result = {0}; - - //- rjf: key -> hash * slot idx * slot - String8 key_string = str8_struct(&key); - U64 hash = df_hash_from_string(key_string); - U64 slot_idx = hash%eval_view->view_rule_table.slot_count; - DF_EvalViewRuleCacheSlot *slot = &eval_view->view_rule_table.slots[slot_idx]; - - //- rjf: slot -> existing node - DF_EvalViewRuleCacheNode *existing_node = 0; - for(DF_EvalViewRuleCacheNode *n = slot->first; n != 0; n = n->hash_next) - { - if(df_expand_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; -} - -//////////////////////////////// -//~ rjf: Evaluation View Visualization & Interaction - -//- rjf: evaluation value string builder helpers - -internal String8 -df_string_from_ascii_value(Arena *arena, U8 val) -{ - String8 result = {0}; - switch(val) - { - case 0x00:{result = str8_lit("\\0");}break; - case 0x07:{result = str8_lit("\\a");}break; - case 0x08:{result = str8_lit("\\b");}break; - case 0x0c:{result = str8_lit("\\f");}break; - case 0x0a:{result = str8_lit("\\n");}break; - case 0x0d:{result = str8_lit("\\r");}break; - case 0x09:{result = str8_lit("\\t");}break; - case 0x0b:{result = str8_lit("\\v");}break; - case 0x3f:{result = str8_lit("\\?");}break; - case '"': {result = str8_lit("\\\"");}break; - case '\'':{result = str8_lit("\\'");}break; - case '\\':{result = str8_lit("\\\\");}break; - default: - if(32 <= val && val < 255) - { - result = push_str8f(arena, "%c", val); - }break; - } - return result; -} - -internal String8 -df_string_from_simple_typed_eval(Arena *arena, TG_Graph *graph, RDI_Parsed *rdi, DF_EvalVizStringFlags flags, U32 radix, DF_Eval eval) -{ - ProfBeginFunction(); - String8 result = {0}; - TG_Key type_key = tg_unwrapped_from_graph_rdi_key(graph, rdi, eval.type_key); - TG_Kind type_kind = tg_kind_from_key(type_key); - U64 type_byte_size = tg_byte_size_from_graph_rdi_key(graph, rdi, type_key); - U8 digit_group_separator = 0; - if(!(flags & DF_EvalVizStringFlag_ReadOnlyDisplayRules)) - { - digit_group_separator = 0; - } - switch(type_kind) - { - default:{}break; - - case TG_Kind_Handle: - { - U64 min_digits = (radix == 16) ? type_byte_size*2 : 0; - result = str8_from_s64(arena, eval.imm_s64, radix, 0, digit_group_separator); - }break; - - case TG_Kind_Char8: - case TG_Kind_Char16: - case TG_Kind_Char32: - case TG_Kind_UChar8: - case TG_Kind_UChar16: - case TG_Kind_UChar32: - { - String8 char_str = df_string_from_ascii_value(arena, eval.imm_s64); - if(char_str.size != 0) - { - if(flags & DF_EvalVizStringFlag_ReadOnlyDisplayRules) - { - String8 imm_string = str8_from_s64(arena, eval.imm_s64, radix, 0, digit_group_separator); - result = push_str8f(arena, "'%S' (%S)", char_str, imm_string); - } - else - { - result = push_str8f(arena, "'%S'", char_str); - } - } - else - { - result = str8_from_s64(arena, eval.imm_s64, radix, 0, digit_group_separator); - } - }break; - - case TG_Kind_S8: - case TG_Kind_S16: - case TG_Kind_S32: - case TG_Kind_S64: - { - U64 min_digits = (radix == 16) ? type_byte_size*2 : 0; - result = str8_from_s64(arena, eval.imm_s64, radix, 0, digit_group_separator); - }break; - - case TG_Kind_U8: - case TG_Kind_U16: - case TG_Kind_U32: - case TG_Kind_U64: - { - U64 min_digits = (radix == 16) ? type_byte_size*2 : 0; - result = str8_from_u64(arena, eval.imm_u64, radix, min_digits, digit_group_separator); - }break; - - case TG_Kind_U128: - { - Temp scratch = scratch_begin(&arena, 1); - U64 min_digits = (radix == 16) ? type_byte_size*2 : 0; - String8 upper64 = str8_from_u64(scratch.arena, eval.imm_u128[0], radix, min_digits, digit_group_separator); - String8 lower64 = str8_from_u64(scratch.arena, eval.imm_u128[1], radix, min_digits, digit_group_separator); - result = push_str8f(arena, "%S:%S", upper64, lower64); - scratch_end(scratch); - }break; - - case TG_Kind_F32: {result = push_str8f(arena, "%f", eval.imm_f32);}break; - case TG_Kind_F64: {result = push_str8f(arena, "%f", eval.imm_f64);}break; - case TG_Kind_Bool:{result = push_str8f(arena, "%s", eval.imm_u64 ? "true" : "false");}break; - case TG_Kind_Ptr: {result = push_str8f(arena, "0x%I64x", eval.imm_u64);}break; - case TG_Kind_LRef:{result = push_str8f(arena, "0x%I64x", eval.imm_u64);}break; - case TG_Kind_RRef:{result = push_str8f(arena, "0x%I64x", eval.imm_u64);}break; - case TG_Kind_Function:{result = push_str8f(arena, "0x%I64x", eval.imm_u64);}break; - - case TG_Kind_Enum: - { - Temp scratch = scratch_begin(&arena, 1); - TG_Type *type = tg_type_from_graph_rdi_key(scratch.arena, graph, rdi, type_key); - String8 constant_name = {0}; - for(U64 val_idx = 0; val_idx < type->count; val_idx += 1) - { - if(eval.imm_u64 == type->enum_vals[val_idx].val) - { - constant_name = type->enum_vals[val_idx].name; - break; - } - } - if(flags & DF_EvalVizStringFlag_ReadOnlyDisplayRules) - { - if(constant_name.size != 0) - { - result = push_str8f(arena, "0x%I64x (%S)", eval.imm_u64, constant_name); - } - else - { - result = push_str8f(arena, "0x%I64x (%I64u)", eval.imm_u64, eval.imm_u64); - } - } - else if(constant_name.size != 0) - { - result = push_str8_copy(arena, constant_name); - } - else - { - result = push_str8f(arena, "0x%I64x (%I64u)", eval.imm_u64, eval.imm_u64); - } - scratch_end(scratch); - }break; - } - - ProfEnd(); - return result; -} - -//- rjf: writing values back to child processes - -internal B32 -df_commit_eval_value(TG_Graph *graph, RDI_Parsed *rdi, DF_CtrlCtx *ctrl_ctx, DF_Eval dst_eval, DF_Eval src_eval) -{ - B32 result = 0; - Temp scratch = scratch_begin(0, 0); - - //- rjf: unpack arguments - DF_Entity *thread = df_entity_from_handle(ctrl_ctx->thread); - DF_Entity *process = thread->parent; - TG_Key dst_type_key = dst_eval.type_key; - TG_Key src_type_key = src_eval.type_key; - TG_Kind dst_type_kind = tg_kind_from_key(dst_type_key); - TG_Kind src_type_kind = tg_kind_from_key(src_type_key); - U64 dst_type_byte_size = tg_byte_size_from_graph_rdi_key(graph, rdi, dst_type_key); - U64 src_type_byte_size = tg_byte_size_from_graph_rdi_key(graph, rdi, src_type_key); - - //- rjf: get commit data based on destination type - String8 commit_data = {0}; - if(src_eval.errors.count == 0) - { - result = 1; - switch(dst_type_kind) - { - default: - { - // NOTE(rjf): not supported - result = 0; - }break; - - //- rjf: pointers - case TG_Kind_Ptr: - case TG_Kind_LRef: - if((TG_Kind_Char8 <= src_type_kind && src_type_kind <= TG_Kind_Bool) || src_type_kind == TG_Kind_Ptr) - { - DF_Eval value_eval = df_value_mode_eval_from_eval(graph, rdi, ctrl_ctx, src_eval); - commit_data = str8((U8 *)&value_eval.imm_u64, dst_type_byte_size); - commit_data = push_str8_copy(scratch.arena, commit_data); - }break; - - //- rjf: integers - case TG_Kind_Char8: - case TG_Kind_Char16: - case TG_Kind_Char32: - case TG_Kind_S8: - case TG_Kind_S16: - case TG_Kind_S32: - case TG_Kind_S64: - case TG_Kind_UChar8: - case TG_Kind_UChar16: - case TG_Kind_UChar32: - case TG_Kind_U8: - case TG_Kind_U16: - case TG_Kind_U32: - case TG_Kind_U64: - case TG_Kind_Bool: - if(TG_Kind_Char8 <= src_type_kind && src_type_kind <= TG_Kind_Bool) - { - DF_Eval value_eval = df_value_mode_eval_from_eval(graph, rdi, ctrl_ctx, src_eval); - commit_data = str8((U8 *)&value_eval.imm_u64, dst_type_byte_size); - commit_data = push_str8_copy(scratch.arena, commit_data); - }break; - - //- rjf: float32s - case TG_Kind_F32: - if((TG_Kind_Char8 <= src_type_kind && src_type_kind <= TG_Kind_Bool) || - src_type_kind == TG_Kind_F32 || - src_type_kind == TG_Kind_F64) - { - F32 value = 0; - DF_Eval value_eval = df_value_mode_eval_from_eval(graph, rdi, ctrl_ctx, src_eval); - switch(src_type_kind) - { - case TG_Kind_F32:{value = value_eval.imm_f32;}break; - case TG_Kind_F64:{value = (F32)value_eval.imm_f64;}break; - default:{value = (F32)value_eval.imm_s64;}break; - } - commit_data = str8((U8 *)&value, sizeof(F32)); - commit_data = push_str8_copy(scratch.arena, commit_data); - }break; - - //- rjf: float64s - case TG_Kind_F64: - if((TG_Kind_Char8 <= src_type_kind && src_type_kind <= TG_Kind_Bool) || - src_type_kind == TG_Kind_F32 || - src_type_kind == TG_Kind_F64) - { - F64 value = 0; - DF_Eval value_eval = df_value_mode_eval_from_eval(graph, rdi, ctrl_ctx, src_eval); - switch(src_type_kind) - { - case TG_Kind_F32:{value = (F64)value_eval.imm_f32;}break; - case TG_Kind_F64:{value = value_eval.imm_f64;}break; - default:{value = (F64)value_eval.imm_s64;}break; - } - commit_data = str8((U8 *)&value, sizeof(F64)); - commit_data = push_str8_copy(scratch.arena, commit_data); - }break; - - //- rjf: enums - case TG_Kind_Enum: - if(TG_Kind_Char8 <= src_type_kind && src_type_kind <= TG_Kind_Bool) - { - DF_Eval value_eval = df_value_mode_eval_from_eval(graph, rdi, ctrl_ctx, src_eval); - commit_data = str8((U8 *)&value_eval.imm_u64, dst_type_byte_size); - commit_data = push_str8_copy(scratch.arena, commit_data); - }break; - } - } - - //- rjf: commit - if(result && commit_data.size != 0) - { - switch(dst_eval.mode) - { - default:{}break; - case EVAL_EvalMode_Addr: - { - ctrl_process_write(process->ctrl_machine_id, process->ctrl_handle, r1u64(dst_eval.offset, dst_eval.offset+commit_data.size), commit_data.str); - }break; - case EVAL_EvalMode_Reg: - { - CTRL_Unwind unwind = df_query_cached_unwind_from_thread(thread); - Architecture arch = df_architecture_from_entity(thread); - U64 reg_block_size = regs_block_size_from_architecture(arch); - if(unwind.frames.count != 0 && - (0 <= dst_eval.offset && dst_eval.offset+commit_data.size < reg_block_size)) - { - void *new_regs = push_array(scratch.arena, U8, reg_block_size); - MemoryCopy(new_regs, unwind.frames.v[0].regs, reg_block_size); - MemoryCopy((U8 *)new_regs+dst_eval.offset, commit_data.str, commit_data.size); - result = ctrl_thread_write_reg_block(thread->ctrl_machine_id, thread->ctrl_handle, new_regs); - } - }break; - } - } - - scratch_end(scratch); - return result; -} - -//- rjf: type helpers - -internal TG_MemberArray -df_filtered_data_members_from_members_cfg_table(Arena *arena, TG_MemberArray members, DF_CfgTable *cfg) -{ - DF_CfgVal *only = df_cfg_val_from_string(cfg, str8_lit("only")); - DF_CfgVal *omit = df_cfg_val_from_string(cfg, str8_lit("omit")); - TG_MemberArray filtered_members = members; - if(only != &df_g_nil_cfg_val || omit != &df_g_nil_cfg_val) - { - Temp scratch = scratch_begin(&arena, 1); - typedef struct DF_TypeMemberLooseNode DF_TypeMemberLooseNode; - struct DF_TypeMemberLooseNode - { - DF_TypeMemberLooseNode *next; - TG_Member *member; - }; - DF_TypeMemberLooseNode *first_member = 0; - DF_TypeMemberLooseNode *last_member = 0; - U64 member_count = 0; - MemoryZeroStruct(&filtered_members); - for(U64 idx = 0; idx < members.count; idx += 1) - { - // rjf: check if included by 'only's - B32 is_included = 1; - for(DF_CfgNode *r = only->first; r != &df_g_nil_cfg_node; r = r->next) - { - is_included = 0; - for(DF_CfgNode *name_node = r->first; name_node != &df_g_nil_cfg_node; name_node = name_node->next) - { - String8 name = name_node->string; - if(str8_match(members.v[idx].name, name, 0)) - { - is_included = 1; - goto end_inclusion_check; - } - } - } - end_inclusion_check:; - - // rjf: remove if excluded by 'omit's - for(DF_CfgNode *r = omit->first; r != &df_g_nil_cfg_node; r = r->next) - { - for(DF_CfgNode *name_node = r->first; name_node != &df_g_nil_cfg_node; name_node = name_node->next) - { - String8 name = name_node->string; - if(str8_match(members.v[idx].name, name, 0)) - { - is_included = 0; - goto end_exclusion_check; - } - } - } - end_exclusion_check:; - - // rjf: push if included - if(is_included) - { - DF_TypeMemberLooseNode *n = push_array(scratch.arena, DF_TypeMemberLooseNode, 1); - n->member = &members.v[idx]; - SLLQueuePush(first_member, last_member, n); - member_count += 1; - } - } - - // rjf: bake - { - filtered_members.count = member_count; - filtered_members.v = push_array_no_zero(arena, TG_Member, filtered_members.count); - U64 idx = 0; - for(DF_TypeMemberLooseNode *n = first_member; n != 0; n = n->next, idx += 1) - { - MemoryCopyStruct(&filtered_members.v[idx], n->member); - filtered_members.v[idx].name = push_str8_copy(arena, filtered_members.v[idx].name); - } - } - scratch_end(scratch); - } - return filtered_members; -} - -internal DF_EvalLinkBaseChunkList -df_eval_link_base_chunk_list_from_eval(Arena *arena, TG_Graph *graph, RDI_Parsed *rdi, TG_Key link_member_type_key, U64 link_member_off, DF_CtrlCtx *ctrl_ctx, DF_Eval eval, U64 cap) -{ - DF_EvalLinkBaseChunkList list = {0}; - for(DF_Eval base_eval = eval, last_eval = zero_struct; list.count < cap;) - { - // rjf: check this ptr's validity - if(base_eval.offset == 0 || (base_eval.offset == last_eval.offset && base_eval.mode == last_eval.mode)) - { - break; - } - - // rjf: gather - { - DF_EvalLinkBaseChunkNode *chunk = list.last; - if(chunk == 0 || chunk->count == ArrayCount(chunk->b)) - { - chunk = push_array_no_zero(arena, DF_EvalLinkBaseChunkNode, 1); - chunk->next = 0; - chunk->count = 0; - SLLQueuePush(list.first, list.last, chunk); - } - chunk->b[chunk->count].mode = base_eval.mode; - chunk->b[chunk->count].offset = base_eval.offset; - chunk->count += 1; - list.count += 1; - } - - // rjf: grab link member - DF_Eval link_member_eval = - { - link_member_type_key, - base_eval.mode, - base_eval.offset + link_member_off, - }; - DF_Eval link_member_value_eval = df_value_mode_eval_from_eval(graph, rdi, ctrl_ctx, link_member_eval); - - // rjf: advance to next link - last_eval = base_eval; - base_eval.mode = EVAL_EvalMode_Addr; - base_eval.offset = link_member_value_eval.imm_u64; - } - return list; -} - -internal DF_EvalLinkBase -df_eval_link_base_from_chunk_list_index(DF_EvalLinkBaseChunkList *list, U64 idx) -{ - DF_EvalLinkBase result = zero_struct; - U64 scan_idx = 0; - for(DF_EvalLinkBaseChunkNode *chunk = list->first; chunk != 0; chunk = chunk->next) - { - U64 chunk_idx_opl = scan_idx+chunk->count; - if(scan_idx <= idx && idx < chunk_idx_opl) - { - result = chunk->b[idx - scan_idx]; - } - scan_idx = chunk_idx_opl; - } - return result; -} - -internal DF_EvalLinkBaseArray -df_eval_link_base_array_from_chunk_list(Arena *arena, DF_EvalLinkBaseChunkList *chunks) -{ - DF_EvalLinkBaseArray array = {0}; - array.count = chunks->count; - array.v = push_array_no_zero(arena, DF_EvalLinkBase, array.count); - U64 idx = 0; - for(DF_EvalLinkBaseChunkNode *n = chunks->first; n != 0; n = n->next) - { - MemoryCopy(array.v + idx, n->b, n->count * sizeof(DF_EvalLinkBase)); - idx += n->count; - } - return array; -} - -//- rjf: viz block collection building - -internal DF_EvalVizBlock * -df_eval_viz_block_begin(Arena *arena, DF_EvalVizBlockKind kind, DF_ExpandKey parent_key, DF_ExpandKey key, S32 depth) -{ - DF_EvalVizBlockNode *n = push_array(arena, DF_EvalVizBlockNode, 1); - n->v.kind = kind; - n->v.parent_key = parent_key; - n->v.key = key; - n->v.depth = depth; - return &n->v; -} - -internal DF_EvalVizBlock * -df_eval_viz_block_split_and_continue(Arena *arena, DF_EvalVizBlockList *list, DF_EvalVizBlock *split_block, U64 split_idx) -{ - U64 total_count = split_block->semantic_idx_range.max; - split_block->visual_idx_range.max = split_block->semantic_idx_range.max = split_idx; - df_eval_viz_block_end(list, split_block); - DF_EvalVizBlock *continue_block = df_eval_viz_block_begin(arena, split_block->kind, split_block->parent_key, split_block->key, split_block->depth); - continue_block->eval = split_block->eval; - continue_block->string = split_block->string; - continue_block->member = split_block->member; - continue_block->visual_idx_range = continue_block->semantic_idx_range = r1u64(split_idx+1, total_count); - continue_block->fzy_backing_items = split_block->fzy_backing_items; - continue_block->fzy_target = split_block->fzy_target; - continue_block->cfg_table = split_block->cfg_table; - continue_block->link_member_type_key = split_block->link_member_type_key; - continue_block->link_member_off = split_block->link_member_off; - return continue_block; -} - -internal void -df_eval_viz_block_end(DF_EvalVizBlockList *list, DF_EvalVizBlock *block) -{ - DF_EvalVizBlockNode *n = CastFromMember(DF_EvalVizBlockNode, v, block); - SLLQueuePush(list->first, list->last, n); - list->count += 1; - list->total_visual_row_count += dim_1u64(block->visual_idx_range); - list->total_semantic_row_count += dim_1u64(block->semantic_idx_range); -} - -internal void -df_append_viz_blocks_for_parent__rec(Arena *arena, DI_Scope *scope, DF_EvalView *eval_view, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_ExpandKey parent_key, DF_ExpandKey key, String8 string, DF_Eval eval, TG_Member *opt_member, DF_CfgTable *cfg_table, S32 depth, DF_EvalVizBlockList *list_out) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(&arena, 1); - - ////////////////////////////// - //- rjf: determine if this key is expanded - // - DF_ExpandNode *node = df_expand_node_from_key(&eval_view->expand_tree_table, key); - B32 parent_is_expanded = (node != 0 && node->expanded && !tg_key_match(tg_key_zero(), eval.type_key)); - - ////////////////////////////// - //- rjf: apply view rules & resolve eval - // - eval = df_dynamically_typed_eval_from_eval(parse_ctx->type_graph, parse_ctx->rdi, ctrl_ctx, eval); - eval = df_eval_from_eval_cfg_table(arena, scope, ctrl_ctx, parse_ctx, macro_map, eval, cfg_table); - - ////////////////////////////// - //- rjf: unpack eval - // - TG_Key eval_type_key = tg_unwrapped_from_graph_rdi_key(parse_ctx->type_graph, parse_ctx->rdi, eval.type_key); - TG_Kind eval_type_kind = tg_kind_from_key(eval_type_key); - String8 eval_string = push_str8_copy(arena, string); - - ////////////////////////////// - //- rjf: make and push block for root - // - { - DF_EvalVizBlock *block = df_eval_viz_block_begin(arena, DF_EvalVizBlockKind_Root, parent_key, key, depth); - block->eval = eval; - block->cfg_table = *cfg_table; - block->string = eval_string; - block->visual_idx_range = r1u64(key.child_num-1, key.child_num+0); - block->semantic_idx_range = r1u64(key.child_num-1, key.child_num+0); - if(opt_member != 0) - { - block->member = tg_member_copy(arena, opt_member); - } - df_eval_viz_block_end(list_out, block); - } - - ////////////////////////////// - //- rjf: (pointers) extract type & info to use for members and/or arrays - // - DF_Eval udt_eval = eval; - DF_Eval arr_eval = eval; - DF_Eval ptr_eval = zero_struct; - TG_Kind udt_type_kind = eval_type_kind; - TG_Kind arr_type_kind = eval_type_kind; - TG_Kind ptr_type_kind = TG_Kind_Null; - if(eval_type_kind == TG_Kind_Ptr || eval_type_kind == TG_Kind_LRef || eval_type_kind == TG_Kind_RRef) - { - TG_Key direct_type_key = tg_ptee_from_graph_rdi_key(parse_ctx->type_graph, parse_ctx->rdi, eval_type_key); - TG_Kind direct_type_kind = tg_kind_from_key(direct_type_key); - DF_Eval ptr_val_eval = df_value_mode_eval_from_eval(parse_ctx->type_graph, parse_ctx->rdi, ctrl_ctx, eval); - - // rjf: ptrs to udts - if(parent_is_expanded && - (direct_type_kind == TG_Kind_Struct || - direct_type_kind == TG_Kind_Union || - direct_type_kind == TG_Kind_Class || - direct_type_kind == TG_Kind_IncompleteStruct || - direct_type_kind == TG_Kind_IncompleteUnion || - direct_type_kind == TG_Kind_IncompleteClass)) - { - udt_eval.type_key = direct_type_key; - udt_eval.mode = EVAL_EvalMode_Addr; - udt_eval.offset = ptr_val_eval.imm_u64; - udt_type_kind = tg_kind_from_key(direct_type_key); - } - - // rjf: ptrs to arrays - if(direct_type_kind == TG_Kind_Array) - { - arr_eval.type_key = direct_type_key; - arr_eval.mode = EVAL_EvalMode_Addr; - arr_eval.offset = ptr_val_eval.imm_u64; - arr_type_kind = tg_kind_from_key(direct_type_key); - } - - // rjf: ptrs to ptrs - if(direct_type_kind == TG_Kind_Ptr || direct_type_kind == TG_Kind_LRef || direct_type_kind == TG_Kind_RRef) - { - ptr_eval.type_key = direct_type_key; - ptr_eval.mode = EVAL_EvalMode_Addr; - ptr_eval.offset = ptr_val_eval.imm_u64; - ptr_type_kind = tg_kind_from_key(direct_type_key); - } - } - - ////////////////////////////// - //- rjf: determine rule for building expansion children - // - typedef enum DF_EvalVizExpandRule - { - DF_EvalVizExpandRule_Default, - DF_EvalVizExpandRule_List, - DF_EvalVizExpandRule_ViewRule, - } - DF_EvalVizExpandRule; - DF_EvalVizExpandRule expand_rule = DF_EvalVizExpandRule_Default; - DF_CoreViewRuleSpec *expand_view_rule_spec = &df_g_nil_core_view_rule_spec; - DF_CfgVal *expand_view_rule_cfg = &df_g_nil_cfg_val; - String8 list_next_link_member_name = {0}; - { - //- rjf: look for view rules which have their own custom viz block building rules - if(expand_rule == DF_EvalVizExpandRule_Default && parent_is_expanded) - { - for(DF_CfgVal *val = cfg_table->first_val; val != 0 && val != &df_g_nil_cfg_val; val = val->linear_next) - { - DF_CoreViewRuleSpec *spec = df_core_view_rule_spec_from_string(val->string); - if(str8_match(spec->info.string, str8_lit("list"), 0) || - str8_match(spec->info.string, str8_lit("omit"), 0) || - str8_match(spec->info.string, str8_lit("only"), 0)) - { - // TODO(rjf): "list" view rule needs to be formally moved into the visualization - // engine hooks when the system is mature enough to support it - // also "omit", "only" - continue; - } - if(spec->info.flags & DF_CoreViewRuleSpecInfoFlag_VizBlockProd) - { - expand_rule = DF_EvalVizExpandRule_ViewRule; - expand_view_rule_spec = spec; - expand_view_rule_cfg = val; - break; - } - } - } - - //- rjf: get linked list viz view rule info for structs - if(expand_rule == DF_EvalVizExpandRule_Default && - parent_is_expanded && - (udt_type_kind == TG_Kind_Struct || - udt_type_kind == TG_Kind_Union || - udt_type_kind == TG_Kind_Class)) - { - DF_CfgVal *list_cfg = df_cfg_val_from_string(cfg_table, str8_lit("list")); - if(list_cfg != &df_g_nil_cfg_val) - { - list_next_link_member_name = list_cfg->first->first->string; - expand_rule = DF_EvalVizExpandRule_List; - } - } - } - - ////////////////////////////// - //- rjf: (all) descend to make blocks according to lens - // - if(parent_is_expanded && expand_rule == DF_EvalVizExpandRule_ViewRule && - expand_view_rule_spec != &df_g_nil_core_view_rule_spec && - expand_view_rule_cfg != &df_g_nil_cfg_val) - ProfScope("build viz blocks for lens") - { - expand_view_rule_spec->info.viz_block_prod(arena, scope, ctrl_ctx, parse_ctx, macro_map, eval_view, eval, string, cfg_table, parent_key, key, depth+1, expand_view_rule_cfg->last, list_out); - } - - ////////////////////////////// - //- rjf: (structs, unions, classes) descend to members & make block(s), normally - // - if(parent_is_expanded && expand_rule == DF_EvalVizExpandRule_Default && - (udt_type_kind == TG_Kind_Struct || - udt_type_kind == TG_Kind_Union || - udt_type_kind == TG_Kind_Class)) - ProfScope("build viz blocks for UDT members") - { - //- rjf: type -> filtered data members - TG_MemberArray data_members = tg_data_members_from_graph_rdi_key(scratch.arena, parse_ctx->type_graph, parse_ctx->rdi, udt_eval.type_key); - TG_MemberArray filtered_data_members = df_filtered_data_members_from_members_cfg_table(scratch.arena, data_members, cfg_table); - - //- rjf: build blocks for all members, split by sub-expansions - DF_EvalVizBlock *last_vb = df_eval_viz_block_begin(arena, DF_EvalVizBlockKind_Members, key, df_expand_key_make(df_hash_from_expand_key(key), 0), depth+1); - { - last_vb->eval = udt_eval; - last_vb->string = eval_string; - last_vb->cfg_table = *cfg_table; - last_vb->visual_idx_range = last_vb->semantic_idx_range = r1u64(0, filtered_data_members.count); - } - for(DF_ExpandNode *child = node->first; child != 0; child = child->next) - { - // rjf: unpack expansion info; skip out-of-bounds splits - U64 child_num = child->key.child_num; - U64 child_idx = child_num-1; - if(child_idx >= filtered_data_members.count) - { - continue; - } - - // rjf: form split: truncate & complete last block; begin next block - last_vb = df_eval_viz_block_split_and_continue(arena, list_out, last_vb, child_idx); - - // rjf: recurse for sub-expansion - { - DF_CfgTable child_cfg = *cfg_table; - { - String8 view_rule_string = df_eval_view_rule_from_key(eval_view, df_expand_key_make(df_hash_from_expand_key(key), child_num)); - child_cfg = df_cfg_table_from_inheritance(arena, cfg_table); - if(view_rule_string.size != 0) - { - df_cfg_table_push_unparsed_string(arena, &child_cfg, view_rule_string, DF_CfgSrc_User); - } - } - TG_Member *member = &filtered_data_members.v[child_idx]; - DF_Eval child_eval = zero_struct; - { - child_eval.type_key = member->type_key; - child_eval.mode = udt_eval.mode; - child_eval.offset = udt_eval.offset + member->off; - } - df_append_viz_blocks_for_parent__rec(arena, scope, eval_view, ctrl_ctx, parse_ctx, macro_map, key, child->key, member->name, child_eval, member, &child_cfg, depth+1, list_out); - } - } - df_eval_viz_block_end(list_out, last_vb); - } - - ////////////////////////////// - //- rjf: (enums) descend to members & make block(s) - // - if(parent_is_expanded && expand_rule == DF_EvalVizExpandRule_Default && - udt_eval.mode == EVAL_EvalMode_NULL && - udt_type_kind == TG_Kind_Enum) - ProfScope("build viz blocks for UDT type-eval enums") - { - //- rjf: type -> full type info - TG_Type *type = tg_type_from_graph_rdi_key(scratch.arena, parse_ctx->type_graph, parse_ctx->rdi, udt_eval.type_key); - - //- rjf: build block for all members (cannot be expanded) - DF_EvalVizBlock *last_vb = df_eval_viz_block_begin(arena, DF_EvalVizBlockKind_EnumMembers, key, df_expand_key_make(df_hash_from_expand_key(key), 0), depth+1); - { - last_vb->eval = udt_eval; - last_vb->string = eval_string; - last_vb->cfg_table = *cfg_table; - last_vb->visual_idx_range = last_vb->semantic_idx_range = r1u64(0, type->count); - } - df_eval_viz_block_end(list_out, last_vb); - } - - ////////////////////////////// - //- rjf: (structs, unions, classes) descend to members & make block(s), with linked list view - // - if(parent_is_expanded && expand_rule == DF_EvalVizExpandRule_List && - (udt_type_kind == TG_Kind_Struct || - udt_type_kind == TG_Kind_Union || - udt_type_kind == TG_Kind_Class)) - ProfScope("(structs, unions, classes) descend to members & make block(s), with linked list view") - { - //- rjf: type -> data members - TG_MemberArray data_members = tg_data_members_from_graph_rdi_key(scratch.arena, parse_ctx->type_graph, parse_ctx->rdi, udt_eval.type_key); - - //- rjf: find link member - TG_Member *link_member = 0; - TG_Kind link_member_type_kind = TG_Kind_Null; - TG_Key link_member_ptee_type_key = zero_struct; - for(U64 idx = 0; idx < data_members.count; idx += 1) - { - TG_Member *mem = &data_members.v[idx]; - if(str8_match(mem->name, list_next_link_member_name, 0)) - { - link_member = mem; - link_member_type_kind = tg_kind_from_key(link_member->type_key); - link_member_ptee_type_key = tg_ptee_from_graph_rdi_key(parse_ctx->type_graph, parse_ctx->rdi, link_member->type_key); - break; - } - } - - //- rjf: check if link member is good - B32 link_member_is_good = 1; - if(link_member == 0 || - link_member_type_kind != TG_Kind_Ptr || - !tg_key_match(link_member_ptee_type_key, udt_eval.type_key)) - { - link_member_is_good = 0; - } - - //- rjf: gather link bases - DF_EvalLinkBaseChunkList link_bases = {0}; - if(link_member_is_good) - { - link_bases = df_eval_link_base_chunk_list_from_eval(scratch.arena, parse_ctx->type_graph, parse_ctx->rdi, link_member->type_key, link_member->off, ctrl_ctx, udt_eval, 512); - } - - //- rjf: build blocks for all links, split by sub-expansions - if(link_member_is_good) - { - DF_EvalVizBlock *last_vb = df_eval_viz_block_begin(arena, DF_EvalVizBlockKind_Links, key, df_expand_key_make(df_hash_from_expand_key(key), 0), depth+1); - { - last_vb->eval = udt_eval; - last_vb->string = eval_string; - last_vb->cfg_table = *cfg_table; - last_vb->link_member_type_key = link_member->type_key; - last_vb->link_member_off = link_member->off; - last_vb->visual_idx_range = r1u64(0, link_bases.count); - last_vb->semantic_idx_range = r1u64(0, link_bases.count); - } - for(DF_ExpandNode *child = node->first; child != 0; child = child->next) - { - // rjf: unpack expansion info; skip out-of-bounds splits - U64 child_num = child->key.child_num; - U64 child_idx = child_num-1; - if(child_idx >= link_bases.count) - { - continue; - } - - // rjf: form split: truncate & complete last block; begin next block - last_vb = df_eval_viz_block_split_and_continue(arena, list_out, last_vb, child_idx); - - // rjf: find mode/offset of this link - DF_EvalLinkBase link_base = df_eval_link_base_from_chunk_list_index(&link_bases, child_idx); - - // rjf: recurse for sub-expansion - { - DF_CfgTable child_cfg = *cfg_table; - { - String8 view_rule_string = df_eval_view_rule_from_key(eval_view, df_expand_key_make(df_hash_from_expand_key(key), child_num)); - child_cfg = df_cfg_table_from_inheritance(arena, cfg_table); - if(view_rule_string.size != 0) - { - df_cfg_table_push_unparsed_string(arena, &child_cfg, view_rule_string, DF_CfgSrc_User); - } - } - DF_Eval child_eval = zero_struct; - { - child_eval.type_key = udt_eval.type_key; - child_eval.mode = link_base.mode; - child_eval.offset = link_base.offset; - } - df_append_viz_blocks_for_parent__rec(arena, scope, eval_view, ctrl_ctx, parse_ctx, macro_map, key, child->key, push_str8f(arena, "[%I64u]", child_idx), child_eval, 0, &child_cfg, depth+1, list_out); - } - } - df_eval_viz_block_end(list_out, last_vb); - } - } - - ////////////////////////////// - //- rjf: (arrays) descend to elements & make block(s), normally - // - if(parent_is_expanded && expand_rule == DF_EvalVizExpandRule_Default && - arr_type_kind == TG_Kind_Array) - ProfScope("(arrays) descend to elements & make block(s)") - { - //- rjf: unpack array type info - TG_Type *array_type = tg_type_from_graph_rdi_key(scratch.arena, parse_ctx->type_graph, parse_ctx->rdi, arr_eval.type_key); - U64 array_count = array_type->count; - TG_Key element_type_key = array_type->direct_type_key; - U64 element_type_byte_size = tg_byte_size_from_graph_rdi_key(parse_ctx->type_graph, parse_ctx->rdi, element_type_key); - - //- rjf: build blocks for all elements, split by sub-expansions - DF_EvalVizBlock *last_vb = df_eval_viz_block_begin(arena, DF_EvalVizBlockKind_Elements, key, df_expand_key_make(df_hash_from_expand_key(key), 0), depth+1); - { - last_vb->eval = arr_eval; - last_vb->string = eval_string; - last_vb->cfg_table = *cfg_table; - last_vb->visual_idx_range = last_vb->semantic_idx_range = r1u64(0, array_count); - } - for(DF_ExpandNode *child = node->first; child != 0; child = child->next) - { - // rjf: unpack expansion info; skip out-of-bounds splits - U64 child_num = child->key.child_num; - U64 child_idx = child_num-1; - if(child_idx >= array_count) - { - continue; - } - - // rjf: form split: truncate & complete last block; begin next block - last_vb = df_eval_viz_block_split_and_continue(arena, list_out, last_vb, child_idx); - - // rjf: recurse for sub-expansion - { - DF_CfgTable child_cfg = *cfg_table; - { - String8 view_rule_string = df_eval_view_rule_from_key(eval_view, df_expand_key_make(df_hash_from_expand_key(key), child_num)); - child_cfg = df_cfg_table_from_inheritance(arena, cfg_table); - if(view_rule_string.size != 0) - { - df_cfg_table_push_unparsed_string(arena, &child_cfg, view_rule_string, DF_CfgSrc_User); - } - } - DF_Eval child_eval = zero_struct; - { - child_eval.type_key = element_type_key; - child_eval.mode = arr_eval.mode; - child_eval.offset = arr_eval.offset + child_idx*element_type_byte_size; - } - df_append_viz_blocks_for_parent__rec(arena, scope, eval_view, ctrl_ctx, parse_ctx, macro_map, key, child->key, push_str8f(arena, "[%I64u]", child_idx), child_eval, 0, &child_cfg, depth+1, list_out); - } - } - df_eval_viz_block_end(list_out, last_vb); - } - - ////////////////////////////// - //- rjf: (ptr to ptrs) descend to make blocks for pointed-at-pointer - // - if(parent_is_expanded && expand_rule == DF_EvalVizExpandRule_Default && (ptr_type_kind == TG_Kind_Ptr || ptr_type_kind == TG_Kind_LRef || ptr_type_kind == TG_Kind_RRef)) - ProfScope("build viz blocks for ptr-to-ptrs") - { - String8 subexpr = push_str8f(arena, "*(%S)", string); - df_append_viz_blocks_for_parent__rec(arena, scope, eval_view, ctrl_ctx, parse_ctx, macro_map, key, df_expand_key_make(df_hash_from_expand_key(key), 1), subexpr, ptr_eval, 0, cfg_table, depth+1, list_out); - } - - scratch_end(scratch); - ProfEnd(); -} - -internal DF_EvalVizBlockList -df_eval_viz_block_list_from_eval_view_expr_keys(Arena *arena, DI_Scope *scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_EvalView *eval_view, String8 expr, DF_ExpandKey parent_key, DF_ExpandKey key) -{ - ProfBeginFunction(); - DF_EvalVizBlockList blocks = {0}; - { - DF_Eval eval = df_eval_from_string(arena, scope, ctrl_ctx, parse_ctx, macro_map, expr); - U64 expr_comma_pos = str8_find_needle(expr, 0, str8_lit(","), 0); - U64 passthrough_pos = str8_find_needle(expr, 0, str8_lit("--"), 0); - String8List default_view_rules = {0}; - if(expr_comma_pos < expr.size && expr_comma_pos < passthrough_pos) - { - String8 expr_extension = str8_substr(expr, r1u64(expr_comma_pos+1, passthrough_pos)); - expr_extension = str8_skip_chop_whitespace(expr_extension); - if(str8_match(expr_extension, str8_lit("x"), StringMatchFlag_CaseInsensitive)) - { - str8_list_pushf(arena, &default_view_rules, "hex"); - } - else if(str8_match(expr_extension, str8_lit("b"), StringMatchFlag_CaseInsensitive)) - { - str8_list_pushf(arena, &default_view_rules, "bin"); - } - else if(str8_match(expr_extension, str8_lit("o"), StringMatchFlag_CaseInsensitive)) - { - str8_list_pushf(arena, &default_view_rules, "oct"); - } - else if(expr_extension.size != 0) - { - str8_list_pushf(arena, &default_view_rules, "array:{%S}", expr_extension); - } - } - if(passthrough_pos < expr.size) - { - String8 passthrough_view_rule = str8_skip_chop_whitespace(str8_skip(expr, passthrough_pos+2)); - if(passthrough_view_rule.size != 0) - { - str8_list_push(arena, &default_view_rules, passthrough_view_rule); - } - } - String8 view_rule_string = df_eval_view_rule_from_key(eval_view, key); - DF_CfgTable view_rule_table = {0}; - for(String8Node *n = default_view_rules.first; n != 0; n = n->next) - { - df_cfg_table_push_unparsed_string(arena, &view_rule_table, n->string, DF_CfgSrc_User); - } - df_cfg_table_push_unparsed_string(arena, &view_rule_table, view_rule_string, DF_CfgSrc_User); - df_append_viz_blocks_for_parent__rec(arena, scope, eval_view, ctrl_ctx, parse_ctx, macro_map, parent_key, key, expr, eval, 0, &view_rule_table, 0, &blocks); - } - ProfEnd(); - return blocks; -} - -internal void -df_eval_viz_block_list_concat__in_place(DF_EvalVizBlockList *dst, DF_EvalVizBlockList *to_push) -{ - if(dst->last == 0) - { - *dst = *to_push; - } - else if(to_push->first != 0) - { - dst->last->next = to_push->first; - dst->last = to_push->last; - dst->count += to_push->count; - dst->total_visual_row_count += to_push->total_visual_row_count; - dst->total_semantic_row_count += to_push->total_semantic_row_count; - } - MemoryZeroStruct(to_push); -} - -internal S64 -df_row_num_from_viz_block_list_key(DF_EvalVizBlockList *blocks, DF_ExpandKey key) -{ - S64 row_num = 1; - B32 found = 0; - for(DF_EvalVizBlockNode *n = blocks->first; n != 0; n = n->next) - { - DF_EvalVizBlock *block = &n->v; - if(key.parent_hash == block->key.parent_hash) - { - B32 this_block_contains_this_key = 0; - { - if(block->fzy_backing_items.v != 0) - { - U64 item_num = fzy_item_num_from_array_element_idx__linear_search(&block->fzy_backing_items, key.child_num); - this_block_contains_this_key = (item_num != 0 && contains_1u64(block->semantic_idx_range, item_num-1)); - } - else - { - this_block_contains_this_key = (block->semantic_idx_range.min+1 <= key.child_num && key.child_num < block->semantic_idx_range.max+1); - } - } - if(this_block_contains_this_key) - { - found = 1; - if(block->fzy_backing_items.v != 0) - { - U64 item_num = fzy_item_num_from_array_element_idx__linear_search(&block->fzy_backing_items, key.child_num); - row_num += item_num-1-block->semantic_idx_range.min; - } - else - { - row_num += key.child_num-1-block->semantic_idx_range.min; - } - break; - } - } - if(!found) - { - row_num += (S64)dim_1u64(block->semantic_idx_range); - } - } - if(!found) - { - row_num = 0; - } - return row_num; -} - -internal DF_ExpandKey -df_key_from_viz_block_list_row_num(DF_EvalVizBlockList *blocks, S64 row_num) -{ - DF_ExpandKey key = {0}; - S64 scan_y = 1; - for(DF_EvalVizBlockNode *n = blocks->first; n != 0; n = n->next) - { - DF_EvalVizBlock *vb = &n->v; - Rng1S64 vb_row_num_range = r1s64(scan_y, scan_y + (S64)dim_1u64(vb->semantic_idx_range)); - if(contains_1s64(vb_row_num_range, row_num)) - { - key = vb->key; - if(vb->fzy_backing_items.v != 0) - { - U64 item_idx = (U64)((row_num - vb_row_num_range.min) + vb->semantic_idx_range.min); - if(item_idx < vb->fzy_backing_items.count) - { - key.child_num = vb->fzy_backing_items.v[item_idx].idx; - } - } - else - { - key.child_num = vb->semantic_idx_range.min + (row_num - vb_row_num_range.min) + 1; - } - break; - } - scan_y += dim_1s64(vb_row_num_range); - } - return key; -} - -internal DF_ExpandKey -df_parent_key_from_viz_block_list_row_num(DF_EvalVizBlockList *blocks, S64 row_num) -{ - DF_ExpandKey key = {0}; - S64 scan_y = 1; - for(DF_EvalVizBlockNode *n = blocks->first; n != 0; n = n->next) - { - DF_EvalVizBlock *vb = &n->v; - Rng1S64 vb_row_num_range = r1s64(scan_y, scan_y + (S64)dim_1u64(vb->semantic_idx_range)); - if(contains_1s64(vb_row_num_range, row_num)) - { - key = vb->parent_key; - break; - } - scan_y += dim_1s64(vb_row_num_range); - } - return key; -} - -//- rjf: viz row list building - -internal DF_EvalVizRow * -df_eval_viz_row_list_push_new(Arena *arena, EVAL_ParseCtx *parse_ctx, DF_EvalVizWindowedRowList *rows, DF_EvalVizBlock *block, DF_ExpandKey key, DF_Eval eval) -{ - // rjf: push - DF_EvalVizRow *row = push_array(arena, DF_EvalVizRow, 1); - SLLQueuePush(rows->first, rows->last, row); - rows->count += 1; - - // rjf: fill basics - row->depth = block->depth; - row->parent_key = block->parent_key; - row->key = key; - row->eval = eval; - row->size_in_rows = 1; - - // rjf: determine exandability, editability - if(tg_kind_from_key(eval.type_key) != TG_Kind_Null) - { - for(TG_Key t = eval.type_key;; t = tg_unwrapped_direct_from_graph_rdi_key(parse_ctx->type_graph, parse_ctx->rdi, t)) - { - TG_Kind kind = tg_kind_from_key(t); - if(kind == TG_Kind_Null) - { - break; - } - if(eval.mode != EVAL_EvalMode_NULL && ((TG_Kind_FirstBasic <= kind && kind <= TG_Kind_LastBasic) || kind == TG_Kind_Ptr || kind == TG_Kind_LRef || kind == TG_Kind_RRef)) - { - row->flags |= DF_EvalVizRowFlag_CanEditValue; - } - if(eval.mode == EVAL_EvalMode_NULL && kind == TG_Kind_Enum) - { - row->flags |= DF_EvalVizRowFlag_CanExpand; - } - if(kind == TG_Kind_Struct || - kind == TG_Kind_Union || - kind == TG_Kind_Class || - kind == TG_Kind_Array) - { - row->flags |= DF_EvalVizRowFlag_CanExpand; - } - if(row->flags & DF_EvalVizRowFlag_CanExpand) - { - break; - } - if(eval.mode == EVAL_EvalMode_NULL) - { - break; - } - if(kind == TG_Kind_Function) - { - break; - } - } - } - - return row; -} - -//////////////////////////////// -//~ rjf: Main State Accessors/Mutators - -//- rjf: frame data - -internal F32 -df_dt(void) -{ - return df_state->dt; -} - -internal U64 -df_frame_index(void) -{ - return df_state->frame_index; -} - -internal Arena * -df_frame_arena(void) -{ - return df_state->frame_arenas[df_state->frame_index%ArrayCount(df_state->frame_arenas)]; -} - -internal F64 -df_time_in_seconds(void) -{ - return df_state->time_in_seconds; -} - -//- rjf: interaction registers - -internal DF_InteractRegs * -df_interact_regs(void) -{ - DF_InteractRegs *regs = &df_state->top_interact_regs->v; - return regs; -} - -internal DF_InteractRegs * -df_push_interact_regs(void) -{ - DF_InteractRegs *top = df_interact_regs(); - DF_InteractRegsNode *n = push_array(df_frame_arena(), DF_InteractRegsNode, 1); - MemoryCopyStruct(&n->v, top); - SLLStackPush(df_state->top_interact_regs, n); - return &n->v; -} - -internal DF_InteractRegs * -df_pop_interact_regs(void) -{ - DF_InteractRegs *regs = &df_state->top_interact_regs->v; - SLLStackPop(df_state->top_interact_regs); - if(df_state->top_interact_regs == 0) - { - df_state->top_interact_regs = &df_state->base_interact_regs; - } - return regs; -} - -//- rjf: undo/redo history - -internal DF_StateDeltaHistory * -df_state_delta_history(void) -{ - return df_state->hist; -} - -//- rjf: control state - -internal DF_RunKind -df_ctrl_last_run_kind(void) -{ - return df_state->ctrl_last_run_kind; -} - -internal U64 -df_ctrl_last_run_frame_idx(void) -{ - return df_state->ctrl_last_run_frame_idx; -} - -internal U64 -df_ctrl_run_gen(void) -{ - return df_state->ctrl_run_gen; -} - -internal B32 -df_ctrl_targets_running(void) -{ - return df_state->ctrl_is_running; -} - -//- rjf: control context - -internal DF_CtrlCtx -df_ctrl_ctx(void) -{ - return df_state->ctrl_ctx; -} - -internal void -df_ctrl_ctx_apply_overrides(DF_CtrlCtx *ctx, DF_CtrlCtx *overrides) -{ - if(!df_handle_match(overrides->thread, df_handle_zero())) - { - ctx->thread = overrides->thread; - ctx->unwind_count = overrides->unwind_count; - ctx->inline_unwind_count = overrides->inline_unwind_count; - } -} - -//- rjf: config paths - -internal String8 -df_cfg_path_from_src(DF_CfgSrc src) -{ - return df_state->cfg_paths[src]; -} - -//- rjf: config state - -internal DF_CfgTable * -df_cfg_table(void) -{ - return &df_state->cfg_table; -} - -//- rjf: config serialization - -internal String8 -df_cfg_escaped_from_raw_string(Arena *arena, String8 string) -{ - Temp scratch = scratch_begin(&arena, 1); - String8List parts = {0}; - U64 split_start_idx = 0; - for(U64 idx = 0; idx <= string.size; idx += 1) - { - U8 byte = (idx < string.size ? string.str[idx] : 0); - if(byte == 0 || byte == '\"' || byte == '\\') - { - String8 part = str8_substr(string, r1u64(split_start_idx, idx)); - str8_list_push(scratch.arena, &parts, part); - switch(byte) - { - default:{}break; - case '\"':{str8_list_push(scratch.arena, &parts, str8_lit("\\\""));}break; - case '\\':{str8_list_push(scratch.arena, &parts, str8_lit("\\\\"));}break; - } - split_start_idx = idx+1; - } - } - StringJoin join = {0}; - String8 result = str8_list_join(arena, &parts, &join); - scratch_end(scratch); - return result; -} - -internal String8 -df_cfg_raw_from_escaped_string(Arena *arena, String8 string) -{ - Temp scratch = scratch_begin(&arena, 1); - String8List parts = {0}; - U64 split_start_idx = 0; - U64 extra_advance = 0; - for(U64 idx = 0; idx <= string.size; ((idx += 1+extra_advance), extra_advance=0)) - { - U8 byte = (idx < string.size ? string.str[idx] : 0); - if(byte == 0 || byte == '\\') - { - String8 part = str8_substr(string, r1u64(split_start_idx, idx)); - str8_list_push(scratch.arena, &parts, part); - if(byte == '\\' && idx+1 < string.size) - { - switch(string.str[idx+1]) - { - default:{}break; - case '"': {extra_advance = 1; str8_list_push(scratch.arena, &parts, str8_lit("\""));}break; - case '\\':{extra_advance = 1; str8_list_push(scratch.arena, &parts, str8_lit("\\"));}break; - } - } - split_start_idx = idx+1+extra_advance; - } - } - StringJoin join = {0}; - String8 result = str8_list_join(arena, &parts, &join); - scratch_end(scratch); - return result; -} - -internal String8List -df_cfg_strings_from_core(Arena *arena, String8 root_path, DF_CfgSrc source) -{ - ProfBeginFunction(); - String8List strs = {0}; - - //- rjf: write recent projects - { - B32 first = 1; - DF_EntityList recent_projects = df_query_cached_entity_list_with_kind(DF_EntityKind_RecentProject); - for(DF_EntityNode *n = recent_projects.first; n != 0; n = n->next) - { - DF_Entity *rp = n->entity; - if(rp->cfg_src == source) - { - if(first) - { - first = 0; - str8_list_push(arena, &strs, str8_lit("/// recent projects ///////////////////////////////////////////////////////////\n")); - str8_list_push(arena, &strs, str8_lit("\n")); - } - Temp scratch = scratch_begin(&arena, 1); - String8 path_absolute = path_normalized_from_string(scratch.arena, rp->name); - String8 path_relative = path_relative_dst_from_absolute_dst_src(scratch.arena, path_absolute, root_path); - str8_list_pushf(arena, &strs, "recent_project: {\"%S\"}\n", path_relative); - scratch_end(scratch); - } - } - if(!first) - { - str8_list_push(arena, &strs, str8_lit("\n")); - } - } - - //- rjf: write targets - { - B32 first = 1; - DF_EntityList targets = df_query_cached_entity_list_with_kind(DF_EntityKind_Target); - for(DF_EntityNode *n = targets.first; n != 0; n = n->next) - { - DF_Entity *target = n->entity; - if(target->cfg_src == source) - { - if(first) - { - first = 0; - str8_list_push(arena, &strs, str8_lit("/// targets ///////////////////////////////////////////////////////////////////\n")); - str8_list_push(arena, &strs, str8_lit("\n")); - } - Temp scratch = scratch_begin(&arena, 1); - DF_Entity *exe__ent = df_entity_child_from_kind(target, DF_EntityKind_Executable); - DF_Entity *args__ent = df_entity_child_from_kind(target, DF_EntityKind_Arguments); - DF_Entity *wdir__ent = df_entity_child_from_kind(target, DF_EntityKind_ExecutionPath); - DF_Entity *entr__ent = df_entity_child_from_kind(target, DF_EntityKind_EntryPointName); - String8 label = target->name; - String8 exe = exe__ent->name; - String8 exe_normalized = path_normalized_from_string(scratch.arena, exe); - String8 exe_normalized_rel = path_relative_dst_from_absolute_dst_src(scratch.arena, exe_normalized, root_path); - String8 wdir = wdir__ent->name; - String8 wdir_normalized = path_normalized_from_string(scratch.arena, wdir); - String8 wdir_normalized_rel = path_relative_dst_from_absolute_dst_src(scratch.arena, wdir_normalized, root_path); - String8 entry_point_name = entr__ent->name; - String8 label_escaped = df_cfg_escaped_from_raw_string(arena, label); - String8 args_escaped = df_cfg_escaped_from_raw_string(arena, args__ent->name); - String8 entry_escaped = df_cfg_escaped_from_raw_string(arena, entry_point_name); - str8_list_push (arena, &strs, str8_lit("target:\n")); - str8_list_push (arena, &strs, str8_lit("{\n")); - if(label.size != 0) - { - str8_list_pushf(arena, &strs, " label: \"%S\"\n", label_escaped); - } - str8_list_pushf(arena, &strs, " exe: \"%S\"\n", exe_normalized_rel); - str8_list_pushf(arena, &strs, " arguments: \"%S\"\n", args_escaped); - str8_list_pushf(arena, &strs, " working_directory: \"%S\"\n", wdir_normalized_rel); - if(entry_point_name.size != 0) - { - str8_list_pushf(arena, &strs, " entry_point: \"%S\"\n", entry_escaped); - } - str8_list_pushf(arena, &strs, " active: %i\n", (int)target->b32); - if(target->flags & DF_EntityFlag_HasColor) - { - Vec4F32 hsva = df_hsva_from_entity(target); - str8_list_pushf(arena, &strs, " hsva: %.2f %.2f %.2f %.2f\n", hsva.x, hsva.y, hsva.z, hsva.w); - } - str8_list_push (arena, &strs, str8_lit("}\n")); - str8_list_push (arena, &strs, str8_lit("\n")); - scratch_end(scratch); - } - } - } - - //- rjf: write path maps - { - B32 first = 1; - DF_EntityList path_maps = df_query_cached_entity_list_with_kind(DF_EntityKind_OverrideFileLink); - for(DF_EntityNode *n = path_maps.first; n != 0; n = n->next) - { - DF_Entity *map = n->entity; - if(map->cfg_src == source) - { - if(first) - { - first = 0; - str8_list_push(arena, &strs, str8_lit("/// file path maps ////////////////////////////////////////////////////////////\n")); - str8_list_push(arena, &strs, str8_lit("\n")); - } - String8 src_path = df_full_path_from_entity(arena, map); - String8 dst_path = df_full_path_from_entity(arena, df_entity_from_handle(map->entity_handle)); - str8_list_push (arena, &strs, str8_lit("file_path_map:\n")); - str8_list_push (arena, &strs, str8_lit("{\n")); - str8_list_pushf(arena, &strs, " source_path: \"%S\"\n", src_path); - str8_list_pushf(arena, &strs, " dest_path: \"%S\"\n", dst_path); - str8_list_push (arena, &strs, str8_lit("}\n")); - str8_list_push (arena, &strs, str8_lit("\n")); - } - } - } - - //- rjf: write auto view rules - { - B32 first = 1; - DF_EntityList avrs = df_query_cached_entity_list_with_kind(DF_EntityKind_AutoViewRule); - for(DF_EntityNode *n = avrs.first; n != 0; n = n->next) - { - DF_Entity *map = n->entity; - if(map->cfg_src == source) - { - if(first) - { - first = 0; - str8_list_push(arena, &strs, str8_lit("/// auto view rules ///////////////////////////////////////////////////////////\n")); - str8_list_push(arena, &strs, str8_lit("\n")); - } - String8 type = df_entity_child_from_kind(map, DF_EntityKind_Source)->name; - String8 view_rule = df_entity_child_from_kind(map, DF_EntityKind_Dest)->name; - type = df_cfg_escaped_from_raw_string(arena, type); - view_rule= df_cfg_escaped_from_raw_string(arena, view_rule); - str8_list_push (arena, &strs, str8_lit("auto_view_rule:\n")); - str8_list_push (arena, &strs, str8_lit("{\n")); - str8_list_pushf(arena, &strs, " type: \"%S\"\n", type); - str8_list_pushf(arena, &strs, " view_rule: \"%S\"\n", view_rule); - str8_list_push (arena, &strs, str8_lit("}\n")); - str8_list_push (arena, &strs, str8_lit("\n")); - } - } - } - - //- rjf: write breakpoints - { - B32 first = 1; - DF_EntityList bps = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); - for(DF_EntityNode *n = bps.first; n != 0; n = n->next) - { - DF_Entity *bp = n->entity; - if(bp->cfg_src == source) - { - DF_Entity *file = df_entity_ancestor_from_kind(bp, DF_EntityKind_File); - DF_Entity *symb = df_entity_child_from_kind(bp, DF_EntityKind_EntryPointName); - DF_Entity *cond = df_entity_child_from_kind(bp, DF_EntityKind_Condition); - if(first) - { - first = 0; - str8_list_push(arena, &strs, str8_lit("/// breakpoints ///////////////////////////////////////////////////////////////\n")); - str8_list_push(arena, &strs, str8_lit("\n")); - } - - // rjf: begin - str8_list_push(arena, &strs, str8_lit("breakpoint:\n")); - str8_list_push(arena, &strs, str8_lit("{\n")); - - // rjf: textual breakpoints - if(!df_entity_is_nil(file) && bp->flags & DF_EntityFlag_HasTextPoint) - { - String8 bp_file_path = df_full_path_from_entity(arena, file); - String8 srlized_bp_file_path = path_relative_dst_from_absolute_dst_src(arena, bp_file_path, root_path); - String8 string = push_str8f(arena, " line: (\"%S\":%I64d)\n", srlized_bp_file_path, bp->text_point.line); - str8_list_push(arena, &strs, string); - } - - // rjf: function name breakpoints - else if(!df_entity_is_nil(symb) && symb->name.size != 0) - { - String8 symb_escaped = df_cfg_escaped_from_raw_string(arena, symb->name); - str8_list_pushf(arena, &strs, " symbol: \"%S\"\n", symb_escaped); - } - - // rjf: address breakpoints - else if(bp->flags & DF_EntityFlag_HasVAddr) - { - str8_list_pushf(arena, &strs, " addr: 0x%I64x\n", bp->vaddr); - } - - // rjf: conditions - if(!df_entity_is_nil(cond)) - { - String8 cond_escaped = df_cfg_escaped_from_raw_string(arena, cond->name); - str8_list_pushf(arena, &strs, " condition: \"%S\"\n", cond_escaped); - } - - // rjf: universal options - str8_list_pushf(arena, &strs, " enabled: %i\n", (int)bp->b32); - if(bp->name.size != 0) - { - String8 label_escaped = df_cfg_escaped_from_raw_string(arena, bp->name); - str8_list_pushf(arena, &strs, " label: \"%S\"\n", bp->name); - } - if(bp->flags & DF_EntityFlag_HasColor) - { - Vec4F32 hsva = df_hsva_from_entity(bp); - str8_list_pushf(arena, &strs, " hsva: %.2f %.2f %.2f %.2f\n", hsva.x, hsva.y, hsva.z, hsva.w); - } - - // rjf: end - str8_list_push(arena, &strs, str8_lit("}\n\n")); - } - } - } - - //- rjf: write watch pins - { - B32 first = 1; - DF_EntityList pins = df_query_cached_entity_list_with_kind(DF_EntityKind_WatchPin); - for(DF_EntityNode *n = pins.first; n != 0; n = n->next) - { - DF_Entity *pin = n->entity; - if(pin->cfg_src == source) - { - if(first) - { - first = 0; - str8_list_push(arena, &strs, str8_lit("/// watch pins ////////////////////////////////////////////////////////////////\n")); - str8_list_push(arena, &strs, str8_lit("\n")); - } - - // rjf: write - str8_list_push(arena, &strs, str8_lit("watch_pin:\n")); - str8_list_push(arena, &strs, str8_lit("{\n")); - String8 expr_escaped = df_cfg_escaped_from_raw_string(arena, pin->name); - str8_list_pushf(arena, &strs, " expression: \"%S\"\n", expr_escaped); - DF_Entity *file = df_entity_ancestor_from_kind(pin, DF_EntityKind_File); - if(pin->flags & DF_EntityFlag_HasTextPoint && !df_entity_is_nil(file)) - { - String8 project_path = root_path; - String8 pin_file_path = df_full_path_from_entity(arena, file); - project_path = path_normalized_from_string(arena, project_path); - pin_file_path = path_normalized_from_string(arena, pin_file_path); - String8 srlized_pin_file_path = path_relative_dst_from_absolute_dst_src(arena, pin_file_path, project_path); - str8_list_pushf(arena, &strs, " line: (\"%S\":%I64d)\n", srlized_pin_file_path, pin->text_point.line); - } - else if(pin->flags & DF_EntityFlag_HasVAddr) - { - str8_list_pushf(arena, &strs, " addr: (0x%I64x)\n", pin->vaddr); - } - if(pin->flags & DF_EntityFlag_HasColor) - { - Vec4F32 hsva = df_hsva_from_entity(pin); - str8_list_pushf(arena, &strs, " hsva: %.2f %.2f %.2f %.2f\n", hsva.x, hsva.y, hsva.z, hsva.w); - } - str8_list_push(arena, &strs, str8_lit("}\n\n")); - } - } - } - - //- rjf: write exception code filters - if(source == DF_CfgSrc_Project) - { - str8_list_push(arena, &strs, str8_lit("/// exception code filters ////////////////////////////////////////////////////\n")); - str8_list_push(arena, &strs, str8_lit("\n")); - str8_list_push(arena, &strs, str8_lit("exception_code_filters:\n")); - str8_list_push(arena, &strs, str8_lit("{\n")); - for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)(CTRL_ExceptionCodeKind_Null+1); - k < CTRL_ExceptionCodeKind_COUNT; - k = (CTRL_ExceptionCodeKind)(k+1)) - { - String8 name = ctrl_exception_code_kind_lowercase_code_string_table[k]; - B32 value = !!(df_state->ctrl_exception_code_filters[k/64] & (1ull<<(k%64))); - str8_list_pushf(arena, &strs, " %S: %i\n", name, value); - } - str8_list_push(arena, &strs, str8_lit("}\n\n")); - } - - //- rjf: write eval view cache -#if 0 - if(source == DF_CfgSrc_Project) - { - B32 first = 1; - for(U64 eval_view_slot_idx = 0; - eval_view_slot_idx < df_state->eval_view_cache.slots_count; - eval_view_slot_idx += 1) - { - for(DF_EvalView *ev = df_state->eval_view_cache.slots[idx].first; - ev != &df_g_nil_eval_view && ev != 0; - ev = ev->hash_next) - { - if(first) - { - first = 0; - str8_list_push(arena, &strs, str8_lit("/// eval view state ///////////////////////////////////////////////////////////\n")); - str8_list_push(arena, &strs, str8_lit("\n")); - } - str8_list_push(arena, &strs, str8_lit("eval_view:\n")); - str8_list_push(arena, &strs, str8_lit("{\n")); - str8_list_pushf(arena, &strs, " key: (%I64x, %I64x)\n", ev->key.u64[0], ev->key.u64[1]); - for(U64 expand_slot_idx = 0; - expand_slot_idx < ev->expand_tree_table.slots_count; - expand_slot_idx += 1) - { - for(DF_ExpandNode *expand_node = ev->expand_tree_table.slots[expand_slot_idx].first; - expand_node != 0; - expand_node = expand_node->hash_next) - { - DF_ExpandKey key = expand_node->key; - B32 expanded = expand_node->expanded; - str8_list_pushf(arena, &strs, " node: ()\n"); - } - } - str8_list_push(arena, &strs, str8_lit("}\n\n")); - } - } - } -#endif - - ProfEnd(); - return strs; -} - -internal void -df_cfg_push_write_string(DF_CfgSrc src, String8 string) -{ - str8_list_push(df_state->cfg_write_arenas[src], &df_state->cfg_write_data[src], push_str8_copy(df_state->cfg_write_arenas[src], string)); -} - -//- rjf: current path - -internal String8 -df_current_path(void) -{ - return df_state->current_path; -} - -//- rjf: architecture info table lookups - -internal String8 -df_info_summary_from_string__x64(String8 string) -{ - String8 result = {0}; - { - U64 hash = df_hash_from_string__case_insensitive(string); - U64 slot_idx = hash % df_state->arch_info_x64_table_size; - DF_ArchInfoSlot *slot = &df_state->arch_info_x64_table[slot_idx]; - for(DF_ArchInfoNode *n = slot->first; n != 0; n = n->hash_next) - { - if(str8_match(n->key, string, StringMatchFlag_CaseInsensitive)) - { - result = n->val; - break; - } - } - } - return result; -} - -internal String8 -df_info_summary_from_string(Architecture arch, String8 string) -{ - String8 result = {0}; - switch(arch) - { - default:{}break; - case Architecture_x64: - { - result = df_info_summary_from_string__x64(string); - }break; - } - return result; -} - -//- rjf: entity kind cache - -internal DF_EntityList -df_query_cached_entity_list_with_kind(DF_EntityKind kind) -{ - ProfBeginFunction(); - DF_EntityListCache *cache = &df_state->kind_caches[kind]; - - // rjf: build cached list if we're out-of-date - if(cache->alloc_gen != df_state->kind_alloc_gens[kind]) - { - cache->alloc_gen = df_state->kind_alloc_gens[kind]; - if(cache->arena == 0) - { - cache->arena = arena_alloc(); - } - arena_clear(cache->arena); - cache->list = df_push_entity_list_with_kind(cache->arena, kind); - } - - // rjf: grab & return cached list - DF_EntityList result = cache->list; - ProfEnd(); - return result; -} - -//- rjf: active entity based queries - -internal DI_KeyList -df_push_active_dbgi_key_list(Arena *arena) -{ - DI_KeyList dbgis = {0}; - DF_EntityList modules = df_query_cached_entity_list_with_kind(DF_EntityKind_Module); - for(DF_EntityNode *n = modules.first; n != 0; n = n->next) - { - DF_Entity *module = n->entity; - DI_Key key = df_dbgi_key_from_module(module); - di_key_list_push(arena, &dbgis, &key); - } - return dbgis; -} - -internal DF_EntityList -df_push_active_target_list(Arena *arena) -{ - DF_EntityList active_targets = {0}; - DF_EntityList all_targets = df_query_cached_entity_list_with_kind(DF_EntityKind_Target); - for(DF_EntityNode *n = all_targets.first; n != 0; n = n->next) - { - if(n->entity->b32) - { - df_entity_list_push(arena, &active_targets, n->entity); - } - } - return active_targets; -} - -//- rjf: per-run caches - -internal CTRL_Unwind -df_query_cached_unwind_from_thread(DF_Entity *thread) -{ - Temp scratch = scratch_begin(0, 0); - CTRL_Unwind result = {0}; - if(thread->kind == DF_EntityKind_Thread) - { - U64 reg_gen = ctrl_reg_gen(); - U64 mem_gen = ctrl_mem_gen(); - DF_UnwindCache *cache = &df_state->unwind_cache; - DF_Handle handle = df_handle_from_entity(thread); - U64 hash = df_hash_from_string(str8_struct(&handle)); - U64 slot_idx = hash%cache->slots_count; - DF_UnwindCacheSlot *slot = &cache->slots[slot_idx]; - DF_UnwindCacheNode *node = 0; - for(DF_UnwindCacheNode *n = slot->first; n != 0; n = n->next) - { - if(df_handle_match(handle, n->thread)) - { - node = n; - break; - } - } - if(node == 0) - { - node = cache->free_node; - if(node != 0) - { - SLLStackPop(cache->free_node); - } - else - { - node = push_array_no_zero(df_state->arena, DF_UnwindCacheNode, 1); - } - MemoryZeroStruct(node); - DLLPushBack(slot->first, slot->last, node); - node->arena = arena_alloc(); - node->thread = handle; - } - if(node->reggen != reg_gen || - node->memgen != mem_gen) - { - CTRL_Unwind new_unwind = ctrl_unwind_from_thread(scratch.arena, df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle, os_now_microseconds()+100); - if(!(new_unwind.flags & (CTRL_UnwindFlag_Error|CTRL_UnwindFlag_Stale)) && new_unwind.frames.count != 0) - { - node->unwind = ctrl_unwind_deep_copy(node->arena, thread->arch, &new_unwind); - node->reggen = reg_gen; - node->memgen = mem_gen; - } - } - result = node->unwind; - } - scratch_end(scratch); - return result; -} - -internal U64 -df_query_cached_rip_from_thread(DF_Entity *thread) -{ - U64 result = df_query_cached_rip_from_thread_unwind(thread, 0); - return result; -} - -internal U64 -df_query_cached_rip_from_thread_unwind(DF_Entity *thread, U64 unwind_count) -{ - U64 result = 0; - if(unwind_count == 0) - { - result = ctrl_query_cached_rip_from_thread(df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); - } - else - { - CTRL_Unwind unwind = df_query_cached_unwind_from_thread(thread); - if(unwind.frames.count != 0) - { - result = regs_rip_from_arch_block(thread->arch, unwind.frames.v[unwind_count%unwind.frames.count].regs); - } - } - return result; -} - -internal U64 -df_query_cached_tls_base_vaddr_from_process_root_rip(DF_Entity *process, U64 root_vaddr, U64 rip_vaddr) -{ - U64 result = 0; - for(U64 cache_idx = 0; cache_idx < ArrayCount(df_state->tls_base_caches); cache_idx += 1) - { - DF_RunTLSBaseCache *cache = &df_state->tls_base_caches[(df_state->tls_base_cache_gen+cache_idx)%ArrayCount(df_state->tls_base_caches)]; - if(cache_idx == 0 && cache->slots_count == 0) - { - cache->slots_count = 256; - cache->slots = push_array(cache->arena, DF_RunTLSBaseCacheSlot, cache->slots_count); - } - else if(cache->slots_count == 0) - { - break; - } - DF_Handle handle = df_handle_from_entity(process); - U64 hash = df_hash_from_seed_string(df_hash_from_string(str8_struct(&handle)), str8_struct(&rip_vaddr)); - U64 slot_idx = hash%cache->slots_count; - DF_RunTLSBaseCacheSlot *slot = &cache->slots[slot_idx]; - DF_RunTLSBaseCacheNode *node = 0; - for(DF_RunTLSBaseCacheNode *n = slot->first; n != 0; n = n->hash_next) - { - if(df_handle_match(n->process, handle) && n->root_vaddr == root_vaddr && n->rip_vaddr == rip_vaddr) - { - node = n; - break; - } - } - if(node == 0) - { - U64 tls_base_vaddr = df_tls_base_vaddr_from_process_root_rip(process, root_vaddr, rip_vaddr); - if(tls_base_vaddr != 0) - { - node = push_array(cache->arena, DF_RunTLSBaseCacheNode, 1); - SLLQueuePush_N(slot->first, slot->last, node, hash_next); - node->process = handle; - node->root_vaddr = root_vaddr; - node->rip_vaddr = rip_vaddr; - node->tls_base_vaddr = tls_base_vaddr; - } - } - if(node != 0 && node->tls_base_vaddr != 0) - { - result = node->tls_base_vaddr; - break; - } - } - return result; -} - -internal EVAL_String2NumMap * -df_query_cached_locals_map_from_dbgi_key_voff(DI_Key *dbgi_key, U64 voff) -{ - ProfBeginFunction(); - EVAL_String2NumMap *map = &eval_string2num_map_nil; - for(U64 cache_idx = 0; cache_idx < ArrayCount(df_state->locals_caches); cache_idx += 1) - { - DF_RunLocalsCache *cache = &df_state->locals_caches[(df_state->locals_cache_gen+cache_idx)%ArrayCount(df_state->locals_caches)]; - if(cache_idx == 0 && cache->table_size == 0) - { - cache->table_size = 256; - cache->table = push_array(cache->arena, DF_RunLocalsCacheSlot, cache->table_size); - } - else if(cache->table_size == 0) - { - break; - } - U64 hash = di_hash_from_key(dbgi_key); - U64 slot_idx = hash % cache->table_size; - DF_RunLocalsCacheSlot *slot = &cache->table[slot_idx]; - DF_RunLocalsCacheNode *node = 0; - for(DF_RunLocalsCacheNode *n = slot->first; n != 0; n = n->hash_next) - { - if(di_key_match(&n->dbgi_key, dbgi_key) && n->voff == voff) - { - node = n; - break; - } - } - if(node == 0) - { - DI_Scope *scope = di_scope_open(); - EVAL_String2NumMap *map = df_push_locals_map_from_dbgi_key_voff(cache->arena, scope, dbgi_key, voff); - if(map->slots_count != 0) - { - node = push_array(cache->arena, DF_RunLocalsCacheNode, 1); - node->dbgi_key = di_key_copy(cache->arena, dbgi_key); - node->voff = voff; - node->locals_map = map; - SLLQueuePush_N(slot->first, slot->last, node, hash_next); - } - di_scope_close(scope); - } - if(node != 0 && node->locals_map->slots_count != 0) - { - map = node->locals_map; - break; - } - } - ProfEnd(); - return map; -} - -internal EVAL_String2NumMap * -df_query_cached_member_map_from_dbgi_key_voff(DI_Key *dbgi_key, U64 voff) -{ - ProfBeginFunction(); - EVAL_String2NumMap *map = &eval_string2num_map_nil; - for(U64 cache_idx = 0; cache_idx < ArrayCount(df_state->member_caches); cache_idx += 1) - { - DF_RunLocalsCache *cache = &df_state->member_caches[(df_state->member_cache_gen+cache_idx)%ArrayCount(df_state->member_caches)]; - if(cache_idx == 0 && cache->table_size == 0) - { - cache->table_size = 256; - cache->table = push_array(cache->arena, DF_RunLocalsCacheSlot, cache->table_size); - } - else if(cache->table_size == 0) - { - break; - } - U64 hash = di_hash_from_key(dbgi_key); - U64 slot_idx = hash % cache->table_size; - DF_RunLocalsCacheSlot *slot = &cache->table[slot_idx]; - DF_RunLocalsCacheNode *node = 0; - for(DF_RunLocalsCacheNode *n = slot->first; n != 0; n = n->hash_next) - { - if(di_key_match(&n->dbgi_key, dbgi_key) && n->voff == voff) - { - node = n; - break; - } - } - if(node == 0) - { - DI_Scope *scope = di_scope_open(); - EVAL_String2NumMap *map = df_push_member_map_from_dbgi_key_voff(cache->arena, scope, dbgi_key, voff); - if(map->slots_count != 0) - { - node = push_array(cache->arena, DF_RunLocalsCacheNode, 1); - node->dbgi_key = di_key_copy(cache->arena, dbgi_key); - node->voff = voff; - node->locals_map = map; - SLLQueuePush_N(slot->first, slot->last, node, hash_next); - } - di_scope_close(scope); - } - if(node != 0 && node->locals_map->slots_count != 0) - { - map = node->locals_map; - break; - } - } - ProfEnd(); - return map; -} - -//- rjf: top-level command dispatch - -internal void -df_push_cmd__root(DF_CmdParams *params, DF_CmdSpec *spec) -{ - // rjf: log - if(params->os_event == 0 || params->os_event->kind != OS_EventKind_MouseMove) - { - Temp scratch = scratch_begin(0, 0); - DF_Entity *entity = df_entity_from_handle(params->entity); - log_infof("df_cmd:\n{\n", spec->info.string); - log_infof("spec: \"%S\"\n", spec->info.string); -#define HandleParamPrint(mem_name) if(!df_handle_match(df_handle_zero(), params->mem_name)) { log_infof("%s: [0x%I64x, 0x%I64x]\n", #mem_name, params->mem_name.u64[0], params->mem_name.u64[1]); } - HandleParamPrint(window); - HandleParamPrint(panel); - HandleParamPrint(dest_panel); - HandleParamPrint(prev_view); - HandleParamPrint(view); - if(!df_entity_is_nil(entity)) - { - String8 entity_name = df_display_string_from_entity(scratch.arena, entity); - log_infof("entity: \"%S\"\n", entity_name); - } - U64 idx = 0; - for(DF_HandleNode *n = params->entity_list.first; n != 0; n = n->next, idx += 1) - { - DF_Entity *entity = df_entity_from_handle(n->handle); - if(!df_entity_is_nil(entity)) - { - String8 entity_name = df_display_string_from_entity(scratch.arena, entity); - log_infof("entity_list[%I64u]: \"%S\"\n", idx, entity_name); - } - } - if(!df_cmd_spec_is_nil(params->cmd_spec)) - { - log_infof("cmd_spec: \"%S\"\n", params->cmd_spec->info.string); - } - if(params->string.size != 0) { log_infof("string: \"%S\"\n", params->string); } - if(params->file_path.size != 0) { log_infof("file_path: \"%S\"\n", params->file_path); } - if(params->text_point.line != 0) { log_infof("text_point: [line:%I64d, col:%I64d]\n", params->text_point.line, params->text_point.column); } - if(params->vaddr != 0) { log_infof("vaddr: 0x%I64x\n", params->vaddr); } - if(params->voff != 0) { log_infof("voff: 0x%I64x\n", params->voff); } - if(params->index != 0) { log_infof("index: 0x%I64x\n", params->index); } - if(params->base_unwind_index != 0) { log_infof("base_unwind_index: 0x%I64x\n", params->base_unwind_index); } - if(params->inline_unwind_index != 0){ log_infof("inline_unwind_index: 0x%I64x\n", params->inline_unwind_index); } - if(params->id != 0) { log_infof("id: 0x%I64x\n", params->id); } - if(params->os_event != 0) - { - String8 kind_string = str8_lit(""); - switch(params->os_event->kind) - { - default:{}break; - case OS_EventKind_Press: {kind_string = str8_lit("press");}break; - case OS_EventKind_Release: {kind_string = str8_lit("release");}break; - case OS_EventKind_MouseMove: {kind_string = str8_lit("mousemove");}break; - case OS_EventKind_Text: {kind_string = str8_lit("text");}break; - case OS_EventKind_Scroll: {kind_string = str8_lit("scroll");}break; - case OS_EventKind_WindowLoseFocus:{kind_string = str8_lit("losefocus");}break; - case OS_EventKind_WindowClose: {kind_string = str8_lit("closewindow");}break; - case OS_EventKind_FileDrop: {kind_string = str8_lit("filedrop");}break; - case OS_EventKind_Wakeup: {kind_string = str8_lit("wakeup");}break; - } - log_infof("os_event->kind: %S\n", kind_string); - } -#undef HandleParamPrint - log_infof("}\n\n"); - scratch_end(scratch); - } - df_cmd_list_push(df_state->root_cmd_arena, &df_state->root_cmds, params, spec); -} - -//////////////////////////////// -//~ rjf: Main Layer Top-Level Calls - -internal void -df_core_init(CmdLine *cmdln, DF_StateDeltaHistory *hist) -{ - Arena *arena = arena_alloc(); - df_state = push_array(arena, DF_State, 1); - df_state->arena = arena; - for(U64 idx = 0; idx < ArrayCount(df_state->frame_arenas); idx += 1) - { - df_state->frame_arenas[idx] = arena_alloc(); - } - df_state->root_cmd_arena = arena_alloc(); - df_state->output_log_key = hs_hash_from_data(str8_lit("df_output_log_key")); - df_state->entities_arena = arena_alloc__sized(GB(64), KB(64)); - df_state->entities_root = &df_g_nil_entity; - df_state->entities_base = push_array(df_state->entities_arena, DF_Entity, 0); - df_state->entities_count = 0; - df_state->ctrl_msg_arena = arena_alloc(); - df_state->ctrl_entity_store = ctrl_entity_store_alloc(); - df_state->ctrl_stop_arena = arena_alloc(); - df_state->entities_root = df_entity_alloc(0, &df_g_nil_entity, DF_EntityKind_Root); - df_state->cmd_spec_table_size = 1024; - df_state->cmd_spec_table = push_array(arena, DF_CmdSpec *, df_state->cmd_spec_table_size); - df_state->view_rule_spec_table_size = 1024; - df_state->view_rule_spec_table = push_array(arena, DF_CoreViewRuleSpec *, df_state->view_rule_spec_table_size); - df_state->seconds_til_autosave = 0.5f; - df_state->hist = hist; - - // rjf: set up initial exception filtering rules - for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)0; k < CTRL_ExceptionCodeKind_COUNT; k = (CTRL_ExceptionCodeKind)(k+1)) - { - if(ctrl_exception_code_kind_default_enable_table[k]) - { - df_state->ctrl_exception_code_filters[k/64] |= 1ull<<(k%64); - } - } - - // rjf: set up initial entities - { - DF_Entity *local_machine = df_entity_alloc(0, df_state->entities_root, DF_EntityKind_Machine); - df_entity_equip_ctrl_machine_id(local_machine, CTRL_MachineID_Local); - df_entity_equip_name(0, local_machine, str8_lit("This PC")); - } - - // rjf: register core commands - { - DF_CmdSpecInfoArray array = {df_g_core_cmd_kind_spec_info_table, ArrayCount(df_g_core_cmd_kind_spec_info_table)}; - df_register_cmd_specs(array); - } - - // rjf: register core view rules - { - DF_CoreViewRuleSpecInfoArray array = {df_g_core_view_rule_spec_info_table, ArrayCount(df_g_core_view_rule_spec_info_table)}; - df_register_core_view_rule_specs(array); - } - - // rjf: set up caches - df_state->unwind_cache.slots_count = 1024; - df_state->unwind_cache.slots = push_array(arena, DF_UnwindCacheSlot, df_state->unwind_cache.slots_count); - for(U64 idx = 0; idx < ArrayCount(df_state->tls_base_caches); idx += 1) - { - df_state->tls_base_caches[idx].arena = arena_alloc(); - } - for(U64 idx = 0; idx < ArrayCount(df_state->locals_caches); idx += 1) - { - df_state->locals_caches[idx].arena = arena_alloc(); - } - for(U64 idx = 0; idx < ArrayCount(df_state->member_caches); idx += 1) - { - df_state->member_caches[idx].arena = arena_alloc(); - } - - // rjf: set up eval view cache - df_state->eval_view_cache.slots_count = 4096; - df_state->eval_view_cache.slots = push_array(arena, DF_EvalViewSlot, df_state->eval_view_cache.slots_count); - - // rjf: set up run state - df_state->ctrl_last_run_arena = arena_alloc(); - - // rjf: set up config reading state - { - Temp scratch = scratch_begin(0, 0); - - // rjf: unpack command line arguments - String8 user_cfg_path = cmd_line_string(cmdln, str8_lit("user")); - String8 project_cfg_path = cmd_line_string(cmdln, str8_lit("project")); - if(project_cfg_path.size == 0) - { - project_cfg_path = cmd_line_string(cmdln, str8_lit("profile")); - } - { - String8 user_program_data_path = os_string_from_system_path(scratch.arena, OS_SystemPath_UserProgramData); - String8 user_data_folder = push_str8f(scratch.arena, "%S/%S", user_program_data_path, str8_lit("raddbg")); - os_make_directory(user_data_folder); - if(user_cfg_path.size == 0) - { - user_cfg_path = push_str8f(scratch.arena, "%S/default.raddbg_user", user_data_folder); - } - if(project_cfg_path.size == 0) - { - project_cfg_path = push_str8f(scratch.arena, "%S/default.raddbg_project", user_data_folder); - } - } - - // rjf: set up config path state - String8 cfg_src_paths[DF_CfgSrc_COUNT] = {user_cfg_path, project_cfg_path}; - for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) - { - df_state->cfg_path_arenas[src] = arena_alloc(); - DF_CmdParams params = df_cmd_params_zero(); - params.file_path = path_normalized_from_string(scratch.arena, cfg_src_paths[src]); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(df_g_cfg_src_load_cmd_kind_table[src])); - } - - // rjf: set up config table arena - df_state->cfg_arena = arena_alloc(); - scratch_end(scratch); - } - - // rjf: set up config write state - for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) - { - df_state->cfg_write_arenas[src] = arena_alloc(); - } - - // rjf: set up initial browse path - { - Temp scratch = scratch_begin(0, 0); - String8List current_path_strs = {0}; - os_string_list_from_system_path(scratch.arena, OS_SystemPath_Current, ¤t_path_strs); - df_state->current_path_arena = arena_alloc(); - String8 current_path = str8_list_first(¤t_path_strs); - String8 current_path_with_slash = push_str8f(scratch.arena, "%S/", current_path); - df_state->current_path = push_str8_copy(df_state->current_path_arena, current_path_with_slash); - scratch_end(scratch); - } - - // rjf: set up architecture info tables - df_state->arch_info_x64_table_size = 1024; - df_state->arch_info_x64_table = push_array(df_state->arena, DF_ArchInfoSlot, df_state->arch_info_x64_table_size); - for(U64 idx = 0; idx < ArrayCount(df_g_inst_table_x64); idx += 1) - { - String8 key = df_g_inst_table_x64[idx].mnemonic; - String8 val = df_g_inst_table_x64[idx].summary; - U64 hash = df_hash_from_string__case_insensitive(key); - U64 slot_idx = hash % df_state->arch_info_x64_table_size; - DF_ArchInfoSlot *slot = &df_state->arch_info_x64_table[slot_idx]; - DF_ArchInfoNode *n = push_array(df_state->arena, DF_ArchInfoNode, 1); - SLLQueuePush_N(slot->first, slot->last, n, hash_next); - n->key = key; - n->val = val; - } -} - -internal DF_CmdList -df_core_gather_root_cmds(Arena *arena) -{ - DF_CmdList cmds = {0}; - for(DF_CmdNode *n = df_state->root_cmds.first; n != 0; n = n->next) - { - df_cmd_list_push(arena, &cmds, &n->cmd.params, n->cmd.spec); - } - return cmds; -} - -internal void -df_core_begin_frame(Arena *arena, DF_CmdList *cmds, F32 dt) -{ - ProfBeginFunction(); - df_state->frame_index += 1; - arena_clear(df_frame_arena()); - df_state->dt = dt; - df_state->time_in_seconds += dt; - df_state->top_interact_regs = &df_state->base_interact_regs; - df_state->top_interact_regs->v.lines = df_line_list_copy(df_frame_arena(), &df_state->top_interact_regs->v.lines); - df_state->top_interact_regs->v.dbgi_key = di_key_copy(df_frame_arena(), &df_state->top_interact_regs->v.dbgi_key); - - //- rjf: sync with ctrl thread - ProfScope("sync with ctrl thread") - { - Temp scratch = scratch_begin(&arena, 1); - - //- rjf: grab next reggen/memgen - U64 new_mem_gen = ctrl_mem_gen(); - U64 new_reg_gen = ctrl_reg_gen(); - - //- rjf: consume & process events - CTRL_EventList events = ctrl_c2u_pop_events(scratch.arena); - ctrl_entity_store_apply_events(df_state->ctrl_entity_store, &events); - for(CTRL_EventNode *event_n = events.first; event_n != 0; event_n = event_n->next) - { - CTRL_Event *event = &event_n->v; - log_infof("ctrl_event:\n{\n"); - log_infof("kind: \"%S\"\n", ctrl_string_from_event_kind(event->kind)); - log_infof("entity_id: %u\n", event->entity_id); - switch(event->kind) - { - default:{}break; - - //- rjf: errors - - case CTRL_EventKind_Error: - { - log_user_error(event->string); - }break; - - //- rjf: starts/stops - - case CTRL_EventKind_Started: - { - df_state->ctrl_is_running = 1; - }break; - - case CTRL_EventKind_Stopped: - { - B32 should_snap = !(df_state->ctrl_soft_halt_issued); - df_state->ctrl_is_running = 0; - df_state->ctrl_soft_halt_issued = 0; - DF_Entity *stop_thread = df_entity_from_ctrl_handle(event->machine_id, event->entity); - - // rjf: gather stop info - { - arena_clear(df_state->ctrl_stop_arena); - MemoryCopyStruct(&df_state->ctrl_last_stop_event, event); - df_state->ctrl_last_stop_event.string = push_str8_copy(df_state->ctrl_stop_arena, df_state->ctrl_last_stop_event.string); - } - - // rjf: select & snap to thread causing stop - if(should_snap && stop_thread->kind == DF_EntityKind_Thread) - { - log_infof("stop_thread: \"%S\"\n", df_display_string_from_entity(scratch.arena, stop_thread)); - DF_CmdParams params = df_cmd_params_zero(); - params.entity = df_handle_from_entity(stop_thread); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectThread)); - } - - // rjf: if no stop-causing thread, and if selected thread, snap to selected - if(should_snap && df_entity_is_nil(stop_thread)) - { - DF_Entity *selected_thread = df_entity_from_handle(df_state->ctrl_ctx.thread); - if(!df_entity_is_nil(selected_thread)) - { - DF_CmdParams params = df_cmd_params_zero(); - params.entity = df_handle_from_entity(selected_thread); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FindThread)); - } - } - - // rjf: thread hit user breakpoint -> increment breakpoint hit count - if(should_snap && event->cause == CTRL_EventCause_UserBreakpoint) - { - U64 stop_thread_vaddr = ctrl_query_cached_rip_from_thread(df_state->ctrl_entity_store, stop_thread->ctrl_machine_id, stop_thread->ctrl_handle); - DF_Entity *process = df_entity_ancestor_from_kind(stop_thread, DF_EntityKind_Process); - DF_Entity *module = df_module_from_process_vaddr(process, stop_thread_vaddr); - DI_Key dbgi_key = df_dbgi_key_from_module(module); - U64 stop_thread_voff = df_voff_from_vaddr(module, stop_thread_vaddr); - DF_EntityList user_bps = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); - for(DF_EntityNode *n = user_bps.first; n != 0; n = n->next) - { - DF_Entity *bp = n->entity; - DF_Entity *symb = df_entity_child_from_kind(bp, DF_EntityKind_EntryPointName); - if(bp->flags & DF_EntityFlag_HasVAddr && bp->vaddr == stop_thread_vaddr) - { - bp->u64 += 1; - } - if(bp->flags & DF_EntityFlag_HasTextPoint) - { - DF_Entity *bp_file = df_entity_ancestor_from_kind(bp, DF_EntityKind_File); - DF_LineList lines = df_lines_from_file_line_num(scratch.arena, bp_file, bp->text_point.line); - for(DF_LineNode *n = lines.first; n != 0; n = n->next) - { - if(contains_1u64(n->v.voff_range, stop_thread_voff)) - { - bp->u64 += 1; - break; - } - } - } - if(!df_entity_is_nil(symb)) - { - U64 symb_voff = df_voff_from_dbgi_key_symbol_name(&dbgi_key, symb->name); - if(symb_voff == stop_thread_voff) - { - bp->u64 += 1; - } - } - } - } - - // rjf: exception or unexpected trap -> push error - if(event->cause == CTRL_EventCause_InterruptedByException || - event->cause == CTRL_EventCause_InterruptedByTrap) - { - DF_CmdParams params = df_cmd_params_zero(); - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - - // rjf: kill all entities which are marked to die on stop - { - DF_Entity *request = df_entity_from_id(event->msg_id); - if(df_entity_is_nil(request)) - { - for(DF_Entity *entity = df_entity_root(); - !df_entity_is_nil(entity); - entity = df_entity_rec_df_pre(entity, df_entity_root()).next) - { - if(entity->flags & DF_EntityFlag_DiesOnRunStop) - { - df_entity_mark_for_deletion(entity); - } - } - } - } - }break; - - //- rjf: entity creation/deletion - - case CTRL_EventKind_NewProc: - { - // rjf: the first process? -> clear session output & reset all bp hit counts - DF_EntityList existing_processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); - if(existing_processes.count == 0) - { - MTX_Op op = {r1u64(0, 0xffffffffffffffffull), str8_lit("[new session]\n")}; - mtx_push_op(df_state->output_log_key, op); - DF_EntityList bps = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); - for(DF_EntityNode *n = bps.first; n != 0; n = n->next) - { - n->entity->u64 = 0; - } - } - - // rjf: create entity - DF_Entity *machine = df_machine_entity_from_machine_id(event->machine_id); - DF_Entity *entity = df_entity_alloc(0, machine, DF_EntityKind_Process); - df_entity_equip_u64(entity, event->msg_id); - df_entity_equip_ctrl_machine_id(entity, event->machine_id); - df_entity_equip_ctrl_handle(entity, event->entity); - df_entity_equip_ctrl_id(entity, event->entity_id); - df_entity_equip_arch(entity, event->arch); - }break; - - case CTRL_EventKind_NewThread: - { - // rjf: create entity - DF_Entity *parent = df_entity_from_ctrl_handle(event->machine_id, event->parent); - DF_Entity *entity = df_entity_alloc(0, parent, DF_EntityKind_Thread); - df_entity_equip_ctrl_machine_id(entity, event->machine_id); - df_entity_equip_ctrl_handle(entity, event->entity); - df_entity_equip_arch(entity, event->arch); - df_entity_equip_ctrl_id(entity, event->entity_id); - df_entity_equip_stack_base(entity, event->stack_base); - df_entity_equip_tls_root(entity, event->tls_root); - df_entity_equip_vaddr(entity, event->rip_vaddr); - if(event->string.size != 0) - { - df_entity_equip_name(0, entity, event->string); - } - - // rjf: find any pending thread names correllating with this TID -> equip name if found match - { - DF_EntityList pending_thread_names = df_query_cached_entity_list_with_kind(DF_EntityKind_PendingThreadName); - for(DF_EntityNode *n = pending_thread_names.first; n != 0; n = n->next) - { - DF_Entity *pending_thread_name = n->entity; - if(event->machine_id == pending_thread_name->ctrl_machine_id && event->entity_id == pending_thread_name->ctrl_id) - { - df_entity_mark_for_deletion(pending_thread_name); - df_entity_equip_name(0, entity, pending_thread_name->name); - break; - } - } - } - - // rjf: determine index in process - U64 thread_idx_in_process = 0; - for(DF_Entity *child = parent->first; !df_entity_is_nil(child); child = child->next) - { - if(child == entity) - { - break; - } - if(child->kind == DF_EntityKind_Thread) - { - thread_idx_in_process += 1; - } - } - - // rjf: build default thread color table - Vec4F32 thread_colors[] = - { - df_rgba_from_theme_color(DF_ThemeColor_Thread0), - df_rgba_from_theme_color(DF_ThemeColor_Thread1), - df_rgba_from_theme_color(DF_ThemeColor_Thread2), - df_rgba_from_theme_color(DF_ThemeColor_Thread3), - df_rgba_from_theme_color(DF_ThemeColor_Thread4), - df_rgba_from_theme_color(DF_ThemeColor_Thread5), - df_rgba_from_theme_color(DF_ThemeColor_Thread6), - df_rgba_from_theme_color(DF_ThemeColor_Thread7), - }; - - // rjf: pick color - Vec4F32 thread_color = thread_colors[thread_idx_in_process % ArrayCount(thread_colors)]; - - // rjf: equip color - df_entity_equip_color_rgba(entity, thread_color); - - // rjf: automatically select if we don't have a selected thread - DF_Entity *selected_thread = df_entity_from_handle(df_state->ctrl_ctx.thread); - if(df_entity_is_nil(selected_thread)) - { - df_state->ctrl_ctx.thread = df_handle_from_entity(entity); - } - - // rjf: do initial snap - DF_EntityList already_existing_processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); - B32 do_initial_snap = (already_existing_processes.count == 1 && thread_idx_in_process == 0); - if(do_initial_snap) - { - DF_CmdParams params = df_cmd_params_zero(); - params.entity = df_handle_from_entity(entity); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectThread)); - } - }break; - - case CTRL_EventKind_NewModule: - { - // rjf: grab process - DF_Entity *parent = df_entity_from_ctrl_handle(event->machine_id, event->parent); - - // rjf: determine if this is the first module - B32 is_first = 0; - if(df_entity_is_nil(df_entity_child_from_kind(parent, DF_EntityKind_Module))) - { - is_first = 1; - } - - // rjf: create module entity - DF_Entity *module = df_entity_alloc(0, parent, DF_EntityKind_Module); - df_entity_equip_ctrl_machine_id(module, event->machine_id); - df_entity_equip_ctrl_handle(module, event->entity); - df_entity_equip_arch(module, event->arch); - df_entity_equip_name(0, module, event->string); - df_entity_equip_vaddr_rng(module, event->vaddr_rng); - df_entity_equip_vaddr(module, event->rip_vaddr); - df_entity_equip_timestamp(module, event->timestamp); - - // rjf: is first -> find target, equip process & module & first thread with target color - if(is_first) - { - DF_EntityList targets = df_query_cached_entity_list_with_kind(DF_EntityKind_Target); - for(DF_EntityNode *n = targets.first; n != 0; n = n->next) - { - DF_Entity *target = n->entity; - DF_Entity *exe = df_entity_child_from_kind(target, DF_EntityKind_Executable); - String8 exe_name = exe->name; - String8 exe_name_normalized = path_normalized_from_string(scratch.arena, exe_name); - String8 module_name_normalized = path_normalized_from_string(scratch.arena, module->name); - if(str8_match(exe_name_normalized, module_name_normalized, StringMatchFlag_CaseInsensitive) && - target->flags & DF_EntityFlag_HasColor) - { - DF_Entity *first_thread = df_entity_child_from_kind(parent, DF_EntityKind_Thread); - Vec4F32 rgba = df_rgba_from_entity(target); - df_entity_equip_color_rgba(parent, rgba); - df_entity_equip_color_rgba(first_thread, rgba); - df_entity_equip_color_rgba(module, rgba); - break; - } - } - } - }break; - - case CTRL_EventKind_EndProc: - { - U32 pid = event->entity_id; - DF_Entity *process = df_entity_from_ctrl_handle(event->machine_id, event->entity); - df_entity_mark_for_deletion(process); - }break; - - case CTRL_EventKind_EndThread: - { - DF_Entity *thread = df_entity_from_ctrl_handle(event->machine_id, event->entity); - df_set_thread_freeze_state(thread, 0); - df_entity_mark_for_deletion(thread); - }break; - - case CTRL_EventKind_EndModule: - { - DF_Entity *module = df_entity_from_ctrl_handle(event->machine_id, event->entity); - df_entity_mark_for_deletion(module); - }break; - - //- rjf: debug info changes - - case CTRL_EventKind_ModuleDebugInfoPathChange: - { - DF_Entity *module = df_entity_from_ctrl_handle(event->machine_id, event->entity); - DF_Entity *debug_info = df_entity_child_from_kind(module, DF_EntityKind_DebugInfoPath); - if(df_entity_is_nil(debug_info)) - { - debug_info = df_entity_alloc(0, module, DF_EntityKind_DebugInfoPath); - } - df_entity_equip_name(0, debug_info, event->string); - df_entity_equip_timestamp(debug_info, event->timestamp); - }break; - - //- rjf: debug strings - - case CTRL_EventKind_DebugString: - { - MTX_Op op = {r1u64(max_U64, max_U64), event->string}; - mtx_push_op(df_state->output_log_key, op); - }break; - - case CTRL_EventKind_ThreadName: - { - String8 string = event->string; - DF_Entity *entity = df_entity_from_ctrl_handle(event->machine_id, event->entity); - if(event->entity_id != 0) - { - entity = df_entity_from_ctrl_id(event->machine_id, event->entity_id); - } - if(df_entity_is_nil(entity)) - { - DF_Entity *process = df_entity_from_ctrl_handle(event->machine_id, event->parent); - if(!df_entity_is_nil(process)) - { - entity = df_entity_alloc(0, process, DF_EntityKind_PendingThreadName); - df_entity_equip_name(0, entity, string); - df_entity_equip_ctrl_machine_id(entity, event->machine_id); - df_entity_equip_ctrl_id(entity, event->entity_id); - } - } - if(!df_entity_is_nil(entity)) - { - df_entity_equip_name(0, entity, string); - } - }break; - - //- rjf: memory - - case CTRL_EventKind_MemReserve:{}break; - case CTRL_EventKind_MemCommit:{}break; - case CTRL_EventKind_MemDecommit:{}break; - case CTRL_EventKind_MemRelease:{}break; - } - log_infof("}\n\n"); - } - - //- rjf: clear tls base cache - if((df_state->tls_base_cache_reggen_idx != new_reg_gen || - df_state->tls_base_cache_memgen_idx != new_mem_gen) && - !df_ctrl_targets_running()) - { - df_state->tls_base_cache_gen += 1; - DF_RunTLSBaseCache *cache = &df_state->tls_base_caches[df_state->tls_base_cache_gen%ArrayCount(df_state->tls_base_caches)]; - arena_clear(cache->arena); - cache->slots_count = 0; - cache->slots = 0; - df_state->tls_base_cache_reggen_idx = new_reg_gen; - df_state->tls_base_cache_memgen_idx = new_mem_gen; - } - - //- rjf: clear locals cache - if(df_state->locals_cache_reggen_idx != new_reg_gen && - !df_ctrl_targets_running()) - { - df_state->locals_cache_gen += 1; - DF_RunLocalsCache *cache = &df_state->locals_caches[df_state->locals_cache_gen%ArrayCount(df_state->locals_caches)]; - arena_clear(cache->arena); - cache->table_size = 0; - cache->table = 0; - df_state->locals_cache_reggen_idx = new_reg_gen; - } - - //- rjf: clear members cache - if(df_state->member_cache_reggen_idx != new_reg_gen && - !df_ctrl_targets_running()) - { - df_state->member_cache_gen += 1; - DF_RunLocalsCache *cache = &df_state->member_caches[df_state->member_cache_gen%ArrayCount(df_state->member_caches)]; - arena_clear(cache->arena); - cache->table_size = 0; - cache->table = 0; - df_state->member_cache_reggen_idx = new_reg_gen; - } - - scratch_end(scratch); - } - - //- rjf: sync with di parsers - ProfScope("sync with di parsers") - { - Temp scratch = scratch_begin(&arena, 1); - DI_EventList events = di_p2u_pop_events(scratch.arena, 0); - for(DI_EventNode *n = events.first; n != 0; n = n->next) - { - DI_Event *event = &n->v; - switch(event->kind) - { - default:{}break; - case DI_EventKind_ConversionStarted: - { - DF_Entity *task = df_entity_alloc(0, df_entity_root(), DF_EntityKind_ConversionTask); - df_entity_equip_name(0, task, event->string); - }break; - case DI_EventKind_ConversionEnded: - { - DF_Entity *task = df_entity_from_name_and_kind(event->string, DF_EntityKind_ConversionTask); - if(!df_entity_is_nil(task)) - { - df_entity_mark_for_deletion(task); - } - }break; - case DI_EventKind_ConversionFailureUnsupportedFormat: - { - // DF_Entity *task = df_entity_alloc(df_entity_root(), DF_EntityKind_ConversionFail); - // df_entity_equip_name(task, event->string); - // df_entity_equip_death_timer(task, 15.f); - }break; - } - } - scratch_end(scratch); - } - - //- rjf: start/stop telemetry captures - ProfScope("start/stop telemetry captures") - { - if(!ProfIsCapturing() && DEV_telemetry_capture) - { - ProfBeginCapture("raddbg"); - } - if(ProfIsCapturing() && !DEV_telemetry_capture) - { - ProfEndCapture(); - } - } - - //- rjf: clear root level commands - { - arena_clear(df_state->root_cmd_arena); - MemoryZeroStruct(&df_state->root_cmds); - } - - //- rjf: autosave - { - df_state->seconds_til_autosave -= dt; - if(df_state->seconds_til_autosave <= 0.f) - { - DF_CmdParams params = df_cmd_params_zero(); - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_WriteUserData)); - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_WriteProjectData)); - df_state->seconds_til_autosave = 5.f; - } - } - - //- rjf: process top-level commands - ProfScope("process top-level commands") - { - Temp scratch = scratch_begin(&arena, 1); - for(DF_CmdNode *cmd_node = cmds->first; - cmd_node != 0; - cmd_node = cmd_node->next) - { - temp_end(scratch); - - // rjf: unpack command - DF_Cmd *cmd = &cmd_node->cmd; - DF_CmdParams params = cmd->params; - DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); - df_cmd_spec_counter_inc(cmd->spec); - - // rjf: process command - switch(core_cmd_kind) - { - default:{}break; - - //- rjf: command fast paths - case DF_CoreCmdKind_RunCommand: - { - DF_CmdSpec *spec = params.cmd_spec; - if(spec != cmd->spec) - { - df_cmd_spec_counter_inc(spec); - if(!(spec->info.query.flags & DF_CmdQueryFlag_Required) && - (spec->info.query.slot == DF_CmdParamSlot_Null || - df_cmd_params_has_slot(¶ms, spec->info.query.slot))) - { - df_cmd_list_push(arena, cmds, ¶ms, spec); - } - } - }break; - - //- rjf: low-level target control operations - case DF_CoreCmdKind_LaunchAndRun: - case DF_CoreCmdKind_LaunchAndInit: - { - // rjf: get list of targets to launch - DF_EntityList targets = df_entity_list_from_handle_list(scratch.arena, params.entity_list); - - // rjf: no targets => assume all active targets - if(targets.count == 0) - { - targets = df_push_active_target_list(scratch.arena); - } - - // rjf: launch - if(targets.count != 0) - { - for(DF_EntityNode *n = targets.first; n != 0; n = n->next) - { - // rjf: extract data from target - DF_Entity *target = n->entity; - String8 name = df_entity_child_from_kind(target, DF_EntityKind_Executable)->name; - String8 args = df_entity_child_from_kind(target, DF_EntityKind_Arguments)->name; - String8 path = df_entity_child_from_kind(target, DF_EntityKind_ExecutionPath)->name; - String8 entry= df_entity_child_from_kind(target, DF_EntityKind_EntryPointName)->name; - name = str8_skip_chop_whitespace(name); - args = str8_skip_chop_whitespace(args); - path = str8_skip_chop_whitespace(path); - entry = str8_skip_chop_whitespace(entry); - if(path.size == 0) - { - String8List current_path_strs = {0}; - os_string_list_from_system_path(scratch.arena, OS_SystemPath_Current, ¤t_path_strs); - path = str8_list_first(¤t_path_strs); - } - - // rjf: build launch options - String8List cmdln_strings = {0}; - { - str8_list_push(scratch.arena, &cmdln_strings, name); - { - U64 start_split_idx = 0; - B32 quoted = 0; - for(U64 idx = 0; idx <= args.size; idx += 1) - { - U8 byte = idx < args.size ? args.str[idx] : 0; - if(byte == '"') - { - quoted ^= 1; - } - B32 splitter_found = (!quoted && (byte == 0 || char_is_space(byte))); - if(splitter_found) - { - String8 string = str8_substr(args, r1u64(start_split_idx, idx)); - if(string.size > 0) - { - str8_list_push(scratch.arena, &cmdln_strings, string); - } - start_split_idx = idx+1; - } - } - } - } - - // rjf: push message to launch - { - CTRL_Msg msg = {CTRL_MsgKind_Launch}; - msg.path = path; - msg.cmd_line_string_list = cmdln_strings; - msg.env_inherit = 1; - MemoryCopyArray(msg.exception_code_filters, df_state->ctrl_exception_code_filters); - str8_list_push(scratch.arena, &msg.entry_points, entry); - df_push_ctrl_msg(&msg); - } - } - - // rjf: run - df_ctrl_run(DF_RunKind_Run, &df_g_nil_entity, CTRL_RunFlag_StopOnEntryPoint * (core_cmd_kind == DF_CoreCmdKind_LaunchAndInit), 0); - } - - // rjf: no targets -> error - if(targets.count == 0) - { - DF_CmdParams p = params; - p.string = str8_lit("No active targets exist; cannot launch."); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - }break; - case DF_CoreCmdKind_Kill: - { - DF_EntityList processes = df_entity_list_from_handle_list(scratch.arena, params.entity_list); - - // rjf: no processes => kill everything - if(processes.count == 0) - { - processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); - } - - // rjf: kill processes - if(processes.count != 0) - { - for(DF_EntityNode *n = processes.first; n != 0; n = n->next) - { - DF_Entity *process = n->entity; - CTRL_Msg msg = {CTRL_MsgKind_Kill}; - { - msg.exit_code = 1; - msg.machine_id = process->ctrl_machine_id; - msg.entity = process->ctrl_handle; - MemoryCopyArray(msg.exception_code_filters, df_state->ctrl_exception_code_filters); - } - df_push_ctrl_msg(&msg); - } - } - - // rjf: no processes -> error - if(processes.count == 0) - { - DF_CmdParams p = params; - p.string = str8_lit("No attached running processes exist; cannot kill."); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - }break; - case DF_CoreCmdKind_KillAll: - { - DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); - if(processes.count != 0) - { - for(DF_EntityNode *n = processes.first; n != 0; n = n->next) - { - DF_Entity *process = n->entity; - CTRL_Msg msg = {CTRL_MsgKind_Kill}; - { - msg.exit_code = 1; - msg.machine_id = process->ctrl_machine_id; - msg.entity = process->ctrl_handle; - MemoryCopyArray(msg.exception_code_filters, df_state->ctrl_exception_code_filters); - } - df_push_ctrl_msg(&msg); - } - } - if(processes.count == 0) - { - DF_CmdParams p = params; - p.string = str8_lit("No attached running processes exist; cannot kill."); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - }break; - case DF_CoreCmdKind_Detach: - { - for(DF_HandleNode *n = params.entity_list.first; n != 0; n = n->next) - { - DF_Entity *entity = df_entity_from_handle(n->handle); - if(entity->kind == DF_EntityKind_Process) - { - CTRL_Msg msg = {CTRL_MsgKind_Detach}; - msg.machine_id = entity->ctrl_machine_id; - msg.entity = entity->ctrl_handle; - MemoryCopyArray(msg.exception_code_filters, df_state->ctrl_exception_code_filters); - df_push_ctrl_msg(&msg); - } - } - }break; - case DF_CoreCmdKind_Continue: - { - B32 good_to_run = 0; - DF_EntityList machines = df_query_cached_entity_list_with_kind(DF_EntityKind_Machine); - for(DF_EntityNode *n = machines.first; n != 0; n = n->next) - { - DF_Entity *machine = n->entity; - if(!df_entity_is_frozen(machine)) - { - good_to_run = 1; - break; - } - } - if(good_to_run) - { - df_ctrl_run(DF_RunKind_Run, &df_g_nil_entity, 0, 0); - } - else - { - DF_CmdParams p = params; - p.string = str8_lit("Cannot run with all threads frozen."); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - }break; - case DF_CoreCmdKind_StepIntoInst: - case DF_CoreCmdKind_StepOverInst: - case DF_CoreCmdKind_StepIntoLine: - case DF_CoreCmdKind_StepOverLine: - case DF_CoreCmdKind_StepOut: - { - DF_Entity *thread = df_entity_from_handle(params.entity); - if(df_ctrl_targets_running()) - { - if(df_ctrl_last_run_kind() == DF_RunKind_Run) - { - DF_CmdParams p = params; - p.string = str8_lit("Must halt before stepping."); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - } - else if(df_entity_is_frozen(thread)) - { - DF_CmdParams p = params; - p.string = str8_lit("Must thaw selected thread before stepping."); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - else - { - B32 good = 1; - CTRL_TrapList traps = {0}; - switch(core_cmd_kind) - { - default: break; - case DF_CoreCmdKind_StepIntoInst: {}break; - case DF_CoreCmdKind_StepOverInst: {traps = df_trap_net_from_thread__step_over_inst(scratch.arena, thread);}break; - case DF_CoreCmdKind_StepIntoLine: {traps = df_trap_net_from_thread__step_into_line(scratch.arena, thread);}break; - case DF_CoreCmdKind_StepOverLine: {traps = df_trap_net_from_thread__step_over_line(scratch.arena, thread);}break; - case DF_CoreCmdKind_StepOut: - { - // rjf: thread => full unwind - CTRL_Unwind unwind = ctrl_unwind_from_thread(scratch.arena, df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle, os_now_microseconds()+10000); - - // rjf: use first unwind frame to generate trap - if(unwind.flags == 0 && unwind.frames.count > 1) - { - U64 vaddr = regs_rip_from_arch_block(thread->arch, unwind.frames.v[1].regs); - CTRL_Trap trap = {CTRL_TrapFlag_EndStepping|CTRL_TrapFlag_IgnoreStackPointerCheck, vaddr}; - ctrl_trap_list_push(scratch.arena, &traps, &trap); - } - else - { - DF_CmdParams p = params; - p.string = str8_lit("Could not find the return address of the current callstack frame successfully."); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - good = 0; - } - }break; - } - if(good && traps.count != 0) - { - df_ctrl_run(DF_RunKind_Step, thread, 0, &traps); - } - if(good && traps.count == 0) - { - df_ctrl_run(DF_RunKind_SingleStep, thread, 0, &traps); - } - } - }break; - case DF_CoreCmdKind_Halt: - if(df_ctrl_targets_running()) - { - ctrl_halt(); - }break; - case DF_CoreCmdKind_SoftHaltRefresh: - { - if(df_ctrl_targets_running()) - { - df_ctrl_run(df_state->ctrl_last_run_kind, df_entity_from_handle(df_state->ctrl_last_run_thread), df_state->ctrl_last_run_flags, &df_state->ctrl_last_run_traps); - } - }break; - case DF_CoreCmdKind_SetThreadIP: - { - DF_Entity *thread = df_entity_from_handle(params.entity); - U64 vaddr = params.vaddr; - if(thread->kind == DF_EntityKind_Thread && vaddr != 0) - { - df_set_thread_rip(thread, vaddr); - } - }break; - - //- rjf: high-level composite target control operations - case DF_CoreCmdKind_RunToLine: - { - DF_Entity *file = df_entity_from_handle(params.entity); - TxtPt point = params.text_point; - if(file->kind == DF_EntityKind_File) - { - DF_Entity *bp = df_entity_alloc(0, file, DF_EntityKind_Breakpoint); - bp->flags |= DF_EntityFlag_DiesOnRunStop; - df_entity_equip_b32(bp, 1); - df_entity_equip_txt_pt(bp, point); - df_entity_equip_cfg_src(bp, DF_CfgSrc_Transient); - DF_CmdParams p = df_cmd_params_zero(); - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Run)); - } - }break; - case DF_CoreCmdKind_RunToAddress: - { - DF_Entity *bp = df_entity_alloc(0, df_entity_root(), DF_EntityKind_Breakpoint); - bp->flags |= DF_EntityFlag_DiesOnRunStop; - df_entity_equip_b32(bp, 1); - df_entity_equip_vaddr(bp, params.vaddr); - df_entity_equip_cfg_src(bp, DF_CfgSrc_Transient); - DF_CmdParams p = df_cmd_params_zero(); - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Run)); - }break; - case DF_CoreCmdKind_Run: - { - DF_CmdParams params = df_cmd_params_zero(); - DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); - if(processes.count != 0) - { - DF_CmdParams params = df_cmd_params_zero(); - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Continue)); - } - else if(!df_ctrl_targets_running()) - { - DF_CmdParams params = df_cmd_params_zero(); - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_LaunchAndRun)); - } - }break; - case DF_CoreCmdKind_Restart: - { - // rjf: kill all - { - DF_CmdParams params = df_cmd_params_zero(); - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_KillAll)); - } - - // rjf: gather targets corresponding to all launched processes - DF_EntityList targets = {0}; - { - DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); - for(DF_EntityNode *n = processes.first; n != 0; n = n->next) - { - DF_Entity *process = n->entity; - DF_Entity *target = df_entity_from_handle(process->entity_handle); - if(!df_entity_is_nil(target)) - { - df_entity_list_push(scratch.arena, &targets, target); - } - } - } - - // rjf: re-launch targets - { - DF_CmdParams params = df_cmd_params_zero(); - params.entity_list = df_handle_list_from_entity_list(scratch.arena, targets); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_EntityList); - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_LaunchAndRun)); - } - }break; - case DF_CoreCmdKind_StepInto: - case DF_CoreCmdKind_StepOver: - { - DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); - if(processes.count != 0) - { - DF_CoreCmdKind step_cmd_kind = (core_cmd_kind == DF_CoreCmdKind_StepInto - ? DF_CoreCmdKind_StepIntoLine - : DF_CoreCmdKind_StepOverLine); - B32 prefer_dasm = params.prefer_dasm; - if(prefer_dasm) - { - step_cmd_kind = (core_cmd_kind == DF_CoreCmdKind_StepInto - ? DF_CoreCmdKind_StepIntoInst - : DF_CoreCmdKind_StepOverInst); - } - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(step_cmd_kind)); - } - else if(!df_ctrl_targets_running()) - { - DF_EntityList targets = df_push_active_target_list(scratch.arena); - DF_CmdParams p = params; - p.entity_list = df_handle_list_from_entity_list(scratch.arena, targets); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_EntityList); - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_LaunchAndInit)); - } - }break; - - //- rjf: debug control context management operations - case DF_CoreCmdKind_SelectThread: - { - MemoryZeroStruct(&df_state->ctrl_ctx); - df_state->ctrl_ctx.thread = params.entity; - }break; - case DF_CoreCmdKind_SelectUnwind: - { - DI_Scope *di_scope = di_scope_open(); - DF_Entity *thread = df_entity_from_handle(df_state->ctrl_ctx.thread); - DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); - CTRL_Unwind base_unwind = df_query_cached_unwind_from_thread(thread); - DF_Unwind rich_unwind = df_unwind_from_ctrl_unwind(scratch.arena, di_scope, process, &base_unwind); - DF_UnwindFrame *frame = df_frame_from_unwind_idxs(&rich_unwind, params.base_unwind_index, params.inline_unwind_index); - if(frame != 0) - { - df_state->ctrl_ctx.unwind_count = frame->base_unwind_idx; - df_state->ctrl_ctx.inline_unwind_count = frame->inline_unwind_idx; - } - di_scope_close(di_scope); - }break; - case DF_CoreCmdKind_UpOneFrame: - case DF_CoreCmdKind_DownOneFrame: - { - DF_CtrlCtx ctrl_ctx = df_ctrl_ctx(); - DI_Scope *di_scope = di_scope_open(); - DF_Entity *thread = df_entity_from_handle(ctrl_ctx.thread); - DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); - CTRL_Unwind base_unwind = df_query_cached_unwind_from_thread(thread); - DF_Unwind rich_unwind = df_unwind_from_ctrl_unwind(scratch.arena, di_scope, process, &base_unwind); - DF_UnwindFrame *current_frame = 0; - for(U64 idx = 0; idx < rich_unwind.frames.count; idx += 1) - { - if(rich_unwind.frames.v[idx].base_unwind_idx == ctrl_ctx.unwind_count && - rich_unwind.frames.v[idx].inline_unwind_idx == ctrl_ctx.inline_unwind_count) - { - current_frame = &rich_unwind.frames.v[idx]; - break; - } - } - if(current_frame == 0 && rich_unwind.frames.count != 0) - { - current_frame = &rich_unwind.frames.v[0]; - } - DF_UnwindFrame *next_frame = current_frame; - switch(core_cmd_kind) - { - default:{}break; - case DF_CoreCmdKind_UpOneFrame: - if(current_frame != 0 && (current_frame - rich_unwind.frames.v) > 0) - { - next_frame -= 1; - }break; - case DF_CoreCmdKind_DownOneFrame: - if(current_frame != 0 && (current_frame - rich_unwind.frames.v)+1 < rich_unwind.frames.count) - { - next_frame += 1; - }break; - } - if(next_frame != 0) - { - DF_CmdParams p = params; - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_BaseUnwindIndex); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_InlineUnwindIndex); - p.base_unwind_index = next_frame->base_unwind_idx; - p.inline_unwind_index = next_frame->base_unwind_idx; - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectUnwind)); - } - di_scope_close(di_scope); - }break; - case DF_CoreCmdKind_FreezeThread: - case DF_CoreCmdKind_ThawThread: - case DF_CoreCmdKind_FreezeProcess: - case DF_CoreCmdKind_ThawProcess: - case DF_CoreCmdKind_FreezeMachine: - case DF_CoreCmdKind_ThawMachine: - { - DF_CoreCmdKind disptch_kind = ((core_cmd_kind == DF_CoreCmdKind_FreezeThread || - core_cmd_kind == DF_CoreCmdKind_FreezeProcess || - core_cmd_kind == DF_CoreCmdKind_FreezeMachine) - ? DF_CoreCmdKind_FreezeEntity - : DF_CoreCmdKind_ThawEntity); - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(disptch_kind)); - }break; - case DF_CoreCmdKind_FreezeLocalMachine: - { - CTRL_MachineID machine_id = CTRL_MachineID_Local; - DF_CmdParams params = df_cmd_params_zero(); - params.entity = df_handle_from_entity(df_machine_entity_from_machine_id(machine_id)); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FreezeMachine)); - }break; - case DF_CoreCmdKind_ThawLocalMachine: - { - CTRL_MachineID machine_id = CTRL_MachineID_Local; - DF_CmdParams params = df_cmd_params_zero(); - params.entity = df_handle_from_entity(df_machine_entity_from_machine_id(machine_id)); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_ThawMachine)); - }break; - - //- rjf: undo/redo - case DF_CoreCmdKind_Undo: - { - df_state_delta_history_wind(df_state->hist, Side_Min); - }break; - case DF_CoreCmdKind_Redo: - { - df_state_delta_history_wind(df_state->hist, Side_Max); - }break; - - //- rjf: files - case DF_CoreCmdKind_SetCurrentPath: - { - arena_clear(df_state->current_path_arena); - df_state->current_path = push_str8_copy(df_state->current_path_arena, params.file_path); - }break; - case DF_CoreCmdKind_Open: - { - String8 path = path_normalized_from_string(scratch.arena, params.file_path); - if(path.size == 0) - { - DF_CmdParams p = params; - p.string = str8_lit("File name not specified."); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - else - { - DF_Entity *loaded_file = df_entity_from_path(path, DF_EntityFromPathFlag_OpenAsNeeded|DF_EntityFromPathFlag_OpenMissing); - if(loaded_file->flags & DF_EntityFlag_IsMissing) - { - DF_CmdParams p = params; - p.string = push_str8f(scratch.arena, "Could not load \"%S\".", path); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - } - }break; - - //- rjf: config path saving/loading/applying - case DF_CoreCmdKind_OpenRecentProject: - { - DF_Entity *entity = df_entity_from_handle(params.entity); - if(entity->kind == DF_EntityKind_RecentProject) - { - DF_CmdParams p = df_cmd_params_zero(); - p.file_path = entity->name; - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_OpenProject)); - } - }break; - case DF_CoreCmdKind_OpenUser: - case DF_CoreCmdKind_OpenProject: - { - B32 load_cfg[DF_CfgSrc_COUNT] = {0}; - for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) - { - load_cfg[src] = (core_cmd_kind == df_g_cfg_src_load_cmd_kind_table[src]); - } - - //- rjf: normalize path - String8 new_path = path_normalized_from_string(scratch.arena, params.file_path); - - //- rjf: path -> data - FileProperties props = {0}; - String8 data = {0}; - { - OS_Handle file = os_file_open(OS_AccessFlag_ShareRead|OS_AccessFlag_Read, new_path); - props = os_properties_from_file(file); - data = os_string_from_file_range(scratch.arena, file, r1u64(0, props.size)); - os_file_close(file); - } - - //- rjf: investigate file path/data - B32 file_is_okay = 1; - if(props.modified != 0 && data.size != 0 && !str8_match(str8_prefix(data, 9), str8_lit("// raddbg"), 0)) - { - file_is_okay = 0; - } - - //- rjf: set new config paths - if(file_is_okay) - { - for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) - { - if(load_cfg[src]) - { - arena_clear(df_state->cfg_path_arenas[src]); - df_state->cfg_paths[src] = push_str8_copy(df_state->cfg_path_arenas[src], new_path); - } - } - } - - //- rjf: get config files - DF_Entity *cfg_files[DF_CfgSrc_COUNT] = {0}; - if(file_is_okay) - { - for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) - { - String8 path = df_cfg_path_from_src(src); - cfg_files[src] = df_entity_from_path(path, DF_EntityFromPathFlag_OpenMissing|DF_EntityFromPathFlag_OpenAsNeeded); - } - } - - //- rjf: load files - String8 cfg_data[DF_CfgSrc_COUNT] = {0}; - U64 cfg_timestamps[DF_CfgSrc_COUNT] = {0}; - if(file_is_okay) - { - for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) - { - DF_Entity *file_entity = cfg_files[src]; - String8 path = df_full_path_from_entity(scratch.arena, file_entity); - OS_Handle file = os_file_open(OS_AccessFlag_ShareRead|OS_AccessFlag_Read, path); - FileProperties props = os_properties_from_file(file); - String8 data = os_string_from_file_range(scratch.arena, file, r1u64(0, props.size)); - if(data.size != 0) - { - cfg_data[src] = data; - cfg_timestamps[src] = props.modified; - } - os_file_close(file); - } - } - - //- rjf: determine if we need to save config - B32 cfg_save[DF_CfgSrc_COUNT] = {0}; - if(file_is_okay) - { - for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) - { - cfg_save[src] = (load_cfg[src] && cfg_files[src]->flags & DF_EntityFlag_IsMissing); - } - } - - //- rjf: determine if we need to reload config - B32 cfg_load[DF_CfgSrc_COUNT] = {0}; - B32 cfg_load_any = 0; - if(file_is_okay) - { - for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) - { - cfg_load[src] = (load_cfg[src] && ((cfg_save[src] == 0 && df_state->cfg_cached_timestamp[src] != cfg_timestamps[src]) || cfg_files[src]->timestamp == 0)); - cfg_load_any = cfg_load_any || cfg_load[src]; - } - } - - //- rjf: load => build new config table - if(cfg_load_any) - { - arena_clear(df_state->cfg_arena); - MemoryZeroStruct(&df_state->cfg_table); - for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) - { - df_cfg_table_push_unparsed_string(df_state->cfg_arena, &df_state->cfg_table, cfg_data[src], src); - } - } - - //- rjf: load => dispatch apply - // - // NOTE(rjf): must happen before `save`. we need to create a default before saving, which - // occurs in the 'apply' path. - // - if(file_is_okay) - { - for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) - { - if(cfg_load[src]) - { - DF_CoreCmdKind cmd_kind = df_g_cfg_src_apply_cmd_kind_table[src]; - DF_CmdParams params = df_cmd_params_zero(); - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(cmd_kind)); - df_state->cfg_cached_timestamp[src] = cfg_timestamps[src]; - } - } - } - - //- rjf: save => dispatch write - if(file_is_okay) - { - for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) - { - if(cfg_save[src]) - { - DF_CoreCmdKind cmd_kind = df_g_cfg_src_write_cmd_kind_table[src]; - DF_CmdParams params = df_cmd_params_zero(); - df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(cmd_kind)); - } - } - } - - //- rjf: bad file -> alert user - if(!file_is_okay) - { - DF_CmdParams p = params; - p.string = push_str8f(scratch.arena, "\"%S\" appears to refer to an existing file which is not a RADDBG config file. This would overwrite the file.", new_path); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - }break; - - //- rjf: loading/applying stateful config changes - case DF_CoreCmdKind_ApplyUserData: - case DF_CoreCmdKind_ApplyProjectData: - { - DF_CfgTable *table = df_cfg_table(); - - //- rjf: get config source - DF_CfgSrc src = DF_CfgSrc_User; - for(DF_CfgSrc s = (DF_CfgSrc)0; s < DF_CfgSrc_COUNT; s = (DF_CfgSrc)(s+1)) - { - if(core_cmd_kind == df_g_cfg_src_apply_cmd_kind_table[s]) - { - src = s; - break; - } - } - - //- rjf: get paths - String8 cfg_path = df_cfg_path_from_src(src); - String8 cfg_folder = str8_chop_last_slash(cfg_path); - - //- rjf: keep track of recent projects - if(src == DF_CfgSrc_Project) - { - DF_Entity *recent_project = df_entity_from_name_and_kind(cfg_path, DF_EntityKind_RecentProject); - if(df_entity_is_nil(recent_project)) - { - recent_project = df_entity_alloc(0, df_entity_root(), DF_EntityKind_RecentProject); - df_entity_equip_name(0, recent_project, cfg_path); - df_entity_equip_cfg_src(recent_project, DF_CfgSrc_User); - } - } - - //- rjf: eliminate all existing entities - { - DF_EntityList rps = df_query_cached_entity_list_with_kind(DF_EntityKind_RecentProject); - for(DF_EntityNode *n = rps.first; n != 0; n = n->next) - { - if(n->entity->cfg_src == src) - { - df_entity_mark_for_deletion(n->entity); - } - } - DF_EntityList targets = df_query_cached_entity_list_with_kind(DF_EntityKind_Target); - for(DF_EntityNode *n = targets.first; n != 0; n = n->next) - { - if(n->entity->cfg_src == src) - { - df_entity_mark_for_deletion(n->entity); - } - } - DF_EntityList bps = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); - for(DF_EntityNode *n = bps.first; n != 0; n = n->next) - { - if(n->entity->cfg_src == src) - { - df_entity_mark_for_deletion(n->entity); - } - } - DF_EntityList pins = df_query_cached_entity_list_with_kind(DF_EntityKind_WatchPin); - for(DF_EntityNode *n = pins.first; n != 0; n = n->next) - { - if(n->entity->cfg_src == src) - { - df_entity_mark_for_deletion(n->entity); - } - } - DF_EntityList links = df_query_cached_entity_list_with_kind(DF_EntityKind_OverrideFileLink); - for(DF_EntityNode *n = links.first; n != 0; n = n->next) - { - if(n->entity->cfg_src == src) - { - df_entity_mark_for_deletion(n->entity); - } - } - } - - //- rjf: apply recent projects - DF_CfgVal *recent_projects = df_cfg_val_from_string(table, str8_lit("recent_project")); - for(DF_CfgNode *rp = recent_projects->first; - rp != &df_g_nil_cfg_node; - rp = rp->next) - { - if(rp->source == src) - { - String8 path_saved = rp->first->string; - String8 path_absolute = path_absolute_dst_from_relative_dst_src(scratch.arena, path_saved, cfg_folder); - DF_Entity *existing = df_entity_from_name_and_kind(path_absolute, DF_EntityKind_RecentProject); - if(df_entity_is_nil(existing)) - { - DF_Entity *rp_ent = df_entity_alloc(0, df_entity_root(), DF_EntityKind_RecentProject); - df_entity_equip_cfg_src(rp_ent, src); - df_entity_equip_name(0, rp_ent, path_absolute); - } - } - } - - //- rjf: apply targets - DF_CfgVal *targets = df_cfg_val_from_string(table, str8_lit("target")); - { - B32 cmd_line_target_present = 0; - { - DF_EntityList existing_target_entities = df_query_cached_entity_list_with_kind(DF_EntityKind_Target); - for(DF_EntityNode *n = existing_target_entities.first; n != 0; n = n->next) - { - DF_Entity *target = n->entity; - if(target->cfg_src == DF_CfgSrc_CommandLine && target->b32) - { - cmd_line_target_present = 1; - } - } - } - for(DF_CfgNode *target = targets->first; - target != &df_g_nil_cfg_node; - target = target->next) - { - if(target->source == src) - { - DF_CfgNode *label_cfg = df_cfg_node_child_from_string(target, str8_lit("label"), StringMatchFlag_CaseInsensitive); - DF_CfgNode *exe_cfg = df_cfg_node_child_from_string(target, str8_lit("exe"), StringMatchFlag_CaseInsensitive); - if(exe_cfg == &df_g_nil_cfg_node) - { - exe_cfg = df_cfg_node_child_from_string(target, str8_lit("name"), StringMatchFlag_CaseInsensitive); - } - DF_CfgNode *args_cfg = df_cfg_node_child_from_string(target, str8_lit("arguments"), StringMatchFlag_CaseInsensitive); - DF_CfgNode *wdir_cfg = df_cfg_node_child_from_string(target, str8_lit("working_directory"), StringMatchFlag_CaseInsensitive); - DF_CfgNode *entry_cfg = df_cfg_node_child_from_string(target, str8_lit("entry_point"), StringMatchFlag_CaseInsensitive); - DF_CfgNode *active_cfg = df_cfg_node_child_from_string(target, str8_lit("active"), StringMatchFlag_CaseInsensitive); - Vec4F32 hsva = df_hsva_from_cfg_node(target); - U64 is_active_u64 = 0; - if(!cmd_line_target_present) - { - try_u64_from_str8_c_rules(active_cfg->first->string, &is_active_u64); - } - DF_Entity *target__ent = df_entity_alloc(0, df_entity_root(), DF_EntityKind_Target); - DF_Entity *exe__ent = df_entity_alloc(0, target__ent, DF_EntityKind_Executable); - DF_Entity *args__ent = df_entity_alloc(0, target__ent, DF_EntityKind_Arguments); - DF_Entity *path__ent = df_entity_alloc(0, target__ent, DF_EntityKind_ExecutionPath); - DF_Entity *entry__ent = df_entity_alloc(0, target__ent, DF_EntityKind_EntryPointName); - String8 saved_label = label_cfg->first->string; - String8 saved_exe = exe_cfg->first->string; - String8 saved_exe_absolute = path_absolute_dst_from_relative_dst_src(scratch.arena, saved_exe, cfg_folder); - String8 saved_wdir = wdir_cfg->first->string; - String8 saved_wdir_absolute = path_absolute_dst_from_relative_dst_src(scratch.arena, saved_wdir, cfg_folder); - String8 saved_entry_point = entry_cfg->first->string; - String8 saved_label_raw = df_cfg_raw_from_escaped_string(scratch.arena, saved_label); - String8 saved_entry_raw = df_cfg_raw_from_escaped_string(scratch.arena, saved_entry_point); - String8 saved_args_raw = df_cfg_raw_from_escaped_string(scratch.arena, args_cfg->first->string); - df_entity_equip_b32(target__ent, active_cfg != &df_g_nil_cfg_node ? !!is_active_u64 : 1); - df_entity_equip_name(0, target__ent, saved_label_raw); - df_entity_equip_name(0, exe__ent, saved_exe_absolute); - df_entity_equip_name(0, args__ent, saved_args_raw); - df_entity_equip_name(0, path__ent, saved_wdir_absolute); - df_entity_equip_name(0, entry__ent, saved_entry_raw); - df_entity_equip_cfg_src(target__ent, src); - if(!memory_is_zero(&hsva, sizeof(hsva))) - { - df_entity_equip_color_hsva(target__ent, hsva); - } - } - } - } - - //- rjf: apply path maps - DF_CfgVal *path_maps = df_cfg_val_from_string(table, str8_lit("file_path_map")); - for(DF_CfgNode *map = path_maps->first; - map != &df_g_nil_cfg_node; - map = map->next) - { - if(map->source == src) - { - DF_CfgNode *src_cfg = df_cfg_node_child_from_string(map, str8_lit("source_path"), StringMatchFlag_CaseInsensitive); - DF_CfgNode *dst_cfg = df_cfg_node_child_from_string(map, str8_lit("dest_path"), StringMatchFlag_CaseInsensitive); - String8 src_path = src_cfg->first->string; - String8 dst_path = dst_cfg->first->string; - DF_Entity *link_loc_entity = df_entity_from_path(src_path, DF_EntityFromPathFlag_OpenAsNeeded|DF_EntityFromPathFlag_OpenMissing); - DF_Entity *link_entity = df_entity_alloc(0, link_loc_entity->parent, DF_EntityKind_OverrideFileLink); - DF_Entity *link_dst_entity = df_entity_from_path(dst_path, DF_EntityFromPathFlag_OpenAsNeeded|DF_EntityFromPathFlag_OpenMissing); - df_entity_equip_name(0, link_entity, str8_skip_last_slash(src_path)); - df_entity_equip_entity_handle(link_entity, df_handle_from_entity(link_dst_entity)); - df_entity_equip_cfg_src(link_entity, src); - } - } - - //- rjf: apply auto view rules - DF_CfgVal *avrs = df_cfg_val_from_string(table, str8_lit("auto_view_rule")); - for(DF_CfgNode *map = avrs->first; - map != &df_g_nil_cfg_node; - map = map->next) - { - if(map->source == src) - { - DF_CfgNode *src_cfg = df_cfg_node_child_from_string(map, str8_lit("type"), StringMatchFlag_CaseInsensitive); - DF_CfgNode *dst_cfg = df_cfg_node_child_from_string(map, str8_lit("view_rule"), StringMatchFlag_CaseInsensitive); - String8 type = src_cfg->first->string; - String8 view_rule = dst_cfg->first->string; - type = df_cfg_raw_from_escaped_string(scratch.arena, type); - view_rule = df_cfg_raw_from_escaped_string(scratch.arena, view_rule); - DF_Entity *map_entity = df_entity_alloc(0, df_entity_root(), DF_EntityKind_AutoViewRule); - DF_Entity *src_entity = df_entity_alloc(0, map_entity, DF_EntityKind_Source); - DF_Entity *dst_entity = df_entity_alloc(0, map_entity, DF_EntityKind_Dest); - df_entity_equip_name(0, src_entity, type); - df_entity_equip_name(0, dst_entity, view_rule); - df_entity_equip_cfg_src(map_entity, src); - } - } - - //- rjf: apply breakpoints - DF_CfgVal *bps = df_cfg_val_from_string(table, str8_lit("breakpoint")); - for(DF_CfgNode *bp = bps->first; - bp != &df_g_nil_cfg_node; - bp = bp->next) - { - if(bp->source != src) - { - continue; - } - - // rjf: get metadata - Vec4F32 hsva = df_hsva_from_cfg_node(bp); - - // rjf: get nodes encoding location info - B32 is_enabled = 1; - DF_CfgNode *line_cfg = &df_g_nil_cfg_node; - DF_CfgNode *addr_cfg = &df_g_nil_cfg_node; - DF_CfgNode *symb_cfg = &df_g_nil_cfg_node; - DF_CfgNode *labl_cfg = &df_g_nil_cfg_node; - for(DF_CfgNode *child = bp->first; child != &df_g_nil_cfg_node; child = child->next) - { - if(child->flags & DF_CfgNodeFlag_Identifier && str8_match(child->string, str8_lit("line"), StringMatchFlag_CaseInsensitive)) - { - line_cfg = child; - } - if(child->flags & DF_CfgNodeFlag_Identifier && str8_match(child->string, str8_lit("addr"), StringMatchFlag_CaseInsensitive)) - { - addr_cfg = child; - } - if(child->flags & DF_CfgNodeFlag_Identifier && str8_match(child->string, str8_lit("symbol"), StringMatchFlag_CaseInsensitive)) - { - symb_cfg = child; - } - else if(child->flags & DF_CfgNodeFlag_Identifier && str8_match(child->string, str8_lit("label"), StringMatchFlag_CaseInsensitive)) - { - labl_cfg = child; - } - else if(child->flags & DF_CfgNodeFlag_Identifier && str8_match(child->string, str8_lit("enabled"), StringMatchFlag_CaseInsensitive)) - { - U64 is_enabled_u64 = 0; - try_u64_from_str8_c_rules(child->first->string, &is_enabled_u64); - is_enabled = (B32)is_enabled_u64; - } - } - - // rjf: extract textual location bp info - DF_Entity *bp_parent_ent = df_entity_root(); - TxtPt pt = {0}; - if(line_cfg != &df_g_nil_cfg_node) - { - DF_CfgNode *file = line_cfg->first; - DF_CfgNode *line = file->first; - U64 line_num = 0; - if(try_u64_from_str8_c_rules(line->string, &line_num)) - { - String8 saved_path = file->string; - String8 saved_path_absolute = path_absolute_dst_from_relative_dst_src(scratch.arena, saved_path, cfg_folder); - bp_parent_ent = df_entity_from_path(saved_path_absolute, DF_EntityFromPathFlag_All); - pt = txt_pt((S64)line_num, 1); - } - } - - // rjf: get condition info - DF_CfgNode *cond_cfg = df_cfg_node_child_from_string(bp, str8_lit("condition"), StringMatchFlag_CaseInsensitive); - - // rjf: build entity - { - DF_Entity *bp_ent = df_entity_alloc(0, bp_parent_ent, DF_EntityKind_Breakpoint); - df_entity_equip_b32(bp_ent, is_enabled); - df_entity_equip_cfg_src(bp_ent, src); - if(pt.line != 0) - { - df_entity_equip_txt_pt(bp_ent, pt); - } - if(addr_cfg != &df_g_nil_cfg_node) - { - U64 u64 = 0; - try_u64_from_str8_c_rules(addr_cfg->first->string, &u64); - df_entity_equip_vaddr(bp_ent, u64); - } - if(symb_cfg != &df_g_nil_cfg_node) - { - String8 symb_raw = df_cfg_raw_from_escaped_string(scratch.arena, symb_cfg->first->string); - DF_Entity *symb = df_entity_alloc(0, bp_ent, DF_EntityKind_EntryPointName); - df_entity_equip_name(0, symb, symb_raw); - } - if(labl_cfg->string.size != 0) - { - String8 label_raw = df_cfg_raw_from_escaped_string(scratch.arena, labl_cfg->string); - df_entity_equip_name(0, bp_ent, label_raw); - } - if(!memory_is_zero(&hsva, sizeof(hsva))) - { - df_entity_equip_color_hsva(bp_ent, hsva); - } - if(cond_cfg->first->string.size != 0) - { - String8 cond_raw = df_cfg_raw_from_escaped_string(scratch.arena, cond_cfg->first->string); - DF_Entity *cond = df_entity_alloc(0, bp_ent, DF_EntityKind_Condition); - df_entity_equip_name(0, cond, cond_raw); - } - } - } - - //- rjf: apply watch pins - DF_CfgVal *pins = df_cfg_val_from_string(table, str8_lit("watch_pin")); - for(DF_CfgNode *pin = pins->first; - pin != &df_g_nil_cfg_node; - pin = pin->next) - { - if(pin->source != src) - { - continue; - } - Vec4F32 hsva = df_hsva_from_cfg_node(pin); - String8 string = df_string_from_cfg_node_key(pin, str8_lit("expression"), StringMatchFlag_CaseInsensitive); - String8 string_raw = df_cfg_raw_from_escaped_string(scratch.arena, string); - DF_CfgNode *line_cfg = df_cfg_node_child_from_string(pin, str8_lit("line"), StringMatchFlag_CaseInsensitive); - DF_CfgNode *addr_cfg = df_cfg_node_child_from_string(pin, str8_lit("addr"), StringMatchFlag_CaseInsensitive); - DF_Entity *pin_parent_ent = df_entity_root(); - TxtPt pt = {0}; - if(line_cfg != &df_g_nil_cfg_node) - { - String8 saved_path = line_cfg->first->string; - String8 line_num_string = line_cfg->first->first->string; - String8 saved_path_absolute = path_absolute_dst_from_relative_dst_src(scratch.arena, saved_path, cfg_folder); - pin_parent_ent = df_entity_from_path(saved_path_absolute, DF_EntityFromPathFlag_All); - U64 line_num = 0; - if(try_u64_from_str8_c_rules(line_num_string, &line_num)) - { - if(line_num != 0) - { - pt = txt_pt((S64)line_num, 1); - } - } - } - U64 vaddr = 0; - if(addr_cfg != &df_g_nil_cfg_node) - { - try_u64_from_str8_c_rules(addr_cfg->first->string, &vaddr); - } - DF_Entity *pin_ent = df_entity_alloc(0, pin_parent_ent, DF_EntityKind_WatchPin); - df_entity_equip_cfg_src(pin_ent, src); - df_entity_equip_name(0, pin_ent, string_raw); - if(!memory_is_zero(&hsva, sizeof(hsva))) - { - df_entity_equip_color_hsva(pin_ent, hsva); - } - if(pt.line != 0) - { - df_entity_equip_txt_pt(pin_ent, pt); - } - if(vaddr != 0) - { - df_entity_equip_vaddr(pin_ent, vaddr); - } - } - - //- rjf: apply exception code filters - DF_CfgVal *filter_tables = df_cfg_val_from_string(table, str8_lit("exception_code_filters")); - for(DF_CfgNode *table = filter_tables->first; - table != &df_g_nil_cfg_node; - table = table->next) - { - for(DF_CfgNode *rule = table->first; - rule != &df_g_nil_cfg_node; - rule = rule->next) - { - String8 name = rule->string; - String8 val_string = rule->first->string; - U64 val = 0; - if(try_u64_from_str8_c_rules(val_string, &val)) - { - CTRL_ExceptionCodeKind kind = CTRL_ExceptionCodeKind_Null; - for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)(CTRL_ExceptionCodeKind_Null+1); - k < CTRL_ExceptionCodeKind_COUNT; - k = (CTRL_ExceptionCodeKind)(k+1)) - { - if(str8_match(name, ctrl_exception_code_kind_lowercase_code_string_table[k], 0)) - { - kind = k; - break; - } - } - if(kind != CTRL_ExceptionCodeKind_Null) - { - if(val) - { - df_state->ctrl_exception_code_filters[kind/64] |= (1ull<<(kind%64)); - } - else - { - df_state->ctrl_exception_code_filters[kind/64] &= ~(1ull<<(kind%64)); - } - } - } - } - } - }break; - - //- rjf: writing config changes - case DF_CoreCmdKind_WriteUserData: - case DF_CoreCmdKind_WriteProjectData: - { - DF_CfgSrc src = DF_CfgSrc_User; - for(DF_CfgSrc s = (DF_CfgSrc)0; s < DF_CfgSrc_COUNT; s = (DF_CfgSrc)(s+1)) - { - if(core_cmd_kind == df_g_cfg_src_write_cmd_kind_table[s]) - { - src = s; - break; - } - } - arena_clear(df_state->cfg_write_arenas[src]); - MemoryZeroStruct(&df_state->cfg_write_data[src]); - String8 path = df_cfg_path_from_src(src); - String8List strs = df_cfg_strings_from_core(scratch.arena, path, src); - String8 header = push_str8f(scratch.arena, "// raddbg %s file\n\n", df_g_cfg_src_string_table[src].str); - str8_list_push_front(scratch.arena, &strs, header); - String8 data = str8_list_join(scratch.arena, &strs, 0); - df_state->cfg_write_issued[src] = 1; - df_cfg_push_write_string(src, data); - }break; - - //- rjf: override file links - case DF_CoreCmdKind_SetFileOverrideLinkSrc: - case DF_CoreCmdKind_SetFileOverrideLinkDst: - { - // rjf: unpack args - DF_Entity *map = df_entity_from_handle(params.entity); - String8 path = path_normalized_from_string(scratch.arena, params.file_path); - String8 path_folder = str8_chop_last_slash(path); - String8 path_file = str8_skip_last_slash(path); - - // rjf: src -> move map & commit name; dst -> open destination file & refer to it in map - switch(core_cmd_kind) - { - default:{}break; - case DF_CoreCmdKind_SetFileOverrideLinkSrc: - { - DF_Entity *map_parent = (params.file_path.size != 0) ? df_entity_from_path(path_folder, DF_EntityFromPathFlag_OpenAsNeeded|DF_EntityFromPathFlag_OpenMissing) : df_entity_root(); - if(df_entity_is_nil(map)) - { - map = df_entity_alloc(0, map_parent, DF_EntityKind_OverrideFileLink); - } - else - { - df_entity_change_parent(0, map, map->parent, map_parent); - } - df_entity_equip_name(0, map, path_file); - }break; - case DF_CoreCmdKind_SetFileOverrideLinkDst: - { - if(df_entity_is_nil(map)) - { - map = df_entity_alloc(0, df_entity_root(), DF_EntityKind_OverrideFileLink); - } - DF_Entity *map_dst_entity = &df_g_nil_entity; - if(params.file_path.size != 0) - { - map_dst_entity = df_entity_from_path(path, DF_EntityFromPathFlag_All); - } - df_entity_equip_entity_handle(map, df_handle_from_entity(map_dst_entity)); - }break; - } - - // rjf: empty src/dest -> delete - if(!df_entity_is_nil(map) && map->name.size == 0 && df_entity_is_nil(df_entity_from_handle(map->entity_handle))) - { - df_entity_mark_for_deletion(map); - } - }break; - case DF_CoreCmdKind_SetFileReplacementPath: - { - // NOTE(rjf): - // - // C:/foo/bar/baz.c - // D:/foo/bar/baz.c - // -> override C: -> D: - // - // C:/1/2/foo/bar.c - // C:/2/3/foo/bar.c - // -> override C:/1/2 -> C:2/3 - // - // C:/foo/bar/baz.c - // D:/1/2/3.c - // -> override C:/foo/bar/baz.c -> D:/1/2/3.c - - //- rjf: grab src file & chosen replacement - DF_Entity *file = df_entity_from_handle(params.entity); - DF_Entity *replacement = df_entity_from_path(params.file_path, DF_EntityFromPathFlag_OpenAsNeeded|DF_EntityFromPathFlag_OpenMissing); - - //- rjf: find - DF_Entity *first_diff_src = file; - DF_Entity *first_diff_dst = replacement; - for(;!df_entity_is_nil(first_diff_src) && !df_entity_is_nil(first_diff_dst);) - { - if(!str8_match(first_diff_src->name, first_diff_dst->name, StringMatchFlag_CaseInsensitive) || - first_diff_src->parent->kind != DF_EntityKind_File || - first_diff_src->parent->parent->kind != DF_EntityKind_File || - first_diff_dst->parent->kind != DF_EntityKind_File || - first_diff_dst->parent->parent->kind != DF_EntityKind_File) - { - break; - } - first_diff_src = first_diff_src->parent; - first_diff_dst = first_diff_dst->parent; - } - - //- rjf: override first different - if(!df_entity_is_nil(first_diff_src) && !df_entity_is_nil(first_diff_dst)) - { - DF_Entity *link = df_entity_child_from_name_and_kind(first_diff_src->parent, first_diff_src->name, DF_EntityKind_OverrideFileLink); - if(df_entity_is_nil(link)) - { - link = df_entity_alloc(0, first_diff_src->parent, DF_EntityKind_OverrideFileLink); - df_entity_equip_name(0, link, first_diff_src->name); - } - df_entity_equip_entity_handle(link, df_handle_from_entity(first_diff_dst)); - } - }break; - - //- rjf: auto view rules - case DF_CoreCmdKind_SetAutoViewRuleType: - case DF_CoreCmdKind_SetAutoViewRuleViewRule: - { - DF_Entity *map = df_entity_from_handle(params.entity); - if(df_entity_is_nil(map)) - { - map = df_entity_alloc(df_state_delta_history(), df_entity_root(), DF_EntityKind_AutoViewRule); - df_entity_equip_cfg_src(map, DF_CfgSrc_Project); - } - DF_Entity *src = df_entity_child_from_kind(map, DF_EntityKind_Source); - if(df_entity_is_nil(src)) - { - src = df_entity_alloc(df_state_delta_history(), map, DF_EntityKind_Source); - } - DF_Entity *dst = df_entity_child_from_kind(map, DF_EntityKind_Dest); - if(df_entity_is_nil(dst)) - { - dst = df_entity_alloc(df_state_delta_history(), map, DF_EntityKind_Dest); - } - if(map->kind == DF_EntityKind_AutoViewRule) - { - DF_Entity *edit_child = (core_cmd_kind == DF_CoreCmdKind_SetAutoViewRuleType ? src : dst); - df_entity_equip_name(df_state_delta_history(), edit_child, params.string); - } - if(src->name.size == 0 && dst->name.size == 0) - { - df_entity_mark_for_deletion(map); - } - { - DF_AutoViewRuleMapCache *cache = &df_state->auto_view_rule_cache; - if(cache->arena == 0) - { - cache->arena = arena_alloc(); - } - arena_clear(cache->arena); - cache->slots_count = 1024; - cache->slots = push_array(cache->arena, DF_AutoViewRuleSlot, cache->slots_count); - DF_EntityList maps = df_query_cached_entity_list_with_kind(DF_EntityKind_AutoViewRule); - for(DF_EntityNode *n = maps.first; n != 0; n = n->next) - { - DF_Entity *map = n->entity; - DF_Entity *src = df_entity_child_from_kind(map, DF_EntityKind_Source); - DF_Entity *dst = df_entity_child_from_kind(map, DF_EntityKind_Dest); - String8 type = src->name; - String8 view_rule = dst->name; - U64 hash = df_hash_from_string(type); - U64 slot_idx = hash%cache->slots_count; - DF_AutoViewRuleSlot *slot = &cache->slots[slot_idx]; - DF_AutoViewRuleNode *node = push_array(cache->arena, DF_AutoViewRuleNode, 1); - node->type = push_str8_copy(cache->arena, type); - node->view_rule = push_str8_copy(cache->arena, view_rule); - SLLQueuePush(slot->first, slot->last, node); - } - } - }break; - - //- rjf: general entity operations - case DF_CoreCmdKind_EnableEntity: - case DF_CoreCmdKind_EnableBreakpoint: - case DF_CoreCmdKind_EnableTarget: - { - DF_Entity *entity = df_entity_from_handle(params.entity); - df_state_delta_history_push_batch(df_state->hist, &entity->generation); - df_state_delta_history_push_struct_delta(df_state->hist, &entity->b32); - df_entity_equip_b32(entity, 1); - }break; - case DF_CoreCmdKind_DisableEntity: - case DF_CoreCmdKind_DisableBreakpoint: - case DF_CoreCmdKind_DisableTarget: - { - DF_Entity *entity = df_entity_from_handle(params.entity); - df_state_delta_history_push_batch(df_state->hist, &entity->generation); - df_state_delta_history_push_struct_delta(df_state->hist, &entity->b32); - df_entity_equip_b32(entity, 0); - }break; - case DF_CoreCmdKind_FreezeEntity: - case DF_CoreCmdKind_ThawEntity: - { - B32 should_freeze = (core_cmd_kind == DF_CoreCmdKind_FreezeEntity); - DF_Entity *root = df_entity_from_handle(params.entity); - for(DF_Entity *e = root; !df_entity_is_nil(e); e = df_entity_rec_df_pre(e, root).next) - { - if(e->kind == DF_EntityKind_Thread) - { - df_set_thread_freeze_state(e, should_freeze); - } - } - }break; - case DF_CoreCmdKind_RemoveEntity: - case DF_CoreCmdKind_RemoveBreakpoint: - case DF_CoreCmdKind_RemoveTarget: - { - DF_Entity *entity = df_entity_from_handle(params.entity); - DF_EntityOpFlags op_flags = df_g_entity_kind_op_flags_table[entity->kind]; - if(op_flags & DF_EntityOpFlag_Delete) - { - df_entity_mark_for_deletion(entity); - } - }break; - case DF_CoreCmdKind_NameEntity: - { - DF_Entity *entity = df_entity_from_handle(params.entity); - String8 string = params.string; - df_state_delta_history_push_batch(df_state_delta_history(), &entity->generation); - df_entity_equip_name(df_state_delta_history(), entity, string); - }break; - case DF_CoreCmdKind_EditEntity:{}break; - case DF_CoreCmdKind_DuplicateEntity: - { - DF_Entity *src = df_entity_from_handle(params.entity); - if(!df_entity_is_nil(src)) - { - typedef struct Task Task; - struct Task - { - Task *next; - DF_Entity *src_n; - DF_Entity *dst_parent; - }; - Task starter_task = {0, src, src->parent}; - Task *first_task = &starter_task; - Task *last_task = &starter_task; - df_state_delta_history_push_batch(df_state_delta_history(), 0); - for(Task *task = first_task; task != 0; task = task->next) - { - DF_Entity *src_n = task->src_n; - DF_Entity *dst_n = df_entity_alloc(df_state_delta_history(), task->dst_parent, task->src_n->kind); - if(src_n->flags & DF_EntityFlag_HasTextPoint) {df_entity_equip_txt_pt(dst_n, src_n->text_point);} - if(src_n->flags & DF_EntityFlag_HasTextPointAlt) {df_entity_equip_txt_pt_alt(dst_n, src_n->text_point_alt);} - if(src_n->flags & DF_EntityFlag_HasB32) {df_entity_equip_b32(dst_n, src_n->b32);} - if(src_n->flags & DF_EntityFlag_HasU64) {df_entity_equip_u64(dst_n, src_n->u64);} - if(src_n->flags & DF_EntityFlag_HasRng1U64) {df_entity_equip_rng1u64(dst_n, src_n->rng1u64);} - if(src_n->flags & DF_EntityFlag_HasColor) {df_entity_equip_color_hsva(dst_n, df_hsva_from_entity(src_n));} - if(src_n->flags & DF_EntityFlag_HasVAddrRng) {df_entity_equip_vaddr_rng(dst_n, src_n->vaddr_rng);} - if(src_n->flags & DF_EntityFlag_HasVAddr) {df_entity_equip_vaddr(dst_n, src_n->vaddr);} - if(src_n->name.size != 0) {df_entity_equip_name(df_state_delta_history(), dst_n, src_n->name);} - dst_n->cfg_src = src_n->cfg_src; - for(DF_Entity *src_child = task->src_n->first; !df_entity_is_nil(src_child); src_child = src_child->next) - { - Task *child_task = push_array(scratch.arena, Task, 1); - child_task->src_n = src_child; - child_task->dst_parent = dst_n; - SLLQueuePush(first_task, last_task, child_task); - } - } - } - }break; - - //- rjf: breakpoints - case DF_CoreCmdKind_TextBreakpoint: - { - DF_Entity *entity = df_entity_from_handle(params.entity); - if(df_entity_is_nil(entity)) - { - entity = df_entity_from_path(params.file_path, 0); - } - if(!df_entity_is_nil(entity)) - { - S64 line_num = params.text_point.line; - B32 removed_existing = 0; - for(DF_Entity *child = entity->first, *next = 0; !df_entity_is_nil(child); child = next) - { - next = child->next; - if(child->deleted) { continue; } - if(child->kind == DF_EntityKind_Breakpoint && child->flags & DF_EntityFlag_HasTextPoint && child->text_point.line == line_num) - { - removed_existing = 1; - df_entity_mark_for_deletion(child); - } - } - if(removed_existing == 0) - { - df_state_delta_history_push_batch(df_state_delta_history(), 0); - DF_Entity *bp = df_entity_alloc(df_state_delta_history(), entity, DF_EntityKind_Breakpoint); - df_entity_equip_txt_pt(bp, params.text_point); - df_entity_equip_b32(bp, 1); - df_entity_equip_cfg_src(bp, DF_CfgSrc_Project); - } - } - }break; - case DF_CoreCmdKind_AddressBreakpoint: - { - U64 vaddr = params.vaddr; - if(vaddr != 0) - { - DF_Entity *bp = &df_g_nil_entity; - DF_EntityList existing_bps = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); - for(DF_EntityNode *n = existing_bps.first; n != 0; n = n->next) - { - if(n->entity->vaddr == vaddr) - { - bp = n->entity; - break; - } - } - if(df_entity_is_nil(bp)) - { - df_state_delta_history_push_batch(df_state_delta_history(), 0); - bp = df_entity_alloc(df_state_delta_history(), df_entity_root(), DF_EntityKind_Breakpoint); - df_entity_equip_vaddr(bp, vaddr); - df_entity_equip_b32(bp, 1); - df_entity_equip_cfg_src(bp, DF_CfgSrc_Project); - } - else - { - df_entity_mark_for_deletion(bp); - } - } - }break; - case DF_CoreCmdKind_FunctionBreakpoint: - { - String8 function_name = params.string; - if(function_name.size != 0) - { - DF_Entity *symb = df_entity_from_name_and_kind(function_name, DF_EntityKind_EntryPointName); - DF_Entity *bp = df_entity_ancestor_from_kind(symb, DF_EntityKind_Breakpoint); - if(df_entity_is_nil(bp)) - { - df_state_delta_history_push_batch(df_state_delta_history(), 0); - bp = df_entity_alloc(df_state_delta_history(), df_entity_root(), DF_EntityKind_Breakpoint); - DF_Entity *symbol_name_entity = df_entity_alloc(df_state_delta_history(), bp, DF_EntityKind_EntryPointName); - df_entity_equip_name(df_state_delta_history(), symbol_name_entity, function_name); - df_entity_equip_b32(bp, 1); - df_entity_equip_cfg_src(bp, DF_CfgSrc_Project); - } - else - { - df_entity_mark_for_deletion(bp); - } - } - }break; - - //- rjf: watches - case DF_CoreCmdKind_ToggleWatchPin: - { - DF_Entity *entity = df_entity_from_handle(params.entity); - S64 line_num = params.text_point.line; - if(!df_entity_is_nil(entity) && line_num != 0) - { - B32 removed_existing = 0; - for(DF_Entity *child = entity->first, *next = 0; !df_entity_is_nil(child); child = next) - { - next = child->next; - if(child->deleted) { continue; } - if(child->kind == DF_EntityKind_WatchPin && child->flags & DF_EntityFlag_HasTextPoint && child->text_point.line == line_num && - str8_match(child->name, params.string, 0)) - { - removed_existing = 1; - df_entity_mark_for_deletion(child); - } - } - if(removed_existing == 0) - { - df_state_delta_history_push_batch(df_state_delta_history(), 0); - DF_Entity *watch = df_entity_alloc(df_state_delta_history(), entity, DF_EntityKind_WatchPin); - df_entity_equip_txt_pt(watch, params.text_point); - df_entity_equip_name(df_state_delta_history(), watch, params.string); - df_entity_equip_cfg_src(watch, DF_CfgSrc_Project); - } - } - else if(params.vaddr != 0) - { - B32 removed_existing = 0; - DF_EntityList pins = df_query_cached_entity_list_with_kind(DF_EntityKind_WatchPin); - for(DF_EntityNode *n = pins.first; n != 0; n = n->next) - { - DF_Entity *pin = n->entity; - if(pin->flags & DF_EntityFlag_HasVAddr && pin->vaddr == params.vaddr && str8_match(pin->name, params.string, 0)) - { - removed_existing = 1; - df_entity_mark_for_deletion(pin); - } - } - if(!removed_existing) - { - df_state_delta_history_push_batch(df_state_delta_history(), 0); - DF_Entity *pin = df_entity_alloc(df_state_delta_history(), df_entity_root(), DF_EntityKind_WatchPin); - df_entity_equip_vaddr(pin, params.vaddr); - df_entity_equip_name(df_state_delta_history(), pin, params.string); - df_entity_equip_cfg_src(pin, DF_CfgSrc_Project); - } - } - }break; - - //- rjf: cursor operations - case DF_CoreCmdKind_ToggleBreakpointAtCursor: - { - DF_InteractRegs *regs = df_interact_regs(); - DF_Entity *file = df_entity_from_handle(regs->file); - if(file->kind == DF_EntityKind_File && regs->cursor.line != 0) - { - DF_CmdParams p = df_cmd_params_zero(); - p.entity = df_handle_from_entity(file); - p.text_point = regs->cursor; - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_TextBreakpoint)); - } - else if(regs->vaddr_range.min != 0) - { - DF_CmdParams p = df_cmd_params_zero(); - p.vaddr = regs->vaddr_range.min; - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_AddressBreakpoint)); - } - }break; - case DF_CoreCmdKind_ToggleWatchPinAtCursor: - { - DF_InteractRegs *regs = df_interact_regs(); - DF_Entity *file = df_entity_from_handle(regs->file); - if(file->kind == DF_EntityKind_File && regs->cursor.line != 0) - { - DF_CmdParams p = df_cmd_params_zero(); - p.entity = df_handle_from_entity(file); - p.text_point = regs->cursor; - p.string = params.string; - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_ToggleWatchPin)); - } - else if(regs->vaddr_range.min != 0) - { - DF_CmdParams p = df_cmd_params_zero(); - p.vaddr = regs->vaddr_range.min; - p.string = params.string; - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_ToggleWatchPin)); - } - }break; - case DF_CoreCmdKind_GoToNameAtCursor: - case DF_CoreCmdKind_ToggleWatchExpressionAtCursor: - { - HS_Scope *hs_scope = hs_scope_open(); - TXT_Scope *txt_scope = txt_scope_open(); - DF_InteractRegs *regs = df_interact_regs(); - U128 text_key = regs->text_key; - TXT_LangKind lang_kind = regs->lang_kind; - TxtRng range = txt_rng(regs->cursor, regs->mark); - U128 hash = {0}; - TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, text_key, lang_kind, &hash); - String8 data = hs_data_from_hash(hs_scope, hash); - Rng1U64 expr_off_range = {0}; - if(range.min.column != range.max.column) - { - expr_off_range = r1u64(txt_off_from_info_pt(&info, range.min), txt_off_from_info_pt(&info, range.max)); - } - else - { - expr_off_range = txt_expr_off_range_from_info_data_pt(&info, data, range.min); - } - String8 expr = str8_substr(data, expr_off_range); - DF_CmdParams p = df_cmd_params_zero(); - p.string = expr; - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(core_cmd_kind == DF_CoreCmdKind_GoToNameAtCursor ? DF_CoreCmdKind_GoToName : - core_cmd_kind == DF_CoreCmdKind_ToggleWatchExpressionAtCursor ? DF_CoreCmdKind_ToggleWatchExpression : - DF_CoreCmdKind_GoToName)); - txt_scope_close(txt_scope); - hs_scope_close(hs_scope); - }break; - case DF_CoreCmdKind_RunToCursor: - { - DF_Entity *file = df_entity_from_handle(df_interact_regs()->file); - if(!df_entity_is_nil(file)) - { - DF_CmdParams p = df_cmd_params_zero(); - p.entity = df_handle_from_entity(file); - p.text_point = df_interact_regs()->cursor; - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunToLine)); - } - else - { - DF_CmdParams p = df_cmd_params_zero(); - p.vaddr = df_interact_regs()->vaddr_range.min; - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunToAddress)); - } - }break; - case DF_CoreCmdKind_SetNextStatement: - { - DF_Entity *file = df_entity_from_handle(df_interact_regs()->file); - DF_Entity *thread = df_entity_from_handle(df_interact_regs()->thread); - U64 new_rip_vaddr = df_interact_regs()->vaddr_range.min; - if(!df_entity_is_nil(file)) - { - DF_LineList *lines = &df_interact_regs()->lines; - for(DF_LineNode *n = lines->first; n != 0; n = n->next) - { - DF_EntityList modules = df_modules_from_dbgi_key(scratch.arena, &n->v.dbgi_key); - DF_Entity *module = df_module_from_thread_candidates(thread, &modules); - if(!df_entity_is_nil(module)) - { - new_rip_vaddr = df_vaddr_from_voff(module, n->v.voff_range.min); - break; - } - } - } - DF_CmdParams p = df_cmd_params_zero(); - p.entity = df_handle_from_entity(thread); - p.vaddr = new_rip_vaddr; - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SetThreadIP)); - }break; - - //- rjf: targets - case DF_CoreCmdKind_AddTarget: - { - // rjf: build target - df_state_delta_history_push_batch(df_state_delta_history(), 0); - DF_Entity *entity = df_entity_alloc(df_state_delta_history(), df_entity_root(), DF_EntityKind_Target); - df_entity_equip_cfg_src(entity, DF_CfgSrc_Project); - DF_Entity *exe = df_entity_alloc(df_state_delta_history(), entity, DF_EntityKind_Executable); - df_entity_equip_name(df_state_delta_history(), exe, params.file_path); - String8 working_dir = str8_chop_last_slash(params.file_path); - if(working_dir.size != 0) - { - String8 working_dir_path = push_str8f(scratch.arena, "%S/", working_dir); - DF_Entity *execution_path = df_entity_alloc(df_state_delta_history(), entity, DF_EntityKind_ExecutionPath); - df_entity_equip_name(df_state_delta_history(), execution_path, working_dir_path); - } - DF_CmdParams p = params; - p.entity = df_handle_from_entity(entity); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_Entity); - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_EditTarget)); - df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectTarget)); - }break; - case DF_CoreCmdKind_SelectTarget: - { - DF_Entity *entity = df_entity_from_handle(params.entity); - if(entity->kind == DF_EntityKind_Target) - { - DF_EntityList all_targets = df_query_cached_entity_list_with_kind(DF_EntityKind_Target); - B32 is_selected = entity->b32; - for(DF_EntityNode *n = all_targets.first; n != 0; n = n->next) - { - DF_Entity *target = n->entity; - df_entity_equip_b32(target, 0); - } - if(!is_selected) - { - df_entity_equip_b32(entity, 1); - } - } - }break; - - //- rjf: ended processes - case DF_CoreCmdKind_RetryEndedProcess: - { - DF_Entity *ended_process = df_entity_from_handle(params.entity); - DF_Entity *target = df_entity_from_handle(ended_process->entity_handle); - if(target->kind == DF_EntityKind_Target) - { - DF_CmdParams p = params; - p.entity = df_handle_from_entity(target); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_LaunchAndRun)); - } - else if(df_entity_is_nil(target)) - { - DF_CmdParams p = params; - p.string = str8_lit("The ended process' corresponding target is missing."); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - else if(df_entity_is_nil(ended_process)) - { - DF_CmdParams p = params; - p.string = str8_lit("Invalid ended process."); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - }break; - - //- rjf: attaching - case DF_CoreCmdKind_Attach: - { - U64 pid = params.id; - if(pid != 0) - { - CTRL_Msg msg = {CTRL_MsgKind_Attach}; - msg.entity_id = (U32)pid; - MemoryCopyArray(msg.exception_code_filters, df_state->ctrl_exception_code_filters); - df_push_ctrl_msg(&msg); - } - }break; - - //- rjf: jit-debugger registration - case DF_CoreCmdKind_RegisterAsJITDebugger: - { -#if OS_WINDOWS - char filename_cstr[MAX_PATH] = {0}; - GetModuleFileName(0, filename_cstr, sizeof(filename_cstr)); - String8 debugger_binary_path = str8_cstring(filename_cstr); - String8 name8 = str8_lit("Debugger"); - String8 data8 = push_str8f(scratch.arena, "%S --jit_pid:%%ld --jit_code:%%ld --jit_addr:0x%%p", debugger_binary_path); - String16 name16 = str16_from_8(scratch.arena, name8); - String16 data16 = str16_from_8(scratch.arena, data8); - B32 likely_not_in_admin_mode = 0; - { - HKEY reg_key = 0; - LSTATUS status = 0; - status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug\\", 0, KEY_SET_VALUE, ®_key); - likely_not_in_admin_mode = (status == ERROR_ACCESS_DENIED); - status = RegSetValueExW(reg_key, (LPCWSTR)name16.str, 0, REG_SZ, (BYTE *)data16.str, data16.size*sizeof(U16)+2); - RegCloseKey(reg_key); - } - { - HKEY reg_key = 0; - LSTATUS status = 0; - status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug\\", 0, KEY_SET_VALUE, ®_key); - likely_not_in_admin_mode = (status == ERROR_ACCESS_DENIED); - status = RegSetValueExW(reg_key, (LPCWSTR)name16.str, 0, REG_SZ, (BYTE *)data16.str, data16.size*sizeof(U16)+2); - RegCloseKey(reg_key); - } - if(likely_not_in_admin_mode) - { - DF_CmdParams p = params; - p.string = str8_lit("Could not register as the just-in-time debugger, access was denied; try running the debugger as administrator."); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } -#else - DF_CmdParams p = params; - p.string = str8_lit("Registering as the just-in-time debugger is currently not supported on this system."); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); -#endif - }break; - - //- rjf: developer commands - case DF_CoreCmdKind_LogMarker: - { - log_infof("\"#MARKER\""); - }break; - } - } - scratch_end(scratch); - } - - //- rjf: fill core interaction register info - { - DF_Entity *thread = df_entity_from_handle(df_state->ctrl_ctx.thread); - DF_Entity *module = df_module_from_thread(thread); - DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); - df_interact_regs()->thread = df_handle_from_entity(thread); - df_interact_regs()->module = df_handle_from_entity(module); - df_interact_regs()->process = df_handle_from_entity(process); - df_interact_regs()->unwind_count = df_state->ctrl_ctx.unwind_count; - df_interact_regs()->inline_unwind_count = df_state->ctrl_ctx.inline_unwind_count; - } - - ProfEnd(); -} - -internal void -df_core_end_frame(void) -{ - ProfBeginFunction(); - - //- rjf: entity mutation -> soft halt - if(df_state->entities_mut_soft_halt) - { - df_state->entities_mut_soft_halt = 0; - DF_CmdParams params = df_cmd_params_zero(); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SoftHaltRefresh)); - } - - //- rjf: entity mutation -> send refreshed debug info map - if(df_state->entities_mut_dbg_info_map) ProfScope("entity mutation -> send refreshed debug info map") - { - df_state->entities_mut_dbg_info_map = 0; - // TODO(rjf) - } - - //- rjf: send messages - if(df_state->ctrl_msgs.count != 0) - { - if(ctrl_u2c_push_msgs(&df_state->ctrl_msgs, os_now_microseconds()+100)) - { - MemoryZeroStruct(&df_state->ctrl_msgs); - arena_clear(df_state->ctrl_msg_arena); - } - } - - //- rjf: eliminate entities that are marked for deletion + kill off entities with a death-timer - ProfScope("eliminate deleted/deletion-timer entities") - { - for(DF_Entity *entity = df_entity_root(), *next = 0; !df_entity_is_nil(entity); entity = next) - { - next = df_entity_rec_df_pre(entity, &df_g_nil_entity).next; - if(entity->flags & DF_EntityFlag_DiesWithTime) - { - entity->life_left -= df_dt(); - if(entity->life_left <= 0.f) - { - df_entity_mark_for_deletion(entity); - } - } - if(entity->flags & DF_EntityFlag_MarkedForDeletion) - { - B32 undoable = (df_g_entity_kind_flags_table[entity->kind] & DF_EntityKindFlag_UserDefinedLifetime); - - // rjf: fixup next entity to iterate to - next = df_entity_rec_df(entity, &df_g_nil_entity, OffsetOf(DF_Entity, next), OffsetOf(DF_Entity, next)).next; - - // rjf: undoable -> just mark as deleted; this must be able to be trivially undone - if(undoable) - { - df_state_delta_history_push_batch(df_state->hist, 0); - df_state_delta_history_push_struct_delta(df_state->hist, &entity->deleted); - df_state_delta_history_push_struct_delta(df_state->hist, &entity->generation); - df_state_delta_history_push_struct_delta(df_state->hist, &df_state->kind_alloc_gens[entity->kind]); - entity->deleted = 1; - entity->generation += 1; - entity->flags &= ~DF_EntityFlag_MarkedForDeletion; - df_state->kind_alloc_gens[entity->kind] += 1; - } - - // rjf: not undoable -> actually release - if(!undoable) - { - // rjf: eliminate root entity if we're freeing it - if(entity == df_state->entities_root) - { - df_state->entities_root = &df_g_nil_entity; - } - - // rjf: unhook & release this entity tree - df_entity_change_parent(0, entity, entity->parent, &df_g_nil_entity); - df_entity_release(0, entity); - } - } - } - } - - //- rjf: garbage collect eliminated thread unwinds - for(U64 slot_idx = 0; slot_idx < df_state->unwind_cache.slots_count; slot_idx += 1) - { - DF_UnwindCacheSlot *slot = &df_state->unwind_cache.slots[slot_idx]; - for(DF_UnwindCacheNode *n = slot->first, *next = 0; n != 0; n = next) - { - next = n->next; - if(df_entity_is_nil(df_entity_from_handle(n->thread))) - { - DLLRemove(slot->first, slot->last, n); - arena_release(n->arena); - SLLStackPush(df_state->unwind_cache.free_node, n); - } - } - } - - //- rjf: write config changes - ProfScope("write config changes") - { - for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) ProfScope("write %.*s config data", str8_varg(df_g_cfg_src_string_table[src])) - { - if(df_state->cfg_write_issued[src]) - { - df_state->cfg_write_issued[src] = 0; - String8 path = df_cfg_path_from_src(src); - os_write_data_list_to_file_path(path, df_state->cfg_write_data[src]); - } - arena_clear(df_state->cfg_write_arenas[src]); - MemoryZeroStruct(&df_state->cfg_write_data[src]); - } - } - - ProfEnd(); -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#undef RADDBG_LAYER_COLOR +#define RADDBG_LAYER_COLOR 0.70f, 0.50f, 0.25f + +//////////////////////////////// +//~ rjf: Generated Code + +#include "df/core/generated/df_core.meta.c" + +//////////////////////////////// +//~ rjf: Basic Helpers + +internal U64 +df_hash_from_seed_string(U64 seed, String8 string) +{ + U64 result = seed; + for(U64 i = 0; i < string.size; i += 1) + { + result = ((result << 5) + result) + string.str[i]; + } + return result; +} + +internal U64 +df_hash_from_string(String8 string) +{ + return df_hash_from_seed_string(5381, string); +} + +internal U64 +df_hash_from_seed_string__case_insensitive(U64 seed, String8 string) +{ + U64 result = seed; + for(U64 i = 0; i < string.size; i += 1) + { + result = ((result << 5) + result) + char_to_lower(string.str[i]); + } + return result; +} + +internal U64 +df_hash_from_string__case_insensitive(String8 string) +{ + return df_hash_from_seed_string__case_insensitive(5381, string); +} + +//////////////////////////////// +//~ rjf: Handles + +internal DF_Handle +df_handle_zero(void) +{ + DF_Handle result = {0}; + return result; +} + +internal B32 +df_handle_match(DF_Handle a, DF_Handle b) +{ + return (a.u64[0] == b.u64[0] && a.u64[1] == b.u64[1]); +} + +internal void +df_handle_list_push_node(DF_HandleList *list, DF_HandleNode *node) +{ + DLLPushBack(list->first, list->last, node); + list->count += 1; +} + +internal void +df_handle_list_push(Arena *arena, DF_HandleList *list, DF_Handle handle) +{ + DF_HandleNode *n = push_array(arena, DF_HandleNode, 1); + n->handle = handle; + df_handle_list_push_node(list, n); +} + +internal void +df_handle_list_remove(DF_HandleList *list, DF_HandleNode *node) +{ + DLLRemove(list->first, list->last, node); + list->count -= 1; +} + +internal DF_HandleNode * +df_handle_list_find(DF_HandleList *list, DF_Handle handle) +{ + DF_HandleNode *result = 0; + for(DF_HandleNode *n = list->first; n != 0; n = n->next) + { + if(df_handle_match(n->handle, handle)) + { + result = n; + break; + } + } + return result; +} + +internal DF_HandleList +df_push_handle_list_copy(Arena *arena, DF_HandleList list) +{ + DF_HandleList result = {0}; + for(DF_HandleNode *n = list.first; n != 0; n = n->next) + { + df_handle_list_push(arena, &result, n->handle); + } + return result; +} + +//////////////////////////////// +//~ rjf: State History Data Structure + +internal DF_StateDeltaHistory * +df_state_delta_history_alloc(void) +{ + Arena *arena = arena_alloc(); + DF_StateDeltaHistory *hist = push_array(arena, DF_StateDeltaHistory, 1); + hist->arena = arena; + for(Side side = (Side)0; side < Side_COUNT; side = (Side)(side+1)) + { + hist->side_arenas[side] = arena_alloc(); + } + return hist; +} + +internal void +df_state_delta_history_release(DF_StateDeltaHistory *hist) +{ + for(Side side = (Side)0; side < Side_COUNT; side = (Side)(side+1)) + { + arena_release(hist->side_arenas[side]); + } + arena_release(hist->arena); +} + +internal void +df_state_delta_history_push_batch(DF_StateDeltaHistory *hist, U64 *optional_gen_ptr) +{ + if(hist == 0) { return; } + if(hist->side_arenas[Side_Max] != 0) + { + arena_clear(hist->side_arenas[Side_Max]); + hist->side_tops[Side_Max] = 0; + } + DF_StateDeltaBatch *batch = push_array(hist->side_arenas[Side_Min], DF_StateDeltaBatch, 1); + SLLStackPush(hist->side_tops[Side_Min], batch); + if(optional_gen_ptr != 0) + { + batch->gen = *optional_gen_ptr; + batch->gen_vaddr = (U64)optional_gen_ptr; + } +} + +internal void +df_state_delta_history_push_delta(DF_StateDeltaHistory *hist, void *ptr, U64 size) +{ + if(hist == 0) { return; } + DF_StateDeltaBatch *batch = hist->side_tops[Side_Min]; + if(batch == 0) + { + df_state_delta_history_push_batch(hist, 0); + batch = hist->side_tops[Side_Min]; + } + DF_StateDeltaNode *n = push_array(hist->side_arenas[Side_Min], DF_StateDeltaNode, 1); + SLLQueuePush(batch->first, batch->last, n); + n->v.vaddr = (U64)ptr; + n->v.data = push_str8_copy(hist->arena, str8((U8*)ptr, size)); +} + +internal void +df_state_delta_history_wind(DF_StateDeltaHistory *hist, Side side) +{ + if(hist == 0) { return; } + DF_StateDeltaBatch *src_batch = hist->side_tops[side]; + if(src_batch != 0) + { + B32 src_batch_gen_good = (src_batch->gen_vaddr == 0 || src_batch->gen == *(U64 *)(src_batch->gen_vaddr)); + U64 pop_pos = (U64)hist->side_tops[side] - (U64)hist->side_arenas[side]; + SLLStackPop(hist->side_tops[side]); + if(src_batch_gen_good) + { + DF_StateDeltaBatch *dst_batch = push_array(hist->side_arenas[side_flip(side)], DF_StateDeltaBatch, 1); + SLLStackPush(hist->side_tops[side_flip(side)], dst_batch); + for(DF_StateDeltaNode *src_n = src_batch->first; src_n != 0; src_n = src_n->next) + { + DF_StateDelta *src_delta = &src_n->v; + DF_StateDeltaNode *dst_n = push_array(hist->side_arenas[side_flip(side)], DF_StateDeltaNode, 1); + SLLQueuePush(dst_batch->first, dst_batch->last, dst_n); + dst_n->v.vaddr = src_delta->vaddr; + dst_n->v.data = push_str8_copy(hist->side_arenas[side_flip(side)], str8((U8 *)src_delta->vaddr, src_delta->data.size)); + MemoryCopy((void *)src_delta->vaddr, src_delta->data.str, src_delta->data.size); + } + } + arena_pop_to(hist->side_arenas[side], pop_pos); + } +} + +//////////////////////////////// +//~ rjf: Sparse Tree Expansion State Data Structure + +//- rjf: keys + +internal DF_ExpandKey +df_expand_key_make(U64 parent_hash, U64 child_num) +{ + DF_ExpandKey key; + { + key.parent_hash = parent_hash; + key.child_num = child_num; + } + return key; +} + +internal DF_ExpandKey +df_expand_key_zero(void) +{ + DF_ExpandKey key = {0}; + return key; +} + +internal B32 +df_expand_key_match(DF_ExpandKey a, DF_ExpandKey b) +{ + return MemoryMatchStruct(&a, &b); +} + +internal U64 +df_hash_from_expand_key(DF_ExpandKey key) +{ + U64 data[] = + { + key.child_num, + }; + U64 hash = df_hash_from_seed_string(key.parent_hash, str8((U8 *)data, sizeof(data))); + return hash; +} + +//- rjf: table + +internal void +df_expand_tree_table_init(Arena *arena, DF_ExpandTreeTable *table, U64 slot_count) +{ + MemoryZeroStruct(table); + table->slots_count = slot_count; + table->slots = push_array(arena, DF_ExpandSlot, table->slots_count); +} + +internal DF_ExpandNode * +df_expand_node_from_key(DF_ExpandTreeTable *table, DF_ExpandKey key) +{ + U64 hash = df_hash_from_expand_key(key); + U64 slot_idx = hash%table->slots_count; + DF_ExpandSlot *slot = &table->slots[slot_idx]; + DF_ExpandNode *node = 0; + for(DF_ExpandNode *n = slot->first; n != 0; n = n->hash_next) + { + if(df_expand_key_match(n->key, key)) + { + node = n; + break; + } + } + return node; +} + +internal B32 +df_expand_key_is_set(DF_ExpandTreeTable *table, DF_ExpandKey key) +{ + DF_ExpandNode *node = df_expand_node_from_key(table, key); + return (node != 0 && node->expanded); +} + +internal void +df_expand_set_expansion(Arena *arena, DF_ExpandTreeTable *table, DF_ExpandKey parent_key, DF_ExpandKey key, B32 expanded) +{ + // rjf: map keys => nodes + DF_ExpandNode *parent_node = df_expand_node_from_key(table, parent_key); + DF_ExpandNode *node = df_expand_node_from_key(table, key); + + // rjf: make node if we don't have one, and we need one + if(node == 0 && expanded) + { + node = table->free_node; + if(node != 0) + { + table->free_node = table->free_node->next; + MemoryZeroStruct(node); + } + else + { + node = push_array(arena, DF_ExpandNode, 1); + } + + // rjf: link into table + U64 hash = df_hash_from_expand_key(key); + U64 slot = hash % table->slots_count; + DLLPushBack_NP(table->slots[slot].first, table->slots[slot].last, node, hash_next, hash_prev); + + // rjf: link into parent + if(parent_node != 0) + { + DF_ExpandNode *prev = 0; + for(DF_ExpandNode *n = parent_node->first; n != 0; n = n->next) + { + if(n->key.child_num < key.child_num) + { + prev = n; + } + else + { + break; + } + } + DLLInsert_NP(parent_node->first, parent_node->last, prev, node, next, prev); + node->parent = parent_node; + } + } + + // rjf: fill + if(node != 0) + { + node->key = key; + node->expanded = expanded; + } + + // rjf: unlink node & free if we don't need it anymore + if(expanded == 0 && node != 0 && node->first == 0) + { + // rjf: unlink from table + U64 hash = df_hash_from_expand_key(key); + U64 slot = hash % table->slots_count; + DLLRemove_NP(table->slots[slot].first, table->slots[slot].last, node, hash_next, hash_prev); + + // rjf: unlink from tree + if(parent_node != 0) + { + DLLRemove_NP(parent_node->first, parent_node->last, node, next, prev); + } + + // rjf: free + node->next = table->free_node; + table->free_node = node; + } +} + +//////////////////////////////// +//~ rjf: Config Type Functions + +internal DF_CfgNode * +df_cfg_tree_copy(Arena *arena, DF_CfgNode *src_root) +{ + DF_CfgNode *dst_root = &df_g_nil_cfg_node; + DF_CfgNode *dst_parent = dst_root; + { + DF_CfgNodeRec rec = {0}; + for(DF_CfgNode *src = src_root; src != &df_g_nil_cfg_node; src = rec.next) + { + DF_CfgNode *dst = push_array(arena, DF_CfgNode, 1); + dst->first = dst->last = dst->parent = dst->next = &df_g_nil_cfg_node; + dst->flags = src->flags; + dst->string = push_str8_copy(arena, src->string); + dst->source = src->source; + dst->parent = dst_parent; + if(dst_parent != &df_g_nil_cfg_node) + { + SLLQueuePush_NZ(&df_g_nil_cfg_node, dst_parent->first, dst_parent->last, dst, next); + } + else + { + dst_root = dst_parent = dst; + } + rec = df_cfg_node_rec__depth_first_pre(src, src_root); + if(rec.push_count != 0) + { + dst_parent = dst; + } + else for(U64 idx = 0; idx < rec.pop_count; idx += 1) + { + dst_parent = dst_parent->parent; + } + } + } + return dst_root; +} + +internal DF_CfgNodeRec +df_cfg_node_rec__depth_first_pre(DF_CfgNode *node, DF_CfgNode *root) +{ + DF_CfgNodeRec rec = {0}; + rec.next = &df_g_nil_cfg_node; + if(node->first != &df_g_nil_cfg_node) + { + rec.next = node->first; + rec.push_count = 1; + } + else for(DF_CfgNode *p = node; p != &df_g_nil_cfg_node && p != root; p = p->parent, rec.pop_count += 1) + { + if(p->next != &df_g_nil_cfg_node) + { + rec.next = p->next; + break; + } + } + return rec; +} + +internal void +df_cfg_table_push_unparsed_string(Arena *arena, DF_CfgTable *table, String8 string, DF_CfgSrc source) +{ + Temp scratch = scratch_begin(&arena, 1); + if(table->slot_count == 0) + { + table->slot_count = 64; + table->slots = push_array(arena, DF_CfgSlot, table->slot_count); + } + MD_TokenizeResult tokenize = md_tokenize_from_text(scratch.arena, string); + MD_ParseResult parse = md_parse_from_text_tokens(scratch.arena, str8_lit(""), string, tokenize.tokens); + MD_Node *md_root = parse.root; + for(MD_EachNode(tln, md_root->first)) if(tln->string.size != 0) + { + // rjf: map string -> hash*slot + String8 string = str8(tln->string.str, tln->string.size); + U64 hash = df_hash_from_string__case_insensitive(string); + U64 slot_idx = hash % table->slot_count; + DF_CfgSlot *slot = &table->slots[slot_idx]; + + // rjf: find existing value for this string + DF_CfgVal *val = 0; + for(DF_CfgVal *v = slot->first; v != 0; v = v->hash_next) + { + if(str8_match(v->string, string, StringMatchFlag_CaseInsensitive)) + { + val = v; + break; + } + } + + // rjf: create new value if needed + if(val == 0) + { + val = push_array(arena, DF_CfgVal, 1); + val->string = push_str8_copy(arena, string); + val->insertion_stamp = table->insertion_stamp_counter; + SLLStackPush_N(slot->first, val, hash_next); + SLLQueuePush_N(table->first_val, table->last_val, val, linear_next); + table->insertion_stamp_counter += 1; + } + + // rjf: deep copy tree into streamlined config structure + DF_CfgNode *dst_root = &df_g_nil_cfg_node; + { + DF_CfgNode *dst_parent = &df_g_nil_cfg_node; + for(MD_Node *src = tln, *src_next = 0; !md_node_is_nil(src); src = src_next) + { + src_next = 0; + + // rjf: copy + DF_CfgNode *dst = push_array(arena, DF_CfgNode, 1); + dst->first = dst->last = dst->parent = dst->next = &df_g_nil_cfg_node; + if(dst_parent == &df_g_nil_cfg_node) + { + dst_root = dst; + } + else + { + SLLQueuePush_NZ(&df_g_nil_cfg_node, dst_parent->first, dst_parent->last, dst, next); + dst->parent = dst_parent; + } + { + dst->flags |= !!(src->flags & MD_NodeFlag_Identifier) * DF_CfgNodeFlag_Identifier; + dst->flags |= !!(src->flags & MD_NodeFlag_Numeric) * DF_CfgNodeFlag_Numeric; + dst->flags |= !!(src->flags & MD_NodeFlag_StringLiteral) * DF_CfgNodeFlag_StringLiteral; + dst->string = push_str8_copy(arena, str8(src->string.str, src->string.size)); + dst->source = source; + } + + // rjf: grab next + if(!md_node_is_nil(src->first)) + { + src_next = src->first; + dst_parent = dst; + } + else for(MD_Node *p = src; !md_node_is_nil(p) && p != tln; p = p->parent, dst_parent = dst_parent->parent) + { + if(!md_node_is_nil(p->next)) + { + src_next = p->next; + break; + } + } + } + } + + // rjf: push tree into value + SLLQueuePush_NZ(&df_g_nil_cfg_node, val->first, val->last, dst_root, next); + } + scratch_end(scratch); +} + +internal DF_CfgTable +df_cfg_table_from_inheritance(Arena *arena, DF_CfgTable *src) +{ + DF_CfgTable dst_ = {0}; + DF_CfgTable *dst = &dst_; + { + dst->slot_count = src->slot_count; + dst->slots = push_array(arena, DF_CfgSlot, dst->slot_count); + } + for(DF_CfgVal *src_val = src->first_val; src_val != 0 && src_val != &df_g_nil_cfg_val; src_val = src_val->linear_next) + { + DF_CoreViewRuleSpec *spec = df_core_view_rule_spec_from_string(src_val->string); + if(spec->info.flags & DF_CoreViewRuleSpecInfoFlag_Inherited) + { + U64 hash = df_hash_from_string(spec->info.string); + U64 dst_slot_idx = hash%dst->slot_count; + DF_CfgSlot *dst_slot = &dst->slots[dst_slot_idx]; + DF_CfgVal *dst_val = push_array(arena, DF_CfgVal, 1); + dst_val->first = src_val->first; + dst_val->last = src_val->last; + dst_val->string = src_val->string; + dst_val->insertion_stamp = dst->insertion_stamp_counter; + SLLStackPush_N(dst_slot->first, dst_val, hash_next); + dst->insertion_stamp_counter += 1; + } + } + return dst_; +} + +internal DF_CfgTable +df_cfg_table_copy(Arena *arena, DF_CfgTable *src) +{ + DF_CfgTable result = {0}; + result.slot_count = src->slot_count; + result.slots = push_array(arena, DF_CfgSlot, result.slot_count); + MemoryCopy(result.slots, src->slots, sizeof(DF_CfgSlot)*result.slot_count); + return result; +} + +internal DF_CfgVal * +df_cfg_val_from_string(DF_CfgTable *table, String8 string) +{ + DF_CfgVal *result = &df_g_nil_cfg_val; + if(table->slot_count != 0) + { + U64 hash = df_hash_from_string__case_insensitive(string); + U64 slot_idx = hash % table->slot_count; + DF_CfgSlot *slot = &table->slots[slot_idx]; + for(DF_CfgVal *val = slot->first; val != 0; val = val->hash_next) + { + if(str8_match(val->string, string, StringMatchFlag_CaseInsensitive)) + { + result = val; + break; + } + } + } + return result; +} + +internal DF_CfgNode * +df_cfg_node_child_from_string(DF_CfgNode *node, String8 string, StringMatchFlags flags) +{ + DF_CfgNode *result = &df_g_nil_cfg_node; + for(DF_CfgNode *child = node->first; child != &df_g_nil_cfg_node; child = child->next) + { + if(str8_match(child->string, string, flags)) + { + result = child; + break; + } + } + return result; +} + +internal DF_CfgNode * +df_first_cfg_node_child_from_flags(DF_CfgNode *node, DF_CfgNodeFlags flags) +{ + DF_CfgNode *result = &df_g_nil_cfg_node; + for(DF_CfgNode *child = node->first; child != &df_g_nil_cfg_node; child = child->next) + { + if(child->flags & flags) + { + result = child; + break; + } + } + return result; +} + +internal String8 +df_string_from_cfg_node_children(Arena *arena, DF_CfgNode *node) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List strs = {0}; + for(DF_CfgNode *child = node->first; child != &df_g_nil_cfg_node; child = child->next) + { + str8_list_push(scratch.arena, &strs, child->string); + } + String8 result = str8_list_join(arena, &strs, 0); + scratch_end(scratch); + return result; +} + +internal Vec4F32 +df_hsva_from_cfg_node(DF_CfgNode *node) +{ + Vec4F32 result = {0}; + DF_CfgNode *hsva = df_cfg_node_child_from_string(node, str8_lit("hsva"), StringMatchFlag_CaseInsensitive); + DF_CfgNode *rgba = df_cfg_node_child_from_string(node, str8_lit("rgba"), StringMatchFlag_CaseInsensitive); + DF_CfgNode *hsv = df_cfg_node_child_from_string(node, str8_lit("hsv"), StringMatchFlag_CaseInsensitive); + DF_CfgNode *rgb = df_cfg_node_child_from_string(node, str8_lit("rgb"), StringMatchFlag_CaseInsensitive); + if(hsva != &df_g_nil_cfg_node) + { + DF_CfgNode *hue = hsva->first; + DF_CfgNode *sat = hue->next; + DF_CfgNode *val = sat->next; + DF_CfgNode *alp = val->next; + F32 hue_f32 = (F32)f64_from_str8(hue->string); + F32 sat_f32 = (F32)f64_from_str8(sat->string); + F32 val_f32 = (F32)f64_from_str8(val->string); + F32 alp_f32 = (F32)f64_from_str8(alp->string); + result = v4f32(hue_f32, sat_f32, val_f32, alp_f32); + } + else if(hsv != &df_g_nil_cfg_node) + { + DF_CfgNode *hue = hsva->first; + DF_CfgNode *sat = hue->next; + DF_CfgNode *val = sat->next; + F32 hue_f32 = (F32)f64_from_str8(hue->string); + F32 sat_f32 = (F32)f64_from_str8(sat->string); + F32 val_f32 = (F32)f64_from_str8(val->string); + result = v4f32(hue_f32, sat_f32, val_f32, 1.f); + } + else if(rgba != &df_g_nil_cfg_node) + { + DF_CfgNode *red = rgba->first; + DF_CfgNode *grn = red->next; + DF_CfgNode *blu = grn->next; + DF_CfgNode *alp = blu->next; + F32 red_f32 = (F32)f64_from_str8(red->string); + F32 grn_f32 = (F32)f64_from_str8(grn->string); + F32 blu_f32 = (F32)f64_from_str8(blu->string); + F32 alp_f32 = (F32)f64_from_str8(alp->string); + Vec3F32 hsv = hsv_from_rgb(v3f32(red_f32, grn_f32, blu_f32)); + result = v4f32(hsv.x, hsv.y, hsv.z, alp_f32); + } + else if(rgb != &df_g_nil_cfg_node) + { + DF_CfgNode *red = rgba->first; + DF_CfgNode *grn = red->next; + DF_CfgNode *blu = grn->next; + F32 red_f32 = (F32)f64_from_str8(red->string); + F32 grn_f32 = (F32)f64_from_str8(grn->string); + F32 blu_f32 = (F32)f64_from_str8(blu->string); + Vec3F32 hsv = hsv_from_rgb(v3f32(red_f32, grn_f32, blu_f32)); + result = v4f32(hsv.x, hsv.y, hsv.z, 1.f); + } + return result; +} + +internal String8 +df_string_from_cfg_node_key(DF_CfgNode *node, String8 key, StringMatchFlags flags) +{ + DF_CfgNode *child = df_cfg_node_child_from_string(node, key, flags); + return child->first->string; +} + +//////////////////////////////// +//~ rjf: Disassembling + +#include "third_party/udis86/config.h" +#include "third_party/udis86/udis86.h" +#include "third_party/udis86/libudis86/syn.h" + +internal DF_Inst +df_single_inst_from_machine_code__x64(Arena *arena, U64 start_voff, String8 string) +{ + Architecture arch = Architecture_x64; + + //- rjf: prep ud state + struct ud ud_ctx_; + struct ud *ud_ctx = &ud_ctx_; + ud_init(ud_ctx); + ud_set_mode(ud_ctx, bit_size_from_arch(arch)); + ud_set_pc(ud_ctx, start_voff); + ud_set_input_buffer(ud_ctx, string.str, string.size); + ud_set_vendor(ud_ctx, UD_VENDOR_ANY); + ud_set_syntax(ud_ctx, UD_SYN_INTEL); + + //- rjf: disassembly + get info + U32 bytes_disassembled = ud_disassemble(ud_ctx); + struct ud_operand *first_op = (struct ud_operand *)ud_insn_opr(ud_ctx, 0); + U64 rel_voff = (first_op != 0 && first_op->type == UD_OP_JIMM) ? ud_syn_rel_target(ud_ctx, first_op) : 0; + DF_InstFlags flags = 0; + enum ud_mnemonic_code code = ud_insn_mnemonic(ud_ctx); + switch(code) + { + case UD_Icall: + { + flags |= DF_InstFlag_Call; + }break; + + /* TODO(wonchun) + case UD_Iiretd: + case UD_Iiretw: + */ + + case UD_Ija: + case UD_Ijae: + case UD_Ijb: + case UD_Ijbe: + case UD_Ijcxz: + case UD_Ijecxz: + case UD_Ijg: + case UD_Ijge: + case UD_Ijl: + case UD_Ijle: + { + flags |= DF_InstFlag_Branch; + }break; + + case UD_Ijmp: + { + flags |= DF_InstFlag_UnconditionalJump; + }break; + + case UD_Ijno: + case UD_Ijnp: + case UD_Ijns: + case UD_Ijnz: + case UD_Ijo: + case UD_Ijp: + case UD_Ijrcxz: + case UD_Ijs: + case UD_Ijz: + case UD_Iloop: + case UD_Iloope: + case UD_Iloopne: + { + flags |= DF_InstFlag_Branch; + }break; + + case UD_Iret: + case UD_Iretf: + { + flags |= DF_InstFlag_Return; + }break; + + /* TODO(wonchun) + case UD_Isyscall: + case UD_Isysenter: + case UD_Isysexit: + case UD_Isysret: + case UD_Ivmcall: + case UD_Ivmmcall: + */ + default: + { + flags |= DF_InstFlag_NonFlow; + }break; + } + + //- rjf: check for stack pointer modifications + S64 sp_delta = 0; + { + struct ud_operand *dst_op = (struct ud_operand *)ud_insn_opr(ud_ctx, 0); + struct ud_operand *src_op = (struct ud_operand *)ud_insn_opr(ud_ctx, 1); + + // rjf: direct additions/subtractions to RSP + if(dst_op && src_op && dst_op->base == UD_R_RSP && dst_op->type == UD_OP_REG) + { + flags |= DF_InstFlag_ChangesStackPointer; + // TODO(rjf): does the library report constant changes to the stack pointer + // as UD_OP_CONST too? what does UD_OP_JIMM refer to? + if(src_op->base == UD_NONE && src_op->type == UD_OP_IMM && code == UD_Isub) + { + S64 sign = -1; + sp_delta = sign * src_op->lval.sqword; + } + else if(src_op->base == UD_NONE && src_op->type == UD_OP_IMM && code == UD_Iadd) + { + S64 sign = +1; + sp_delta = sign * src_op->lval.sqword; + } + else + { + flags |= DF_InstFlag_ChangesStackPointerVariably; + } + } + + // rjf: push/pop + if(code == UD_Ipush) + { + flags |= DF_InstFlag_ChangesStackPointer; + sp_delta = -8; + } + else if(code == UD_Ipop) + { + flags |= DF_InstFlag_ChangesStackPointer; + sp_delta = +8; + } + + // rjf: mark extra flags + if(ud_ctx->pfx_rep != 0 || + ud_ctx->pfx_repe != 0 || + ud_ctx->pfx_repne != 0) + { + flags |= DF_InstFlag_Repeats; + } + } + + //- rjf: fill+return + DF_Inst inst = {0}; + inst.size = bytes_disassembled; + inst.string = push_str8_copy(arena, str8_cstring((char *)ud_insn_asm(ud_ctx))); + inst.rel_voff = rel_voff; + inst.sp_delta = sp_delta; + inst.flags = flags; + return inst; +} + +internal DF_Inst +df_single_inst_from_machine_code(Arena *arena, Architecture arch, U64 start_voff, String8 string) +{ + DF_Inst result = {0}; + switch(arch) + { + default:{}break; + case Architecture_x64: + { + result = df_single_inst_from_machine_code__x64(arena, start_voff, string); + }break; + } + return result; +} + +//////////////////////////////// +//~ rjf: Debug Info Extraction Type Pure Functions + +internal DF_LineList +df_line_list_copy(Arena *arena, DF_LineList *list) +{ + DF_LineList dst = {0}; + for(DF_LineNode *src_n = list->first; src_n != 0; src_n = src_n->next) + { + DF_LineNode *dst_n = push_array(arena, DF_LineNode, 1); + MemoryCopyStruct(dst_n, src_n); + dst_n->v.dbgi_key = di_key_copy(arena, &src_n->v.dbgi_key); + SLLQueuePush(dst.first, dst.last, dst_n); + dst.count += 1; + } + return dst; +} + +//////////////////////////////// +//~ rjf: Control Flow Analysis Functions + +internal DF_CtrlFlowInfo +df_ctrl_flow_info_from_vaddr_code__x64(Arena *arena, DF_InstFlags exit_points_mask, U64 vaddr, String8 code) +{ + Temp scratch = scratch_begin(&arena, 1); + DF_CtrlFlowInfo info = {0}; + for(U64 offset = 0; offset < code.size;) + { + DF_Inst inst = df_single_inst_from_machine_code__x64(scratch.arena, 0, str8_skip(code, offset)); + U64 inst_vaddr = vaddr+offset; + info.cumulative_sp_delta += inst.sp_delta; + offset += inst.size; + info.total_size += inst.size; + if(inst.flags & exit_points_mask) + { + DF_CtrlFlowPoint point = {0}; + point.inst_flags = inst.flags; + point.vaddr = inst_vaddr; + point.jump_dest_vaddr = 0; + point.expected_sp_delta = info.cumulative_sp_delta; + if(inst.rel_voff != 0) + { + point.jump_dest_vaddr = (U64)(point.vaddr + (S64)((S32)inst.rel_voff)); + } + DF_CtrlFlowPointNode *node = push_array(arena, DF_CtrlFlowPointNode, 1); + node->v = point; + SLLQueuePush(info.exit_points.first, info.exit_points.last, node); + info.exit_points.count += 1; + } + } + scratch_end(scratch); + return info; +} + +internal DF_CtrlFlowInfo +df_ctrl_flow_info_from_arch_vaddr_code(Arena *arena, DF_InstFlags exit_points_mask, Architecture arch, U64 vaddr, String8 code) +{ + DF_CtrlFlowInfo result = {0}; + switch(arch) + { + default:{}break; + case Architecture_x64: + { + result = df_ctrl_flow_info_from_vaddr_code__x64(arena, exit_points_mask, vaddr, code); + }break; + } + return result; +} + +//////////////////////////////// +//~ rjf: Command Type Pure Functions + +//- rjf: specs + +internal B32 +df_cmd_spec_is_nil(DF_CmdSpec *spec) +{ + return (spec == 0 || spec == &df_g_nil_cmd_spec); +} + +internal void +df_cmd_spec_list_push(Arena *arena, DF_CmdSpecList *list, DF_CmdSpec *spec) +{ + DF_CmdSpecNode *n = push_array(arena, DF_CmdSpecNode, 1); + n->spec = spec; + SLLQueuePush(list->first, list->last, n); + list->count += 1; +} + +internal DF_CmdSpecArray +df_cmd_spec_array_from_list(Arena *arena, DF_CmdSpecList list) +{ + DF_CmdSpecArray result = {0}; + result.count = list.count; + result.v = push_array(arena, DF_CmdSpec *, list.count); + U64 idx = 0; + for(DF_CmdSpecNode *n = list.first; n != 0; n = n->next, idx += 1) + { + result.v[idx] = n->spec; + } + return result; +} + +internal int +df_qsort_compare_cmd_spec__run_counter(DF_CmdSpec **a, DF_CmdSpec **b) +{ + int result = 0; + if(a[0]->run_count > b[0]->run_count) + { + result = -1; + } + else if(a[0]->run_count < b[0]->run_count) + { + result = +1; + } + return result; +} + +internal void +df_cmd_spec_array_sort_by_run_counter__in_place(DF_CmdSpecArray array) +{ + quick_sort(array.v, array.count, sizeof(DF_CmdSpec *), df_qsort_compare_cmd_spec__run_counter); +} + +internal DF_Handle +df_handle_from_cmd_spec(DF_CmdSpec *spec) +{ + DF_Handle handle = {0}; + handle.u64[0] = (U64)spec; + return handle; +} + +internal DF_CmdSpec * +df_cmd_spec_from_handle(DF_Handle handle) +{ + DF_CmdSpec *result = (DF_CmdSpec *)handle.u64[0]; + if(result == 0) + { + result = &df_g_nil_cmd_spec; + } + return result; +} + +//- rjf: string -> command parsing + +internal String8 +df_cmd_name_part_from_string(String8 string) +{ + String8 result = string; + for(U64 idx = 0; idx <= string.size; idx += 1) + { + if(idx == string.size || char_is_space(string.str[idx])) + { + result = str8_prefix(string, idx); + break; + } + } + return result; +} + +internal String8 +df_cmd_arg_part_from_string(String8 string) +{ + String8 result = str8_lit(""); + B32 found_space = 0; + for(U64 idx = 0; idx <= string.size; idx += 1) + { + if(found_space && (idx == string.size || !char_is_space(string.str[idx]))) + { + result = str8_skip(string, idx); + break; + } + else if(!found_space && (idx == string.size || char_is_space(string.str[idx]))) + { + found_space = 1; + } + } + return result; +} + +//- rjf: command parameter bundles + +internal DF_CmdParams +df_cmd_params_zero(void) +{ + DF_CmdParams p = {0}; + return p; +} + +internal void +df_cmd_params_mark_slot(DF_CmdParams *params, DF_CmdParamSlot slot) +{ + params->slot_props[slot/64] |= (1ull<<(slot%64)); +} + +internal B32 +df_cmd_params_has_slot(DF_CmdParams *params, DF_CmdParamSlot slot) +{ + return !!(params->slot_props[slot/64] & (1ull<<(slot%64))); +} + +internal String8 +df_cmd_params_apply_spec_query(Arena *arena, DF_CtrlCtx *ctrl_ctx, DF_CmdParams *params, DF_CmdSpec *spec, String8 query) +{ + String8 error = {0}; + B32 prefer_imm = 0; + switch(spec->info.query.slot) + { + default: + case DF_CmdParamSlot_String: + { + params->string = push_str8_copy(arena, query); + df_cmd_params_mark_slot(params, DF_CmdParamSlot_String); + }break; + case DF_CmdParamSlot_FilePath: + { + String8TxtPtPair pair = str8_txt_pt_pair_from_string(query); + params->file_path = push_str8_copy(arena, pair.string); + params->text_point = pair.pt; + df_cmd_params_mark_slot(params, DF_CmdParamSlot_FilePath); + df_cmd_params_mark_slot(params, DF_CmdParamSlot_TextPoint); + }break; + case DF_CmdParamSlot_TextPoint: + { + U64 v = 0; + if(try_u64_from_str8_c_rules(query, &v)) + { + params->text_point.column = 1; + params->text_point.line = v; + df_cmd_params_mark_slot(params, DF_CmdParamSlot_TextPoint); + } + else + { + error = str8_lit("Couldn't interpret as a line number."); + } + }break; + case DF_CmdParamSlot_VirtualAddr: prefer_imm = 0; goto use_numeric_eval; + case DF_CmdParamSlot_VirtualOff: prefer_imm = 0; goto use_numeric_eval; + case DF_CmdParamSlot_Index: prefer_imm = 1; goto use_numeric_eval; + case DF_CmdParamSlot_ID: prefer_imm = 1; goto use_numeric_eval; + use_numeric_eval: + { + Temp scratch = scratch_begin(&arena, 1); + DI_Scope *scope = di_scope_open(); + DF_Entity *thread = df_entity_from_handle(ctrl_ctx->thread); + U64 vaddr = df_query_cached_rip_from_thread_unwind(thread, ctrl_ctx->unwind_count); + DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); + EVAL_ParseCtx parse_ctx = df_eval_parse_ctx_from_process_vaddr(scope, process, vaddr); + DF_Eval eval = df_eval_from_string(scratch.arena, scope, ctrl_ctx, &parse_ctx, &eval_string2expr_map_nil, query); + if(eval.errors.count == 0) + { + TG_Kind eval_type_kind = tg_kind_from_key(tg_unwrapped_from_graph_rdi_key(parse_ctx.type_graph, parse_ctx.rdi, eval.type_key)); + if(eval_type_kind == TG_Kind_Ptr || eval_type_kind == TG_Kind_LRef || eval_type_kind == TG_Kind_RRef) + { + eval = df_value_mode_eval_from_eval(parse_ctx.type_graph, parse_ctx.rdi, ctrl_ctx, eval); + prefer_imm = 1; + } + U64 u64 = !prefer_imm && eval.offset ? eval.offset : eval.imm_u64; + switch(spec->info.query.slot) + { + default:{}break; + case DF_CmdParamSlot_VirtualAddr: + { + params->vaddr = u64; + df_cmd_params_mark_slot(params, DF_CmdParamSlot_VirtualAddr); + }break; + case DF_CmdParamSlot_VirtualOff: + { + params->voff = u64; + df_cmd_params_mark_slot(params, DF_CmdParamSlot_VirtualOff); + }break; + case DF_CmdParamSlot_Index: + { + params->index = u64; + df_cmd_params_mark_slot(params, DF_CmdParamSlot_Index); + }break; + case DF_CmdParamSlot_BaseUnwindIndex: + { + params->base_unwind_index = u64; + df_cmd_params_mark_slot(params, DF_CmdParamSlot_BaseUnwindIndex); + }break; + case DF_CmdParamSlot_InlineUnwindIndex: + { + params->inline_unwind_index = u64; + df_cmd_params_mark_slot(params, DF_CmdParamSlot_InlineUnwindIndex); + }break; + case DF_CmdParamSlot_ID: + { + params->id = u64; + df_cmd_params_mark_slot(params, DF_CmdParamSlot_ID); + }break; + } + } + else + { + error = push_str8f(scratch.arena, "Couldn't evaluate \"%S\" as an address", query); + } + di_scope_close(scope); + scratch_end(scratch); + }break; + } + return error; +} + +//- rjf: command lists + +internal void +df_cmd_list_push(Arena *arena, DF_CmdList *cmds, DF_CmdParams *params, DF_CmdSpec *spec) +{ + DF_CmdNode *n = push_array(arena, DF_CmdNode, 1); + n->cmd.spec = spec; + n->cmd.params = df_cmd_params_copy(arena, params); + DLLPushBack(cmds->first, cmds->last, n); + cmds->count += 1; +} + +//- rjf: string -> core layer command kind + +internal DF_CoreCmdKind +df_core_cmd_kind_from_string(String8 string) +{ + DF_CoreCmdKind result = DF_CoreCmdKind_Null; + for(U64 idx = 0; idx < ArrayCount(df_g_core_cmd_kind_spec_info_table); idx += 1) + { + if(str8_match(string, df_g_core_cmd_kind_spec_info_table[idx].string, StringMatchFlag_CaseInsensitive)) + { + result = (DF_CoreCmdKind)idx; + break; + } + } + return result; +} + +//////////////////////////////// +//~ rjf: Entity Functions + +//- rjf: nil + +internal B32 +df_entity_is_nil(DF_Entity *entity) +{ + return (entity == 0 || entity == &df_g_nil_entity); +} + +//- rjf: handle <-> entity conversions + +internal U64 +df_index_from_entity(DF_Entity *entity) +{ + return (U64)(entity - df_state->entities_base); +} + +internal DF_Handle +df_handle_from_entity(DF_Entity *entity) +{ + DF_Handle handle = df_handle_zero(); + if(!df_entity_is_nil(entity)) + { + handle.u64[0] = df_index_from_entity(entity); + handle.u64[1] = entity->generation; + } + return handle; +} + +internal DF_Entity * +df_entity_from_handle(DF_Handle handle) +{ + DF_Entity *result = df_state->entities_base + handle.u64[0]; + if(handle.u64[0] >= df_state->entities_count || result->generation != handle.u64[1]) + { + result = &df_g_nil_entity; + } + return result; +} + +internal DF_EntityList +df_entity_list_from_handle_list(Arena *arena, DF_HandleList handles) +{ + DF_EntityList result = {0}; + for(DF_HandleNode *n = handles.first; n != 0; n = n->next) + { + DF_Entity *entity = df_entity_from_handle(n->handle); + if(!df_entity_is_nil(entity)) + { + df_entity_list_push(arena, &result, entity); + } + } + return result; +} + +internal DF_HandleList +df_handle_list_from_entity_list(Arena *arena, DF_EntityList entities) +{ + DF_HandleList result = {0}; + for(DF_EntityNode *n = entities.first; n != 0; n = n->next) + { + DF_Handle handle = df_handle_from_entity(n->entity); + df_handle_list_push(arena, &result, handle); + } + return result; +} + +//- rjf: entity recursion iterators + +internal DF_EntityRec +df_entity_rec_df(DF_Entity *entity, DF_Entity *subtree_root, U64 sib_off, U64 child_off) +{ + DF_EntityRec result = {0}; + if(!df_entity_is_nil(*MemberFromOffset(DF_Entity **, entity, child_off))) + { + result.next = *MemberFromOffset(DF_Entity **, entity, child_off); + result.push_count = 1; + } + else for(DF_Entity *parent = entity; parent != subtree_root && !df_entity_is_nil(parent); parent = parent->parent) + { + if(!df_entity_is_nil(*MemberFromOffset(DF_Entity **, parent, sib_off))) + { + result.next = *MemberFromOffset(DF_Entity **, parent, sib_off); + break; + } + result.pop_count += 1; + } + return result; +} + +//- rjf: ancestor/child introspection + +internal DF_Entity * +df_entity_child_from_kind(DF_Entity *entity, DF_EntityKind kind) +{ + DF_Entity *result = &df_g_nil_entity; + for(DF_Entity *child = entity->first; !df_entity_is_nil(child); child = child->next) + { + if(!child->deleted && child->kind == kind) + { + result = child; + break; + } + } + return result; +} + +internal DF_Entity * +df_entity_ancestor_from_kind(DF_Entity *entity, DF_EntityKind kind) +{ + DF_Entity *result = &df_g_nil_entity; + for(DF_Entity *p = entity->parent; !df_entity_is_nil(p); p = p->parent) + { + if(p->kind == kind) + { + result = p; + break; + } + } + return result; +} + +internal DF_EntityList +df_push_entity_child_list_with_kind(Arena *arena, DF_Entity *entity, DF_EntityKind kind) +{ + DF_EntityList result = {0}; + for(DF_Entity *child = entity->first; !df_entity_is_nil(child); child = child->next) + { + if(!child->deleted && child->kind == kind) + { + df_entity_list_push(arena, &result, child); + } + } + return result; +} + +internal DF_Entity * +df_entity_child_from_name_and_kind(DF_Entity *parent, String8 string, DF_EntityKind kind) +{ + DF_Entity *result = &df_g_nil_entity; + for(DF_Entity *child = parent->first; !df_entity_is_nil(child); child = child->next) + { + if(!child->deleted && str8_match(child->name, string, 0) && child->kind == kind) + { + result = child; + break; + } + } + return result; +} + +//- rjf: entity list building + +internal void +df_entity_list_push(Arena *arena, DF_EntityList *list, DF_Entity *entity) +{ + DF_EntityNode *n = push_array(arena, DF_EntityNode, 1); + n->entity = entity; + SLLQueuePush(list->first, list->last, n); + list->count += 1; +} + +internal DF_EntityArray +df_entity_array_from_list(Arena *arena, DF_EntityList *list) +{ + DF_EntityArray result = {0}; + result.count = list->count; + result.v = push_array(arena, DF_Entity *, result.count); + U64 idx = 0; + for(DF_EntityNode *n = list->first; n != 0; n = n->next, idx += 1) + { + result.v[idx] = n->entity; + } + return result; +} + +//- rjf: entity fuzzy list building + +internal DF_EntityFuzzyItemArray +df_entity_fuzzy_item_array_from_entity_list_needle(Arena *arena, DF_EntityList *list, String8 needle) +{ + Temp scratch = scratch_begin(&arena, 1); + DF_EntityArray array = df_entity_array_from_list(scratch.arena, list); + DF_EntityFuzzyItemArray result = df_entity_fuzzy_item_array_from_entity_array_needle(arena, &array, needle); + return result; +} + +internal DF_EntityFuzzyItemArray +df_entity_fuzzy_item_array_from_entity_array_needle(Arena *arena, DF_EntityArray *array, String8 needle) +{ + Temp scratch = scratch_begin(&arena, 1); + DF_EntityFuzzyItemArray result = {0}; + result.count = array->count; + result.v = push_array(arena, DF_EntityFuzzyItem, result.count); + U64 result_idx = 0; + for(U64 src_idx = 0; src_idx < array->count; src_idx += 1) + { + DF_Entity *entity = array->v[src_idx]; + String8 display_string = df_display_string_from_entity(scratch.arena, entity); + FuzzyMatchRangeList matches = fuzzy_match_find(arena, needle, display_string); + if(matches.count >= matches.needle_part_count) + { + result.v[result_idx].entity = entity; + result.v[result_idx].matches = matches; + result_idx += 1; + } + else + { + String8 search_tags = df_search_tags_from_entity(scratch.arena, entity); + if(search_tags.size != 0) + { + FuzzyMatchRangeList tag_matches = fuzzy_match_find(scratch.arena, needle, search_tags); + if(tag_matches.count >= tag_matches.needle_part_count) + { + result.v[result_idx].entity = entity; + result.v[result_idx].matches = matches; + result_idx += 1; + } + } + } + } + result.count = result_idx; + scratch_end(scratch); + return result; +} + +//- rjf: full path building, from file/folder entities + +internal String8 +df_full_path_from_entity(Arena *arena, DF_Entity *entity) +{ + String8 string = {0}; + { + Temp scratch = scratch_begin(&arena, 1); + String8List strs = {0}; + for(DF_Entity *e = entity; !df_entity_is_nil(e); e = e->parent) + { + if(e->kind == DF_EntityKind_File || + e->kind == DF_EntityKind_OverrideFileLink) + { + str8_list_push_front(scratch.arena, &strs, e->name); + } + } + StringJoin join = {0}; + join.sep = str8_lit("/"); + string = str8_list_join(arena, &strs, &join); + scratch_end(scratch); + } + return string; +} + +//- rjf: display string entities, for referencing entities in ui + +internal String8 +df_display_string_from_entity(Arena *arena, DF_Entity *entity) +{ + String8 result = {0}; + switch(entity->kind) + { + default: + { + if(entity->name.size != 0) + { + result = push_str8_copy(arena, entity->name); + } + else + { + String8 kind_string = df_g_entity_kind_display_string_table[entity->kind]; + result = push_str8f(arena, "%S $%I64u", kind_string, entity->id); + } + }break; + + case DF_EntityKind_Target: + { + if(entity->name.size != 0) + { + result = push_str8_copy(arena, entity->name); + } + else + { + DF_Entity *exe = df_entity_child_from_kind(entity, DF_EntityKind_Executable); + result = push_str8_copy(arena, exe->name); + } + }break; + + case DF_EntityKind_Breakpoint: + { + if(entity->name.size != 0) + { + result = push_str8_copy(arena, entity->name); + } + else if(entity->flags & DF_EntityFlag_HasVAddr) + { + result = str8_from_u64(arena, entity->vaddr, 16, 16, 0); + } + else + { + DF_Entity *symb = df_entity_child_from_kind(entity, DF_EntityKind_EntryPointName); + DF_Entity *file = df_entity_ancestor_from_kind(entity, DF_EntityKind_File); + if(!df_entity_is_nil(symb)) + { + result = push_str8_copy(arena, symb->name); + } + else if(!df_entity_is_nil(file) && entity->flags & DF_EntityFlag_HasTextPoint) + { + result = push_str8f(arena, "%S:%I64d:%I64d", file->name, entity->text_point.line, entity->text_point.column); + } + } + }break; + + case DF_EntityKind_Process: + { + DF_Entity *main_mod_child = df_entity_child_from_kind(entity, DF_EntityKind_Module); + String8 main_mod_name = str8_skip_last_slash(main_mod_child->name); + result = push_str8f(arena, "%S%s%sPID: %i%s", + main_mod_name, + main_mod_name.size != 0 ? " " : "", + main_mod_name.size != 0 ? "(" : "", + entity->ctrl_id, + main_mod_name.size != 0 ? ")" : ""); + }break; + + case DF_EntityKind_Thread: + { + String8 name = entity->name; + if(name.size == 0) + { + DF_Entity *process = df_entity_ancestor_from_kind(entity, DF_EntityKind_Process); + DF_Entity *first_thread = df_entity_child_from_kind(process, DF_EntityKind_Thread); + if(first_thread == entity) + { + name = str8_lit("Main Thread"); + } + } + result = push_str8f(arena, "%S%s%sTID: %i%s", + name, + name.size != 0 ? " " : "", + name.size != 0 ? "(" : "", + entity->ctrl_id, + name.size != 0 ? ")" : ""); + }break; + + case DF_EntityKind_Module: + { + result = push_str8_copy(arena, str8_skip_last_slash(entity->name)); + }break; + + case DF_EntityKind_RecentProject: + { + result = push_str8_copy(arena, str8_skip_last_slash(entity->name)); + }break; + } + return result; +} + +//- rjf: extra search tag strings for fuzzy filtering entities + +internal String8 +df_search_tags_from_entity(Arena *arena, DF_Entity *entity) +{ + String8 result = {0}; + if(entity->kind == DF_EntityKind_Thread) + { + Temp scratch = scratch_begin(&arena, 1); + DF_Entity *process = df_entity_ancestor_from_kind(entity, DF_EntityKind_Process); + CTRL_Unwind unwind = df_query_cached_unwind_from_thread(entity); + String8List strings = {0}; + for(U64 frame_num = unwind.frames.count; frame_num > 0; frame_num -= 1) + { + CTRL_UnwindFrame *f = &unwind.frames.v[frame_num-1]; + U64 rip_vaddr = regs_rip_from_arch_block(entity->arch, f->regs); + DF_Entity *module = df_module_from_process_vaddr(process, rip_vaddr); + U64 rip_voff = df_voff_from_vaddr(module, rip_vaddr); + DI_Key dbgi_key = df_dbgi_key_from_module(module); + String8 procedure_name = df_symbol_name_from_dbgi_key_voff(scratch.arena, &dbgi_key, rip_voff); + if(procedure_name.size != 0) + { + str8_list_push(scratch.arena, &strings, procedure_name); + } + } + StringJoin join = {0}; + join.sep = str8_lit(","); + result = str8_list_join(arena, &strings, &join); + scratch_end(scratch); + } + return result; +} + +//- rjf: entity -> color operations + +internal Vec4F32 +df_hsva_from_entity(DF_Entity *entity) +{ + Vec4F32 result = {0}; + if(entity->flags & DF_EntityFlag_HasColor) + { + result = entity->color_hsva; + } + return result; +} + +internal Vec4F32 +df_rgba_from_entity(DF_Entity *entity) +{ + Vec4F32 result = {0}; + if(entity->flags & DF_EntityFlag_HasColor) + { + Vec3F32 hsv = v3f32(entity->color_hsva.x, entity->color_hsva.y, entity->color_hsva.z); + Vec3F32 rgb = rgb_from_hsv(hsv); + result = v4f32(rgb.x, rgb.y, rgb.z, entity->color_hsva.w); + } + return result; +} + +//////////////////////////////// +//~ rjf: Name Allocation + +internal U64 +df_name_bucket_idx_from_string_size(U64 size) +{ + U64 size_rounded = u64_up_to_pow2(size+1); + size_rounded = ClampBot((1<<4), size_rounded); + U64 bucket_idx = 0; + switch(size_rounded) + { + case 1<<4: {bucket_idx = 0;}break; + case 1<<5: {bucket_idx = 1;}break; + case 1<<6: {bucket_idx = 2;}break; + case 1<<7: {bucket_idx = 3;}break; + case 1<<8: {bucket_idx = 4;}break; + case 1<<9: {bucket_idx = 5;}break; + case 1<<10:{bucket_idx = 6;}break; + default:{bucket_idx = ArrayCount(df_state->free_name_chunks)-1;}break; + } + return bucket_idx; +} + +internal String8 +df_name_alloc(DF_StateDeltaHistory *hist, String8 string) +{ + if(string.size == 0) {return str8_zero();} + U64 bucket_idx = df_name_bucket_idx_from_string_size(string.size); + DF_NameChunkNode *node = df_state->free_name_chunks[bucket_idx]; + + // rjf: pull from bucket free list + if(node != 0) + { + if(bucket_idx == ArrayCount(df_state->free_name_chunks)-1) + { + node = 0; + DF_NameChunkNode *prev = 0; + for(DF_NameChunkNode *n = df_state->free_name_chunks[bucket_idx]; + n != 0; + prev = n, n = n->next) + { + if(n->size >= string.size+1) + { + if(prev == 0) + { + df_state->free_name_chunks[bucket_idx] = n->next; + } + else + { + prev->next = n->next; + } + node = n; + break; + } + } + } + else + { + SLLStackPop(df_state->free_name_chunks[bucket_idx]); + } + } + + // rjf: no found node -> allocate new + if(node == 0) + { + U64 chunk_size = 0; + if(bucket_idx < ArrayCount(df_state->free_name_chunks)-1) + { + chunk_size = 1<<(bucket_idx+4); + } + else + { + chunk_size = u64_up_to_pow2(string.size); + } + U8 *chunk_memory = push_array(df_state->arena, U8, chunk_size); + node = (DF_NameChunkNode *)chunk_memory; + } + + // rjf: fill string & return + String8 allocated_string = str8((U8 *)node, string.size); + MemoryCopy((U8 *)node, string.str, string.size); + return allocated_string; +} + +internal void +df_name_release(DF_StateDeltaHistory *hist, String8 string) +{ + if(string.size == 0) {return;} + U64 bucket_idx = df_name_bucket_idx_from_string_size(string.size); + DF_NameChunkNode *node = (DF_NameChunkNode *)string.str; + node->size = u64_up_to_pow2(string.size); + SLLStackPush(df_state->free_name_chunks[bucket_idx], node); +} + +//////////////////////////////// +//~ rjf: Entity State Functions + +//- rjf: entity mutation notification codepath + +internal void +df_entity_notify_mutation(DF_Entity *entity) +{ + for(DF_Entity *e = entity; !df_entity_is_nil(e); e = e->parent) + { + DF_EntityKindFlags flags = df_g_entity_kind_flags_table[entity->kind]; + if(e == entity && flags & DF_EntityKindFlag_LeafMutationProjectConfig) + { + DF_CmdParams p = {0}; + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_WriteProjectData)); + } + if(e == entity && flags & DF_EntityKindFlag_LeafMutationSoftHalt && df_ctrl_targets_running()) + { + df_state->entities_mut_soft_halt = 1; + } + if(e == entity && flags & DF_EntityKindFlag_LeafMutationDebugInfoMap) + { + df_state->entities_mut_dbg_info_map = 1; + } + if(flags & DF_EntityKindFlag_TreeMutationSoftHalt && df_ctrl_targets_running()) + { + df_state->entities_mut_soft_halt = 1; + } + if(flags & DF_EntityKindFlag_TreeMutationDebugInfoMap) + { + df_state->entities_mut_dbg_info_map = 1; + } + } +} + +//- rjf: entity allocation + tree forming + +internal DF_Entity * +df_entity_alloc(DF_StateDeltaHistory *hist, DF_Entity *parent, DF_EntityKind kind) +{ + B32 user_defined_lifetime = !!(df_g_entity_kind_flags_table[kind] & DF_EntityKindFlag_UserDefinedLifetime); + U64 free_list_idx = !!user_defined_lifetime; + if(df_entity_is_nil(parent)) { parent = df_state->entities_root; } + + // rjf: empty free list -> push new + if(!df_state->entities_free[free_list_idx]) + { + DF_Entity *entity = push_array(df_state->entities_arena, DF_Entity, 1); + df_state->entities_count += 1; + df_state->entities_free_count += 1; + SLLStackPush(df_state->entities_free[free_list_idx], entity); + } + + // rjf: user-defined lifetimes -> push record of df_state info + if(user_defined_lifetime) + { + df_state_delta_history_push_struct_delta(hist, &df_state->entities_root); + df_state_delta_history_push_struct_delta(hist, &df_state->entities_free_count); + df_state_delta_history_push_struct_delta(hist, &df_state->entities_active_count); + df_state_delta_history_push_struct_delta(hist, &df_state->entities_free[free_list_idx]); + df_state_delta_history_push_struct_delta(hist, &df_state->kind_alloc_gens[kind]); + } + + // rjf: pop new entity off free-list + DF_Entity *entity = df_state->entities_free[free_list_idx]; + SLLStackPop(df_state->entities_free[free_list_idx]); + df_state->entities_free_count -= 1; + df_state->entities_active_count += 1; + + // rjf: user-defined lifetimes -> push records of initial entity data + if(user_defined_lifetime) + { + df_state_delta_history_push_struct_delta(hist, &entity->next); + df_state_delta_history_push_struct_delta(hist, &entity->prev); + df_state_delta_history_push_struct_delta(hist, &entity->first); + df_state_delta_history_push_struct_delta(hist, &entity->last); + df_state_delta_history_push_struct_delta(hist, &entity->parent); + df_state_delta_history_push_struct_delta(hist, &entity->generation); + df_state_delta_history_push_struct_delta(hist, &entity->id); + df_state_delta_history_push_struct_delta(hist, &entity->kind); + if(!df_entity_is_nil(parent)) + { + df_state_delta_history_push_struct_delta(hist, &parent->first); + df_state_delta_history_push_struct_delta(hist, &parent->last); + } + if(!df_entity_is_nil(parent->last)) + { + df_state_delta_history_push_struct_delta(hist, &parent->last->next); + } + } + + // rjf: zero entity + { + U64 generation = entity->generation; + MemoryZeroStruct(entity); + entity->generation = generation; + } + + // rjf: set up alloc'd entity links + entity->first = entity->last = entity->next = entity->prev = entity->parent = &df_g_nil_entity; + entity->parent = parent; + + // rjf: stitch up parent links + if(df_entity_is_nil(parent)) + { + df_state->entities_root = entity; + } + else + { + DLLPushBack_NPZ(&df_g_nil_entity, parent->first, parent->last, entity, next, prev); + } + + // rjf: fill out metadata + entity->kind = kind; + df_state->entities_id_gen += 1; + entity->id = df_state->entities_id_gen; + entity->generation += 1; + entity->alloc_time_us = os_now_microseconds(); + + // rjf: dirtify caches + df_state->kind_alloc_gens[kind] += 1; + df_entity_notify_mutation(entity); + + // rjf: log + LogInfoNamedBlockF("new_entity") + { + log_infof("kind: \"%S\"\n", df_g_entity_kind_display_string_table[kind]); + log_infof("id: $0x%I64x\n", entity->id); + } + + return entity; +} + +internal void +df_entity_mark_for_deletion(DF_Entity *entity) +{ + if(!df_entity_is_nil(entity)) + { + entity->flags |= DF_EntityFlag_MarkedForDeletion; + df_entity_notify_mutation(entity); + } +} + +internal void +df_entity_release(DF_StateDeltaHistory *hist, DF_Entity *entity) +{ + Temp scratch = scratch_begin(0, 0); + + // rjf: unpack + U64 free_list_idx = !!(df_g_entity_kind_flags_table[entity->kind] & DF_EntityKindFlag_UserDefinedLifetime); + + // rjf: record pre-deletion entity state + df_state_delta_history_push_struct_delta(hist, &df_state->entities_free_count); + df_state_delta_history_push_struct_delta(hist, &df_state->entities_active_count); + + // rjf: release whole tree + typedef struct Task Task; + struct Task + { + Task *next; + DF_Entity *e; + }; + Task start_task = {0, entity}; + Task *first_task = &start_task; + Task *last_task = &start_task; + for(Task *task = first_task; task != 0; task = task->next) + { + for(DF_Entity *child = task->e->first; !df_entity_is_nil(child); child = child->next) + { + Task *t = push_array(scratch.arena, Task, 1); + t->e = child; + SLLQueuePush(first_task, last_task, t); + } + LogInfoNamedBlockF("end_entity") + { + String8 name = df_display_string_from_entity(scratch.arena, task->e); + log_infof("kind: \"%S\"\n", df_g_entity_kind_display_string_table[task->e->kind]); + log_infof("id: $0x%I64x\n", task->e->id); + log_infof("display_string: \"%S\"\n", name); + } + df_state_delta_history_push_struct_delta(hist, &task->e->first); + df_state_delta_history_push_struct_delta(hist, &task->e->last); + df_state_delta_history_push_struct_delta(hist, &task->e->next); + df_state_delta_history_push_struct_delta(hist, &task->e->prev); + df_state_delta_history_push_struct_delta(hist, &task->e->parent); + df_state_delta_history_push_struct_delta(hist, &df_state->kind_alloc_gens[task->e->kind]); + df_state_delta_history_push_struct_delta(hist, &df_state->entities_free[free_list_idx]); + df_set_thread_freeze_state(task->e, 0); + SLLStackPush(df_state->entities_free[free_list_idx], task->e); + df_state->entities_free_count += 1; + df_state->entities_active_count -= 1; + task->e->generation += 1; + if(task->e->name.size != 0) + { + df_name_release(hist, task->e->name); + } + df_state->kind_alloc_gens[task->e->kind] += 1; + } + + scratch_end(scratch); +} + +internal void +df_entity_change_parent(DF_StateDeltaHistory *hist, DF_Entity *entity, DF_Entity *old_parent, DF_Entity *new_parent) +{ + Assert(entity->parent == old_parent); + + // rjf: push delta records + if(hist != 0) + { + if(!df_entity_is_nil(old_parent)) + { + df_state_delta_history_push_struct_delta(df_state->hist, &old_parent->first); + df_state_delta_history_push_struct_delta(df_state->hist, &old_parent->last); + } + if(!df_entity_is_nil(new_parent)) + { + df_state_delta_history_push_struct_delta(df_state->hist, &new_parent->first); + df_state_delta_history_push_struct_delta(df_state->hist, &new_parent->last); + } + if(!df_entity_is_nil(entity->prev)) + { + df_state_delta_history_push_struct_delta(df_state->hist, &entity->prev->next); + } + if(!df_entity_is_nil(entity->next)) + { + df_state_delta_history_push_struct_delta(df_state->hist, &entity->next->prev); + } + df_state_delta_history_push_struct_delta(df_state->hist, &entity->next); + df_state_delta_history_push_struct_delta(df_state->hist, &entity->prev); + df_state_delta_history_push_struct_delta(df_state->hist, &entity->parent); + } + + // rjf: fix up links + if(!df_entity_is_nil(old_parent)) + { + DLLRemove_NPZ(&df_g_nil_entity, old_parent->first, old_parent->last, entity, next, prev); + } + if(!df_entity_is_nil(new_parent)) + { + DLLPushBack_NPZ(&df_g_nil_entity, new_parent->first, new_parent->last, entity, next, prev); + } + entity->parent = new_parent; + + // rjf: notify + df_entity_notify_mutation(entity); + df_entity_notify_mutation(new_parent); + df_entity_notify_mutation(old_parent); +} + +//- rjf: entity simple equipment + +internal void +df_entity_equip_txt_pt(DF_Entity *entity, TxtPt point) +{ + df_require_entity_nonnil(entity, return); + entity->text_point = point; + entity->flags |= DF_EntityFlag_HasTextPoint; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_txt_pt_alt(DF_Entity *entity, TxtPt point) +{ + df_require_entity_nonnil(entity, return); + entity->text_point_alt = point; + entity->flags |= DF_EntityFlag_HasTextPointAlt; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_entity_handle(DF_Entity *entity, DF_Handle handle) +{ + df_require_entity_nonnil(entity, return); + entity->entity_handle = handle; + entity->flags |= DF_EntityFlag_HasEntityHandle; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_b32(DF_Entity *entity, B32 b32) +{ + df_require_entity_nonnil(entity, return); + entity->b32 = b32; + entity->flags |= DF_EntityFlag_HasB32; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_u64(DF_Entity *entity, U64 u64) +{ + df_require_entity_nonnil(entity, return); + entity->u64 = u64; + entity->flags |= DF_EntityFlag_HasU64; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_rng1u64(DF_Entity *entity, Rng1U64 range) +{ + df_require_entity_nonnil(entity, return); + entity->rng1u64 = range; + entity->flags |= DF_EntityFlag_HasRng1U64; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_color_rgba(DF_Entity *entity, Vec4F32 rgba) +{ + df_require_entity_nonnil(entity, return); + Vec3F32 rgb = v3f32(rgba.x, rgba.y, rgba.z); + Vec3F32 hsv = hsv_from_rgb(rgb); + Vec4F32 hsva = v4f32(hsv.x, hsv.y, hsv.z, rgba.w); + df_entity_equip_color_hsva(entity, hsva); +} + +internal void +df_entity_equip_color_hsva(DF_Entity *entity, Vec4F32 hsva) +{ + df_require_entity_nonnil(entity, return); + entity->color_hsva = hsva; + entity->flags |= DF_EntityFlag_HasColor; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_death_timer(DF_Entity *entity, F32 seconds_til_death) +{ + df_require_entity_nonnil(entity, return); + entity->flags |= DF_EntityFlag_DiesWithTime; + entity->life_left = seconds_til_death; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_cfg_src(DF_Entity *entity, DF_CfgSrc cfg_src) +{ + df_require_entity_nonnil(entity, return); + entity->cfg_src = cfg_src; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_timestamp(DF_Entity *entity, U64 timestamp) +{ + df_require_entity_nonnil(entity, return); + entity->timestamp = timestamp; + df_entity_notify_mutation(entity); +} + +//- rjf: control layer correllation equipment + +internal void +df_entity_equip_ctrl_machine_id(DF_Entity *entity, CTRL_MachineID machine_id) +{ + df_require_entity_nonnil(entity, return); + entity->ctrl_machine_id = machine_id; + entity->flags |= DF_EntityFlag_HasCtrlMachineID; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_ctrl_handle(DF_Entity *entity, DMN_Handle handle) +{ + df_require_entity_nonnil(entity, return); + entity->ctrl_handle = handle; + entity->flags |= DF_EntityFlag_HasCtrlHandle; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_arch(DF_Entity *entity, Architecture arch) +{ + df_require_entity_nonnil(entity, return); + entity->arch = arch; + entity->flags |= DF_EntityFlag_HasArch; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_ctrl_id(DF_Entity *entity, U32 id) +{ + df_require_entity_nonnil(entity, return); + entity->ctrl_id = id; + entity->flags |= DF_EntityFlag_HasCtrlID; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_stack_base(DF_Entity *entity, U64 stack_base) +{ + df_require_entity_nonnil(entity, return); + entity->stack_base = stack_base; + entity->flags |= DF_EntityFlag_HasStackBase; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_tls_root(DF_Entity *entity, U64 tls_root) +{ + df_require_entity_nonnil(entity, return); + entity->tls_root = tls_root; + entity->flags |= DF_EntityFlag_HasTLSRoot; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_vaddr_rng(DF_Entity *entity, Rng1U64 range) +{ + df_require_entity_nonnil(entity, return); + entity->vaddr_rng = range; + entity->flags |= DF_EntityFlag_HasVAddrRng; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_vaddr(DF_Entity *entity, U64 vaddr) +{ + df_require_entity_nonnil(entity, return); + entity->vaddr = vaddr; + entity->flags |= DF_EntityFlag_HasVAddr; + df_entity_notify_mutation(entity); +} + +//- rjf: name equipment + +internal void +df_entity_equip_name(DF_StateDeltaHistory *hist, DF_Entity *entity, String8 name) +{ + df_require_entity_nonnil(entity, return); + if(entity->name.size != 0) + { + df_name_release(hist, entity->name); + } + if(name.size != 0) + { + entity->name = df_name_alloc(hist, name); + } + else + { + entity->name = str8_zero(); + } + entity->name_generation += 1; + df_entity_notify_mutation(entity); +} + +internal void +df_entity_equip_namef(DF_StateDeltaHistory *hist, DF_Entity *entity, char *fmt, ...) +{ + Temp scratch = scratch_begin(0, 0); + va_list args; + va_start(args, fmt); + String8 string = push_str8fv(scratch.arena, fmt, args); + va_end(args); + df_entity_equip_name(hist, entity, string); + scratch_end(scratch); +} + +//- rjf: opening folders/files & maintaining the entity model of the filesystem + +internal DF_Entity * +df_entity_from_path(String8 path, DF_EntityFromPathFlags flags) +{ + Temp scratch = scratch_begin(0, 0); + PathStyle path_style = PathStyle_Relative; + String8List path_parts = path_normalized_list_from_string(scratch.arena, path, &path_style); + StringMatchFlags path_match_flags = path_match_flags_from_os(operating_system_from_context()); + + //- rjf: pass 1: open parts, ignore overrides + DF_Entity *file_no_override = &df_g_nil_entity; + { + DF_Entity *parent = df_entity_root(); + for(String8Node *path_part_n = path_parts.first; + path_part_n != 0; + path_part_n = path_part_n->next) + { + // rjf: find next child + DF_Entity *next_parent = &df_g_nil_entity; + for(DF_Entity *child = parent->first; !df_entity_is_nil(child); child = child->next) + { + B32 name_matches = str8_match(child->name, path_part_n->string, path_match_flags); + if(name_matches && child->kind == DF_EntityKind_File) + { + next_parent = child; + break; + } + } + + // rjf: no next -> allocate one + if(df_entity_is_nil(next_parent)) + { + if(flags & DF_EntityFromPathFlag_OpenAsNeeded) + { + String8 parent_path = df_full_path_from_entity(scratch.arena, parent); + String8 path = push_str8f(scratch.arena, "%S%s%S", parent_path, parent_path.size != 0 ? "/" : "", path_part_n->string); + FileProperties file_properties = os_properties_from_file_path(path); + if(file_properties.created != 0 || flags & DF_EntityFromPathFlag_OpenMissing) + { + next_parent = df_entity_alloc(0, parent, DF_EntityKind_File); + df_entity_equip_name(0, next_parent, path_part_n->string); + next_parent->timestamp = file_properties.modified; + next_parent->flags |= DF_EntityFlag_IsFolder * !!(file_properties.flags & FilePropertyFlag_IsFolder); + next_parent->flags |= DF_EntityFlag_IsMissing * !!(file_properties.created == 0); + if(path_part_n->next != 0) + { + next_parent->flags |= DF_EntityFlag_IsFolder; + } + } + } + else + { + parent = &df_g_nil_entity; + break; + } + } + + // rjf: next parent -> follow it + parent = next_parent; + } + file_no_override = (parent != df_entity_root() ? parent : &df_g_nil_entity); + } + + //- rjf: pass 2: follow overrides + DF_Entity *file_overrides_applied = &df_g_nil_entity; + if(flags & DF_EntityFromPathFlag_AllowOverrides) + { + DF_Entity *parent = df_entity_root(); + for(String8Node *path_part_n = path_parts.first; + path_part_n != 0; + path_part_n = path_part_n->next) + { + // rjf: find next child + DF_Entity *next_parent = &df_g_nil_entity; + for(DF_Entity *child = parent->first; !df_entity_is_nil(child); child = child->next) + { + B32 name_matches = str8_match(child->name, path_part_n->string, path_match_flags); + if(name_matches && child->kind == DF_EntityKind_File) + { + next_parent = child; + } + if(name_matches && child->kind == DF_EntityKind_OverrideFileLink) + { + next_parent = df_entity_from_handle(child->entity_handle); + break; + } + } + + // rjf: no next -> allocate one + if(df_entity_is_nil(next_parent)) + { + if(flags & DF_EntityFromPathFlag_OpenAsNeeded) + { + String8 parent_path = df_full_path_from_entity(scratch.arena, parent); + String8 path = push_str8f(scratch.arena, "%S%s%S", parent_path, parent_path.size != 0 ? "/" : "", path_part_n->string); + FileProperties file_properties = os_properties_from_file_path(path); + if(file_properties.created != 0 || flags & DF_EntityFromPathFlag_OpenMissing) + { + next_parent = df_entity_alloc(0, parent, DF_EntityKind_File); + df_entity_equip_name(0, next_parent, path_part_n->string); + next_parent->timestamp = file_properties.modified; + next_parent->flags |= DF_EntityFlag_IsFolder * !!(file_properties.flags & FilePropertyFlag_IsFolder); + next_parent->flags |= DF_EntityFlag_IsMissing * !!(file_properties.created == 0); + if(path_part_n->next != 0) + { + next_parent->flags |= DF_EntityFlag_IsFolder; + } + } + } + else + { + parent = &df_g_nil_entity; + break; + } + } + + // rjf: next parent -> follow it + parent = next_parent; + } + file_overrides_applied = (parent != df_entity_root() ? parent : &df_g_nil_entity);; + } + + //- rjf: pick & return result + DF_Entity *result = (flags & DF_EntityFromPathFlag_AllowOverrides) ? file_overrides_applied : file_no_override; + if(flags & DF_EntityFromPathFlag_AllowOverrides && + result == file_overrides_applied && + result->flags & DF_EntityFlag_IsMissing) + { + result = file_no_override; + } + + scratch_end(scratch); + return result; +} + +internal DF_EntityList +df_possible_overrides_from_entity(Arena *arena, DF_Entity *entity) +{ + Temp scratch = scratch_begin(&arena, 1); + StringMatchFlags path_match_flags = path_match_flags_from_os(operating_system_from_context()); + DF_EntityList result = {0}; + df_entity_list_push(arena, &result, entity); + { + DF_EntityList links = df_query_cached_entity_list_with_kind(DF_EntityKind_OverrideFileLink); + String8List p_chain_names_to_entity = {0}; + for(DF_Entity *p = entity; + !df_entity_is_nil(p); + str8_list_push_front(scratch.arena, &p_chain_names_to_entity, p->name), p = p->parent) + { + // rjf: gather all links which would redirect to this chain + DF_EntityList links_going_to_p = {0}; + for(DF_EntityNode *n = links.first; n != 0; n = n->next) + { + DF_Entity *link_src = n->entity; + DF_Entity *link_dst = df_entity_from_handle(link_src->entity_handle); + if(link_dst == p) + { + df_entity_list_push(scratch.arena, &links_going_to_p, link_src); + } + } + + // rjf: for each link, gather possible overrides + for(DF_EntityNode *n = links_going_to_p.first; n != 0; n = n->next) + { + DF_Entity *link_src = n->entity; + DF_Entity *link_src_parent = link_src->parent; + + // rjf: find the sibling that this link overrides + DF_Entity *link_overridden_sibling = &df_g_nil_entity; + for(DF_Entity *child = link_src_parent->first; + !df_entity_is_nil(child); + child = child->next) + { + B32 name_matches = str8_match(child->name, link_src->name, path_match_flags); + if(name_matches && child->kind == DF_EntityKind_File) + { + link_overridden_sibling = child; + break; + } + } + + // rjf: descend tree if needed, by the chain names, find override + DF_Entity *override = link_overridden_sibling; + if(!df_entity_is_nil(override)) + { + DF_Entity *parent = override; + for(String8Node *path_part_n = p_chain_names_to_entity.first; + path_part_n != 0; + path_part_n = path_part_n->next) + { + // rjf: find next child + DF_Entity *next_parent = &df_g_nil_entity; + for(DF_Entity *child = parent->first; !df_entity_is_nil(child); child = child->next) + { + B32 name_matches = str8_match(child->name, path_part_n->string, path_match_flags); + if(name_matches && child->kind == DF_EntityKind_File) + { + next_parent = child; + break; + } + } + + // rjf: no next -> allocate one + if(df_entity_is_nil(next_parent)) + { + next_parent = df_entity_alloc(0, parent, DF_EntityKind_File); + df_entity_equip_name(0, next_parent, path_part_n->string); + String8 path = df_full_path_from_entity(scratch.arena, next_parent); + FileProperties file_properties = os_properties_from_file_path(path); + next_parent->timestamp = file_properties.modified; + next_parent->flags |= DF_EntityFlag_IsFolder * !!(file_properties.flags & FilePropertyFlag_IsFolder); + next_parent->flags |= DF_EntityFlag_IsMissing * !!(file_properties.created == 0); + } + + // rjf: next parent -> follow it + parent = next_parent; + } + override = parent; + } + + // rjf: valid override -> push + if(!df_entity_is_nil(override)) + { + df_entity_list_push(arena, &result, override); + } + } + } + } + scratch_end(scratch); + return result; +} + +//- rjf: top-level state queries + +internal DF_Entity * +df_entity_root(void) +{ + return df_state->entities_root; +} + +internal DF_EntityList +df_push_entity_list_with_kind(Arena *arena, DF_EntityKind kind) +{ + ProfBeginFunction(); + DF_EntityList result = {0}; + for(DF_Entity *entity = df_state->entities_root; + !df_entity_is_nil(entity); + entity = df_entity_rec_df_pre(entity, &df_g_nil_entity).next) + { + if(!entity->deleted && entity->kind == kind) + { + df_entity_list_push(arena, &result, entity); + } + } + ProfEnd(); + return result; +} + +internal DF_Entity * +df_entity_from_id(DF_EntityID id) +{ + DF_Entity *result = &df_g_nil_entity; + for(DF_Entity *e = df_entity_root(); + !df_entity_is_nil(e); + e = df_entity_rec_df_pre(e, &df_g_nil_entity).next) + { + if(e->id == id) + { + result = e; + break; + } + } + return result; +} + +internal DF_Entity * +df_machine_entity_from_machine_id(CTRL_MachineID machine_id) +{ + DF_Entity *result = &df_g_nil_entity; + for(DF_Entity *e = df_entity_root(); + !df_entity_is_nil(e); + e = df_entity_rec_df_pre(e, &df_g_nil_entity).next) + { + if(e->kind == DF_EntityKind_Machine && e->ctrl_machine_id == machine_id) + { + result = e; + break; + } + } + if(df_entity_is_nil(result)) + { + result = df_entity_alloc(0, df_entity_root(), DF_EntityKind_Machine); + df_entity_equip_ctrl_machine_id(result, machine_id); + } + return result; +} + +internal DF_Entity * +df_entity_from_ctrl_handle(CTRL_MachineID machine_id, DMN_Handle handle) +{ + DF_Entity *result = &df_g_nil_entity; + if(handle.u64[0] != 0) + { + for(DF_Entity *e = df_entity_root(); + !df_entity_is_nil(e); + e = df_entity_rec_df_pre(e, &df_g_nil_entity).next) + { + if(e->flags & DF_EntityFlag_HasCtrlMachineID && + e->flags & DF_EntityFlag_HasCtrlHandle && + e->ctrl_machine_id == machine_id && + MemoryMatchStruct(&e->ctrl_handle, &handle)) + { + result = e; + break; + } + } + } + return result; +} + +internal DF_Entity * +df_entity_from_ctrl_id(CTRL_MachineID machine_id, U32 id) +{ + DF_Entity *result = &df_g_nil_entity; + if(id != 0) + { + for(DF_Entity *e = df_entity_root(); + !df_entity_is_nil(e); + e = df_entity_rec_df_pre(e, &df_g_nil_entity).next) + { + if(e->flags & DF_EntityFlag_HasCtrlMachineID && + e->flags & DF_EntityFlag_HasCtrlID && + e->ctrl_machine_id == machine_id && + e->ctrl_id == id) + { + result = e; + break; + } + } + } + return result; +} + +internal DF_Entity * +df_entity_from_name_and_kind(String8 string, DF_EntityKind kind) +{ + DF_Entity *result = &df_g_nil_entity; + DF_EntityList all_of_this_kind = df_query_cached_entity_list_with_kind(kind); + for(DF_EntityNode *n = all_of_this_kind.first; n != 0; n = n->next) + { + if(str8_match(n->entity->name, string, 0)) + { + result = n->entity; + break; + } + } + return result; +} + +internal DF_Entity * +df_entity_from_u64_and_kind(U64 u64, DF_EntityKind kind) +{ + DF_Entity *result = &df_g_nil_entity; + DF_EntityList all_of_this_kind = df_query_cached_entity_list_with_kind(kind); + for(DF_EntityNode *n = all_of_this_kind.first; n != 0; n = n->next) + { + if(n->entity->u64 == u64) + { + result = n->entity; + break; + } + } + return result; +} + +//- rjf: entity freezing state + +internal void +df_set_thread_freeze_state(DF_Entity *thread, B32 frozen) +{ + DF_Handle thread_handle = df_handle_from_entity(thread); + DF_HandleNode *already_frozen_node = df_handle_list_find(&df_state->frozen_threads, thread_handle); + B32 is_frozen = !!already_frozen_node; + B32 should_be_frozen = frozen; + + // rjf: not frozen => frozen + if(!is_frozen && should_be_frozen) + { + DF_HandleNode *node = df_state->free_handle_node; + if(node) + { + SLLStackPop(df_state->free_handle_node); + } + else + { + node = push_array(df_state->arena, DF_HandleNode, 1); + } + node->handle = thread_handle; + df_handle_list_push_node(&df_state->frozen_threads, node); + df_state->entities_mut_soft_halt = 1; + } + + // rjf: frozen => not frozen + if(is_frozen && !should_be_frozen) + { + df_state->entities_mut_soft_halt = 1; + df_handle_list_remove(&df_state->frozen_threads, already_frozen_node); + SLLStackPush(df_state->free_handle_node, already_frozen_node); + } + + df_entity_notify_mutation(thread); +} + +internal B32 +df_entity_is_frozen(DF_Entity *entity) +{ + B32 is_frozen = !df_entity_is_nil(entity); + for(DF_Entity *e = entity; !df_entity_is_nil(e); e = df_entity_rec_df_pre(e, entity).next) + { + if(e->kind == DF_EntityKind_Thread) + { + B32 thread_is_frozen = !!df_handle_list_find(&df_state->frozen_threads, df_handle_from_entity(e)); + if(!thread_is_frozen) + { + is_frozen = 0; + break; + } + } + } + return is_frozen; +} + +//////////////////////////////// +//~ rjf: Command Stateful Functions + +internal void +df_register_cmd_specs(DF_CmdSpecInfoArray specs) +{ + U64 registrar_idx = df_state->total_registrar_count; + df_state->total_registrar_count += 1; + for(U64 idx = 0; idx < specs.count; idx += 1) + { + // rjf: extract info from array slot + DF_CmdSpecInfo *info = &specs.v[idx]; + + // rjf: skip empties + if(info->string.size == 0) + { + continue; + } + + // rjf: determine hash/slot + U64 hash = df_hash_from_string(info->string); + U64 slot = hash % df_state->cmd_spec_table_size; + + // rjf: allocate node & push + DF_CmdSpec *spec = push_array(df_state->arena, DF_CmdSpec, 1); + SLLStackPush_N(df_state->cmd_spec_table[slot], spec, hash_next); + + // rjf: fill node + DF_CmdSpecInfo *info_copy = &spec->info; + info_copy->string = push_str8_copy(df_state->arena, info->string); + info_copy->description = push_str8_copy(df_state->arena, info->description); + info_copy->search_tags = push_str8_copy(df_state->arena, info->search_tags); + info_copy->display_name = push_str8_copy(df_state->arena, info->display_name); + info_copy->flags = info->flags; + info_copy->query = info->query; + info_copy->canonical_icon_kind = info->canonical_icon_kind; + spec->registrar_index = registrar_idx; + spec->ordering_index = idx; + } +} + +internal DF_CmdSpec * +df_cmd_spec_from_string(String8 string) +{ + DF_CmdSpec *result = &df_g_nil_cmd_spec; + { + U64 hash = df_hash_from_string(string); + U64 slot = hash%df_state->cmd_spec_table_size; + for(DF_CmdSpec *n = df_state->cmd_spec_table[slot]; n != 0; n = n->hash_next) + { + if(str8_match(n->info.string, string, 0)) + { + result = n; + break; + } + } + } + return result; +} + +internal DF_CmdSpec * +df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind core_cmd_kind) +{ + String8 string = df_g_core_cmd_kind_spec_info_table[core_cmd_kind].string; + DF_CmdSpec *result = df_cmd_spec_from_string(string); + return result; +} + +internal void +df_cmd_spec_counter_inc(DF_CmdSpec *spec) +{ + if(!df_cmd_spec_is_nil(spec)) + { + spec->run_count += 1; + } +} + +internal DF_CmdSpecList +df_push_cmd_spec_list(Arena *arena) +{ + DF_CmdSpecList list = {0}; + for(U64 idx = 0; idx < df_state->cmd_spec_table_size; idx += 1) + { + for(DF_CmdSpec *spec = df_state->cmd_spec_table[idx]; spec != 0; spec = spec->hash_next) + { + df_cmd_spec_list_push(arena, &list, spec); + } + } + return list; +} + +//////////////////////////////// +//~ rjf: View Rule Spec Stateful Functions + +internal void +df_register_core_view_rule_specs(DF_CoreViewRuleSpecInfoArray specs) +{ + for(U64 idx = 0; idx < specs.count; idx += 1) + { + // rjf: extract info from array slot + DF_CoreViewRuleSpecInfo *info = &specs.v[idx]; + + // rjf: skip empties + if(info->string.size == 0) + { + continue; + } + + // rjf: determine hash/slot + U64 hash = df_hash_from_string(info->string); + U64 slot_idx = hash%df_state->view_rule_spec_table_size; + + // rjf: allocate node & push + DF_CoreViewRuleSpec *spec = push_array(df_state->arena, DF_CoreViewRuleSpec, 1); + SLLStackPush_N(df_state->view_rule_spec_table[slot_idx], spec, hash_next); + + // rjf: fill node + DF_CoreViewRuleSpecInfo *info_copy = &spec->info; + MemoryCopyStruct(info_copy, info); + info_copy->string = push_str8_copy(df_state->arena, info->string); + info_copy->display_string = push_str8_copy(df_state->arena, info->display_string); + info_copy->description = push_str8_copy(df_state->arena, info->description); + } +} + +internal DF_CoreViewRuleSpec * +df_core_view_rule_spec_from_string(String8 string) +{ + DF_CoreViewRuleSpec *spec = &df_g_nil_core_view_rule_spec; + { + U64 hash = df_hash_from_string(string); + U64 slot_idx = hash%df_state->view_rule_spec_table_size; + for(DF_CoreViewRuleSpec *s = df_state->view_rule_spec_table[slot_idx]; s != 0; s = s->hash_next) + { + if(str8_match(string, s->info.string, 0)) + { + spec = s; + break; + } + } + } + return spec; +} + +//////////////////////////////// +//~ rjf: Stepping "Trap Net" Builders + +// NOTE(rjf): Stepping Algorithm Overview (2024/01/17) +// +// The basic idea behind all stepping algorithms in the debugger are setting up +// a "trap net". A "trap net" is just a collection of high-level traps that are +// meant to "catch" a thread after letting it run. This trap net is submitted +// when the debugger frontend sends a "run" command (it is just empty if doing +// an actual 'run' or 'continue'). The debugger control thread then uses this +// trap net to program a state machine, to appropriately respond to a variety +// of debug events which it is passed from the OS. +// +// These are "high-level traps" because they can have specific behavioral info +// attached to them. These are encoded via the `CTRL_TrapFlags` type, which +// allow expression of the following behaviors: +// +// - end-stepping: when this trap is hit, it will end the stepping operation, +// and the target will not continue. +// - ignore-stack-pointer-check: when a trap in the trap net is hit, it will +// by-default be ignored if the thread's stack pointer has changed. this +// flag disables that behavior, for when the stack pointer is expected to +// change (e.g. step-out). +// - single-step-after-hit: when a trap with this flag is hit, the debugger +// will immediately single-step the thread which hit it. +// - save-stack-pointer: when a trap with this flag is hit, it will rewrite +// the stack pointer which is used to compare against, when deciding +// whether or not to filter a trap (based on stack pointer changes). +// - begin-spoof-mode: this enables "spoof mode". "spoof mode" is a special +// mode that disables the trap net entirely, and lets the thread run +// freely - but it catches the thread not with a trap, but a false return +// address. the debugger will overwrite a specific return address on the +// stack. this address will be overwritten with an address which does NOT +// point to a valid page, such that when the thread returns out of a +// particular call frame, the debugger will receive a debug event, at +// which point it can move the thread back to the correct return address, +// and resume with the trap net enabled. this is used in "step over" +// operations, because it avoids target <-> debugger "roundtrips" (e.g. +// target being stopped, debugger being called with debug events, then +// target resumes when debugger's control thread is done running) for +// recursions. (it doesn't make a difference with non-recursive calls, +// but the debugger can't detect the difference). +// +// Each stepping command prepares its trap net differently. +// +// --- Instruction Step Into -------------------------------------------------- +// In this case, no trap net is prepared, and only a low-level single-step is +// performed. +// +// --- Instruction Step Over -------------------------------------------------- +// To build a trap net for an instruction-level step-over, the next instruction +// at the thread's current instruction pointer is decoded. If it is a call +// instruction, or if it is a repeating instruction, then a trap with the +// 'end-stepping' behavior is placed at the instruction immediately following +// the 'call' instruction. +// +// --- Line Step Into --------------------------------------------------------- +// For a source-line step-into, the thread's instruction pointer is first used +// to look up into the debug info's line info, to find the machine code in the +// thread's current source line. Every instruction in this range is decoded. +// Traps are then built in the following way: +// +// - 'call' instruction -> if can decode call destination address, place +// "end-stepping | ignore-stack-pointer-check" trap at destination. if +// can't, "end-stepping | single-step-after | ignore-stack-pointer-check" +// trap at call. +// - 'jmp' (both unconditional & conditional) -> if can decode jump destination +// address, AND if jump leaves the line, place "end-stepping | ignore- +// stack-pointer-check" trap at destination. if can't, "end-stepping | +// single-step-after | ignore-stack-pointer-check" trap at jmp. if jump +// stays within the line, do nothing. +// - 'return' -> place "end-stepping | single-step-after" trap at return inst. +// - "end-stepping" trap is placed at the first address after the line, to +// catch all steps which simply proceed linearly through the instruction +// stream. +// +// --- Line Step Over --------------------------------------------------------- +// For a source-line step-over, the thread's instruction pointer is first used +// to look up into the debug info's line info, to find the machine code in the +// thread's current source line. Every instruction in this range is decoded. +// Traps are then built in the following way: +// +// - 'call' instruction -> place "single-step-after | begin-spoof-mode" trap at +// call instruction. +// - 'jmp' (both unconditional & conditional) -> if can decode jump destination +// address, AND if jump leaves the line, place "end-stepping" trap at +// destination. if can't, "end-stepping | single-step-after" trap at jmp. +// if jump stays within the line, do nothing. +// - 'return' -> place "end-stepping | single-step-after" trap at return inst. +// - "end-stepping" trap is placed at the first address after the line, to +// catch all steps which simply proceed linearly through the instruction +// stream. +// - for any instructions which may change the stack pointer, traps are placed +// at them with the "save-stack-pointer | single-step-after" behaviors. + +internal CTRL_TrapList +df_trap_net_from_thread__step_over_inst(Arena *arena, DF_Entity *thread) +{ + Temp scratch = scratch_begin(&arena, 1); + CTRL_TrapList result = {0}; + + // rjf: thread => unpacked info + DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); + Architecture arch = df_architecture_from_entity(thread); + U64 ip_vaddr = ctrl_query_cached_rip_from_thread(df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); + + // rjf: ip => machine code + String8 machine_code = {0}; + { + Rng1U64 rng = r1u64(ip_vaddr, ip_vaddr+max_instruction_size_from_arch(arch)); + CTRL_ProcessMemorySlice machine_code_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, rng, os_now_microseconds()+5000); + machine_code = machine_code_slice.data; + } + + // rjf: build traps if machine code was read successfully + if(machine_code.size != 0) + { + // rjf: decode instruction + DF_Inst inst = df_single_inst_from_machine_code(scratch.arena, arch, ip_vaddr, machine_code); + + // rjf: call => run until call returns + if(inst.flags & DF_InstFlag_Call || inst.flags & DF_InstFlag_Repeats) + { + CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, ip_vaddr+inst.size}; + ctrl_trap_list_push(arena, &result, &trap); + } + } + + scratch_end(scratch); + return result; +} + +internal CTRL_TrapList +df_trap_net_from_thread__step_over_line(Arena *arena, DF_Entity *thread) +{ + Temp scratch = scratch_begin(&arena, 1); + log_infof("step_over_line:\n{\n"); + CTRL_TrapList result = {0}; + + // rjf: thread => info + DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); + DF_Entity *module = df_module_from_thread(thread); + DI_Key dbgi_key = df_dbgi_key_from_module(module); + Architecture arch = df_architecture_from_entity(thread); + U64 ip_vaddr = ctrl_query_cached_rip_from_thread(df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); + log_infof("ip_vaddr: 0x%I64x\n", ip_vaddr); + log_infof("dbgi_key: {%S, 0x%I64x}\n", dbgi_key.path, dbgi_key.min_timestamp); + + // rjf: ip => line vaddr range + Rng1U64 line_vaddr_rng = {0}; + { + U64 ip_voff = df_voff_from_vaddr(module, ip_vaddr); + DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, ip_voff); + Rng1U64 line_voff_rng = {0}; + if(lines.first != 0) + { + line_voff_rng = lines.first->v.voff_range; + line_vaddr_rng = df_vaddr_range_from_voff_range(module, line_voff_rng); + DF_Entity *file = df_entity_from_handle(lines.first->v.file); + log_infof("line: {%S:%I64i}\n", file->name, lines.first->v.pt.line); + } + log_infof("voff_range: {0x%I64x, 0x%I64x}\n", line_voff_rng.min, line_voff_rng.max); + log_infof("vaddr_range: {0x%I64x, 0x%I64x}\n", line_vaddr_rng.min, line_vaddr_rng.max); + } + + // rjf: opl line_vaddr_rng -> 0xf00f00 or 0xfeefee? => include in line vaddr range + // + // MSVC exports line info at these line numbers when /JMC (Just My Code) debugging + // is enabled. This is enabled by default normally. + { + U64 opl_line_voff_rng = df_voff_from_vaddr(module, line_vaddr_rng.max); + DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, opl_line_voff_rng); + if(lines.first != 0 && (lines.first->v.pt.line == 0xf00f00 || lines.first->v.pt.line == 0xfeefee)) + { + line_vaddr_rng.max = df_vaddr_from_voff(module, lines.first->v.voff_range.max); + } + } + + // rjf: line vaddr range => did we find anything successfully? + B32 good_line_info = (line_vaddr_rng.max != 0); + + // rjf: line vaddr range => line's machine code + String8 machine_code = {0}; + if(good_line_info) + { + CTRL_ProcessMemorySlice machine_code_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, line_vaddr_rng, os_now_microseconds()+50000); + machine_code = machine_code_slice.data; + LogInfoNamedBlockF("machine_code_slice") + { + log_infof("stale: %i\n", machine_code_slice.stale); + log_infof("any_byte_bad: %i\n", machine_code_slice.any_byte_bad); + log_infof("any_byte_changed: %i\n", machine_code_slice.any_byte_changed); + log_infof("bytes:\n[\n"); + for(U64 idx = 0; idx < machine_code_slice.data.size; idx += 1) + { + log_infof("0x%x,", machine_code_slice.data.str[idx]); + if(idx%16 == 15 || idx+1 == machine_code_slice.data.size) + { + log_infof("\n"); + } + } + log_infof("]\n"); + } + } + + // rjf: machine code => ctrl flow analysis + DF_CtrlFlowInfo ctrl_flow_info = {0}; + if(good_line_info) + { + ctrl_flow_info = df_ctrl_flow_info_from_arch_vaddr_code(scratch.arena, + DF_InstFlag_Call| + DF_InstFlag_Branch| + DF_InstFlag_UnconditionalJump| + DF_InstFlag_ChangesStackPointer| + DF_InstFlag_Return, + arch, + line_vaddr_rng.min, + machine_code); + LogInfoNamedBlockF("ctrl_flow_info") + { + log_infof("flags: %x\n", ctrl_flow_info.flags); + LogInfoNamedBlockF("exit_points") for(DF_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) + { + log_infof("{vaddr:0x%I64x, jump_dest_vaddr:0x%I64x, expected_sp_delta:0x%I64x, inst_flags:%x}\n", n->v.vaddr, n->v.jump_dest_vaddr, n->v.expected_sp_delta, n->v.inst_flags); + } + } + } + + // rjf: push traps for all exit points + if(good_line_info) for(DF_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) + { + DF_CtrlFlowPoint *point = &n->v; + CTRL_TrapFlags flags = 0; + B32 add = 1; + U64 trap_addr = point->vaddr; + + // rjf: branches/jumps/returns => single-step & end, OR trap @ destination. + if(point->inst_flags & (DF_InstFlag_Branch| + DF_InstFlag_UnconditionalJump| + DF_InstFlag_Return)) + { + flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_EndStepping); + + // rjf: omit if this jump stays inside of this line + if(contains_1u64(line_vaddr_rng, point->jump_dest_vaddr)) + { + add = 0; + } + + // rjf: trap @ destination, if we can - we can avoid a single-step this way. + if(point->jump_dest_vaddr != 0) + { + trap_addr = point->jump_dest_vaddr; + flags &= ~CTRL_TrapFlag_SingleStepAfterHit; + } + + } + + // rjf: call => place spoof at return spot in stack, single-step after hitting + else if(point->inst_flags & DF_InstFlag_Call) + { + flags |= (CTRL_TrapFlag_BeginSpoofMode|CTRL_TrapFlag_SingleStepAfterHit); + } + + // rjf: instruction changes stack pointer => save off the stack pointer, single-step over, keep stepping + else if(point->inst_flags & DF_InstFlag_ChangesStackPointer) + { + flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_SaveStackPointer); + } + + // rjf: add if appropriate + if(add) + { + CTRL_Trap trap = {flags, trap_addr}; + ctrl_trap_list_push(arena, &result, &trap); + } + } + + // rjf: push trap for natural linear flow + if(good_line_info) + { + CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, line_vaddr_rng.max}; + ctrl_trap_list_push(arena, &result, &trap); + } + + // rjf: log + LogInfoNamedBlockF("traps") for(CTRL_TrapNode *n = result.first; n != 0; n = n->next) + { + log_infof("{flags:0x%x, vaddr:0x%I64x}\n", n->v.flags, n->v.vaddr); + } + + scratch_end(scratch); + log_infof("}\n\n"); + return result; +} + +internal CTRL_TrapList +df_trap_net_from_thread__step_into_line(Arena *arena, DF_Entity *thread) +{ + Temp scratch = scratch_begin(&arena, 1); + CTRL_TrapList result = {0}; + + // rjf: thread => info + DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); + DF_Entity *module = df_module_from_thread(thread); + DI_Key dbgi_key = df_dbgi_key_from_module(module); + Architecture arch = df_architecture_from_entity(thread); + U64 ip_vaddr = ctrl_query_cached_rip_from_thread(df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); + + // rjf: ip => line vaddr range + Rng1U64 line_vaddr_rng = {0}; + { + U64 ip_voff = df_voff_from_vaddr(module, ip_vaddr); + DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, ip_voff); + Rng1U64 line_voff_rng = {0}; + if(lines.first != 0) + { + line_voff_rng = lines.first->v.voff_range; + line_vaddr_rng = df_vaddr_range_from_voff_range(module, line_voff_rng); + } + } + + // rjf: opl line_vaddr_rng -> 0xf00f00 or 0xfeefee? => include in line vaddr range + // + // MSVC exports line info at these line numbers when /JMC (Just My Code) debugging + // is enabled. This is enabled by default normally. + { + U64 opl_line_voff_rng = df_voff_from_vaddr(module, line_vaddr_rng.max); + DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, opl_line_voff_rng); + if(lines.first != 0 && (lines.first->v.pt.line == 0xf00f00 || lines.first->v.pt.line == 0xfeefee)) + { + line_vaddr_rng.max = df_vaddr_from_voff(module, lines.first->v.voff_range.max); + } + } + + // rjf: line vaddr range => did we find anything successfully? + B32 good_line_info = (line_vaddr_rng.max != 0); + + // rjf: line vaddr range => line's machine code + String8 machine_code = {0}; + if(good_line_info) + { + CTRL_ProcessMemorySlice machine_code_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, line_vaddr_rng, os_now_microseconds()+5000); + machine_code = machine_code_slice.data; + } + + // rjf: machine code => ctrl flow analysis + DF_CtrlFlowInfo ctrl_flow_info = {0}; + if(good_line_info) + { + ctrl_flow_info = df_ctrl_flow_info_from_arch_vaddr_code(scratch.arena, + DF_InstFlag_Call| + DF_InstFlag_Branch| + DF_InstFlag_UnconditionalJump| + DF_InstFlag_ChangesStackPointer| + DF_InstFlag_Return, + arch, + line_vaddr_rng.min, + machine_code); + } + + // rjf: push traps for all exit points + if(good_line_info) for(DF_CtrlFlowPointNode *n = ctrl_flow_info.exit_points.first; n != 0; n = n->next) + { + DF_CtrlFlowPoint *point = &n->v; + CTRL_TrapFlags flags = 0; + B32 add = 1; + U64 trap_addr = point->vaddr; + + // rjf: branches/jumps/returns => single-step & end, OR trap @ destination. + if(point->inst_flags & (DF_InstFlag_Call| + DF_InstFlag_Branch| + DF_InstFlag_UnconditionalJump| + DF_InstFlag_Return)) + { + flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_EndStepping|CTRL_TrapFlag_IgnoreStackPointerCheck); + + // rjf: omit if this jump stays inside of this line + if(contains_1u64(line_vaddr_rng, point->jump_dest_vaddr)) + { + add = 0; + } + + // rjf: trap @ destination, if we can - we can avoid a single-step this way. + if(point->jump_dest_vaddr != 0) + { + trap_addr = point->jump_dest_vaddr; + flags &= ~CTRL_TrapFlag_SingleStepAfterHit; + } + } + + // rjf: instruction changes stack pointer => save off the stack pointer, single-step over, keep stepping + else if(point->inst_flags & DF_InstFlag_ChangesStackPointer) + { + flags |= (CTRL_TrapFlag_SingleStepAfterHit|CTRL_TrapFlag_SaveStackPointer); + } + + // rjf: add if appropriate + if(add) + { + CTRL_Trap trap = {flags, trap_addr}; + ctrl_trap_list_push(arena, &result, &trap); + } + } + + // rjf: push trap for natural linear flow + if(good_line_info) + { + CTRL_Trap trap = {CTRL_TrapFlag_EndStepping, line_vaddr_rng.max}; + ctrl_trap_list_push(arena, &result, &trap); + } + + scratch_end(scratch); + return result; +} + +//////////////////////////////// +//~ rjf: Modules & Debug Info Mappings + +//- rjf: module <=> debug info keys + +internal DI_Key +df_dbgi_key_from_module(DF_Entity *module) +{ + DF_Entity *debug_info_path = df_entity_child_from_kind(module, DF_EntityKind_DebugInfoPath); + DI_Key key = {debug_info_path->name, debug_info_path->timestamp}; + return key; +} + +internal DF_EntityList +df_modules_from_dbgi_key(Arena *arena, DI_Key *dbgi_key) +{ + DF_EntityList list = {0}; + DF_EntityList all_modules = df_query_cached_entity_list_with_kind(DF_EntityKind_Module); + for(DF_EntityNode *n = all_modules.first; n != 0; n = n->next) + { + DF_Entity *module = n->entity; + DI_Key module_dbgi_key = df_dbgi_key_from_module(module); + if(di_key_match(&module_dbgi_key, dbgi_key)) + { + df_entity_list_push(arena, &list, module); + } + } + return list; +} + +//- rjf: voff <=> vaddr + +internal U64 +df_base_vaddr_from_module(DF_Entity *module) +{ + U64 module_base_vaddr = module->vaddr; + return module_base_vaddr; +} + +internal U64 +df_voff_from_vaddr(DF_Entity *module, U64 vaddr) +{ + U64 module_base_vaddr = df_base_vaddr_from_module(module); + U64 voff = vaddr - module_base_vaddr; + return voff; +} + +internal U64 +df_vaddr_from_voff(DF_Entity *module, U64 voff) +{ + U64 module_base_vaddr = df_base_vaddr_from_module(module); + U64 vaddr = voff + module_base_vaddr; + return vaddr; +} + +internal Rng1U64 +df_voff_range_from_vaddr_range(DF_Entity *module, Rng1U64 vaddr_rng) +{ + U64 rng_size = dim_1u64(vaddr_rng); + Rng1U64 voff_rng = {0}; + voff_rng.min = df_voff_from_vaddr(module, vaddr_rng.min); + voff_rng.max = voff_rng.min + rng_size; + return voff_rng; +} + +internal Rng1U64 +df_vaddr_range_from_voff_range(DF_Entity *module, Rng1U64 voff_rng) +{ + U64 rng_size = dim_1u64(voff_rng); + Rng1U64 vaddr_rng = {0}; + vaddr_rng.min = df_vaddr_from_voff(module, voff_rng.min); + vaddr_rng.max = vaddr_rng.min + rng_size; + return vaddr_rng; +} + +//////////////////////////////// +//~ rjf: Debug Info Lookups + +//- rjf: symbol lookups + +internal String8 +df_symbol_name_from_dbgi_key_voff(Arena *arena, DI_Key *dbgi_key, U64 voff) +{ + String8 result = {0}; + { + Temp scratch = scratch_begin(&arena, 1); + DI_Scope *scope = di_scope_open(); + RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); + if(result.size == 0) + { + U64 scope_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_ScopeVMap, voff); + RDI_Scope *scope = rdi_element_from_name_idx(rdi, Scopes, scope_idx); + U64 proc_idx = scope->proc_idx; + RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, proc_idx); + U64 name_size = 0; + U8 *name_ptr = rdi_string_from_idx(rdi, procedure->name_string_idx, &name_size); + result = push_str8_copy(arena, str8(name_ptr, name_size)); + } + if(result.size == 0) + { + U64 global_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_GlobalVMap, voff); + RDI_GlobalVariable *global_var = rdi_element_from_name_idx(rdi, GlobalVariables, global_idx); + U64 name_size = 0; + U8 *name_ptr = rdi_string_from_idx(rdi, global_var->name_string_idx, &name_size); + result = push_str8_copy(arena, str8(name_ptr, name_size)); + } + di_scope_close(scope); + scratch_end(scratch); + } + return result; +} + +internal String8 +df_symbol_name_from_process_vaddr(Arena *arena, DF_Entity *process, U64 vaddr) +{ + String8 result = {0}; + { + DF_Entity *module = df_module_from_process_vaddr(process, vaddr); + DI_Key dbgi_key = df_dbgi_key_from_module(module); + U64 voff = df_voff_from_vaddr(module, vaddr); + result = df_symbol_name_from_dbgi_key_voff(arena, &dbgi_key, voff); + } + return result; +} + +//- rjf: symbol -> voff lookups + +internal U64 +df_voff_from_dbgi_key_symbol_name(DI_Key *dbgi_key, String8 symbol_name) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DI_Scope *scope = di_scope_open(); + U64 result = 0; + { + RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); + RDI_NameMapKind name_map_kinds[] = + { + RDI_NameMapKind_GlobalVariables, + RDI_NameMapKind_Procedures, + }; + if(rdi != &di_rdi_parsed_nil) + { + for(U64 name_map_kind_idx = 0; + name_map_kind_idx < ArrayCount(name_map_kinds); + name_map_kind_idx += 1) + { + RDI_NameMapKind name_map_kind = name_map_kinds[name_map_kind_idx]; + RDI_NameMap *name_map = rdi_element_from_name_idx(rdi, NameMaps, name_map_kind); + RDI_ParsedNameMap parsed_name_map = {0}; + rdi_parsed_from_name_map(rdi, name_map, &parsed_name_map); + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &parsed_name_map, symbol_name.str, symbol_name.size); + + // rjf: node -> num + U64 entity_num = 0; + if(node != 0) + { + switch(node->match_count) + { + case 1: + { + entity_num = node->match_idx_or_idx_run_first + 1; + }break; + default: + { + U32 num = 0; + U32 *run = rdi_matches_from_map_node(rdi, node, &num); + if(num != 0) + { + entity_num = run[0]+1; + } + }break; + } + } + + // rjf: num -> voff + U64 voff = 0; + if(entity_num != 0) switch(name_map_kind) + { + default:{}break; + case RDI_NameMapKind_GlobalVariables: + { + RDI_GlobalVariable *global_var = rdi_element_from_name_idx(rdi, GlobalVariables, entity_num-1); + voff = global_var->voff; + }break; + case RDI_NameMapKind_Procedures: + { + RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, entity_num-1); + RDI_Scope *scope = rdi_element_from_name_idx(rdi, Scopes, procedure->root_scope_idx); + voff = *rdi_element_from_name_idx(rdi, ScopeVOffData, scope->voff_range_first); + }break; + } + + // rjf: nonzero voff -> break + if(voff != 0) + { + result = voff; + break; + } + } + } + } + di_scope_close(scope); + scratch_end(scratch); + ProfEnd(); + return result; +} + +internal U64 +df_type_num_from_dbgi_key_name(DI_Key *dbgi_key, String8 name) +{ + ProfBeginFunction(); + DI_Scope *scope = di_scope_open(); + U64 result = 0; + { + RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); + RDI_NameMap *name_map = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_Types); + RDI_ParsedNameMap parsed_name_map = {0}; + rdi_parsed_from_name_map(rdi, name_map, &parsed_name_map); + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &parsed_name_map, name.str, name.size); + U64 entity_num = 0; + if(node != 0) + { + switch(node->match_count) + { + case 1: + { + entity_num = node->match_idx_or_idx_run_first + 1; + }break; + default: + { + U32 num = 0; + U32 *run = rdi_matches_from_map_node(rdi, node, &num); + if(num != 0) + { + entity_num = run[0]+1; + } + }break; + } + } + result = entity_num; + } + di_scope_close(scope); + ProfEnd(); + return result; +} + +//- rjf: voff -> line info + +internal DF_LineList +df_lines_from_dbgi_key_voff(Arena *arena, DI_Key *dbgi_key, U64 voff) +{ + Temp scratch = scratch_begin(&arena, 1); + DI_Scope *scope = di_scope_open(); + RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); + DF_LineList result = {0}; + { + //- rjf: gather line tables + typedef struct LineTableNode LineTableNode; + struct LineTableNode + { + LineTableNode *next; + RDI_ParsedLineTable parsed_line_table; + }; + LineTableNode start_line_table = {0}; + RDI_Unit *unit = rdi_unit_from_voff(rdi, voff); + RDI_LineTable *unit_line_table = rdi_line_table_from_unit(rdi, unit); + rdi_parsed_from_line_table(rdi, unit_line_table, &start_line_table.parsed_line_table); + LineTableNode *top_line_table = &start_line_table; + RDI_Scope *scope = rdi_scope_from_voff(rdi, voff); + { + for(RDI_Scope *s = scope; + s->inline_site_idx != 0; + s = rdi_element_from_name_idx(rdi, Scopes, s->parent_scope_idx)) + { + RDI_InlineSite *inline_site = rdi_element_from_name_idx(rdi, InlineSites, s->inline_site_idx); + if(inline_site->line_table_idx != 0) + { + LineTableNode *n = push_array(scratch.arena, LineTableNode, 1); + SLLStackPush(top_line_table, n); + RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, inline_site->line_table_idx); + rdi_parsed_from_line_table(rdi, line_table, &n->parsed_line_table); + } + } + } + + //- rjf: gather lines in each line table + Rng1U64 shallowest_voff_range = {0}; + for(LineTableNode *n = top_line_table; n != 0; n = n->next) + { + RDI_ParsedLineTable parsed_line_table = n->parsed_line_table; + U64 line_info_idx = rdi_line_info_idx_from_voff(&parsed_line_table, voff); + if(line_info_idx < parsed_line_table.count) + { + RDI_Line *line = &parsed_line_table.lines[line_info_idx]; + RDI_Column *column = (line_info_idx < parsed_line_table.col_count) ? &parsed_line_table.cols[line_info_idx] : 0; + RDI_SourceFile *file = rdi_element_from_name_idx(rdi, SourceFiles, line->file_idx); + String8 file_normalized_full_path = {0}; + file_normalized_full_path.str = rdi_string_from_idx(rdi, file->normal_full_path_string_idx, &file_normalized_full_path.size); + DF_LineNode *n = push_array(arena, DF_LineNode, 1); + SLLQueuePush(result.first, result.last, n); + result.count += 1; + if(line->file_idx != 0 && file_normalized_full_path.size != 0) + { + n->v.file = df_handle_from_entity(df_entity_from_path(file_normalized_full_path, DF_EntityFromPathFlag_All)); + } + n->v.pt = txt_pt(line->line_num, column ? column->col_first : 1); + n->v.voff_range = r1u64(parsed_line_table.voffs[line_info_idx], parsed_line_table.voffs[line_info_idx+1]); + n->v.dbgi_key = *dbgi_key; + shallowest_voff_range = n->v.voff_range; + } + } + + //- rjf: clamp all lines from all tables by shallowest (most unwound) range + for(DF_LineNode *n = result.first; n != 0; n = n->next) + { + n->v.voff_range = intersect_1u64(n->v.voff_range, shallowest_voff_range); + } + } + di_scope_close(scope); + scratch_end(scratch); + return result; +} + +//- rjf: file:line -> line info + +internal DF_LineListArray +df_lines_array_from_file_line_range(Arena *arena, DF_Entity *file, Rng1S64 line_num_range) +{ + DF_LineListArray array = {0}; + { + array.count = dim_1s64(line_num_range)+1; + array.v = push_array(arena, DF_LineList, array.count); + } + Temp scratch = scratch_begin(&arena, 1); + DI_Scope *scope = di_scope_open(); + DI_KeyList dbgi_keys = df_push_active_dbgi_key_list(scratch.arena); + DF_EntityList overrides = df_possible_overrides_from_entity(scratch.arena, file); + for(DF_EntityNode *override_n = overrides.first; + override_n != 0; + override_n = override_n->next) + { + DF_Entity *override = override_n->entity; + String8 file_path = df_full_path_from_entity(scratch.arena, override); + String8 file_path_normalized = lower_from_str8(scratch.arena, file_path); + for(DI_KeyNode *dbgi_key_n = dbgi_keys.first; + dbgi_key_n != 0; + dbgi_key_n = dbgi_key_n->next) + { + // rjf: binary -> rdi + DI_Key key = dbgi_key_n->v; + RDI_Parsed *rdi = di_rdi_from_key(scope, &key, 0); + + // rjf: file_path_normalized * rdi -> src_id + B32 good_src_id = 0; + U32 src_id = 0; + if(rdi != &di_rdi_parsed_nil) + { + RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_NormalSourcePaths); + RDI_ParsedNameMap map = {0}; + rdi_parsed_from_name_map(rdi, mapptr, &map); + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, file_path_normalized.str, file_path_normalized.size); + if(node != 0) + { + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + good_src_id = 1; + src_id = ids[0]; + } + } + } + + // rjf: good src-id -> look up line info for visible range + if(good_src_id) + { + RDI_SourceFile *src = rdi_element_from_name_idx(rdi, SourceFiles, src_id); + RDI_SourceLineMap *src_line_map = rdi_element_from_name_idx(rdi, SourceLineMaps, src->source_line_map_idx); + RDI_ParsedSourceLineMap line_map = {0}; + rdi_parsed_from_source_line_map(rdi, src_line_map, &line_map); + U64 line_idx = 0; + for(S64 line_num = line_num_range.min; + line_num <= line_num_range.max; + line_num += 1, line_idx += 1) + { + DF_LineList *list = &array.v[line_idx]; + U32 voff_count = 0; + U64 *voffs = rdi_line_voffs_from_num(&line_map, u32_from_u64_saturate((U64)line_num), &voff_count); + for(U64 idx = 0; idx < voff_count; idx += 1) + { + U64 base_voff = voffs[idx]; + U64 unit_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_UnitVMap, base_voff); + RDI_Unit *unit = rdi_element_from_name_idx(rdi, Units, unit_idx); + RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, unit->line_table_idx); + RDI_ParsedLineTable unit_line_info = {0}; + rdi_parsed_from_line_table(rdi, line_table, &unit_line_info); + U64 line_info_idx = rdi_line_info_idx_from_voff(&unit_line_info, base_voff); + if(unit_line_info.voffs != 0) + { + Rng1U64 range = r1u64(base_voff, unit_line_info.voffs[line_info_idx+1]); + S64 actual_line = (S64)unit_line_info.lines[line_info_idx].line_num; + DF_LineNode *n = push_array(arena, DF_LineNode, 1); + n->v.voff_range = range; + n->v.pt.line = (S64)actual_line; + n->v.pt.column = 1; + n->v.dbgi_key = key; + SLLQueuePush(list->first, list->last, n); + list->count += 1; + } + } + } + } + + // rjf: good src id -> push to relevant dbgi keys + if(good_src_id) + { + di_key_list_push(arena, &array.dbgi_keys, &key); + } + } + } + di_scope_close(scope); + scratch_end(scratch); + return array; +} + +internal DF_LineList +df_lines_from_file_line_num(Arena *arena, DF_Entity *file, S64 line_num) +{ + DF_LineListArray array = df_lines_array_from_file_line_range(arena, file, r1s64(line_num, line_num+1)); + DF_LineList list = {0}; + if(array.count != 0) + { + list = array.v[0]; + } + return list; +} + +//- rjf: src -> voff lookups + +internal DF_TextLineSrc2DasmInfoListArray +df_text_line_src2dasm_info_list_array_from_src_line_range(Arena *arena, DF_Entity *file, Rng1S64 line_num_range) +{ + DF_TextLineSrc2DasmInfoListArray src2dasm_array = {0}; + { + src2dasm_array.count = dim_1s64(line_num_range)+1; + src2dasm_array.v = push_array(arena, DF_TextLineSrc2DasmInfoList, src2dasm_array.count); + } + Temp scratch = scratch_begin(&arena, 1); + DI_Scope *scope = di_scope_open(); + DI_KeyList dbgi_keys = df_push_active_dbgi_key_list(scratch.arena); + DF_EntityList overrides = df_possible_overrides_from_entity(scratch.arena, file); + for(DF_EntityNode *override_n = overrides.first; + override_n != 0; + override_n = override_n->next) + { + DF_Entity *override = override_n->entity; + String8 file_path = df_full_path_from_entity(scratch.arena, override); + String8 file_path_normalized = lower_from_str8(scratch.arena, file_path); + for(DI_KeyNode *dbgi_key_n = dbgi_keys.first; + dbgi_key_n != 0; + dbgi_key_n = dbgi_key_n->next) + { + // rjf: binary -> rdi + DI_Key key = dbgi_key_n->v; + RDI_Parsed *rdi = di_rdi_from_key(scope, &key, 0); + + // rjf: file_path_normalized * rdi -> src_id + B32 good_src_id = 0; + U32 src_id = 0; + if(rdi != &di_rdi_parsed_nil) + { + RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_NormalSourcePaths); + RDI_ParsedNameMap map = {0}; + rdi_parsed_from_name_map(rdi, mapptr, &map); + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, file_path_normalized.str, file_path_normalized.size); + if(node != 0) + { + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + good_src_id = 1; + src_id = ids[0]; + } + } + } + + // rjf: good src-id -> look up line info for visible range + if(good_src_id) + { + RDI_SourceFile *src = rdi_element_from_name_idx(rdi, SourceFiles, src_id); + RDI_SourceLineMap *src_line_map = rdi_element_from_name_idx(rdi, SourceLineMaps, src->source_line_map_idx); + RDI_ParsedSourceLineMap line_map = {0}; + rdi_parsed_from_source_line_map(rdi, src_line_map, &line_map); + U64 line_idx = 0; + for(S64 line_num = line_num_range.min; + line_num <= line_num_range.max; + line_num += 1, line_idx += 1) + { + DF_TextLineSrc2DasmInfoList *src2dasm_list = &src2dasm_array.v[line_idx]; + U32 voff_count = 0; + U64 *voffs = rdi_line_voffs_from_num(&line_map, u32_from_u64_saturate((U64)line_num), &voff_count); + for(U64 idx = 0; idx < voff_count; idx += 1) + { + U64 base_voff = voffs[idx]; + U64 unit_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_UnitVMap, base_voff); + RDI_Unit *unit = rdi_element_from_name_idx(rdi, Units, unit_idx); + RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, unit->line_table_idx); + RDI_ParsedLineTable unit_line_info = {0}; + rdi_parsed_from_line_table(rdi, line_table, &unit_line_info); + U64 line_info_idx = rdi_line_info_idx_from_voff(&unit_line_info, base_voff); + if(unit_line_info.voffs != 0) + { + Rng1U64 range = r1u64(base_voff, unit_line_info.voffs[line_info_idx+1]); + S64 actual_line = (S64)unit_line_info.lines[line_info_idx].line_num; + DF_TextLineSrc2DasmInfoNode *src2dasm_n = push_array(arena, DF_TextLineSrc2DasmInfoNode, 1); + src2dasm_n->v.voff_range = range; + src2dasm_n->v.remap_line = (S64)actual_line; + src2dasm_n->v.dbgi_key = key; + SLLQueuePush(src2dasm_list->first, src2dasm_list->last, src2dasm_n); + src2dasm_list->count += 1; + } + } + } + } + + // rjf: good src id -> push to relevant dbgi keys + if(good_src_id) + { + di_key_list_push(arena, &src2dasm_array.dbgi_keys, &key); + } + } + } + di_scope_close(scope); + scratch_end(scratch); + return src2dasm_array; +} + +//////////////////////////////// +//~ rjf: Process/Thread/Module Info Lookups + +internal DF_Entity * +df_module_from_process_vaddr(DF_Entity *process, U64 vaddr) +{ + ProfBeginFunction(); + DF_Entity *module = &df_g_nil_entity; + for(DF_Entity *child = process->first; !df_entity_is_nil(child); child = child->next) + { + if(child->kind == DF_EntityKind_Module && contains_1u64(child->vaddr_rng, vaddr)) + { + module = child; + break; + } + } + ProfEnd(); + return module; +} + +internal DF_Entity * +df_module_from_thread(DF_Entity *thread) +{ + DF_Entity *process = thread->parent; + U64 rip = df_query_cached_rip_from_thread(thread); + return df_module_from_process_vaddr(process, rip); +} + +internal U64 +df_tls_base_vaddr_from_process_root_rip(DF_Entity *process, U64 root_vaddr, U64 rip_vaddr) +{ + ProfBeginFunction(); + U64 base_vaddr = 0; + Temp scratch = scratch_begin(0, 0); + if(!df_ctrl_targets_running()) + { + //- rjf: unpack module info + DF_Entity *module = df_module_from_process_vaddr(process, rip_vaddr); + Rng1U64 tls_vaddr_range = ctrl_tls_vaddr_range_from_module(module->ctrl_machine_id, module->ctrl_handle); + U64 addr_size = bit_size_from_arch(process->arch)/8; + + //- rjf: read module's TLS index + U64 tls_index = 0; + if(addr_size != 0) + { + CTRL_ProcessMemorySlice tls_index_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, tls_vaddr_range, 0); + if(tls_index_slice.data.size >= addr_size) + { + tls_index = *(U64 *)tls_index_slice.data.str; + } + } + + //- rjf: PE path + if(addr_size != 0) + { + U64 thread_info_addr = root_vaddr; + U64 tls_addr_off = tls_index*addr_size; + U64 tls_addr_array = 0; + CTRL_ProcessMemorySlice tls_addr_array_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, r1u64(thread_info_addr, thread_info_addr+addr_size), 0); + String8 tls_addr_array_data = tls_addr_array_slice.data; + if(tls_addr_array_data.size >= 8) + { + MemoryCopy(&tls_addr_array, tls_addr_array_data.str, sizeof(U64)); + } + CTRL_ProcessMemorySlice result_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, r1u64(tls_addr_array + tls_addr_off, tls_addr_array + tls_addr_off + addr_size), 0); + String8 result_data = result_slice.data; + if(result_data.size >= 8) + { + MemoryCopy(&base_vaddr, result_data.str, sizeof(U64)); + } + } + + //- rjf: non-PE path (not implemented) +#if 0 + if(!bin_is_pe) + { + // TODO(rjf): not supported. old code from the prototype that Nick had sketched out: + // TODO(nick): This code works only if the linked c runtime library is glibc. + // Implement CRT detection here. + + U64 dtv_addr = UINT64_MAX; + demon_read_memory(process->demon_handle, &dtv_addr, thread_info_addr, addr_size); + + /* + union delta_thread_vector + { + size_t counter; + struct + { + void *value; + void *to_free; + } pointer; + }; + */ + + U64 dtv_size = 16; + U64 dtv_count = 0; + demon_read_memory(process->demon_handle, &dtv_count, dtv_addr - dtv_size, addr_size); + + if (tls_index > 0 && tls_index < dtv_count) + { + demon_read_memory(process->demon_handle, &result, dtv_addr + dtv_size*tls_index, addr_size); + } + } +#endif + } + scratch_end(scratch); + ProfEnd(); + return base_vaddr; +} + +internal Architecture +df_architecture_from_entity(DF_Entity *entity) +{ + return entity->arch; +} + +internal EVAL_String2NumMap * +df_push_locals_map_from_dbgi_key_voff(Arena *arena, DI_Scope *scope, DI_Key *dbgi_key, U64 voff) +{ + RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); + EVAL_String2NumMap *result = eval_push_locals_map_from_rdi_voff(arena, rdi, voff); + return result; +} + +internal EVAL_String2NumMap * +df_push_member_map_from_dbgi_key_voff(Arena *arena, DI_Scope *scope, DI_Key *dbgi_key, U64 voff) +{ + RDI_Parsed *rdi = di_rdi_from_key(scope, dbgi_key, 0); + EVAL_String2NumMap *result = eval_push_member_map_from_rdi_voff(arena, rdi, voff); + return result; +} + +internal B32 +df_set_thread_rip(DF_Entity *thread, U64 vaddr) +{ + Temp scratch = scratch_begin(0, 0); + void *block = ctrl_query_cached_reg_block_from_thread(scratch.arena, df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); + regs_arch_block_write_rip(thread->arch, block, vaddr); + B32 result = ctrl_thread_write_reg_block(thread->ctrl_machine_id, thread->ctrl_handle, block); + + // rjf: early mutation of unwind cache for immediate frontend effect + if(result) + { + DF_UnwindCache *cache = &df_state->unwind_cache; + if(cache->slots_count != 0) + { + DF_Handle thread_handle = df_handle_from_entity(thread); + U64 hash = df_hash_from_string(str8_struct(&thread_handle)); + U64 slot_idx = hash%cache->slots_count; + DF_UnwindCacheSlot *slot = &cache->slots[slot_idx]; + for(DF_UnwindCacheNode *n = slot->first; n != 0; n = n->next) + { + if(df_handle_match(n->thread, thread_handle) && n->unwind.frames.count != 0) + { + regs_arch_block_write_rip(thread->arch, n->unwind.frames.v[0].regs, vaddr); + break; + } + } + } + } + + scratch_end(scratch); + return result; +} + +internal DF_Entity * +df_module_from_thread_candidates(DF_Entity *thread, DF_EntityList *candidates) +{ + DF_Entity *src_module = df_module_from_thread(thread); + DF_Entity *module = &df_g_nil_entity; + DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); + for(DF_EntityNode *n = candidates->first; n != 0; n = n->next) + { + DF_Entity *candidate_module = n->entity; + DF_Entity *candidate_process = df_entity_ancestor_from_kind(candidate_module, DF_EntityKind_Process); + if(candidate_process == process) + { + module = candidate_module; + } + if(candidate_module == src_module) + { + break; + } + } + return module; +} + +internal DF_Unwind +df_unwind_from_ctrl_unwind(Arena *arena, DI_Scope *di_scope, DF_Entity *process, CTRL_Unwind *base_unwind) +{ + Temp scratch = scratch_begin(&arena, 1); + DF_UnwindFrameList rich_frames_list = {0}; + Architecture arch = df_architecture_from_entity(process); + for(U64 base_frame_idx = 0; base_frame_idx < base_unwind->frames.count; base_frame_idx += 1) + { + CTRL_UnwindFrame *base_frame = &base_unwind->frames.v[base_frame_idx]; + U64 rip_vaddr = regs_rip_from_arch_block(arch, base_frame->regs); + DF_Entity *module = df_module_from_process_vaddr(process, rip_vaddr); + U64 rip_voff = df_voff_from_vaddr(module, rip_vaddr); + DI_Key dbgi_key = df_dbgi_key_from_module(module); + RDI_Parsed *rdi = di_rdi_from_key(di_scope, &dbgi_key, 0); + RDI_Scope *scope = rdi_scope_from_voff(rdi, rip_voff); + + // rjf: add rich frames for inlines + U64 inline_unwind_idx = 0; + for(RDI_Scope *s = scope; s->inline_site_idx != 0; s = rdi_element_from_name_idx(rdi, Scopes, s->parent_scope_idx)) + { + RDI_InlineSite *site = rdi_element_from_name_idx(rdi, InlineSites, s->inline_site_idx); + DF_UnwindFrameNode *n = push_array(scratch.arena, DF_UnwindFrameNode, 1); + SLLQueuePush(rich_frames_list.first, rich_frames_list.last, n); + rich_frames_list.count += 1; + n->v.regs = base_frame->regs; + n->v.rdi = rdi; + n->v.procedure = 0; + n->v.inline_site = site; + n->v.base_unwind_idx = base_frame_idx; + n->v.inline_unwind_idx = inline_unwind_idx; + inline_unwind_idx += 1; + } + + // rjf: add frame for concrete frame + DF_UnwindFrameNode *n = push_array(scratch.arena, DF_UnwindFrameNode, 1); + SLLQueuePush(rich_frames_list.first, rich_frames_list.last, n); + rich_frames_list.count += 1; + n->v.regs = base_frame->regs; + n->v.rdi = rdi; + n->v.procedure = rdi_element_from_name_idx(rdi, Procedures, scope->proc_idx); + n->v.inline_site = 0; + n->v.base_unwind_idx = base_frame_idx; + n->v.inline_unwind_idx = inline_unwind_idx; + inline_unwind_idx = 0; + } + DF_Unwind result = {0}; + { + result.frames.count = rich_frames_list.count; + result.frames.v = push_array(arena, DF_UnwindFrame, result.frames.count); + U64 idx = 0; + for(DF_UnwindFrameNode *n = rich_frames_list.first; n != 0; n = n->next, idx += 1) + { + MemoryCopyStruct(&result.frames.v[idx], &n->v); + } + } + scratch_end(scratch); + return result; +} + +internal DF_UnwindFrame * +df_frame_from_unwind_idxs(DF_Unwind *unwind, U64 base_unwind_idx, U64 inline_unwind_idx) +{ + DF_UnwindFrame *f = 0; + for(U64 idx = 0; idx < unwind->frames.count; idx += 1) + { + if(unwind->frames.v[idx].base_unwind_idx == base_unwind_idx) + { + f = &unwind->frames.v[idx]; + if(unwind->frames.v[idx].inline_unwind_idx == inline_unwind_idx) + { + break; + } + } + } + return f; +} + +//////////////////////////////// +//~ rjf: Target Controls + +//- rjf: control message dispatching + +internal void +df_push_ctrl_msg(CTRL_Msg *msg) +{ + CTRL_Msg *dst = ctrl_msg_list_push(df_state->ctrl_msg_arena, &df_state->ctrl_msgs); + ctrl_msg_deep_copy(df_state->ctrl_msg_arena, dst, msg); + if(df_state->ctrl_soft_halt_issued == 0 && df_ctrl_targets_running()) + { + df_state->ctrl_soft_halt_issued = 1; + ctrl_halt(); + } +} + +//- rjf: control thread running + +internal void +df_ctrl_run(DF_RunKind run, DF_Entity *run_thread, CTRL_RunFlags flags, CTRL_TrapList *run_traps) +{ + Temp scratch = scratch_begin(0, 0); + + // rjf: build run message + CTRL_Msg msg = {(run == DF_RunKind_Run || run == DF_RunKind_Step) ? CTRL_MsgKind_Run : CTRL_MsgKind_SingleStep}; + { + DF_EntityList user_bps = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); + DF_Entity *process = df_entity_ancestor_from_kind(run_thread, DF_EntityKind_Process); + msg.run_flags = flags; + msg.machine_id = run_thread->ctrl_machine_id; + msg.entity = run_thread->ctrl_handle; + msg.parent = process->ctrl_handle; + MemoryCopyArray(msg.exception_code_filters, df_state->ctrl_exception_code_filters); + if(run_traps != 0) + { + MemoryCopyStruct(&msg.traps, run_traps); + } + for(DF_EntityNode *user_bp_n = user_bps.first; + user_bp_n != 0; + user_bp_n = user_bp_n->next) + { + // rjf: unpack user breakpoint entity + DF_Entity *user_bp = user_bp_n->entity; + if(user_bp->b32 == 0) + { + continue; + } + DF_Entity *file = df_entity_ancestor_from_kind(user_bp, DF_EntityKind_File); + DF_Entity *symb = df_entity_child_from_kind(user_bp, DF_EntityKind_EntryPointName); + DF_EntityList overrides = df_possible_overrides_from_entity(scratch.arena, file); + for(DF_EntityNode *override_n = overrides.first; override_n != 0; override_n = override_n->next) + { + DF_Entity *override = override_n->entity; + DF_Entity *condition_child = df_entity_child_from_kind(user_bp, DF_EntityKind_Condition); + String8 condition = condition_child->name; + + // rjf: generate user breakpoint info depending on breakpoint placement + CTRL_UserBreakpointKind ctrl_user_bp_kind = CTRL_UserBreakpointKind_FileNameAndLineColNumber; + String8 ctrl_user_bp_string = {0}; + TxtPt ctrl_user_bp_pt = {0}; + U64 ctrl_user_bp_u64 = 0; + { + if(user_bp->flags & DF_EntityFlag_HasTextPoint) + { + ctrl_user_bp_kind = CTRL_UserBreakpointKind_FileNameAndLineColNumber; + ctrl_user_bp_string = df_full_path_from_entity(scratch.arena, override); + ctrl_user_bp_pt = user_bp->text_point; + } + else if(user_bp->flags & DF_EntityFlag_HasVAddr) + { + ctrl_user_bp_kind = CTRL_UserBreakpointKind_VirtualAddress; + ctrl_user_bp_u64 = user_bp->vaddr; + } + else if(!df_entity_is_nil(symb)) + { + ctrl_user_bp_kind = CTRL_UserBreakpointKind_SymbolNameAndOffset; + ctrl_user_bp_string = symb->name; + } + } + + // rjf: push user breakpoint to list + { + CTRL_UserBreakpoint ctrl_user_bp = {ctrl_user_bp_kind}; + ctrl_user_bp.string = ctrl_user_bp_string; + ctrl_user_bp.pt = ctrl_user_bp_pt; + ctrl_user_bp.u64 = ctrl_user_bp_u64; + ctrl_user_bp.condition = condition; + ctrl_user_breakpoint_list_push(scratch.arena, &msg.user_bps, &ctrl_user_bp); + } + } + } + for(DF_HandleNode *n = df_state->frozen_threads.first; n != 0; n = n->next) + { + DF_Entity *thread = df_entity_from_handle(n->handle); + if(!df_entity_is_nil(thread)) + { + CTRL_MachineIDHandlePair pair = {thread->ctrl_machine_id, thread->ctrl_handle}; + ctrl_machine_id_handle_pair_list_push(scratch.arena, &msg.freeze_state_threads, &pair); + } + } + msg.freeze_state_is_frozen = 1; + } + + // rjf: push msg + df_push_ctrl_msg(&msg); + + // rjf: copy run traps to scratch (needed, if the caller can pass `df_state->ctrl_last_run_traps`) + CTRL_TrapList run_traps_copy = {0}; + if(run_traps != 0) + { + run_traps_copy = ctrl_trap_list_copy(scratch.arena, run_traps); + } + + // rjf: store last run info + arena_clear(df_state->ctrl_last_run_arena); + df_state->ctrl_last_run_kind = run; + df_state->ctrl_last_run_frame_idx = df_frame_index(); + df_state->ctrl_last_run_thread = df_handle_from_entity(run_thread); + df_state->ctrl_last_run_flags = flags; + df_state->ctrl_last_run_traps = ctrl_trap_list_copy(df_state->ctrl_last_run_arena, &run_traps_copy); + df_state->ctrl_is_running = 1; + + // rjf: set control context to top unwind + df_state->ctrl_ctx.unwind_count = 0; + df_state->ctrl_ctx.inline_unwind_count = 0; + + scratch_end(scratch); +} + +//- rjf: stopped info from the control thread + +internal CTRL_Event +df_ctrl_last_stop_event(void) +{ + return df_state->ctrl_last_stop_event; +} + +//////////////////////////////// +//~ rjf: Evaluation + +internal B32 +df_eval_memory_read(void *u, void *out, U64 addr, U64 size) +{ + DF_Entity *process = (DF_Entity *)u; + Assert(process->kind == DF_EntityKind_Process); + Temp scratch = scratch_begin(0, 0); + B32 result = 0; + CTRL_ProcessMemorySlice slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, r1u64(addr, addr+size), 0); + String8 data = slice.data; + if(data.size == size) + { + result = 1; + MemoryCopy(out, data.str, data.size); + } + scratch_end(scratch); + return result; +} + +internal EVAL_ParseCtx +df_eval_parse_ctx_from_process_vaddr(DI_Scope *scope, DF_Entity *process, U64 vaddr) +{ + Temp scratch = scratch_begin(0, 0); + + //- rjf: extract info + DF_Entity *module = df_module_from_process_vaddr(process, vaddr); + U64 voff = df_voff_from_vaddr(module, vaddr); + DI_Key dbgi_key = df_dbgi_key_from_module(module); + RDI_Parsed *rdi = di_rdi_from_key(scope, &dbgi_key, 0); + Architecture arch = df_architecture_from_entity(process); + EVAL_String2NumMap *reg_map = ctrl_string2reg_from_arch(arch); + EVAL_String2NumMap *reg_alias_map = ctrl_string2alias_from_arch(arch); + EVAL_String2NumMap *locals_map = df_query_cached_locals_map_from_dbgi_key_voff(&dbgi_key, voff); + EVAL_String2NumMap *member_map = df_query_cached_member_map_from_dbgi_key_voff(&dbgi_key, voff); + + //- rjf: build ctx + EVAL_ParseCtx ctx = zero_struct; + { + ctx.arch = arch; + ctx.ip_voff = voff; + ctx.rdi = rdi; + ctx.type_graph = tg_graph_begin(bit_size_from_arch(arch)/8, 256); + ctx.regs_map = reg_map; + ctx.reg_alias_map = reg_alias_map; + ctx.locals_map = locals_map; + ctx.member_map = member_map; + } + scratch_end(scratch); + return ctx; +} + +internal EVAL_ParseCtx +df_eval_parse_ctx_from_src_loc(DI_Scope *scope, DF_Entity *file, TxtPt pt) +{ + Temp scratch = scratch_begin(0, 0); + EVAL_ParseCtx ctx = zero_struct; + DI_KeyList dbgi_keys = df_push_active_dbgi_key_list(scratch.arena); + DF_TextLineSrc2DasmInfoList src2dasm_list = {0}; + + //- rjf: search for line info in all binaries for this file:pt + DF_EntityList overrides = df_possible_overrides_from_entity(scratch.arena, file); + for(DF_EntityNode *override_n = overrides.first; + override_n != 0; + override_n = override_n->next) + { + DF_Entity *override = override_n->entity; + String8 file_path = df_full_path_from_entity(scratch.arena, override); + String8 file_path_normalized = lower_from_str8(scratch.arena, file_path); + for(DI_KeyNode *dbgi_key_n = dbgi_keys.first; + dbgi_key_n != 0; + dbgi_key_n = dbgi_key_n->next) + { + // rjf: key -> rdi + DI_Key key = dbgi_key_n->v; + RDI_Parsed *rdi = di_rdi_from_key(scope, &key, 0); + + // rjf: file_path_normalized * rdi -> src_id + B32 good_src_id = 0; + U32 src_id = 0; + { + RDI_NameMap *mapptr = rdi_element_from_name_idx(rdi, NameMaps, RDI_NameMapKind_NormalSourcePaths); + RDI_ParsedNameMap map = {0}; + rdi_parsed_from_name_map(rdi, mapptr, &map); + RDI_NameMapNode *node = rdi_name_map_lookup(rdi, &map, file_path_normalized.str, file_path_normalized.size); + if(node != 0) + { + U32 id_count = 0; + U32 *ids = rdi_matches_from_map_node(rdi, node, &id_count); + if(id_count > 0) + { + good_src_id = 1; + src_id = ids[0]; + } + } + } + + // rjf: good src-id -> look up line info for visible range + if(good_src_id) + { + RDI_SourceFile *src = rdi_element_from_name_idx(rdi, SourceFiles, src_id); + RDI_SourceLineMap *src_line_map = rdi_element_from_name_idx(rdi, SourceLineMaps, src->source_line_map_idx); + RDI_ParsedSourceLineMap line_map = {0}; + rdi_parsed_from_source_line_map(rdi, src_line_map, &line_map); + U32 voff_count = 0; + U64 *voffs = rdi_line_voffs_from_num(&line_map, (U32)pt.line, &voff_count); + for(U64 idx = 0; idx < voff_count; idx += 1) + { + U64 base_voff = voffs[idx]; + U64 unit_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_UnitVMap, base_voff); + RDI_Unit *unit = rdi_element_from_name_idx(rdi, Units, unit_idx); + RDI_LineTable *line_table = rdi_element_from_name_idx(rdi, LineTables, unit->line_table_idx); + RDI_ParsedLineTable unit_line_info = {0}; + rdi_parsed_from_line_table(rdi, line_table, &unit_line_info); + U64 line_info_idx = rdi_line_info_idx_from_voff(&unit_line_info, base_voff); + Rng1U64 range = r1u64(base_voff, unit_line_info.voffs[line_info_idx+1]); + S64 actual_line = (S64)unit_line_info.lines[line_info_idx].line_num; + DF_TextLineSrc2DasmInfoNode *src2dasm_n = push_array(scratch.arena, DF_TextLineSrc2DasmInfoNode, 1); + src2dasm_n->v.voff_range = range; + src2dasm_n->v.remap_line = (S64)actual_line; + src2dasm_n->v.dbgi_key = key; + SLLQueuePush(src2dasm_list.first, src2dasm_list.last, src2dasm_n); + src2dasm_list.count += 1; + } + } + } + } + + //- rjf: try to form ctx from line info + B32 good_ctx = 0; + if(src2dasm_list.count != 0) + { + for(DF_TextLineSrc2DasmInfoNode *n = src2dasm_list.first; n != 0; n = n->next) + { + DF_TextLineSrc2DasmInfo *src2dasm = &n->v; + DF_EntityList modules = df_modules_from_dbgi_key(scratch.arena, &src2dasm->dbgi_key); + if(modules.count != 0) + { + DF_Entity *module = modules.first->entity; + DF_Entity *process = df_entity_ancestor_from_kind(module, DF_EntityKind_Process); + U64 voff = src2dasm->voff_range.min; + U64 vaddr = df_vaddr_from_voff(module, voff); + ctx = df_eval_parse_ctx_from_process_vaddr(scope, process, vaddr); + good_ctx = 1; + break; + } + } + } + + //- rjf: bad ctx -> reset with graceful defaults + if(good_ctx == 0) + { + ctx.rdi = &di_rdi_parsed_nil; + ctx.type_graph = tg_graph_begin(8, 256); + ctx.regs_map = &eval_string2num_map_nil; + ctx.regs_map = &eval_string2num_map_nil; + ctx.reg_alias_map = &eval_string2num_map_nil; + ctx.locals_map = &eval_string2num_map_nil; + ctx.member_map = &eval_string2num_map_nil; + } + + scratch_end(scratch); + return ctx; +} + +internal DF_Eval +df_eval_from_string(Arena *arena, DI_Scope *scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, String8 string) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(&arena, 1); + + //- rjf: unpack arguments + DF_Entity *thread = df_entity_from_handle(ctrl_ctx->thread); + U64 tls_root_vaddr = ctrl_query_cached_tls_root_vaddr_from_thread(df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); + DF_Entity *process = thread->parent; + U64 unwind_count = ctrl_ctx->unwind_count; + CTRL_Unwind unwind = df_query_cached_unwind_from_thread(thread); + Architecture arch = df_architecture_from_entity(thread); + U64 reg_size = regs_block_size_from_architecture(arch); + U64 thread_unwind_ip_vaddr = 0; + void *thread_unwind_regs_block = push_array(scratch.arena, U8, reg_size); + if(unwind.frames.count != 0) + { + thread_unwind_regs_block = unwind.frames.v[unwind_count%unwind.frames.count].regs; + thread_unwind_ip_vaddr = regs_rip_from_arch_block(arch, thread_unwind_regs_block); + } + + //- rjf: unpack module info & produce eval machine + DF_Entity *module = df_module_from_process_vaddr(process, thread_unwind_ip_vaddr); + U64 module_base = df_base_vaddr_from_module(module); + U64 tls_base = df_query_cached_tls_base_vaddr_from_process_root_rip(process, tls_root_vaddr, thread_unwind_ip_vaddr); + EVAL_Machine machine = {0}; + machine.u = (void *)thread->parent; + machine.arch = arch; + machine.memory_read = df_eval_memory_read; + machine.reg_data = thread_unwind_regs_block; + machine.reg_size = reg_size; + machine.module_base = &module_base; + machine.tls_base = &tls_base; + + //- rjf: lex & parse + EVAL_TokenArray tokens = eval_token_array_from_text(arena, string); + EVAL_ParseResult parse = eval_parse_expr_from_text_tokens(arena, parse_ctx, string, &tokens); + EVAL_ErrorList errors = parse.errors; + B32 parse_has_expr = (parse.expr != &eval_expr_nil); + B32 parse_is_type = (parse_has_expr && parse.expr->kind == EVAL_ExprKind_TypeIdent); + + //- rjf: produce IR tree & type + EVAL_IRTreeAndType ir_tree_and_type = {&eval_irtree_nil}; + if(parse_has_expr && errors.count == 0) + { + ir_tree_and_type = eval_irtree_and_type_from_expr(arena, parse_ctx->type_graph, parse_ctx->rdi, macro_map, parse.expr, &errors); + } + + //- rjf: get list of ops + EVAL_OpList op_list = {0}; + if(parse_has_expr && ir_tree_and_type.tree != &eval_irtree_nil) + { + eval_oplist_from_irtree(arena, ir_tree_and_type.tree, &op_list); + } + + //- rjf: get bytecode string + String8 bytecode = {0}; + if(parse_has_expr && parse_is_type == 0 && op_list.encoded_size != 0) + { + bytecode = eval_bytecode_from_oplist(arena, &op_list); + } + + //- rjf: evaluate + EVAL_Result eval = {0}; + if(bytecode.size != 0) + { + eval = eval_interpret(&machine, bytecode); + } + + //- rjf: fill result + DF_Eval result = zero_struct; + { + result.type_key = ir_tree_and_type.type_key; + result.mode = ir_tree_and_type.mode; + switch(result.mode) + { + default: + case EVAL_EvalMode_Value: + { + MemoryCopyArray(result.imm_u128, eval.value.u128); + }break; + case EVAL_EvalMode_Addr: + { + result.offset = eval.value.u64; + }break; + case EVAL_EvalMode_Reg: + { + U64 reg_off = (eval.value.u64 & 0x0000ffff) >> 0; + U64 reg_size = (eval.value.u64 & 0xffff0000) >> 16; + result.offset = reg_off; + (void)reg_size; + }break; + } + result.errors = errors; + if(EVAL_ResultCode_Good < eval.code && eval.code < EVAL_ResultCode_COUNT) + { + eval_error(arena, &result.errors, EVAL_ErrorKind_InterpretationError, 0, eval_result_code_display_strings[eval.code]); + } + } + + //- rjf: apply dynamic type overrides + if(parse.expr != 0 && parse.expr->kind != EVAL_ExprKind_Cast) + { + result = df_dynamically_typed_eval_from_eval(parse_ctx->type_graph, parse_ctx->rdi, ctrl_ctx, result); + } + + //- rjf: try to resolve basic integral values into symbols + if((result.mode == EVAL_EvalMode_Value || result.mode == EVAL_EvalMode_Reg) && parse.expr->kind != EVAL_ExprKind_Cast && + (tg_key_match(result.type_key, tg_key_basic(TG_Kind_S64)) || + tg_key_match(result.type_key, tg_key_basic(TG_Kind_U64)) || + tg_key_match(result.type_key, tg_key_basic(TG_Kind_S32)) || + tg_key_match(result.type_key, tg_key_basic(TG_Kind_U32)))) + { + U64 vaddr = result.imm_u64; + DF_Entity *module = df_module_from_process_vaddr(process, vaddr); + DI_Key dbgi_key = df_dbgi_key_from_module(module); + U64 voff = df_voff_from_vaddr(module, vaddr); + String8 symbol_name = df_symbol_name_from_dbgi_key_voff(scratch.arena, &dbgi_key, voff); + if(symbol_name.size != 0) + { + result.type_key = tg_cons_type_make(parse_ctx->type_graph, TG_Kind_Ptr, tg_key_basic(TG_Kind_Void), 0); + } + } + + scratch_end(scratch); + ProfEnd(); + return result; +} + +internal DF_Eval +df_value_mode_eval_from_eval(TG_Graph *graph, RDI_Parsed *rdi, DF_CtrlCtx *ctrl_ctx, DF_Eval eval) +{ + ProfBeginFunction(); + DF_Entity *thread = df_entity_from_handle(ctrl_ctx->thread); + DF_Entity *process = thread->parent; + switch(eval.mode) + { + //- rjf: no work to be done. already in value mode + default: + case EVAL_EvalMode_Value:{}break; + + //- rjf: address => resolve into value, if leaf + case EVAL_EvalMode_Addr: + { + TG_Key type_key = eval.type_key; + TG_Kind type_kind = tg_kind_from_key(type_key); + U64 type_byte_size = tg_byte_size_from_graph_rdi_key(graph, rdi, type_key); + if(!tg_key_match(type_key, tg_key_zero()) && type_byte_size <= sizeof(U64)*2) + { + Temp scratch = scratch_begin(0, 0); + Rng1U64 vaddr_range = r1u64(eval.offset, eval.offset + type_byte_size); + if(dim_1u64(vaddr_range) == type_byte_size) + { + CTRL_ProcessMemorySlice slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, vaddr_range, 0); + String8 data = slice.data; + MemoryZeroArray(eval.imm_u128); + MemoryCopy(eval.imm_u128, data.str, Min(data.size, sizeof(U64)*2)); + eval.mode = EVAL_EvalMode_Value; + + // rjf: mask&shift, for bitfields + if(type_kind == TG_Kind_Bitfield && type_byte_size <= sizeof(U64)) + { + TG_Type *type = tg_type_from_graph_rdi_key(scratch.arena, graph, rdi, type_key); + U64 valid_bits_mask = 0; + for(U64 idx = 0; idx < type->count; idx += 1) + { + valid_bits_mask |= (1<> type->off; + eval.imm_u64 = eval.imm_u64 & valid_bits_mask; + eval.type_key = type->direct_type_key; + } + + // rjf: manually sign-extend + switch(type_kind) + { + default: break; + case TG_Kind_S8: {eval.imm_s64 = (S64)*((S8 *)&eval.imm_u64);}break; + case TG_Kind_S16: {eval.imm_s64 = (S64)*((S16 *)&eval.imm_u64);}break; + case TG_Kind_S32: {eval.imm_s64 = (S64)*((S32 *)&eval.imm_u64);}break; + } + } + scratch_end(scratch); + } + }break; + + //- rjf: register => resolve into value + case EVAL_EvalMode_Reg: + { + TG_Key type_key = eval.type_key; + U64 type_byte_size = tg_byte_size_from_graph_rdi_key(graph, rdi, type_key); + U64 reg_off = eval.offset; + CTRL_Unwind unwind = df_query_cached_unwind_from_thread(thread); + if(unwind.frames.count != 0) + { + CTRL_UnwindFrame *frame = &unwind.frames.v[ctrl_ctx->unwind_count%unwind.frames.count]; + MemoryCopy(&eval.imm_u128[0], ((U8 *)frame->regs + reg_off), Min(type_byte_size, sizeof(U64)*2)); + } + eval.mode = EVAL_EvalMode_Value; + }break; + } + + ProfEnd(); + return eval; +} + +internal DF_Eval +df_dynamically_typed_eval_from_eval(TG_Graph *graph, RDI_Parsed *rdi, DF_CtrlCtx *ctrl_ctx, DF_Eval eval) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DF_Entity *thread = df_entity_from_handle(ctrl_ctx->thread); + Architecture arch = df_architecture_from_entity(thread); + DF_Entity *process = thread->parent; + U64 unwind_count = ctrl_ctx->unwind_count; + U64 thread_rip_vaddr = df_query_cached_rip_from_thread_unwind(thread, unwind_count); + DF_Entity *module = df_module_from_process_vaddr(process, thread_rip_vaddr); + TG_Key type_key = eval.type_key; + TG_Kind type_kind = tg_kind_from_key(type_key); + if(type_kind == TG_Kind_Ptr) + { + TG_Key ptee_type_key = tg_unwrapped_direct_from_graph_rdi_key(graph, rdi, type_key); + TG_Kind ptee_type_kind = tg_kind_from_key(ptee_type_key); + if(ptee_type_kind == TG_Kind_Struct || ptee_type_kind == TG_Kind_Class) + { + TG_Type *ptee_type = tg_type_from_graph_rdi_key(scratch.arena, graph, rdi, ptee_type_key); + B32 has_vtable = 0; + for(U64 idx = 0; idx < ptee_type->count; idx += 1) + { + if(ptee_type->members[idx].kind == TG_MemberKind_VirtualMethod) + { + has_vtable = 1; + break; + } + } + if(has_vtable) + { + U64 ptr_vaddr = eval.offset; + U64 addr_size = bit_size_from_arch(arch)/8; + CTRL_ProcessMemorySlice ptr_value_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, + r1u64(ptr_vaddr, ptr_vaddr+addr_size), 0); + String8 ptr_value_memory = ptr_value_slice.data; + if(ptr_value_memory.size >= addr_size) + { + U64 class_base_vaddr = 0; + MemoryCopy(&class_base_vaddr, ptr_value_memory.str, addr_size); + CTRL_ProcessMemorySlice vtable_base_ptr_slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, + r1u64(class_base_vaddr, class_base_vaddr+addr_size), 0); + String8 vtable_base_ptr_memory = vtable_base_ptr_slice.data; + if(vtable_base_ptr_memory.size >= addr_size) + { + U64 vtable_vaddr = 0; + MemoryCopy(&vtable_vaddr, vtable_base_ptr_memory.str, addr_size); + U64 vtable_voff = df_voff_from_vaddr(module, vtable_vaddr); + U64 global_idx = rdi_vmap_idx_from_section_kind_voff(rdi, RDI_SectionKind_GlobalVMap, vtable_voff); + RDI_GlobalVariable *global_var = rdi_element_from_name_idx(rdi, GlobalVariables, global_idx); + if(global_var->link_flags & RDI_LinkFlag_TypeScoped) + { + RDI_UDT *udt = rdi_element_from_name_idx(rdi, UDTs, global_var->container_idx); + RDI_TypeNode *type = rdi_element_from_name_idx(rdi, TypeNodes, udt->self_type_idx); + TG_Key derived_type_key = tg_key_ext(tg_kind_from_rdi_type_kind(type->kind), (U64)udt->self_type_idx); + TG_Key ptr_to_derived_type_key = tg_cons_type_make(graph, TG_Kind_Ptr, derived_type_key, 0); + eval.type_key = ptr_to_derived_type_key; + } + } + } + } + } + } + scratch_end(scratch); + ProfEnd(); + return eval; +} + +internal DF_Eval +df_eval_from_eval_cfg_table(Arena *arena, DI_Scope *scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_Eval eval, DF_CfgTable *cfg) +{ + ProfBeginFunction(); + + //- rjf: apply view rules + for(DF_CfgVal *val = cfg->first_val; val != 0 && val != &df_g_nil_cfg_val; val = val->linear_next) + { + DF_CoreViewRuleSpec *spec = df_core_view_rule_spec_from_string(val->string); + if(spec->info.flags & DF_CoreViewRuleSpecInfoFlag_EvalResolution) + { + eval = spec->info.eval_resolution(arena, scope, ctrl_ctx, parse_ctx, macro_map, eval, val); + goto end_resolve; + } + } + end_resolve:; + ProfEnd(); + return eval; +} + +//////////////////////////////// +//~ rjf: Evaluation Views + +#if !defined(BLAKE2_H) +#define HAVE_SSE2 +#include "third_party/blake2/blake2.h" +#include "third_party/blake2/blake2b.c" +#endif + +internal DF_EvalViewKey +df_eval_view_key_make(U64 v0, U64 v1) +{ + DF_EvalViewKey v = {v0, v1}; + return v; +} + +internal DF_EvalViewKey +df_eval_view_key_from_string(String8 string) +{ + DF_EvalViewKey key = {0}; + blake2b((U8 *)&key.u64[0], sizeof(key), string.str, string.size, 0, 0); + return key; +} + +internal DF_EvalViewKey +df_eval_view_key_from_stringf(char *fmt, ...) +{ + Temp scratch = scratch_begin(0, 0); + va_list args; + va_start(args, fmt); + String8 string = push_str8fv(scratch.arena, fmt, args); + va_end(args); + DF_EvalViewKey key = df_eval_view_key_from_string(string); + scratch_end(scratch); + return key; +} + +internal B32 +df_eval_view_key_match(DF_EvalViewKey a, DF_EvalViewKey b) +{ + return MemoryMatchStruct(&a, &b); +} + +internal DF_EvalView * +df_eval_view_from_key(DF_EvalViewKey key) +{ + DF_EvalView *eval_view = &df_g_nil_eval_view; + { + U64 slot_idx = key.u64[1]%df_state->eval_view_cache.slots_count; + DF_EvalViewSlot *slot = &df_state->eval_view_cache.slots[slot_idx]; + for(DF_EvalView *v = slot->first; v != &df_g_nil_eval_view && v != 0; v = v->hash_next) + { + if(df_eval_view_key_match(key, v->key)) + { + eval_view = v; + break; + } + } + if(eval_view == &df_g_nil_eval_view) + { + eval_view = push_array(df_state->arena, DF_EvalView, 1); + DLLPushBack_NPZ(&df_g_nil_eval_view, slot->first, slot->last, eval_view, hash_next, hash_prev); + eval_view->key = key; + eval_view->arena = arena_alloc(); + df_expand_tree_table_init(eval_view->arena, &eval_view->expand_tree_table, 256); + eval_view->view_rule_table.slot_count = 64; + eval_view->view_rule_table.slots = push_array(eval_view->arena, DF_EvalViewRuleCacheSlot, eval_view->view_rule_table.slot_count); + } + } + return eval_view; +} + +//- rjf: key -> view rules + +internal void +df_eval_view_set_key_rule(DF_EvalView *eval_view, DF_ExpandKey key, String8 view_rule_string) +{ + //- rjf: key -> hash * slot idx * slot + String8 key_string = str8_struct(&key); + U64 hash = df_hash_from_string(key_string); + U64 slot_idx = hash%eval_view->view_rule_table.slot_count; + DF_EvalViewRuleCacheSlot *slot = &eval_view->view_rule_table.slots[slot_idx]; + + //- rjf: slot -> existing node + DF_EvalViewRuleCacheNode *existing_node = 0; + for(DF_EvalViewRuleCacheNode *n = slot->first; n != 0; n = n->hash_next) + { + if(df_expand_key_match(n->key, key)) + { + existing_node = n; + break; + } + } + + //- rjf: existing node * new node -> node + DF_EvalViewRuleCacheNode *node = existing_node; + if(node == 0) + { + node = push_array(eval_view->arena, DF_EvalViewRuleCacheNode, 1); + DLLPushBack_NP(slot->first, slot->last, node, hash_next, hash_prev); + node->key = key; + node->buffer_cap = 512; + node->buffer = push_array(eval_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); + } +} + +internal String8 +df_eval_view_rule_from_key(DF_EvalView *eval_view, DF_ExpandKey key) +{ + String8 result = {0}; + + //- rjf: key -> hash * slot idx * slot + String8 key_string = str8_struct(&key); + U64 hash = df_hash_from_string(key_string); + U64 slot_idx = hash%eval_view->view_rule_table.slot_count; + DF_EvalViewRuleCacheSlot *slot = &eval_view->view_rule_table.slots[slot_idx]; + + //- rjf: slot -> existing node + DF_EvalViewRuleCacheNode *existing_node = 0; + for(DF_EvalViewRuleCacheNode *n = slot->first; n != 0; n = n->hash_next) + { + if(df_expand_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; +} + +//////////////////////////////// +//~ rjf: Evaluation View Visualization & Interaction + +//- rjf: evaluation value string builder helpers + +internal String8 +df_string_from_ascii_value(Arena *arena, U8 val) +{ + String8 result = {0}; + switch(val) + { + case 0x00:{result = str8_lit("\\0");}break; + case 0x07:{result = str8_lit("\\a");}break; + case 0x08:{result = str8_lit("\\b");}break; + case 0x0c:{result = str8_lit("\\f");}break; + case 0x0a:{result = str8_lit("\\n");}break; + case 0x0d:{result = str8_lit("\\r");}break; + case 0x09:{result = str8_lit("\\t");}break; + case 0x0b:{result = str8_lit("\\v");}break; + case 0x3f:{result = str8_lit("\\?");}break; + case '"': {result = str8_lit("\\\"");}break; + case '\'':{result = str8_lit("\\'");}break; + case '\\':{result = str8_lit("\\\\");}break; + default: + if(32 <= val && val < 255) + { + result = push_str8f(arena, "%c", val); + }break; + } + return result; +} + +internal String8 +df_string_from_simple_typed_eval(Arena *arena, TG_Graph *graph, RDI_Parsed *rdi, DF_EvalVizStringFlags flags, U32 radix, DF_Eval eval) +{ + ProfBeginFunction(); + String8 result = {0}; + TG_Key type_key = tg_unwrapped_from_graph_rdi_key(graph, rdi, eval.type_key); + TG_Kind type_kind = tg_kind_from_key(type_key); + U64 type_byte_size = tg_byte_size_from_graph_rdi_key(graph, rdi, type_key); + U8 digit_group_separator = 0; + if(!(flags & DF_EvalVizStringFlag_ReadOnlyDisplayRules)) + { + digit_group_separator = 0; + } + switch(type_kind) + { + default:{}break; + + case TG_Kind_Handle: + { + U64 min_digits = (radix == 16) ? type_byte_size*2 : 0; + result = str8_from_s64(arena, eval.imm_s64, radix, 0, digit_group_separator); + }break; + + case TG_Kind_Char8: + case TG_Kind_Char16: + case TG_Kind_Char32: + case TG_Kind_UChar8: + case TG_Kind_UChar16: + case TG_Kind_UChar32: + { + String8 char_str = df_string_from_ascii_value(arena, eval.imm_s64); + if(char_str.size != 0) + { + if(flags & DF_EvalVizStringFlag_ReadOnlyDisplayRules) + { + String8 imm_string = str8_from_s64(arena, eval.imm_s64, radix, 0, digit_group_separator); + result = push_str8f(arena, "'%S' (%S)", char_str, imm_string); + } + else + { + result = push_str8f(arena, "'%S'", char_str); + } + } + else + { + result = str8_from_s64(arena, eval.imm_s64, radix, 0, digit_group_separator); + } + }break; + + case TG_Kind_S8: + case TG_Kind_S16: + case TG_Kind_S32: + case TG_Kind_S64: + { + U64 min_digits = (radix == 16) ? type_byte_size*2 : 0; + result = str8_from_s64(arena, eval.imm_s64, radix, 0, digit_group_separator); + }break; + + case TG_Kind_U8: + case TG_Kind_U16: + case TG_Kind_U32: + case TG_Kind_U64: + { + U64 min_digits = (radix == 16) ? type_byte_size*2 : 0; + result = str8_from_u64(arena, eval.imm_u64, radix, min_digits, digit_group_separator); + }break; + + case TG_Kind_U128: + { + Temp scratch = scratch_begin(&arena, 1); + U64 min_digits = (radix == 16) ? type_byte_size*2 : 0; + String8 upper64 = str8_from_u64(scratch.arena, eval.imm_u128[0], radix, min_digits, digit_group_separator); + String8 lower64 = str8_from_u64(scratch.arena, eval.imm_u128[1], radix, min_digits, digit_group_separator); + result = push_str8f(arena, "%S:%S", upper64, lower64); + scratch_end(scratch); + }break; + + case TG_Kind_F32: {result = push_str8f(arena, "%f", eval.imm_f32);}break; + case TG_Kind_F64: {result = push_str8f(arena, "%f", eval.imm_f64);}break; + case TG_Kind_Bool:{result = push_str8f(arena, "%s", eval.imm_u64 ? "true" : "false");}break; + case TG_Kind_Ptr: {result = push_str8f(arena, "0x%I64x", eval.imm_u64);}break; + case TG_Kind_LRef:{result = push_str8f(arena, "0x%I64x", eval.imm_u64);}break; + case TG_Kind_RRef:{result = push_str8f(arena, "0x%I64x", eval.imm_u64);}break; + case TG_Kind_Function:{result = push_str8f(arena, "0x%I64x", eval.imm_u64);}break; + + case TG_Kind_Enum: + { + Temp scratch = scratch_begin(&arena, 1); + TG_Type *type = tg_type_from_graph_rdi_key(scratch.arena, graph, rdi, type_key); + String8 constant_name = {0}; + for(U64 val_idx = 0; val_idx < type->count; val_idx += 1) + { + if(eval.imm_u64 == type->enum_vals[val_idx].val) + { + constant_name = type->enum_vals[val_idx].name; + break; + } + } + if(flags & DF_EvalVizStringFlag_ReadOnlyDisplayRules) + { + if(constant_name.size != 0) + { + result = push_str8f(arena, "0x%I64x (%S)", eval.imm_u64, constant_name); + } + else + { + result = push_str8f(arena, "0x%I64x (%I64u)", eval.imm_u64, eval.imm_u64); + } + } + else if(constant_name.size != 0) + { + result = push_str8_copy(arena, constant_name); + } + else + { + result = push_str8f(arena, "0x%I64x (%I64u)", eval.imm_u64, eval.imm_u64); + } + scratch_end(scratch); + }break; + } + + ProfEnd(); + return result; +} + +//- rjf: writing values back to child processes + +internal B32 +df_commit_eval_value(TG_Graph *graph, RDI_Parsed *rdi, DF_CtrlCtx *ctrl_ctx, DF_Eval dst_eval, DF_Eval src_eval) +{ + B32 result = 0; + Temp scratch = scratch_begin(0, 0); + + //- rjf: unpack arguments + DF_Entity *thread = df_entity_from_handle(ctrl_ctx->thread); + DF_Entity *process = thread->parent; + TG_Key dst_type_key = dst_eval.type_key; + TG_Key src_type_key = src_eval.type_key; + TG_Kind dst_type_kind = tg_kind_from_key(dst_type_key); + TG_Kind src_type_kind = tg_kind_from_key(src_type_key); + U64 dst_type_byte_size = tg_byte_size_from_graph_rdi_key(graph, rdi, dst_type_key); + U64 src_type_byte_size = tg_byte_size_from_graph_rdi_key(graph, rdi, src_type_key); + + //- rjf: get commit data based on destination type + String8 commit_data = {0}; + if(src_eval.errors.count == 0) + { + result = 1; + switch(dst_type_kind) + { + default: + { + // NOTE(rjf): not supported + result = 0; + }break; + + //- rjf: pointers + case TG_Kind_Ptr: + case TG_Kind_LRef: + if((TG_Kind_Char8 <= src_type_kind && src_type_kind <= TG_Kind_Bool) || src_type_kind == TG_Kind_Ptr) + { + DF_Eval value_eval = df_value_mode_eval_from_eval(graph, rdi, ctrl_ctx, src_eval); + commit_data = str8((U8 *)&value_eval.imm_u64, dst_type_byte_size); + commit_data = push_str8_copy(scratch.arena, commit_data); + }break; + + //- rjf: integers + case TG_Kind_Char8: + case TG_Kind_Char16: + case TG_Kind_Char32: + case TG_Kind_S8: + case TG_Kind_S16: + case TG_Kind_S32: + case TG_Kind_S64: + case TG_Kind_UChar8: + case TG_Kind_UChar16: + case TG_Kind_UChar32: + case TG_Kind_U8: + case TG_Kind_U16: + case TG_Kind_U32: + case TG_Kind_U64: + case TG_Kind_Bool: + if(TG_Kind_Char8 <= src_type_kind && src_type_kind <= TG_Kind_Bool) + { + DF_Eval value_eval = df_value_mode_eval_from_eval(graph, rdi, ctrl_ctx, src_eval); + commit_data = str8((U8 *)&value_eval.imm_u64, dst_type_byte_size); + commit_data = push_str8_copy(scratch.arena, commit_data); + }break; + + //- rjf: float32s + case TG_Kind_F32: + if((TG_Kind_Char8 <= src_type_kind && src_type_kind <= TG_Kind_Bool) || + src_type_kind == TG_Kind_F32 || + src_type_kind == TG_Kind_F64) + { + F32 value = 0; + DF_Eval value_eval = df_value_mode_eval_from_eval(graph, rdi, ctrl_ctx, src_eval); + switch(src_type_kind) + { + case TG_Kind_F32:{value = value_eval.imm_f32;}break; + case TG_Kind_F64:{value = (F32)value_eval.imm_f64;}break; + default:{value = (F32)value_eval.imm_s64;}break; + } + commit_data = str8((U8 *)&value, sizeof(F32)); + commit_data = push_str8_copy(scratch.arena, commit_data); + }break; + + //- rjf: float64s + case TG_Kind_F64: + if((TG_Kind_Char8 <= src_type_kind && src_type_kind <= TG_Kind_Bool) || + src_type_kind == TG_Kind_F32 || + src_type_kind == TG_Kind_F64) + { + F64 value = 0; + DF_Eval value_eval = df_value_mode_eval_from_eval(graph, rdi, ctrl_ctx, src_eval); + switch(src_type_kind) + { + case TG_Kind_F32:{value = (F64)value_eval.imm_f32;}break; + case TG_Kind_F64:{value = value_eval.imm_f64;}break; + default:{value = (F64)value_eval.imm_s64;}break; + } + commit_data = str8((U8 *)&value, sizeof(F64)); + commit_data = push_str8_copy(scratch.arena, commit_data); + }break; + + //- rjf: enums + case TG_Kind_Enum: + if(TG_Kind_Char8 <= src_type_kind && src_type_kind <= TG_Kind_Bool) + { + DF_Eval value_eval = df_value_mode_eval_from_eval(graph, rdi, ctrl_ctx, src_eval); + commit_data = str8((U8 *)&value_eval.imm_u64, dst_type_byte_size); + commit_data = push_str8_copy(scratch.arena, commit_data); + }break; + } + } + + //- rjf: commit + if(result && commit_data.size != 0) + { + switch(dst_eval.mode) + { + default:{}break; + case EVAL_EvalMode_Addr: + { + ctrl_process_write(process->ctrl_machine_id, process->ctrl_handle, r1u64(dst_eval.offset, dst_eval.offset+commit_data.size), commit_data.str); + }break; + case EVAL_EvalMode_Reg: + { + CTRL_Unwind unwind = df_query_cached_unwind_from_thread(thread); + Architecture arch = df_architecture_from_entity(thread); + U64 reg_block_size = regs_block_size_from_architecture(arch); + if(unwind.frames.count != 0 && + (0 <= dst_eval.offset && dst_eval.offset+commit_data.size < reg_block_size)) + { + void *new_regs = push_array(scratch.arena, U8, reg_block_size); + MemoryCopy(new_regs, unwind.frames.v[0].regs, reg_block_size); + MemoryCopy((U8 *)new_regs+dst_eval.offset, commit_data.str, commit_data.size); + result = ctrl_thread_write_reg_block(thread->ctrl_machine_id, thread->ctrl_handle, new_regs); + } + }break; + } + } + + scratch_end(scratch); + return result; +} + +//- rjf: type helpers + +internal TG_MemberArray +df_filtered_data_members_from_members_cfg_table(Arena *arena, TG_MemberArray members, DF_CfgTable *cfg) +{ + DF_CfgVal *only = df_cfg_val_from_string(cfg, str8_lit("only")); + DF_CfgVal *omit = df_cfg_val_from_string(cfg, str8_lit("omit")); + TG_MemberArray filtered_members = members; + if(only != &df_g_nil_cfg_val || omit != &df_g_nil_cfg_val) + { + Temp scratch = scratch_begin(&arena, 1); + typedef struct DF_TypeMemberLooseNode DF_TypeMemberLooseNode; + struct DF_TypeMemberLooseNode + { + DF_TypeMemberLooseNode *next; + TG_Member *member; + }; + DF_TypeMemberLooseNode *first_member = 0; + DF_TypeMemberLooseNode *last_member = 0; + U64 member_count = 0; + MemoryZeroStruct(&filtered_members); + for(U64 idx = 0; idx < members.count; idx += 1) + { + // rjf: check if included by 'only's + B32 is_included = 1; + for(DF_CfgNode *r = only->first; r != &df_g_nil_cfg_node; r = r->next) + { + is_included = 0; + for(DF_CfgNode *name_node = r->first; name_node != &df_g_nil_cfg_node; name_node = name_node->next) + { + String8 name = name_node->string; + if(str8_match(members.v[idx].name, name, 0)) + { + is_included = 1; + goto end_inclusion_check; + } + } + } + end_inclusion_check:; + + // rjf: remove if excluded by 'omit's + for(DF_CfgNode *r = omit->first; r != &df_g_nil_cfg_node; r = r->next) + { + for(DF_CfgNode *name_node = r->first; name_node != &df_g_nil_cfg_node; name_node = name_node->next) + { + String8 name = name_node->string; + if(str8_match(members.v[idx].name, name, 0)) + { + is_included = 0; + goto end_exclusion_check; + } + } + } + end_exclusion_check:; + + // rjf: push if included + if(is_included) + { + DF_TypeMemberLooseNode *n = push_array(scratch.arena, DF_TypeMemberLooseNode, 1); + n->member = &members.v[idx]; + SLLQueuePush(first_member, last_member, n); + member_count += 1; + } + } + + // rjf: bake + { + filtered_members.count = member_count; + filtered_members.v = push_array_no_zero(arena, TG_Member, filtered_members.count); + U64 idx = 0; + for(DF_TypeMemberLooseNode *n = first_member; n != 0; n = n->next, idx += 1) + { + MemoryCopyStruct(&filtered_members.v[idx], n->member); + filtered_members.v[idx].name = push_str8_copy(arena, filtered_members.v[idx].name); + } + } + scratch_end(scratch); + } + return filtered_members; +} + +internal DF_EvalLinkBaseChunkList +df_eval_link_base_chunk_list_from_eval(Arena *arena, TG_Graph *graph, RDI_Parsed *rdi, TG_Key link_member_type_key, U64 link_member_off, DF_CtrlCtx *ctrl_ctx, DF_Eval eval, U64 cap) +{ + DF_EvalLinkBaseChunkList list = {0}; + for(DF_Eval base_eval = eval, last_eval = zero_struct; list.count < cap;) + { + // rjf: check this ptr's validity + if(base_eval.offset == 0 || (base_eval.offset == last_eval.offset && base_eval.mode == last_eval.mode)) + { + break; + } + + // rjf: gather + { + DF_EvalLinkBaseChunkNode *chunk = list.last; + if(chunk == 0 || chunk->count == ArrayCount(chunk->b)) + { + chunk = push_array_no_zero(arena, DF_EvalLinkBaseChunkNode, 1); + chunk->next = 0; + chunk->count = 0; + SLLQueuePush(list.first, list.last, chunk); + } + chunk->b[chunk->count].mode = base_eval.mode; + chunk->b[chunk->count].offset = base_eval.offset; + chunk->count += 1; + list.count += 1; + } + + // rjf: grab link member + DF_Eval link_member_eval = + { + link_member_type_key, + base_eval.mode, + base_eval.offset + link_member_off, + }; + DF_Eval link_member_value_eval = df_value_mode_eval_from_eval(graph, rdi, ctrl_ctx, link_member_eval); + + // rjf: advance to next link + last_eval = base_eval; + base_eval.mode = EVAL_EvalMode_Addr; + base_eval.offset = link_member_value_eval.imm_u64; + } + return list; +} + +internal DF_EvalLinkBase +df_eval_link_base_from_chunk_list_index(DF_EvalLinkBaseChunkList *list, U64 idx) +{ + DF_EvalLinkBase result = zero_struct; + U64 scan_idx = 0; + for(DF_EvalLinkBaseChunkNode *chunk = list->first; chunk != 0; chunk = chunk->next) + { + U64 chunk_idx_opl = scan_idx+chunk->count; + if(scan_idx <= idx && idx < chunk_idx_opl) + { + result = chunk->b[idx - scan_idx]; + } + scan_idx = chunk_idx_opl; + } + return result; +} + +internal DF_EvalLinkBaseArray +df_eval_link_base_array_from_chunk_list(Arena *arena, DF_EvalLinkBaseChunkList *chunks) +{ + DF_EvalLinkBaseArray array = {0}; + array.count = chunks->count; + array.v = push_array_no_zero(arena, DF_EvalLinkBase, array.count); + U64 idx = 0; + for(DF_EvalLinkBaseChunkNode *n = chunks->first; n != 0; n = n->next) + { + MemoryCopy(array.v + idx, n->b, n->count * sizeof(DF_EvalLinkBase)); + idx += n->count; + } + return array; +} + +//- rjf: viz block collection building + +internal DF_EvalVizBlock * +df_eval_viz_block_begin(Arena *arena, DF_EvalVizBlockKind kind, DF_ExpandKey parent_key, DF_ExpandKey key, S32 depth) +{ + DF_EvalVizBlockNode *n = push_array(arena, DF_EvalVizBlockNode, 1); + n->v.kind = kind; + n->v.parent_key = parent_key; + n->v.key = key; + n->v.depth = depth; + return &n->v; +} + +internal DF_EvalVizBlock * +df_eval_viz_block_split_and_continue(Arena *arena, DF_EvalVizBlockList *list, DF_EvalVizBlock *split_block, U64 split_idx) +{ + U64 total_count = split_block->semantic_idx_range.max; + split_block->visual_idx_range.max = split_block->semantic_idx_range.max = split_idx; + df_eval_viz_block_end(list, split_block); + DF_EvalVizBlock *continue_block = df_eval_viz_block_begin(arena, split_block->kind, split_block->parent_key, split_block->key, split_block->depth); + continue_block->eval = split_block->eval; + continue_block->string = split_block->string; + continue_block->member = split_block->member; + continue_block->visual_idx_range = continue_block->semantic_idx_range = r1u64(split_idx+1, total_count); + continue_block->fzy_backing_items = split_block->fzy_backing_items; + continue_block->fzy_target = split_block->fzy_target; + continue_block->cfg_table = split_block->cfg_table; + continue_block->link_member_type_key = split_block->link_member_type_key; + continue_block->link_member_off = split_block->link_member_off; + return continue_block; +} + +internal void +df_eval_viz_block_end(DF_EvalVizBlockList *list, DF_EvalVizBlock *block) +{ + DF_EvalVizBlockNode *n = CastFromMember(DF_EvalVizBlockNode, v, block); + SLLQueuePush(list->first, list->last, n); + list->count += 1; + list->total_visual_row_count += dim_1u64(block->visual_idx_range); + list->total_semantic_row_count += dim_1u64(block->semantic_idx_range); +} + +internal void +df_append_viz_blocks_for_parent__rec(Arena *arena, DI_Scope *scope, DF_EvalView *eval_view, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_ExpandKey parent_key, DF_ExpandKey key, String8 string, DF_Eval eval, TG_Member *opt_member, DF_CfgTable *cfg_table, S32 depth, DF_EvalVizBlockList *list_out) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(&arena, 1); + + ////////////////////////////// + //- rjf: determine if this key is expanded + // + DF_ExpandNode *node = df_expand_node_from_key(&eval_view->expand_tree_table, key); + B32 parent_is_expanded = (node != 0 && node->expanded && !tg_key_match(tg_key_zero(), eval.type_key)); + + ////////////////////////////// + //- rjf: apply view rules & resolve eval + // + eval = df_dynamically_typed_eval_from_eval(parse_ctx->type_graph, parse_ctx->rdi, ctrl_ctx, eval); + eval = df_eval_from_eval_cfg_table(arena, scope, ctrl_ctx, parse_ctx, macro_map, eval, cfg_table); + + ////////////////////////////// + //- rjf: unpack eval + // + TG_Key eval_type_key = tg_unwrapped_from_graph_rdi_key(parse_ctx->type_graph, parse_ctx->rdi, eval.type_key); + TG_Kind eval_type_kind = tg_kind_from_key(eval_type_key); + String8 eval_string = push_str8_copy(arena, string); + + ////////////////////////////// + //- rjf: make and push block for root + // + { + DF_EvalVizBlock *block = df_eval_viz_block_begin(arena, DF_EvalVizBlockKind_Root, parent_key, key, depth); + block->eval = eval; + block->cfg_table = *cfg_table; + block->string = eval_string; + block->visual_idx_range = r1u64(key.child_num-1, key.child_num+0); + block->semantic_idx_range = r1u64(key.child_num-1, key.child_num+0); + if(opt_member != 0) + { + block->member = tg_member_copy(arena, opt_member); + } + df_eval_viz_block_end(list_out, block); + } + + ////////////////////////////// + //- rjf: (pointers) extract type & info to use for members and/or arrays + // + DF_Eval udt_eval = eval; + DF_Eval arr_eval = eval; + DF_Eval ptr_eval = zero_struct; + TG_Kind udt_type_kind = eval_type_kind; + TG_Kind arr_type_kind = eval_type_kind; + TG_Kind ptr_type_kind = TG_Kind_Null; + if(eval_type_kind == TG_Kind_Ptr || eval_type_kind == TG_Kind_LRef || eval_type_kind == TG_Kind_RRef) + { + TG_Key direct_type_key = tg_ptee_from_graph_rdi_key(parse_ctx->type_graph, parse_ctx->rdi, eval_type_key); + TG_Kind direct_type_kind = tg_kind_from_key(direct_type_key); + DF_Eval ptr_val_eval = df_value_mode_eval_from_eval(parse_ctx->type_graph, parse_ctx->rdi, ctrl_ctx, eval); + + // rjf: ptrs to udts + if(parent_is_expanded && + (direct_type_kind == TG_Kind_Struct || + direct_type_kind == TG_Kind_Union || + direct_type_kind == TG_Kind_Class || + direct_type_kind == TG_Kind_IncompleteStruct || + direct_type_kind == TG_Kind_IncompleteUnion || + direct_type_kind == TG_Kind_IncompleteClass)) + { + udt_eval.type_key = direct_type_key; + udt_eval.mode = EVAL_EvalMode_Addr; + udt_eval.offset = ptr_val_eval.imm_u64; + udt_type_kind = tg_kind_from_key(direct_type_key); + } + + // rjf: ptrs to arrays + if(direct_type_kind == TG_Kind_Array) + { + arr_eval.type_key = direct_type_key; + arr_eval.mode = EVAL_EvalMode_Addr; + arr_eval.offset = ptr_val_eval.imm_u64; + arr_type_kind = tg_kind_from_key(direct_type_key); + } + + // rjf: ptrs to ptrs + if(direct_type_kind == TG_Kind_Ptr || direct_type_kind == TG_Kind_LRef || direct_type_kind == TG_Kind_RRef) + { + ptr_eval.type_key = direct_type_key; + ptr_eval.mode = EVAL_EvalMode_Addr; + ptr_eval.offset = ptr_val_eval.imm_u64; + ptr_type_kind = tg_kind_from_key(direct_type_key); + } + } + + ////////////////////////////// + //- rjf: determine rule for building expansion children + // + typedef enum DF_EvalVizExpandRule + { + DF_EvalVizExpandRule_Default, + DF_EvalVizExpandRule_List, + DF_EvalVizExpandRule_ViewRule, + } + DF_EvalVizExpandRule; + DF_EvalVizExpandRule expand_rule = DF_EvalVizExpandRule_Default; + DF_CoreViewRuleSpec *expand_view_rule_spec = &df_g_nil_core_view_rule_spec; + DF_CfgVal *expand_view_rule_cfg = &df_g_nil_cfg_val; + String8 list_next_link_member_name = {0}; + { + //- rjf: look for view rules which have their own custom viz block building rules + if(expand_rule == DF_EvalVizExpandRule_Default && parent_is_expanded) + { + for(DF_CfgVal *val = cfg_table->first_val; val != 0 && val != &df_g_nil_cfg_val; val = val->linear_next) + { + DF_CoreViewRuleSpec *spec = df_core_view_rule_spec_from_string(val->string); + if(str8_match(spec->info.string, str8_lit("list"), 0) || + str8_match(spec->info.string, str8_lit("omit"), 0) || + str8_match(spec->info.string, str8_lit("only"), 0)) + { + // TODO(rjf): "list" view rule needs to be formally moved into the visualization + // engine hooks when the system is mature enough to support it + // also "omit", "only" + continue; + } + if(spec->info.flags & DF_CoreViewRuleSpecInfoFlag_VizBlockProd) + { + expand_rule = DF_EvalVizExpandRule_ViewRule; + expand_view_rule_spec = spec; + expand_view_rule_cfg = val; + break; + } + } + } + + //- rjf: get linked list viz view rule info for structs + if(expand_rule == DF_EvalVizExpandRule_Default && + parent_is_expanded && + (udt_type_kind == TG_Kind_Struct || + udt_type_kind == TG_Kind_Union || + udt_type_kind == TG_Kind_Class)) + { + DF_CfgVal *list_cfg = df_cfg_val_from_string(cfg_table, str8_lit("list")); + if(list_cfg != &df_g_nil_cfg_val) + { + list_next_link_member_name = list_cfg->first->first->string; + expand_rule = DF_EvalVizExpandRule_List; + } + } + } + + ////////////////////////////// + //- rjf: (all) descend to make blocks according to lens + // + if(parent_is_expanded && expand_rule == DF_EvalVizExpandRule_ViewRule && + expand_view_rule_spec != &df_g_nil_core_view_rule_spec && + expand_view_rule_cfg != &df_g_nil_cfg_val) + ProfScope("build viz blocks for lens") + { + expand_view_rule_spec->info.viz_block_prod(arena, scope, ctrl_ctx, parse_ctx, macro_map, eval_view, eval, string, cfg_table, parent_key, key, depth+1, expand_view_rule_cfg->last, list_out); + } + + ////////////////////////////// + //- rjf: (structs, unions, classes) descend to members & make block(s), normally + // + if(parent_is_expanded && expand_rule == DF_EvalVizExpandRule_Default && + (udt_type_kind == TG_Kind_Struct || + udt_type_kind == TG_Kind_Union || + udt_type_kind == TG_Kind_Class)) + ProfScope("build viz blocks for UDT members") + { + //- rjf: type -> filtered data members + TG_MemberArray data_members = tg_data_members_from_graph_rdi_key(scratch.arena, parse_ctx->type_graph, parse_ctx->rdi, udt_eval.type_key); + TG_MemberArray filtered_data_members = df_filtered_data_members_from_members_cfg_table(scratch.arena, data_members, cfg_table); + + //- rjf: build blocks for all members, split by sub-expansions + DF_EvalVizBlock *last_vb = df_eval_viz_block_begin(arena, DF_EvalVizBlockKind_Members, key, df_expand_key_make(df_hash_from_expand_key(key), 0), depth+1); + { + last_vb->eval = udt_eval; + last_vb->string = eval_string; + last_vb->cfg_table = *cfg_table; + last_vb->visual_idx_range = last_vb->semantic_idx_range = r1u64(0, filtered_data_members.count); + } + for(DF_ExpandNode *child = node->first; child != 0; child = child->next) + { + // rjf: unpack expansion info; skip out-of-bounds splits + U64 child_num = child->key.child_num; + U64 child_idx = child_num-1; + if(child_idx >= filtered_data_members.count) + { + continue; + } + + // rjf: form split: truncate & complete last block; begin next block + last_vb = df_eval_viz_block_split_and_continue(arena, list_out, last_vb, child_idx); + + // rjf: recurse for sub-expansion + { + DF_CfgTable child_cfg = *cfg_table; + { + String8 view_rule_string = df_eval_view_rule_from_key(eval_view, df_expand_key_make(df_hash_from_expand_key(key), child_num)); + child_cfg = df_cfg_table_from_inheritance(arena, cfg_table); + if(view_rule_string.size != 0) + { + df_cfg_table_push_unparsed_string(arena, &child_cfg, view_rule_string, DF_CfgSrc_User); + } + } + TG_Member *member = &filtered_data_members.v[child_idx]; + DF_Eval child_eval = zero_struct; + { + child_eval.type_key = member->type_key; + child_eval.mode = udt_eval.mode; + child_eval.offset = udt_eval.offset + member->off; + } + df_append_viz_blocks_for_parent__rec(arena, scope, eval_view, ctrl_ctx, parse_ctx, macro_map, key, child->key, member->name, child_eval, member, &child_cfg, depth+1, list_out); + } + } + df_eval_viz_block_end(list_out, last_vb); + } + + ////////////////////////////// + //- rjf: (enums) descend to members & make block(s) + // + if(parent_is_expanded && expand_rule == DF_EvalVizExpandRule_Default && + udt_eval.mode == EVAL_EvalMode_NULL && + udt_type_kind == TG_Kind_Enum) + ProfScope("build viz blocks for UDT type-eval enums") + { + //- rjf: type -> full type info + TG_Type *type = tg_type_from_graph_rdi_key(scratch.arena, parse_ctx->type_graph, parse_ctx->rdi, udt_eval.type_key); + + //- rjf: build block for all members (cannot be expanded) + DF_EvalVizBlock *last_vb = df_eval_viz_block_begin(arena, DF_EvalVizBlockKind_EnumMembers, key, df_expand_key_make(df_hash_from_expand_key(key), 0), depth+1); + { + last_vb->eval = udt_eval; + last_vb->string = eval_string; + last_vb->cfg_table = *cfg_table; + last_vb->visual_idx_range = last_vb->semantic_idx_range = r1u64(0, type->count); + } + df_eval_viz_block_end(list_out, last_vb); + } + + ////////////////////////////// + //- rjf: (structs, unions, classes) descend to members & make block(s), with linked list view + // + if(parent_is_expanded && expand_rule == DF_EvalVizExpandRule_List && + (udt_type_kind == TG_Kind_Struct || + udt_type_kind == TG_Kind_Union || + udt_type_kind == TG_Kind_Class)) + ProfScope("(structs, unions, classes) descend to members & make block(s), with linked list view") + { + //- rjf: type -> data members + TG_MemberArray data_members = tg_data_members_from_graph_rdi_key(scratch.arena, parse_ctx->type_graph, parse_ctx->rdi, udt_eval.type_key); + + //- rjf: find link member + TG_Member *link_member = 0; + TG_Kind link_member_type_kind = TG_Kind_Null; + TG_Key link_member_ptee_type_key = zero_struct; + for(U64 idx = 0; idx < data_members.count; idx += 1) + { + TG_Member *mem = &data_members.v[idx]; + if(str8_match(mem->name, list_next_link_member_name, 0)) + { + link_member = mem; + link_member_type_kind = tg_kind_from_key(link_member->type_key); + link_member_ptee_type_key = tg_ptee_from_graph_rdi_key(parse_ctx->type_graph, parse_ctx->rdi, link_member->type_key); + break; + } + } + + //- rjf: check if link member is good + B32 link_member_is_good = 1; + if(link_member == 0 || + link_member_type_kind != TG_Kind_Ptr || + !tg_key_match(link_member_ptee_type_key, udt_eval.type_key)) + { + link_member_is_good = 0; + } + + //- rjf: gather link bases + DF_EvalLinkBaseChunkList link_bases = {0}; + if(link_member_is_good) + { + link_bases = df_eval_link_base_chunk_list_from_eval(scratch.arena, parse_ctx->type_graph, parse_ctx->rdi, link_member->type_key, link_member->off, ctrl_ctx, udt_eval, 512); + } + + //- rjf: build blocks for all links, split by sub-expansions + if(link_member_is_good) + { + DF_EvalVizBlock *last_vb = df_eval_viz_block_begin(arena, DF_EvalVizBlockKind_Links, key, df_expand_key_make(df_hash_from_expand_key(key), 0), depth+1); + { + last_vb->eval = udt_eval; + last_vb->string = eval_string; + last_vb->cfg_table = *cfg_table; + last_vb->link_member_type_key = link_member->type_key; + last_vb->link_member_off = link_member->off; + last_vb->visual_idx_range = r1u64(0, link_bases.count); + last_vb->semantic_idx_range = r1u64(0, link_bases.count); + } + for(DF_ExpandNode *child = node->first; child != 0; child = child->next) + { + // rjf: unpack expansion info; skip out-of-bounds splits + U64 child_num = child->key.child_num; + U64 child_idx = child_num-1; + if(child_idx >= link_bases.count) + { + continue; + } + + // rjf: form split: truncate & complete last block; begin next block + last_vb = df_eval_viz_block_split_and_continue(arena, list_out, last_vb, child_idx); + + // rjf: find mode/offset of this link + DF_EvalLinkBase link_base = df_eval_link_base_from_chunk_list_index(&link_bases, child_idx); + + // rjf: recurse for sub-expansion + { + DF_CfgTable child_cfg = *cfg_table; + { + String8 view_rule_string = df_eval_view_rule_from_key(eval_view, df_expand_key_make(df_hash_from_expand_key(key), child_num)); + child_cfg = df_cfg_table_from_inheritance(arena, cfg_table); + if(view_rule_string.size != 0) + { + df_cfg_table_push_unparsed_string(arena, &child_cfg, view_rule_string, DF_CfgSrc_User); + } + } + DF_Eval child_eval = zero_struct; + { + child_eval.type_key = udt_eval.type_key; + child_eval.mode = link_base.mode; + child_eval.offset = link_base.offset; + } + df_append_viz_blocks_for_parent__rec(arena, scope, eval_view, ctrl_ctx, parse_ctx, macro_map, key, child->key, push_str8f(arena, "[%I64u]", child_idx), child_eval, 0, &child_cfg, depth+1, list_out); + } + } + df_eval_viz_block_end(list_out, last_vb); + } + } + + ////////////////////////////// + //- rjf: (arrays) descend to elements & make block(s), normally + // + if(parent_is_expanded && expand_rule == DF_EvalVizExpandRule_Default && + arr_type_kind == TG_Kind_Array) + ProfScope("(arrays) descend to elements & make block(s)") + { + //- rjf: unpack array type info + TG_Type *array_type = tg_type_from_graph_rdi_key(scratch.arena, parse_ctx->type_graph, parse_ctx->rdi, arr_eval.type_key); + U64 array_count = array_type->count; + TG_Key element_type_key = array_type->direct_type_key; + U64 element_type_byte_size = tg_byte_size_from_graph_rdi_key(parse_ctx->type_graph, parse_ctx->rdi, element_type_key); + + //- rjf: build blocks for all elements, split by sub-expansions + DF_EvalVizBlock *last_vb = df_eval_viz_block_begin(arena, DF_EvalVizBlockKind_Elements, key, df_expand_key_make(df_hash_from_expand_key(key), 0), depth+1); + { + last_vb->eval = arr_eval; + last_vb->string = eval_string; + last_vb->cfg_table = *cfg_table; + last_vb->visual_idx_range = last_vb->semantic_idx_range = r1u64(0, array_count); + } + for(DF_ExpandNode *child = node->first; child != 0; child = child->next) + { + // rjf: unpack expansion info; skip out-of-bounds splits + U64 child_num = child->key.child_num; + U64 child_idx = child_num-1; + if(child_idx >= array_count) + { + continue; + } + + // rjf: form split: truncate & complete last block; begin next block + last_vb = df_eval_viz_block_split_and_continue(arena, list_out, last_vb, child_idx); + + // rjf: recurse for sub-expansion + { + DF_CfgTable child_cfg = *cfg_table; + { + String8 view_rule_string = df_eval_view_rule_from_key(eval_view, df_expand_key_make(df_hash_from_expand_key(key), child_num)); + child_cfg = df_cfg_table_from_inheritance(arena, cfg_table); + if(view_rule_string.size != 0) + { + df_cfg_table_push_unparsed_string(arena, &child_cfg, view_rule_string, DF_CfgSrc_User); + } + } + DF_Eval child_eval = zero_struct; + { + child_eval.type_key = element_type_key; + child_eval.mode = arr_eval.mode; + child_eval.offset = arr_eval.offset + child_idx*element_type_byte_size; + } + df_append_viz_blocks_for_parent__rec(arena, scope, eval_view, ctrl_ctx, parse_ctx, macro_map, key, child->key, push_str8f(arena, "[%I64u]", child_idx), child_eval, 0, &child_cfg, depth+1, list_out); + } + } + df_eval_viz_block_end(list_out, last_vb); + } + + ////////////////////////////// + //- rjf: (ptr to ptrs) descend to make blocks for pointed-at-pointer + // + if(parent_is_expanded && expand_rule == DF_EvalVizExpandRule_Default && (ptr_type_kind == TG_Kind_Ptr || ptr_type_kind == TG_Kind_LRef || ptr_type_kind == TG_Kind_RRef)) + ProfScope("build viz blocks for ptr-to-ptrs") + { + String8 subexpr = push_str8f(arena, "*(%S)", string); + df_append_viz_blocks_for_parent__rec(arena, scope, eval_view, ctrl_ctx, parse_ctx, macro_map, key, df_expand_key_make(df_hash_from_expand_key(key), 1), subexpr, ptr_eval, 0, cfg_table, depth+1, list_out); + } + + scratch_end(scratch); + ProfEnd(); +} + +internal DF_EvalVizBlockList +df_eval_viz_block_list_from_eval_view_expr_keys(Arena *arena, DI_Scope *scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_EvalView *eval_view, String8 expr, DF_ExpandKey parent_key, DF_ExpandKey key) +{ + ProfBeginFunction(); + DF_EvalVizBlockList blocks = {0}; + { + DF_Eval eval = df_eval_from_string(arena, scope, ctrl_ctx, parse_ctx, macro_map, expr); + U64 expr_comma_pos = str8_find_needle(expr, 0, str8_lit(","), 0); + U64 passthrough_pos = str8_find_needle(expr, 0, str8_lit("--"), 0); + String8List default_view_rules = {0}; + if(expr_comma_pos < expr.size && expr_comma_pos < passthrough_pos) + { + String8 expr_extension = str8_substr(expr, r1u64(expr_comma_pos+1, passthrough_pos)); + expr_extension = str8_skip_chop_whitespace(expr_extension); + if(str8_match(expr_extension, str8_lit("x"), StringMatchFlag_CaseInsensitive)) + { + str8_list_pushf(arena, &default_view_rules, "hex"); + } + else if(str8_match(expr_extension, str8_lit("b"), StringMatchFlag_CaseInsensitive)) + { + str8_list_pushf(arena, &default_view_rules, "bin"); + } + else if(str8_match(expr_extension, str8_lit("o"), StringMatchFlag_CaseInsensitive)) + { + str8_list_pushf(arena, &default_view_rules, "oct"); + } + else if(expr_extension.size != 0) + { + str8_list_pushf(arena, &default_view_rules, "array:{%S}", expr_extension); + } + } + if(passthrough_pos < expr.size) + { + String8 passthrough_view_rule = str8_skip_chop_whitespace(str8_skip(expr, passthrough_pos+2)); + if(passthrough_view_rule.size != 0) + { + str8_list_push(arena, &default_view_rules, passthrough_view_rule); + } + } + String8 view_rule_string = df_eval_view_rule_from_key(eval_view, key); + DF_CfgTable view_rule_table = {0}; + for(String8Node *n = default_view_rules.first; n != 0; n = n->next) + { + df_cfg_table_push_unparsed_string(arena, &view_rule_table, n->string, DF_CfgSrc_User); + } + df_cfg_table_push_unparsed_string(arena, &view_rule_table, view_rule_string, DF_CfgSrc_User); + df_append_viz_blocks_for_parent__rec(arena, scope, eval_view, ctrl_ctx, parse_ctx, macro_map, parent_key, key, expr, eval, 0, &view_rule_table, 0, &blocks); + } + ProfEnd(); + return blocks; +} + +internal void +df_eval_viz_block_list_concat__in_place(DF_EvalVizBlockList *dst, DF_EvalVizBlockList *to_push) +{ + if(dst->last == 0) + { + *dst = *to_push; + } + else if(to_push->first != 0) + { + dst->last->next = to_push->first; + dst->last = to_push->last; + dst->count += to_push->count; + dst->total_visual_row_count += to_push->total_visual_row_count; + dst->total_semantic_row_count += to_push->total_semantic_row_count; + } + MemoryZeroStruct(to_push); +} + +internal S64 +df_row_num_from_viz_block_list_key(DF_EvalVizBlockList *blocks, DF_ExpandKey key) +{ + S64 row_num = 1; + B32 found = 0; + for(DF_EvalVizBlockNode *n = blocks->first; n != 0; n = n->next) + { + DF_EvalVizBlock *block = &n->v; + if(key.parent_hash == block->key.parent_hash) + { + B32 this_block_contains_this_key = 0; + { + if(block->fzy_backing_items.v != 0) + { + U64 item_num = fzy_item_num_from_array_element_idx__linear_search(&block->fzy_backing_items, key.child_num); + this_block_contains_this_key = (item_num != 0 && contains_1u64(block->semantic_idx_range, item_num-1)); + } + else + { + this_block_contains_this_key = (block->semantic_idx_range.min+1 <= key.child_num && key.child_num < block->semantic_idx_range.max+1); + } + } + if(this_block_contains_this_key) + { + found = 1; + if(block->fzy_backing_items.v != 0) + { + U64 item_num = fzy_item_num_from_array_element_idx__linear_search(&block->fzy_backing_items, key.child_num); + row_num += item_num-1-block->semantic_idx_range.min; + } + else + { + row_num += key.child_num-1-block->semantic_idx_range.min; + } + break; + } + } + if(!found) + { + row_num += (S64)dim_1u64(block->semantic_idx_range); + } + } + if(!found) + { + row_num = 0; + } + return row_num; +} + +internal DF_ExpandKey +df_key_from_viz_block_list_row_num(DF_EvalVizBlockList *blocks, S64 row_num) +{ + DF_ExpandKey key = {0}; + S64 scan_y = 1; + for(DF_EvalVizBlockNode *n = blocks->first; n != 0; n = n->next) + { + DF_EvalVizBlock *vb = &n->v; + Rng1S64 vb_row_num_range = r1s64(scan_y, scan_y + (S64)dim_1u64(vb->semantic_idx_range)); + if(contains_1s64(vb_row_num_range, row_num)) + { + key = vb->key; + if(vb->fzy_backing_items.v != 0) + { + U64 item_idx = (U64)((row_num - vb_row_num_range.min) + vb->semantic_idx_range.min); + if(item_idx < vb->fzy_backing_items.count) + { + key.child_num = vb->fzy_backing_items.v[item_idx].idx; + } + } + else + { + key.child_num = vb->semantic_idx_range.min + (row_num - vb_row_num_range.min) + 1; + } + break; + } + scan_y += dim_1s64(vb_row_num_range); + } + return key; +} + +internal DF_ExpandKey +df_parent_key_from_viz_block_list_row_num(DF_EvalVizBlockList *blocks, S64 row_num) +{ + DF_ExpandKey key = {0}; + S64 scan_y = 1; + for(DF_EvalVizBlockNode *n = blocks->first; n != 0; n = n->next) + { + DF_EvalVizBlock *vb = &n->v; + Rng1S64 vb_row_num_range = r1s64(scan_y, scan_y + (S64)dim_1u64(vb->semantic_idx_range)); + if(contains_1s64(vb_row_num_range, row_num)) + { + key = vb->parent_key; + break; + } + scan_y += dim_1s64(vb_row_num_range); + } + return key; +} + +//- rjf: viz row list building + +internal DF_EvalVizRow * +df_eval_viz_row_list_push_new(Arena *arena, EVAL_ParseCtx *parse_ctx, DF_EvalVizWindowedRowList *rows, DF_EvalVizBlock *block, DF_ExpandKey key, DF_Eval eval) +{ + // rjf: push + DF_EvalVizRow *row = push_array(arena, DF_EvalVizRow, 1); + SLLQueuePush(rows->first, rows->last, row); + rows->count += 1; + + // rjf: fill basics + row->depth = block->depth; + row->parent_key = block->parent_key; + row->key = key; + row->eval = eval; + row->size_in_rows = 1; + + // rjf: determine exandability, editability + if(tg_kind_from_key(eval.type_key) != TG_Kind_Null) + { + for(TG_Key t = eval.type_key;; t = tg_unwrapped_direct_from_graph_rdi_key(parse_ctx->type_graph, parse_ctx->rdi, t)) + { + TG_Kind kind = tg_kind_from_key(t); + if(kind == TG_Kind_Null) + { + break; + } + if(eval.mode != EVAL_EvalMode_NULL && ((TG_Kind_FirstBasic <= kind && kind <= TG_Kind_LastBasic) || kind == TG_Kind_Ptr || kind == TG_Kind_LRef || kind == TG_Kind_RRef)) + { + row->flags |= DF_EvalVizRowFlag_CanEditValue; + } + if(eval.mode == EVAL_EvalMode_NULL && kind == TG_Kind_Enum) + { + row->flags |= DF_EvalVizRowFlag_CanExpand; + } + if(kind == TG_Kind_Struct || + kind == TG_Kind_Union || + kind == TG_Kind_Class || + kind == TG_Kind_Array) + { + row->flags |= DF_EvalVizRowFlag_CanExpand; + } + if(row->flags & DF_EvalVizRowFlag_CanExpand) + { + break; + } + if(eval.mode == EVAL_EvalMode_NULL) + { + break; + } + if(kind == TG_Kind_Function) + { + break; + } + } + } + + return row; +} + +//////////////////////////////// +//~ rjf: Main State Accessors/Mutators + +//- rjf: frame data + +internal F32 +df_dt(void) +{ + return df_state->dt; +} + +internal U64 +df_frame_index(void) +{ + return df_state->frame_index; +} + +internal Arena * +df_frame_arena(void) +{ + return df_state->frame_arenas[df_state->frame_index%ArrayCount(df_state->frame_arenas)]; +} + +internal F64 +df_time_in_seconds(void) +{ + return df_state->time_in_seconds; +} + +//- rjf: interaction registers + +internal DF_InteractRegs * +df_interact_regs(void) +{ + DF_InteractRegs *regs = &df_state->top_interact_regs->v; + return regs; +} + +internal DF_InteractRegs * +df_push_interact_regs(void) +{ + DF_InteractRegs *top = df_interact_regs(); + DF_InteractRegsNode *n = push_array(df_frame_arena(), DF_InteractRegsNode, 1); + MemoryCopyStruct(&n->v, top); + SLLStackPush(df_state->top_interact_regs, n); + return &n->v; +} + +internal DF_InteractRegs * +df_pop_interact_regs(void) +{ + DF_InteractRegs *regs = &df_state->top_interact_regs->v; + SLLStackPop(df_state->top_interact_regs); + if(df_state->top_interact_regs == 0) + { + df_state->top_interact_regs = &df_state->base_interact_regs; + } + return regs; +} + +//- rjf: undo/redo history + +internal DF_StateDeltaHistory * +df_state_delta_history(void) +{ + return df_state->hist; +} + +//- rjf: control state + +internal DF_RunKind +df_ctrl_last_run_kind(void) +{ + return df_state->ctrl_last_run_kind; +} + +internal U64 +df_ctrl_last_run_frame_idx(void) +{ + return df_state->ctrl_last_run_frame_idx; +} + +internal U64 +df_ctrl_run_gen(void) +{ + return df_state->ctrl_run_gen; +} + +internal B32 +df_ctrl_targets_running(void) +{ + return df_state->ctrl_is_running; +} + +//- rjf: control context + +internal DF_CtrlCtx +df_ctrl_ctx(void) +{ + return df_state->ctrl_ctx; +} + +internal void +df_ctrl_ctx_apply_overrides(DF_CtrlCtx *ctx, DF_CtrlCtx *overrides) +{ + if(!df_handle_match(overrides->thread, df_handle_zero())) + { + ctx->thread = overrides->thread; + ctx->unwind_count = overrides->unwind_count; + ctx->inline_unwind_count = overrides->inline_unwind_count; + } +} + +//- rjf: config paths + +internal String8 +df_cfg_path_from_src(DF_CfgSrc src) +{ + return df_state->cfg_paths[src]; +} + +//- rjf: config state + +internal DF_CfgTable * +df_cfg_table(void) +{ + return &df_state->cfg_table; +} + +//- rjf: config serialization + +internal String8 +df_cfg_escaped_from_raw_string(Arena *arena, String8 string) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List parts = {0}; + U64 split_start_idx = 0; + for(U64 idx = 0; idx <= string.size; idx += 1) + { + U8 byte = (idx < string.size ? string.str[idx] : 0); + if(byte == 0 || byte == '\"' || byte == '\\') + { + String8 part = str8_substr(string, r1u64(split_start_idx, idx)); + str8_list_push(scratch.arena, &parts, part); + switch(byte) + { + default:{}break; + case '\"':{str8_list_push(scratch.arena, &parts, str8_lit("\\\""));}break; + case '\\':{str8_list_push(scratch.arena, &parts, str8_lit("\\\\"));}break; + } + split_start_idx = idx+1; + } + } + StringJoin join = {0}; + String8 result = str8_list_join(arena, &parts, &join); + scratch_end(scratch); + return result; +} + +internal String8 +df_cfg_raw_from_escaped_string(Arena *arena, String8 string) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List parts = {0}; + U64 split_start_idx = 0; + U64 extra_advance = 0; + for(U64 idx = 0; idx <= string.size; ((idx += 1+extra_advance), extra_advance=0)) + { + U8 byte = (idx < string.size ? string.str[idx] : 0); + if(byte == 0 || byte == '\\') + { + String8 part = str8_substr(string, r1u64(split_start_idx, idx)); + str8_list_push(scratch.arena, &parts, part); + if(byte == '\\' && idx+1 < string.size) + { + switch(string.str[idx+1]) + { + default:{}break; + case '"': {extra_advance = 1; str8_list_push(scratch.arena, &parts, str8_lit("\""));}break; + case '\\':{extra_advance = 1; str8_list_push(scratch.arena, &parts, str8_lit("\\"));}break; + } + } + split_start_idx = idx+1+extra_advance; + } + } + StringJoin join = {0}; + String8 result = str8_list_join(arena, &parts, &join); + scratch_end(scratch); + return result; +} + +internal String8List +df_cfg_strings_from_core(Arena *arena, String8 root_path, DF_CfgSrc source) +{ + ProfBeginFunction(); + String8List strs = {0}; + + //- rjf: write recent projects + { + B32 first = 1; + DF_EntityList recent_projects = df_query_cached_entity_list_with_kind(DF_EntityKind_RecentProject); + for(DF_EntityNode *n = recent_projects.first; n != 0; n = n->next) + { + DF_Entity *rp = n->entity; + if(rp->cfg_src == source) + { + if(first) + { + first = 0; + str8_list_push(arena, &strs, str8_lit("/// recent projects ///////////////////////////////////////////////////////////\n")); + str8_list_push(arena, &strs, str8_lit("\n")); + } + Temp scratch = scratch_begin(&arena, 1); + String8 path_absolute = path_normalized_from_string(scratch.arena, rp->name); + String8 path_relative = path_relative_dst_from_absolute_dst_src(scratch.arena, path_absolute, root_path); + str8_list_pushf(arena, &strs, "recent_project: {\"%S\"}\n", path_relative); + scratch_end(scratch); + } + } + if(!first) + { + str8_list_push(arena, &strs, str8_lit("\n")); + } + } + + //- rjf: write targets + { + B32 first = 1; + DF_EntityList targets = df_query_cached_entity_list_with_kind(DF_EntityKind_Target); + for(DF_EntityNode *n = targets.first; n != 0; n = n->next) + { + DF_Entity *target = n->entity; + if(target->cfg_src == source) + { + if(first) + { + first = 0; + str8_list_push(arena, &strs, str8_lit("/// targets ///////////////////////////////////////////////////////////////////\n")); + str8_list_push(arena, &strs, str8_lit("\n")); + } + Temp scratch = scratch_begin(&arena, 1); + DF_Entity *exe__ent = df_entity_child_from_kind(target, DF_EntityKind_Executable); + DF_Entity *args__ent = df_entity_child_from_kind(target, DF_EntityKind_Arguments); + DF_Entity *wdir__ent = df_entity_child_from_kind(target, DF_EntityKind_ExecutionPath); + DF_Entity *entr__ent = df_entity_child_from_kind(target, DF_EntityKind_EntryPointName); + String8 label = target->name; + String8 exe = exe__ent->name; + String8 exe_normalized = path_normalized_from_string(scratch.arena, exe); + String8 exe_normalized_rel = path_relative_dst_from_absolute_dst_src(scratch.arena, exe_normalized, root_path); + String8 wdir = wdir__ent->name; + String8 wdir_normalized = path_normalized_from_string(scratch.arena, wdir); + String8 wdir_normalized_rel = path_relative_dst_from_absolute_dst_src(scratch.arena, wdir_normalized, root_path); + String8 entry_point_name = entr__ent->name; + String8 label_escaped = df_cfg_escaped_from_raw_string(arena, label); + String8 args_escaped = df_cfg_escaped_from_raw_string(arena, args__ent->name); + String8 entry_escaped = df_cfg_escaped_from_raw_string(arena, entry_point_name); + str8_list_push (arena, &strs, str8_lit("target:\n")); + str8_list_push (arena, &strs, str8_lit("{\n")); + if(label.size != 0) + { + str8_list_pushf(arena, &strs, " label: \"%S\"\n", label_escaped); + } + str8_list_pushf(arena, &strs, " exe: \"%S\"\n", exe_normalized_rel); + str8_list_pushf(arena, &strs, " arguments: \"%S\"\n", args_escaped); + str8_list_pushf(arena, &strs, " working_directory: \"%S\"\n", wdir_normalized_rel); + if(entry_point_name.size != 0) + { + str8_list_pushf(arena, &strs, " entry_point: \"%S\"\n", entry_escaped); + } + str8_list_pushf(arena, &strs, " active: %i\n", (int)target->b32); + if(target->flags & DF_EntityFlag_HasColor) + { + Vec4F32 hsva = df_hsva_from_entity(target); + str8_list_pushf(arena, &strs, " hsva: %.2f %.2f %.2f %.2f\n", hsva.x, hsva.y, hsva.z, hsva.w); + } + str8_list_push (arena, &strs, str8_lit("}\n")); + str8_list_push (arena, &strs, str8_lit("\n")); + scratch_end(scratch); + } + } + } + + //- rjf: write path maps + { + B32 first = 1; + DF_EntityList path_maps = df_query_cached_entity_list_with_kind(DF_EntityKind_OverrideFileLink); + for(DF_EntityNode *n = path_maps.first; n != 0; n = n->next) + { + DF_Entity *map = n->entity; + if(map->cfg_src == source) + { + if(first) + { + first = 0; + str8_list_push(arena, &strs, str8_lit("/// file path maps ////////////////////////////////////////////////////////////\n")); + str8_list_push(arena, &strs, str8_lit("\n")); + } + String8 src_path = df_full_path_from_entity(arena, map); + String8 dst_path = df_full_path_from_entity(arena, df_entity_from_handle(map->entity_handle)); + str8_list_push (arena, &strs, str8_lit("file_path_map:\n")); + str8_list_push (arena, &strs, str8_lit("{\n")); + str8_list_pushf(arena, &strs, " source_path: \"%S\"\n", src_path); + str8_list_pushf(arena, &strs, " dest_path: \"%S\"\n", dst_path); + str8_list_push (arena, &strs, str8_lit("}\n")); + str8_list_push (arena, &strs, str8_lit("\n")); + } + } + } + + //- rjf: write auto view rules + { + B32 first = 1; + DF_EntityList avrs = df_query_cached_entity_list_with_kind(DF_EntityKind_AutoViewRule); + for(DF_EntityNode *n = avrs.first; n != 0; n = n->next) + { + DF_Entity *map = n->entity; + if(map->cfg_src == source) + { + if(first) + { + first = 0; + str8_list_push(arena, &strs, str8_lit("/// auto view rules ///////////////////////////////////////////////////////////\n")); + str8_list_push(arena, &strs, str8_lit("\n")); + } + String8 type = df_entity_child_from_kind(map, DF_EntityKind_Source)->name; + String8 view_rule = df_entity_child_from_kind(map, DF_EntityKind_Dest)->name; + type = df_cfg_escaped_from_raw_string(arena, type); + view_rule= df_cfg_escaped_from_raw_string(arena, view_rule); + str8_list_push (arena, &strs, str8_lit("auto_view_rule:\n")); + str8_list_push (arena, &strs, str8_lit("{\n")); + str8_list_pushf(arena, &strs, " type: \"%S\"\n", type); + str8_list_pushf(arena, &strs, " view_rule: \"%S\"\n", view_rule); + str8_list_push (arena, &strs, str8_lit("}\n")); + str8_list_push (arena, &strs, str8_lit("\n")); + } + } + } + + //- rjf: write breakpoints + { + B32 first = 1; + DF_EntityList bps = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); + for(DF_EntityNode *n = bps.first; n != 0; n = n->next) + { + DF_Entity *bp = n->entity; + if(bp->cfg_src == source) + { + DF_Entity *file = df_entity_ancestor_from_kind(bp, DF_EntityKind_File); + DF_Entity *symb = df_entity_child_from_kind(bp, DF_EntityKind_EntryPointName); + DF_Entity *cond = df_entity_child_from_kind(bp, DF_EntityKind_Condition); + if(first) + { + first = 0; + str8_list_push(arena, &strs, str8_lit("/// breakpoints ///////////////////////////////////////////////////////////////\n")); + str8_list_push(arena, &strs, str8_lit("\n")); + } + + // rjf: begin + str8_list_push(arena, &strs, str8_lit("breakpoint:\n")); + str8_list_push(arena, &strs, str8_lit("{\n")); + + // rjf: textual breakpoints + if(!df_entity_is_nil(file) && bp->flags & DF_EntityFlag_HasTextPoint) + { + String8 bp_file_path = df_full_path_from_entity(arena, file); + String8 srlized_bp_file_path = path_relative_dst_from_absolute_dst_src(arena, bp_file_path, root_path); + String8 string = push_str8f(arena, " line: (\"%S\":%I64d)\n", srlized_bp_file_path, bp->text_point.line); + str8_list_push(arena, &strs, string); + } + + // rjf: function name breakpoints + else if(!df_entity_is_nil(symb) && symb->name.size != 0) + { + String8 symb_escaped = df_cfg_escaped_from_raw_string(arena, symb->name); + str8_list_pushf(arena, &strs, " symbol: \"%S\"\n", symb_escaped); + } + + // rjf: address breakpoints + else if(bp->flags & DF_EntityFlag_HasVAddr) + { + str8_list_pushf(arena, &strs, " addr: 0x%I64x\n", bp->vaddr); + } + + // rjf: conditions + if(!df_entity_is_nil(cond)) + { + String8 cond_escaped = df_cfg_escaped_from_raw_string(arena, cond->name); + str8_list_pushf(arena, &strs, " condition: \"%S\"\n", cond_escaped); + } + + // rjf: universal options + str8_list_pushf(arena, &strs, " enabled: %i\n", (int)bp->b32); + if(bp->name.size != 0) + { + String8 label_escaped = df_cfg_escaped_from_raw_string(arena, bp->name); + str8_list_pushf(arena, &strs, " label: \"%S\"\n", bp->name); + } + if(bp->flags & DF_EntityFlag_HasColor) + { + Vec4F32 hsva = df_hsva_from_entity(bp); + str8_list_pushf(arena, &strs, " hsva: %.2f %.2f %.2f %.2f\n", hsva.x, hsva.y, hsva.z, hsva.w); + } + + // rjf: end + str8_list_push(arena, &strs, str8_lit("}\n\n")); + } + } + } + + //- rjf: write watch pins + { + B32 first = 1; + DF_EntityList pins = df_query_cached_entity_list_with_kind(DF_EntityKind_WatchPin); + for(DF_EntityNode *n = pins.first; n != 0; n = n->next) + { + DF_Entity *pin = n->entity; + if(pin->cfg_src == source) + { + if(first) + { + first = 0; + str8_list_push(arena, &strs, str8_lit("/// watch pins ////////////////////////////////////////////////////////////////\n")); + str8_list_push(arena, &strs, str8_lit("\n")); + } + + // rjf: write + str8_list_push(arena, &strs, str8_lit("watch_pin:\n")); + str8_list_push(arena, &strs, str8_lit("{\n")); + String8 expr_escaped = df_cfg_escaped_from_raw_string(arena, pin->name); + str8_list_pushf(arena, &strs, " expression: \"%S\"\n", expr_escaped); + DF_Entity *file = df_entity_ancestor_from_kind(pin, DF_EntityKind_File); + if(pin->flags & DF_EntityFlag_HasTextPoint && !df_entity_is_nil(file)) + { + String8 project_path = root_path; + String8 pin_file_path = df_full_path_from_entity(arena, file); + project_path = path_normalized_from_string(arena, project_path); + pin_file_path = path_normalized_from_string(arena, pin_file_path); + String8 srlized_pin_file_path = path_relative_dst_from_absolute_dst_src(arena, pin_file_path, project_path); + str8_list_pushf(arena, &strs, " line: (\"%S\":%I64d)\n", srlized_pin_file_path, pin->text_point.line); + } + else if(pin->flags & DF_EntityFlag_HasVAddr) + { + str8_list_pushf(arena, &strs, " addr: (0x%I64x)\n", pin->vaddr); + } + if(pin->flags & DF_EntityFlag_HasColor) + { + Vec4F32 hsva = df_hsva_from_entity(pin); + str8_list_pushf(arena, &strs, " hsva: %.2f %.2f %.2f %.2f\n", hsva.x, hsva.y, hsva.z, hsva.w); + } + str8_list_push(arena, &strs, str8_lit("}\n\n")); + } + } + } + + //- rjf: write exception code filters + if(source == DF_CfgSrc_Project) + { + str8_list_push(arena, &strs, str8_lit("/// exception code filters ////////////////////////////////////////////////////\n")); + str8_list_push(arena, &strs, str8_lit("\n")); + str8_list_push(arena, &strs, str8_lit("exception_code_filters:\n")); + str8_list_push(arena, &strs, str8_lit("{\n")); + for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)(CTRL_ExceptionCodeKind_Null+1); + k < CTRL_ExceptionCodeKind_COUNT; + k = (CTRL_ExceptionCodeKind)(k+1)) + { + String8 name = ctrl_exception_code_kind_lowercase_code_string_table[k]; + B32 value = !!(df_state->ctrl_exception_code_filters[k/64] & (1ull<<(k%64))); + str8_list_pushf(arena, &strs, " %S: %i\n", name, value); + } + str8_list_push(arena, &strs, str8_lit("}\n\n")); + } + + //- rjf: write eval view cache +#if 0 + if(source == DF_CfgSrc_Project) + { + B32 first = 1; + for(U64 eval_view_slot_idx = 0; + eval_view_slot_idx < df_state->eval_view_cache.slots_count; + eval_view_slot_idx += 1) + { + for(DF_EvalView *ev = df_state->eval_view_cache.slots[idx].first; + ev != &df_g_nil_eval_view && ev != 0; + ev = ev->hash_next) + { + if(first) + { + first = 0; + str8_list_push(arena, &strs, str8_lit("/// eval view state ///////////////////////////////////////////////////////////\n")); + str8_list_push(arena, &strs, str8_lit("\n")); + } + str8_list_push(arena, &strs, str8_lit("eval_view:\n")); + str8_list_push(arena, &strs, str8_lit("{\n")); + str8_list_pushf(arena, &strs, " key: (%I64x, %I64x)\n", ev->key.u64[0], ev->key.u64[1]); + for(U64 expand_slot_idx = 0; + expand_slot_idx < ev->expand_tree_table.slots_count; + expand_slot_idx += 1) + { + for(DF_ExpandNode *expand_node = ev->expand_tree_table.slots[expand_slot_idx].first; + expand_node != 0; + expand_node = expand_node->hash_next) + { + DF_ExpandKey key = expand_node->key; + B32 expanded = expand_node->expanded; + str8_list_pushf(arena, &strs, " node: ()\n"); + } + } + str8_list_push(arena, &strs, str8_lit("}\n\n")); + } + } + } +#endif + + ProfEnd(); + return strs; +} + +internal void +df_cfg_push_write_string(DF_CfgSrc src, String8 string) +{ + str8_list_push(df_state->cfg_write_arenas[src], &df_state->cfg_write_data[src], push_str8_copy(df_state->cfg_write_arenas[src], string)); +} + +//- rjf: current path + +internal String8 +df_current_path(void) +{ + return df_state->current_path; +} + +//- rjf: architecture info table lookups + +internal String8 +df_info_summary_from_string__x64(String8 string) +{ + String8 result = {0}; + { + U64 hash = df_hash_from_string__case_insensitive(string); + U64 slot_idx = hash % df_state->arch_info_x64_table_size; + DF_ArchInfoSlot *slot = &df_state->arch_info_x64_table[slot_idx]; + for(DF_ArchInfoNode *n = slot->first; n != 0; n = n->hash_next) + { + if(str8_match(n->key, string, StringMatchFlag_CaseInsensitive)) + { + result = n->val; + break; + } + } + } + return result; +} + +internal String8 +df_info_summary_from_string(Architecture arch, String8 string) +{ + String8 result = {0}; + switch(arch) + { + default:{}break; + case Architecture_x64: + { + result = df_info_summary_from_string__x64(string); + }break; + } + return result; +} + +//- rjf: entity kind cache + +internal DF_EntityList +df_query_cached_entity_list_with_kind(DF_EntityKind kind) +{ + ProfBeginFunction(); + DF_EntityListCache *cache = &df_state->kind_caches[kind]; + + // rjf: build cached list if we're out-of-date + if(cache->alloc_gen != df_state->kind_alloc_gens[kind]) + { + cache->alloc_gen = df_state->kind_alloc_gens[kind]; + if(cache->arena == 0) + { + cache->arena = arena_alloc(); + } + arena_clear(cache->arena); + cache->list = df_push_entity_list_with_kind(cache->arena, kind); + } + + // rjf: grab & return cached list + DF_EntityList result = cache->list; + ProfEnd(); + return result; +} + +//- rjf: active entity based queries + +internal DI_KeyList +df_push_active_dbgi_key_list(Arena *arena) +{ + DI_KeyList dbgis = {0}; + DF_EntityList modules = df_query_cached_entity_list_with_kind(DF_EntityKind_Module); + for(DF_EntityNode *n = modules.first; n != 0; n = n->next) + { + DF_Entity *module = n->entity; + DI_Key key = df_dbgi_key_from_module(module); + di_key_list_push(arena, &dbgis, &key); + } + return dbgis; +} + +internal DF_EntityList +df_push_active_target_list(Arena *arena) +{ + DF_EntityList active_targets = {0}; + DF_EntityList all_targets = df_query_cached_entity_list_with_kind(DF_EntityKind_Target); + for(DF_EntityNode *n = all_targets.first; n != 0; n = n->next) + { + if(n->entity->b32) + { + df_entity_list_push(arena, &active_targets, n->entity); + } + } + return active_targets; +} + +//- rjf: per-run caches + +internal CTRL_Unwind +df_query_cached_unwind_from_thread(DF_Entity *thread) +{ + Temp scratch = scratch_begin(0, 0); + CTRL_Unwind result = {0}; + if(thread->kind == DF_EntityKind_Thread) + { + U64 reg_gen = ctrl_reg_gen(); + U64 mem_gen = ctrl_mem_gen(); + DF_UnwindCache *cache = &df_state->unwind_cache; + DF_Handle handle = df_handle_from_entity(thread); + U64 hash = df_hash_from_string(str8_struct(&handle)); + U64 slot_idx = hash%cache->slots_count; + DF_UnwindCacheSlot *slot = &cache->slots[slot_idx]; + DF_UnwindCacheNode *node = 0; + for(DF_UnwindCacheNode *n = slot->first; n != 0; n = n->next) + { + if(df_handle_match(handle, n->thread)) + { + node = n; + break; + } + } + if(node == 0) + { + node = cache->free_node; + if(node != 0) + { + SLLStackPop(cache->free_node); + } + else + { + node = push_array_no_zero(df_state->arena, DF_UnwindCacheNode, 1); + } + MemoryZeroStruct(node); + DLLPushBack(slot->first, slot->last, node); + node->arena = arena_alloc(); + node->thread = handle; + } + if(node->reggen != reg_gen || + node->memgen != mem_gen) + { + CTRL_Unwind new_unwind = ctrl_unwind_from_thread(scratch.arena, df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle, os_now_microseconds()+100); + if(!(new_unwind.flags & (CTRL_UnwindFlag_Error|CTRL_UnwindFlag_Stale)) && new_unwind.frames.count != 0) + { + node->unwind = ctrl_unwind_deep_copy(node->arena, thread->arch, &new_unwind); + node->reggen = reg_gen; + node->memgen = mem_gen; + } + } + result = node->unwind; + } + scratch_end(scratch); + return result; +} + +internal U64 +df_query_cached_rip_from_thread(DF_Entity *thread) +{ + U64 result = df_query_cached_rip_from_thread_unwind(thread, 0); + return result; +} + +internal U64 +df_query_cached_rip_from_thread_unwind(DF_Entity *thread, U64 unwind_count) +{ + U64 result = 0; + if(unwind_count == 0) + { + result = ctrl_query_cached_rip_from_thread(df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle); + } + else + { + CTRL_Unwind unwind = df_query_cached_unwind_from_thread(thread); + if(unwind.frames.count != 0) + { + result = regs_rip_from_arch_block(thread->arch, unwind.frames.v[unwind_count%unwind.frames.count].regs); + } + } + return result; +} + +internal U64 +df_query_cached_tls_base_vaddr_from_process_root_rip(DF_Entity *process, U64 root_vaddr, U64 rip_vaddr) +{ + U64 result = 0; + for(U64 cache_idx = 0; cache_idx < ArrayCount(df_state->tls_base_caches); cache_idx += 1) + { + DF_RunTLSBaseCache *cache = &df_state->tls_base_caches[(df_state->tls_base_cache_gen+cache_idx)%ArrayCount(df_state->tls_base_caches)]; + if(cache_idx == 0 && cache->slots_count == 0) + { + cache->slots_count = 256; + cache->slots = push_array(cache->arena, DF_RunTLSBaseCacheSlot, cache->slots_count); + } + else if(cache->slots_count == 0) + { + break; + } + DF_Handle handle = df_handle_from_entity(process); + U64 hash = df_hash_from_seed_string(df_hash_from_string(str8_struct(&handle)), str8_struct(&rip_vaddr)); + U64 slot_idx = hash%cache->slots_count; + DF_RunTLSBaseCacheSlot *slot = &cache->slots[slot_idx]; + DF_RunTLSBaseCacheNode *node = 0; + for(DF_RunTLSBaseCacheNode *n = slot->first; n != 0; n = n->hash_next) + { + if(df_handle_match(n->process, handle) && n->root_vaddr == root_vaddr && n->rip_vaddr == rip_vaddr) + { + node = n; + break; + } + } + if(node == 0) + { + U64 tls_base_vaddr = df_tls_base_vaddr_from_process_root_rip(process, root_vaddr, rip_vaddr); + if(tls_base_vaddr != 0) + { + node = push_array(cache->arena, DF_RunTLSBaseCacheNode, 1); + SLLQueuePush_N(slot->first, slot->last, node, hash_next); + node->process = handle; + node->root_vaddr = root_vaddr; + node->rip_vaddr = rip_vaddr; + node->tls_base_vaddr = tls_base_vaddr; + } + } + if(node != 0 && node->tls_base_vaddr != 0) + { + result = node->tls_base_vaddr; + break; + } + } + return result; +} + +internal EVAL_String2NumMap * +df_query_cached_locals_map_from_dbgi_key_voff(DI_Key *dbgi_key, U64 voff) +{ + ProfBeginFunction(); + EVAL_String2NumMap *map = &eval_string2num_map_nil; + for(U64 cache_idx = 0; cache_idx < ArrayCount(df_state->locals_caches); cache_idx += 1) + { + DF_RunLocalsCache *cache = &df_state->locals_caches[(df_state->locals_cache_gen+cache_idx)%ArrayCount(df_state->locals_caches)]; + if(cache_idx == 0 && cache->table_size == 0) + { + cache->table_size = 256; + cache->table = push_array(cache->arena, DF_RunLocalsCacheSlot, cache->table_size); + } + else if(cache->table_size == 0) + { + break; + } + U64 hash = di_hash_from_key(dbgi_key); + U64 slot_idx = hash % cache->table_size; + DF_RunLocalsCacheSlot *slot = &cache->table[slot_idx]; + DF_RunLocalsCacheNode *node = 0; + for(DF_RunLocalsCacheNode *n = slot->first; n != 0; n = n->hash_next) + { + if(di_key_match(&n->dbgi_key, dbgi_key) && n->voff == voff) + { + node = n; + break; + } + } + if(node == 0) + { + DI_Scope *scope = di_scope_open(); + EVAL_String2NumMap *map = df_push_locals_map_from_dbgi_key_voff(cache->arena, scope, dbgi_key, voff); + if(map->slots_count != 0) + { + node = push_array(cache->arena, DF_RunLocalsCacheNode, 1); + node->dbgi_key = di_key_copy(cache->arena, dbgi_key); + node->voff = voff; + node->locals_map = map; + SLLQueuePush_N(slot->first, slot->last, node, hash_next); + } + di_scope_close(scope); + } + if(node != 0 && node->locals_map->slots_count != 0) + { + map = node->locals_map; + break; + } + } + ProfEnd(); + return map; +} + +internal EVAL_String2NumMap * +df_query_cached_member_map_from_dbgi_key_voff(DI_Key *dbgi_key, U64 voff) +{ + ProfBeginFunction(); + EVAL_String2NumMap *map = &eval_string2num_map_nil; + for(U64 cache_idx = 0; cache_idx < ArrayCount(df_state->member_caches); cache_idx += 1) + { + DF_RunLocalsCache *cache = &df_state->member_caches[(df_state->member_cache_gen+cache_idx)%ArrayCount(df_state->member_caches)]; + if(cache_idx == 0 && cache->table_size == 0) + { + cache->table_size = 256; + cache->table = push_array(cache->arena, DF_RunLocalsCacheSlot, cache->table_size); + } + else if(cache->table_size == 0) + { + break; + } + U64 hash = di_hash_from_key(dbgi_key); + U64 slot_idx = hash % cache->table_size; + DF_RunLocalsCacheSlot *slot = &cache->table[slot_idx]; + DF_RunLocalsCacheNode *node = 0; + for(DF_RunLocalsCacheNode *n = slot->first; n != 0; n = n->hash_next) + { + if(di_key_match(&n->dbgi_key, dbgi_key) && n->voff == voff) + { + node = n; + break; + } + } + if(node == 0) + { + DI_Scope *scope = di_scope_open(); + EVAL_String2NumMap *map = df_push_member_map_from_dbgi_key_voff(cache->arena, scope, dbgi_key, voff); + if(map->slots_count != 0) + { + node = push_array(cache->arena, DF_RunLocalsCacheNode, 1); + node->dbgi_key = di_key_copy(cache->arena, dbgi_key); + node->voff = voff; + node->locals_map = map; + SLLQueuePush_N(slot->first, slot->last, node, hash_next); + } + di_scope_close(scope); + } + if(node != 0 && node->locals_map->slots_count != 0) + { + map = node->locals_map; + break; + } + } + ProfEnd(); + return map; +} + +//- rjf: top-level command dispatch + +internal void +df_push_cmd__root(DF_CmdParams *params, DF_CmdSpec *spec) +{ + // rjf: log + if(params->os_event == 0 || params->os_event->kind != OS_EventKind_MouseMove) + { + Temp scratch = scratch_begin(0, 0); + DF_Entity *entity = df_entity_from_handle(params->entity); + log_infof("df_cmd:\n{\n", spec->info.string); + log_infof("spec: \"%S\"\n", spec->info.string); +#define HandleParamPrint(mem_name) if(!df_handle_match(df_handle_zero(), params->mem_name)) { log_infof("%s: [0x%I64x, 0x%I64x]\n", #mem_name, params->mem_name.u64[0], params->mem_name.u64[1]); } + HandleParamPrint(window); + HandleParamPrint(panel); + HandleParamPrint(dest_panel); + HandleParamPrint(prev_view); + HandleParamPrint(view); + if(!df_entity_is_nil(entity)) + { + String8 entity_name = df_display_string_from_entity(scratch.arena, entity); + log_infof("entity: \"%S\"\n", entity_name); + } + U64 idx = 0; + for(DF_HandleNode *n = params->entity_list.first; n != 0; n = n->next, idx += 1) + { + DF_Entity *entity = df_entity_from_handle(n->handle); + if(!df_entity_is_nil(entity)) + { + String8 entity_name = df_display_string_from_entity(scratch.arena, entity); + log_infof("entity_list[%I64u]: \"%S\"\n", idx, entity_name); + } + } + if(!df_cmd_spec_is_nil(params->cmd_spec)) + { + log_infof("cmd_spec: \"%S\"\n", params->cmd_spec->info.string); + } + if(params->string.size != 0) { log_infof("string: \"%S\"\n", params->string); } + if(params->file_path.size != 0) { log_infof("file_path: \"%S\"\n", params->file_path); } + if(params->text_point.line != 0) { log_infof("text_point: [line:%I64d, col:%I64d]\n", params->text_point.line, params->text_point.column); } + if(params->vaddr != 0) { log_infof("vaddr: 0x%I64x\n", params->vaddr); } + if(params->voff != 0) { log_infof("voff: 0x%I64x\n", params->voff); } + if(params->index != 0) { log_infof("index: 0x%I64x\n", params->index); } + if(params->base_unwind_index != 0) { log_infof("base_unwind_index: 0x%I64x\n", params->base_unwind_index); } + if(params->inline_unwind_index != 0){ log_infof("inline_unwind_index: 0x%I64x\n", params->inline_unwind_index); } + if(params->id != 0) { log_infof("id: 0x%I64x\n", params->id); } + if(params->os_event != 0) + { + String8 kind_string = str8_lit(""); + switch(params->os_event->kind) + { + default:{}break; + case OS_EventKind_Press: {kind_string = str8_lit("press");}break; + case OS_EventKind_Release: {kind_string = str8_lit("release");}break; + case OS_EventKind_MouseMove: {kind_string = str8_lit("mousemove");}break; + case OS_EventKind_Text: {kind_string = str8_lit("text");}break; + case OS_EventKind_Scroll: {kind_string = str8_lit("scroll");}break; + case OS_EventKind_WindowLoseFocus:{kind_string = str8_lit("losefocus");}break; + case OS_EventKind_WindowClose: {kind_string = str8_lit("closewindow");}break; + case OS_EventKind_FileDrop: {kind_string = str8_lit("filedrop");}break; + case OS_EventKind_Wakeup: {kind_string = str8_lit("wakeup");}break; + } + log_infof("os_event->kind: %S\n", kind_string); + } +#undef HandleParamPrint + log_infof("}\n\n"); + scratch_end(scratch); + } + df_cmd_list_push(df_state->root_cmd_arena, &df_state->root_cmds, params, spec); +} + +//////////////////////////////// +//~ rjf: Main Layer Top-Level Calls + +internal void +df_core_init(CmdLine *cmdln, DF_StateDeltaHistory *hist) +{ + Arena *arena = arena_alloc(); + df_state = push_array(arena, DF_State, 1); + df_state->arena = arena; + for(U64 idx = 0; idx < ArrayCount(df_state->frame_arenas); idx += 1) + { + df_state->frame_arenas[idx] = arena_alloc(); + } + df_state->root_cmd_arena = arena_alloc(); + df_state->output_log_key = hs_hash_from_data(str8_lit("df_output_log_key")); + df_state->entities_arena = arena_alloc(.reserve_size = GB(64), .commit_size = KB(64)); + df_state->entities_root = &df_g_nil_entity; + df_state->entities_base = push_array(df_state->entities_arena, DF_Entity, 0); + df_state->entities_count = 0; + df_state->ctrl_msg_arena = arena_alloc(); + df_state->ctrl_entity_store = ctrl_entity_store_alloc(); + df_state->ctrl_stop_arena = arena_alloc(); + df_state->entities_root = df_entity_alloc(0, &df_g_nil_entity, DF_EntityKind_Root); + df_state->cmd_spec_table_size = 1024; + df_state->cmd_spec_table = push_array(arena, DF_CmdSpec *, df_state->cmd_spec_table_size); + df_state->view_rule_spec_table_size = 1024; + df_state->view_rule_spec_table = push_array(arena, DF_CoreViewRuleSpec *, df_state->view_rule_spec_table_size); + df_state->seconds_til_autosave = 0.5f; + df_state->hist = hist; + + // rjf: set up initial exception filtering rules + for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)0; k < CTRL_ExceptionCodeKind_COUNT; k = (CTRL_ExceptionCodeKind)(k+1)) + { + if(ctrl_exception_code_kind_default_enable_table[k]) + { + df_state->ctrl_exception_code_filters[k/64] |= 1ull<<(k%64); + } + } + + // rjf: set up initial entities + { + DF_Entity *local_machine = df_entity_alloc(0, df_state->entities_root, DF_EntityKind_Machine); + df_entity_equip_ctrl_machine_id(local_machine, CTRL_MachineID_Local); + df_entity_equip_name(0, local_machine, str8_lit("This PC")); + } + + // rjf: register core commands + { + DF_CmdSpecInfoArray array = {df_g_core_cmd_kind_spec_info_table, ArrayCount(df_g_core_cmd_kind_spec_info_table)}; + df_register_cmd_specs(array); + } + + // rjf: register core view rules + { + DF_CoreViewRuleSpecInfoArray array = {df_g_core_view_rule_spec_info_table, ArrayCount(df_g_core_view_rule_spec_info_table)}; + df_register_core_view_rule_specs(array); + } + + // rjf: set up caches + df_state->unwind_cache.slots_count = 1024; + df_state->unwind_cache.slots = push_array(arena, DF_UnwindCacheSlot, df_state->unwind_cache.slots_count); + for(U64 idx = 0; idx < ArrayCount(df_state->tls_base_caches); idx += 1) + { + df_state->tls_base_caches[idx].arena = arena_alloc(); + } + for(U64 idx = 0; idx < ArrayCount(df_state->locals_caches); idx += 1) + { + df_state->locals_caches[idx].arena = arena_alloc(); + } + for(U64 idx = 0; idx < ArrayCount(df_state->member_caches); idx += 1) + { + df_state->member_caches[idx].arena = arena_alloc(); + } + + // rjf: set up eval view cache + df_state->eval_view_cache.slots_count = 4096; + df_state->eval_view_cache.slots = push_array(arena, DF_EvalViewSlot, df_state->eval_view_cache.slots_count); + + // rjf: set up run state + df_state->ctrl_last_run_arena = arena_alloc(); + + // rjf: set up config reading state + { + Temp scratch = scratch_begin(0, 0); + + // rjf: unpack command line arguments + String8 user_cfg_path = cmd_line_string(cmdln, str8_lit("user")); + String8 project_cfg_path = cmd_line_string(cmdln, str8_lit("project")); + if(project_cfg_path.size == 0) + { + project_cfg_path = cmd_line_string(cmdln, str8_lit("profile")); + } + { + String8 user_program_data_path = os_get_process_info()->user_program_data_path; + String8 user_data_folder = push_str8f(scratch.arena, "%S/%S", user_program_data_path, str8_lit("raddbg")); + os_make_directory(user_data_folder); + if(user_cfg_path.size == 0) + { + user_cfg_path = push_str8f(scratch.arena, "%S/default.raddbg_user", user_data_folder); + } + if(project_cfg_path.size == 0) + { + project_cfg_path = push_str8f(scratch.arena, "%S/default.raddbg_project", user_data_folder); + } + } + + // rjf: set up config path state + String8 cfg_src_paths[DF_CfgSrc_COUNT] = {user_cfg_path, project_cfg_path}; + for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) + { + df_state->cfg_path_arenas[src] = arena_alloc(); + DF_CmdParams params = df_cmd_params_zero(); + params.file_path = path_normalized_from_string(scratch.arena, cfg_src_paths[src]); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(df_g_cfg_src_load_cmd_kind_table[src])); + } + + // rjf: set up config table arena + df_state->cfg_arena = arena_alloc(); + scratch_end(scratch); + } + + // rjf: set up config write state + for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) + { + df_state->cfg_write_arenas[src] = arena_alloc(); + } + + // rjf: set up initial browse path + { + Temp scratch = scratch_begin(0, 0); + String8 current_path = os_get_current_path(scratch.arena); + String8 current_path_with_slash = push_str8f(scratch.arena, "%S/", current_path); + df_state->current_path_arena = arena_alloc(); + df_state->current_path = push_str8_copy(df_state->current_path_arena, current_path_with_slash); + scratch_end(scratch); + } + + // rjf: set up architecture info tables + df_state->arch_info_x64_table_size = 1024; + df_state->arch_info_x64_table = push_array(df_state->arena, DF_ArchInfoSlot, df_state->arch_info_x64_table_size); + for(U64 idx = 0; idx < ArrayCount(df_g_inst_table_x64); idx += 1) + { + String8 key = df_g_inst_table_x64[idx].mnemonic; + String8 val = df_g_inst_table_x64[idx].summary; + U64 hash = df_hash_from_string__case_insensitive(key); + U64 slot_idx = hash % df_state->arch_info_x64_table_size; + DF_ArchInfoSlot *slot = &df_state->arch_info_x64_table[slot_idx]; + DF_ArchInfoNode *n = push_array(df_state->arena, DF_ArchInfoNode, 1); + SLLQueuePush_N(slot->first, slot->last, n, hash_next); + n->key = key; + n->val = val; + } +} + +internal DF_CmdList +df_core_gather_root_cmds(Arena *arena) +{ + DF_CmdList cmds = {0}; + for(DF_CmdNode *n = df_state->root_cmds.first; n != 0; n = n->next) + { + df_cmd_list_push(arena, &cmds, &n->cmd.params, n->cmd.spec); + } + return cmds; +} + +internal void +df_core_begin_frame(Arena *arena, DF_CmdList *cmds, F32 dt) +{ + ProfBeginFunction(); + df_state->frame_index += 1; + arena_clear(df_frame_arena()); + df_state->dt = dt; + df_state->time_in_seconds += dt; + df_state->top_interact_regs = &df_state->base_interact_regs; + df_state->top_interact_regs->v.lines = df_line_list_copy(df_frame_arena(), &df_state->top_interact_regs->v.lines); + df_state->top_interact_regs->v.dbgi_key = di_key_copy(df_frame_arena(), &df_state->top_interact_regs->v.dbgi_key); + + //- rjf: sync with ctrl thread + ProfScope("sync with ctrl thread") + { + Temp scratch = scratch_begin(&arena, 1); + + //- rjf: grab next reggen/memgen + U64 new_mem_gen = ctrl_mem_gen(); + U64 new_reg_gen = ctrl_reg_gen(); + + //- rjf: consume & process events + CTRL_EventList events = ctrl_c2u_pop_events(scratch.arena); + ctrl_entity_store_apply_events(df_state->ctrl_entity_store, &events); + for(CTRL_EventNode *event_n = events.first; event_n != 0; event_n = event_n->next) + { + CTRL_Event *event = &event_n->v; + log_infof("ctrl_event:\n{\n"); + log_infof("kind: \"%S\"\n", ctrl_string_from_event_kind(event->kind)); + log_infof("entity_id: %u\n", event->entity_id); + switch(event->kind) + { + default:{}break; + + //- rjf: errors + + case CTRL_EventKind_Error: + { + log_user_error(event->string); + }break; + + //- rjf: starts/stops + + case CTRL_EventKind_Started: + { + df_state->ctrl_is_running = 1; + }break; + + case CTRL_EventKind_Stopped: + { + B32 should_snap = !(df_state->ctrl_soft_halt_issued); + df_state->ctrl_is_running = 0; + df_state->ctrl_soft_halt_issued = 0; + DF_Entity *stop_thread = df_entity_from_ctrl_handle(event->machine_id, event->entity); + + // rjf: gather stop info + { + arena_clear(df_state->ctrl_stop_arena); + MemoryCopyStruct(&df_state->ctrl_last_stop_event, event); + df_state->ctrl_last_stop_event.string = push_str8_copy(df_state->ctrl_stop_arena, df_state->ctrl_last_stop_event.string); + } + + // rjf: select & snap to thread causing stop + if(should_snap && stop_thread->kind == DF_EntityKind_Thread) + { + log_infof("stop_thread: \"%S\"\n", df_display_string_from_entity(scratch.arena, stop_thread)); + DF_CmdParams params = df_cmd_params_zero(); + params.entity = df_handle_from_entity(stop_thread); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectThread)); + } + + // rjf: if no stop-causing thread, and if selected thread, snap to selected + if(should_snap && df_entity_is_nil(stop_thread)) + { + DF_Entity *selected_thread = df_entity_from_handle(df_state->ctrl_ctx.thread); + if(!df_entity_is_nil(selected_thread)) + { + DF_CmdParams params = df_cmd_params_zero(); + params.entity = df_handle_from_entity(selected_thread); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FindThread)); + } + } + + // rjf: thread hit user breakpoint -> increment breakpoint hit count + if(should_snap && event->cause == CTRL_EventCause_UserBreakpoint) + { + U64 stop_thread_vaddr = ctrl_query_cached_rip_from_thread(df_state->ctrl_entity_store, stop_thread->ctrl_machine_id, stop_thread->ctrl_handle); + DF_Entity *process = df_entity_ancestor_from_kind(stop_thread, DF_EntityKind_Process); + DF_Entity *module = df_module_from_process_vaddr(process, stop_thread_vaddr); + DI_Key dbgi_key = df_dbgi_key_from_module(module); + U64 stop_thread_voff = df_voff_from_vaddr(module, stop_thread_vaddr); + DF_EntityList user_bps = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); + for(DF_EntityNode *n = user_bps.first; n != 0; n = n->next) + { + DF_Entity *bp = n->entity; + DF_Entity *symb = df_entity_child_from_kind(bp, DF_EntityKind_EntryPointName); + if(bp->flags & DF_EntityFlag_HasVAddr && bp->vaddr == stop_thread_vaddr) + { + bp->u64 += 1; + } + if(bp->flags & DF_EntityFlag_HasTextPoint) + { + DF_Entity *bp_file = df_entity_ancestor_from_kind(bp, DF_EntityKind_File); + DF_LineList lines = df_lines_from_file_line_num(scratch.arena, bp_file, bp->text_point.line); + for(DF_LineNode *n = lines.first; n != 0; n = n->next) + { + if(contains_1u64(n->v.voff_range, stop_thread_voff)) + { + bp->u64 += 1; + break; + } + } + } + if(!df_entity_is_nil(symb)) + { + U64 symb_voff = df_voff_from_dbgi_key_symbol_name(&dbgi_key, symb->name); + if(symb_voff == stop_thread_voff) + { + bp->u64 += 1; + } + } + } + } + + // rjf: exception or unexpected trap -> push error + if(event->cause == CTRL_EventCause_InterruptedByException || + event->cause == CTRL_EventCause_InterruptedByTrap) + { + DF_CmdParams params = df_cmd_params_zero(); + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + + // rjf: kill all entities which are marked to die on stop + { + DF_Entity *request = df_entity_from_id(event->msg_id); + if(df_entity_is_nil(request)) + { + for(DF_Entity *entity = df_entity_root(); + !df_entity_is_nil(entity); + entity = df_entity_rec_df_pre(entity, df_entity_root()).next) + { + if(entity->flags & DF_EntityFlag_DiesOnRunStop) + { + df_entity_mark_for_deletion(entity); + } + } + } + } + }break; + + //- rjf: entity creation/deletion + + case CTRL_EventKind_NewProc: + { + // rjf: the first process? -> clear session output & reset all bp hit counts + DF_EntityList existing_processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); + if(existing_processes.count == 0) + { + MTX_Op op = {r1u64(0, 0xffffffffffffffffull), str8_lit("[new session]\n")}; + mtx_push_op(df_state->output_log_key, op); + DF_EntityList bps = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); + for(DF_EntityNode *n = bps.first; n != 0; n = n->next) + { + n->entity->u64 = 0; + } + } + + // rjf: create entity + DF_Entity *machine = df_machine_entity_from_machine_id(event->machine_id); + DF_Entity *entity = df_entity_alloc(0, machine, DF_EntityKind_Process); + df_entity_equip_u64(entity, event->msg_id); + df_entity_equip_ctrl_machine_id(entity, event->machine_id); + df_entity_equip_ctrl_handle(entity, event->entity); + df_entity_equip_ctrl_id(entity, event->entity_id); + df_entity_equip_arch(entity, event->arch); + }break; + + case CTRL_EventKind_NewThread: + { + // rjf: create entity + DF_Entity *parent = df_entity_from_ctrl_handle(event->machine_id, event->parent); + DF_Entity *entity = df_entity_alloc(0, parent, DF_EntityKind_Thread); + df_entity_equip_ctrl_machine_id(entity, event->machine_id); + df_entity_equip_ctrl_handle(entity, event->entity); + df_entity_equip_arch(entity, event->arch); + df_entity_equip_ctrl_id(entity, event->entity_id); + df_entity_equip_stack_base(entity, event->stack_base); + df_entity_equip_tls_root(entity, event->tls_root); + df_entity_equip_vaddr(entity, event->rip_vaddr); + if(event->string.size != 0) + { + df_entity_equip_name(0, entity, event->string); + } + + // rjf: find any pending thread names correllating with this TID -> equip name if found match + { + DF_EntityList pending_thread_names = df_query_cached_entity_list_with_kind(DF_EntityKind_PendingThreadName); + for(DF_EntityNode *n = pending_thread_names.first; n != 0; n = n->next) + { + DF_Entity *pending_thread_name = n->entity; + if(event->machine_id == pending_thread_name->ctrl_machine_id && event->entity_id == pending_thread_name->ctrl_id) + { + df_entity_mark_for_deletion(pending_thread_name); + df_entity_equip_name(0, entity, pending_thread_name->name); + break; + } + } + } + + // rjf: determine index in process + U64 thread_idx_in_process = 0; + for(DF_Entity *child = parent->first; !df_entity_is_nil(child); child = child->next) + { + if(child == entity) + { + break; + } + if(child->kind == DF_EntityKind_Thread) + { + thread_idx_in_process += 1; + } + } + + // rjf: build default thread color table + Vec4F32 thread_colors[] = + { + df_rgba_from_theme_color(DF_ThemeColor_Thread0), + df_rgba_from_theme_color(DF_ThemeColor_Thread1), + df_rgba_from_theme_color(DF_ThemeColor_Thread2), + df_rgba_from_theme_color(DF_ThemeColor_Thread3), + df_rgba_from_theme_color(DF_ThemeColor_Thread4), + df_rgba_from_theme_color(DF_ThemeColor_Thread5), + df_rgba_from_theme_color(DF_ThemeColor_Thread6), + df_rgba_from_theme_color(DF_ThemeColor_Thread7), + }; + + // rjf: pick color + Vec4F32 thread_color = thread_colors[thread_idx_in_process % ArrayCount(thread_colors)]; + + // rjf: equip color + df_entity_equip_color_rgba(entity, thread_color); + + // rjf: automatically select if we don't have a selected thread + DF_Entity *selected_thread = df_entity_from_handle(df_state->ctrl_ctx.thread); + if(df_entity_is_nil(selected_thread)) + { + df_state->ctrl_ctx.thread = df_handle_from_entity(entity); + } + + // rjf: do initial snap + DF_EntityList already_existing_processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); + B32 do_initial_snap = (already_existing_processes.count == 1 && thread_idx_in_process == 0); + if(do_initial_snap) + { + DF_CmdParams params = df_cmd_params_zero(); + params.entity = df_handle_from_entity(entity); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectThread)); + } + }break; + + case CTRL_EventKind_NewModule: + { + // rjf: grab process + DF_Entity *parent = df_entity_from_ctrl_handle(event->machine_id, event->parent); + + // rjf: determine if this is the first module + B32 is_first = 0; + if(df_entity_is_nil(df_entity_child_from_kind(parent, DF_EntityKind_Module))) + { + is_first = 1; + } + + // rjf: create module entity + DF_Entity *module = df_entity_alloc(0, parent, DF_EntityKind_Module); + df_entity_equip_ctrl_machine_id(module, event->machine_id); + df_entity_equip_ctrl_handle(module, event->entity); + df_entity_equip_arch(module, event->arch); + df_entity_equip_name(0, module, event->string); + df_entity_equip_vaddr_rng(module, event->vaddr_rng); + df_entity_equip_vaddr(module, event->rip_vaddr); + df_entity_equip_timestamp(module, event->timestamp); + + // rjf: is first -> find target, equip process & module & first thread with target color + if(is_first) + { + DF_EntityList targets = df_query_cached_entity_list_with_kind(DF_EntityKind_Target); + for(DF_EntityNode *n = targets.first; n != 0; n = n->next) + { + DF_Entity *target = n->entity; + DF_Entity *exe = df_entity_child_from_kind(target, DF_EntityKind_Executable); + String8 exe_name = exe->name; + String8 exe_name_normalized = path_normalized_from_string(scratch.arena, exe_name); + String8 module_name_normalized = path_normalized_from_string(scratch.arena, module->name); + if(str8_match(exe_name_normalized, module_name_normalized, StringMatchFlag_CaseInsensitive) && + target->flags & DF_EntityFlag_HasColor) + { + DF_Entity *first_thread = df_entity_child_from_kind(parent, DF_EntityKind_Thread); + Vec4F32 rgba = df_rgba_from_entity(target); + df_entity_equip_color_rgba(parent, rgba); + df_entity_equip_color_rgba(first_thread, rgba); + df_entity_equip_color_rgba(module, rgba); + break; + } + } + } + }break; + + case CTRL_EventKind_EndProc: + { + U32 pid = event->entity_id; + DF_Entity *process = df_entity_from_ctrl_handle(event->machine_id, event->entity); + df_entity_mark_for_deletion(process); + }break; + + case CTRL_EventKind_EndThread: + { + DF_Entity *thread = df_entity_from_ctrl_handle(event->machine_id, event->entity); + df_set_thread_freeze_state(thread, 0); + df_entity_mark_for_deletion(thread); + }break; + + case CTRL_EventKind_EndModule: + { + DF_Entity *module = df_entity_from_ctrl_handle(event->machine_id, event->entity); + df_entity_mark_for_deletion(module); + }break; + + //- rjf: debug info changes + + case CTRL_EventKind_ModuleDebugInfoPathChange: + { + DF_Entity *module = df_entity_from_ctrl_handle(event->machine_id, event->entity); + DF_Entity *debug_info = df_entity_child_from_kind(module, DF_EntityKind_DebugInfoPath); + if(df_entity_is_nil(debug_info)) + { + debug_info = df_entity_alloc(0, module, DF_EntityKind_DebugInfoPath); + } + df_entity_equip_name(0, debug_info, event->string); + df_entity_equip_timestamp(debug_info, event->timestamp); + }break; + + //- rjf: debug strings + + case CTRL_EventKind_DebugString: + { + MTX_Op op = {r1u64(max_U64, max_U64), event->string}; + mtx_push_op(df_state->output_log_key, op); + }break; + + case CTRL_EventKind_ThreadName: + { + String8 string = event->string; + DF_Entity *entity = df_entity_from_ctrl_handle(event->machine_id, event->entity); + if(event->entity_id != 0) + { + entity = df_entity_from_ctrl_id(event->machine_id, event->entity_id); + } + if(df_entity_is_nil(entity)) + { + DF_Entity *process = df_entity_from_ctrl_handle(event->machine_id, event->parent); + if(!df_entity_is_nil(process)) + { + entity = df_entity_alloc(0, process, DF_EntityKind_PendingThreadName); + df_entity_equip_name(0, entity, string); + df_entity_equip_ctrl_machine_id(entity, event->machine_id); + df_entity_equip_ctrl_id(entity, event->entity_id); + } + } + if(!df_entity_is_nil(entity)) + { + df_entity_equip_name(0, entity, string); + } + }break; + + //- rjf: memory + + case CTRL_EventKind_MemReserve:{}break; + case CTRL_EventKind_MemCommit:{}break; + case CTRL_EventKind_MemDecommit:{}break; + case CTRL_EventKind_MemRelease:{}break; + } + log_infof("}\n\n"); + } + + //- rjf: clear tls base cache + if((df_state->tls_base_cache_reggen_idx != new_reg_gen || + df_state->tls_base_cache_memgen_idx != new_mem_gen) && + !df_ctrl_targets_running()) + { + df_state->tls_base_cache_gen += 1; + DF_RunTLSBaseCache *cache = &df_state->tls_base_caches[df_state->tls_base_cache_gen%ArrayCount(df_state->tls_base_caches)]; + arena_clear(cache->arena); + cache->slots_count = 0; + cache->slots = 0; + df_state->tls_base_cache_reggen_idx = new_reg_gen; + df_state->tls_base_cache_memgen_idx = new_mem_gen; + } + + //- rjf: clear locals cache + if(df_state->locals_cache_reggen_idx != new_reg_gen && + !df_ctrl_targets_running()) + { + df_state->locals_cache_gen += 1; + DF_RunLocalsCache *cache = &df_state->locals_caches[df_state->locals_cache_gen%ArrayCount(df_state->locals_caches)]; + arena_clear(cache->arena); + cache->table_size = 0; + cache->table = 0; + df_state->locals_cache_reggen_idx = new_reg_gen; + } + + //- rjf: clear members cache + if(df_state->member_cache_reggen_idx != new_reg_gen && + !df_ctrl_targets_running()) + { + df_state->member_cache_gen += 1; + DF_RunLocalsCache *cache = &df_state->member_caches[df_state->member_cache_gen%ArrayCount(df_state->member_caches)]; + arena_clear(cache->arena); + cache->table_size = 0; + cache->table = 0; + df_state->member_cache_reggen_idx = new_reg_gen; + } + + scratch_end(scratch); + } + + //- rjf: sync with di parsers + ProfScope("sync with di parsers") + { + Temp scratch = scratch_begin(&arena, 1); + DI_EventList events = di_p2u_pop_events(scratch.arena, 0); + for(DI_EventNode *n = events.first; n != 0; n = n->next) + { + DI_Event *event = &n->v; + switch(event->kind) + { + default:{}break; + case DI_EventKind_ConversionStarted: + { + DF_Entity *task = df_entity_alloc(0, df_entity_root(), DF_EntityKind_ConversionTask); + df_entity_equip_name(0, task, event->string); + }break; + case DI_EventKind_ConversionEnded: + { + DF_Entity *task = df_entity_from_name_and_kind(event->string, DF_EntityKind_ConversionTask); + if(!df_entity_is_nil(task)) + { + df_entity_mark_for_deletion(task); + } + }break; + case DI_EventKind_ConversionFailureUnsupportedFormat: + { + // DF_Entity *task = df_entity_alloc(df_entity_root(), DF_EntityKind_ConversionFail); + // df_entity_equip_name(task, event->string); + // df_entity_equip_death_timer(task, 15.f); + }break; + } + } + scratch_end(scratch); + } + + //- rjf: start/stop telemetry captures + ProfScope("start/stop telemetry captures") + { + if(!ProfIsCapturing() && DEV_telemetry_capture) + { + ProfBeginCapture("raddbg"); + } + if(ProfIsCapturing() && !DEV_telemetry_capture) + { + ProfEndCapture(); + } + } + + //- rjf: clear root level commands + { + arena_clear(df_state->root_cmd_arena); + MemoryZeroStruct(&df_state->root_cmds); + } + + //- rjf: autosave + { + df_state->seconds_til_autosave -= dt; + if(df_state->seconds_til_autosave <= 0.f) + { + DF_CmdParams params = df_cmd_params_zero(); + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_WriteUserData)); + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_WriteProjectData)); + df_state->seconds_til_autosave = 5.f; + } + } + + //- rjf: process top-level commands + ProfScope("process top-level commands") + { + Temp scratch = scratch_begin(&arena, 1); + for(DF_CmdNode *cmd_node = cmds->first; + cmd_node != 0; + cmd_node = cmd_node->next) + { + temp_end(scratch); + + // rjf: unpack command + DF_Cmd *cmd = &cmd_node->cmd; + DF_CmdParams params = cmd->params; + DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); + df_cmd_spec_counter_inc(cmd->spec); + + // rjf: process command + switch(core_cmd_kind) + { + default:{}break; + + //- rjf: command fast paths + case DF_CoreCmdKind_RunCommand: + { + DF_CmdSpec *spec = params.cmd_spec; + if(spec != cmd->spec) + { + df_cmd_spec_counter_inc(spec); + if(!(spec->info.query.flags & DF_CmdQueryFlag_Required) && + (spec->info.query.slot == DF_CmdParamSlot_Null || + df_cmd_params_has_slot(¶ms, spec->info.query.slot))) + { + df_cmd_list_push(arena, cmds, ¶ms, spec); + } + } + }break; + + //- rjf: low-level target control operations + case DF_CoreCmdKind_LaunchAndRun: + case DF_CoreCmdKind_LaunchAndInit: + { + // rjf: get list of targets to launch + DF_EntityList targets = df_entity_list_from_handle_list(scratch.arena, params.entity_list); + + // rjf: no targets => assume all active targets + if(targets.count == 0) + { + targets = df_push_active_target_list(scratch.arena); + } + + // rjf: launch + if(targets.count != 0) + { + for(DF_EntityNode *n = targets.first; n != 0; n = n->next) + { + // rjf: extract data from target + DF_Entity *target = n->entity; + String8 name = df_entity_child_from_kind(target, DF_EntityKind_Executable)->name; + String8 args = df_entity_child_from_kind(target, DF_EntityKind_Arguments)->name; + String8 path = df_entity_child_from_kind(target, DF_EntityKind_ExecutionPath)->name; + String8 entry= df_entity_child_from_kind(target, DF_EntityKind_EntryPointName)->name; + name = str8_skip_chop_whitespace(name); + args = str8_skip_chop_whitespace(args); + path = str8_skip_chop_whitespace(path); + entry = str8_skip_chop_whitespace(entry); + if(path.size == 0) + { + path = os_get_current_path(scratch.arena); + } + + // rjf: build launch options + String8List cmdln_strings = {0}; + { + str8_list_push(scratch.arena, &cmdln_strings, name); + { + U64 start_split_idx = 0; + B32 quoted = 0; + for(U64 idx = 0; idx <= args.size; idx += 1) + { + U8 byte = idx < args.size ? args.str[idx] : 0; + if(byte == '"') + { + quoted ^= 1; + } + B32 splitter_found = (!quoted && (byte == 0 || char_is_space(byte))); + if(splitter_found) + { + String8 string = str8_substr(args, r1u64(start_split_idx, idx)); + if(string.size > 0) + { + str8_list_push(scratch.arena, &cmdln_strings, string); + } + start_split_idx = idx+1; + } + } + } + } + + // rjf: push message to launch + { + CTRL_Msg msg = {CTRL_MsgKind_Launch}; + msg.path = path; + msg.cmd_line_string_list = cmdln_strings; + msg.env_inherit = 1; + MemoryCopyArray(msg.exception_code_filters, df_state->ctrl_exception_code_filters); + str8_list_push(scratch.arena, &msg.entry_points, entry); + df_push_ctrl_msg(&msg); + } + } + + // rjf: run + df_ctrl_run(DF_RunKind_Run, &df_g_nil_entity, CTRL_RunFlag_StopOnEntryPoint * (core_cmd_kind == DF_CoreCmdKind_LaunchAndInit), 0); + } + + // rjf: no targets -> error + if(targets.count == 0) + { + DF_CmdParams p = params; + p.string = str8_lit("No active targets exist; cannot launch."); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + }break; + case DF_CoreCmdKind_Kill: + { + DF_EntityList processes = df_entity_list_from_handle_list(scratch.arena, params.entity_list); + + // rjf: no processes => kill everything + if(processes.count == 0) + { + processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); + } + + // rjf: kill processes + if(processes.count != 0) + { + for(DF_EntityNode *n = processes.first; n != 0; n = n->next) + { + DF_Entity *process = n->entity; + CTRL_Msg msg = {CTRL_MsgKind_Kill}; + { + msg.exit_code = 1; + msg.machine_id = process->ctrl_machine_id; + msg.entity = process->ctrl_handle; + MemoryCopyArray(msg.exception_code_filters, df_state->ctrl_exception_code_filters); + } + df_push_ctrl_msg(&msg); + } + } + + // rjf: no processes -> error + if(processes.count == 0) + { + DF_CmdParams p = params; + p.string = str8_lit("No attached running processes exist; cannot kill."); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + }break; + case DF_CoreCmdKind_KillAll: + { + DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); + if(processes.count != 0) + { + for(DF_EntityNode *n = processes.first; n != 0; n = n->next) + { + DF_Entity *process = n->entity; + CTRL_Msg msg = {CTRL_MsgKind_Kill}; + { + msg.exit_code = 1; + msg.machine_id = process->ctrl_machine_id; + msg.entity = process->ctrl_handle; + MemoryCopyArray(msg.exception_code_filters, df_state->ctrl_exception_code_filters); + } + df_push_ctrl_msg(&msg); + } + } + if(processes.count == 0) + { + DF_CmdParams p = params; + p.string = str8_lit("No attached running processes exist; cannot kill."); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + }break; + case DF_CoreCmdKind_Detach: + { + for(DF_HandleNode *n = params.entity_list.first; n != 0; n = n->next) + { + DF_Entity *entity = df_entity_from_handle(n->handle); + if(entity->kind == DF_EntityKind_Process) + { + CTRL_Msg msg = {CTRL_MsgKind_Detach}; + msg.machine_id = entity->ctrl_machine_id; + msg.entity = entity->ctrl_handle; + MemoryCopyArray(msg.exception_code_filters, df_state->ctrl_exception_code_filters); + df_push_ctrl_msg(&msg); + } + } + }break; + case DF_CoreCmdKind_Continue: + { + B32 good_to_run = 0; + DF_EntityList machines = df_query_cached_entity_list_with_kind(DF_EntityKind_Machine); + for(DF_EntityNode *n = machines.first; n != 0; n = n->next) + { + DF_Entity *machine = n->entity; + if(!df_entity_is_frozen(machine)) + { + good_to_run = 1; + break; + } + } + if(good_to_run) + { + df_ctrl_run(DF_RunKind_Run, &df_g_nil_entity, 0, 0); + } + else + { + DF_CmdParams p = params; + p.string = str8_lit("Cannot run with all threads frozen."); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + }break; + case DF_CoreCmdKind_StepIntoInst: + case DF_CoreCmdKind_StepOverInst: + case DF_CoreCmdKind_StepIntoLine: + case DF_CoreCmdKind_StepOverLine: + case DF_CoreCmdKind_StepOut: + { + DF_Entity *thread = df_entity_from_handle(params.entity); + if(df_ctrl_targets_running()) + { + if(df_ctrl_last_run_kind() == DF_RunKind_Run) + { + DF_CmdParams p = params; + p.string = str8_lit("Must halt before stepping."); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + } + else if(df_entity_is_frozen(thread)) + { + DF_CmdParams p = params; + p.string = str8_lit("Must thaw selected thread before stepping."); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + else + { + B32 good = 1; + CTRL_TrapList traps = {0}; + switch(core_cmd_kind) + { + default: break; + case DF_CoreCmdKind_StepIntoInst: {}break; + case DF_CoreCmdKind_StepOverInst: {traps = df_trap_net_from_thread__step_over_inst(scratch.arena, thread);}break; + case DF_CoreCmdKind_StepIntoLine: {traps = df_trap_net_from_thread__step_into_line(scratch.arena, thread);}break; + case DF_CoreCmdKind_StepOverLine: {traps = df_trap_net_from_thread__step_over_line(scratch.arena, thread);}break; + case DF_CoreCmdKind_StepOut: + { + // rjf: thread => full unwind + CTRL_Unwind unwind = ctrl_unwind_from_thread(scratch.arena, df_state->ctrl_entity_store, thread->ctrl_machine_id, thread->ctrl_handle, os_now_microseconds()+10000); + + // rjf: use first unwind frame to generate trap + if(unwind.flags == 0 && unwind.frames.count > 1) + { + U64 vaddr = regs_rip_from_arch_block(thread->arch, unwind.frames.v[1].regs); + CTRL_Trap trap = {CTRL_TrapFlag_EndStepping|CTRL_TrapFlag_IgnoreStackPointerCheck, vaddr}; + ctrl_trap_list_push(scratch.arena, &traps, &trap); + } + else + { + DF_CmdParams p = params; + p.string = str8_lit("Could not find the return address of the current callstack frame successfully."); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + good = 0; + } + }break; + } + if(good && traps.count != 0) + { + df_ctrl_run(DF_RunKind_Step, thread, 0, &traps); + } + if(good && traps.count == 0) + { + df_ctrl_run(DF_RunKind_SingleStep, thread, 0, &traps); + } + } + }break; + case DF_CoreCmdKind_Halt: + if(df_ctrl_targets_running()) + { + ctrl_halt(); + }break; + case DF_CoreCmdKind_SoftHaltRefresh: + { + if(df_ctrl_targets_running()) + { + df_ctrl_run(df_state->ctrl_last_run_kind, df_entity_from_handle(df_state->ctrl_last_run_thread), df_state->ctrl_last_run_flags, &df_state->ctrl_last_run_traps); + } + }break; + case DF_CoreCmdKind_SetThreadIP: + { + DF_Entity *thread = df_entity_from_handle(params.entity); + U64 vaddr = params.vaddr; + if(thread->kind == DF_EntityKind_Thread && vaddr != 0) + { + df_set_thread_rip(thread, vaddr); + } + }break; + + //- rjf: high-level composite target control operations + case DF_CoreCmdKind_RunToLine: + { + DF_Entity *file = df_entity_from_handle(params.entity); + TxtPt point = params.text_point; + if(file->kind == DF_EntityKind_File) + { + DF_Entity *bp = df_entity_alloc(0, file, DF_EntityKind_Breakpoint); + bp->flags |= DF_EntityFlag_DiesOnRunStop; + df_entity_equip_b32(bp, 1); + df_entity_equip_txt_pt(bp, point); + df_entity_equip_cfg_src(bp, DF_CfgSrc_Transient); + DF_CmdParams p = df_cmd_params_zero(); + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Run)); + } + }break; + case DF_CoreCmdKind_RunToAddress: + { + DF_Entity *bp = df_entity_alloc(0, df_entity_root(), DF_EntityKind_Breakpoint); + bp->flags |= DF_EntityFlag_DiesOnRunStop; + df_entity_equip_b32(bp, 1); + df_entity_equip_vaddr(bp, params.vaddr); + df_entity_equip_cfg_src(bp, DF_CfgSrc_Transient); + DF_CmdParams p = df_cmd_params_zero(); + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Run)); + }break; + case DF_CoreCmdKind_Run: + { + DF_CmdParams params = df_cmd_params_zero(); + DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); + if(processes.count != 0) + { + DF_CmdParams params = df_cmd_params_zero(); + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Continue)); + } + else if(!df_ctrl_targets_running()) + { + DF_CmdParams params = df_cmd_params_zero(); + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_LaunchAndRun)); + } + }break; + case DF_CoreCmdKind_Restart: + { + // rjf: kill all + { + DF_CmdParams params = df_cmd_params_zero(); + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_KillAll)); + } + + // rjf: gather targets corresponding to all launched processes + DF_EntityList targets = {0}; + { + DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); + for(DF_EntityNode *n = processes.first; n != 0; n = n->next) + { + DF_Entity *process = n->entity; + DF_Entity *target = df_entity_from_handle(process->entity_handle); + if(!df_entity_is_nil(target)) + { + df_entity_list_push(scratch.arena, &targets, target); + } + } + } + + // rjf: re-launch targets + { + DF_CmdParams params = df_cmd_params_zero(); + params.entity_list = df_handle_list_from_entity_list(scratch.arena, targets); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_EntityList); + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_LaunchAndRun)); + } + }break; + case DF_CoreCmdKind_StepInto: + case DF_CoreCmdKind_StepOver: + { + DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); + if(processes.count != 0) + { + DF_CoreCmdKind step_cmd_kind = (core_cmd_kind == DF_CoreCmdKind_StepInto + ? DF_CoreCmdKind_StepIntoLine + : DF_CoreCmdKind_StepOverLine); + B32 prefer_dasm = params.prefer_dasm; + if(prefer_dasm) + { + step_cmd_kind = (core_cmd_kind == DF_CoreCmdKind_StepInto + ? DF_CoreCmdKind_StepIntoInst + : DF_CoreCmdKind_StepOverInst); + } + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(step_cmd_kind)); + } + else if(!df_ctrl_targets_running()) + { + DF_EntityList targets = df_push_active_target_list(scratch.arena); + DF_CmdParams p = params; + p.entity_list = df_handle_list_from_entity_list(scratch.arena, targets); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_EntityList); + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_LaunchAndInit)); + } + }break; + + //- rjf: debug control context management operations + case DF_CoreCmdKind_SelectThread: + { + MemoryZeroStruct(&df_state->ctrl_ctx); + df_state->ctrl_ctx.thread = params.entity; + }break; + case DF_CoreCmdKind_SelectUnwind: + { + DI_Scope *di_scope = di_scope_open(); + DF_Entity *thread = df_entity_from_handle(df_state->ctrl_ctx.thread); + DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); + CTRL_Unwind base_unwind = df_query_cached_unwind_from_thread(thread); + DF_Unwind rich_unwind = df_unwind_from_ctrl_unwind(scratch.arena, di_scope, process, &base_unwind); + DF_UnwindFrame *frame = df_frame_from_unwind_idxs(&rich_unwind, params.base_unwind_index, params.inline_unwind_index); + if(frame != 0) + { + df_state->ctrl_ctx.unwind_count = frame->base_unwind_idx; + df_state->ctrl_ctx.inline_unwind_count = frame->inline_unwind_idx; + } + di_scope_close(di_scope); + }break; + case DF_CoreCmdKind_UpOneFrame: + case DF_CoreCmdKind_DownOneFrame: + { + DF_CtrlCtx ctrl_ctx = df_ctrl_ctx(); + DI_Scope *di_scope = di_scope_open(); + DF_Entity *thread = df_entity_from_handle(ctrl_ctx.thread); + DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); + CTRL_Unwind base_unwind = df_query_cached_unwind_from_thread(thread); + DF_Unwind rich_unwind = df_unwind_from_ctrl_unwind(scratch.arena, di_scope, process, &base_unwind); + DF_UnwindFrame *current_frame = 0; + for(U64 idx = 0; idx < rich_unwind.frames.count; idx += 1) + { + if(rich_unwind.frames.v[idx].base_unwind_idx == ctrl_ctx.unwind_count && + rich_unwind.frames.v[idx].inline_unwind_idx == ctrl_ctx.inline_unwind_count) + { + current_frame = &rich_unwind.frames.v[idx]; + break; + } + } + if(current_frame == 0 && rich_unwind.frames.count != 0) + { + current_frame = &rich_unwind.frames.v[0]; + } + DF_UnwindFrame *next_frame = current_frame; + switch(core_cmd_kind) + { + default:{}break; + case DF_CoreCmdKind_UpOneFrame: + if(current_frame != 0 && (current_frame - rich_unwind.frames.v) > 0) + { + next_frame -= 1; + }break; + case DF_CoreCmdKind_DownOneFrame: + if(current_frame != 0 && (current_frame - rich_unwind.frames.v)+1 < rich_unwind.frames.count) + { + next_frame += 1; + }break; + } + if(next_frame != 0) + { + DF_CmdParams p = params; + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_BaseUnwindIndex); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_InlineUnwindIndex); + p.base_unwind_index = next_frame->base_unwind_idx; + p.inline_unwind_index = next_frame->base_unwind_idx; + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectUnwind)); + } + di_scope_close(di_scope); + }break; + case DF_CoreCmdKind_FreezeThread: + case DF_CoreCmdKind_ThawThread: + case DF_CoreCmdKind_FreezeProcess: + case DF_CoreCmdKind_ThawProcess: + case DF_CoreCmdKind_FreezeMachine: + case DF_CoreCmdKind_ThawMachine: + { + DF_CoreCmdKind disptch_kind = ((core_cmd_kind == DF_CoreCmdKind_FreezeThread || + core_cmd_kind == DF_CoreCmdKind_FreezeProcess || + core_cmd_kind == DF_CoreCmdKind_FreezeMachine) + ? DF_CoreCmdKind_FreezeEntity + : DF_CoreCmdKind_ThawEntity); + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(disptch_kind)); + }break; + case DF_CoreCmdKind_FreezeLocalMachine: + { + CTRL_MachineID machine_id = CTRL_MachineID_Local; + DF_CmdParams params = df_cmd_params_zero(); + params.entity = df_handle_from_entity(df_machine_entity_from_machine_id(machine_id)); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FreezeMachine)); + }break; + case DF_CoreCmdKind_ThawLocalMachine: + { + CTRL_MachineID machine_id = CTRL_MachineID_Local; + DF_CmdParams params = df_cmd_params_zero(); + params.entity = df_handle_from_entity(df_machine_entity_from_machine_id(machine_id)); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_ThawMachine)); + }break; + + //- rjf: undo/redo + case DF_CoreCmdKind_Undo: + { + df_state_delta_history_wind(df_state->hist, Side_Min); + }break; + case DF_CoreCmdKind_Redo: + { + df_state_delta_history_wind(df_state->hist, Side_Max); + }break; + + //- rjf: files + case DF_CoreCmdKind_SetCurrentPath: + { + arena_clear(df_state->current_path_arena); + df_state->current_path = push_str8_copy(df_state->current_path_arena, params.file_path); + }break; + case DF_CoreCmdKind_Open: + { + String8 path = path_normalized_from_string(scratch.arena, params.file_path); + if(path.size == 0) + { + DF_CmdParams p = params; + p.string = str8_lit("File name not specified."); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + else + { + DF_Entity *loaded_file = df_entity_from_path(path, DF_EntityFromPathFlag_OpenAsNeeded|DF_EntityFromPathFlag_OpenMissing); + if(loaded_file->flags & DF_EntityFlag_IsMissing) + { + DF_CmdParams p = params; + p.string = push_str8f(scratch.arena, "Could not load \"%S\".", path); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + } + }break; + + //- rjf: config path saving/loading/applying + case DF_CoreCmdKind_OpenRecentProject: + { + DF_Entity *entity = df_entity_from_handle(params.entity); + if(entity->kind == DF_EntityKind_RecentProject) + { + DF_CmdParams p = df_cmd_params_zero(); + p.file_path = entity->name; + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_OpenProject)); + } + }break; + case DF_CoreCmdKind_OpenUser: + case DF_CoreCmdKind_OpenProject: + { + B32 load_cfg[DF_CfgSrc_COUNT] = {0}; + for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) + { + load_cfg[src] = (core_cmd_kind == df_g_cfg_src_load_cmd_kind_table[src]); + } + + //- rjf: normalize path + String8 new_path = path_normalized_from_string(scratch.arena, params.file_path); + + //- rjf: path -> data + FileProperties props = {0}; + String8 data = {0}; + { + OS_Handle file = os_file_open(OS_AccessFlag_ShareRead|OS_AccessFlag_Read, new_path); + props = os_properties_from_file(file); + data = os_string_from_file_range(scratch.arena, file, r1u64(0, props.size)); + os_file_close(file); + } + + //- rjf: investigate file path/data + B32 file_is_okay = 1; + if(props.modified != 0 && data.size != 0 && !str8_match(str8_prefix(data, 9), str8_lit("// raddbg"), 0)) + { + file_is_okay = 0; + } + + //- rjf: set new config paths + if(file_is_okay) + { + for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) + { + if(load_cfg[src]) + { + arena_clear(df_state->cfg_path_arenas[src]); + df_state->cfg_paths[src] = push_str8_copy(df_state->cfg_path_arenas[src], new_path); + } + } + } + + //- rjf: get config files + DF_Entity *cfg_files[DF_CfgSrc_COUNT] = {0}; + if(file_is_okay) + { + for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) + { + String8 path = df_cfg_path_from_src(src); + cfg_files[src] = df_entity_from_path(path, DF_EntityFromPathFlag_OpenMissing|DF_EntityFromPathFlag_OpenAsNeeded); + } + } + + //- rjf: load files + String8 cfg_data[DF_CfgSrc_COUNT] = {0}; + U64 cfg_timestamps[DF_CfgSrc_COUNT] = {0}; + if(file_is_okay) + { + for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) + { + DF_Entity *file_entity = cfg_files[src]; + String8 path = df_full_path_from_entity(scratch.arena, file_entity); + OS_Handle file = os_file_open(OS_AccessFlag_ShareRead|OS_AccessFlag_Read, path); + FileProperties props = os_properties_from_file(file); + String8 data = os_string_from_file_range(scratch.arena, file, r1u64(0, props.size)); + if(data.size != 0) + { + cfg_data[src] = data; + cfg_timestamps[src] = props.modified; + } + os_file_close(file); + } + } + + //- rjf: determine if we need to save config + B32 cfg_save[DF_CfgSrc_COUNT] = {0}; + if(file_is_okay) + { + for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) + { + cfg_save[src] = (load_cfg[src] && cfg_files[src]->flags & DF_EntityFlag_IsMissing); + } + } + + //- rjf: determine if we need to reload config + B32 cfg_load[DF_CfgSrc_COUNT] = {0}; + B32 cfg_load_any = 0; + if(file_is_okay) + { + for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) + { + cfg_load[src] = (load_cfg[src] && ((cfg_save[src] == 0 && df_state->cfg_cached_timestamp[src] != cfg_timestamps[src]) || cfg_files[src]->timestamp == 0)); + cfg_load_any = cfg_load_any || cfg_load[src]; + } + } + + //- rjf: load => build new config table + if(cfg_load_any) + { + arena_clear(df_state->cfg_arena); + MemoryZeroStruct(&df_state->cfg_table); + for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) + { + df_cfg_table_push_unparsed_string(df_state->cfg_arena, &df_state->cfg_table, cfg_data[src], src); + } + } + + //- rjf: load => dispatch apply + // + // NOTE(rjf): must happen before `save`. we need to create a default before saving, which + // occurs in the 'apply' path. + // + if(file_is_okay) + { + for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) + { + if(cfg_load[src]) + { + DF_CoreCmdKind cmd_kind = df_g_cfg_src_apply_cmd_kind_table[src]; + DF_CmdParams params = df_cmd_params_zero(); + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(cmd_kind)); + df_state->cfg_cached_timestamp[src] = cfg_timestamps[src]; + } + } + } + + //- rjf: save => dispatch write + if(file_is_okay) + { + for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) + { + if(cfg_save[src]) + { + DF_CoreCmdKind cmd_kind = df_g_cfg_src_write_cmd_kind_table[src]; + DF_CmdParams params = df_cmd_params_zero(); + df_cmd_list_push(arena, cmds, ¶ms, df_cmd_spec_from_core_cmd_kind(cmd_kind)); + } + } + } + + //- rjf: bad file -> alert user + if(!file_is_okay) + { + DF_CmdParams p = params; + p.string = push_str8f(scratch.arena, "\"%S\" appears to refer to an existing file which is not a RADDBG config file. This would overwrite the file.", new_path); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + }break; + + //- rjf: loading/applying stateful config changes + case DF_CoreCmdKind_ApplyUserData: + case DF_CoreCmdKind_ApplyProjectData: + { + DF_CfgTable *table = df_cfg_table(); + + //- rjf: get config source + DF_CfgSrc src = DF_CfgSrc_User; + for(DF_CfgSrc s = (DF_CfgSrc)0; s < DF_CfgSrc_COUNT; s = (DF_CfgSrc)(s+1)) + { + if(core_cmd_kind == df_g_cfg_src_apply_cmd_kind_table[s]) + { + src = s; + break; + } + } + + //- rjf: get paths + String8 cfg_path = df_cfg_path_from_src(src); + String8 cfg_folder = str8_chop_last_slash(cfg_path); + + //- rjf: keep track of recent projects + if(src == DF_CfgSrc_Project) + { + DF_Entity *recent_project = df_entity_from_name_and_kind(cfg_path, DF_EntityKind_RecentProject); + if(df_entity_is_nil(recent_project)) + { + recent_project = df_entity_alloc(0, df_entity_root(), DF_EntityKind_RecentProject); + df_entity_equip_name(0, recent_project, cfg_path); + df_entity_equip_cfg_src(recent_project, DF_CfgSrc_User); + } + } + + //- rjf: eliminate all existing entities + { + DF_EntityList rps = df_query_cached_entity_list_with_kind(DF_EntityKind_RecentProject); + for(DF_EntityNode *n = rps.first; n != 0; n = n->next) + { + if(n->entity->cfg_src == src) + { + df_entity_mark_for_deletion(n->entity); + } + } + DF_EntityList targets = df_query_cached_entity_list_with_kind(DF_EntityKind_Target); + for(DF_EntityNode *n = targets.first; n != 0; n = n->next) + { + if(n->entity->cfg_src == src) + { + df_entity_mark_for_deletion(n->entity); + } + } + DF_EntityList bps = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); + for(DF_EntityNode *n = bps.first; n != 0; n = n->next) + { + if(n->entity->cfg_src == src) + { + df_entity_mark_for_deletion(n->entity); + } + } + DF_EntityList pins = df_query_cached_entity_list_with_kind(DF_EntityKind_WatchPin); + for(DF_EntityNode *n = pins.first; n != 0; n = n->next) + { + if(n->entity->cfg_src == src) + { + df_entity_mark_for_deletion(n->entity); + } + } + DF_EntityList links = df_query_cached_entity_list_with_kind(DF_EntityKind_OverrideFileLink); + for(DF_EntityNode *n = links.first; n != 0; n = n->next) + { + if(n->entity->cfg_src == src) + { + df_entity_mark_for_deletion(n->entity); + } + } + } + + //- rjf: apply recent projects + DF_CfgVal *recent_projects = df_cfg_val_from_string(table, str8_lit("recent_project")); + for(DF_CfgNode *rp = recent_projects->first; + rp != &df_g_nil_cfg_node; + rp = rp->next) + { + if(rp->source == src) + { + String8 path_saved = rp->first->string; + String8 path_absolute = path_absolute_dst_from_relative_dst_src(scratch.arena, path_saved, cfg_folder); + DF_Entity *existing = df_entity_from_name_and_kind(path_absolute, DF_EntityKind_RecentProject); + if(df_entity_is_nil(existing)) + { + DF_Entity *rp_ent = df_entity_alloc(0, df_entity_root(), DF_EntityKind_RecentProject); + df_entity_equip_cfg_src(rp_ent, src); + df_entity_equip_name(0, rp_ent, path_absolute); + } + } + } + + //- rjf: apply targets + DF_CfgVal *targets = df_cfg_val_from_string(table, str8_lit("target")); + { + B32 cmd_line_target_present = 0; + { + DF_EntityList existing_target_entities = df_query_cached_entity_list_with_kind(DF_EntityKind_Target); + for(DF_EntityNode *n = existing_target_entities.first; n != 0; n = n->next) + { + DF_Entity *target = n->entity; + if(target->cfg_src == DF_CfgSrc_CommandLine && target->b32) + { + cmd_line_target_present = 1; + } + } + } + for(DF_CfgNode *target = targets->first; + target != &df_g_nil_cfg_node; + target = target->next) + { + if(target->source == src) + { + DF_CfgNode *label_cfg = df_cfg_node_child_from_string(target, str8_lit("label"), StringMatchFlag_CaseInsensitive); + DF_CfgNode *exe_cfg = df_cfg_node_child_from_string(target, str8_lit("exe"), StringMatchFlag_CaseInsensitive); + if(exe_cfg == &df_g_nil_cfg_node) + { + exe_cfg = df_cfg_node_child_from_string(target, str8_lit("name"), StringMatchFlag_CaseInsensitive); + } + DF_CfgNode *args_cfg = df_cfg_node_child_from_string(target, str8_lit("arguments"), StringMatchFlag_CaseInsensitive); + DF_CfgNode *wdir_cfg = df_cfg_node_child_from_string(target, str8_lit("working_directory"), StringMatchFlag_CaseInsensitive); + DF_CfgNode *entry_cfg = df_cfg_node_child_from_string(target, str8_lit("entry_point"), StringMatchFlag_CaseInsensitive); + DF_CfgNode *active_cfg = df_cfg_node_child_from_string(target, str8_lit("active"), StringMatchFlag_CaseInsensitive); + Vec4F32 hsva = df_hsva_from_cfg_node(target); + U64 is_active_u64 = 0; + if(!cmd_line_target_present) + { + try_u64_from_str8_c_rules(active_cfg->first->string, &is_active_u64); + } + DF_Entity *target__ent = df_entity_alloc(0, df_entity_root(), DF_EntityKind_Target); + DF_Entity *exe__ent = df_entity_alloc(0, target__ent, DF_EntityKind_Executable); + DF_Entity *args__ent = df_entity_alloc(0, target__ent, DF_EntityKind_Arguments); + DF_Entity *path__ent = df_entity_alloc(0, target__ent, DF_EntityKind_ExecutionPath); + DF_Entity *entry__ent = df_entity_alloc(0, target__ent, DF_EntityKind_EntryPointName); + String8 saved_label = label_cfg->first->string; + String8 saved_exe = exe_cfg->first->string; + String8 saved_exe_absolute = path_absolute_dst_from_relative_dst_src(scratch.arena, saved_exe, cfg_folder); + String8 saved_wdir = wdir_cfg->first->string; + String8 saved_wdir_absolute = path_absolute_dst_from_relative_dst_src(scratch.arena, saved_wdir, cfg_folder); + String8 saved_entry_point = entry_cfg->first->string; + String8 saved_label_raw = df_cfg_raw_from_escaped_string(scratch.arena, saved_label); + String8 saved_entry_raw = df_cfg_raw_from_escaped_string(scratch.arena, saved_entry_point); + String8 saved_args_raw = df_cfg_raw_from_escaped_string(scratch.arena, args_cfg->first->string); + df_entity_equip_b32(target__ent, active_cfg != &df_g_nil_cfg_node ? !!is_active_u64 : 1); + df_entity_equip_name(0, target__ent, saved_label_raw); + df_entity_equip_name(0, exe__ent, saved_exe_absolute); + df_entity_equip_name(0, args__ent, saved_args_raw); + df_entity_equip_name(0, path__ent, saved_wdir_absolute); + df_entity_equip_name(0, entry__ent, saved_entry_raw); + df_entity_equip_cfg_src(target__ent, src); + if(!memory_is_zero(&hsva, sizeof(hsva))) + { + df_entity_equip_color_hsva(target__ent, hsva); + } + } + } + } + + //- rjf: apply path maps + DF_CfgVal *path_maps = df_cfg_val_from_string(table, str8_lit("file_path_map")); + for(DF_CfgNode *map = path_maps->first; + map != &df_g_nil_cfg_node; + map = map->next) + { + if(map->source == src) + { + DF_CfgNode *src_cfg = df_cfg_node_child_from_string(map, str8_lit("source_path"), StringMatchFlag_CaseInsensitive); + DF_CfgNode *dst_cfg = df_cfg_node_child_from_string(map, str8_lit("dest_path"), StringMatchFlag_CaseInsensitive); + String8 src_path = src_cfg->first->string; + String8 dst_path = dst_cfg->first->string; + DF_Entity *link_loc_entity = df_entity_from_path(src_path, DF_EntityFromPathFlag_OpenAsNeeded|DF_EntityFromPathFlag_OpenMissing); + DF_Entity *link_entity = df_entity_alloc(0, link_loc_entity->parent, DF_EntityKind_OverrideFileLink); + DF_Entity *link_dst_entity = df_entity_from_path(dst_path, DF_EntityFromPathFlag_OpenAsNeeded|DF_EntityFromPathFlag_OpenMissing); + df_entity_equip_name(0, link_entity, str8_skip_last_slash(src_path)); + df_entity_equip_entity_handle(link_entity, df_handle_from_entity(link_dst_entity)); + df_entity_equip_cfg_src(link_entity, src); + } + } + + //- rjf: apply auto view rules + DF_CfgVal *avrs = df_cfg_val_from_string(table, str8_lit("auto_view_rule")); + for(DF_CfgNode *map = avrs->first; + map != &df_g_nil_cfg_node; + map = map->next) + { + if(map->source == src) + { + DF_CfgNode *src_cfg = df_cfg_node_child_from_string(map, str8_lit("type"), StringMatchFlag_CaseInsensitive); + DF_CfgNode *dst_cfg = df_cfg_node_child_from_string(map, str8_lit("view_rule"), StringMatchFlag_CaseInsensitive); + String8 type = src_cfg->first->string; + String8 view_rule = dst_cfg->first->string; + type = df_cfg_raw_from_escaped_string(scratch.arena, type); + view_rule = df_cfg_raw_from_escaped_string(scratch.arena, view_rule); + DF_Entity *map_entity = df_entity_alloc(0, df_entity_root(), DF_EntityKind_AutoViewRule); + DF_Entity *src_entity = df_entity_alloc(0, map_entity, DF_EntityKind_Source); + DF_Entity *dst_entity = df_entity_alloc(0, map_entity, DF_EntityKind_Dest); + df_entity_equip_name(0, src_entity, type); + df_entity_equip_name(0, dst_entity, view_rule); + df_entity_equip_cfg_src(map_entity, src); + } + } + + //- rjf: apply breakpoints + DF_CfgVal *bps = df_cfg_val_from_string(table, str8_lit("breakpoint")); + for(DF_CfgNode *bp = bps->first; + bp != &df_g_nil_cfg_node; + bp = bp->next) + { + if(bp->source != src) + { + continue; + } + + // rjf: get metadata + Vec4F32 hsva = df_hsva_from_cfg_node(bp); + + // rjf: get nodes encoding location info + B32 is_enabled = 1; + DF_CfgNode *line_cfg = &df_g_nil_cfg_node; + DF_CfgNode *addr_cfg = &df_g_nil_cfg_node; + DF_CfgNode *symb_cfg = &df_g_nil_cfg_node; + DF_CfgNode *labl_cfg = &df_g_nil_cfg_node; + for(DF_CfgNode *child = bp->first; child != &df_g_nil_cfg_node; child = child->next) + { + if(child->flags & DF_CfgNodeFlag_Identifier && str8_match(child->string, str8_lit("line"), StringMatchFlag_CaseInsensitive)) + { + line_cfg = child; + } + if(child->flags & DF_CfgNodeFlag_Identifier && str8_match(child->string, str8_lit("addr"), StringMatchFlag_CaseInsensitive)) + { + addr_cfg = child; + } + if(child->flags & DF_CfgNodeFlag_Identifier && str8_match(child->string, str8_lit("symbol"), StringMatchFlag_CaseInsensitive)) + { + symb_cfg = child; + } + else if(child->flags & DF_CfgNodeFlag_Identifier && str8_match(child->string, str8_lit("label"), StringMatchFlag_CaseInsensitive)) + { + labl_cfg = child; + } + else if(child->flags & DF_CfgNodeFlag_Identifier && str8_match(child->string, str8_lit("enabled"), StringMatchFlag_CaseInsensitive)) + { + U64 is_enabled_u64 = 0; + try_u64_from_str8_c_rules(child->first->string, &is_enabled_u64); + is_enabled = (B32)is_enabled_u64; + } + } + + // rjf: extract textual location bp info + DF_Entity *bp_parent_ent = df_entity_root(); + TxtPt pt = {0}; + if(line_cfg != &df_g_nil_cfg_node) + { + DF_CfgNode *file = line_cfg->first; + DF_CfgNode *line = file->first; + U64 line_num = 0; + if(try_u64_from_str8_c_rules(line->string, &line_num)) + { + String8 saved_path = file->string; + String8 saved_path_absolute = path_absolute_dst_from_relative_dst_src(scratch.arena, saved_path, cfg_folder); + bp_parent_ent = df_entity_from_path(saved_path_absolute, DF_EntityFromPathFlag_All); + pt = txt_pt((S64)line_num, 1); + } + } + + // rjf: get condition info + DF_CfgNode *cond_cfg = df_cfg_node_child_from_string(bp, str8_lit("condition"), StringMatchFlag_CaseInsensitive); + + // rjf: build entity + { + DF_Entity *bp_ent = df_entity_alloc(0, bp_parent_ent, DF_EntityKind_Breakpoint); + df_entity_equip_b32(bp_ent, is_enabled); + df_entity_equip_cfg_src(bp_ent, src); + if(pt.line != 0) + { + df_entity_equip_txt_pt(bp_ent, pt); + } + if(addr_cfg != &df_g_nil_cfg_node) + { + U64 u64 = 0; + try_u64_from_str8_c_rules(addr_cfg->first->string, &u64); + df_entity_equip_vaddr(bp_ent, u64); + } + if(symb_cfg != &df_g_nil_cfg_node) + { + String8 symb_raw = df_cfg_raw_from_escaped_string(scratch.arena, symb_cfg->first->string); + DF_Entity *symb = df_entity_alloc(0, bp_ent, DF_EntityKind_EntryPointName); + df_entity_equip_name(0, symb, symb_raw); + } + if(labl_cfg->string.size != 0) + { + String8 label_raw = df_cfg_raw_from_escaped_string(scratch.arena, labl_cfg->string); + df_entity_equip_name(0, bp_ent, label_raw); + } + if(!memory_is_zero(&hsva, sizeof(hsva))) + { + df_entity_equip_color_hsva(bp_ent, hsva); + } + if(cond_cfg->first->string.size != 0) + { + String8 cond_raw = df_cfg_raw_from_escaped_string(scratch.arena, cond_cfg->first->string); + DF_Entity *cond = df_entity_alloc(0, bp_ent, DF_EntityKind_Condition); + df_entity_equip_name(0, cond, cond_raw); + } + } + } + + //- rjf: apply watch pins + DF_CfgVal *pins = df_cfg_val_from_string(table, str8_lit("watch_pin")); + for(DF_CfgNode *pin = pins->first; + pin != &df_g_nil_cfg_node; + pin = pin->next) + { + if(pin->source != src) + { + continue; + } + Vec4F32 hsva = df_hsva_from_cfg_node(pin); + String8 string = df_string_from_cfg_node_key(pin, str8_lit("expression"), StringMatchFlag_CaseInsensitive); + String8 string_raw = df_cfg_raw_from_escaped_string(scratch.arena, string); + DF_CfgNode *line_cfg = df_cfg_node_child_from_string(pin, str8_lit("line"), StringMatchFlag_CaseInsensitive); + DF_CfgNode *addr_cfg = df_cfg_node_child_from_string(pin, str8_lit("addr"), StringMatchFlag_CaseInsensitive); + DF_Entity *pin_parent_ent = df_entity_root(); + TxtPt pt = {0}; + if(line_cfg != &df_g_nil_cfg_node) + { + String8 saved_path = line_cfg->first->string; + String8 line_num_string = line_cfg->first->first->string; + String8 saved_path_absolute = path_absolute_dst_from_relative_dst_src(scratch.arena, saved_path, cfg_folder); + pin_parent_ent = df_entity_from_path(saved_path_absolute, DF_EntityFromPathFlag_All); + U64 line_num = 0; + if(try_u64_from_str8_c_rules(line_num_string, &line_num)) + { + if(line_num != 0) + { + pt = txt_pt((S64)line_num, 1); + } + } + } + U64 vaddr = 0; + if(addr_cfg != &df_g_nil_cfg_node) + { + try_u64_from_str8_c_rules(addr_cfg->first->string, &vaddr); + } + DF_Entity *pin_ent = df_entity_alloc(0, pin_parent_ent, DF_EntityKind_WatchPin); + df_entity_equip_cfg_src(pin_ent, src); + df_entity_equip_name(0, pin_ent, string_raw); + if(!memory_is_zero(&hsva, sizeof(hsva))) + { + df_entity_equip_color_hsva(pin_ent, hsva); + } + if(pt.line != 0) + { + df_entity_equip_txt_pt(pin_ent, pt); + } + if(vaddr != 0) + { + df_entity_equip_vaddr(pin_ent, vaddr); + } + } + + //- rjf: apply exception code filters + DF_CfgVal *filter_tables = df_cfg_val_from_string(table, str8_lit("exception_code_filters")); + for(DF_CfgNode *table = filter_tables->first; + table != &df_g_nil_cfg_node; + table = table->next) + { + for(DF_CfgNode *rule = table->first; + rule != &df_g_nil_cfg_node; + rule = rule->next) + { + String8 name = rule->string; + String8 val_string = rule->first->string; + U64 val = 0; + if(try_u64_from_str8_c_rules(val_string, &val)) + { + CTRL_ExceptionCodeKind kind = CTRL_ExceptionCodeKind_Null; + for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)(CTRL_ExceptionCodeKind_Null+1); + k < CTRL_ExceptionCodeKind_COUNT; + k = (CTRL_ExceptionCodeKind)(k+1)) + { + if(str8_match(name, ctrl_exception_code_kind_lowercase_code_string_table[k], 0)) + { + kind = k; + break; + } + } + if(kind != CTRL_ExceptionCodeKind_Null) + { + if(val) + { + df_state->ctrl_exception_code_filters[kind/64] |= (1ull<<(kind%64)); + } + else + { + df_state->ctrl_exception_code_filters[kind/64] &= ~(1ull<<(kind%64)); + } + } + } + } + } + }break; + + //- rjf: writing config changes + case DF_CoreCmdKind_WriteUserData: + case DF_CoreCmdKind_WriteProjectData: + { + DF_CfgSrc src = DF_CfgSrc_User; + for(DF_CfgSrc s = (DF_CfgSrc)0; s < DF_CfgSrc_COUNT; s = (DF_CfgSrc)(s+1)) + { + if(core_cmd_kind == df_g_cfg_src_write_cmd_kind_table[s]) + { + src = s; + break; + } + } + arena_clear(df_state->cfg_write_arenas[src]); + MemoryZeroStruct(&df_state->cfg_write_data[src]); + String8 path = df_cfg_path_from_src(src); + String8List strs = df_cfg_strings_from_core(scratch.arena, path, src); + String8 header = push_str8f(scratch.arena, "// raddbg %s file\n\n", df_g_cfg_src_string_table[src].str); + str8_list_push_front(scratch.arena, &strs, header); + String8 data = str8_list_join(scratch.arena, &strs, 0); + df_state->cfg_write_issued[src] = 1; + df_cfg_push_write_string(src, data); + }break; + + //- rjf: override file links + case DF_CoreCmdKind_SetFileOverrideLinkSrc: + case DF_CoreCmdKind_SetFileOverrideLinkDst: + { + // rjf: unpack args + DF_Entity *map = df_entity_from_handle(params.entity); + String8 path = path_normalized_from_string(scratch.arena, params.file_path); + String8 path_folder = str8_chop_last_slash(path); + String8 path_file = str8_skip_last_slash(path); + + // rjf: src -> move map & commit name; dst -> open destination file & refer to it in map + switch(core_cmd_kind) + { + default:{}break; + case DF_CoreCmdKind_SetFileOverrideLinkSrc: + { + DF_Entity *map_parent = (params.file_path.size != 0) ? df_entity_from_path(path_folder, DF_EntityFromPathFlag_OpenAsNeeded|DF_EntityFromPathFlag_OpenMissing) : df_entity_root(); + if(df_entity_is_nil(map)) + { + map = df_entity_alloc(0, map_parent, DF_EntityKind_OverrideFileLink); + } + else + { + df_entity_change_parent(0, map, map->parent, map_parent); + } + df_entity_equip_name(0, map, path_file); + }break; + case DF_CoreCmdKind_SetFileOverrideLinkDst: + { + if(df_entity_is_nil(map)) + { + map = df_entity_alloc(0, df_entity_root(), DF_EntityKind_OverrideFileLink); + } + DF_Entity *map_dst_entity = &df_g_nil_entity; + if(params.file_path.size != 0) + { + map_dst_entity = df_entity_from_path(path, DF_EntityFromPathFlag_All); + } + df_entity_equip_entity_handle(map, df_handle_from_entity(map_dst_entity)); + }break; + } + + // rjf: empty src/dest -> delete + if(!df_entity_is_nil(map) && map->name.size == 0 && df_entity_is_nil(df_entity_from_handle(map->entity_handle))) + { + df_entity_mark_for_deletion(map); + } + }break; + case DF_CoreCmdKind_SetFileReplacementPath: + { + // NOTE(rjf): + // + // C:/foo/bar/baz.c + // D:/foo/bar/baz.c + // -> override C: -> D: + // + // C:/1/2/foo/bar.c + // C:/2/3/foo/bar.c + // -> override C:/1/2 -> C:2/3 + // + // C:/foo/bar/baz.c + // D:/1/2/3.c + // -> override C:/foo/bar/baz.c -> D:/1/2/3.c + + //- rjf: grab src file & chosen replacement + DF_Entity *file = df_entity_from_handle(params.entity); + DF_Entity *replacement = df_entity_from_path(params.file_path, DF_EntityFromPathFlag_OpenAsNeeded|DF_EntityFromPathFlag_OpenMissing); + + //- rjf: find + DF_Entity *first_diff_src = file; + DF_Entity *first_diff_dst = replacement; + for(;!df_entity_is_nil(first_diff_src) && !df_entity_is_nil(first_diff_dst);) + { + if(!str8_match(first_diff_src->name, first_diff_dst->name, StringMatchFlag_CaseInsensitive) || + first_diff_src->parent->kind != DF_EntityKind_File || + first_diff_src->parent->parent->kind != DF_EntityKind_File || + first_diff_dst->parent->kind != DF_EntityKind_File || + first_diff_dst->parent->parent->kind != DF_EntityKind_File) + { + break; + } + first_diff_src = first_diff_src->parent; + first_diff_dst = first_diff_dst->parent; + } + + //- rjf: override first different + if(!df_entity_is_nil(first_diff_src) && !df_entity_is_nil(first_diff_dst)) + { + DF_Entity *link = df_entity_child_from_name_and_kind(first_diff_src->parent, first_diff_src->name, DF_EntityKind_OverrideFileLink); + if(df_entity_is_nil(link)) + { + link = df_entity_alloc(0, first_diff_src->parent, DF_EntityKind_OverrideFileLink); + df_entity_equip_name(0, link, first_diff_src->name); + } + df_entity_equip_entity_handle(link, df_handle_from_entity(first_diff_dst)); + } + }break; + + //- rjf: auto view rules + case DF_CoreCmdKind_SetAutoViewRuleType: + case DF_CoreCmdKind_SetAutoViewRuleViewRule: + { + DF_Entity *map = df_entity_from_handle(params.entity); + if(df_entity_is_nil(map)) + { + map = df_entity_alloc(df_state_delta_history(), df_entity_root(), DF_EntityKind_AutoViewRule); + df_entity_equip_cfg_src(map, DF_CfgSrc_Project); + } + DF_Entity *src = df_entity_child_from_kind(map, DF_EntityKind_Source); + if(df_entity_is_nil(src)) + { + src = df_entity_alloc(df_state_delta_history(), map, DF_EntityKind_Source); + } + DF_Entity *dst = df_entity_child_from_kind(map, DF_EntityKind_Dest); + if(df_entity_is_nil(dst)) + { + dst = df_entity_alloc(df_state_delta_history(), map, DF_EntityKind_Dest); + } + if(map->kind == DF_EntityKind_AutoViewRule) + { + DF_Entity *edit_child = (core_cmd_kind == DF_CoreCmdKind_SetAutoViewRuleType ? src : dst); + df_entity_equip_name(df_state_delta_history(), edit_child, params.string); + } + if(src->name.size == 0 && dst->name.size == 0) + { + df_entity_mark_for_deletion(map); + } + { + DF_AutoViewRuleMapCache *cache = &df_state->auto_view_rule_cache; + if(cache->arena == 0) + { + cache->arena = arena_alloc(); + } + arena_clear(cache->arena); + cache->slots_count = 1024; + cache->slots = push_array(cache->arena, DF_AutoViewRuleSlot, cache->slots_count); + DF_EntityList maps = df_query_cached_entity_list_with_kind(DF_EntityKind_AutoViewRule); + for(DF_EntityNode *n = maps.first; n != 0; n = n->next) + { + DF_Entity *map = n->entity; + DF_Entity *src = df_entity_child_from_kind(map, DF_EntityKind_Source); + DF_Entity *dst = df_entity_child_from_kind(map, DF_EntityKind_Dest); + String8 type = src->name; + String8 view_rule = dst->name; + U64 hash = df_hash_from_string(type); + U64 slot_idx = hash%cache->slots_count; + DF_AutoViewRuleSlot *slot = &cache->slots[slot_idx]; + DF_AutoViewRuleNode *node = push_array(cache->arena, DF_AutoViewRuleNode, 1); + node->type = push_str8_copy(cache->arena, type); + node->view_rule = push_str8_copy(cache->arena, view_rule); + SLLQueuePush(slot->first, slot->last, node); + } + } + }break; + + //- rjf: general entity operations + case DF_CoreCmdKind_EnableEntity: + case DF_CoreCmdKind_EnableBreakpoint: + case DF_CoreCmdKind_EnableTarget: + { + DF_Entity *entity = df_entity_from_handle(params.entity); + df_state_delta_history_push_batch(df_state->hist, &entity->generation); + df_state_delta_history_push_struct_delta(df_state->hist, &entity->b32); + df_entity_equip_b32(entity, 1); + }break; + case DF_CoreCmdKind_DisableEntity: + case DF_CoreCmdKind_DisableBreakpoint: + case DF_CoreCmdKind_DisableTarget: + { + DF_Entity *entity = df_entity_from_handle(params.entity); + df_state_delta_history_push_batch(df_state->hist, &entity->generation); + df_state_delta_history_push_struct_delta(df_state->hist, &entity->b32); + df_entity_equip_b32(entity, 0); + }break; + case DF_CoreCmdKind_FreezeEntity: + case DF_CoreCmdKind_ThawEntity: + { + B32 should_freeze = (core_cmd_kind == DF_CoreCmdKind_FreezeEntity); + DF_Entity *root = df_entity_from_handle(params.entity); + for(DF_Entity *e = root; !df_entity_is_nil(e); e = df_entity_rec_df_pre(e, root).next) + { + if(e->kind == DF_EntityKind_Thread) + { + df_set_thread_freeze_state(e, should_freeze); + } + } + }break; + case DF_CoreCmdKind_RemoveEntity: + case DF_CoreCmdKind_RemoveBreakpoint: + case DF_CoreCmdKind_RemoveTarget: + { + DF_Entity *entity = df_entity_from_handle(params.entity); + DF_EntityOpFlags op_flags = df_g_entity_kind_op_flags_table[entity->kind]; + if(op_flags & DF_EntityOpFlag_Delete) + { + df_entity_mark_for_deletion(entity); + } + }break; + case DF_CoreCmdKind_NameEntity: + { + DF_Entity *entity = df_entity_from_handle(params.entity); + String8 string = params.string; + df_state_delta_history_push_batch(df_state_delta_history(), &entity->generation); + df_entity_equip_name(df_state_delta_history(), entity, string); + }break; + case DF_CoreCmdKind_EditEntity:{}break; + case DF_CoreCmdKind_DuplicateEntity: + { + DF_Entity *src = df_entity_from_handle(params.entity); + if(!df_entity_is_nil(src)) + { + typedef struct Task Task; + struct Task + { + Task *next; + DF_Entity *src_n; + DF_Entity *dst_parent; + }; + Task starter_task = {0, src, src->parent}; + Task *first_task = &starter_task; + Task *last_task = &starter_task; + df_state_delta_history_push_batch(df_state_delta_history(), 0); + for(Task *task = first_task; task != 0; task = task->next) + { + DF_Entity *src_n = task->src_n; + DF_Entity *dst_n = df_entity_alloc(df_state_delta_history(), task->dst_parent, task->src_n->kind); + if(src_n->flags & DF_EntityFlag_HasTextPoint) {df_entity_equip_txt_pt(dst_n, src_n->text_point);} + if(src_n->flags & DF_EntityFlag_HasTextPointAlt) {df_entity_equip_txt_pt_alt(dst_n, src_n->text_point_alt);} + if(src_n->flags & DF_EntityFlag_HasB32) {df_entity_equip_b32(dst_n, src_n->b32);} + if(src_n->flags & DF_EntityFlag_HasU64) {df_entity_equip_u64(dst_n, src_n->u64);} + if(src_n->flags & DF_EntityFlag_HasRng1U64) {df_entity_equip_rng1u64(dst_n, src_n->rng1u64);} + if(src_n->flags & DF_EntityFlag_HasColor) {df_entity_equip_color_hsva(dst_n, df_hsva_from_entity(src_n));} + if(src_n->flags & DF_EntityFlag_HasVAddrRng) {df_entity_equip_vaddr_rng(dst_n, src_n->vaddr_rng);} + if(src_n->flags & DF_EntityFlag_HasVAddr) {df_entity_equip_vaddr(dst_n, src_n->vaddr);} + if(src_n->name.size != 0) {df_entity_equip_name(df_state_delta_history(), dst_n, src_n->name);} + dst_n->cfg_src = src_n->cfg_src; + for(DF_Entity *src_child = task->src_n->first; !df_entity_is_nil(src_child); src_child = src_child->next) + { + Task *child_task = push_array(scratch.arena, Task, 1); + child_task->src_n = src_child; + child_task->dst_parent = dst_n; + SLLQueuePush(first_task, last_task, child_task); + } + } + } + }break; + + //- rjf: breakpoints + case DF_CoreCmdKind_TextBreakpoint: + { + DF_Entity *entity = df_entity_from_handle(params.entity); + if(df_entity_is_nil(entity)) + { + entity = df_entity_from_path(params.file_path, 0); + } + if(!df_entity_is_nil(entity)) + { + S64 line_num = params.text_point.line; + B32 removed_existing = 0; + for(DF_Entity *child = entity->first, *next = 0; !df_entity_is_nil(child); child = next) + { + next = child->next; + if(child->deleted) { continue; } + if(child->kind == DF_EntityKind_Breakpoint && child->flags & DF_EntityFlag_HasTextPoint && child->text_point.line == line_num) + { + removed_existing = 1; + df_entity_mark_for_deletion(child); + } + } + if(removed_existing == 0) + { + df_state_delta_history_push_batch(df_state_delta_history(), 0); + DF_Entity *bp = df_entity_alloc(df_state_delta_history(), entity, DF_EntityKind_Breakpoint); + df_entity_equip_txt_pt(bp, params.text_point); + df_entity_equip_b32(bp, 1); + df_entity_equip_cfg_src(bp, DF_CfgSrc_Project); + } + } + }break; + case DF_CoreCmdKind_AddressBreakpoint: + { + U64 vaddr = params.vaddr; + if(vaddr != 0) + { + DF_Entity *bp = &df_g_nil_entity; + DF_EntityList existing_bps = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); + for(DF_EntityNode *n = existing_bps.first; n != 0; n = n->next) + { + if(n->entity->vaddr == vaddr) + { + bp = n->entity; + break; + } + } + if(df_entity_is_nil(bp)) + { + df_state_delta_history_push_batch(df_state_delta_history(), 0); + bp = df_entity_alloc(df_state_delta_history(), df_entity_root(), DF_EntityKind_Breakpoint); + df_entity_equip_vaddr(bp, vaddr); + df_entity_equip_b32(bp, 1); + df_entity_equip_cfg_src(bp, DF_CfgSrc_Project); + } + else + { + df_entity_mark_for_deletion(bp); + } + } + }break; + case DF_CoreCmdKind_FunctionBreakpoint: + { + String8 function_name = params.string; + if(function_name.size != 0) + { + DF_Entity *symb = df_entity_from_name_and_kind(function_name, DF_EntityKind_EntryPointName); + DF_Entity *bp = df_entity_ancestor_from_kind(symb, DF_EntityKind_Breakpoint); + if(df_entity_is_nil(bp)) + { + df_state_delta_history_push_batch(df_state_delta_history(), 0); + bp = df_entity_alloc(df_state_delta_history(), df_entity_root(), DF_EntityKind_Breakpoint); + DF_Entity *symbol_name_entity = df_entity_alloc(df_state_delta_history(), bp, DF_EntityKind_EntryPointName); + df_entity_equip_name(df_state_delta_history(), symbol_name_entity, function_name); + df_entity_equip_b32(bp, 1); + df_entity_equip_cfg_src(bp, DF_CfgSrc_Project); + } + else + { + df_entity_mark_for_deletion(bp); + } + } + }break; + + //- rjf: watches + case DF_CoreCmdKind_ToggleWatchPin: + { + DF_Entity *entity = df_entity_from_handle(params.entity); + S64 line_num = params.text_point.line; + if(!df_entity_is_nil(entity) && line_num != 0) + { + B32 removed_existing = 0; + for(DF_Entity *child = entity->first, *next = 0; !df_entity_is_nil(child); child = next) + { + next = child->next; + if(child->deleted) { continue; } + if(child->kind == DF_EntityKind_WatchPin && child->flags & DF_EntityFlag_HasTextPoint && child->text_point.line == line_num && + str8_match(child->name, params.string, 0)) + { + removed_existing = 1; + df_entity_mark_for_deletion(child); + } + } + if(removed_existing == 0) + { + df_state_delta_history_push_batch(df_state_delta_history(), 0); + DF_Entity *watch = df_entity_alloc(df_state_delta_history(), entity, DF_EntityKind_WatchPin); + df_entity_equip_txt_pt(watch, params.text_point); + df_entity_equip_name(df_state_delta_history(), watch, params.string); + df_entity_equip_cfg_src(watch, DF_CfgSrc_Project); + } + } + else if(params.vaddr != 0) + { + B32 removed_existing = 0; + DF_EntityList pins = df_query_cached_entity_list_with_kind(DF_EntityKind_WatchPin); + for(DF_EntityNode *n = pins.first; n != 0; n = n->next) + { + DF_Entity *pin = n->entity; + if(pin->flags & DF_EntityFlag_HasVAddr && pin->vaddr == params.vaddr && str8_match(pin->name, params.string, 0)) + { + removed_existing = 1; + df_entity_mark_for_deletion(pin); + } + } + if(!removed_existing) + { + df_state_delta_history_push_batch(df_state_delta_history(), 0); + DF_Entity *pin = df_entity_alloc(df_state_delta_history(), df_entity_root(), DF_EntityKind_WatchPin); + df_entity_equip_vaddr(pin, params.vaddr); + df_entity_equip_name(df_state_delta_history(), pin, params.string); + df_entity_equip_cfg_src(pin, DF_CfgSrc_Project); + } + } + }break; + + //- rjf: cursor operations + case DF_CoreCmdKind_ToggleBreakpointAtCursor: + { + DF_InteractRegs *regs = df_interact_regs(); + DF_Entity *file = df_entity_from_handle(regs->file); + if(file->kind == DF_EntityKind_File && regs->cursor.line != 0) + { + DF_CmdParams p = df_cmd_params_zero(); + p.entity = df_handle_from_entity(file); + p.text_point = regs->cursor; + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_TextBreakpoint)); + } + else if(regs->vaddr_range.min != 0) + { + DF_CmdParams p = df_cmd_params_zero(); + p.vaddr = regs->vaddr_range.min; + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_AddressBreakpoint)); + } + }break; + case DF_CoreCmdKind_ToggleWatchPinAtCursor: + { + DF_InteractRegs *regs = df_interact_regs(); + DF_Entity *file = df_entity_from_handle(regs->file); + if(file->kind == DF_EntityKind_File && regs->cursor.line != 0) + { + DF_CmdParams p = df_cmd_params_zero(); + p.entity = df_handle_from_entity(file); + p.text_point = regs->cursor; + p.string = params.string; + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_ToggleWatchPin)); + } + else if(regs->vaddr_range.min != 0) + { + DF_CmdParams p = df_cmd_params_zero(); + p.vaddr = regs->vaddr_range.min; + p.string = params.string; + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_ToggleWatchPin)); + } + }break; + case DF_CoreCmdKind_GoToNameAtCursor: + case DF_CoreCmdKind_ToggleWatchExpressionAtCursor: + { + HS_Scope *hs_scope = hs_scope_open(); + TXT_Scope *txt_scope = txt_scope_open(); + DF_InteractRegs *regs = df_interact_regs(); + U128 text_key = regs->text_key; + TXT_LangKind lang_kind = regs->lang_kind; + TxtRng range = txt_rng(regs->cursor, regs->mark); + U128 hash = {0}; + TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, text_key, lang_kind, &hash); + String8 data = hs_data_from_hash(hs_scope, hash); + Rng1U64 expr_off_range = {0}; + if(range.min.column != range.max.column) + { + expr_off_range = r1u64(txt_off_from_info_pt(&info, range.min), txt_off_from_info_pt(&info, range.max)); + } + else + { + expr_off_range = txt_expr_off_range_from_info_data_pt(&info, data, range.min); + } + String8 expr = str8_substr(data, expr_off_range); + DF_CmdParams p = df_cmd_params_zero(); + p.string = expr; + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(core_cmd_kind == DF_CoreCmdKind_GoToNameAtCursor ? DF_CoreCmdKind_GoToName : + core_cmd_kind == DF_CoreCmdKind_ToggleWatchExpressionAtCursor ? DF_CoreCmdKind_ToggleWatchExpression : + DF_CoreCmdKind_GoToName)); + txt_scope_close(txt_scope); + hs_scope_close(hs_scope); + }break; + case DF_CoreCmdKind_RunToCursor: + { + DF_Entity *file = df_entity_from_handle(df_interact_regs()->file); + if(!df_entity_is_nil(file)) + { + DF_CmdParams p = df_cmd_params_zero(); + p.entity = df_handle_from_entity(file); + p.text_point = df_interact_regs()->cursor; + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunToLine)); + } + else + { + DF_CmdParams p = df_cmd_params_zero(); + p.vaddr = df_interact_regs()->vaddr_range.min; + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunToAddress)); + } + }break; + case DF_CoreCmdKind_SetNextStatement: + { + DF_Entity *file = df_entity_from_handle(df_interact_regs()->file); + DF_Entity *thread = df_entity_from_handle(df_interact_regs()->thread); + U64 new_rip_vaddr = df_interact_regs()->vaddr_range.min; + if(!df_entity_is_nil(file)) + { + DF_LineList *lines = &df_interact_regs()->lines; + for(DF_LineNode *n = lines->first; n != 0; n = n->next) + { + DF_EntityList modules = df_modules_from_dbgi_key(scratch.arena, &n->v.dbgi_key); + DF_Entity *module = df_module_from_thread_candidates(thread, &modules); + if(!df_entity_is_nil(module)) + { + new_rip_vaddr = df_vaddr_from_voff(module, n->v.voff_range.min); + break; + } + } + } + DF_CmdParams p = df_cmd_params_zero(); + p.entity = df_handle_from_entity(thread); + p.vaddr = new_rip_vaddr; + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SetThreadIP)); + }break; + + //- rjf: targets + case DF_CoreCmdKind_AddTarget: + { + // rjf: build target + df_state_delta_history_push_batch(df_state_delta_history(), 0); + DF_Entity *entity = df_entity_alloc(df_state_delta_history(), df_entity_root(), DF_EntityKind_Target); + df_entity_equip_cfg_src(entity, DF_CfgSrc_Project); + DF_Entity *exe = df_entity_alloc(df_state_delta_history(), entity, DF_EntityKind_Executable); + df_entity_equip_name(df_state_delta_history(), exe, params.file_path); + String8 working_dir = str8_chop_last_slash(params.file_path); + if(working_dir.size != 0) + { + String8 working_dir_path = push_str8f(scratch.arena, "%S/", working_dir); + DF_Entity *execution_path = df_entity_alloc(df_state_delta_history(), entity, DF_EntityKind_ExecutionPath); + df_entity_equip_name(df_state_delta_history(), execution_path, working_dir_path); + } + DF_CmdParams p = params; + p.entity = df_handle_from_entity(entity); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_Entity); + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_EditTarget)); + df_cmd_list_push(arena, cmds, &p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectTarget)); + }break; + case DF_CoreCmdKind_SelectTarget: + { + DF_Entity *entity = df_entity_from_handle(params.entity); + if(entity->kind == DF_EntityKind_Target) + { + DF_EntityList all_targets = df_query_cached_entity_list_with_kind(DF_EntityKind_Target); + B32 is_selected = entity->b32; + for(DF_EntityNode *n = all_targets.first; n != 0; n = n->next) + { + DF_Entity *target = n->entity; + df_entity_equip_b32(target, 0); + } + if(!is_selected) + { + df_entity_equip_b32(entity, 1); + } + } + }break; + + //- rjf: ended processes + case DF_CoreCmdKind_RetryEndedProcess: + { + DF_Entity *ended_process = df_entity_from_handle(params.entity); + DF_Entity *target = df_entity_from_handle(ended_process->entity_handle); + if(target->kind == DF_EntityKind_Target) + { + DF_CmdParams p = params; + p.entity = df_handle_from_entity(target); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_LaunchAndRun)); + } + else if(df_entity_is_nil(target)) + { + DF_CmdParams p = params; + p.string = str8_lit("The ended process' corresponding target is missing."); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + else if(df_entity_is_nil(ended_process)) + { + DF_CmdParams p = params; + p.string = str8_lit("Invalid ended process."); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + }break; + + //- rjf: attaching + case DF_CoreCmdKind_Attach: + { + U64 pid = params.id; + if(pid != 0) + { + CTRL_Msg msg = {CTRL_MsgKind_Attach}; + msg.entity_id = (U32)pid; + MemoryCopyArray(msg.exception_code_filters, df_state->ctrl_exception_code_filters); + df_push_ctrl_msg(&msg); + } + }break; + + //- rjf: jit-debugger registration + case DF_CoreCmdKind_RegisterAsJITDebugger: + { +#if OS_WINDOWS + char filename_cstr[MAX_PATH] = {0}; + GetModuleFileName(0, filename_cstr, sizeof(filename_cstr)); + String8 debugger_binary_path = str8_cstring(filename_cstr); + String8 name8 = str8_lit("Debugger"); + String8 data8 = push_str8f(scratch.arena, "%S --jit_pid:%%ld --jit_code:%%ld --jit_addr:0x%%p", debugger_binary_path); + String16 name16 = str16_from_8(scratch.arena, name8); + String16 data16 = str16_from_8(scratch.arena, data8); + B32 likely_not_in_admin_mode = 0; + { + HKEY reg_key = 0; + LSTATUS status = 0; + status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug\\", 0, KEY_SET_VALUE, ®_key); + likely_not_in_admin_mode = (status == ERROR_ACCESS_DENIED); + status = RegSetValueExW(reg_key, (LPCWSTR)name16.str, 0, REG_SZ, (BYTE *)data16.str, data16.size*sizeof(U16)+2); + RegCloseKey(reg_key); + } + { + HKEY reg_key = 0; + LSTATUS status = 0; + status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug\\", 0, KEY_SET_VALUE, ®_key); + likely_not_in_admin_mode = (status == ERROR_ACCESS_DENIED); + status = RegSetValueExW(reg_key, (LPCWSTR)name16.str, 0, REG_SZ, (BYTE *)data16.str, data16.size*sizeof(U16)+2); + RegCloseKey(reg_key); + } + if(likely_not_in_admin_mode) + { + DF_CmdParams p = params; + p.string = str8_lit("Could not register as the just-in-time debugger, access was denied; try running the debugger as administrator."); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } +#else + DF_CmdParams p = params; + p.string = str8_lit("Registering as the just-in-time debugger is currently not supported on this system."); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); +#endif + }break; + + //- rjf: developer commands + case DF_CoreCmdKind_LogMarker: + { + log_infof("\"#MARKER\""); + }break; + } + } + scratch_end(scratch); + } + + //- rjf: fill core interaction register info + { + DF_Entity *thread = df_entity_from_handle(df_state->ctrl_ctx.thread); + DF_Entity *module = df_module_from_thread(thread); + DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); + df_interact_regs()->thread = df_handle_from_entity(thread); + df_interact_regs()->module = df_handle_from_entity(module); + df_interact_regs()->process = df_handle_from_entity(process); + df_interact_regs()->unwind_count = df_state->ctrl_ctx.unwind_count; + df_interact_regs()->inline_unwind_count = df_state->ctrl_ctx.inline_unwind_count; + } + + ProfEnd(); +} + +internal void +df_core_end_frame(void) +{ + ProfBeginFunction(); + + //- rjf: entity mutation -> soft halt + if(df_state->entities_mut_soft_halt) + { + df_state->entities_mut_soft_halt = 0; + DF_CmdParams params = df_cmd_params_zero(); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SoftHaltRefresh)); + } + + //- rjf: entity mutation -> send refreshed debug info map + if(df_state->entities_mut_dbg_info_map) ProfScope("entity mutation -> send refreshed debug info map") + { + df_state->entities_mut_dbg_info_map = 0; + // TODO(rjf) + } + + //- rjf: send messages + if(df_state->ctrl_msgs.count != 0) + { + if(ctrl_u2c_push_msgs(&df_state->ctrl_msgs, os_now_microseconds()+100)) + { + MemoryZeroStruct(&df_state->ctrl_msgs); + arena_clear(df_state->ctrl_msg_arena); + } + } + + //- rjf: eliminate entities that are marked for deletion + kill off entities with a death-timer + ProfScope("eliminate deleted/deletion-timer entities") + { + for(DF_Entity *entity = df_entity_root(), *next = 0; !df_entity_is_nil(entity); entity = next) + { + next = df_entity_rec_df_pre(entity, &df_g_nil_entity).next; + if(entity->flags & DF_EntityFlag_DiesWithTime) + { + entity->life_left -= df_dt(); + if(entity->life_left <= 0.f) + { + df_entity_mark_for_deletion(entity); + } + } + if(entity->flags & DF_EntityFlag_MarkedForDeletion) + { + B32 undoable = (df_g_entity_kind_flags_table[entity->kind] & DF_EntityKindFlag_UserDefinedLifetime); + + // rjf: fixup next entity to iterate to + next = df_entity_rec_df(entity, &df_g_nil_entity, OffsetOf(DF_Entity, next), OffsetOf(DF_Entity, next)).next; + + // rjf: undoable -> just mark as deleted; this must be able to be trivially undone + if(undoable) + { + df_state_delta_history_push_batch(df_state->hist, 0); + df_state_delta_history_push_struct_delta(df_state->hist, &entity->deleted); + df_state_delta_history_push_struct_delta(df_state->hist, &entity->generation); + df_state_delta_history_push_struct_delta(df_state->hist, &df_state->kind_alloc_gens[entity->kind]); + entity->deleted = 1; + entity->generation += 1; + entity->flags &= ~DF_EntityFlag_MarkedForDeletion; + df_state->kind_alloc_gens[entity->kind] += 1; + } + + // rjf: not undoable -> actually release + if(!undoable) + { + // rjf: eliminate root entity if we're freeing it + if(entity == df_state->entities_root) + { + df_state->entities_root = &df_g_nil_entity; + } + + // rjf: unhook & release this entity tree + df_entity_change_parent(0, entity, entity->parent, &df_g_nil_entity); + df_entity_release(0, entity); + } + } + } + } + + //- rjf: garbage collect eliminated thread unwinds + for(U64 slot_idx = 0; slot_idx < df_state->unwind_cache.slots_count; slot_idx += 1) + { + DF_UnwindCacheSlot *slot = &df_state->unwind_cache.slots[slot_idx]; + for(DF_UnwindCacheNode *n = slot->first, *next = 0; n != 0; n = next) + { + next = n->next; + if(df_entity_is_nil(df_entity_from_handle(n->thread))) + { + DLLRemove(slot->first, slot->last, n); + arena_release(n->arena); + SLLStackPush(df_state->unwind_cache.free_node, n); + } + } + } + + //- rjf: write config changes + ProfScope("write config changes") + { + for(DF_CfgSrc src = (DF_CfgSrc)0; src < DF_CfgSrc_COUNT; src = (DF_CfgSrc)(src+1)) ProfScope("write %.*s config data", str8_varg(df_g_cfg_src_string_table[src])) + { + if(df_state->cfg_write_issued[src]) + { + df_state->cfg_write_issued[src] = 0; + String8 path = df_cfg_path_from_src(src); + os_write_data_list_to_file_path(path, df_state->cfg_write_data[src]); + } + arena_clear(df_state->cfg_write_arenas[src]); + MemoryZeroStruct(&df_state->cfg_write_data[src]); + } + } + + ProfEnd(); +} diff --git a/src/df/core/df_core.h b/src/df/core/df_core.h index f4cb69d1..dd47c823 100644 --- a/src/df/core/df_core.h +++ b/src/df/core/df_core.h @@ -1,1834 +1,1829 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef DF_CORE_H -#define DF_CORE_H - -//////////////////////////////// -//~ rjf: Handles - -typedef struct DF_Handle DF_Handle; -struct DF_Handle -{ - U64 u64[2]; -}; - -typedef struct DF_HandleNode DF_HandleNode; -struct DF_HandleNode -{ - DF_HandleNode *next; - DF_HandleNode *prev; - DF_Handle handle; -}; - -typedef struct DF_HandleList DF_HandleList; -struct DF_HandleList -{ - DF_HandleNode *first; - DF_HandleNode *last; - U64 count; -}; - -//////////////////////////////// -//~ rjf: Sparse Tree Expansion State Data Structure - -typedef struct DF_ExpandKey DF_ExpandKey; -struct DF_ExpandKey -{ - U64 parent_hash; - U64 child_num; -}; - -typedef struct DF_ExpandNode DF_ExpandNode; -struct DF_ExpandNode -{ - DF_ExpandNode *hash_next; - DF_ExpandNode *hash_prev; - DF_ExpandNode *first; - DF_ExpandNode *last; - DF_ExpandNode *next; - DF_ExpandNode *prev; - DF_ExpandNode *parent; - DF_ExpandKey key; - B32 expanded; - F32 expanded_t; -}; - -typedef struct DF_ExpandSlot DF_ExpandSlot; -struct DF_ExpandSlot -{ - DF_ExpandNode *first; - DF_ExpandNode *last; -}; - -typedef struct DF_ExpandTreeTable DF_ExpandTreeTable; -struct DF_ExpandTreeTable -{ - DF_ExpandSlot *slots; - U64 slots_count; - DF_ExpandNode *free_node; -}; - -//////////////////////////////// -//~ rjf: Control Context Types - -typedef struct DF_CtrlCtx DF_CtrlCtx; -struct DF_CtrlCtx -{ - DF_Handle thread; - U64 unwind_count; - U64 inline_unwind_count; -}; - -//////////////////////////////// -//~ rjf: Entity Kind Flags - -typedef U32 DF_EntityKindFlags; -enum -{ - DF_EntityKindFlag_LeafMutationUserConfig = (1<<0), - DF_EntityKindFlag_TreeMutationUserConfig = (1<<1), - DF_EntityKindFlag_LeafMutationProjectConfig= (1<<2), - DF_EntityKindFlag_TreeMutationProjectConfig= (1<<3), - DF_EntityKindFlag_LeafMutationSoftHalt = (1<<4), - DF_EntityKindFlag_TreeMutationSoftHalt = (1<<5), - DF_EntityKindFlag_LeafMutationDebugInfoMap = (1<<6), - DF_EntityKindFlag_TreeMutationDebugInfoMap = (1<<7), - DF_EntityKindFlag_NameIsCode = (1<<8), - DF_EntityKindFlag_UserDefinedLifetime = (1<<9), -}; - -//////////////////////////////// -//~ rjf: Entity Operation Flags - -typedef U32 DF_EntityOpFlags; -enum -{ - DF_EntityOpFlag_Delete = (1<<0), - DF_EntityOpFlag_Freeze = (1<<1), - DF_EntityOpFlag_Edit = (1<<2), - DF_EntityOpFlag_Rename = (1<<3), - DF_EntityOpFlag_Enable = (1<<4), - DF_EntityOpFlag_Condition = (1<<5), - DF_EntityOpFlag_Duplicate = (1<<6), -}; - -//////////////////////////////// -//~ rjf: Entity Filesystem Lookup Flags - -typedef U32 DF_EntityFromPathFlags; -enum -{ - DF_EntityFromPathFlag_AllowOverrides = (1<<0), - DF_EntityFromPathFlag_OpenAsNeeded = (1<<1), - DF_EntityFromPathFlag_OpenMissing = (1<<2), - - DF_EntityFromPathFlag_All = 0xffffffff, -}; - -//////////////////////////////// -//~ rjf: Debug Engine Control Communication Types - -typedef enum DF_RunKind -{ - DF_RunKind_Run, - DF_RunKind_SingleStep, - DF_RunKind_Step, - DF_RunKind_COUNT -} -DF_RunKind; - -//////////////////////////////// -//~ rjf: Disassembly Types - -typedef U32 DF_InstFlags; -enum -{ - DF_InstFlag_Call = (1<<0), - DF_InstFlag_Branch = (1<<1), - DF_InstFlag_UnconditionalJump = (1<<2), - DF_InstFlag_Return = (1<<3), - DF_InstFlag_NonFlow = (1<<4), - DF_InstFlag_Repeats = (1<<5), - DF_InstFlag_ChangesStackPointer = (1<<6), - DF_InstFlag_ChangesStackPointerVariably = (1<<7), -}; - -typedef struct DF_Inst DF_Inst; -struct DF_Inst -{ - DF_InstFlags flags; - U64 size; - String8 string; - U64 rel_voff; - S64 sp_delta; -}; - -typedef struct DF_InstNode DF_InstNode; -struct DF_InstNode -{ - DF_InstNode *next; - DF_Inst inst; -}; - -typedef struct DF_InstList DF_InstList; -struct DF_InstList -{ - DF_InstNode *first; - DF_InstNode *last; - U64 count; -}; - -typedef struct DF_InstArray DF_InstArray; -struct DF_InstArray -{ - DF_InstArray *v; - U64 count; -}; - -typedef struct DF_InstMemVOffTuple DF_InstMemVOffTuple; -struct DF_InstMemVOffTuple -{ - DF_Inst inst; - String8 mem; - U64 voff; -}; - -typedef struct DF_InstMemVOffTupleArray DF_InstMemVOffTupleArray; -struct DF_InstMemVOffTupleArray -{ - DF_InstMemVOffTuple *v; - U64 count; -}; - -//////////////////////////////// -//~ rjf: Control Flow Analysis Types - -typedef U32 DF_CtrlFlowFlags; -enum -{ - DF_CtrlFlowFlag_StackPointerChangesVariably = (1<<0), -}; - -typedef struct DF_CtrlFlowPoint DF_CtrlFlowPoint; -struct DF_CtrlFlowPoint -{ - U64 vaddr; - U64 jump_dest_vaddr; - S64 expected_sp_delta; - DF_InstFlags inst_flags; -}; - -typedef struct DF_CtrlFlowPointNode DF_CtrlFlowPointNode; -struct DF_CtrlFlowPointNode -{ - DF_CtrlFlowPointNode *next; - DF_CtrlFlowPoint v; -}; - -typedef struct DF_CtrlFlowPointList DF_CtrlFlowPointList; -struct DF_CtrlFlowPointList -{ - DF_CtrlFlowPointNode *first; - DF_CtrlFlowPointNode *last; - U64 count; -}; - -typedef struct DF_CtrlFlowInfo DF_CtrlFlowInfo; -struct DF_CtrlFlowInfo -{ - DF_CtrlFlowFlags flags; - DF_CtrlFlowPointList exit_points; - U64 total_size; - S64 cumulative_sp_delta; -}; - -//////////////////////////////// -//~ rjf: Evaluation Types - -typedef struct DF_Eval DF_Eval; -struct DF_Eval -{ - TG_Key type_key; - EVAL_EvalMode mode; - U64 offset; - union - { - S64 imm_s64; - U64 imm_u64; - F32 imm_f32; - F64 imm_f64; - U64 imm_u128[2]; - }; - EVAL_ErrorList errors; -}; - -//////////////////////////////// -//~ rjf: View Rule Hook Types - -typedef struct DF_CfgNode DF_CfgNode; -typedef struct DF_CfgVal DF_CfgVal; -typedef struct DF_CfgTable DF_CfgTable; -typedef struct DF_EvalView DF_EvalView; -typedef struct DF_EvalVizBlockList DF_EvalVizBlockList; -#define DF_CORE_VIEW_RULE_EVAL_RESOLUTION_FUNCTION_SIG(name) DF_Eval name(Arena *arena, DI_Scope *di_scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_Eval eval, DF_CfgVal *val) -#define DF_CORE_VIEW_RULE_EVAL_RESOLUTION_FUNCTION_NAME(name) df_core_view_rule_eval_resolution__##name -#define DF_CORE_VIEW_RULE_EVAL_RESOLUTION_FUNCTION_DEF(name) internal DF_CORE_VIEW_RULE_EVAL_RESOLUTION_FUNCTION_SIG(DF_CORE_VIEW_RULE_EVAL_RESOLUTION_FUNCTION_NAME(name)) -#define DF_CORE_VIEW_RULE_VIZ_BLOCK_PROD_FUNCTION_SIG(name) void name(Arena *arena, DI_Scope *di_scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_EvalView *eval_view, DF_Eval eval, String8 string, DF_CfgTable *cfg_table, DF_ExpandKey parent_key, DF_ExpandKey key, S32 depth, DF_CfgNode *cfg, struct DF_EvalVizBlockList *out) -#define DF_CORE_VIEW_RULE_VIZ_BLOCK_PROD_FUNCTION_NAME(name) df_core_view_rule_viz_block_prod__##name -#define DF_CORE_VIEW_RULE_VIZ_BLOCK_PROD_FUNCTION_DEF(name) internal DF_CORE_VIEW_RULE_VIZ_BLOCK_PROD_FUNCTION_SIG(DF_CORE_VIEW_RULE_VIZ_BLOCK_PROD_FUNCTION_NAME(name)) -typedef DF_CORE_VIEW_RULE_EVAL_RESOLUTION_FUNCTION_SIG(DF_CoreViewRuleEvalResolutionHookFunctionType); -typedef DF_CORE_VIEW_RULE_VIZ_BLOCK_PROD_FUNCTION_SIG(DF_CoreViewRuleVizBlockProdHookFunctionType); - -//////////////////////////////// -//~ rjf: Generated Code - -#include "df/core/generated/df_core.meta.h" - -//////////////////////////////// -//~ rjf: Config Types - -typedef U32 DF_CfgNodeFlags; -enum -{ - DF_CfgNodeFlag_Identifier = (1<<0), - DF_CfgNodeFlag_Numeric = (1<<1), - DF_CfgNodeFlag_StringLiteral = (1<<2), -}; - -typedef struct DF_CfgNode DF_CfgNode; -struct DF_CfgNode -{ - DF_CfgNode *first; - DF_CfgNode *last; - DF_CfgNode *parent; - DF_CfgNode *next; - DF_CfgNodeFlags flags; - String8 string; - DF_CfgSrc source; -}; - -typedef struct DF_CfgNodeRec DF_CfgNodeRec; -struct DF_CfgNodeRec -{ - DF_CfgNode *next; - S32 push_count; - S32 pop_count; -}; - -typedef struct DF_CfgVal DF_CfgVal; -struct DF_CfgVal -{ - DF_CfgVal *hash_next; - DF_CfgVal *linear_next; - DF_CfgNode *first; - DF_CfgNode *last; - U64 insertion_stamp; - String8 string; -}; - -typedef struct DF_CfgSlot DF_CfgSlot; -struct DF_CfgSlot -{ - DF_CfgVal *first; -}; - -typedef struct DF_CfgTable DF_CfgTable; -struct DF_CfgTable -{ - U64 slot_count; - DF_CfgSlot *slots; - U64 insertion_stamp_counter; - DF_CfgVal *first_val; - DF_CfgVal *last_val; -}; - -//////////////////////////////// -//~ rjf: View Rules - -typedef U32 DF_CoreViewRuleSpecInfoFlags; // NOTE(rjf): see @view_rule_info -enum -{ - DF_CoreViewRuleSpecInfoFlag_Inherited = (1<<0), - DF_CoreViewRuleSpecInfoFlag_Expandable = (1<<1), - DF_CoreViewRuleSpecInfoFlag_EvalResolution = (1<<2), - DF_CoreViewRuleSpecInfoFlag_VizBlockProd = (1<<3), -}; - -typedef struct DF_CoreViewRuleSpecInfo DF_CoreViewRuleSpecInfo; -struct DF_CoreViewRuleSpecInfo -{ - String8 string; - String8 display_string; - String8 schema; - String8 description; - DF_CoreViewRuleSpecInfoFlags flags; - DF_CoreViewRuleEvalResolutionHookFunctionType *eval_resolution; - DF_CoreViewRuleVizBlockProdHookFunctionType *viz_block_prod; -}; - -typedef struct DF_CoreViewRuleSpecInfoArray DF_CoreViewRuleSpecInfoArray; -struct DF_CoreViewRuleSpecInfoArray -{ - DF_CoreViewRuleSpecInfo *v; - U64 count; -}; - -typedef struct DF_CoreViewRuleSpec DF_CoreViewRuleSpec; -struct DF_CoreViewRuleSpec -{ - DF_CoreViewRuleSpec *hash_next; - DF_CoreViewRuleSpecInfo info; -}; - -//////////////////////////////// -//~ rjf: Entity Types - -typedef U32 DF_EntityFlags; -enum -{ - //- rjf: allocationless, simple equipment - DF_EntityFlag_HasTextPoint = (1<<0), - DF_EntityFlag_HasTextPointAlt = (1<<1), - DF_EntityFlag_HasEntityHandle = (1<<2), - DF_EntityFlag_HasB32 = (1<<3), - DF_EntityFlag_HasU64 = (1<<4), - DF_EntityFlag_HasRng1U64 = (1<<5), - DF_EntityFlag_HasColor = (1<<6), - DF_EntityFlag_DiesWithTime = (1<<7), - DF_EntityFlag_DiesOnRunStop = (1<<8), - - //- rjf: ctrl entity equipment - DF_EntityFlag_HasCtrlMachineID = (1<<9), - DF_EntityFlag_HasCtrlHandle = (1<<10), - DF_EntityFlag_HasArch = (1<<11), - DF_EntityFlag_HasCtrlID = (1<<12), - DF_EntityFlag_HasStackBase = (1<<13), - DF_EntityFlag_HasTLSRoot = (1<<14), - DF_EntityFlag_HasVAddrRng = (1<<15), - DF_EntityFlag_HasVAddr = (1<<16), - - //- rjf: file properties - DF_EntityFlag_IsFolder = (1<<17), - DF_EntityFlag_IsMissing = (1<<18), - DF_EntityFlag_Output = (1<<19), // NOTE(rjf): might be missing, but written by us - - //- rjf: deletion - DF_EntityFlag_MarkedForDeletion = (1<<31), -}; - -typedef U64 DF_EntityID; - -typedef struct DF_Entity DF_Entity; -struct DF_Entity -{ - // rjf: tree links - DF_Entity *first; - DF_Entity *last; - DF_Entity *next; - DF_Entity *prev; - DF_Entity *parent; - - // rjf: metadata - DF_EntityKind kind; - DF_EntityFlags flags; - DF_EntityID id; - U64 generation; - U64 alloc_time_us; - B32 deleted; - F32 alive_t; - - // rjf: allocationless, simple equipment - TxtPt text_point; - TxtPt text_point_alt; - DF_Handle entity_handle; - B32 b32; - U64 u64; - Rng1U64 rng1u64; - Vec4F32 color_hsva; - F32 life_left; - DF_CfgSrc cfg_src; - - // rjf: ctrl entity equipment - CTRL_MachineID ctrl_machine_id; - DMN_Handle ctrl_handle; - Architecture arch; - U32 ctrl_id; - U64 stack_base; - U64 tls_root; - Rng1U64 vaddr_rng; - U64 vaddr; - - // rjf: name equipment - String8 name; - U64 name_generation; - - // rjf: timestamp - U64 timestamp; -}; - -typedef struct DF_EntityNode DF_EntityNode; -struct DF_EntityNode -{ - DF_EntityNode *next; - DF_Entity *entity; -}; - -typedef struct DF_EntityList DF_EntityList; -struct DF_EntityList -{ - DF_EntityNode *first; - DF_EntityNode *last; - U64 count; -}; - -typedef struct DF_EntityArray DF_EntityArray; -struct DF_EntityArray -{ - DF_Entity **v; - U64 count; -}; - -typedef struct DF_EntityRec DF_EntityRec; -struct DF_EntityRec -{ - DF_Entity *next; - S32 push_count; - S32 pop_count; -}; - -//////////////////////////////// -//~ rjf: Entity Fuzzy Listing Types - -typedef struct DF_EntityFuzzyItem DF_EntityFuzzyItem; -struct DF_EntityFuzzyItem -{ - DF_Entity *entity; - FuzzyMatchRangeList matches; -}; - -typedef struct DF_EntityFuzzyItemArray DF_EntityFuzzyItemArray; -struct DF_EntityFuzzyItemArray -{ - DF_EntityFuzzyItem *v; - U64 count; -}; - -//////////////////////////////// -//~ rjf: Rich (Including Inline) Unwind Types - -typedef struct DF_UnwindFrame DF_UnwindFrame; -struct DF_UnwindFrame -{ - void *regs; - RDI_Parsed *rdi; - RDI_Procedure *procedure; - RDI_InlineSite *inline_site; - U64 base_unwind_idx; - U64 inline_unwind_idx; -}; - -typedef struct DF_UnwindFrameNode DF_UnwindFrameNode; -struct DF_UnwindFrameNode -{ - DF_UnwindFrameNode *next; - DF_UnwindFrame v; -}; - -typedef struct DF_UnwindFrameList DF_UnwindFrameList; -struct DF_UnwindFrameList -{ - DF_UnwindFrameNode *first; - DF_UnwindFrameNode *last; - U64 count; -}; - -typedef struct DF_UnwindFrameArray DF_UnwindFrameArray; -struct DF_UnwindFrameArray -{ - DF_UnwindFrame *v; - U64 count; -}; - -typedef struct DF_Unwind DF_Unwind; -struct DF_Unwind -{ - DF_UnwindFrameArray frames; -}; - -//////////////////////////////// -//~ rjf: Line Info Types - -typedef struct DF_Line DF_Line; -struct DF_Line -{ - DF_Handle file; - TxtPt pt; - Rng1U64 voff_range; - DI_Key dbgi_key; -}; - -typedef struct DF_LineNode DF_LineNode; -struct DF_LineNode -{ - DF_LineNode *next; - DF_Line v; -}; - -typedef struct DF_LineList DF_LineList; -struct DF_LineList -{ - DF_LineNode *first; - DF_LineNode *last; - U64 count; -}; - -typedef struct DF_LineListArray DF_LineListArray; -struct DF_LineListArray -{ - DF_LineList *v; - U64 count; - DI_KeyList dbgi_keys; -}; - -//////////////////////////////// -//~ rjf: Source <-> Disasm Types - -//- rjf: debug info for mapping src -> disasm - -typedef struct DF_TextLineSrc2DasmInfo DF_TextLineSrc2DasmInfo; -struct DF_TextLineSrc2DasmInfo -{ - Rng1U64 voff_range; - S64 remap_line; - DI_Key dbgi_key; -}; - -typedef struct DF_TextLineSrc2DasmInfoNode DF_TextLineSrc2DasmInfoNode; -struct DF_TextLineSrc2DasmInfoNode -{ - DF_TextLineSrc2DasmInfoNode *next; - DF_TextLineSrc2DasmInfo v; -}; - -typedef struct DF_TextLineSrc2DasmInfoList DF_TextLineSrc2DasmInfoList; -struct DF_TextLineSrc2DasmInfoList -{ - DF_TextLineSrc2DasmInfoNode *first; - DF_TextLineSrc2DasmInfoNode *last; - U64 count; -}; - -typedef struct DF_TextLineSrc2DasmInfoListArray DF_TextLineSrc2DasmInfoListArray; -struct DF_TextLineSrc2DasmInfoListArray -{ - DF_TextLineSrc2DasmInfoList *v; - DI_KeyList dbgi_keys; - U64 count; -}; - -//- rjf: debug info for mapping disasm -> src - -typedef struct DF_TextLineDasm2SrcInfo DF_TextLineDasm2SrcInfo; -struct DF_TextLineDasm2SrcInfo -{ - DI_Key dbgi_key; - DF_Entity *file; - TxtPt pt; - Rng1U64 voff_range; -}; - -typedef struct DF_TextLineDasm2SrcInfoNode DF_TextLineDasm2SrcInfoNode; -struct DF_TextLineDasm2SrcInfoNode -{ - DF_TextLineDasm2SrcInfoNode *next; - DF_TextLineDasm2SrcInfo v; -}; - -typedef struct DF_TextLineDasm2SrcInfoList DF_TextLineDasm2SrcInfoList; -struct DF_TextLineDasm2SrcInfoList -{ - DF_TextLineDasm2SrcInfoNode *first; - DF_TextLineDasm2SrcInfoNode *last; - U64 count; -}; - -//////////////////////////////// -//~ rjf: Interaction Context Register Types - -typedef struct DF_InteractRegs DF_InteractRegs; -struct DF_InteractRegs -{ - DF_Handle module; - DF_Handle process; - DF_Handle thread; - U64 unwind_count; - U64 inline_unwind_count; - DF_Handle window; - DF_Handle panel; - DF_Handle view; - DF_Handle file; - TxtPt cursor; - TxtPt mark; - U128 text_key; - TXT_LangKind lang_kind; - Rng1U64 vaddr_range; - Rng1U64 voff_range; - DF_LineList lines; - DI_Key dbgi_key; -}; - -typedef struct DF_InteractRegsNode DF_InteractRegsNode; -struct DF_InteractRegsNode -{ - DF_InteractRegsNode *next; - DF_InteractRegs v; -}; - -//////////////////////////////// -//~ rjf: Evaluation Visualization Types - -//- rjf: expansion key -> view rule table - -typedef struct DF_EvalViewRuleCacheNode DF_EvalViewRuleCacheNode; -struct DF_EvalViewRuleCacheNode -{ - DF_EvalViewRuleCacheNode *hash_next; - DF_EvalViewRuleCacheNode *hash_prev; - DF_ExpandKey key; - U8 *buffer; - U64 buffer_cap; - U64 buffer_string_size; -}; - -typedef struct DF_EvalViewRuleCacheSlot DF_EvalViewRuleCacheSlot; -struct DF_EvalViewRuleCacheSlot -{ - DF_EvalViewRuleCacheNode *first; - DF_EvalViewRuleCacheNode *last; -}; - -typedef struct DF_EvalViewRuleCacheTable DF_EvalViewRuleCacheTable; -struct DF_EvalViewRuleCacheTable -{ - U64 slot_count; - DF_EvalViewRuleCacheSlot *slots; -}; - -//- rjf: 'eval view' entities for sparse-state expandable tree view cache for evaluation visualization - -typedef struct DF_EvalViewKey DF_EvalViewKey; -struct DF_EvalViewKey -{ - U64 u64[2]; -}; - -typedef struct DF_EvalView DF_EvalView; -struct DF_EvalView -{ - // rjf: links - DF_EvalView *hash_next; - DF_EvalView *hash_prev; - - // rjf: key - DF_EvalViewKey key; - - // rjf: arena - Arena *arena; - - // rjf: expansion state - DF_ExpandTreeTable expand_tree_table; - - // rjf: key -> view rule cache - DF_EvalViewRuleCacheTable view_rule_table; -}; - -typedef struct DF_EvalViewSlot DF_EvalViewSlot; -struct DF_EvalViewSlot -{ - DF_EvalView *first; - DF_EvalView *last; -}; - -typedef struct DF_EvalViewCache DF_EvalViewCache; -struct DF_EvalViewCache -{ - DF_EvalViewSlot *slots; - U64 slots_count; -}; - -//- rjf: eval view visualization building - -typedef struct DF_EvalLinkBase DF_EvalLinkBase; -struct DF_EvalLinkBase -{ - EVAL_EvalMode mode; - U64 offset; -}; - -typedef struct DF_EvalLinkBaseChunkNode DF_EvalLinkBaseChunkNode; -struct DF_EvalLinkBaseChunkNode -{ - DF_EvalLinkBaseChunkNode *next; - DF_EvalLinkBase b[64]; - U64 count; -}; - -typedef struct DF_EvalLinkBaseChunkList DF_EvalLinkBaseChunkList; -struct DF_EvalLinkBaseChunkList -{ - DF_EvalLinkBaseChunkNode *first; - DF_EvalLinkBaseChunkNode *last; - U64 count; -}; - -typedef struct DF_EvalLinkBaseArray DF_EvalLinkBaseArray; -struct DF_EvalLinkBaseArray -{ - DF_EvalLinkBase *v; - U64 count; -}; - -typedef enum DF_EvalVizBlockKind -{ - DF_EvalVizBlockKind_Null, // empty - DF_EvalVizBlockKind_Root, // root of tree or subtree; possibly-expandable expression. - DF_EvalVizBlockKind_Members, // members of struct, class, union - DF_EvalVizBlockKind_EnumMembers, // members of enum - DF_EvalVizBlockKind_Elements, // elements of array - DF_EvalVizBlockKind_Links, // flattened nodes in a linked list - DF_EvalVizBlockKind_Canvas, // escape hatch for arbitrary UI - DF_EvalVizBlockKind_DebugInfoTable, // block of filtered debug info table elements - DF_EvalVizBlockKind_COUNT, -} -DF_EvalVizBlockKind; - -typedef struct DF_EvalVizBlock DF_EvalVizBlock; -struct DF_EvalVizBlock -{ - // rjf: kind & keys - DF_EvalVizBlockKind kind; - DF_ExpandKey parent_key; - DF_ExpandKey key; - S32 depth; - - // rjf: evaluation info - DF_Eval eval; - String8 string; - TG_Member *member; - - // rjf: info about ranges that this block spans - Rng1U64 visual_idx_range; - Rng1U64 semantic_idx_range; - FZY_Target fzy_target; - FZY_ItemArray fzy_backing_items; - - // rjf: visualization config extensions - DF_CfgTable cfg_table; - TG_Key link_member_type_key; - U64 link_member_off; -}; - -typedef struct DF_EvalVizBlockNode DF_EvalVizBlockNode; -struct DF_EvalVizBlockNode -{ - DF_EvalVizBlockNode *next; - DF_EvalVizBlock v; -}; - -typedef struct DF_EvalVizBlockList DF_EvalVizBlockList; -struct DF_EvalVizBlockList -{ - DF_EvalVizBlockNode *first; - DF_EvalVizBlockNode *last; - U64 count; - U64 total_visual_row_count; - U64 total_semantic_row_count; -}; - -typedef struct DF_EvalVizBlockArray DF_EvalVizBlockArray; -struct DF_EvalVizBlockArray -{ - DF_EvalVizBlock *v; - U64 count; - U64 total_visual_row_count; - U64 total_semantic_row_count; -}; - -typedef U32 DF_EvalVizStringFlags; -enum -{ - DF_EvalVizStringFlag_ReadOnlyDisplayRules = (1<<0), -}; - -// TODO(rjf): move viz-row stuff to gfx layer - -typedef U32 DF_EvalVizRowFlags; -enum -{ - DF_EvalVizRowFlag_CanExpand = (1<<0), - DF_EvalVizRowFlag_CanEditValue = (1<<1), - DF_EvalVizRowFlag_Canvas = (1<<2), - DF_EvalVizRowFlag_ExprIsSpecial= (1<<3), -}; - -typedef struct DF_EvalVizRow DF_EvalVizRow; -struct DF_EvalVizRow -{ - DF_EvalVizRow *next; - DF_EvalVizRowFlags flags; - - // rjf: block info - S32 depth; - DF_ExpandKey parent_key; - DF_ExpandKey key; - - // rjf: evaluation artifacts - DF_Eval eval; - - // rjf: basic visualization contents - String8 display_expr; - String8 edit_expr; - String8 display_value; - String8 edit_value; - TG_KeyList inherited_type_key_chain; - - // rjf: variable-size & hook info - U64 size_in_rows; - U64 skipped_size_in_rows; - U64 chopped_size_in_rows; - struct DF_GfxViewRuleSpec *expand_ui_rule_spec; - struct DF_CfgNode *expand_ui_rule_node; - - // rjf: value area override view rule spec - struct DF_GfxViewRuleSpec *value_ui_rule_spec; - struct DF_CfgNode *value_ui_rule_node; -}; - -typedef struct DF_EvalVizWindowedRowList DF_EvalVizWindowedRowList; -struct DF_EvalVizWindowedRowList -{ - DF_EvalVizRow *first; - DF_EvalVizRow *last; - U64 count; - U64 count_before_visual; - U64 count_before_semantic; -}; - -//////////////////////////////// -//~ rjf: Command Specification Types - -typedef U32 DF_CmdQueryFlags; -enum -{ - DF_CmdQueryFlag_AllowFiles = (1<<0), - DF_CmdQueryFlag_AllowFolders = (1<<1), - DF_CmdQueryFlag_CodeInput = (1<<2), - DF_CmdQueryFlag_KeepOldInput = (1<<3), - DF_CmdQueryFlag_SelectOldInput = (1<<4), - DF_CmdQueryFlag_Required = (1<<5), -}; - -typedef struct DF_CmdQuery DF_CmdQuery; -struct DF_CmdQuery -{ - DF_CmdParamSlot slot; - DF_EntityKind entity_kind; - DF_CmdQueryFlags flags; -}; - -typedef U32 DF_CmdSpecFlags; -enum -{ - DF_CmdSpecFlag_OmitFromLists = (1<<0), -}; - -typedef struct DF_CmdSpecInfo DF_CmdSpecInfo; -struct DF_CmdSpecInfo -{ - String8 string; - String8 description; - String8 search_tags; - String8 display_name; - DF_CmdSpecFlags flags; - DF_CmdQuery query; - DF_IconKind canonical_icon_kind; -}; - -typedef struct DF_CmdSpec DF_CmdSpec; -struct DF_CmdSpec -{ - DF_CmdSpec *hash_next; - DF_CmdSpecInfo info; - U64 registrar_index; - U64 ordering_index; - U64 run_count; -}; - -typedef struct DF_CmdSpecNode DF_CmdSpecNode; -struct DF_CmdSpecNode -{ - DF_CmdSpecNode *next; - DF_CmdSpec *spec; -}; - -typedef struct DF_CmdSpecList DF_CmdSpecList; -struct DF_CmdSpecList -{ - DF_CmdSpecNode *first; - DF_CmdSpecNode *last; - U64 count; -}; - -typedef struct DF_CmdSpecArray DF_CmdSpecArray; -struct DF_CmdSpecArray -{ - DF_CmdSpec **v; - U64 count; -}; - -typedef struct DF_CmdSpecInfoArray DF_CmdSpecInfoArray; -struct DF_CmdSpecInfoArray -{ - DF_CmdSpecInfo *v; - U64 count; -}; - -//////////////////////////////// -//~ rjf: Command Types - -typedef struct DF_Cmd DF_Cmd; -struct DF_Cmd -{ - DF_CmdParams params; - DF_CmdSpec *spec; -}; - -typedef struct DF_CmdNode DF_CmdNode; -struct DF_CmdNode -{ - DF_CmdNode *next; - DF_CmdNode *prev; - DF_Cmd cmd; -}; - -typedef struct DF_CmdList DF_CmdList; -struct DF_CmdList -{ - DF_CmdNode *first; - DF_CmdNode *last; - U64 count; -}; - -//////////////////////////////// -//~ rjf: Main State Caches - -//- rjf: per-entity-kind state cache - -typedef struct DF_EntityListCache DF_EntityListCache; -struct DF_EntityListCache -{ - Arena *arena; - U64 alloc_gen; - DF_EntityList list; -}; - -//- rjf: auto view rules hash table cache - -typedef struct DF_AutoViewRuleNode DF_AutoViewRuleNode; -struct DF_AutoViewRuleNode -{ - DF_AutoViewRuleNode *next; - String8 type; - String8 view_rule; -}; - -typedef struct DF_AutoViewRuleSlot DF_AutoViewRuleSlot; -struct DF_AutoViewRuleSlot -{ - DF_AutoViewRuleNode *first; - DF_AutoViewRuleNode *last; -}; - -typedef struct DF_AutoViewRuleMapCache DF_AutoViewRuleMapCache; -struct DF_AutoViewRuleMapCache -{ - Arena *arena; - U64 slots_count; - DF_AutoViewRuleSlot *slots; -}; - -//- rjf: per-thread unwind cache - -typedef struct DF_UnwindCacheNode DF_UnwindCacheNode; -struct DF_UnwindCacheNode -{ - DF_UnwindCacheNode *next; - DF_UnwindCacheNode *prev; - U64 reggen; - U64 memgen; - Arena *arena; - DF_Handle thread; - CTRL_Unwind unwind; -}; - -typedef struct DF_UnwindCacheSlot DF_UnwindCacheSlot; -struct DF_UnwindCacheSlot -{ - DF_UnwindCacheNode *first; - DF_UnwindCacheNode *last; -}; - -typedef struct DF_UnwindCache DF_UnwindCache; -struct DF_UnwindCache -{ - U64 slots_count; - DF_UnwindCacheSlot *slots; - DF_UnwindCacheNode *free_node; -}; - -//- rjf: per-run tls-base-vaddr cache - -typedef struct DF_RunTLSBaseCacheNode DF_RunTLSBaseCacheNode; -struct DF_RunTLSBaseCacheNode -{ - DF_RunTLSBaseCacheNode *hash_next; - DF_Handle process; - U64 root_vaddr; - U64 rip_vaddr; - U64 tls_base_vaddr; -}; - -typedef struct DF_RunTLSBaseCacheSlot DF_RunTLSBaseCacheSlot; -struct DF_RunTLSBaseCacheSlot -{ - DF_RunTLSBaseCacheNode *first; - DF_RunTLSBaseCacheNode *last; -}; - -typedef struct DF_RunTLSBaseCache DF_RunTLSBaseCache; -struct DF_RunTLSBaseCache -{ - Arena *arena; - U64 slots_count; - DF_RunTLSBaseCacheSlot *slots; -}; - -//- rjf: per-run locals cache - -typedef struct DF_RunLocalsCacheNode DF_RunLocalsCacheNode; -struct DF_RunLocalsCacheNode -{ - DF_RunLocalsCacheNode *hash_next; - DI_Key dbgi_key; - U64 voff; - EVAL_String2NumMap *locals_map; -}; - -typedef struct DF_RunLocalsCacheSlot DF_RunLocalsCacheSlot; -struct DF_RunLocalsCacheSlot -{ - DF_RunLocalsCacheNode *first; - DF_RunLocalsCacheNode *last; -}; - -typedef struct DF_RunLocalsCache DF_RunLocalsCache; -struct DF_RunLocalsCache -{ - Arena *arena; - U64 table_size; - DF_RunLocalsCacheSlot *table; -}; - -//////////////////////////////// -//~ rjf: File Change Detector Shared Data Structure Types - -typedef struct DF_FileScanNode DF_FileScanNode; -struct DF_FileScanNode -{ - DF_FileScanNode *next; - String8 path; - U64 stamp; -}; - -typedef struct DF_FileScanSlot DF_FileScanSlot; -struct DF_FileScanSlot -{ - DF_FileScanNode *first; - DF_FileScanNode *last; -}; - -//////////////////////////////// -//~ rjf: State Delta History Types - -typedef struct DF_StateDelta DF_StateDelta; -struct DF_StateDelta -{ - U64 vaddr; - String8 data; -}; - -typedef struct DF_StateDeltaNode DF_StateDeltaNode; -struct DF_StateDeltaNode -{ - DF_StateDeltaNode *next; - DF_StateDelta v; -}; - -typedef struct DF_StateDeltaBatch DF_StateDeltaBatch; -struct DF_StateDeltaBatch -{ - DF_StateDeltaBatch *next; - DF_StateDeltaNode *first; - DF_StateDeltaNode *last; - U64 gen; - U64 gen_vaddr; -}; - -typedef struct DF_StateDeltaHistory DF_StateDeltaHistory; -struct DF_StateDeltaHistory -{ - Arena *arena; - Arena *side_arenas[Side_COUNT]; // min -> undo; max -> redo - DF_StateDeltaBatch *side_tops[Side_COUNT]; -}; - -//////////////////////////////// -//~ rjf: Main State Types - -//- rjf: architecture info table types - -typedef struct DF_ArchInfoNode DF_ArchInfoNode; -struct DF_ArchInfoNode -{ - DF_ArchInfoNode *hash_next; - String8 key; - String8 val; -}; - -typedef struct DF_ArchInfoSlot DF_ArchInfoSlot; -struct DF_ArchInfoSlot -{ - DF_ArchInfoNode *first; - DF_ArchInfoNode *last; -}; - -//- rjf: name allocator types - -typedef struct DF_NameChunkNode DF_NameChunkNode; -struct DF_NameChunkNode -{ - DF_NameChunkNode *next; - U64 size; -}; - -//- rjf: core bundle state type - -typedef struct DF_State DF_State; -struct DF_State -{ - // rjf: top-level state - Arena *arena; - U64 frame_index; - F64 time_in_seconds; - F32 dt; - F32 seconds_til_autosave; - - // rjf: interaction registers - Arena *frame_arenas[2]; - DF_InteractRegsNode base_interact_regs; - DF_InteractRegsNode *top_interact_regs; - - // rjf: top-level command batch - Arena *root_cmd_arena; - DF_CmdList root_cmds; - - // rjf: output log key - U128 output_log_key; - - // rjf: history cache - DF_StateDeltaHistory *hist; - - // rjf: name allocator - DF_NameChunkNode *free_name_chunks[8]; - - // rjf: entity state - Arena *entities_arena; - DF_Entity *entities_base; - U64 entities_count; - U64 entities_id_gen; - DF_Entity *entities_root; - DF_Entity *entities_free[2]; // [0] -> normal lifetime, not user defined; [1] -> user defined lifetime (& thus undoable) - U64 entities_free_count; - U64 entities_active_count; - B32 entities_mut_soft_halt; - B32 entities_mut_dbg_info_map; - - // rjf: entity query caches - U64 kind_alloc_gens[DF_EntityKind_COUNT]; - DF_EntityListCache kind_caches[DF_EntityKind_COUNT]; - DF_AutoViewRuleMapCache auto_view_rule_cache; - - // rjf: per-run caches - DF_UnwindCache unwind_cache; - U64 tls_base_cache_reggen_idx; - U64 tls_base_cache_memgen_idx; - DF_RunTLSBaseCache tls_base_caches[2]; - U64 tls_base_cache_gen; - U64 locals_cache_reggen_idx; - DF_RunLocalsCache locals_caches[2]; - U64 locals_cache_gen; - U64 member_cache_reggen_idx; - DF_RunLocalsCache member_caches[2]; - U64 member_cache_gen; - - // rjf: eval view cache - DF_EvalViewCache eval_view_cache; - - // rjf: command specification table - U64 total_registrar_count; - U64 cmd_spec_table_size; - DF_CmdSpec **cmd_spec_table; - - // rjf: view rule specification table - U64 view_rule_spec_table_size; - DF_CoreViewRuleSpec **view_rule_spec_table; - - // rjf: freeze state - DF_HandleList frozen_threads; - DF_HandleNode *free_handle_node; - - // rjf: main control context - DF_CtrlCtx ctrl_ctx; - - // rjf: control thread user -> ctrl driving state - Arena *ctrl_last_run_arena; - DF_RunKind ctrl_last_run_kind; - U64 ctrl_last_run_frame_idx; - DF_Handle ctrl_last_run_thread; - CTRL_RunFlags ctrl_last_run_flags; - CTRL_TrapList ctrl_last_run_traps; - U64 ctrl_run_gen; - B32 ctrl_is_running; - B32 ctrl_soft_halt_issued; - Arena *ctrl_msg_arena; - CTRL_MsgList ctrl_msgs; - U64 ctrl_exception_code_filters[(CTRL_ExceptionCodeKind_COUNT+63)/64]; - - // rjf: control thread ctrl -> user reading state - CTRL_EntityStore *ctrl_entity_store; - Arena *ctrl_stop_arena; - CTRL_Event ctrl_last_stop_event; - - // rjf: config reading state - Arena *cfg_path_arenas[DF_CfgSrc_COUNT]; - String8 cfg_paths[DF_CfgSrc_COUNT]; - U64 cfg_cached_timestamp[DF_CfgSrc_COUNT]; - Arena *cfg_arena; - DF_CfgTable cfg_table; - - // rjf: config writing state - B32 cfg_write_issued[DF_CfgSrc_COUNT]; - Arena *cfg_write_arenas[DF_CfgSrc_COUNT]; - String8List cfg_write_data[DF_CfgSrc_COUNT]; - - // rjf: current path - Arena *current_path_arena; - String8 current_path; - - // rjf: architecture info tables - U64 arch_info_x64_table_size; - DF_ArchInfoSlot *arch_info_x64_table; -}; - -//////////////////////////////// -//~ rjf: Globals - -read_only global DF_CmdSpec df_g_nil_cmd_spec = {0}; -read_only global DF_CoreViewRuleSpec df_g_nil_core_view_rule_spec = {0}; -read_only global DF_CfgNode df_g_nil_cfg_node = {&df_g_nil_cfg_node, &df_g_nil_cfg_node, &df_g_nil_cfg_node, &df_g_nil_cfg_node}; -read_only global DF_CfgVal df_g_nil_cfg_val = {&df_g_nil_cfg_val, &df_g_nil_cfg_val, &df_g_nil_cfg_node, &df_g_nil_cfg_node}; -read_only global DF_Entity df_g_nil_entity = -{ - // rjf: tree links - &df_g_nil_entity, - &df_g_nil_entity, - &df_g_nil_entity, - &df_g_nil_entity, - &df_g_nil_entity, - - // rjf: metadata - DF_EntityKind_Nil, - 0, - 0, - 0, - 0, - 0, - 0, - - // rjf: allocationless, simple equipment - {0}, - {0}, - {0}, - 0, - 0, - {0}, - {0}, - 0, - DF_CfgSrc_User, - - // rjf: ctrl entity equipment - 0, - {0}, - Architecture_Null, - 0, - 0, - 0, - {0}, - 0, - - // rjf: name equipment - {0}, - 0, - - // rjf: timestamp - 0, -}; -read_only global DF_EvalView df_g_nil_eval_view = {&df_g_nil_eval_view, &df_g_nil_eval_view}; - -global DF_State *df_state = 0; - -//////////////////////////////// -//~ rjf: Basic Helpers - -internal U64 df_hash_from_seed_string(U64 seed, String8 string); -internal U64 df_hash_from_string(String8 string); -internal U64 df_hash_from_seed_string__case_insensitive(U64 seed, String8 string); -internal U64 df_hash_from_string__case_insensitive(String8 string); - -//////////////////////////////// -//~ rjf: Handle Type Pure Functions - -internal DF_Handle df_handle_zero(void); -internal B32 df_handle_match(DF_Handle a, DF_Handle b); -internal void df_handle_list_push_node(DF_HandleList *list, DF_HandleNode *node); -internal void df_handle_list_push(Arena *arena, DF_HandleList *list, DF_Handle handle); -internal void df_handle_list_remove(DF_HandleList *list, DF_HandleNode *node); -internal DF_HandleNode *df_handle_list_find(DF_HandleList *list, DF_Handle handle); -internal DF_HandleList df_push_handle_list_copy(Arena *arena, DF_HandleList list); - -//////////////////////////////// -//~ rjf: State History Data Structure - -internal DF_StateDeltaHistory *df_state_delta_history_alloc(void); -internal void df_state_delta_history_release(DF_StateDeltaHistory *hist); -internal void df_state_delta_history_push_batch(DF_StateDeltaHistory *hist, U64 *optional_gen_ptr); -internal void df_state_delta_history_push_delta(DF_StateDeltaHistory *hist, void *ptr, U64 size); -#define df_state_delta_history_push_struct_delta(hist, ptr) df_state_delta_history_push_delta((hist), (ptr), sizeof(*(ptr))) -internal void df_state_delta_history_wind(DF_StateDeltaHistory *hist, Side side); - -//////////////////////////////// -//~ rjf: Sparse Tree Expansion State Data Structure - -//- rjf: keys -internal DF_ExpandKey df_expand_key_make(U64 parent_hash, U64 child_num); -internal DF_ExpandKey df_expand_key_zero(void); -internal B32 df_expand_key_match(DF_ExpandKey a, DF_ExpandKey b); - -//- rjf: table -internal void df_expand_tree_table_init(Arena *arena, DF_ExpandTreeTable *table, U64 slot_count); -internal DF_ExpandNode *df_expand_node_from_key(DF_ExpandTreeTable *table, DF_ExpandKey key); -internal B32 df_expand_key_is_set(DF_ExpandTreeTable *table, DF_ExpandKey key); -internal void df_expand_set_expansion(Arena *arena, DF_ExpandTreeTable *table, DF_ExpandKey parent_key, DF_ExpandKey key, B32 expanded); - -//////////////////////////////// -//~ rjf: Config Type Pure Functions - -internal DF_CfgNode *df_cfg_tree_copy(Arena *arena, DF_CfgNode *src_root); -internal DF_CfgNodeRec df_cfg_node_rec__depth_first_pre(DF_CfgNode *node, DF_CfgNode *root); -internal void df_cfg_table_push_unparsed_string(Arena *arena, DF_CfgTable *table, String8 string, DF_CfgSrc source); -internal DF_CfgTable df_cfg_table_from_inheritance(Arena *arena, DF_CfgTable *src); -internal DF_CfgTable df_cfg_table_copy(Arena *arena, DF_CfgTable *src); -internal DF_CfgVal *df_cfg_val_from_string(DF_CfgTable *table, String8 string); -internal DF_CfgNode *df_cfg_node_child_from_string(DF_CfgNode *node, String8 string, StringMatchFlags flags); -internal DF_CfgNode *df_first_cfg_node_child_from_flags(DF_CfgNode *node, DF_CfgNodeFlags flags); -internal String8 df_string_from_cfg_node_children(Arena *arena, DF_CfgNode *node); -internal Vec4F32 df_hsva_from_cfg_node(DF_CfgNode *node); -internal String8 df_string_from_cfg_node_key(DF_CfgNode *node, String8 key, StringMatchFlags flags); - -//////////////////////////////// -//~ rjf: Disassembly Pure Functions - -internal DF_Inst df_single_inst_from_machine_code__x64(Arena *arena, U64 start_voff, String8 string); -internal DF_Inst df_single_inst_from_machine_code(Arena *arena, Architecture arch, U64 start_voff, String8 string); - -//////////////////////////////// -//~ rjf: Debug Info Extraction Type Pure Functions - -internal DF_LineList df_line_list_copy(Arena *arena, DF_LineList *list); - -//////////////////////////////// -//~ rjf: Control Flow Analysis Pure Functions - -internal DF_CtrlFlowInfo df_ctrl_flow_info_from_vaddr_code__x64(Arena *arena, DF_InstFlags exit_points_mask, U64 vaddr, String8 code); -internal DF_CtrlFlowInfo df_ctrl_flow_info_from_arch_vaddr_code(Arena *arena, DF_InstFlags exit_points_mask, Architecture arch, U64 vaddr, String8 code); - -//////////////////////////////// -//~ rjf: Command Type Pure Functions - -//- rjf: specs -internal B32 df_cmd_spec_is_nil(DF_CmdSpec *spec); -internal void df_cmd_spec_list_push(Arena *arena, DF_CmdSpecList *list, DF_CmdSpec *spec); -internal DF_CmdSpecArray df_cmd_spec_array_from_list(Arena *arena, DF_CmdSpecList list); -internal int df_qsort_compare_cmd_spec__run_counter(DF_CmdSpec **a, DF_CmdSpec **b); -internal void df_cmd_spec_array_sort_by_run_counter__in_place(DF_CmdSpecArray array); -internal DF_Handle df_handle_from_cmd_spec(DF_CmdSpec *spec); -internal DF_CmdSpec *df_cmd_spec_from_handle(DF_Handle handle); - -//- rjf: string -> command parsing -internal String8 df_cmd_name_part_from_string(String8 string); -internal String8 df_cmd_arg_part_from_string(String8 string); - -//- rjf: command parameter bundles -internal DF_CmdParams df_cmd_params_zero(void); -internal void df_cmd_params_mark_slot(DF_CmdParams *params, DF_CmdParamSlot slot); -internal B32 df_cmd_params_has_slot(DF_CmdParams *params, DF_CmdParamSlot slot); -internal String8 df_cmd_params_apply_spec_query(Arena *arena, DF_CtrlCtx *ctrl_ctx, DF_CmdParams *params, DF_CmdSpec *spec, String8 query); - -//- rjf: command lists -internal void df_cmd_list_push(Arena *arena, DF_CmdList *cmds, DF_CmdParams *params, DF_CmdSpec *spec); - -//- rjf: string -> core layer command kind -internal DF_CoreCmdKind df_core_cmd_kind_from_string(String8 string); - -//////////////////////////////// -//~ rjf: Entity Type Pure Functions - -//- rjf: nil -internal B32 df_entity_is_nil(DF_Entity *entity); -#define df_require_entity_nonnil(entity, if_nil_stmts) do{if(df_entity_is_nil(entity)){if_nil_stmts;}}while(0) - -//- rjf: handle <-> entity conversions -internal U64 df_index_from_entity(DF_Entity *entity); -internal DF_Handle df_handle_from_entity(DF_Entity *entity); -internal DF_Entity *df_entity_from_handle(DF_Handle handle); -internal DF_EntityList df_entity_list_from_handle_list(Arena *arena, DF_HandleList handles); -internal DF_HandleList df_handle_list_from_entity_list(Arena *arena, DF_EntityList entities); - -//- rjf: entity recursion iterators -internal DF_EntityRec df_entity_rec_df(DF_Entity *entity, DF_Entity *subtree_root, U64 sib_off, U64 child_off); -#define df_entity_rec_df_pre(entity, subtree_root) df_entity_rec_df((entity), (subtree_root), OffsetOf(DF_Entity, next), OffsetOf(DF_Entity, first)) -#define df_entity_rec_df_post(entity, subtree_root) df_entity_rec_df((entity), (subtree_root), OffsetOf(DF_Entity, prev), OffsetOf(DF_Entity, last)) - -//- rjf: ancestor/child introspection -internal DF_Entity *df_entity_child_from_kind(DF_Entity *entity, DF_EntityKind kind); -internal DF_Entity *df_entity_ancestor_from_kind(DF_Entity *entity, DF_EntityKind kind); -internal DF_EntityList df_push_entity_child_list_with_kind(Arena *arena, DF_Entity *entity, DF_EntityKind kind); -internal DF_Entity *df_entity_child_from_name_and_kind(DF_Entity *parent, String8 string, DF_EntityKind kind); - -//- rjf: entity list building -internal void df_entity_list_push(Arena *arena, DF_EntityList *list, DF_Entity *entity); -internal DF_EntityArray df_entity_array_from_list(Arena *arena, DF_EntityList *list); -#define df_first_entity_from_list(list) ((list)->first != 0 ? (list)->first->entity : &df_g_nil_entity) - -//- rjf: entity fuzzy list building -internal DF_EntityFuzzyItemArray df_entity_fuzzy_item_array_from_entity_list_needle(Arena *arena, DF_EntityList *list, String8 needle); -internal DF_EntityFuzzyItemArray df_entity_fuzzy_item_array_from_entity_array_needle(Arena *arena, DF_EntityArray *array, String8 needle); - -//- rjf: full path building, from file/folder entities -internal String8 df_full_path_from_entity(Arena *arena, DF_Entity *entity); - -//- rjf: display string entities, for referencing entities in ui -internal String8 df_display_string_from_entity(Arena *arena, DF_Entity *entity); - -//- rjf: extra search tag strings for fuzzy filtering entities -internal String8 df_search_tags_from_entity(Arena *arena, DF_Entity *entity); - -//- rjf: entity -> color operations -internal Vec4F32 df_hsva_from_entity(DF_Entity *entity); -internal Vec4F32 df_rgba_from_entity(DF_Entity *entity); - -//////////////////////////////// -//~ rjf: Name Allocation - -internal U64 df_name_bucket_idx_from_string_size(U64 size); -internal String8 df_name_alloc(DF_StateDeltaHistory *hist, String8 string); -internal void df_name_release(DF_StateDeltaHistory *hist, String8 string); - -//////////////////////////////// -//~ rjf: Entity Stateful Functions - -//- rjf: entity mutation notification codepath -internal void df_entity_notify_mutation(DF_Entity *entity); - -//- rjf: entity allocation + tree forming -internal DF_Entity *df_entity_alloc(DF_StateDeltaHistory *hist, DF_Entity *parent, DF_EntityKind kind); -internal void df_entity_mark_for_deletion(DF_Entity *entity); -internal void df_entity_release(DF_StateDeltaHistory *hist, DF_Entity *entity); -internal void df_entity_change_parent(DF_StateDeltaHistory *hist, DF_Entity *entity, DF_Entity *old_parent, DF_Entity *new_parent); - -//- rjf: entity simple equipment -internal void df_entity_equip_txt_pt(DF_Entity *entity, TxtPt point); -internal void df_entity_equip_txt_pt_alt(DF_Entity *entity, TxtPt point); -internal void df_entity_equip_entity_handle(DF_Entity *entity, DF_Handle handle); -internal void df_entity_equip_b32(DF_Entity *entity, B32 b32); -internal void df_entity_equip_u64(DF_Entity *entity, U64 u64); -internal void df_entity_equip_rng1u64(DF_Entity *entity, Rng1U64 range); -internal void df_entity_equip_color_rgba(DF_Entity *entity, Vec4F32 rgba); -internal void df_entity_equip_color_hsva(DF_Entity *entity, Vec4F32 hsva); -internal void df_entity_equip_death_timer(DF_Entity *entity, F32 seconds_til_death); -internal void df_entity_equip_cfg_src(DF_Entity *entity, DF_CfgSrc cfg_src); -internal void df_entity_equip_timestamp(DF_Entity *entity, U64 timestamp); - -//- rjf: control layer correllation equipment -internal void df_entity_equip_ctrl_machine_id(DF_Entity *entity, CTRL_MachineID machine_id); -internal void df_entity_equip_ctrl_handle(DF_Entity *entity, DMN_Handle handle); -internal void df_entity_equip_arch(DF_Entity *entity, Architecture arch); -internal void df_entity_equip_ctrl_id(DF_Entity *entity, U32 id); -internal void df_entity_equip_stack_base(DF_Entity *entity, U64 stack_base); -internal void df_entity_equip_tls_root(DF_Entity *entity, U64 tls_root); -internal void df_entity_equip_vaddr_rng(DF_Entity *entity, Rng1U64 range); -internal void df_entity_equip_vaddr(DF_Entity *entity, U64 vaddr); - -//- rjf: name equipment -internal void df_entity_equip_name(DF_StateDeltaHistory *hist, DF_Entity *entity, String8 name); -internal void df_entity_equip_namef(DF_StateDeltaHistory *hist, DF_Entity *entity, char *fmt, ...); - -//- rjf: opening folders/files & maintaining the entity model of the filesystem -internal DF_Entity *df_entity_from_path(String8 path, DF_EntityFromPathFlags flags); -internal DF_EntityList df_possible_overrides_from_entity(Arena *arena, DF_Entity *entity); - -//- rjf: top-level state queries -internal DF_Entity *df_entity_root(void); -internal DF_EntityList df_push_entity_list_with_kind(Arena *arena, DF_EntityKind kind); -internal DF_Entity *df_entity_from_id(DF_EntityID id); -internal DF_Entity *df_machine_entity_from_machine_id(CTRL_MachineID machine_id); -internal DF_Entity *df_entity_from_ctrl_handle(CTRL_MachineID machine_id, DMN_Handle handle); -internal DF_Entity *df_entity_from_ctrl_id(CTRL_MachineID machine_id, U32 id); -internal DF_Entity *df_entity_from_name_and_kind(String8 string, DF_EntityKind kind); -internal DF_Entity *df_entity_from_u64_and_kind(U64 u64, DF_EntityKind kind); - -//- rjf: entity freezing state -internal void df_set_thread_freeze_state(DF_Entity *thread, B32 frozen); -internal B32 df_entity_is_frozen(DF_Entity *entity); - -//////////////////////////////// -//~ rjf: Command Stateful Functions - -internal void df_register_cmd_specs(DF_CmdSpecInfoArray specs); -internal DF_CmdSpec *df_cmd_spec_from_string(String8 string); -internal DF_CmdSpec *df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind core_cmd_kind); -internal void df_cmd_spec_counter_inc(DF_CmdSpec *spec); -internal DF_CmdSpecList df_push_cmd_spec_list(Arena *arena); - -//////////////////////////////// -//~ rjf: View Rule Spec Stateful Functions - -internal void df_register_core_view_rule_specs(DF_CoreViewRuleSpecInfoArray specs); -internal DF_CoreViewRuleSpec *df_core_view_rule_spec_from_string(String8 string); - -//////////////////////////////// -//~ rjf: Stepping "Trap Net" Builders - -internal CTRL_TrapList df_trap_net_from_thread__step_over_inst(Arena *arena, DF_Entity *thread); -internal CTRL_TrapList df_trap_net_from_thread__step_over_line(Arena *arena, DF_Entity *thread); -internal CTRL_TrapList df_trap_net_from_thread__step_into_line(Arena *arena, DF_Entity *thread); - -//////////////////////////////// -//~ rjf: Modules & Debug Info Mappings - -//- rjf: module <=> debug info keys -internal DI_Key df_dbgi_key_from_module(DF_Entity *module); -internal DF_EntityList df_modules_from_dbgi_key(Arena *arena, DI_Key *dbgi_key); - -//- rjf: voff <=> vaddr -internal U64 df_base_vaddr_from_module(DF_Entity *module); -internal U64 df_voff_from_vaddr(DF_Entity *module, U64 vaddr); -internal U64 df_vaddr_from_voff(DF_Entity *module, U64 voff); -internal Rng1U64 df_voff_range_from_vaddr_range(DF_Entity *module, Rng1U64 vaddr_rng); -internal Rng1U64 df_vaddr_range_from_voff_range(DF_Entity *module, Rng1U64 voff_rng); - -//////////////////////////////// -//~ rjf: Debug Info Lookups - -//- rjf: voff|vaddr -> symbol lookups -internal String8 df_symbol_name_from_dbgi_key_voff(Arena *arena, DI_Key *dbgi_key, U64 voff); -internal String8 df_symbol_name_from_process_vaddr(Arena *arena, DF_Entity *process, U64 vaddr); - -//- rjf: symbol -> voff lookups -internal U64 df_voff_from_dbgi_key_symbol_name(DI_Key *dbgi_key, String8 symbol_name); -internal U64 df_type_num_from_dbgi_key_name(DI_Key *dbgi_key, String8 name); - -//- rjf: voff -> line info -internal DF_LineList df_lines_from_dbgi_key_voff(Arena *arena, DI_Key *dbgi_key, U64 voff); - -//- rjf: file:line -> line info -internal DF_LineListArray df_lines_array_from_file_line_range(Arena *arena, DF_Entity *file, Rng1S64 line_num_range); -internal DF_LineList df_lines_from_file_line_num(Arena *arena, DF_Entity *file, S64 line_num); - -//- rjf: src -> voff lookups -internal DF_TextLineSrc2DasmInfoListArray df_text_line_src2dasm_info_list_array_from_src_line_range(Arena *arena, DF_Entity *file, Rng1S64 line_num_range); - -//////////////////////////////// -//~ rjf: Process/Thread/Module Info Lookups - -internal DF_Entity *df_module_from_process_vaddr(DF_Entity *process, U64 vaddr); -internal DF_Entity *df_module_from_thread(DF_Entity *thread); -internal U64 df_tls_base_vaddr_from_process_root_rip(DF_Entity *process, U64 root_vaddr, U64 rip_vaddr); -internal Architecture df_architecture_from_entity(DF_Entity *entity); -internal EVAL_String2NumMap *df_push_locals_map_from_dbgi_key_voff(Arena *arena, DI_Scope *scope, DI_Key *dbgi_key, U64 voff); -internal EVAL_String2NumMap *df_push_member_map_from_dbgi_key_voff(Arena *arena, DI_Scope *scope, DI_Key *dbgi_key, U64 voff); -internal B32 df_set_thread_rip(DF_Entity *thread, U64 vaddr); -internal DF_Entity *df_module_from_thread_candidates(DF_Entity *thread, DF_EntityList *candidates); -internal DF_Unwind df_unwind_from_ctrl_unwind(Arena *arena, DI_Scope *di_scope, DF_Entity *process, CTRL_Unwind *base_unwind); -internal DF_UnwindFrame *df_frame_from_unwind_idxs(DF_Unwind *unwind, U64 base_unwind_idx, U64 inline_unwind_idx); - -//////////////////////////////// -//~ rjf: Entity -> Log Entities - -internal DF_Entity *df_log_from_entity(DF_Entity *entity); - -//////////////////////////////// -//~ rjf: Target Controls - -//- rjf: control message dispatching -internal void df_push_ctrl_msg(CTRL_Msg *msg); - -//- rjf: control thread running -internal void df_ctrl_run(DF_RunKind run, DF_Entity *run_thread, CTRL_RunFlags flags, CTRL_TrapList *run_traps); - -//- rjf: stopped info from the control thread -internal CTRL_Event df_ctrl_last_stop_event(void); - -//////////////////////////////// -//~ rjf: Evaluation - -internal B32 df_eval_memory_read(void *u, void *out, U64 addr, U64 size); -internal EVAL_ParseCtx df_eval_parse_ctx_from_process_vaddr(DI_Scope *scope, DF_Entity *process, U64 vaddr); -internal EVAL_ParseCtx df_eval_parse_ctx_from_src_loc(DI_Scope *scope, DF_Entity *file, TxtPt pt); -internal DF_Eval df_eval_from_string(Arena *arena, DI_Scope *scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, String8 string); -internal DF_Eval df_value_mode_eval_from_eval(TG_Graph *graph, RDI_Parsed *rdi, DF_CtrlCtx *ctrl_ctx, DF_Eval eval); -internal DF_Eval df_dynamically_typed_eval_from_eval(TG_Graph *graph, RDI_Parsed *rdi, DF_CtrlCtx *ctrl_ctx, DF_Eval eval); -internal DF_Eval df_eval_from_eval_cfg_table(Arena *arena, DI_Scope *scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_Eval eval, DF_CfgTable *cfg); - -//////////////////////////////// -//~ rjf: Evaluation Views - -//- rjf: keys -internal DF_EvalViewKey df_eval_view_key_make(U64 v0, U64 v1); -internal DF_EvalViewKey df_eval_view_key_from_string(String8 string); -internal DF_EvalViewKey df_eval_view_key_from_stringf(char *fmt, ...); -internal B32 df_eval_view_key_match(DF_EvalViewKey a, DF_EvalViewKey b); - -//- rjf: cache lookup -internal DF_EvalView *df_eval_view_from_key(DF_EvalViewKey key); - -//- rjf: key -> view rules -internal void df_eval_view_set_key_rule(DF_EvalView *eval_view, DF_ExpandKey key, String8 view_rule_string); -internal String8 df_eval_view_rule_from_key(DF_EvalView *eval_view, DF_ExpandKey key); - -//////////////////////////////// -//~ rjf: Evaluation Visualization - -//- rjf: evaluation value string builder helpers -internal String8 df_string_from_ascii_value(Arena *arena, U8 val); -internal String8 df_string_from_simple_typed_eval(Arena *arena, TG_Graph *graph, RDI_Parsed *rdi, DF_EvalVizStringFlags flags, U32 radix, DF_Eval eval); - -//- rjf: writing values back to child processes -internal B32 df_commit_eval_value(TG_Graph *graph, RDI_Parsed *rdi, DF_CtrlCtx *ctrl_ctx, DF_Eval dst_eval, DF_Eval src_eval); - -//- rjf: type helpers -internal TG_MemberArray df_filtered_data_members_from_members_cfg_table(Arena *arena, TG_MemberArray members, DF_CfgTable *cfg); -internal DF_EvalLinkBaseChunkList df_eval_link_base_chunk_list_from_eval(Arena *arena, TG_Graph *graph, RDI_Parsed *rdi, TG_Key link_member_type_key, U64 link_member_off, DF_CtrlCtx *ctrl_ctx, DF_Eval eval, U64 cap); -internal DF_EvalLinkBase df_eval_link_base_from_chunk_list_index(DF_EvalLinkBaseChunkList *list, U64 idx); -internal DF_EvalLinkBaseArray df_eval_link_base_array_from_chunk_list(Arena *arena, DF_EvalLinkBaseChunkList *chunks); - -//- rjf: viz block collection building -internal DF_EvalVizBlock *df_eval_viz_block_begin(Arena *arena, DF_EvalVizBlockKind kind, DF_ExpandKey parent_key, DF_ExpandKey key, S32 depth); -internal DF_EvalVizBlock *df_eval_viz_block_split_and_continue(Arena *arena, DF_EvalVizBlockList *list, DF_EvalVizBlock *split_block, U64 split_idx); -internal void df_eval_viz_block_end(DF_EvalVizBlockList *list, DF_EvalVizBlock *block); -internal void df_append_viz_blocks_for_parent__rec(Arena *arena, DI_Scope *scope, DF_EvalView *view, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_ExpandKey parent_key, DF_ExpandKey key, String8 string, DF_Eval eval, TG_Member *opt_member, DF_CfgTable *cfg_table, S32 depth, DF_EvalVizBlockList *list_out); -internal DF_EvalVizBlockList df_eval_viz_block_list_from_eval_view_expr_keys(Arena *arena, DI_Scope *scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_EvalView *eval_view, String8 expr, DF_ExpandKey parent_key, DF_ExpandKey key); -internal void df_eval_viz_block_list_concat__in_place(DF_EvalVizBlockList *dst, DF_EvalVizBlockList *to_push); - -//- rjf: viz block list <-> table coordinates -internal S64 df_row_num_from_viz_block_list_key(DF_EvalVizBlockList *blocks, DF_ExpandKey key); -internal DF_ExpandKey df_key_from_viz_block_list_row_num(DF_EvalVizBlockList *blocks, S64 row_num); -internal DF_ExpandKey df_parent_key_from_viz_block_list_row_num(DF_EvalVizBlockList *blocks, S64 row_num); - -//- rjf: viz row list building -internal DF_EvalVizRow *df_eval_viz_row_list_push_new(Arena *arena, EVAL_ParseCtx *parse_ctx, DF_EvalVizWindowedRowList *rows, DF_EvalVizBlock *block, DF_ExpandKey key, DF_Eval eval); - -//////////////////////////////// -//~ rjf: Main State Accessors/Mutators - -//- rjf: frame data -internal F32 df_dt(void); -internal U64 df_frame_index(void); -internal Arena *df_frame_arena(void); -internal F64 df_time_in_seconds(void); - -//- rjf: interaction registers -internal DF_InteractRegs *df_interact_regs(void); -internal DF_InteractRegs *df_push_interact_regs(void); -internal DF_InteractRegs *df_pop_interact_regs(void); - -//- rjf: undo/redo history -internal DF_StateDeltaHistory *df_state_delta_history(void); - -//- rjf: control state -internal DF_RunKind df_ctrl_last_run_kind(void); -internal U64 df_ctrl_last_run_frame_idx(void); -internal U64 df_ctrl_run_gen(void); -internal B32 df_ctrl_targets_running(void); - -//- rjf: control context -internal DF_CtrlCtx df_ctrl_ctx(void); -internal void df_ctrl_ctx_apply_overrides(DF_CtrlCtx *ctx, DF_CtrlCtx *overrides); - -//- rjf: config paths -internal String8 df_cfg_path_from_src(DF_CfgSrc src); - -//- rjf: config state -internal DF_CfgTable *df_cfg_table(void); - -//- rjf: config serialization -internal String8 df_cfg_escaped_from_raw_string(Arena *arena, String8 string); -internal String8 df_cfg_raw_from_escaped_string(Arena *arena, String8 string); -internal String8List df_cfg_strings_from_core(Arena *arena, String8 root_path, DF_CfgSrc source); -internal void df_cfg_push_write_string(DF_CfgSrc src, String8 string); - -//- rjf: current path -internal String8 df_current_path(void); - -//- rjf: architecture info table lookups -internal String8 df_info_summary_from_string__x64(String8 string); -internal String8 df_info_summary_from_string(Architecture arch, String8 string); - -//- rjf: entity kind cache -internal DF_EntityList df_query_cached_entity_list_with_kind(DF_EntityKind kind); - -//- rjf: active entity based queries -internal DI_KeyList df_push_active_dbgi_key_list(Arena *arena); -internal DF_EntityList df_push_active_target_list(Arena *arena); - -//- rjf: per-run caches -internal CTRL_Unwind df_query_cached_unwind_from_thread(DF_Entity *thread); -internal U64 df_query_cached_rip_from_thread(DF_Entity *thread); -internal U64 df_query_cached_rip_from_thread_unwind(DF_Entity *thread, U64 unwind_count); -internal U64 df_query_cached_tls_base_vaddr_from_process_root_rip(DF_Entity *process, U64 root_vaddr, U64 rip_vaddr); -internal EVAL_String2NumMap *df_query_cached_locals_map_from_dbgi_key_voff(DI_Key *dbgi_key, U64 voff); -internal EVAL_String2NumMap *df_query_cached_member_map_from_dbgi_key_voff(DI_Key *dbgi_key, U64 voff); - -//- rjf: top-level command dispatch -internal void df_push_cmd__root(DF_CmdParams *params, DF_CmdSpec *spec); - -//////////////////////////////// -//~ rjf: Main Layer Top-Level Calls - -internal void df_core_init(CmdLine *cmdln, DF_StateDeltaHistory *hist); -internal DF_CmdList df_core_gather_root_cmds(Arena *arena); -internal void df_core_begin_frame(Arena *arena, DF_CmdList *cmds, F32 dt); -internal void df_core_end_frame(void); - -#endif // DF_CORE_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef DF_CORE_H +#define DF_CORE_H + +//////////////////////////////// +//~ rjf: Handles + +typedef struct DF_Handle DF_Handle; +struct DF_Handle +{ + U64 u64[2]; +}; + +typedef struct DF_HandleNode DF_HandleNode; +struct DF_HandleNode +{ + DF_HandleNode *next; + DF_HandleNode *prev; + DF_Handle handle; +}; + +typedef struct DF_HandleList DF_HandleList; +struct DF_HandleList +{ + DF_HandleNode *first; + DF_HandleNode *last; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Sparse Tree Expansion State Data Structure + +typedef struct DF_ExpandKey DF_ExpandKey; +struct DF_ExpandKey +{ + U64 parent_hash; + U64 child_num; +}; + +typedef struct DF_ExpandNode DF_ExpandNode; +struct DF_ExpandNode +{ + DF_ExpandNode *hash_next; + DF_ExpandNode *hash_prev; + DF_ExpandNode *first; + DF_ExpandNode *last; + DF_ExpandNode *next; + DF_ExpandNode *prev; + DF_ExpandNode *parent; + DF_ExpandKey key; + B32 expanded; + F32 expanded_t; +}; + +typedef struct DF_ExpandSlot DF_ExpandSlot; +struct DF_ExpandSlot +{ + DF_ExpandNode *first; + DF_ExpandNode *last; +}; + +typedef struct DF_ExpandTreeTable DF_ExpandTreeTable; +struct DF_ExpandTreeTable +{ + DF_ExpandSlot *slots; + U64 slots_count; + DF_ExpandNode *free_node; +}; + +//////////////////////////////// +//~ rjf: Control Context Types + +typedef struct DF_CtrlCtx DF_CtrlCtx; +struct DF_CtrlCtx +{ + DF_Handle thread; + U64 unwind_count; + U64 inline_unwind_count; +}; + +//////////////////////////////// +//~ rjf: Entity Kind Flags + +typedef U32 DF_EntityKindFlags; +enum +{ + DF_EntityKindFlag_LeafMutationUserConfig = (1<<0), + DF_EntityKindFlag_TreeMutationUserConfig = (1<<1), + DF_EntityKindFlag_LeafMutationProjectConfig= (1<<2), + DF_EntityKindFlag_TreeMutationProjectConfig= (1<<3), + DF_EntityKindFlag_LeafMutationSoftHalt = (1<<4), + DF_EntityKindFlag_TreeMutationSoftHalt = (1<<5), + DF_EntityKindFlag_LeafMutationDebugInfoMap = (1<<6), + DF_EntityKindFlag_TreeMutationDebugInfoMap = (1<<7), + DF_EntityKindFlag_NameIsCode = (1<<8), + DF_EntityKindFlag_UserDefinedLifetime = (1<<9), +}; + +//////////////////////////////// +//~ rjf: Entity Operation Flags + +typedef U32 DF_EntityOpFlags; +enum +{ + DF_EntityOpFlag_Delete = (1<<0), + DF_EntityOpFlag_Freeze = (1<<1), + DF_EntityOpFlag_Edit = (1<<2), + DF_EntityOpFlag_Rename = (1<<3), + DF_EntityOpFlag_Enable = (1<<4), + DF_EntityOpFlag_Condition = (1<<5), + DF_EntityOpFlag_Duplicate = (1<<6), +}; + +//////////////////////////////// +//~ rjf: Entity Filesystem Lookup Flags + +typedef U32 DF_EntityFromPathFlags; +enum +{ + DF_EntityFromPathFlag_AllowOverrides = (1<<0), + DF_EntityFromPathFlag_OpenAsNeeded = (1<<1), + DF_EntityFromPathFlag_OpenMissing = (1<<2), + + DF_EntityFromPathFlag_All = 0xffffffff, +}; + +//////////////////////////////// +//~ rjf: Debug Engine Control Communication Types + +typedef enum DF_RunKind +{ + DF_RunKind_Run, + DF_RunKind_SingleStep, + DF_RunKind_Step, + DF_RunKind_COUNT +} +DF_RunKind; + +//////////////////////////////// +//~ rjf: Disassembly Types + +typedef U32 DF_InstFlags; +enum +{ + DF_InstFlag_Call = (1<<0), + DF_InstFlag_Branch = (1<<1), + DF_InstFlag_UnconditionalJump = (1<<2), + DF_InstFlag_Return = (1<<3), + DF_InstFlag_NonFlow = (1<<4), + DF_InstFlag_Repeats = (1<<5), + DF_InstFlag_ChangesStackPointer = (1<<6), + DF_InstFlag_ChangesStackPointerVariably = (1<<7), +}; + +typedef struct DF_Inst DF_Inst; +struct DF_Inst +{ + DF_InstFlags flags; + U64 size; + String8 string; + U64 rel_voff; + S64 sp_delta; +}; + +typedef struct DF_InstNode DF_InstNode; +struct DF_InstNode +{ + DF_InstNode *next; + DF_Inst inst; +}; + +typedef struct DF_InstList DF_InstList; +struct DF_InstList +{ + DF_InstNode *first; + DF_InstNode *last; + U64 count; +}; + +typedef struct DF_InstArray DF_InstArray; +struct DF_InstArray +{ + DF_InstArray *v; + U64 count; +}; + +typedef struct DF_InstMemVOffTuple DF_InstMemVOffTuple; +struct DF_InstMemVOffTuple +{ + DF_Inst inst; + String8 mem; + U64 voff; +}; + +typedef struct DF_InstMemVOffTupleArray DF_InstMemVOffTupleArray; +struct DF_InstMemVOffTupleArray +{ + DF_InstMemVOffTuple *v; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Control Flow Analysis Types + +typedef U32 DF_CtrlFlowFlags; +enum +{ + DF_CtrlFlowFlag_StackPointerChangesVariably = (1<<0), +}; + +typedef struct DF_CtrlFlowPoint DF_CtrlFlowPoint; +struct DF_CtrlFlowPoint +{ + U64 vaddr; + U64 jump_dest_vaddr; + S64 expected_sp_delta; + DF_InstFlags inst_flags; +}; + +typedef struct DF_CtrlFlowPointNode DF_CtrlFlowPointNode; +struct DF_CtrlFlowPointNode +{ + DF_CtrlFlowPointNode *next; + DF_CtrlFlowPoint v; +}; + +typedef struct DF_CtrlFlowPointList DF_CtrlFlowPointList; +struct DF_CtrlFlowPointList +{ + DF_CtrlFlowPointNode *first; + DF_CtrlFlowPointNode *last; + U64 count; +}; + +typedef struct DF_CtrlFlowInfo DF_CtrlFlowInfo; +struct DF_CtrlFlowInfo +{ + DF_CtrlFlowFlags flags; + DF_CtrlFlowPointList exit_points; + U64 total_size; + S64 cumulative_sp_delta; +}; + +//////////////////////////////// +//~ rjf: Evaluation Types + +typedef struct DF_Eval DF_Eval; +struct DF_Eval +{ + TG_Key type_key; + EVAL_EvalMode mode; + U64 offset; + union + { + S64 imm_s64; + U64 imm_u64; + F32 imm_f32; + F64 imm_f64; + U64 imm_u128[2]; + }; + EVAL_ErrorList errors; +}; + +//////////////////////////////// +//~ rjf: View Rule Hook Types + +typedef struct DF_CfgNode DF_CfgNode; +typedef struct DF_CfgVal DF_CfgVal; +typedef struct DF_CfgTable DF_CfgTable; +typedef struct DF_EvalView DF_EvalView; +typedef struct DF_EvalVizBlockList DF_EvalVizBlockList; +#define DF_CORE_VIEW_RULE_EVAL_RESOLUTION_FUNCTION_SIG(name) DF_Eval name(Arena *arena, DI_Scope *di_scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_Eval eval, DF_CfgVal *val) +#define DF_CORE_VIEW_RULE_EVAL_RESOLUTION_FUNCTION_NAME(name) df_core_view_rule_eval_resolution__##name +#define DF_CORE_VIEW_RULE_EVAL_RESOLUTION_FUNCTION_DEF(name) internal DF_CORE_VIEW_RULE_EVAL_RESOLUTION_FUNCTION_SIG(DF_CORE_VIEW_RULE_EVAL_RESOLUTION_FUNCTION_NAME(name)) +#define DF_CORE_VIEW_RULE_VIZ_BLOCK_PROD_FUNCTION_SIG(name) void name(Arena *arena, DI_Scope *di_scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_EvalView *eval_view, DF_Eval eval, String8 string, DF_CfgTable *cfg_table, DF_ExpandKey parent_key, DF_ExpandKey key, S32 depth, DF_CfgNode *cfg, struct DF_EvalVizBlockList *out) +#define DF_CORE_VIEW_RULE_VIZ_BLOCK_PROD_FUNCTION_NAME(name) df_core_view_rule_viz_block_prod__##name +#define DF_CORE_VIEW_RULE_VIZ_BLOCK_PROD_FUNCTION_DEF(name) internal DF_CORE_VIEW_RULE_VIZ_BLOCK_PROD_FUNCTION_SIG(DF_CORE_VIEW_RULE_VIZ_BLOCK_PROD_FUNCTION_NAME(name)) +typedef DF_CORE_VIEW_RULE_EVAL_RESOLUTION_FUNCTION_SIG(DF_CoreViewRuleEvalResolutionHookFunctionType); +typedef DF_CORE_VIEW_RULE_VIZ_BLOCK_PROD_FUNCTION_SIG(DF_CoreViewRuleVizBlockProdHookFunctionType); + +//////////////////////////////// +//~ rjf: Generated Code + +#include "df/core/generated/df_core.meta.h" + +//////////////////////////////// +//~ rjf: Config Types + +typedef U32 DF_CfgNodeFlags; +enum +{ + DF_CfgNodeFlag_Identifier = (1<<0), + DF_CfgNodeFlag_Numeric = (1<<1), + DF_CfgNodeFlag_StringLiteral = (1<<2), +}; + +typedef struct DF_CfgNode DF_CfgNode; +struct DF_CfgNode +{ + DF_CfgNode *first; + DF_CfgNode *last; + DF_CfgNode *parent; + DF_CfgNode *next; + DF_CfgNodeFlags flags; + String8 string; + DF_CfgSrc source; +}; + +typedef struct DF_CfgNodeRec DF_CfgNodeRec; +struct DF_CfgNodeRec +{ + DF_CfgNode *next; + S32 push_count; + S32 pop_count; +}; + +typedef struct DF_CfgVal DF_CfgVal; +struct DF_CfgVal +{ + DF_CfgVal *hash_next; + DF_CfgVal *linear_next; + DF_CfgNode *first; + DF_CfgNode *last; + U64 insertion_stamp; + String8 string; +}; + +typedef struct DF_CfgSlot DF_CfgSlot; +struct DF_CfgSlot +{ + DF_CfgVal *first; +}; + +typedef struct DF_CfgTable DF_CfgTable; +struct DF_CfgTable +{ + U64 slot_count; + DF_CfgSlot *slots; + U64 insertion_stamp_counter; + DF_CfgVal *first_val; + DF_CfgVal *last_val; +}; + +//////////////////////////////// +//~ rjf: View Rules + +typedef U32 DF_CoreViewRuleSpecInfoFlags; // NOTE(rjf): see @view_rule_info +enum +{ + DF_CoreViewRuleSpecInfoFlag_Inherited = (1<<0), + DF_CoreViewRuleSpecInfoFlag_Expandable = (1<<1), + DF_CoreViewRuleSpecInfoFlag_EvalResolution = (1<<2), + DF_CoreViewRuleSpecInfoFlag_VizBlockProd = (1<<3), +}; + +typedef struct DF_CoreViewRuleSpecInfo DF_CoreViewRuleSpecInfo; +struct DF_CoreViewRuleSpecInfo +{ + String8 string; + String8 display_string; + String8 schema; + String8 description; + DF_CoreViewRuleSpecInfoFlags flags; + DF_CoreViewRuleEvalResolutionHookFunctionType *eval_resolution; + DF_CoreViewRuleVizBlockProdHookFunctionType *viz_block_prod; +}; + +typedef struct DF_CoreViewRuleSpecInfoArray DF_CoreViewRuleSpecInfoArray; +struct DF_CoreViewRuleSpecInfoArray +{ + DF_CoreViewRuleSpecInfo *v; + U64 count; +}; + +typedef struct DF_CoreViewRuleSpec DF_CoreViewRuleSpec; +struct DF_CoreViewRuleSpec +{ + DF_CoreViewRuleSpec *hash_next; + DF_CoreViewRuleSpecInfo info; +}; + +//////////////////////////////// +//~ rjf: Entity Types + +typedef U32 DF_EntityFlags; +enum +{ + //- rjf: allocationless, simple equipment + DF_EntityFlag_HasTextPoint = (1<<0), + DF_EntityFlag_HasTextPointAlt = (1<<1), + DF_EntityFlag_HasEntityHandle = (1<<2), + DF_EntityFlag_HasB32 = (1<<3), + DF_EntityFlag_HasU64 = (1<<4), + DF_EntityFlag_HasRng1U64 = (1<<5), + DF_EntityFlag_HasColor = (1<<6), + DF_EntityFlag_DiesWithTime = (1<<7), + DF_EntityFlag_DiesOnRunStop = (1<<8), + + //- rjf: ctrl entity equipment + DF_EntityFlag_HasCtrlMachineID = (1<<9), + DF_EntityFlag_HasCtrlHandle = (1<<10), + DF_EntityFlag_HasArch = (1<<11), + DF_EntityFlag_HasCtrlID = (1<<12), + DF_EntityFlag_HasStackBase = (1<<13), + DF_EntityFlag_HasTLSRoot = (1<<14), + DF_EntityFlag_HasVAddrRng = (1<<15), + DF_EntityFlag_HasVAddr = (1<<16), + + //- rjf: file properties + DF_EntityFlag_IsFolder = (1<<17), + DF_EntityFlag_IsMissing = (1<<18), + DF_EntityFlag_Output = (1<<19), // NOTE(rjf): might be missing, but written by us + + //- rjf: deletion + DF_EntityFlag_MarkedForDeletion = (1<<31), +}; + +typedef U64 DF_EntityID; + +typedef struct DF_Entity DF_Entity; +struct DF_Entity +{ + // rjf: tree links + DF_Entity *first; + DF_Entity *last; + DF_Entity *next; + DF_Entity *prev; + DF_Entity *parent; + + // rjf: metadata + DF_EntityKind kind; + DF_EntityFlags flags; + DF_EntityID id; + U64 generation; + U64 alloc_time_us; + B32 deleted; + F32 alive_t; + + // rjf: allocationless, simple equipment + TxtPt text_point; + TxtPt text_point_alt; + DF_Handle entity_handle; + B32 b32; + U64 u64; + Rng1U64 rng1u64; + Vec4F32 color_hsva; + F32 life_left; + DF_CfgSrc cfg_src; + + // rjf: ctrl entity equipment + CTRL_MachineID ctrl_machine_id; + DMN_Handle ctrl_handle; + Architecture arch; + U32 ctrl_id; + U64 stack_base; + U64 tls_root; + Rng1U64 vaddr_rng; + U64 vaddr; + + // rjf: name equipment + String8 name; + U64 name_generation; + + // rjf: timestamp + U64 timestamp; +}; + +typedef struct DF_EntityNode DF_EntityNode; +struct DF_EntityNode +{ + DF_EntityNode *next; + DF_Entity *entity; +}; + +typedef struct DF_EntityList DF_EntityList; +struct DF_EntityList +{ + DF_EntityNode *first; + DF_EntityNode *last; + U64 count; +}; + +typedef struct DF_EntityArray DF_EntityArray; +struct DF_EntityArray +{ + DF_Entity **v; + U64 count; +}; + +typedef struct DF_EntityRec DF_EntityRec; +struct DF_EntityRec +{ + DF_Entity *next; + S32 push_count; + S32 pop_count; +}; + +//////////////////////////////// +//~ rjf: Entity Fuzzy Listing Types + +typedef struct DF_EntityFuzzyItem DF_EntityFuzzyItem; +struct DF_EntityFuzzyItem +{ + DF_Entity *entity; + FuzzyMatchRangeList matches; +}; + +typedef struct DF_EntityFuzzyItemArray DF_EntityFuzzyItemArray; +struct DF_EntityFuzzyItemArray +{ + DF_EntityFuzzyItem *v; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Rich (Including Inline) Unwind Types + +typedef struct DF_UnwindFrame DF_UnwindFrame; +struct DF_UnwindFrame +{ + void *regs; + RDI_Parsed *rdi; + RDI_Procedure *procedure; + RDI_InlineSite *inline_site; + U64 base_unwind_idx; + U64 inline_unwind_idx; +}; + +typedef struct DF_UnwindFrameNode DF_UnwindFrameNode; +struct DF_UnwindFrameNode +{ + DF_UnwindFrameNode *next; + DF_UnwindFrame v; +}; + +typedef struct DF_UnwindFrameList DF_UnwindFrameList; +struct DF_UnwindFrameList +{ + DF_UnwindFrameNode *first; + DF_UnwindFrameNode *last; + U64 count; +}; + +typedef struct DF_UnwindFrameArray DF_UnwindFrameArray; +struct DF_UnwindFrameArray +{ + DF_UnwindFrame *v; + U64 count; +}; + +typedef struct DF_Unwind DF_Unwind; +struct DF_Unwind +{ + DF_UnwindFrameArray frames; +}; + +//////////////////////////////// +//~ rjf: Line Info Types + +typedef struct DF_Line DF_Line; +struct DF_Line +{ + DF_Handle file; + TxtPt pt; + Rng1U64 voff_range; + DI_Key dbgi_key; +}; + +typedef struct DF_LineNode DF_LineNode; +struct DF_LineNode +{ + DF_LineNode *next; + DF_Line v; +}; + +typedef struct DF_LineList DF_LineList; +struct DF_LineList +{ + DF_LineNode *first; + DF_LineNode *last; + U64 count; +}; + +typedef struct DF_LineListArray DF_LineListArray; +struct DF_LineListArray +{ + DF_LineList *v; + U64 count; + DI_KeyList dbgi_keys; +}; + +//////////////////////////////// +//~ rjf: Source <-> Disasm Types + +//- rjf: debug info for mapping src -> disasm + +typedef struct DF_TextLineSrc2DasmInfo DF_TextLineSrc2DasmInfo; +struct DF_TextLineSrc2DasmInfo +{ + Rng1U64 voff_range; + S64 remap_line; + DI_Key dbgi_key; +}; + +typedef struct DF_TextLineSrc2DasmInfoNode DF_TextLineSrc2DasmInfoNode; +struct DF_TextLineSrc2DasmInfoNode +{ + DF_TextLineSrc2DasmInfoNode *next; + DF_TextLineSrc2DasmInfo v; +}; + +typedef struct DF_TextLineSrc2DasmInfoList DF_TextLineSrc2DasmInfoList; +struct DF_TextLineSrc2DasmInfoList +{ + DF_TextLineSrc2DasmInfoNode *first; + DF_TextLineSrc2DasmInfoNode *last; + U64 count; +}; + +typedef struct DF_TextLineSrc2DasmInfoListArray DF_TextLineSrc2DasmInfoListArray; +struct DF_TextLineSrc2DasmInfoListArray +{ + DF_TextLineSrc2DasmInfoList *v; + DI_KeyList dbgi_keys; + U64 count; +}; + +//- rjf: debug info for mapping disasm -> src + +typedef struct DF_TextLineDasm2SrcInfo DF_TextLineDasm2SrcInfo; +struct DF_TextLineDasm2SrcInfo +{ + DI_Key dbgi_key; + DF_Entity *file; + TxtPt pt; + Rng1U64 voff_range; +}; + +typedef struct DF_TextLineDasm2SrcInfoNode DF_TextLineDasm2SrcInfoNode; +struct DF_TextLineDasm2SrcInfoNode +{ + DF_TextLineDasm2SrcInfoNode *next; + DF_TextLineDasm2SrcInfo v; +}; + +typedef struct DF_TextLineDasm2SrcInfoList DF_TextLineDasm2SrcInfoList; +struct DF_TextLineDasm2SrcInfoList +{ + DF_TextLineDasm2SrcInfoNode *first; + DF_TextLineDasm2SrcInfoNode *last; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Interaction Context Register Types + +typedef struct DF_InteractRegs DF_InteractRegs; +struct DF_InteractRegs +{ + DF_Handle module; + DF_Handle process; + DF_Handle thread; + U64 unwind_count; + U64 inline_unwind_count; + DF_Handle window; + DF_Handle panel; + DF_Handle view; + DF_Handle file; + TxtPt cursor; + TxtPt mark; + U128 text_key; + TXT_LangKind lang_kind; + Rng1U64 vaddr_range; + Rng1U64 voff_range; + DF_LineList lines; + DI_Key dbgi_key; +}; + +typedef struct DF_InteractRegsNode DF_InteractRegsNode; +struct DF_InteractRegsNode +{ + DF_InteractRegsNode *next; + DF_InteractRegs v; +}; + +//////////////////////////////// +//~ rjf: Evaluation Visualization Types + +//- rjf: expansion key -> view rule table + +typedef struct DF_EvalViewRuleCacheNode DF_EvalViewRuleCacheNode; +struct DF_EvalViewRuleCacheNode +{ + DF_EvalViewRuleCacheNode *hash_next; + DF_EvalViewRuleCacheNode *hash_prev; + DF_ExpandKey key; + U8 *buffer; + U64 buffer_cap; + U64 buffer_string_size; +}; + +typedef struct DF_EvalViewRuleCacheSlot DF_EvalViewRuleCacheSlot; +struct DF_EvalViewRuleCacheSlot +{ + DF_EvalViewRuleCacheNode *first; + DF_EvalViewRuleCacheNode *last; +}; + +typedef struct DF_EvalViewRuleCacheTable DF_EvalViewRuleCacheTable; +struct DF_EvalViewRuleCacheTable +{ + U64 slot_count; + DF_EvalViewRuleCacheSlot *slots; +}; + +//- rjf: 'eval view' entities for sparse-state expandable tree view cache for evaluation visualization + +typedef struct DF_EvalViewKey DF_EvalViewKey; +struct DF_EvalViewKey +{ + U64 u64[2]; +}; + +typedef struct DF_EvalView DF_EvalView; +struct DF_EvalView +{ + // rjf: links + DF_EvalView *hash_next; + DF_EvalView *hash_prev; + + // rjf: key + DF_EvalViewKey key; + + // rjf: arena + Arena *arena; + + // rjf: expansion state + DF_ExpandTreeTable expand_tree_table; + + // rjf: key -> view rule cache + DF_EvalViewRuleCacheTable view_rule_table; +}; + +typedef struct DF_EvalViewSlot DF_EvalViewSlot; +struct DF_EvalViewSlot +{ + DF_EvalView *first; + DF_EvalView *last; +}; + +typedef struct DF_EvalViewCache DF_EvalViewCache; +struct DF_EvalViewCache +{ + DF_EvalViewSlot *slots; + U64 slots_count; +}; + +//- rjf: eval view visualization building + +typedef struct DF_EvalLinkBase DF_EvalLinkBase; +struct DF_EvalLinkBase +{ + EVAL_EvalMode mode; + U64 offset; +}; + +typedef struct DF_EvalLinkBaseChunkNode DF_EvalLinkBaseChunkNode; +struct DF_EvalLinkBaseChunkNode +{ + DF_EvalLinkBaseChunkNode *next; + DF_EvalLinkBase b[64]; + U64 count; +}; + +typedef struct DF_EvalLinkBaseChunkList DF_EvalLinkBaseChunkList; +struct DF_EvalLinkBaseChunkList +{ + DF_EvalLinkBaseChunkNode *first; + DF_EvalLinkBaseChunkNode *last; + U64 count; +}; + +typedef struct DF_EvalLinkBaseArray DF_EvalLinkBaseArray; +struct DF_EvalLinkBaseArray +{ + DF_EvalLinkBase *v; + U64 count; +}; + +typedef enum DF_EvalVizBlockKind +{ + DF_EvalVizBlockKind_Null, // empty + DF_EvalVizBlockKind_Root, // root of tree or subtree; possibly-expandable expression. + DF_EvalVizBlockKind_Members, // members of struct, class, union + DF_EvalVizBlockKind_EnumMembers, // members of enum + DF_EvalVizBlockKind_Elements, // elements of array + DF_EvalVizBlockKind_Links, // flattened nodes in a linked list + DF_EvalVizBlockKind_Canvas, // escape hatch for arbitrary UI + DF_EvalVizBlockKind_DebugInfoTable, // block of filtered debug info table elements + DF_EvalVizBlockKind_COUNT, +} +DF_EvalVizBlockKind; + +typedef struct DF_EvalVizBlock DF_EvalVizBlock; +struct DF_EvalVizBlock +{ + // rjf: kind & keys + DF_EvalVizBlockKind kind; + DF_ExpandKey parent_key; + DF_ExpandKey key; + S32 depth; + + // rjf: evaluation info + DF_Eval eval; + String8 string; + TG_Member *member; + + // rjf: info about ranges that this block spans + Rng1U64 visual_idx_range; + Rng1U64 semantic_idx_range; + FZY_Target fzy_target; + FZY_ItemArray fzy_backing_items; + + // rjf: visualization config extensions + DF_CfgTable cfg_table; + TG_Key link_member_type_key; + U64 link_member_off; +}; + +typedef struct DF_EvalVizBlockNode DF_EvalVizBlockNode; +struct DF_EvalVizBlockNode +{ + DF_EvalVizBlockNode *next; + DF_EvalVizBlock v; +}; + +typedef struct DF_EvalVizBlockList DF_EvalVizBlockList; +struct DF_EvalVizBlockList +{ + DF_EvalVizBlockNode *first; + DF_EvalVizBlockNode *last; + U64 count; + U64 total_visual_row_count; + U64 total_semantic_row_count; +}; + +typedef struct DF_EvalVizBlockArray DF_EvalVizBlockArray; +struct DF_EvalVizBlockArray +{ + DF_EvalVizBlock *v; + U64 count; + U64 total_visual_row_count; + U64 total_semantic_row_count; +}; + +typedef U32 DF_EvalVizStringFlags; +enum +{ + DF_EvalVizStringFlag_ReadOnlyDisplayRules = (1<<0), +}; + +// TODO(rjf): move viz-row stuff to gfx layer + +typedef U32 DF_EvalVizRowFlags; +enum +{ + DF_EvalVizRowFlag_CanExpand = (1<<0), + DF_EvalVizRowFlag_CanEditValue = (1<<1), + DF_EvalVizRowFlag_Canvas = (1<<2), + DF_EvalVizRowFlag_ExprIsSpecial= (1<<3), +}; + +typedef struct DF_EvalVizRow DF_EvalVizRow; +struct DF_EvalVizRow +{ + DF_EvalVizRow *next; + DF_EvalVizRowFlags flags; + + // rjf: block info + S32 depth; + DF_ExpandKey parent_key; + DF_ExpandKey key; + + // rjf: evaluation artifacts + DF_Eval eval; + + // rjf: basic visualization contents + String8 display_expr; + String8 edit_expr; + String8 display_value; + String8 edit_value; + TG_KeyList inherited_type_key_chain; + + // rjf: variable-size & hook info + U64 size_in_rows; + U64 skipped_size_in_rows; + U64 chopped_size_in_rows; + struct DF_GfxViewRuleSpec *expand_ui_rule_spec; + struct DF_CfgNode *expand_ui_rule_node; + + // rjf: value area override view rule spec + struct DF_GfxViewRuleSpec *value_ui_rule_spec; + struct DF_CfgNode *value_ui_rule_node; +}; + +typedef struct DF_EvalVizWindowedRowList DF_EvalVizWindowedRowList; +struct DF_EvalVizWindowedRowList +{ + DF_EvalVizRow *first; + DF_EvalVizRow *last; + U64 count; + U64 count_before_visual; + U64 count_before_semantic; +}; + +//////////////////////////////// +//~ rjf: Command Specification Types + +typedef U32 DF_CmdQueryFlags; +enum +{ + DF_CmdQueryFlag_AllowFiles = (1<<0), + DF_CmdQueryFlag_AllowFolders = (1<<1), + DF_CmdQueryFlag_CodeInput = (1<<2), + DF_CmdQueryFlag_KeepOldInput = (1<<3), + DF_CmdQueryFlag_SelectOldInput = (1<<4), + DF_CmdQueryFlag_Required = (1<<5), +}; + +typedef struct DF_CmdQuery DF_CmdQuery; +struct DF_CmdQuery +{ + DF_CmdParamSlot slot; + DF_EntityKind entity_kind; + DF_CmdQueryFlags flags; +}; + +typedef U32 DF_CmdSpecFlags; +enum +{ + DF_CmdSpecFlag_OmitFromLists = (1<<0), +}; + +typedef struct DF_CmdSpecInfo DF_CmdSpecInfo; +struct DF_CmdSpecInfo +{ + String8 string; + String8 description; + String8 search_tags; + String8 display_name; + DF_CmdSpecFlags flags; + DF_CmdQuery query; + DF_IconKind canonical_icon_kind; +}; + +typedef struct DF_CmdSpec DF_CmdSpec; +struct DF_CmdSpec +{ + DF_CmdSpec *hash_next; + DF_CmdSpecInfo info; + U64 registrar_index; + U64 ordering_index; + U64 run_count; +}; + +typedef struct DF_CmdSpecNode DF_CmdSpecNode; +struct DF_CmdSpecNode +{ + DF_CmdSpecNode *next; + DF_CmdSpec *spec; +}; + +typedef struct DF_CmdSpecList DF_CmdSpecList; +struct DF_CmdSpecList +{ + DF_CmdSpecNode *first; + DF_CmdSpecNode *last; + U64 count; +}; + +typedef struct DF_CmdSpecArray DF_CmdSpecArray; +struct DF_CmdSpecArray +{ + DF_CmdSpec **v; + U64 count; +}; + +typedef struct DF_CmdSpecInfoArray DF_CmdSpecInfoArray; +struct DF_CmdSpecInfoArray +{ + DF_CmdSpecInfo *v; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Command Types + +typedef struct DF_Cmd DF_Cmd; +struct DF_Cmd +{ + DF_CmdParams params; + DF_CmdSpec *spec; +}; + +typedef struct DF_CmdNode DF_CmdNode; +struct DF_CmdNode +{ + DF_CmdNode *next; + DF_CmdNode *prev; + DF_Cmd cmd; +}; + +typedef struct DF_CmdList DF_CmdList; +struct DF_CmdList +{ + DF_CmdNode *first; + DF_CmdNode *last; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Main State Caches + +//- rjf: per-entity-kind state cache + +typedef struct DF_EntityListCache DF_EntityListCache; +struct DF_EntityListCache +{ + Arena *arena; + U64 alloc_gen; + DF_EntityList list; +}; + +//- rjf: auto view rules hash table cache + +typedef struct DF_AutoViewRuleNode DF_AutoViewRuleNode; +struct DF_AutoViewRuleNode +{ + DF_AutoViewRuleNode *next; + String8 type; + String8 view_rule; +}; + +typedef struct DF_AutoViewRuleSlot DF_AutoViewRuleSlot; +struct DF_AutoViewRuleSlot +{ + DF_AutoViewRuleNode *first; + DF_AutoViewRuleNode *last; +}; + +typedef struct DF_AutoViewRuleMapCache DF_AutoViewRuleMapCache; +struct DF_AutoViewRuleMapCache +{ + Arena *arena; + U64 slots_count; + DF_AutoViewRuleSlot *slots; +}; + +//- rjf: per-thread unwind cache + +typedef struct DF_UnwindCacheNode DF_UnwindCacheNode; +struct DF_UnwindCacheNode +{ + DF_UnwindCacheNode *next; + DF_UnwindCacheNode *prev; + U64 reggen; + U64 memgen; + Arena *arena; + DF_Handle thread; + CTRL_Unwind unwind; +}; + +typedef struct DF_UnwindCacheSlot DF_UnwindCacheSlot; +struct DF_UnwindCacheSlot +{ + DF_UnwindCacheNode *first; + DF_UnwindCacheNode *last; +}; + +typedef struct DF_UnwindCache DF_UnwindCache; +struct DF_UnwindCache +{ + U64 slots_count; + DF_UnwindCacheSlot *slots; + DF_UnwindCacheNode *free_node; +}; + +//- rjf: per-run tls-base-vaddr cache + +typedef struct DF_RunTLSBaseCacheNode DF_RunTLSBaseCacheNode; +struct DF_RunTLSBaseCacheNode +{ + DF_RunTLSBaseCacheNode *hash_next; + DF_Handle process; + U64 root_vaddr; + U64 rip_vaddr; + U64 tls_base_vaddr; +}; + +typedef struct DF_RunTLSBaseCacheSlot DF_RunTLSBaseCacheSlot; +struct DF_RunTLSBaseCacheSlot +{ + DF_RunTLSBaseCacheNode *first; + DF_RunTLSBaseCacheNode *last; +}; + +typedef struct DF_RunTLSBaseCache DF_RunTLSBaseCache; +struct DF_RunTLSBaseCache +{ + Arena *arena; + U64 slots_count; + DF_RunTLSBaseCacheSlot *slots; +}; + +//- rjf: per-run locals cache + +typedef struct DF_RunLocalsCacheNode DF_RunLocalsCacheNode; +struct DF_RunLocalsCacheNode +{ + DF_RunLocalsCacheNode *hash_next; + DI_Key dbgi_key; + U64 voff; + EVAL_String2NumMap *locals_map; +}; + +typedef struct DF_RunLocalsCacheSlot DF_RunLocalsCacheSlot; +struct DF_RunLocalsCacheSlot +{ + DF_RunLocalsCacheNode *first; + DF_RunLocalsCacheNode *last; +}; + +typedef struct DF_RunLocalsCache DF_RunLocalsCache; +struct DF_RunLocalsCache +{ + Arena *arena; + U64 table_size; + DF_RunLocalsCacheSlot *table; +}; + +//////////////////////////////// +//~ rjf: File Change Detector Shared Data Structure Types + +typedef struct DF_FileScanNode DF_FileScanNode; +struct DF_FileScanNode +{ + DF_FileScanNode *next; + String8 path; + U64 stamp; +}; + +typedef struct DF_FileScanSlot DF_FileScanSlot; +struct DF_FileScanSlot +{ + DF_FileScanNode *first; + DF_FileScanNode *last; +}; + +//////////////////////////////// +//~ rjf: State Delta History Types + +typedef struct DF_StateDelta DF_StateDelta; +struct DF_StateDelta +{ + U64 vaddr; + String8 data; +}; + +typedef struct DF_StateDeltaNode DF_StateDeltaNode; +struct DF_StateDeltaNode +{ + DF_StateDeltaNode *next; + DF_StateDelta v; +}; + +typedef struct DF_StateDeltaBatch DF_StateDeltaBatch; +struct DF_StateDeltaBatch +{ + DF_StateDeltaBatch *next; + DF_StateDeltaNode *first; + DF_StateDeltaNode *last; + U64 gen; + U64 gen_vaddr; +}; + +typedef struct DF_StateDeltaHistory DF_StateDeltaHistory; +struct DF_StateDeltaHistory +{ + Arena *arena; + Arena *side_arenas[Side_COUNT]; // min -> undo; max -> redo + DF_StateDeltaBatch *side_tops[Side_COUNT]; +}; + +//////////////////////////////// +//~ rjf: Main State Types + +//- rjf: architecture info table types + +typedef struct DF_ArchInfoNode DF_ArchInfoNode; +struct DF_ArchInfoNode +{ + DF_ArchInfoNode *hash_next; + String8 key; + String8 val; +}; + +typedef struct DF_ArchInfoSlot DF_ArchInfoSlot; +struct DF_ArchInfoSlot +{ + DF_ArchInfoNode *first; + DF_ArchInfoNode *last; +}; + +//- rjf: name allocator types + +typedef struct DF_NameChunkNode DF_NameChunkNode; +struct DF_NameChunkNode +{ + DF_NameChunkNode *next; + U64 size; +}; + +//- rjf: core bundle state type + +typedef struct DF_State DF_State; +struct DF_State +{ + // rjf: top-level state + Arena *arena; + U64 frame_index; + F64 time_in_seconds; + F32 dt; + F32 seconds_til_autosave; + + // rjf: interaction registers + Arena *frame_arenas[2]; + DF_InteractRegsNode base_interact_regs; + DF_InteractRegsNode *top_interact_regs; + + // rjf: top-level command batch + Arena *root_cmd_arena; + DF_CmdList root_cmds; + + // rjf: output log key + U128 output_log_key; + + // rjf: history cache + DF_StateDeltaHistory *hist; + + // rjf: name allocator + DF_NameChunkNode *free_name_chunks[8]; + + // rjf: entity state + Arena *entities_arena; + DF_Entity *entities_base; + U64 entities_count; + U64 entities_id_gen; + DF_Entity *entities_root; + DF_Entity *entities_free[2]; // [0] -> normal lifetime, not user defined; [1] -> user defined lifetime (& thus undoable) + U64 entities_free_count; + U64 entities_active_count; + B32 entities_mut_soft_halt; + B32 entities_mut_dbg_info_map; + + // rjf: entity query caches + U64 kind_alloc_gens[DF_EntityKind_COUNT]; + DF_EntityListCache kind_caches[DF_EntityKind_COUNT]; + DF_AutoViewRuleMapCache auto_view_rule_cache; + + // rjf: per-run caches + DF_UnwindCache unwind_cache; + U64 tls_base_cache_reggen_idx; + U64 tls_base_cache_memgen_idx; + DF_RunTLSBaseCache tls_base_caches[2]; + U64 tls_base_cache_gen; + U64 locals_cache_reggen_idx; + DF_RunLocalsCache locals_caches[2]; + U64 locals_cache_gen; + U64 member_cache_reggen_idx; + DF_RunLocalsCache member_caches[2]; + U64 member_cache_gen; + + // rjf: eval view cache + DF_EvalViewCache eval_view_cache; + + // rjf: command specification table + U64 total_registrar_count; + U64 cmd_spec_table_size; + DF_CmdSpec **cmd_spec_table; + + // rjf: view rule specification table + U64 view_rule_spec_table_size; + DF_CoreViewRuleSpec **view_rule_spec_table; + + // rjf: freeze state + DF_HandleList frozen_threads; + DF_HandleNode *free_handle_node; + + // rjf: main control context + DF_CtrlCtx ctrl_ctx; + + // rjf: control thread user -> ctrl driving state + Arena *ctrl_last_run_arena; + DF_RunKind ctrl_last_run_kind; + U64 ctrl_last_run_frame_idx; + DF_Handle ctrl_last_run_thread; + CTRL_RunFlags ctrl_last_run_flags; + CTRL_TrapList ctrl_last_run_traps; + U64 ctrl_run_gen; + B32 ctrl_is_running; + B32 ctrl_soft_halt_issued; + Arena *ctrl_msg_arena; + CTRL_MsgList ctrl_msgs; + U64 ctrl_exception_code_filters[(CTRL_ExceptionCodeKind_COUNT+63)/64]; + + // rjf: control thread ctrl -> user reading state + CTRL_EntityStore *ctrl_entity_store; + Arena *ctrl_stop_arena; + CTRL_Event ctrl_last_stop_event; + + // rjf: config reading state + Arena *cfg_path_arenas[DF_CfgSrc_COUNT]; + String8 cfg_paths[DF_CfgSrc_COUNT]; + U64 cfg_cached_timestamp[DF_CfgSrc_COUNT]; + Arena *cfg_arena; + DF_CfgTable cfg_table; + + // rjf: config writing state + B32 cfg_write_issued[DF_CfgSrc_COUNT]; + Arena *cfg_write_arenas[DF_CfgSrc_COUNT]; + String8List cfg_write_data[DF_CfgSrc_COUNT]; + + // rjf: current path + Arena *current_path_arena; + String8 current_path; + + // rjf: architecture info tables + U64 arch_info_x64_table_size; + DF_ArchInfoSlot *arch_info_x64_table; +}; + +//////////////////////////////// +//~ rjf: Globals + +read_only global DF_CmdSpec df_g_nil_cmd_spec = {0}; +read_only global DF_CoreViewRuleSpec df_g_nil_core_view_rule_spec = {0}; +read_only global DF_CfgNode df_g_nil_cfg_node = {&df_g_nil_cfg_node, &df_g_nil_cfg_node, &df_g_nil_cfg_node, &df_g_nil_cfg_node}; +read_only global DF_CfgVal df_g_nil_cfg_val = {&df_g_nil_cfg_val, &df_g_nil_cfg_val, &df_g_nil_cfg_node, &df_g_nil_cfg_node}; +read_only global DF_Entity df_g_nil_entity = +{ + // rjf: tree links + &df_g_nil_entity, + &df_g_nil_entity, + &df_g_nil_entity, + &df_g_nil_entity, + &df_g_nil_entity, + + // rjf: metadata + DF_EntityKind_Nil, + 0, + 0, + 0, + 0, + 0, + 0, + + // rjf: allocationless, simple equipment + {0}, + {0}, + {0}, + 0, + 0, + {0}, + {0}, + 0, + DF_CfgSrc_User, + + // rjf: ctrl entity equipment + 0, + {0}, + Architecture_Null, + 0, + 0, + 0, + {0}, + 0, + + // rjf: name equipment + {0}, + 0, + + // rjf: timestamp + 0, +}; +read_only global DF_EvalView df_g_nil_eval_view = {&df_g_nil_eval_view, &df_g_nil_eval_view}; + +global DF_State *df_state = 0; + +//////////////////////////////// +//~ rjf: Basic Helpers + +internal U64 df_hash_from_seed_string(U64 seed, String8 string); +internal U64 df_hash_from_string(String8 string); +internal U64 df_hash_from_seed_string__case_insensitive(U64 seed, String8 string); +internal U64 df_hash_from_string__case_insensitive(String8 string); + +//////////////////////////////// +//~ rjf: Handle Type Pure Functions + +internal DF_Handle df_handle_zero(void); +internal B32 df_handle_match(DF_Handle a, DF_Handle b); +internal void df_handle_list_push_node(DF_HandleList *list, DF_HandleNode *node); +internal void df_handle_list_push(Arena *arena, DF_HandleList *list, DF_Handle handle); +internal void df_handle_list_remove(DF_HandleList *list, DF_HandleNode *node); +internal DF_HandleNode *df_handle_list_find(DF_HandleList *list, DF_Handle handle); +internal DF_HandleList df_push_handle_list_copy(Arena *arena, DF_HandleList list); + +//////////////////////////////// +//~ rjf: State History Data Structure + +internal DF_StateDeltaHistory *df_state_delta_history_alloc(void); +internal void df_state_delta_history_release(DF_StateDeltaHistory *hist); +internal void df_state_delta_history_push_batch(DF_StateDeltaHistory *hist, U64 *optional_gen_ptr); +internal void df_state_delta_history_push_delta(DF_StateDeltaHistory *hist, void *ptr, U64 size); +#define df_state_delta_history_push_struct_delta(hist, ptr) df_state_delta_history_push_delta((hist), (ptr), sizeof(*(ptr))) +internal void df_state_delta_history_wind(DF_StateDeltaHistory *hist, Side side); + +//////////////////////////////// +//~ rjf: Sparse Tree Expansion State Data Structure + +//- rjf: keys +internal DF_ExpandKey df_expand_key_make(U64 parent_hash, U64 child_num); +internal DF_ExpandKey df_expand_key_zero(void); +internal B32 df_expand_key_match(DF_ExpandKey a, DF_ExpandKey b); + +//- rjf: table +internal void df_expand_tree_table_init(Arena *arena, DF_ExpandTreeTable *table, U64 slot_count); +internal DF_ExpandNode *df_expand_node_from_key(DF_ExpandTreeTable *table, DF_ExpandKey key); +internal B32 df_expand_key_is_set(DF_ExpandTreeTable *table, DF_ExpandKey key); +internal void df_expand_set_expansion(Arena *arena, DF_ExpandTreeTable *table, DF_ExpandKey parent_key, DF_ExpandKey key, B32 expanded); + +//////////////////////////////// +//~ rjf: Config Type Pure Functions + +internal DF_CfgNode *df_cfg_tree_copy(Arena *arena, DF_CfgNode *src_root); +internal DF_CfgNodeRec df_cfg_node_rec__depth_first_pre(DF_CfgNode *node, DF_CfgNode *root); +internal void df_cfg_table_push_unparsed_string(Arena *arena, DF_CfgTable *table, String8 string, DF_CfgSrc source); +internal DF_CfgTable df_cfg_table_from_inheritance(Arena *arena, DF_CfgTable *src); +internal DF_CfgTable df_cfg_table_copy(Arena *arena, DF_CfgTable *src); +internal DF_CfgVal *df_cfg_val_from_string(DF_CfgTable *table, String8 string); +internal DF_CfgNode *df_cfg_node_child_from_string(DF_CfgNode *node, String8 string, StringMatchFlags flags); +internal DF_CfgNode *df_first_cfg_node_child_from_flags(DF_CfgNode *node, DF_CfgNodeFlags flags); +internal String8 df_string_from_cfg_node_children(Arena *arena, DF_CfgNode *node); +internal Vec4F32 df_hsva_from_cfg_node(DF_CfgNode *node); +internal String8 df_string_from_cfg_node_key(DF_CfgNode *node, String8 key, StringMatchFlags flags); + +//////////////////////////////// +//~ rjf: Disassembly Pure Functions + +internal DF_Inst df_single_inst_from_machine_code__x64(Arena *arena, U64 start_voff, String8 string); +internal DF_Inst df_single_inst_from_machine_code(Arena *arena, Architecture arch, U64 start_voff, String8 string); + +//////////////////////////////// +//~ rjf: Debug Info Extraction Type Pure Functions + +internal DF_LineList df_line_list_copy(Arena *arena, DF_LineList *list); + +//////////////////////////////// +//~ rjf: Control Flow Analysis Pure Functions + +internal DF_CtrlFlowInfo df_ctrl_flow_info_from_vaddr_code__x64(Arena *arena, DF_InstFlags exit_points_mask, U64 vaddr, String8 code); +internal DF_CtrlFlowInfo df_ctrl_flow_info_from_arch_vaddr_code(Arena *arena, DF_InstFlags exit_points_mask, Architecture arch, U64 vaddr, String8 code); + +//////////////////////////////// +//~ rjf: Command Type Pure Functions + +//- rjf: specs +internal B32 df_cmd_spec_is_nil(DF_CmdSpec *spec); +internal void df_cmd_spec_list_push(Arena *arena, DF_CmdSpecList *list, DF_CmdSpec *spec); +internal DF_CmdSpecArray df_cmd_spec_array_from_list(Arena *arena, DF_CmdSpecList list); +internal int df_qsort_compare_cmd_spec__run_counter(DF_CmdSpec **a, DF_CmdSpec **b); +internal void df_cmd_spec_array_sort_by_run_counter__in_place(DF_CmdSpecArray array); +internal DF_Handle df_handle_from_cmd_spec(DF_CmdSpec *spec); +internal DF_CmdSpec *df_cmd_spec_from_handle(DF_Handle handle); + +//- rjf: string -> command parsing +internal String8 df_cmd_name_part_from_string(String8 string); +internal String8 df_cmd_arg_part_from_string(String8 string); + +//- rjf: command parameter bundles +internal DF_CmdParams df_cmd_params_zero(void); +internal void df_cmd_params_mark_slot(DF_CmdParams *params, DF_CmdParamSlot slot); +internal B32 df_cmd_params_has_slot(DF_CmdParams *params, DF_CmdParamSlot slot); +internal String8 df_cmd_params_apply_spec_query(Arena *arena, DF_CtrlCtx *ctrl_ctx, DF_CmdParams *params, DF_CmdSpec *spec, String8 query); + +//- rjf: command lists +internal void df_cmd_list_push(Arena *arena, DF_CmdList *cmds, DF_CmdParams *params, DF_CmdSpec *spec); + +//- rjf: string -> core layer command kind +internal DF_CoreCmdKind df_core_cmd_kind_from_string(String8 string); + +//////////////////////////////// +//~ rjf: Entity Type Pure Functions + +//- rjf: nil +internal B32 df_entity_is_nil(DF_Entity *entity); +#define df_require_entity_nonnil(entity, if_nil_stmts) do{if(df_entity_is_nil(entity)){if_nil_stmts;}}while(0) + +//- rjf: handle <-> entity conversions +internal U64 df_index_from_entity(DF_Entity *entity); +internal DF_Handle df_handle_from_entity(DF_Entity *entity); +internal DF_Entity *df_entity_from_handle(DF_Handle handle); +internal DF_EntityList df_entity_list_from_handle_list(Arena *arena, DF_HandleList handles); +internal DF_HandleList df_handle_list_from_entity_list(Arena *arena, DF_EntityList entities); + +//- rjf: entity recursion iterators +internal DF_EntityRec df_entity_rec_df(DF_Entity *entity, DF_Entity *subtree_root, U64 sib_off, U64 child_off); +#define df_entity_rec_df_pre(entity, subtree_root) df_entity_rec_df((entity), (subtree_root), OffsetOf(DF_Entity, next), OffsetOf(DF_Entity, first)) +#define df_entity_rec_df_post(entity, subtree_root) df_entity_rec_df((entity), (subtree_root), OffsetOf(DF_Entity, prev), OffsetOf(DF_Entity, last)) + +//- rjf: ancestor/child introspection +internal DF_Entity *df_entity_child_from_kind(DF_Entity *entity, DF_EntityKind kind); +internal DF_Entity *df_entity_ancestor_from_kind(DF_Entity *entity, DF_EntityKind kind); +internal DF_EntityList df_push_entity_child_list_with_kind(Arena *arena, DF_Entity *entity, DF_EntityKind kind); +internal DF_Entity *df_entity_child_from_name_and_kind(DF_Entity *parent, String8 string, DF_EntityKind kind); + +//- rjf: entity list building +internal void df_entity_list_push(Arena *arena, DF_EntityList *list, DF_Entity *entity); +internal DF_EntityArray df_entity_array_from_list(Arena *arena, DF_EntityList *list); +#define df_first_entity_from_list(list) ((list)->first != 0 ? (list)->first->entity : &df_g_nil_entity) + +//- rjf: entity fuzzy list building +internal DF_EntityFuzzyItemArray df_entity_fuzzy_item_array_from_entity_list_needle(Arena *arena, DF_EntityList *list, String8 needle); +internal DF_EntityFuzzyItemArray df_entity_fuzzy_item_array_from_entity_array_needle(Arena *arena, DF_EntityArray *array, String8 needle); + +//- rjf: full path building, from file/folder entities +internal String8 df_full_path_from_entity(Arena *arena, DF_Entity *entity); + +//- rjf: display string entities, for referencing entities in ui +internal String8 df_display_string_from_entity(Arena *arena, DF_Entity *entity); + +//- rjf: extra search tag strings for fuzzy filtering entities +internal String8 df_search_tags_from_entity(Arena *arena, DF_Entity *entity); + +//- rjf: entity -> color operations +internal Vec4F32 df_hsva_from_entity(DF_Entity *entity); +internal Vec4F32 df_rgba_from_entity(DF_Entity *entity); + +//////////////////////////////// +//~ rjf: Name Allocation + +internal U64 df_name_bucket_idx_from_string_size(U64 size); +internal String8 df_name_alloc(DF_StateDeltaHistory *hist, String8 string); +internal void df_name_release(DF_StateDeltaHistory *hist, String8 string); + +//////////////////////////////// +//~ rjf: Entity Stateful Functions + +//- rjf: entity mutation notification codepath +internal void df_entity_notify_mutation(DF_Entity *entity); + +//- rjf: entity allocation + tree forming +internal DF_Entity *df_entity_alloc(DF_StateDeltaHistory *hist, DF_Entity *parent, DF_EntityKind kind); +internal void df_entity_mark_for_deletion(DF_Entity *entity); +internal void df_entity_release(DF_StateDeltaHistory *hist, DF_Entity *entity); +internal void df_entity_change_parent(DF_StateDeltaHistory *hist, DF_Entity *entity, DF_Entity *old_parent, DF_Entity *new_parent); + +//- rjf: entity simple equipment +internal void df_entity_equip_txt_pt(DF_Entity *entity, TxtPt point); +internal void df_entity_equip_txt_pt_alt(DF_Entity *entity, TxtPt point); +internal void df_entity_equip_entity_handle(DF_Entity *entity, DF_Handle handle); +internal void df_entity_equip_b32(DF_Entity *entity, B32 b32); +internal void df_entity_equip_u64(DF_Entity *entity, U64 u64); +internal void df_entity_equip_rng1u64(DF_Entity *entity, Rng1U64 range); +internal void df_entity_equip_color_rgba(DF_Entity *entity, Vec4F32 rgba); +internal void df_entity_equip_color_hsva(DF_Entity *entity, Vec4F32 hsva); +internal void df_entity_equip_death_timer(DF_Entity *entity, F32 seconds_til_death); +internal void df_entity_equip_cfg_src(DF_Entity *entity, DF_CfgSrc cfg_src); +internal void df_entity_equip_timestamp(DF_Entity *entity, U64 timestamp); + +//- rjf: control layer correllation equipment +internal void df_entity_equip_ctrl_machine_id(DF_Entity *entity, CTRL_MachineID machine_id); +internal void df_entity_equip_ctrl_handle(DF_Entity *entity, DMN_Handle handle); +internal void df_entity_equip_arch(DF_Entity *entity, Architecture arch); +internal void df_entity_equip_ctrl_id(DF_Entity *entity, U32 id); +internal void df_entity_equip_stack_base(DF_Entity *entity, U64 stack_base); +internal void df_entity_equip_tls_root(DF_Entity *entity, U64 tls_root); +internal void df_entity_equip_vaddr_rng(DF_Entity *entity, Rng1U64 range); +internal void df_entity_equip_vaddr(DF_Entity *entity, U64 vaddr); + +//- rjf: name equipment +internal void df_entity_equip_name(DF_StateDeltaHistory *hist, DF_Entity *entity, String8 name); +internal void df_entity_equip_namef(DF_StateDeltaHistory *hist, DF_Entity *entity, char *fmt, ...); + +//- rjf: opening folders/files & maintaining the entity model of the filesystem +internal DF_Entity *df_entity_from_path(String8 path, DF_EntityFromPathFlags flags); +internal DF_EntityList df_possible_overrides_from_entity(Arena *arena, DF_Entity *entity); + +//- rjf: top-level state queries +internal DF_Entity *df_entity_root(void); +internal DF_EntityList df_push_entity_list_with_kind(Arena *arena, DF_EntityKind kind); +internal DF_Entity *df_entity_from_id(DF_EntityID id); +internal DF_Entity *df_machine_entity_from_machine_id(CTRL_MachineID machine_id); +internal DF_Entity *df_entity_from_ctrl_handle(CTRL_MachineID machine_id, DMN_Handle handle); +internal DF_Entity *df_entity_from_ctrl_id(CTRL_MachineID machine_id, U32 id); +internal DF_Entity *df_entity_from_name_and_kind(String8 string, DF_EntityKind kind); +internal DF_Entity *df_entity_from_u64_and_kind(U64 u64, DF_EntityKind kind); + +//- rjf: entity freezing state +internal void df_set_thread_freeze_state(DF_Entity *thread, B32 frozen); +internal B32 df_entity_is_frozen(DF_Entity *entity); + +//////////////////////////////// +//~ rjf: Command Stateful Functions + +internal void df_register_cmd_specs(DF_CmdSpecInfoArray specs); +internal DF_CmdSpec *df_cmd_spec_from_string(String8 string); +internal DF_CmdSpec *df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind core_cmd_kind); +internal void df_cmd_spec_counter_inc(DF_CmdSpec *spec); +internal DF_CmdSpecList df_push_cmd_spec_list(Arena *arena); + +//////////////////////////////// +//~ rjf: View Rule Spec Stateful Functions + +internal void df_register_core_view_rule_specs(DF_CoreViewRuleSpecInfoArray specs); +internal DF_CoreViewRuleSpec *df_core_view_rule_spec_from_string(String8 string); + +//////////////////////////////// +//~ rjf: Stepping "Trap Net" Builders + +internal CTRL_TrapList df_trap_net_from_thread__step_over_inst(Arena *arena, DF_Entity *thread); +internal CTRL_TrapList df_trap_net_from_thread__step_over_line(Arena *arena, DF_Entity *thread); +internal CTRL_TrapList df_trap_net_from_thread__step_into_line(Arena *arena, DF_Entity *thread); + +//////////////////////////////// +//~ rjf: Modules & Debug Info Mappings + +//- rjf: module <=> debug info keys +internal DI_Key df_dbgi_key_from_module(DF_Entity *module); +internal DF_EntityList df_modules_from_dbgi_key(Arena *arena, DI_Key *dbgi_key); + +//- rjf: voff <=> vaddr +internal U64 df_base_vaddr_from_module(DF_Entity *module); +internal U64 df_voff_from_vaddr(DF_Entity *module, U64 vaddr); +internal U64 df_vaddr_from_voff(DF_Entity *module, U64 voff); +internal Rng1U64 df_voff_range_from_vaddr_range(DF_Entity *module, Rng1U64 vaddr_rng); +internal Rng1U64 df_vaddr_range_from_voff_range(DF_Entity *module, Rng1U64 voff_rng); + +//////////////////////////////// +//~ rjf: Debug Info Lookups + +//- rjf: voff|vaddr -> symbol lookups +internal String8 df_symbol_name_from_dbgi_key_voff(Arena *arena, DI_Key *dbgi_key, U64 voff); +internal String8 df_symbol_name_from_process_vaddr(Arena *arena, DF_Entity *process, U64 vaddr); + +//- rjf: symbol -> voff lookups +internal U64 df_voff_from_dbgi_key_symbol_name(DI_Key *dbgi_key, String8 symbol_name); +internal U64 df_type_num_from_dbgi_key_name(DI_Key *dbgi_key, String8 name); + +//- rjf: voff -> line info +internal DF_LineList df_lines_from_dbgi_key_voff(Arena *arena, DI_Key *dbgi_key, U64 voff); + +//- rjf: file:line -> line info +internal DF_LineListArray df_lines_array_from_file_line_range(Arena *arena, DF_Entity *file, Rng1S64 line_num_range); +internal DF_LineList df_lines_from_file_line_num(Arena *arena, DF_Entity *file, S64 line_num); + +//- rjf: src -> voff lookups +internal DF_TextLineSrc2DasmInfoListArray df_text_line_src2dasm_info_list_array_from_src_line_range(Arena *arena, DF_Entity *file, Rng1S64 line_num_range); + +//////////////////////////////// +//~ rjf: Process/Thread/Module Info Lookups + +internal DF_Entity *df_module_from_process_vaddr(DF_Entity *process, U64 vaddr); +internal DF_Entity *df_module_from_thread(DF_Entity *thread); +internal U64 df_tls_base_vaddr_from_process_root_rip(DF_Entity *process, U64 root_vaddr, U64 rip_vaddr); +internal Architecture df_architecture_from_entity(DF_Entity *entity); +internal EVAL_String2NumMap *df_push_locals_map_from_dbgi_key_voff(Arena *arena, DI_Scope *scope, DI_Key *dbgi_key, U64 voff); +internal EVAL_String2NumMap *df_push_member_map_from_dbgi_key_voff(Arena *arena, DI_Scope *scope, DI_Key *dbgi_key, U64 voff); +internal B32 df_set_thread_rip(DF_Entity *thread, U64 vaddr); +internal DF_Entity *df_module_from_thread_candidates(DF_Entity *thread, DF_EntityList *candidates); +internal DF_Unwind df_unwind_from_ctrl_unwind(Arena *arena, DI_Scope *di_scope, DF_Entity *process, CTRL_Unwind *base_unwind); +internal DF_UnwindFrame *df_frame_from_unwind_idxs(DF_Unwind *unwind, U64 base_unwind_idx, U64 inline_unwind_idx); + +//////////////////////////////// +//~ rjf: Target Controls + +//- rjf: control message dispatching +internal void df_push_ctrl_msg(CTRL_Msg *msg); + +//- rjf: control thread running +internal void df_ctrl_run(DF_RunKind run, DF_Entity *run_thread, CTRL_RunFlags flags, CTRL_TrapList *run_traps); + +//- rjf: stopped info from the control thread +internal CTRL_Event df_ctrl_last_stop_event(void); + +//////////////////////////////// +//~ rjf: Evaluation + +internal B32 df_eval_memory_read(void *u, void *out, U64 addr, U64 size); +internal EVAL_ParseCtx df_eval_parse_ctx_from_process_vaddr(DI_Scope *scope, DF_Entity *process, U64 vaddr); +internal EVAL_ParseCtx df_eval_parse_ctx_from_src_loc(DI_Scope *scope, DF_Entity *file, TxtPt pt); +internal DF_Eval df_eval_from_string(Arena *arena, DI_Scope *scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, String8 string); +internal DF_Eval df_value_mode_eval_from_eval(TG_Graph *graph, RDI_Parsed *rdi, DF_CtrlCtx *ctrl_ctx, DF_Eval eval); +internal DF_Eval df_dynamically_typed_eval_from_eval(TG_Graph *graph, RDI_Parsed *rdi, DF_CtrlCtx *ctrl_ctx, DF_Eval eval); +internal DF_Eval df_eval_from_eval_cfg_table(Arena *arena, DI_Scope *scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_Eval eval, DF_CfgTable *cfg); + +//////////////////////////////// +//~ rjf: Evaluation Views + +//- rjf: keys +internal DF_EvalViewKey df_eval_view_key_make(U64 v0, U64 v1); +internal DF_EvalViewKey df_eval_view_key_from_string(String8 string); +internal DF_EvalViewKey df_eval_view_key_from_stringf(char *fmt, ...); +internal B32 df_eval_view_key_match(DF_EvalViewKey a, DF_EvalViewKey b); + +//- rjf: cache lookup +internal DF_EvalView *df_eval_view_from_key(DF_EvalViewKey key); + +//- rjf: key -> view rules +internal void df_eval_view_set_key_rule(DF_EvalView *eval_view, DF_ExpandKey key, String8 view_rule_string); +internal String8 df_eval_view_rule_from_key(DF_EvalView *eval_view, DF_ExpandKey key); + +//////////////////////////////// +//~ rjf: Evaluation Visualization + +//- rjf: evaluation value string builder helpers +internal String8 df_string_from_ascii_value(Arena *arena, U8 val); +internal String8 df_string_from_simple_typed_eval(Arena *arena, TG_Graph *graph, RDI_Parsed *rdi, DF_EvalVizStringFlags flags, U32 radix, DF_Eval eval); + +//- rjf: writing values back to child processes +internal B32 df_commit_eval_value(TG_Graph *graph, RDI_Parsed *rdi, DF_CtrlCtx *ctrl_ctx, DF_Eval dst_eval, DF_Eval src_eval); + +//- rjf: type helpers +internal TG_MemberArray df_filtered_data_members_from_members_cfg_table(Arena *arena, TG_MemberArray members, DF_CfgTable *cfg); +internal DF_EvalLinkBaseChunkList df_eval_link_base_chunk_list_from_eval(Arena *arena, TG_Graph *graph, RDI_Parsed *rdi, TG_Key link_member_type_key, U64 link_member_off, DF_CtrlCtx *ctrl_ctx, DF_Eval eval, U64 cap); +internal DF_EvalLinkBase df_eval_link_base_from_chunk_list_index(DF_EvalLinkBaseChunkList *list, U64 idx); +internal DF_EvalLinkBaseArray df_eval_link_base_array_from_chunk_list(Arena *arena, DF_EvalLinkBaseChunkList *chunks); + +//- rjf: viz block collection building +internal DF_EvalVizBlock *df_eval_viz_block_begin(Arena *arena, DF_EvalVizBlockKind kind, DF_ExpandKey parent_key, DF_ExpandKey key, S32 depth); +internal DF_EvalVizBlock *df_eval_viz_block_split_and_continue(Arena *arena, DF_EvalVizBlockList *list, DF_EvalVizBlock *split_block, U64 split_idx); +internal void df_eval_viz_block_end(DF_EvalVizBlockList *list, DF_EvalVizBlock *block); +internal void df_append_viz_blocks_for_parent__rec(Arena *arena, DI_Scope *scope, DF_EvalView *view, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_ExpandKey parent_key, DF_ExpandKey key, String8 string, DF_Eval eval, TG_Member *opt_member, DF_CfgTable *cfg_table, S32 depth, DF_EvalVizBlockList *list_out); +internal DF_EvalVizBlockList df_eval_viz_block_list_from_eval_view_expr_keys(Arena *arena, DI_Scope *scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_EvalView *eval_view, String8 expr, DF_ExpandKey parent_key, DF_ExpandKey key); +internal void df_eval_viz_block_list_concat__in_place(DF_EvalVizBlockList *dst, DF_EvalVizBlockList *to_push); + +//- rjf: viz block list <-> table coordinates +internal S64 df_row_num_from_viz_block_list_key(DF_EvalVizBlockList *blocks, DF_ExpandKey key); +internal DF_ExpandKey df_key_from_viz_block_list_row_num(DF_EvalVizBlockList *blocks, S64 row_num); +internal DF_ExpandKey df_parent_key_from_viz_block_list_row_num(DF_EvalVizBlockList *blocks, S64 row_num); + +//- rjf: viz row list building +internal DF_EvalVizRow *df_eval_viz_row_list_push_new(Arena *arena, EVAL_ParseCtx *parse_ctx, DF_EvalVizWindowedRowList *rows, DF_EvalVizBlock *block, DF_ExpandKey key, DF_Eval eval); + +//////////////////////////////// +//~ rjf: Main State Accessors/Mutators + +//- rjf: frame data +internal F32 df_dt(void); +internal U64 df_frame_index(void); +internal Arena *df_frame_arena(void); +internal F64 df_time_in_seconds(void); + +//- rjf: interaction registers +internal DF_InteractRegs *df_interact_regs(void); +internal DF_InteractRegs *df_push_interact_regs(void); +internal DF_InteractRegs *df_pop_interact_regs(void); + +//- rjf: undo/redo history +internal DF_StateDeltaHistory *df_state_delta_history(void); + +//- rjf: control state +internal DF_RunKind df_ctrl_last_run_kind(void); +internal U64 df_ctrl_last_run_frame_idx(void); +internal U64 df_ctrl_run_gen(void); +internal B32 df_ctrl_targets_running(void); + +//- rjf: control context +internal DF_CtrlCtx df_ctrl_ctx(void); +internal void df_ctrl_ctx_apply_overrides(DF_CtrlCtx *ctx, DF_CtrlCtx *overrides); + +//- rjf: config paths +internal String8 df_cfg_path_from_src(DF_CfgSrc src); + +//- rjf: config state +internal DF_CfgTable *df_cfg_table(void); + +//- rjf: config serialization +internal String8 df_cfg_escaped_from_raw_string(Arena *arena, String8 string); +internal String8 df_cfg_raw_from_escaped_string(Arena *arena, String8 string); +internal String8List df_cfg_strings_from_core(Arena *arena, String8 root_path, DF_CfgSrc source); +internal void df_cfg_push_write_string(DF_CfgSrc src, String8 string); + +//- rjf: current path +internal String8 df_current_path(void); + +//- rjf: architecture info table lookups +internal String8 df_info_summary_from_string__x64(String8 string); +internal String8 df_info_summary_from_string(Architecture arch, String8 string); + +//- rjf: entity kind cache +internal DF_EntityList df_query_cached_entity_list_with_kind(DF_EntityKind kind); + +//- rjf: active entity based queries +internal DI_KeyList df_push_active_dbgi_key_list(Arena *arena); +internal DF_EntityList df_push_active_target_list(Arena *arena); + +//- rjf: per-run caches +internal CTRL_Unwind df_query_cached_unwind_from_thread(DF_Entity *thread); +internal U64 df_query_cached_rip_from_thread(DF_Entity *thread); +internal U64 df_query_cached_rip_from_thread_unwind(DF_Entity *thread, U64 unwind_count); +internal U64 df_query_cached_tls_base_vaddr_from_process_root_rip(DF_Entity *process, U64 root_vaddr, U64 rip_vaddr); +internal EVAL_String2NumMap *df_query_cached_locals_map_from_dbgi_key_voff(DI_Key *dbgi_key, U64 voff); +internal EVAL_String2NumMap *df_query_cached_member_map_from_dbgi_key_voff(DI_Key *dbgi_key, U64 voff); + +//- rjf: top-level command dispatch +internal void df_push_cmd__root(DF_CmdParams *params, DF_CmdSpec *spec); + +//////////////////////////////// +//~ rjf: Main Layer Top-Level Calls + +internal void df_core_init(CmdLine *cmdln, DF_StateDeltaHistory *hist); +internal DF_CmdList df_core_gather_root_cmds(Arena *arena); +internal void df_core_begin_frame(Arena *arena, DF_CmdList *cmds, F32 dt); +internal void df_core_end_frame(void); + +#endif // DF_CORE_H diff --git a/src/df/gfx/df_views.c b/src/df/gfx/df_views.c index f14a1d7e..311330fb 100644 --- a/src/df/gfx/df_views.c +++ b/src/df/gfx/df_views.c @@ -1,9461 +1,9461 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Quick Sort Comparisons - -internal int -df_qsort_compare_file_info__default(DF_FileInfo *a, DF_FileInfo *b) -{ - int result = 0; - if(a->props.flags & FilePropertyFlag_IsFolder && !(b->props.flags & FilePropertyFlag_IsFolder)) - { - result = -1; - } - else if(b->props.flags & FilePropertyFlag_IsFolder && !(a->props.flags & FilePropertyFlag_IsFolder)) - { - result = +1; - } - else - { - result = df_qsort_compare_file_info__filename(a, b); - } - return result; -} - -internal int -df_qsort_compare_file_info__default_filtered(DF_FileInfo *a, DF_FileInfo *b) -{ - int result = 0; - if(a->filename.size < b->filename.size) - { - result = -1; - } - else if(a->filename.size > b->filename.size) - { - result = +1; - } - return result; -} - -internal int -df_qsort_compare_file_info__filename(DF_FileInfo *a, DF_FileInfo *b) -{ - return strncmp((char *)a->filename.str, (char *)b->filename.str, Min(a->filename.size, b->filename.size)); -} - -internal int -df_qsort_compare_file_info__last_modified(DF_FileInfo *a, DF_FileInfo *b) -{ - return ((a->props.modified < b->props.modified) ? -1 : - (a->props.modified > b->props.modified) ? +1 : - 0); -} - -internal int -df_qsort_compare_file_info__size(DF_FileInfo *a, DF_FileInfo *b) -{ - return ((a->props.size < b->props.size) ? -1 : - (a->props.size > b->props.size) ? +1 : - 0); -} - -internal int -df_qsort_compare_process_info(DF_ProcessInfo *a, DF_ProcessInfo *b) -{ - int result = 0; - if(a->pid_match_ranges.count > b->pid_match_ranges.count) - { - result = -1; - } - else if(a->pid_match_ranges.count < b->pid_match_ranges.count) - { - result = +1; - } - else if(a->name_match_ranges.count < b->name_match_ranges.count) - { - result = -1; - } - else if(a->name_match_ranges.count > b->name_match_ranges.count) - { - result = +1; - } - else if(a->attached_match_ranges.count < b->attached_match_ranges.count) - { - result = -1; - } - else if(a->attached_match_ranges.count > b->attached_match_ranges.count) - { - result = +1; - } - return result; -} - -internal int -df_qsort_compare_cmd_lister__strength(DF_CmdListerItem *a, DF_CmdListerItem *b) -{ - int result = 0; - if(a->name_match_ranges.count > b->name_match_ranges.count) - { - result = -1; - } - else if(a->name_match_ranges.count < b->name_match_ranges.count) - { - result = +1; - } - else if(a->desc_match_ranges.count > b->desc_match_ranges.count) - { - result = -1; - } - else if(a->desc_match_ranges.count < b->desc_match_ranges.count) - { - result = +1; - } - else if(a->tags_match_ranges.count > b->tags_match_ranges.count) - { - result = -1; - } - else if(a->tags_match_ranges.count < b->tags_match_ranges.count) - { - result = +1; - } - else if(a->registrar_idx < b->registrar_idx) - { - result = -1; - } - else if(a->registrar_idx > b->registrar_idx) - { - result = +1; - } - else if(a->ordering_idx < b->ordering_idx) - { - result = -1; - } - else if(a->ordering_idx > b->ordering_idx) - { - result = +1; - } - return result; -} - -internal int -df_qsort_compare_entity_lister__strength(DF_EntityListerItem *a, DF_EntityListerItem *b) -{ - int result = 0; - if(a->name_match_ranges.count > b->name_match_ranges.count) - { - result = -1; - } - else if(a->name_match_ranges.count < b->name_match_ranges.count) - { - result = +1; - } - return result; -} - -internal int -df_qsort_compare_settings_item(DF_SettingsItem *a, DF_SettingsItem *b) -{ - int result = 0; - if(a->string_matches.count > b->string_matches.count) - { - result = -1; - } - else if(a->string_matches.count < b->string_matches.count) - { - result = +1; - } - else if(a->kind_string_matches.count > b->kind_string_matches.count) - { - result = -1; - } - else if(a->kind_string_matches.count < b->kind_string_matches.count) - { - result = +1; - } - return result; -} - -//////////////////////////////// -//~ rjf: Command Lister - -internal DF_CmdListerItemList -df_cmd_lister_item_list_from_needle(Arena *arena, String8 needle) -{ - Temp scratch = scratch_begin(&arena, 1); - DF_CmdSpecList specs = df_push_cmd_spec_list(scratch.arena); - DF_CmdListerItemList result = {0}; - for(DF_CmdSpecNode *n = specs.first; n != 0; n = n->next) - { - DF_CmdSpec *spec = n->spec; - if(!(spec->info.flags & DF_CmdSpecFlag_OmitFromLists)) - { - String8 cmd_display_name = spec->info.display_name; - String8 cmd_desc = spec->info.description; - String8 cmd_tags = spec->info.search_tags; - FuzzyMatchRangeList name_matches = fuzzy_match_find(arena, needle, cmd_display_name); - FuzzyMatchRangeList desc_matches = fuzzy_match_find(arena, needle, cmd_desc); - FuzzyMatchRangeList tags_matches = fuzzy_match_find(arena, needle, cmd_tags); - if(name_matches.count == name_matches.needle_part_count || - desc_matches.count == name_matches.needle_part_count || - tags_matches.count > 0 || - name_matches.needle_part_count == 0) - { - DF_CmdListerItemNode *node = push_array(arena, DF_CmdListerItemNode, 1); - node->item.cmd_spec = spec; - node->item.registrar_idx = spec->registrar_index; - node->item.ordering_idx = spec->ordering_index; - node->item.name_match_ranges = name_matches; - node->item.desc_match_ranges = desc_matches; - node->item.tags_match_ranges = tags_matches; - SLLQueuePush(result.first, result.last, node); - result.count += 1; - } - } - } - scratch_end(scratch); - return result; -} - -internal DF_CmdListerItemArray -df_cmd_lister_item_array_from_list(Arena *arena, DF_CmdListerItemList list) -{ - DF_CmdListerItemArray result = {0}; - result.count = list.count; - result.v = push_array(arena, DF_CmdListerItem, result.count); - U64 idx = 0; - for(DF_CmdListerItemNode *n = list.first; n != 0; n = n->next, idx += 1) - { - result.v[idx] = n->item; - } - return result; -} - -internal void -df_cmd_lister_item_array_sort_by_strength__in_place(DF_CmdListerItemArray array) -{ - quick_sort(array.v, array.count, sizeof(DF_CmdListerItem), df_qsort_compare_cmd_lister__strength); -} - -//////////////////////////////// -//~ rjf: System Process Lister - -internal DF_ProcessInfoList -df_process_info_list_from_query(Arena *arena, String8 query) -{ - Temp scratch = scratch_begin(&arena, 1); - - //- rjf: gather PIDs that we're currently attached to - U64 attached_process_count = 0; - U32 *attached_process_pids = 0; - { - DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); - attached_process_count = processes.count; - attached_process_pids = push_array(scratch.arena, U32, attached_process_count); - U64 idx = 0; - for(DF_EntityNode *n = processes.first; n != 0; n = n->next, idx += 1) - { - DF_Entity *process = n->entity; - attached_process_pids[idx] = process->ctrl_id; - } - } - - //- rjf: build list - DF_ProcessInfoList list = {0}; - { - DMN_ProcessIter iter = {0}; - dmn_process_iter_begin(&iter); - for(DMN_ProcessInfo info = {0}; dmn_process_iter_next(scratch.arena, &iter, &info);) - { - // rjf: skip root-level or otherwise 0-pid processes - if(info.pid == 0) - { - continue; - } - - // rjf: determine if this process is attached - B32 is_attached = 0; - for(U64 attached_idx = 0; attached_idx < attached_process_count; attached_idx += 1) - { - if(attached_process_pids[attached_idx] == info.pid) - { - is_attached = 1; - break; - } - } - - // rjf: gather fuzzy matches - FuzzyMatchRangeList attached_match_ranges = {0}; - FuzzyMatchRangeList name_match_ranges = fuzzy_match_find(arena, query, info.name); - FuzzyMatchRangeList pid_match_ranges = fuzzy_match_find(arena, query, push_str8f(scratch.arena, "%i", info.pid)); - if(is_attached) - { - attached_match_ranges = fuzzy_match_find(arena, query, str8_lit("[attached]")); - } - - // rjf: determine if this item is filtered out - B32 matches_query = (query.size == 0 || - (attached_match_ranges.needle_part_count != 0 && attached_match_ranges.count >= attached_match_ranges.needle_part_count) || - (name_match_ranges.count != 0 && name_match_ranges.count >= name_match_ranges.needle_part_count) || - (pid_match_ranges.count != 0 && pid_match_ranges.count >= pid_match_ranges.needle_part_count)); - - // rjf: push if unfiltered - if(matches_query) - { - DF_ProcessInfoNode *n = push_array(arena, DF_ProcessInfoNode, 1); - n->info.info = info; - n->info.info.name = push_str8_copy(arena, info.name); - n->info.is_attached = is_attached; - n->info.attached_match_ranges = attached_match_ranges; - n->info.name_match_ranges = name_match_ranges; - n->info.pid_match_ranges = pid_match_ranges; - SLLQueuePush(list.first, list.last, n); - list.count += 1; - } - } - dmn_process_iter_end(&iter); - } - - scratch_end(scratch); - return list; -} - -internal DF_ProcessInfoArray -df_process_info_array_from_list(Arena *arena, DF_ProcessInfoList list) -{ - DF_ProcessInfoArray array = {0}; - array.count = list.count; - array.v = push_array(arena, DF_ProcessInfo, array.count); - U64 idx = 0; - for(DF_ProcessInfoNode *n = list.first; n != 0; n = n->next, idx += 1) - { - array.v[idx] = n->info; - } - return array; -} - -internal void -df_process_info_array_sort_by_strength__in_place(DF_ProcessInfoArray array) -{ - quick_sort(array.v, array.count, sizeof(DF_ProcessInfo), df_qsort_compare_process_info); -} - -//////////////////////////////// -//~ rjf: Entity Lister - -internal DF_EntityListerItemList -df_entity_lister_item_list_from_needle(Arena *arena, DF_EntityKind kind, DF_EntityFlags omit_flags, String8 needle) -{ - Temp scratch = scratch_begin(&arena, 1); - DF_EntityListerItemList result = {0}; - DF_EntityList ent_list = df_query_cached_entity_list_with_kind(kind); - for(DF_EntityNode *n = ent_list.first; n != 0; n = n->next) - { - DF_Entity *entity = n->entity; - if(!(entity->flags & omit_flags)) - { - String8 display_string = df_display_string_from_entity(scratch.arena, entity); - FuzzyMatchRangeList match_rngs = fuzzy_match_find(arena, needle, display_string); - if(match_rngs.count != 0 || needle.size == 0) - { - DF_EntityListerItemNode *item_n = push_array(arena, DF_EntityListerItemNode, 1); - item_n->item.entity = entity; - item_n->item.name_match_ranges = match_rngs; - SLLQueuePush(result.first, result.last, item_n); - result.count += 1; - } - } - } - scratch_end(scratch); - return result; -} - -internal DF_EntityListerItemArray -df_entity_lister_item_array_from_list(Arena *arena, DF_EntityListerItemList list) -{ - DF_EntityListerItemArray result = {0}; - result.count = list.count; - result.v = push_array(arena, DF_EntityListerItem, result.count); - { - U64 idx = 0; - for(DF_EntityListerItemNode *n = list.first; n != 0; n = n->next, idx += 1) - { - result.v[idx] = n->item; - } - } - return result; -} - -internal void -df_entity_lister_item_array_sort_by_strength__in_place(DF_EntityListerItemArray array) -{ - quick_sort(array.v, array.count, sizeof(DF_EntityListerItem), df_qsort_compare_entity_lister__strength); -} - -//////////////////////////////// -//~ rjf: Code Views - -internal void -df_code_view_init(DF_CodeViewState *cv, DF_View *view) -{ - if(cv->initialized == 0) - { - cv->initialized = 1; - cv->preferred_column = 1; - cv->find_text_arena = df_view_push_arena_ext(view); - view->cursor = view->mark = txt_pt(1, 1); - } - df_view_equip_loading_info(view, 1, 0, 0); - view->loading_t = view->loading_t_target = 1.f; -} - -internal void -df_code_view_cmds(DF_Window *ws, DF_Panel *panel, DF_View *view, DF_CodeViewState *cv, DF_CmdList *cmds, String8 text_data, TXT_TextInfo *text_info, DASM_InstArray *dasm_insts, Rng1U64 dasm_vaddr_range, DI_Key dasm_dbgi_key) -{ - for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) - { - DF_Cmd *cmd = &n->cmd; - - // rjf: mismatched window/panel => skip - if(df_window_from_handle(cmd->params.window) != ws || - df_panel_from_handle(cmd->params.panel) != panel) - { - continue; - } - - // rjf: process - DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); - switch(core_cmd_kind) - { - default: break; - case DF_CoreCmdKind_GoToLine: - { - cv->goto_line_num = cmd->params.text_point.line; - }break; - case DF_CoreCmdKind_CenterCursor: - { - cv->center_cursor = 1; - }break; - case DF_CoreCmdKind_ContainCursor: - { - cv->contain_cursor = 1; - }break; - case DF_CoreCmdKind_FindTextForward: - { - arena_clear(cv->find_text_arena); - cv->find_text_fwd = push_str8_copy(cv->find_text_arena, cmd->params.string); - }break; - case DF_CoreCmdKind_FindTextBackward: - { - arena_clear(cv->find_text_arena); - cv->find_text_bwd = push_str8_copy(cv->find_text_arena, cmd->params.string); - }break; - case DF_CoreCmdKind_ToggleWatchExpressionAtMouse: - { - cv->watch_expr_at_mouse = 1; - }break; - } - } -} - -internal DF_CodeViewBuildResult -df_code_view_build(Arena *arena, DF_Window *ws, DF_Panel *panel, DF_View *view, DF_CodeViewState *cv, DF_CodeViewBuildFlags flags, Rng2F32 rect, String8 text_data, TXT_TextInfo *text_info, DASM_InstArray *dasm_insts, Rng1U64 dasm_vaddr_range, DI_Key dasm_dbgi_key) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(&arena, 1); - HS_Scope *hs_scope = hs_scope_open(); - DI_Scope *di_scope = di_scope_open(); - TXT_Scope *txt_scope = txt_scope_open(); - - ////////////////////////////// - //- rjf: extract invariants - // - DF_CtrlCtx ctrl_ctx = df_ctrl_ctx_from_view(ws, view); - F_Tag code_font = df_font_from_slot(DF_FontSlot_Code); - F32 code_font_size = df_font_size_from_slot(ws, DF_FontSlot_Code); - F32 code_tab_size = f_column_size_from_tag_size(code_font, code_font_size)*df_setting_val_from_code(ws, DF_SettingCode_TabWidth).s32; - F_Metrics code_font_metrics = f_metrics_from_tag_size(code_font, code_font_size); - F32 code_line_height = ceil_f32(f_line_height_from_metrics(&code_font_metrics) * 1.5f); - F32 big_glyph_advance = f_dim_from_tag_size_string(code_font, code_font_size, 0, 0, str8_lit("H")).x; - Vec2F32 panel_box_dim = dim_2f32(rect); - F32 scroll_bar_dim = floor_f32(ui_top_font_size()*1.5f); - Vec2F32 code_area_dim = v2f32(panel_box_dim.x - scroll_bar_dim, panel_box_dim.y - scroll_bar_dim); - S64 num_possible_visible_lines = (S64)(code_area_dim.y/code_line_height)+1; - - ////////////////////////////// - //- rjf: unpack ctrl ctx & make parse ctx - // - DF_Entity *thread = df_entity_from_handle(ctrl_ctx.thread); - U64 unwind_count = ctrl_ctx.unwind_count; - U64 rip_vaddr = df_query_cached_rip_from_thread_unwind(thread, unwind_count); - DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); - EVAL_ParseCtx parse_ctx = df_eval_parse_ctx_from_process_vaddr(di_scope, process, rip_vaddr); - - ////////////////////////////// - //- rjf: determine visible line range / count - // - Rng1S64 visible_line_num_range = r1s64(view->scroll_pos.y.idx + (S64)(view->scroll_pos.y.off) + 1 - !!(view->scroll_pos.y.off < 0), - view->scroll_pos.y.idx + (S64)(view->scroll_pos.y.off) + 1 + num_possible_visible_lines); - Rng1S64 target_visible_line_num_range = r1s64(view->scroll_pos.y.idx + 1, - view->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 = 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 = 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; - } - - ////////////////////////////// - //- rjf: calculate scroll bounds - // - S64 line_size_x = 0; - Rng1S64 scroll_idx_rng[Axis2_COUNT] = {0}; - { - line_size_x = (text_info->lines_max_size*big_glyph_advance*3)/2; - 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); - } - - ////////////////////////////// - //- rjf: calculate line-range-dependent info - // - F32 line_num_width_px = big_glyph_advance * (log10(visible_line_num_range.max) + 3); - F32 priority_margin_width_px = 0; - F32 catchall_margin_width_px = 0; - if(flags & DF_CodeViewBuildFlag_Margins) - { - priority_margin_width_px = big_glyph_advance*3.5f; - catchall_margin_width_px = 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); - - ////////////////////////////// - //- rjf: get active search query - // - String8 search_query = {0}; - Side search_query_side = Side_Invalid; - B32 search_query_is_active = 0; - { - DF_CoreCmdKind query_cmd_kind = df_core_cmd_kind_from_string(ws->query_cmd_spec->info.string); - if(query_cmd_kind == DF_CoreCmdKind_FindTextForward || - query_cmd_kind == DF_CoreCmdKind_FindTextBackward) - { - search_query = str8(ws->query_view_stack_top->query_buffer, ws->query_view_stack_top->query_string_size); - search_query_is_active = 1; - search_query_side = (query_cmd_kind == DF_CoreCmdKind_FindTextForward) ? Side_Max : Side_Min; - } - } - - ////////////////////////////// - //- rjf: prepare code slice info bundle, for the viewable region of text - // - DF_CodeSliceParams code_slice_params = {0}; - { - // rjf: fill basics - code_slice_params.flags = DF_CodeSliceFlag_LineNums|DF_CodeSliceFlag_Clickable; - if(flags & DF_CodeViewBuildFlag_Margins) - { - code_slice_params.flags |= DF_CodeSliceFlag_PriorityMargin|DF_CodeSliceFlag_CatchallMargin; - } - code_slice_params.line_num_range = visible_line_num_range; - code_slice_params.line_text = push_array(scratch.arena, String8, visible_line_count); - code_slice_params.line_ranges = push_array(scratch.arena, Rng1U64, visible_line_count); - code_slice_params.line_tokens = push_array(scratch.arena, TXT_TokenArray, visible_line_count); - code_slice_params.line_bps = push_array(scratch.arena, DF_EntityList, visible_line_count); - code_slice_params.line_ips = push_array(scratch.arena, DF_EntityList, visible_line_count); - code_slice_params.line_pins = push_array(scratch.arena, DF_EntityList, visible_line_count); - code_slice_params.line_vaddrs = push_array(scratch.arena, U64, visible_line_count); - code_slice_params.line_infos = push_array(scratch.arena, DF_LineList, visible_line_count); - code_slice_params.font = code_font; - code_slice_params.font_size = code_font_size; - code_slice_params.tab_size = code_tab_size; - code_slice_params.line_height_px = code_line_height; - code_slice_params.search_query = search_query; - code_slice_params.priority_margin_width_px = priority_margin_width_px; - code_slice_params.catchall_margin_width_px = catchall_margin_width_px; - code_slice_params.line_num_width_px = line_num_width_px; - code_slice_params.line_text_max_width_px = (F32)line_size_x; - code_slice_params.margin_float_off_px = view->scroll_pos.x.idx + view->scroll_pos.x.off; - - // rjf: fill text info - { - 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; visible_line_idx += 1, line_idx += 1, line_num += 1) - { - code_slice_params.line_text[visible_line_idx] = str8_substr(text_data, text_info->lines_ranges[line_idx]); - code_slice_params.line_ranges[visible_line_idx] = text_info->lines_ranges[line_idx]; - code_slice_params.line_tokens[visible_line_idx] = slice.line_tokens[visible_line_idx]; - } - } - - // rjf: find visible breakpoints for source code - ProfScope("find visible breakpoints") - { - DF_Entity *file = df_entity_from_handle(df_interact_regs()->file); - for(DF_Entity *bp = file->first; !df_entity_is_nil(bp); bp = bp->next) - { - if(bp->deleted || bp->kind != DF_EntityKind_Breakpoint) { continue; } - if(visible_line_num_range.min <= bp->text_point.line && bp->text_point.line <= visible_line_num_range.max) - { - U64 slice_line_idx = (bp->text_point.line-visible_line_num_range.min); - df_entity_list_push(scratch.arena, &code_slice_params.line_bps[slice_line_idx], bp); - } - } - } - - // rjf: find live threads mapping to source code - ProfScope("find live threads mapping to this file") - { - DF_Entity *file = df_entity_from_handle(df_interact_regs()->file); - DF_Entity *selected_thread = df_entity_from_handle(ctrl_ctx.thread); - DF_EntityList threads = df_query_cached_entity_list_with_kind(DF_EntityKind_Thread); - for(DF_EntityNode *thread_n = threads.first; thread_n != 0; thread_n = thread_n->next) - { - DF_Entity *thread = thread_n->entity; - DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); - U64 base_unwind_count = (thread == selected_thread) ? ctrl_ctx.unwind_count : 0; - U64 inline_unwind_count = (thread == selected_thread) ? ctrl_ctx.inline_unwind_count : 0; - U64 rip_vaddr = df_query_cached_rip_from_thread_unwind(thread, unwind_count); - U64 last_inst_on_unwound_rip_vaddr = rip_vaddr - !!unwind_count; - DF_Entity *module = df_module_from_process_vaddr(process, last_inst_on_unwound_rip_vaddr); - U64 rip_voff = df_voff_from_vaddr(module, last_inst_on_unwound_rip_vaddr); - DI_Key dbgi_key = df_dbgi_key_from_module(module); - DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, rip_voff); - for(DF_LineNode *n = lines.first; n != 0; n = n->next) - { - if(df_entity_from_handle(n->v.file) == file && visible_line_num_range.min <= n->v.pt.line && n->v.pt.line <= visible_line_num_range.max) - { - U64 slice_line_idx = n->v.pt.line-visible_line_num_range.min; - df_entity_list_push(scratch.arena, &code_slice_params.line_ips[slice_line_idx], thread); - } - } - } - } - - // rjf: find visible watch pins for source code - ProfScope("find visible watch pins") - { - DF_Entity *file = df_entity_from_handle(df_interact_regs()->file); - for(DF_Entity *wp = file->first; !df_entity_is_nil(wp); wp = wp->next) - { - if(wp->deleted || wp->kind != DF_EntityKind_WatchPin) { continue; } - if(visible_line_num_range.min <= wp->text_point.line && wp->text_point.line <= visible_line_num_range.max) - { - U64 slice_line_idx = (wp->text_point.line-visible_line_num_range.min); - df_entity_list_push(scratch.arena, &code_slice_params.line_pins[slice_line_idx], wp); - } - } - } - - // rjf: find all src -> dasm info - ProfScope("find all src -> dasm info") - { - DF_Entity *file = df_entity_from_handle(df_interact_regs()->file); - DF_LineListArray lines_array = df_lines_array_from_file_line_range(scratch.arena, file, visible_line_num_range); - if(lines_array.count != 0) - { - MemoryCopy(code_slice_params.line_infos, lines_array.v, sizeof(DF_LineList)*lines_array.count); - } - code_slice_params.relevant_dbgi_keys = lines_array.dbgi_keys; - } - - // rjf: find live threads mapping to disasm - if(dasm_insts) ProfScope("find live threads mapping to this disassembly") - { - DF_Entity *selected_thread = df_entity_from_handle(ctrl_ctx.thread); - DF_EntityList threads = df_query_cached_entity_list_with_kind(DF_EntityKind_Thread); - for(DF_EntityNode *thread_n = threads.first; thread_n != 0; thread_n = thread_n->next) - { - DF_Entity *thread = thread_n->entity; - U64 unwind_count = (thread == selected_thread) ? ctrl_ctx.unwind_count : 0; - U64 rip_vaddr = df_query_cached_rip_from_thread_unwind(thread, unwind_count); - if(df_entity_ancestor_from_kind(thread, DF_EntityKind_Process) == process && contains_1u64(dasm_vaddr_range, rip_vaddr)) - { - U64 rip_off = rip_vaddr - dasm_vaddr_range.min; - S64 line_num = dasm_inst_array_idx_from_code_off__linear_scan(dasm_insts, rip_off)+1; - if(contains_1s64(visible_line_num_range, line_num)) - { - U64 slice_line_idx = (line_num-visible_line_num_range.min); - df_entity_list_push(scratch.arena, &code_slice_params.line_ips[slice_line_idx], thread); - } - } - } - } - - // rjf: find breakpoints mapping to this disasm - if(dasm_insts) ProfScope("find breakpoints mapping to this disassembly") - { - DF_EntityList bps = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); - for(DF_EntityNode *n = bps.first; n != 0; n = n->next) - { - DF_Entity *bp = n->entity; - if(bp->flags & DF_EntityFlag_HasVAddr && contains_1u64(dasm_vaddr_range, bp->vaddr)) - { - U64 off = bp->vaddr-dasm_vaddr_range.min; - U64 idx = dasm_inst_array_idx_from_code_off__linear_scan(dasm_insts, off); - S64 line_num = (S64)(idx+1); - if(contains_1s64(visible_line_num_range, line_num)) - { - U64 slice_line_idx = (line_num-visible_line_num_range.min); - df_entity_list_push(scratch.arena, &code_slice_params.line_bps[slice_line_idx], bp); - } - } - } - } - - // rjf: find watch pins mapping to this disasm - if(dasm_insts) ProfScope("find watch pins mapping to this disassembly") - { - DF_EntityList pins = df_query_cached_entity_list_with_kind(DF_EntityKind_WatchPin); - for(DF_EntityNode *n = pins.first; n != 0; n = n->next) - { - DF_Entity *pin = n->entity; - if(pin->flags & DF_EntityFlag_HasVAddr && contains_1u64(dasm_vaddr_range, pin->vaddr)) - { - U64 off = pin->vaddr-dasm_vaddr_range.min; - U64 idx = dasm_inst_array_idx_from_code_off__linear_scan(dasm_insts, off); - S64 line_num = (S64)(idx+1); - if(contains_1s64(visible_line_num_range, line_num)) - { - U64 slice_line_idx = (line_num-visible_line_num_range.min); - df_entity_list_push(scratch.arena, &code_slice_params.line_pins[slice_line_idx], pin); - } - } - } - } - - // rjf: fill dasm -> src info - if(dasm_insts) - { - DF_Entity *module = df_module_from_process_vaddr(process, dasm_vaddr_range.min); - DI_Key dbgi_key = df_dbgi_key_from_module(module); - for(S64 line_num = visible_line_num_range.min; line_num < visible_line_num_range.max; line_num += 1) - { - U64 vaddr = dasm_vaddr_range.min + dasm_inst_array_code_off_from_idx(dasm_insts, line_num-1); - U64 voff = df_voff_from_vaddr(module, vaddr); - U64 slice_idx = line_num-visible_line_num_range.min; - code_slice_params.line_vaddrs[slice_idx] = vaddr; - code_slice_params.line_infos[slice_idx] = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, voff); - } - } - - // rjf: add dasm dbgi key to relevant dbgis - if(dasm_insts != 0) - { - di_key_list_push(scratch.arena, &code_slice_params.relevant_dbgi_keys, &dasm_dbgi_key); - } - } - - ////////////////////////////// - //- rjf: build container - // - UI_Box *container_box = &ui_g_nil_box; - { - ui_set_next_pref_width(ui_px(code_area_dim.x, 1)); - ui_set_next_pref_height(ui_px(code_area_dim.y, 1)); - ui_set_next_child_layout_axis(Axis2_Y); - container_box = ui_build_box_from_stringf(UI_BoxFlag_Clip| - UI_BoxFlag_Scroll| - UI_BoxFlag_AllowOverflowX| - UI_BoxFlag_AllowOverflowY, - "###code_area_%p", view); - } - - ////////////////////////////// - //- rjf: cancelled search query -> center cursor - // - if(!search_query_is_active && cv->drifted_for_search) - { - cv->drifted_for_search = 0; - cv->center_cursor = 1; - } - - ////////////////////////////// - //- rjf: do searching operations - // - { - //- rjf: find text (forward) - if(cv->find_text_fwd.size != 0) - { - Temp scratch = scratch_begin(0, 0); - B32 found = 0; - B32 first = 1; - S64 line_num_start = view->cursor.line; - S64 line_num_last = (S64)text_info->lines_count; - for(S64 line_num = line_num_start;; first = 0) - { - // rjf: pop scratch - temp_end(scratch); - - // rjf: gather line info - String8 line_string = str8_substr(text_data, text_info->lines_ranges[line_num-1]); - U64 search_start = 0; - if(view->cursor.line == line_num && first) - { - search_start = view->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) - { - view->cursor.line = line_num; - view->cursor.column = needle_pos+1; - view->mark = view->cursor; - 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; - } - } - cv->center_cursor = found; - if(found == 0) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.string = push_str8f(scratch.arena, "Could not find \"%S\"", cv->find_text_fwd); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - scratch_end(scratch); - } - - //- rjf: find text (backward) - if(cv->find_text_bwd.size != 0) - { - Temp scratch = scratch_begin(0, 0); - B32 found = 0; - B32 first = 1; - S64 line_num_start = view->cursor.line; - S64 line_num_last = (S64)text_info->lines_count; - for(S64 line_num = line_num_start;; first = 0) - { - // rjf: pop scratch - temp_end(scratch); - - // rjf: gather line info - String8 line_string = str8_substr(text_data, text_info->lines_ranges[line_num-1]); - if(view->cursor.line == line_num && first) - { - line_string = str8_prefix(line_string, view->cursor.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) - { - next_needle_pos = needle_pos; - needle_pos += 1; - } - } - if(next_needle_pos < line_string.size) - { - view->cursor.line = line_num; - view->cursor.column = next_needle_pos+1; - view->mark = view->cursor; - 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; - } - } - cv->center_cursor = found; - if(found == 0) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.string = push_str8f(scratch.arena, "Could not find \"%S\"", cv->find_text_bwd); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - scratch_end(scratch); - } - - MemoryZeroStruct(&cv->find_text_fwd); - MemoryZeroStruct(&cv->find_text_bwd); - arena_clear(cv->find_text_arena); - } - - ////////////////////////////// - //- rjf: do goto line - // - if(cv->goto_line_num != 0) - { - S64 line_num = cv->goto_line_num; - cv->goto_line_num = 0; - line_num = Clamp(1, line_num, text_info->lines_count); - view->cursor = view->mark = txt_pt(line_num, 1); - cv->center_cursor = !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] = df_do_txt_controls(text_info, text_data, ClampBot(num_possible_visible_lines, 10) - 10, &view->cursor, &view->mark, &cv->preferred_column); - } - } - - ////////////////////////////// - //- rjf: build container contents - // - UI_Parent(container_box) - { - //- rjf: build fractional space - container_box->view_off.x = container_box->view_off_target.x = view->scroll_pos.x.idx + view->scroll_pos.x.off; - container_box->view_off.y = container_box->view_off_target.y = code_line_height*mod_f32(view->scroll_pos.y.off, 1.f) + code_line_height*(view->scroll_pos.y.off < 0) - code_line_height*(view->scroll_pos.y.off == -1.f && view->scroll_pos.y.idx == 1); - - //- rjf: build code slice - DF_CodeSliceSignal sig = {0}; - UI_Focus(UI_FocusKind_On) - { - sig = df_code_slicef(ws, &ctrl_ctx, &parse_ctx, &code_slice_params, &view->cursor, &view->mark, &cv->preferred_column, "txt_view_%p", view); - } - - //- rjf: press code slice? -> focus panel - if(ui_pressed(sig.base)) - { - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - - //- rjf: dragging & outside region? -> contain cursor - if(ui_dragging(sig.base) && sig.base.event_flags == 0) - { - if(!contains_2f32(sig.base.box->rect, ui_mouse())) - { - cv->contain_cursor = 1; - } - else - { - snap[Axis2_X] = 1; - } - } - - //- rjf: ctrl+pressed? -> go to name - if(ui_pressed(sig.base) && sig.base.event_flags & OS_EventFlag_Ctrl) - { - ui_kill_action(); - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.string = txt_string_from_info_data_txt_rng(text_info, text_data, sig.mouse_expr_rng); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_GoToName)); - } - - //- rjf: watch expr at mouse - if(cv->watch_expr_at_mouse) - { - cv->watch_expr_at_mouse = 0; - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.string = txt_string_from_info_data_txt_rng(text_info, text_data, sig.mouse_expr_rng); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_ToggleWatchExpression)); - } - - //- rjf: selected text on single line, no query? -> set search text - if(!txt_pt_match(view->cursor, view->mark) && view->cursor.line == view->mark.line && search_query.size == 0) - { - String8 text = txt_string_from_info_data_txt_rng(text_info, text_data, txt_rng(view->cursor, view->mark)); - df_set_search_string(text); - } - } - - ////////////////////////////// - //- rjf: apply post-build view snapping rules - // - { - // rjf: contain => snap - if(cv->contain_cursor) - { - cv->contain_cursor = 0; - snap[Axis2_X] = 1; - snap[Axis2_Y] = 1; - } - - // rjf: center cursor - if(cv->center_cursor) - { - cv->center_cursor = 0; - String8 cursor_line = str8_substr(text_data, text_info->lines_ranges[view->cursor.line-1]); - F32 cursor_advance = f_dim_from_tag_size_string(code_font, code_font_size, 0, code_tab_size, str8_prefix(cursor_line, view->cursor.column-1)).x; - - // rjf: scroll x - { - S64 new_idx = (S64)(cursor_advance - code_area_dim.x/2); - new_idx = Clamp(scroll_idx_rng[Axis2_X].min, new_idx, scroll_idx_rng[Axis2_X].max); - ui_scroll_pt_target_idx(&view->scroll_pos.x, new_idx); - snap[Axis2_X] = 0; - } - - // rjf: scroll y - { - S64 new_idx = (view->cursor.line-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(&view->scroll_pos.y, new_idx); - snap[Axis2_Y] = 0; - } - } - - // rjf: snap in X - if(snap[Axis2_X]) - { - String8 cursor_line = str8_substr(text_data, text_info->lines_ranges[view->cursor.line-1]); - S64 cursor_off = (S64)(f_dim_from_tag_size_string(code_font, code_font_size, 0, code_tab_size, str8_prefix(cursor_line, view->cursor.column-1)).x + priority_margin_width_px + catchall_margin_width_px + line_num_width_px); - Rng1S64 visible_pixel_range = - { - view->scroll_pos.x.idx, - view->scroll_pos.x.idx + (S64)code_area_dim.x, - }; - Rng1S64 cursor_pixel_range = - { - cursor_off - (S64)(big_glyph_advance*4) - (S64)(priority_margin_width_px + catchall_margin_width_px + line_num_width_px), - cursor_off + (S64)(big_glyph_advance*4), - }; - S64 min_delta = Min(0, cursor_pixel_range.min - visible_pixel_range.min); - S64 max_delta = Max(0, cursor_pixel_range.max - visible_pixel_range.max); - S64 new_idx = view->scroll_pos.x.idx+min_delta+max_delta; - new_idx = Clamp(scroll_idx_rng[Axis2_X].min, new_idx, scroll_idx_rng[Axis2_X].max); - ui_scroll_pt_target_idx(&view->scroll_pos.x, new_idx); - } - - // rjf: snap in Y - if(snap[Axis2_Y]) - { - Rng1S64 cursor_visibility_range = r1s64(view->cursor.line-4, view->cursor.line+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 = view->scroll_pos.y.idx+min_delta+max_delta; - new_idx = Clamp(0, new_idx, (S64)text_info->lines_count-1); - ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx); - } - } - - ////////////////////////////// - //- rjf: build horizontal scroll bar - // - { - ui_set_next_fixed_x(0); - ui_set_next_fixed_y(code_area_dim.y); - ui_set_next_fixed_width(panel_box_dim.x - scroll_bar_dim); - ui_set_next_fixed_height(scroll_bar_dim); - { - view->scroll_pos.x = ui_scroll_bar(Axis2_X, - ui_px(scroll_bar_dim, 1.f), - view->scroll_pos.x, - scroll_idx_rng[Axis2_X], - (S64)code_area_dim.x); - } - } - - ////////////////////////////// - //- rjf: build vertical scroll bar - // - { - ui_set_next_fixed_x(code_area_dim.x); - ui_set_next_fixed_y(0); - ui_set_next_fixed_width(scroll_bar_dim); - ui_set_next_fixed_height(panel_box_dim.y - scroll_bar_dim); - { - view->scroll_pos.y = ui_scroll_bar(Axis2_Y, - ui_px(scroll_bar_dim, 1.f), - view->scroll_pos.y, - scroll_idx_rng[Axis2_Y], - num_possible_visible_lines); - } - } - - ////////////////////////////// - //- rjf: top-level container interaction (scrolling) - // - { - UI_Signal sig = ui_signal_from_box(container_box); - if(sig.scroll.x != 0) - { - S64 new_idx = view->scroll_pos.x.idx+sig.scroll.x*big_glyph_advance; - new_idx = clamp_1s64(scroll_idx_rng[Axis2_X], new_idx); - ui_scroll_pt_target_idx(&view->scroll_pos.x, new_idx); - } - if(sig.scroll.y != 0) - { - S64 new_idx = view->scroll_pos.y.idx + sig.scroll.y; - new_idx = clamp_1s64(scroll_idx_rng[Axis2_Y], new_idx); - ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx); - } - ui_scroll_pt_clamp_idx(&view->scroll_pos.x, scroll_idx_rng[Axis2_X]); - ui_scroll_pt_clamp_idx(&view->scroll_pos.y, scroll_idx_rng[Axis2_Y]); - if(ui_mouse_over(sig)) - { - UI_EventList *events = ui_events(); - for(UI_EventNode *n = events->first, *next = 0; n != 0; n = next) - { - next = n->next; - UI_Event *event = &n->v; - if(event->kind == UI_EventKind_Scroll && event->modifiers & OS_EventFlag_Ctrl) - { - ui_eat_event(events, n); - if(event->delta_2f32.y < 0) - { - DF_CmdParams params = df_cmd_params_from_window(ws); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_IncCodeFontScale)); - } - else if(event->delta_2f32.y > 0) - { - DF_CmdParams params = df_cmd_params_from_window(ws); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_DecCodeFontScale)); - } - } - } - } - } - - ////////////////////////////// - //- rjf: build result - // - DF_CodeViewBuildResult result = {0}; - { - for(DI_KeyNode *n = code_slice_params.relevant_dbgi_keys.first; n != 0; n = n->next) - { - DI_Key copy = di_key_copy(arena, &n->v); - di_key_list_push(arena, &result.dbgi_keys, ©); - } - } - - txt_scope_close(txt_scope); - di_scope_close(di_scope); - hs_scope_close(hs_scope); - scratch_end(scratch); - ProfEnd(); - return result; -} - -//////////////////////////////// -//~ rjf: Watch Views - -//- rjf: eval watch view instance -> eval view key - -internal DF_EvalViewKey -df_eval_view_key_from_eval_watch_view(DF_WatchViewState *ewv) -{ - DF_EvalViewKey key = df_eval_view_key_make((U64)ewv, df_hash_from_string(str8_struct(&ewv))); - return key; -} - -//- rjf: root allocation/deallocation/mutation - -internal DF_EvalRoot * -df_eval_root_alloc(DF_View *view, DF_WatchViewState *ews) -{ - DF_EvalRoot *result = ews->first_free_root; - if(result != 0) - { - SLLStackPop(ews->first_free_root); - result->expr_buffer_string_size = 0; - } - else - { - result = push_array(view->arena, DF_EvalRoot, 1); - result->expr_buffer_cap = 1024; - result->expr_buffer = push_array_no_zero(view->arena, U8, result->expr_buffer_cap); - } - DLLPushBack(ews->first_root, ews->last_root, result); - ews->root_count += 1; - return result; -} - -internal void -df_eval_root_release(DF_WatchViewState *ews, DF_EvalRoot *root) -{ - DLLRemove(ews->first_root, ews->last_root, root); - SLLStackPush(ews->first_free_root, root); - ews->root_count -= 1; -} - -internal void -df_eval_root_equip_string(DF_EvalRoot *root, String8 string) -{ - root->expr_buffer_string_size = Min(string.size, root->expr_buffer_cap); - MemoryCopy(root->expr_buffer, string.str, root->expr_buffer_string_size); -} - -internal DF_EvalRoot * -df_eval_root_from_string(DF_WatchViewState *ews, String8 string) -{ - DF_EvalRoot *root = 0; - for(DF_EvalRoot *r = ews->first_root; r != 0; r = r->next) - { - String8 r_string = df_string_from_eval_root(r); - if(str8_match(r_string, string, 0)) - { - root = r; - break; - } - } - return root; -} - -internal DF_EvalRoot * -df_eval_root_from_expand_key(DF_WatchViewState *ews, DF_EvalView *eval_view, DF_ExpandKey expand_key) -{ - DF_EvalRoot *root = 0; - for(DF_EvalRoot *r = ews->first_root; r != 0; r = r->next) - { - DF_ExpandKey key = df_expand_key_from_eval_root(r); - if(df_expand_key_match(key, expand_key)) - { - root = r; - break; - } - } - return root; -} - -internal String8 -df_string_from_eval_root(DF_EvalRoot *root) -{ - String8 string = str8(root->expr_buffer, root->expr_buffer_string_size); - return string; -} - -internal DF_ExpandKey -df_parent_expand_key_from_eval_root(DF_EvalRoot *root) -{ - DF_ExpandKey parent_key = df_expand_key_make(5381, (U64)root); - return parent_key; -} - -internal DF_ExpandKey -df_expand_key_from_eval_root(DF_EvalRoot *root) -{ - DF_ExpandKey parent_key = df_parent_expand_key_from_eval_root(root); - DF_ExpandKey key = df_expand_key_make(df_hash_from_expand_key(parent_key), (U64)root); - return key; -} - -//- rjf: watch view points <-> table coordinates - -internal B32 -df_watch_view_point_match(DF_WatchViewPoint a, DF_WatchViewPoint b) -{ - return (a.column_kind == b.column_kind && - df_expand_key_match(a.parent_key, b.parent_key) && - df_expand_key_match(a.key, b.key)); -} - -internal DF_WatchViewPoint -df_watch_view_point_from_tbl(DF_EvalVizBlockList *blocks, Vec2S64 tbl) -{ - DF_WatchViewPoint pt = zero_struct; - pt.column_kind = (DF_WatchViewColumnKind)(tbl.x%DF_WatchViewColumnKind_COUNT); - pt.key = df_key_from_viz_block_list_row_num(blocks, tbl.y); - pt.parent_key = df_parent_key_from_viz_block_list_row_num(blocks, tbl.y); - return pt; -} - -internal Vec2S64 -df_tbl_from_watch_view_point(DF_EvalVizBlockList *blocks, DF_WatchViewPoint pt) -{ - Vec2S64 tbl = {0}; - tbl.x = (S64)pt.column_kind; - tbl.y = df_row_num_from_viz_block_list_key(blocks, pt.key); - return tbl; -} - -//- rjf: table coordinates -> strings - -internal String8 -df_string_from_eval_viz_row_column_kind(Arena *arena, DF_EvalView *ev, TG_Graph *graph, RDI_Parsed *rdi, DF_EvalVizRow *row, DF_WatchViewColumnKind col_kind, B32 editable) -{ - String8 result = {0}; - switch(col_kind) - { - default:{}break; - case DF_WatchViewColumnKind_Expr: {result = editable ? row->edit_expr : row->display_expr;}break; - case DF_WatchViewColumnKind_Value: {result = editable ? row->edit_value : row->display_value;}break; - case DF_WatchViewColumnKind_Type: {result = !tg_key_match(row->eval.type_key, tg_key_zero()) ? tg_string_from_key(arena, graph, rdi, row->eval.type_key) : str8_zero();}break; - case DF_WatchViewColumnKind_ViewRule:{result = df_eval_view_rule_from_key(ev, row->key);}break; - } - return result; -} - -//- rjf: table coordinates -> text edit state - -internal DF_WatchViewTextEditState * -df_watch_view_text_edit_state_from_pt(DF_WatchViewState *wv, DF_WatchViewPoint pt) -{ - DF_WatchViewTextEditState *result = &wv->dummy_text_edit_state; - if(wv->text_edit_state_slots_count != 0 && wv->text_editing != 0) - { - U64 hash = df_hash_from_expand_key(pt.key); - U64 slot_idx = hash%wv->text_edit_state_slots_count; - for(DF_WatchViewTextEditState *s = wv->text_edit_state_slots[slot_idx]; s != 0; s = s->pt_hash_next) - { - if(df_watch_view_point_match(pt, s->pt)) - { - result = s; - break; - } - } - } - return result; -} - -//- rjf: windowed watch tree visualization (both single-line and multi-line) - -internal DF_EvalVizBlockList -df_eval_viz_block_list_from_watch_view_state(Arena *arena, DI_Scope *di_scope, FZY_Scope *fzy_scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_View *view, DF_WatchViewState *ews) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(&arena, 1); - DF_EvalVizBlockList blocks = {0}; - DF_EvalViewKey eval_view_key = df_eval_view_key_from_eval_watch_view(ews); - DF_EvalView *eval_view = df_eval_view_from_key(eval_view_key); - String8 filter = str8(view->query_buffer, view->query_string_size); - FZY_Target fzy_target = FZY_Target_UDTs; - switch(ews->fill_kind) - { - //////////////////////////// - //- rjf: mutable watch fill -> build blocks from top-level mutable root expressions - // - default: - case DF_WatchViewFillKind_Mutable: - { - for(DF_EvalRoot *root = ews->first_root; root != 0; root = root->next) - { - String8 root_expr_string = df_string_from_eval_root(root); - FuzzyMatchRangeList matches = fuzzy_match_find(arena, filter, root_expr_string); - if(matches.count == matches.needle_part_count) - { - DF_ExpandKey parent_key = df_parent_expand_key_from_eval_root(root); - DF_ExpandKey key = df_expand_key_from_eval_root(root); - DF_EvalVizBlockList root_blocks = df_eval_viz_block_list_from_eval_view_expr_keys(arena, di_scope, ctrl_ctx, parse_ctx, macro_map, eval_view, root_expr_string, parent_key, key); - df_eval_viz_block_list_concat__in_place(&blocks, &root_blocks); - } - } - }break; - - //////////////////////////// - //- rjf: registers fill -> build blocks via iterating all registers/aliases as root-level expressions - // - case DF_WatchViewFillKind_Registers: - { - DF_Entity *thread = df_entity_from_handle(ctrl_ctx->thread); - Architecture arch = df_architecture_from_entity(thread); - U64 reg_count = regs_reg_code_count_from_architecture(arch); - String8 *reg_strings = regs_reg_code_string_table_from_architecture(arch); - U64 alias_count = regs_alias_code_count_from_architecture(arch); - String8 *alias_strings = regs_alias_code_string_table_from_architecture(arch); - U64 num = 1; - for(U64 reg_idx = 1; reg_idx < reg_count; reg_idx += 1, num += 1) - { - String8 root_expr_string = reg_strings[reg_idx]; - FuzzyMatchRangeList matches = fuzzy_match_find(arena, filter, root_expr_string); - if(matches.count == matches.needle_part_count) - { - DF_ExpandKey parent_key = df_expand_key_make(5381, 0); - DF_ExpandKey key = df_expand_key_make(df_hash_from_expand_key(parent_key), num); - DF_EvalVizBlockList root_blocks = df_eval_viz_block_list_from_eval_view_expr_keys(arena, di_scope, ctrl_ctx, parse_ctx, macro_map, eval_view, root_expr_string, parent_key, key); - df_eval_viz_block_list_concat__in_place(&blocks, &root_blocks); - } - } - for(U64 alias_idx = 1; alias_idx < alias_count; alias_idx += 1, num += 1) - { - String8 root_expr_string = alias_strings[alias_idx]; - FuzzyMatchRangeList matches = fuzzy_match_find(arena, filter, root_expr_string); - if(matches.count == matches.needle_part_count) - { - DF_ExpandKey parent_key = df_expand_key_make(5381, 0); - DF_ExpandKey key = df_expand_key_make(df_hash_from_expand_key(parent_key), num); - DF_EvalVizBlockList root_blocks = df_eval_viz_block_list_from_eval_view_expr_keys(arena, di_scope, ctrl_ctx, parse_ctx, macro_map, eval_view, root_expr_string, parent_key, key); - df_eval_viz_block_list_concat__in_place(&blocks, &root_blocks); - } - } - }break; - - //////////////////////////// - //- rjf: locals fill -> build blocks via iterating all locals as root-level expressions - // - case DF_WatchViewFillKind_Locals: - { - EVAL_String2NumMapNodeArray nodes = eval_string2num_map_node_array_from_map(scratch.arena, parse_ctx->locals_map); - eval_string2num_map_node_array_sort__in_place(&nodes); - for(U64 idx = 0; idx < nodes.count; idx += 1) - { - EVAL_String2NumMapNode *n = nodes.v[idx]; - String8 root_expr_string = n->string; - FuzzyMatchRangeList matches = fuzzy_match_find(arena, filter, root_expr_string); - if(matches.count == matches.needle_part_count) - { - DF_ExpandKey parent_key = df_expand_key_make(5381, 0); - DF_ExpandKey key = df_expand_key_make(df_hash_from_expand_key(parent_key), idx+1); - DF_EvalVizBlockList root_blocks = df_eval_viz_block_list_from_eval_view_expr_keys(arena, di_scope, ctrl_ctx, parse_ctx, macro_map, eval_view, root_expr_string, parent_key, key); - df_eval_viz_block_list_concat__in_place(&blocks, &root_blocks); - } - } - }break; - - //////////////////////////// - //- rjf: debug info table fill -> build split debug info table blocks - // - case DF_WatchViewFillKind_Globals: fzy_target = FZY_Target_GlobalVariables; goto dbgi_table; - case DF_WatchViewFillKind_ThreadLocals: fzy_target = FZY_Target_ThreadVariables; goto dbgi_table; - case DF_WatchViewFillKind_Types: fzy_target = FZY_Target_UDTs; goto dbgi_table; - case DF_WatchViewFillKind_Procedures: fzy_target = FZY_Target_Procedures; goto dbgi_table; - dbgi_table:; - { - //- rjf: unpack context - DF_Entity *thread = df_entity_from_handle(ctrl_ctx->thread); - DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); - U64 thread_rip_unwind_vaddr = df_query_cached_rip_from_thread_unwind(thread, ctrl_ctx->unwind_count); - DF_Entity *module = df_module_from_process_vaddr(process, thread_rip_unwind_vaddr); - DI_Key dbgi_key = df_dbgi_key_from_module(module); - - //- rjf: calculate top-level keys, expand root-level, grab root expansion node - DF_ExpandKey parent_key = df_expand_key_make(5381, 0); - DF_ExpandKey root_key = df_expand_key_make(df_hash_from_expand_key(parent_key), 0); - df_expand_set_expansion(eval_view->arena, &eval_view->expand_tree_table, df_expand_key_zero(), parent_key, 1); - DF_ExpandNode *root_node = df_expand_node_from_key(&eval_view->expand_tree_table, parent_key); - - //- rjf: query all filtered items from dbgi searching system - U128 fuzzy_search_key = {(U64)view, df_hash_from_string(str8_struct(&view))}; - B32 items_stale = 0; - FZY_Params params = {fzy_target}; - { - params.dbgi_keys.count = 1; - params.dbgi_keys.v = &dbgi_key; - } - FZY_ItemArray items = fzy_items_from_key_params_query(fzy_scope, fuzzy_search_key, ¶ms, filter, os_now_microseconds()+100, &items_stale); - if(items_stale) - { - df_gfx_request_frame(); - } - - //- rjf: gather unsorted child expansion keys - // - // Nodes are sorted in the underlying expansion tree data structure, but - // ONLY by THEIR ORDER IN THE UNDERLYING DEBUG INFO TABLE. This is - // because debug info watch rows use the DEBUG INFO TABLE INDEX to form - // their key - this provides more stable/predictable behavior as rows - // are reordered, filtered, and shuffled around, as the user filters. - // - // When we actually build viz blocks, however, we want to produce viz - // blocks BY THE ORDER OF SUB-EXPANSIONS IN THE FILTERED ITEM ARRAY - // SPACE, so that all of the expansions come out in the right order. - // - DF_ExpandKey *sub_expand_keys = 0; - U64 *sub_expand_item_idxs = 0; - U64 sub_expand_keys_count = 0; - { - for(DF_ExpandNode *child = root_node->first; child != 0; child = child->next) - { - sub_expand_keys_count += 1; - } - sub_expand_keys = push_array(scratch.arena, DF_ExpandKey, sub_expand_keys_count); - sub_expand_item_idxs = push_array(scratch.arena, U64, sub_expand_keys_count); - U64 idx = 0; - for(DF_ExpandNode *child = root_node->first; child != 0; child = child->next) - { - U64 item_num = fzy_item_num_from_array_element_idx__linear_search(&items, child->key.child_num); - if(item_num != 0) - { - sub_expand_keys[idx] = child->key; - sub_expand_item_idxs[idx] = item_num-1; - idx += 1; - } - else - { - sub_expand_keys_count -= 1; - } - } - } - - //- rjf: sort child expansion keys - { - for(U64 idx1 = 0; idx1 < sub_expand_keys_count; idx1 += 1) - { - U64 min_idx2 = 0; - U64 min_item_idx = sub_expand_item_idxs[idx1]; - for(U64 idx2 = idx1+1; idx2 < sub_expand_keys_count; idx2 += 1) - { - if(sub_expand_item_idxs[idx2] < min_item_idx) - { - min_idx2 = idx2; - min_item_idx = sub_expand_item_idxs[idx2]; - } - } - if(min_idx2 != 0) - { - Swap(DF_ExpandKey, sub_expand_keys[idx1], sub_expand_keys[min_idx2]); - Swap(U64, sub_expand_item_idxs[idx1], sub_expand_item_idxs[min_idx2]); - } - } - } - - //- rjf: build blocks for all table items, split by sorted sub-expansions - DF_EvalVizBlock *last_vb = df_eval_viz_block_begin(arena, DF_EvalVizBlockKind_DebugInfoTable, parent_key, root_key, 0); - { - last_vb->visual_idx_range = last_vb->semantic_idx_range = r1u64(0, items.count); - last_vb->fzy_target = fzy_target; - last_vb->fzy_backing_items = items; - } - for(U64 sub_expand_idx = 0; sub_expand_idx < sub_expand_keys_count; sub_expand_idx += 1) - { - // rjf: form split: truncate & complete last block; begin next block - last_vb = df_eval_viz_block_split_and_continue(arena, &blocks, last_vb, sub_expand_item_idxs[sub_expand_idx]); - - // rjf: grab name for the expanded row - String8 name = fzy_item_string_from_rdi_target_element_idx(parse_ctx->rdi, fzy_target, sub_expand_keys[sub_expand_idx].child_num); - - // rjf: recurse for sub-expansion - { - DF_CfgTable child_cfg = {0}; - { - String8 view_rule_string = df_eval_view_rule_from_key(eval_view, df_expand_key_make(df_hash_from_expand_key(parent_key), sub_expand_keys[sub_expand_idx].child_num)); - if(view_rule_string.size != 0) - { - df_cfg_table_push_unparsed_string(arena, &child_cfg, view_rule_string, DF_CfgSrc_User); - } - } - DF_Eval eval = df_eval_from_string(arena, di_scope, ctrl_ctx, parse_ctx, macro_map, name); - df_append_viz_blocks_for_parent__rec(arena, di_scope, eval_view, ctrl_ctx, parse_ctx, macro_map, parent_key, sub_expand_keys[sub_expand_idx], name, eval, 0, &child_cfg, 0, &blocks); - } - } - df_eval_viz_block_end(&blocks, last_vb); - }break; - } - scratch_end(scratch); - ProfEnd(); - return blocks; -} - -//- rjf: eval/watch views main hooks - -internal void -df_watch_view_init(DF_WatchViewState *ewv, DF_View *view, DF_WatchViewFillKind fill_kind) -{ - if(ewv->initialized == 0) - { - ewv->initialized = 1; - ewv->expr_column_pct = 0.25f; - ewv->value_column_pct = 0.3f; - ewv->type_column_pct = 0.15f; - ewv->view_rule_column_pct = 0.30f; - ewv->fill_kind = fill_kind; - ewv->text_edit_arena = df_view_push_arena_ext(view); - } -} - -internal void -df_watch_view_cmds(DF_Window *ws, DF_Panel *panel, DF_View *view, DF_WatchViewState *ewv, DF_CmdList *cmds) -{ - for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) - { - DF_Cmd *cmd = &n->cmd; - DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); - - // rjf: process - switch(core_cmd_kind) - { - default:break; - - //- rjf: watch expression toggling - case DF_CoreCmdKind_ToggleWatchExpression: - if(cmd->params.string.size != 0) - { - DF_EvalRoot *already_existing_root = df_eval_root_from_string(ewv, cmd->params.string); - if(already_existing_root != 0) - { - df_eval_root_release(ewv, already_existing_root); - } - else - { - DF_EvalRoot *root = df_eval_root_alloc(view, ewv); - df_eval_root_equip_string(root, cmd->params.string); - } - }break; - } - } -} - -internal void -df_watch_view_build(DF_Window *ws, DF_Panel *panel, DF_View *view, DF_WatchViewState *ewv, B32 modifiable, U32 default_radix, Rng2F32 rect) -{ - ProfBeginFunction(); - DI_Scope *di_scope = di_scope_open(); - FZY_Scope *fzy_scope = fzy_scope_open(); - Temp scratch = scratch_begin(0, 0); - - ////////////////////////////// - //- rjf: unpack arguments - // - F_Tag code_font = df_font_from_slot(DF_FontSlot_Code); - DF_CtrlCtx ctrl_ctx = df_ctrl_ctx_from_view(ws, view); - DF_Entity *thread = df_entity_from_handle(ctrl_ctx.thread); - DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); - U64 thread_ip_vaddr = df_query_cached_rip_from_thread_unwind(thread, ctrl_ctx.unwind_count); - DF_EvalViewKey eval_view_key = df_eval_view_key_from_eval_watch_view(ewv); - DF_EvalView *eval_view = df_eval_view_from_key(eval_view_key); - String8 filter = str8(view->query_buffer, view->query_string_size); - F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); - S64 num_possible_visible_rows = (S64)(dim_2f32(rect).y/row_height_px); - - ////////////////////////////// - //- rjf: process * thread info -> parse_ctx - // - EVAL_ParseCtx parse_ctx = df_eval_parse_ctx_from_process_vaddr(di_scope, process, thread_ip_vaddr); - - ////////////////////////////// - //- rjf: determine autocompletion string - // - String8 autocomplete_hint_string = {0}; - { - UI_EventList *events = ui_events(); - for(UI_EventNode *n = events->first; n != 0; n = n->next) - { - if(n->v.kind == UI_EventKind_AutocompleteHint) - { - autocomplete_hint_string = n->v.string; - break; - } - } - } - - ////////////////////////////// - //- rjf: consume events & perform navigations/edits - calculate state - // - EVAL_String2ExprMap macro_map = {0}; - DF_EvalVizBlockList blocks = {0}; - UI_ScrollListRowBlockArray row_blocks = {0}; - Vec2S64 cursor_tbl = {0}; - Vec2S64 mark_tbl = {0}; - Rng2S64 selection_tbl = {0}; - UI_Focus(UI_FocusKind_On) - { - UI_EventList *events = ui_events(); - B32 state_dirty = 1; - B32 cursor_dirty__tbl = 0; - B32 take_autocomplete = 0; - for(UI_EventNode *event_n = events->first, *next = 0;; event_n = next) - { - ////////////////////////// - //- rjf: state -> macro map - // - if(state_dirty) - { - macro_map = eval_string2expr_map_make(scratch.arena, 256); - for(DF_EvalRoot *root = ewv->first_root; root != 0; root = root->next) - { - String8 root_expr = str8(root->expr_buffer, root->expr_buffer_string_size); - - //- rjf: unpack arguments - DF_Entity *process = thread->parent; - U64 unwind_count = ctrl_ctx.unwind_count; - CTRL_Unwind unwind = df_query_cached_unwind_from_thread(thread); - Architecture arch = df_architecture_from_entity(thread); - U64 reg_size = regs_block_size_from_architecture(arch); - void *thread_unwind_regs_block = push_array(scratch.arena, U8, reg_size); - U64 thread_unwind_ip_vaddr = 0; - if(unwind.frames.count != 0) - { - thread_unwind_regs_block = unwind.frames.v[unwind_count%unwind.frames.count].regs; - thread_unwind_ip_vaddr = regs_rip_from_arch_block(arch, thread_unwind_regs_block); - } - - //- rjf: lex & parse - EVAL_TokenArray tokens = eval_token_array_from_text(scratch.arena, root_expr); - EVAL_ParseResult parse = eval_parse_expr_from_text_tokens(scratch.arena, &parse_ctx, root_expr, &tokens); - EVAL_ErrorList errors = parse.errors; - if(errors.count == 0) - { - eval_push_leaf_ident_exprs_from_expr__in_place(scratch.arena, ¯o_map, parse.expr, &errors); - } - } - } - - ////////////////////////// - //- rjf: state -> viz blocks - // - if(state_dirty) - { - blocks = df_eval_viz_block_list_from_watch_view_state(scratch.arena, di_scope, fzy_scope, &ctrl_ctx, &parse_ctx, ¯o_map, view, ewv); - } - - ////////////////////////// - //- rjf: does this eval watch view allow mutation? -> add extra block for editable empty row - // - DF_ExpandKey empty_row_parent_key = df_expand_key_make(max_U64, max_U64); - DF_ExpandKey empty_row_key = df_expand_key_make(df_hash_from_expand_key(empty_row_parent_key), 1); - if(state_dirty && modifiable) - { - DF_EvalVizBlock *b = df_eval_viz_block_begin(scratch.arena, DF_EvalVizBlockKind_Null, empty_row_parent_key, empty_row_key, 0); - b->visual_idx_range = b->semantic_idx_range = r1u64(0, 1); - df_eval_viz_block_end(&blocks, b); - } - - ////////////////////////// - //- rjf: viz blocks -> ui row blocks - // - { - UI_ScrollListRowBlockChunkList row_block_chunks = {0}; - for(DF_EvalVizBlockNode *n = blocks.first; n != 0; n = n->next) - { - DF_EvalVizBlock *vb = &n->v; - UI_ScrollListRowBlock block = {0}; - block.row_count = dim_1u64(vb->visual_idx_range); - block.item_count = dim_1u64(vb->semantic_idx_range); - ui_scroll_list_row_block_chunk_list_push(scratch.arena, &row_block_chunks, 256, &block); - } - row_blocks = ui_scroll_list_row_block_array_from_chunk_list(scratch.arena, &row_block_chunks); - } - - ////////////////////////// - //- rjf: conclude state update - // - if(state_dirty) - { - state_dirty = 0; - } - - ////////////////////////////// - //- rjf: 2D table coordinates * blocks -> stable cursor state - // - if(cursor_dirty__tbl) - { - cursor_dirty__tbl = 0; - struct - { - DF_WatchViewPoint *pt_state; - Vec2S64 pt_tbl; - } - points[] = - { - {&ewv->cursor, cursor_tbl}, - {&ewv->mark, mark_tbl}, - }; - for(U64 point_idx = 0; point_idx < ArrayCount(points); point_idx += 1) - { - DF_ExpandKey last_key = points[point_idx].pt_state->key; - DF_ExpandKey last_parent_key = points[point_idx].pt_state->parent_key; - points[point_idx].pt_state[0] = df_watch_view_point_from_tbl(&blocks, points[point_idx].pt_tbl); - if(df_expand_key_match(df_expand_key_zero(), points[point_idx].pt_state->key)) - { - points[point_idx].pt_state->key = last_parent_key; - DF_ExpandNode *node = df_expand_node_from_key(&eval_view->expand_tree_table, last_parent_key); - for(DF_ExpandNode *n = node; n != 0; n = n->parent) - { - points[point_idx].pt_state->key = n->key; - if(n->expanded == 0) - { - break; - } - } - } - if(point_idx == 0 && - (!df_expand_key_match(ewv->cursor.key, last_key) || - !df_expand_key_match(ewv->cursor.parent_key, last_parent_key))) - { - ewv->text_editing = 0; - } - } - ewv->next_cursor = ewv->cursor; - ewv->next_mark = ewv->mark; - } - - ////////////////////////// - //- rjf: stable cursor state * blocks -> 2D table coordinates - // - { - cursor_tbl = df_tbl_from_watch_view_point(&blocks, ewv->cursor); - mark_tbl = df_tbl_from_watch_view_point(&blocks, ewv->mark); - selection_tbl = r2s64p(Min(cursor_tbl.x, mark_tbl.x), Min(cursor_tbl.y, mark_tbl.y), - Max(cursor_tbl.x, mark_tbl.x), Max(cursor_tbl.y, mark_tbl.y)); - } - - ////////////////////////////// - //- rjf: apply cursor/mark rugpull change - // - B32 cursor_rugpull = 0; - if(!df_watch_view_point_match(ewv->cursor, ewv->next_cursor)) - { - cursor_rugpull = 1; - ewv->cursor = ewv->next_cursor; - ewv->mark = ewv->next_mark; - } - - ////////////////////////// - //- rjf: grab next event, if any - otherwise exit the loop, as we now have - // the most up-to-date state - // - if(!cursor_rugpull && (event_n == 0 || !ui_is_focus_active())) - { - break; - } - UI_Event dummy_evt = zero_struct; - UI_Event *evt = &dummy_evt; - if(event_n != 0) - { - evt = &event_n->v; - next = event_n->next; - } - B32 taken = 0; - - ////////////////////////// - //- rjf: begin editing on some operations - // - if(!ewv->text_editing && - (evt->kind == UI_EventKind_Text || - evt->flags & UI_EventFlag_Paste || - (evt->kind == UI_EventKind_Press && evt->slot == UI_EventActionSlot_Edit)) && - selection_tbl.min.x == selection_tbl.max.x && - (selection_tbl.min.x != 0 || modifiable)) - { - Vec2S64 selection_dim = dim_2s64(selection_tbl); - ewv->text_editing = 1; - arena_clear(ewv->text_edit_arena); - ewv->text_edit_state_slots_count = u64_up_to_pow2(selection_dim.y+1); - ewv->text_edit_state_slots_count = Max(ewv->text_edit_state_slots_count, 64); - ewv->text_edit_state_slots = push_array(ewv->text_edit_arena, DF_WatchViewTextEditState*, ewv->text_edit_state_slots_count); - DF_EvalVizWindowedRowList rows = df_eval_viz_windowed_row_list_from_viz_block_list(scratch.arena, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, eval_view, default_radix, code_font, ui_top_font_size(), - r1s64(ui_scroll_list_row_from_item(&row_blocks, selection_tbl.min.y-1), - ui_scroll_list_row_from_item(&row_blocks, selection_tbl.max.y-1)+1), &blocks); - DF_EvalVizRow *row = rows.first; - for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y; y += 1, row = row->next) - { - for(S64 x = selection_tbl.min.x; x <= selection_tbl.max.x; x += 1) - { - String8 string = df_string_from_eval_viz_row_column_kind(scratch.arena, eval_view, parse_ctx.type_graph, parse_ctx.rdi, row, (DF_WatchViewColumnKind)x, 1); - string.size = Min(string.size, sizeof(ewv->dummy_text_edit_state.input_buffer)); - DF_WatchViewPoint pt = {(DF_WatchViewColumnKind)x, row->parent_key, row->key}; - U64 hash = df_hash_from_expand_key(pt.key); - U64 slot_idx = hash%ewv->text_edit_state_slots_count; - DF_WatchViewTextEditState *edit_state = push_array(ewv->text_edit_arena, DF_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->input_size = string.size; - MemoryCopy(edit_state->input_buffer, string.str, string.size); - edit_state->initial_size = string.size; - MemoryCopy(edit_state->initial_buffer, string.str, string.size); - } - } - } - - ////////////////////////// - //- rjf: [table] do cell-granularity expansions - // - if(!ewv->text_editing && evt->slot == UI_EventActionSlot_Accept && selection_tbl.min.x <= 0) - { - taken = 1; - DF_EvalVizWindowedRowList rows = df_eval_viz_windowed_row_list_from_viz_block_list(scratch.arena, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, eval_view, default_radix, code_font, ui_top_font_size(), - r1s64(ui_scroll_list_row_from_item(&row_blocks, selection_tbl.min.y-1), - ui_scroll_list_row_from_item(&row_blocks, selection_tbl.max.y-1)+1), &blocks); - DF_EvalVizRow *row = rows.first; - for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y && row != 0; y += 1, row = row->next) - { - if(row->flags & DF_EvalVizRowFlag_CanExpand) - { - B32 is_expanded = df_expand_key_is_set(&eval_view->expand_tree_table, row->key); - df_expand_set_expansion(eval_view->arena, &eval_view->expand_tree_table, row->parent_key, row->key, !is_expanded); - } - if(row->flags & DF_EvalVizRowFlag_Canvas) - { - DF_CfgNode *cfg = df_cfg_tree_copy(scratch.arena, row->expand_ui_rule_node); - DF_CfgNode *cfg_root = push_array(scratch.arena, DF_CfgNode, 1); - cfg_root->first = cfg_root->last = cfg; - cfg_root->next = cfg_root->parent = &df_g_nil_cfg_node; - if(cfg != &df_g_nil_cfg_node) - { - cfg->parent = cfg_root; - } - DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); - p.string = row->edit_expr; - p.view_spec = df_tab_view_spec_from_gfx_view_rule_spec(row->expand_ui_rule_spec); - p.cfg_node = cfg_root; - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_ViewSpec); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_CfgNode); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_OpenTab)); - } - } - } - - ////////////////////////// - //- rjf: [table] do cell-granularity go-to-locations - // - if(!ewv->text_editing && evt->slot == UI_EventActionSlot_Accept && - selection_tbl.min.x == selection_tbl.max.x && - selection_tbl.min.y == selection_tbl.max.y && - selection_tbl.min.x == 1) - { - taken = 1; - DF_EvalVizWindowedRowList rows = df_eval_viz_windowed_row_list_from_viz_block_list(scratch.arena, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, eval_view, default_radix, code_font, ui_top_font_size(), - r1s64(ui_scroll_list_row_from_item(&row_blocks, selection_tbl.min.y-1), - ui_scroll_list_row_from_item(&row_blocks, selection_tbl.max.y-1)+1), &blocks); - DF_EvalVizRow *row = rows.first; - if(!(row->flags & DF_EvalVizRowFlag_CanEditValue)) - { - U64 vaddr = 0; - if(vaddr == 0) { vaddr = row->eval.offset; } - if(vaddr == 0) { vaddr = row->eval.imm_u64; } - DF_Entity *module = df_module_from_process_vaddr(process, vaddr); - DI_Key dbgi_key = df_dbgi_key_from_module(module); - U64 voff = df_voff_from_vaddr(module, vaddr); - DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, voff); - DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); - p.entity = df_handle_from_entity(process); - p.vaddr = vaddr; - if(lines.first != 0) - { - p.file_path = df_full_path_from_entity(scratch.arena, df_entity_from_handle(lines.first->v.file)); - p.text_point = lines.first->v.pt; - } - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FindCodeLocation)); - } - } - - ////////////////////////// - //- rjf: [text] apply textual edits - // - if(ewv->text_editing) - { - B32 editing_complete = ((evt->kind == UI_EventKind_Press && (evt->slot == UI_EventActionSlot_Cancel || evt->slot == UI_EventActionSlot_Accept)) || - (evt->kind == UI_EventKind_Navigate && evt->delta_2s32.y != 0) || - cursor_rugpull); - if(editing_complete || - ((evt->kind == UI_EventKind_Edit || - evt->kind == UI_EventKind_Navigate || - evt->kind == UI_EventKind_Text) && - evt->delta_2s32.y == 0)) - { - taken = 1; - for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y; y += 1) - { - for(S64 x = selection_tbl.min.x; x <= selection_tbl.max.x; x += 1) - { - DF_WatchViewPoint pt = df_watch_view_point_from_tbl(&blocks, v2s64(x, y)); - DF_WatchViewTextEditState *edit_state = df_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); - - // rjf: copy - if(op.flags & UI_TxtOpFlag_Copy && selection_tbl.min.x == selection_tbl.max.x && selection_tbl.min.y == selection_tbl.max.y) - { - os_set_clipboard_text(op.copy); - } - - // rjf: any valid op & autocomplete hint? -> perform autocomplete first, then re-compute op - if(autocomplete_hint_string.size != 0) - { - take_autocomplete = 1; - String8 word_query = df_autocomp_query_word_from_input_string_off(string, edit_state->cursor.column-1); - U64 word_off = (U64)(word_query.str - string.str); - String8 new_string = ui_push_string_replace_range(scratch.arena, string, r1s64(word_off+1, word_off+1+word_query.size), autocomplete_hint_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, word_off+1+autocomplete_hint_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); - } - - // rjf: cancel? -> revert to initial string - if(editing_complete && evt->slot == UI_EventActionSlot_Cancel) - { - string = str8(edit_state->initial_buffer, edit_state->initial_size); - } - - // rjf: obtain edited string - String8 new_string = string; - if(!txt_pt_match(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); - } - - // rjf: commit to edit state - new_string.size = Min(new_string.size, sizeof(edit_state->input_buffer)); - MemoryCopy(edit_state->input_buffer, new_string.str, new_string.size); - edit_state->input_size = new_string.size; - edit_state->cursor = op.cursor; - edit_state->mark = op.mark; - - // rjf: commit edited cell string - Vec2S64 tbl = v2s64(x, y); - switch((DF_WatchViewColumnKind)x) - { - default:{}break; - case DF_WatchViewColumnKind_Expr: - { - DF_WatchViewPoint pt = df_watch_view_point_from_tbl(&blocks, tbl); - DF_EvalRoot *root = df_eval_root_from_expand_key(ewv, eval_view, pt.key); - if(root != 0) - { - df_eval_root_equip_string(root, new_string); - state_dirty = 1; - } - else if(editing_complete && new_string.size != 0 && df_expand_key_match(pt.key, empty_row_key)) - { - root = df_eval_root_alloc(view, ewv); - df_eval_root_equip_string(root, new_string); - DF_ExpandKey key = df_expand_key_from_eval_root(root); - df_eval_view_set_key_rule(eval_view, key, str8_zero()); - state_dirty = 1; - } - }break; - case DF_WatchViewColumnKind_Value: - if(editing_complete && evt->slot != UI_EventActionSlot_Cancel) - { - DF_EvalVizWindowedRowList rows = df_eval_viz_windowed_row_list_from_viz_block_list(scratch.arena, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, eval_view, default_radix, code_font, ui_top_font_size(), - r1s64(ui_scroll_list_row_from_item(&row_blocks, y-1), - ui_scroll_list_row_from_item(&row_blocks, y-1)+1), &blocks); - B32 success = 0; - if(rows.first != 0) - { - DF_Eval write_eval = df_eval_from_string(scratch.arena, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, new_string); - success = df_commit_eval_value(parse_ctx.type_graph, parse_ctx.rdi, &ctrl_ctx, rows.first->eval, write_eval); - } - if(!success) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.string = str8_lit("Could not commit value successfully."); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - }break; - case DF_WatchViewColumnKind_Type:{}break; - case DF_WatchViewColumnKind_ViewRule: - if(editing_complete) - { - DF_WatchViewPoint pt = df_watch_view_point_from_tbl(&blocks, tbl); - df_eval_view_set_key_rule(eval_view, pt.key, new_string); - state_dirty = 1; - }break; - } - } - } - } - if(editing_complete) - { - ewv->text_editing = 0; - } - } - - ////////////////////////// - //- rjf: [table] do cell-granularity copies - // - if(!ewv->text_editing && evt->flags & UI_EventFlag_Copy) - { - taken = 1; - String8List strs = {0}; - DF_EvalVizWindowedRowList rows = df_eval_viz_windowed_row_list_from_viz_block_list(scratch.arena, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, eval_view, default_radix, code_font, ui_top_font_size(), - r1s64(ui_scroll_list_row_from_item(&row_blocks, selection_tbl.min.y-1), - ui_scroll_list_row_from_item(&row_blocks, selection_tbl.max.y-1)+1), &blocks); - DF_EvalVizRow *row = rows.first; - for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y && row != 0; y += 1, row = row->next) - { - for(S64 x = selection_tbl.min.x; x <= selection_tbl.max.x; x += 1) - { - String8 cell_string = df_string_from_eval_viz_row_column_kind(scratch.arena, eval_view, parse_ctx.type_graph, parse_ctx.rdi, row, (DF_WatchViewColumnKind)x, 0); - cell_string = str8_skip_chop_whitespace(cell_string); - U64 comma_pos = str8_find_needle(cell_string, 0, str8_lit(","), 0); - if(selection_tbl.min.x != selection_tbl.max.x || selection_tbl.min.y != selection_tbl.max.y) - { - str8_list_pushf(scratch.arena, &strs, "%s%S%s%s", - comma_pos < cell_string.size ? "\"" : "", - cell_string, - comma_pos < cell_string.size ? "\"" : "", - x+1 <= selection_tbl.max.x ? "," : ""); - } - else - { - str8_list_push(scratch.arena, &strs, cell_string); - } - } - if(y+1 <= selection_tbl.max.y) - { - str8_list_push(scratch.arena, &strs, str8_lit("\n")); - } - } - String8 string = str8_list_join(scratch.arena, &strs, 0); - os_set_clipboard_text(string); - } - - ////////////////////////// - //- rjf: [table] do cell-granularity deletions - // - if(!ewv->text_editing && evt->flags & UI_EventFlag_Delete) - { - taken = 1; - state_dirty = 1; - for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y; y += 1) - { - DF_WatchViewPoint pt = df_watch_view_point_from_tbl(&blocks, v2s64(0, y)); - - // rjf: row deletions - if(selection_tbl.min.x <= 0) - { - DF_EvalRoot *root = df_eval_root_from_expand_key(ewv, eval_view, pt.key); - if(root != 0) - { - DF_ExpandKey new_cursor_key = empty_row_key; - DF_ExpandKey new_cursor_parent_key = empty_row_parent_key; - if((evt->delta_2s32.x < 0 || evt->delta_2s32.y < 0) && root->prev != 0) - { - new_cursor_key = df_expand_key_from_eval_root(root->prev); - new_cursor_parent_key = df_parent_expand_key_from_eval_root(root->prev); - } - else if(root->next != 0) - { - new_cursor_key = df_expand_key_from_eval_root(root->next); - new_cursor_parent_key = df_parent_expand_key_from_eval_root(root->next); - } - DF_WatchViewPoint new_cursor_pt = {DF_WatchViewColumnKind_Expr, new_cursor_parent_key, new_cursor_key}; - df_eval_root_release(ewv, root); - ewv->cursor = ewv->mark = ewv->next_cursor = ewv->next_mark = new_cursor_pt; - } - } - - // rjf: view rule deletions - else if(selection_tbl.min.x <= DF_WatchViewColumnKind_ViewRule && DF_WatchViewColumnKind_ViewRule <= selection_tbl.max.x) - { - df_eval_view_set_key_rule(eval_view, pt.key, str8_zero()); - } - } - } - - ////////////////////////// - //- rjf: [table] apply deltas to cursor & mark - // - if(!ewv->text_editing && !(evt->flags & UI_EventFlag_Delete) && !(evt->flags & UI_EventFlag_Reorder)) - { - B32 cursor_tbl_min_is_empty_selection[Axis2_COUNT] = {0, 1}; - Rng2S64 cursor_tbl_range = r2s64(v2s64(0, 0), v2s64(3, blocks.total_semantic_row_count)); - Rng1S64 item_range = r1s64(0, 1 + blocks.total_visual_row_count); - Vec2S32 delta = evt->delta_2s32; - if(evt->flags & UI_EventFlag_PickSelectSide && !MemoryMatchStruct(&selection_tbl.min, &selection_tbl.max)) - { - if(delta.x > 0 || delta.y > 0) - { - cursor_tbl.x = selection_tbl.max.x; - cursor_tbl.y = selection_tbl.max.y; - } - else if(delta.x < 0 || delta.y < 0) - { - cursor_tbl.x = selection_tbl.min.x; - cursor_tbl.y = selection_tbl.min.y; - } - } - if(evt->flags & UI_EventFlag_ZeroDeltaOnSelect && !MemoryMatchStruct(&selection_tbl.min, &selection_tbl.max)) - { - MemoryZeroStruct(&delta); - } - B32 moved = 1; - switch(evt->delta_unit) - { - default:{moved = 0;}break; - case UI_EventDeltaUnit_Char: - { - for(EachEnumVal(Axis2, axis)) - { - cursor_tbl.v[axis] += delta.v[axis]; - if(cursor_tbl.v[axis] < cursor_tbl_range.min.v[axis]) - { - cursor_tbl.v[axis] = cursor_tbl_range.max.v[axis]; - } - if(cursor_tbl.v[axis] > cursor_tbl_range.max.v[axis]) - { - cursor_tbl.v[axis] = cursor_tbl_range.min.v[axis]; - } - cursor_tbl.v[axis] = clamp_1s64(r1s64(cursor_tbl_range.min.v[axis], cursor_tbl_range.max.v[axis]), cursor_tbl.v[axis]); - } - }break; - case UI_EventDeltaUnit_Word: - case UI_EventDeltaUnit_Line: - case UI_EventDeltaUnit_Page: - { - cursor_tbl.x = (delta.x>0 ? (cursor_tbl_range.max.x) : - delta.x<0 ? (cursor_tbl_range.min.x + !!cursor_tbl_min_is_empty_selection[Axis2_X]) : - cursor_tbl.x); - cursor_tbl.y += ((delta.y>0 ? +(num_possible_visible_rows-3) : - delta.y<0 ? -(num_possible_visible_rows-3) : - 0)); - cursor_tbl.y = clamp_1s64(r1s64(cursor_tbl_range.min.y + !!cursor_tbl_min_is_empty_selection[Axis2_Y], - cursor_tbl_range.max.y), - cursor_tbl.y); - }break; - case UI_EventDeltaUnit_Whole: - { - for(EachEnumVal(Axis2, axis)) - { - cursor_tbl.v[axis] = (delta.v[axis]>0 ? cursor_tbl_range.max.v[axis] : delta.v[axis]<0 ? cursor_tbl_range.min.v[axis] + !!cursor_tbl_min_is_empty_selection[axis] : cursor_tbl.v[axis]); - } - }break; - } - if(moved) - { - taken = 1; - cursor_dirty__tbl = 1; - { - Rng1S64 scroll_row_idx_range = r1s64(item_range.min, ClampBot(item_range.min, item_range.max-1)); - S64 cursor_item_idx = cursor_tbl.y-1; - if(item_range.min <= cursor_item_idx && cursor_item_idx <= item_range.max) - { - UI_ScrollPt *scroll_pt = &view->scroll_pos.y; - - //- rjf: compute visible row range - Rng1S64 visible_row_range = r1s64(scroll_pt->idx + 0 - !!(scroll_pt->off < 0), - scroll_pt->idx + 0 + num_possible_visible_rows + 1); - - //- rjf: compute cursor row range from cursor item - Rng1S64 cursor_visibility_row_range = {0}; - if(row_blocks.count == 0) - { - cursor_visibility_row_range = r1s64(cursor_item_idx-1, cursor_item_idx+3); - } - else - { - cursor_visibility_row_range.min = (S64)ui_scroll_list_row_from_item(&row_blocks, (U64)cursor_item_idx); - cursor_visibility_row_range.max = cursor_visibility_row_range.min + 4; - } - - //- rjf: compute deltas & apply - S64 min_delta = Min(0, cursor_visibility_row_range.min-visible_row_range.min); - S64 max_delta = Max(0, cursor_visibility_row_range.max-visible_row_range.max); - S64 new_idx = scroll_pt->idx+min_delta+max_delta; - new_idx = clamp_1s64(scroll_row_idx_range, new_idx); - ui_scroll_pt_target_idx(scroll_pt, new_idx); - } - } - - } - } - - ////////////////////////// - //- rjf: [table] stick table mark to cursor if needed - // - if(!ewv->text_editing) - { - if(taken && !(evt->flags & UI_EventFlag_KeepMark)) - { - mark_tbl = cursor_tbl; - } - } - - ////////////////////////// - //- rjf: [table] do cell-granularity reorders - // - if(!ewv->text_editing && evt->flags & UI_EventFlag_Reorder) - { - taken = 1; - DF_ExpandKey first_root_key = df_key_from_viz_block_list_row_num(&blocks, selection_tbl.min.y); - DF_EvalRoot *first_root = df_eval_root_from_expand_key(ewv, eval_view, first_root_key); - DF_EvalRoot *last_root = first_root; - if(first_root != 0) - { - for(S64 y = selection_tbl.min.y+1; y <= selection_tbl.max.y; y += 1) - { - DF_ExpandKey key = df_key_from_viz_block_list_row_num(&blocks, y); - DF_EvalRoot *new_root = df_eval_root_from_expand_key(ewv, eval_view, key); - if(new_root != 0) - { - last_root = new_root; - } - } - } - if(evt->delta_2s32.y < 0 && first_root != 0 && first_root->prev != 0) - { - state_dirty = 1; - DF_EvalRoot *reordered = first_root->prev; - DLLRemove(ewv->first_root, ewv->last_root, reordered); - DLLInsert(ewv->first_root, ewv->last_root, last_root, reordered); - } - if(evt->delta_2s32.y > 0 && last_root != 0 && last_root->next != 0) - { - state_dirty = 1; - DF_EvalRoot *prev_child = first_root->prev; - DF_EvalRoot *reordered = last_root->next; - DLLRemove(ewv->first_root, ewv->last_root, reordered); - DLLInsert(ewv->first_root, ewv->last_root, prev_child, reordered); - } - } - - ////////////////////////// - //- rjf: consume event, if taken - // - if(taken && evt != &dummy_evt) - { - ui_eat_event(events, event_n); - } - } - if(take_autocomplete) - { - for(UI_EventNode *n = events->first; n != 0; n = n->next) - { - if(n->v.kind == UI_EventKind_AutocompleteHint) - { - ui_eat_event(events, n); - break; - } - } - } - } - - ////////////////////////////// - //- rjf: build ui - // - F32 *col_pcts[] = - { - &ewv->expr_column_pct, - &ewv->value_column_pct, - &ewv->type_column_pct, - &ewv->view_rule_column_pct, - }; - B32 pressed = 0; - Rng1S64 visible_row_rng = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*2.5f); - scroll_list_params.dim_px = dim_2f32(rect); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(3, blocks.total_semantic_row_count)); - scroll_list_params.item_range = r1s64(0, 1 + blocks.total_visual_row_count); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - UI_ScrollListRowBlockChunkList row_block_chunks = {0}; - for(DF_EvalVizBlockNode *n = blocks.first; n != 0; n = n->next) - { - DF_EvalVizBlock *vb = &n->v; - UI_ScrollListRowBlock block = {0}; - block.row_count = dim_1u64(vb->visual_idx_range); - block.item_count = dim_1u64(vb->semantic_idx_range); - ui_scroll_list_row_block_chunk_list_push(scratch.arena, &row_block_chunks, 256, &block); - } - scroll_list_params.row_blocks = ui_scroll_list_row_block_array_from_chunk_list(scratch.arena, &row_block_chunks); - } - UI_BoxFlags disabled_flags = ui_top_flags(); - if(df_ctrl_targets_running()) - { - disabled_flags |= UI_BoxFlag_Disabled; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, &view->scroll_pos.y, - 0, - 0, - &visible_row_rng, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - UI_TableF(ArrayCount(col_pcts), col_pcts, "table_header") - { - //////////////////////////// - //- rjf: build table header - // - if(visible_row_rng.min == 0) UI_TableVector UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - { - UI_TableCell ui_label(str8_lit("Expression")); - UI_TableCell ui_label(str8_lit("Value")); - UI_TableCell ui_label(str8_lit("Type")); - UI_TableCell if(df_help_label(str8_lit("View Rule"))) UI_Tooltip - { - F32 max_width = ui_top_font_size()*35; - ui_label_multiline(max_width, str8_lit("View rules are used to tweak the way evaluated expressions are visualized. Multiple rules can be specified on each row. They are specified in a key:(value) form. Some examples follow:")); - ui_spacer(ui_em(1.5f, 1)); - DF_Font(ws, DF_FontSlot_Code) ui_labelf("array:(N)"); - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that a pointer points to N elements, rather than only 1.")); - ui_spacer(ui_em(1.5f, 1)); - DF_Font(ws, DF_FontSlot_Code) ui_labelf("omit:(member_1 ... member_n)"); - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Omits a list of member names from appearing in struct, union, or class evaluations.")); - ui_spacer(ui_em(1.5f, 1)); - DF_Font(ws, DF_FontSlot_Code) ui_labelf("only:(member_1 ... member_n)"); - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that only the specified members should appear in struct, union, or class evaluations.")); - ui_spacer(ui_em(1.5f, 1)); - DF_Font(ws, DF_FontSlot_Code) ui_labelf("list:(next_link_member_name)"); - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that some struct, union, or class forms the top of a linked list, with next_link_member_name being the member which points at the next element in the list.")); - ui_spacer(ui_em(1.5f, 1)); - DF_Font(ws, DF_FontSlot_Code) ui_labelf("dec"); - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that all integral evaluations should appear in base-10 form.")); - ui_spacer(ui_em(1.5f, 1)); - DF_Font(ws, DF_FontSlot_Code) ui_labelf("hex"); - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that all integral evaluations should appear in base-16 form.")); - ui_spacer(ui_em(1.5f, 1)); - DF_Font(ws, DF_FontSlot_Code) ui_labelf("oct"); - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that all integral evaluations should appear in base-8 form.")); - ui_spacer(ui_em(1.5f, 1)); - DF_Font(ws, DF_FontSlot_Code) ui_labelf("bin"); - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that all integral evaluations should appear in base-2 form.")); - ui_spacer(ui_em(1.5f, 1)); - DF_Font(ws, DF_FontSlot_Code) ui_labelf("no_addr"); - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Displays only what pointers point to, if possible, without the pointer's address value.")); - ui_spacer(ui_em(1.5f, 1)); - } - } - - //////////////////////////// - //- rjf: viz blocks -> rows - // - DF_EvalVizWindowedRowList rows = {0}; - { - rows = df_eval_viz_windowed_row_list_from_viz_block_list(scratch.arena, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, eval_view, default_radix, code_font, ui_top_font_size(), r1s64(visible_row_rng.min-1, visible_row_rng.max), &blocks); - } - - //////////////////////////// - //- rjf: build table - // - ProfScope("build table") - { - U64 semantic_idx = rows.count_before_semantic; - for(DF_EvalVizRow *row = rows.first; row != 0; row = row->next, semantic_idx += 1) - { - //////////////////////// - //- rjf: unpack row info - // - U64 row_hash = df_hash_from_expand_key(row->key); - U64 expr_hash = df_hash_from_string(row->display_expr); - B32 row_selected = (selection_tbl.min.y <= (semantic_idx+1) && (semantic_idx+1) <= selection_tbl.max.y); - B32 row_expanded = df_expand_key_is_set(&eval_view->expand_tree_table, row->key); - - //////////////////////// - //- rjf: determine if row's data is fresh and/or bad - // - B32 row_is_fresh = 0; - B32 row_is_bad = 0; - switch(row->eval.mode) - { - default:{}break; - case EVAL_EvalMode_Addr: - { - U64 size = tg_byte_size_from_graph_rdi_key(parse_ctx.type_graph, parse_ctx.rdi, row->eval.type_key); - size = Min(size, 64); - Rng1U64 vaddr_rng = r1u64(row->eval.offset, row->eval.offset+size); - CTRL_ProcessMemorySlice slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, vaddr_rng, 0); - for(U64 idx = 0; idx < (slice.data.size+63)/64; idx += 1) - { - if(slice.byte_changed_flags[idx] != 0) - { - row_is_fresh = 1; - } - if(slice.byte_bad_flags[idx] != 0) - { - row_is_bad = 1; - } - } - }break; - } - - //////////////////////// - //- rjf: determine row's color palette - // - UI_BoxFlags row_flags = 0; - UI_Palette *palette = ui_top_palette(); - { - if(row_is_fresh) - { - palette = ui_build_palette(ui_top_palette(), .background = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlay)); - row_flags |= UI_BoxFlag_DrawBackground; - } - } - - //////////////////////// - //- rjf: build row box - // - ui_set_next_palette(palette); - ui_set_next_flags(disabled_flags); - ui_set_next_pref_width(ui_pct(1, 0)); - ui_set_next_pref_height(ui_px(scroll_list_params.row_height_px*row->size_in_rows, 1.f)); - ui_set_next_focus_hot(row_selected ? UI_FocusKind_On : UI_FocusKind_Off); - UI_Box *row_box = ui_build_box_from_stringf(row_flags|UI_BoxFlag_DrawSideBottom|UI_BoxFlag_Clickable|(!(row->flags & DF_EvalVizRowFlag_Canvas) * UI_BoxFlag_DisableFocusOverlay), "row_%I64x", row_hash); - ui_ts_vector_idx += 1; - ui_ts_cell_idx = 0; - - //////////////////////// - //- rjf: canvas row -> fill with canvas ui build - // - if(row->flags & DF_EvalVizRowFlag_Canvas) UI_Parent(row_box) UI_FocusHot(row_selected ? UI_FocusKind_On : UI_FocusKind_Off) - { - //- rjf: build canvas row contents - ui_set_next_fixed_y(-1.f * (row->skipped_size_in_rows) * scroll_list_params.row_height_px); - ui_set_next_fixed_height((row->skipped_size_in_rows + row->size_in_rows + row->chopped_size_in_rows) * scroll_list_params.row_height_px); - ui_set_next_child_layout_axis(Axis2_X); - UI_Box *canvas_box = ui_build_box_from_stringf(UI_BoxFlag_FloatingY, "###canvas_%I64x", row_hash); - if(row->expand_ui_rule_spec != &df_g_nil_gfx_view_rule_spec && row->expand_ui_rule_spec->info.block_ui) - { - UI_Parent(canvas_box) UI_WidthFill UI_HeightFill - { - Vec2F32 canvas_dim = v2f32(scroll_list_params.dim_px.x - ui_top_font_size()*1.5f, - (row->skipped_size_in_rows+row->size_in_rows+row->chopped_size_in_rows)*scroll_list_params.row_height_px); - row->expand_ui_rule_spec->info.block_ui(ws, row->key, row->eval, row->edit_expr, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, row->expand_ui_rule_node, canvas_dim); - } - } - - //- rjf: do canvas row interactions - { - DF_WatchViewPoint pt = {DF_WatchViewColumnKind_Expr, row->parent_key, row->key}; - UI_Signal sig = ui_signal_from_box(row_box); - - // rjf: press -> focus - if(ui_pressed(sig)) - { - ewv->next_cursor = ewv->next_mark = pt; - pressed = 1; - } - - // rjf: double clicked -> open dedicated tab - if(ui_double_clicked(sig)) - { - DF_CfgNode *cfg = df_cfg_tree_copy(scratch.arena, row->expand_ui_rule_node); - DF_CfgNode *cfg_root = push_array(scratch.arena, DF_CfgNode, 1); - cfg_root->first = cfg_root->last = cfg; - cfg_root->next = cfg_root->parent = &df_g_nil_cfg_node; - if(cfg != &df_g_nil_cfg_node) - { - cfg->parent = cfg_root; - } - DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); - p.string = row->edit_expr; - p.view_spec = df_tab_view_spec_from_gfx_view_rule_spec(row->expand_ui_rule_spec); - p.cfg_node = cfg_root; - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_ViewSpec); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_CfgNode); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_OpenTab)); - } - } - } - - //////////////////////// - //- rjf: build non-canvas row contents - // - if(!(row->flags & DF_EvalVizRowFlag_Canvas)) UI_Parent(row_box) UI_HeightFill - { - //////////////////// - //- rjf: draw start of cache lines in expansions - // - if((row->eval.mode == EVAL_EvalMode_Addr || row->eval.mode == EVAL_EvalMode_NULL) && - row->eval.errors.count == 0 && - row->eval.offset%64 == 0 && row->depth > 0 && - !row_expanded) - { - ui_set_next_fixed_x(0); - ui_set_next_fixed_y(0); - ui_set_next_fixed_height(ui_top_font_size()*0.1f); - ui_set_next_palette(ui_build_palette(ui_top_palette(), .background = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlay))); - ui_build_box_from_key(UI_BoxFlag_Floating|UI_BoxFlag_DrawBackground, ui_key_zero()); - } - - //////////////////// - //- rjf: draw mid-row cache line boundaries in expansions - // - if((row->eval.mode == EVAL_EvalMode_Addr || row->eval.mode == EVAL_EvalMode_NULL) && - row->eval.errors.count == 0 && - row->eval.offset%64 != 0 && - row->depth > 0 && - !row_expanded) - { - U64 next_off = (row->eval.offset + tg_byte_size_from_graph_rdi_key(parse_ctx.type_graph, parse_ctx.rdi, row->eval.type_key)); - if(next_off%64 != 0 && row->eval.offset/64 < next_off/64) - { - ui_set_next_fixed_x(0); - ui_set_next_fixed_y(scroll_list_params.row_height_px - ui_top_font_size()*0.5f); - ui_set_next_fixed_height(ui_top_font_size()*1.f); - Vec4F32 boundary_color = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlay); - ui_set_next_palette(ui_build_palette(ui_top_palette(), .background = boundary_color)); - ui_build_box_from_key(UI_BoxFlag_Floating|UI_BoxFlag_DrawBackground, ui_key_zero()); - } - } - - //////////////////// - //- rjf: expression - // - ProfScope("expr") - { - DF_WatchViewPoint pt = {DF_WatchViewColumnKind_Expr, row->parent_key, row->key}; - DF_WatchViewTextEditState *edit_state = df_watch_view_text_edit_state_from_pt(ewv, pt); - B32 cell_selected = (row_selected && selection_tbl.min.x <= pt.column_kind && pt.column_kind <= selection_tbl.max.x); - B32 can_edit_expr = !(row->depth > 0 || modifiable == 0); - - // rjf: build - UI_Signal sig = {0}; - B32 next_expanded = row_expanded; - UI_Palette(palette) UI_TableCell - UI_FocusHot(cell_selected ? UI_FocusKind_On : UI_FocusKind_Off) - UI_FocusActive((cell_selected && ewv->text_editing) ? UI_FocusKind_On : UI_FocusKind_Off) - { - B32 expr_editing_active = ui_is_focus_active(); - B32 is_inherited = (row->inherited_type_key_chain.count != 0); - DF_Font(ws, DF_FontSlot_Code) UI_FlagsAdd(row->depth > 0 ? UI_BoxFlag_DrawTextWeak : 0) - { - FuzzyMatchRangeList matches = {0}; - if(filter.size != 0) - { - matches = fuzzy_match_find(scratch.arena, filter, row->display_expr); - } - sig = df_line_editf(ws, - (DF_LineEditFlag_CodeContents*(!(row->flags & DF_EvalVizRowFlag_ExprIsSpecial))| - DF_LineEditFlag_NoBackground*(!is_inherited)| - DF_LineEditFlag_DisableEdit*(!can_edit_expr)| - DF_LineEditFlag_Expander*!!(row->flags & DF_EvalVizRowFlag_CanExpand)| - DF_LineEditFlag_ExpanderPlaceholder*(row->depth==0)| - DF_LineEditFlag_ExpanderSpace*(row->depth!=0)), - row->depth, - filter.size ? &matches : 0, - &edit_state->cursor, &edit_state->mark, edit_state->input_buffer, sizeof(edit_state->input_buffer), &edit_state->input_size, &next_expanded, - row->display_expr, - "###row_%I64x", row_hash); - } - if(is_inherited && ui_hovering(sig)) UI_Tooltip - { - String8List inheritance_chain_type_names = {0}; - for(TG_KeyNode *n = row->inherited_type_key_chain.first; n != 0; n = n->next) - { - String8 inherited_type_name = tg_string_from_key(scratch.arena, parse_ctx.type_graph, parse_ctx.rdi, n->v); - inherited_type_name = str8_skip_chop_whitespace(inherited_type_name); - str8_list_push(scratch.arena, &inheritance_chain_type_names, inherited_type_name); - } - StringJoin join = {0}; - join.sep = str8_lit("::"); - String8 inheritance_type = str8_list_join(scratch.arena, &inheritance_chain_type_names, &join); - ui_set_next_pref_width(ui_children_sum(1)); - UI_Row - { - ui_labelf("Inherited from "); - DF_Font(ws, DF_FontSlot_Code) df_code_label(1.f, 1.f, df_rgba_from_theme_color(DF_ThemeColor_CodeType), inheritance_type); - } - } - if(DEV_eval_watch_key_tooltips && ui_hovering(sig)) UI_Tooltip DF_Font(ws, DF_FontSlot_Code) - { - ui_labelf("Parent Key: %I64x, %I64x", row->parent_key.parent_hash, row->parent_key.child_num); - ui_labelf("Hover Key: %I64x, %I64x", row->key.parent_hash, row->key.child_num); - ui_labelf("Cursor Key: %I64x, %I64x", ewv->cursor.key.parent_hash, ewv->cursor.key.child_num); - } - if(DEV_eval_compiler_tooltips && row->depth == 0 && ui_hovering(sig)) UI_Tooltip - { - Temp scratch = scratch_begin(0, 0); - String8 string = row->display_expr; - - // rjf: lex & parse - EVAL_TokenArray tokens = eval_token_array_from_text(scratch.arena, string); - EVAL_ParseResult parse = eval_parse_expr_from_text_tokens(scratch.arena, &parse_ctx, string, &tokens); - EVAL_ErrorList errors = parse.errors; - ui_labelf("Tokens:"); - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) for(U64 idx = 0; idx < tokens.count; idx += 1) - { - EVAL_Token *token = tokens.v+idx; - String8 token_string = str8_substr(string, token->range); - String8 token_kind_name = str8_lit("Token"); - switch(token->kind) - { - default:break; - case EVAL_TokenKind_Identifier: {token_kind_name = str8_lit("Identifier");}break; - case EVAL_TokenKind_Numeric: {token_kind_name = str8_lit("Numeric");}break; - case EVAL_TokenKind_StringLiteral:{token_kind_name = str8_lit("StringLiteral");}break; - case EVAL_TokenKind_CharLiteral: {token_kind_name = str8_lit("CharLiteral");}break; - case EVAL_TokenKind_Symbol: {token_kind_name = str8_lit("Symbol");}break; - } - ui_labelf("%S -> \"%S\"", token_kind_name, token_string); - } - - // rjf: produce IR tree & type - EVAL_IRTreeAndType ir_tree_and_type = {&eval_irtree_nil}; - if(parse.expr != &eval_expr_nil && errors.count == 0) - { - ui_labelf("Type:"); - ir_tree_and_type = eval_irtree_and_type_from_expr(scratch.arena, parse_ctx.type_graph, parse_ctx.rdi, &eval_string2expr_map_nil, parse.expr, &errors); - TG_Key type_key = ir_tree_and_type.type_key; - String8 type_string = tg_string_from_key(scratch.arena, parse_ctx.type_graph, parse_ctx.rdi, type_key); - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - ui_label(type_string); - } - - scratch_end(scratch); - } - - // rjf: autocomplete lister - if(expr_editing_active && - selection_tbl.min.x == selection_tbl.max.x && selection_tbl.min.y == selection_tbl.max.y && - txt_pt_match(edit_state->cursor, edit_state->mark)) - { - String8 input = str8(edit_state->input_buffer, edit_state->input_size); - DF_AutoCompListerParams params = {DF_AutoCompListerFlag_Locals}; - df_set_autocomp_lister_query(ws, sig.box->key, ctrl_ctx, ¶ms, input, edit_state->cursor.column-1); - } - } - - // rjf: press -> commit if editing & select - if(ui_pressed(sig)) - { - ewv->next_cursor = ewv->next_mark = pt; - pressed = 1; - } - - // rjf: double-click -> start editing - if(ui_double_clicked(sig) && can_edit_expr) - { - ui_kill_action(); - DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Edit)); - } - - // rjf: commit expansion state - if(next_expanded != row_expanded) - { - df_expand_set_expansion(eval_view->arena, &eval_view->expand_tree_table, row->parent_key, row->key, next_expanded); - } - } - - //////////////////// - //- rjf: value - // - ProfScope("value") - { - DF_WatchViewPoint pt = {DF_WatchViewColumnKind_Value, row->parent_key, row->key}; - DF_WatchViewTextEditState *edit_state = df_watch_view_text_edit_state_from_pt(ewv, pt); - B32 cell_selected = (row_selected && selection_tbl.min.x <= pt.column_kind && pt.column_kind <= selection_tbl.max.x); - B32 value_is_error = (row->eval.errors.count != 0); - B32 value_is_hook = (!value_is_error && row->value_ui_rule_spec != &df_g_nil_gfx_view_rule_spec && row->value_ui_rule_spec != 0); - B32 value_is_complex = (!value_is_error && !value_is_hook && !(row->flags & DF_EvalVizRowFlag_CanEditValue)); - B32 value_is_simple = (!value_is_error && !value_is_hook && (row->flags & DF_EvalVizRowFlag_CanEditValue)); - - // rjf: unpack palette - UI_BoxFlags cell_flags = 0; - UI_Palette *palette = ui_top_palette(); - { - if(row_is_bad || value_is_error) - { - palette = ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_TextNegative), .text_weak = df_rgba_from_theme_color(DF_ThemeColor_TextNegative), .background = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlayError)); - cell_flags |= UI_BoxFlag_DrawBackground; - } - } - - // rjf: build - UI_Signal sig = {0}; - ui_set_next_flags(cell_flags); - UI_Palette(palette) UI_TableCell DF_Font(ws, DF_FontSlot_Code) - UI_FocusHot(cell_selected ? UI_FocusKind_On : UI_FocusKind_Off) - UI_FocusActive((cell_selected && ewv->text_editing) ? UI_FocusKind_On : UI_FocusKind_Off) - { - // rjf: errors? -> show errors - if(value_is_error) DF_Font(ws, DF_FontSlot_Main) - { - String8List strings = {0}; - for(EVAL_Error *error = row->eval.errors.first; error != 0; error = error->next) - { - str8_list_push(scratch.arena, &strings, error->text); - } - StringJoin join = {str8_lit(""), str8_lit(" "), str8_lit("")}; - String8 error_string = str8_list_join(scratch.arena, &strings, &join); - sig = df_error_label(error_string); - } - - // rjf: hook -> call hook - if(value_is_hook) DF_Font(ws, DF_FontSlot_Main) - { - UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clip|UI_BoxFlag_Clickable, "###val_%I64x", row_hash); - UI_Parent(box) - { - row->value_ui_rule_spec->info.row_ui(ws, row->key, row->eval, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, row->value_ui_rule_node); - } - sig = ui_signal_from_box(box); - } - - // rjf: complex values - if(value_is_complex) - { - UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clip|UI_BoxFlag_Clickable, "###val_%I64x", row_hash); - UI_Parent(box) - { - df_code_label(1.f, 1, df_rgba_from_theme_color(DF_ThemeColor_CodeDefault), row->display_value); - } - sig = ui_signal_from_box(box); - } - - // rjf: simple values (editable) - if(value_is_simple) UI_TextRasterFlags(df_raster_flags_from_slot(ws, DF_FontSlot_Code)) - { - sig = df_line_editf(ws, DF_LineEditFlag_CodeContents|DF_LineEditFlag_NoBackground, 0, 0, &edit_state->cursor, &edit_state->mark, edit_state->input_buffer, sizeof(edit_state->input_buffer), &edit_state->input_size, 0, row->display_value, "%S###val_%I64x", row->display_value, row_hash); - } - } - - // rjf: bad & hovering -> display - if(row_is_bad && ui_hovering(sig)) UI_Tooltip - { - UI_PrefWidth(ui_children_sum(1)) df_error_label(str8_lit("Could not read process memory successfully.")); - } - - // rjf: press -> focus & commit if editing & not selected - if(ui_pressed(sig)) - { - ewv->next_cursor = ewv->next_mark = pt; - pressed = 1; - } - - // rjf: double-click -> start editing - if(ui_double_clicked(sig) && value_is_simple) - { - ui_kill_action(); - DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Edit)); - } - - // rjf: double-click, not editable -> go-to-location - if(ui_double_clicked(sig) && !(row->flags & DF_EvalVizRowFlag_CanEditValue)) - { - U64 vaddr = 0; - if(vaddr == 0) { vaddr = row->eval.offset; } - if(vaddr == 0) { vaddr = row->eval.imm_u64; } - DF_Entity *module = df_module_from_process_vaddr(process, vaddr); - DI_Key dbgi_key = df_dbgi_key_from_module(module); - U64 voff = df_voff_from_vaddr(module, vaddr); - DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, voff); - DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); - p.entity = df_handle_from_entity(process); - p.vaddr = vaddr; - if(lines.first != 0) - { - p.file_path = df_full_path_from_entity(scratch.arena, df_entity_from_handle(lines.first->v.file)); - p.text_point = lines.first->v.pt; - } - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FindCodeLocation)); - } - } - - //////////////////// - //- rjf: type - // - ProfScope("type") - { - DF_WatchViewPoint pt = {DF_WatchViewColumnKind_Type, row->parent_key, row->key}; - DF_WatchViewTextEditState *edit_state = df_watch_view_text_edit_state_from_pt(ewv, pt); - B32 cell_selected = (row_selected && selection_tbl.min.x <= pt.column_kind && pt.column_kind <= selection_tbl.max.x); - UI_TableCell DF_Font(ws, DF_FontSlot_Code) - UI_FocusHot(cell_selected ? UI_FocusKind_On : UI_FocusKind_Off) - UI_FocusActive((cell_selected && ewv->text_editing) ? UI_FocusKind_On : UI_FocusKind_Off) - { - TG_Key key = row->eval.type_key; - String8 string = tg_string_from_key(scratch.arena, parse_ctx.type_graph, parse_ctx.rdi, key); - string = str8_skip_chop_whitespace(string); - UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clip|UI_BoxFlag_Clickable, "###type_%I64x", row_hash); - if(!tg_key_match(key, tg_key_zero())) UI_Parent(box) - { - df_code_label(1.f, 1, df_rgba_from_theme_color(DF_ThemeColor_CodeType), string); - } - UI_Signal sig = ui_signal_from_box(box); - if(ui_pressed(sig)) - { - ewv->next_cursor = ewv->next_mark = pt; - pressed = 1; - } - } - } - - //////////////////// - //- rjf: view rule - // - ProfScope("view rule") - { - DF_WatchViewPoint pt = {DF_WatchViewColumnKind_ViewRule, row->parent_key, row->key}; - DF_WatchViewTextEditState *edit_state = df_watch_view_text_edit_state_from_pt(ewv, pt); - B32 cell_selected = (row_selected && selection_tbl.min.x <= pt.column_kind && pt.column_kind <= selection_tbl.max.x); - String8 view_rule = df_eval_view_rule_from_key(eval_view, row->key); - - // rjf: build - UI_Signal sig = {0}; - B32 rule_editing_active = 0; - UI_TableCell DF_Font(ws, DF_FontSlot_Code) - UI_FocusHot(cell_selected ? UI_FocusKind_On : UI_FocusKind_Off) - UI_FocusActive((cell_selected && ewv->text_editing) ? UI_FocusKind_On : UI_FocusKind_Off) - UI_TextRasterFlags(df_raster_flags_from_slot(ws, DF_FontSlot_Code)) - { - rule_editing_active = ui_is_focus_active(); - sig = df_line_editf(ws, DF_LineEditFlag_CodeContents|DF_LineEditFlag_NoBackground, 0, 0, &edit_state->cursor, &edit_state->mark, edit_state->input_buffer, sizeof(edit_state->input_buffer), &edit_state->input_size, 0, view_rule, "###view_rule_%I64x", row_hash); - } - - // rjf: press -> commit if not selected, select this cell - if(ui_pressed(sig)) - { - ewv->next_cursor = ewv->next_mark = pt; - pressed = 1; - } - - // rjf: autocomplete lister - if(rule_editing_active && - selection_tbl.min.x == selection_tbl.max.x && selection_tbl.min.y == selection_tbl.max.y && - txt_pt_match(edit_state->cursor, edit_state->mark)) - { - String8 input = str8(edit_state->input_buffer, edit_state->input_size); - DF_AutoCompListerParams params = df_view_rule_autocomp_lister_params_from_input_cursor(scratch.arena, input, edit_state->cursor.column-1); - if(params.flags == 0) - { - params.flags = DF_AutoCompListerFlag_ViewRules; - } - df_set_autocomp_lister_query(ws, sig.box->key, ctrl_ctx, ¶ms, input, edit_state->cursor.column-1); - } - - // rjf: double-click -> begin editing - if(ui_double_clicked(sig) && !ewv->text_editing) - { - ui_kill_action(); - DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Edit)); - } - } - } - } - } - } - - ////////////////////////////// - //- rjf: general table-wide press logic - // - if(pressed) - { - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - - scratch_end(scratch); - fzy_scope_close(fzy_scope); - di_scope_close(di_scope); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Null @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Null) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Null) { return str8_lit(""); } -DF_VIEW_CMD_FUNCTION_DEF(Null) {} -DF_VIEW_UI_FUNCTION_DEF(Null) {} - -//////////////////////////////// -//~ rjf: Empty @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Empty) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Empty) { return str8_lit(""); } -DF_VIEW_CMD_FUNCTION_DEF(Empty) {} -DF_VIEW_UI_FUNCTION_DEF(Empty) -{ - ui_set_next_flags(UI_BoxFlag_DefaultFocusNav); - UI_Focus(UI_FocusKind_On) UI_WidthFill UI_HeightFill UI_NamedColumn(str8_lit("empty_view")) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - UI_Padding(ui_pct(1, 0)) UI_Focus(UI_FocusKind_Null) - { - UI_PrefHeight(ui_em(3.f, 1.f)) - UI_Row - UI_Padding(ui_pct(1, 0)) - UI_TextAlignment(UI_TextAlign_Center) - UI_PrefWidth(ui_em(15.f, 1.f)) - UI_CornerRadius(ui_top_font_size()/2.f) - DF_Palette(ws, DF_PaletteCode_NegativePopButton) - { - if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_X, 0, "Close Panel"))) - { - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_ClosePanel)); - } - } - } -} - -//////////////////////////////// -//~ rjf: GettingStarted @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(GettingStarted) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(GettingStarted) { return str8_lit(""); } -DF_VIEW_CMD_FUNCTION_DEF(GettingStarted) {} -DF_VIEW_UI_FUNCTION_DEF(GettingStarted) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - ui_set_next_flags(UI_BoxFlag_DefaultFocusNav); - UI_Focus(UI_FocusKind_On) UI_WidthFill UI_HeightFill UI_NamedColumn(str8_lit("empty_view")) - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - UI_Padding(ui_pct(1, 0)) UI_Focus(UI_FocusKind_Null) - { - DF_EntityList targets = df_push_active_target_list(scratch.arena); - DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); - - //- rjf: icon & info - UI_Padding(ui_em(2.f, 1.f)) - { - //- rjf: icon - { - F32 icon_dim = ui_top_font_size()*10.f; - UI_PrefHeight(ui_px(icon_dim, 1.f)) - UI_Row - UI_Padding(ui_pct(1, 0)) - UI_PrefWidth(ui_px(icon_dim, 1.f)) - { - R_Handle texture = df_gfx_state->icon_texture; - Vec2S32 texture_dim = r_size_from_tex2d(texture); - ui_image(texture, R_Tex2DSampleKind_Linear, r2f32p(0, 0, texture_dim.x, texture_dim.y), v4f32(1, 1, 1, 1), 0, str8_lit("")); - } - } - - //- rjf: info - UI_Padding(ui_em(2.f, 1.f)) - UI_WidthFill UI_PrefHeight(ui_em(2.f, 1.f)) - UI_Row - UI_Padding(ui_pct(1, 0)) - UI_TextAlignment(UI_TextAlign_Center) - UI_PrefWidth(ui_text_dim(10, 1)) - { - ui_label(str8_lit(BUILD_TITLE_STRING_LITERAL)); - } - } - - //- rjf: targets state dependent helper - B32 helper_built = 0; - if(processes.count == 0) - { - helper_built = 1; - switch(targets.count) - { - //- rjf: user has no targets. build helper for adding them - case 0: - { - UI_PrefHeight(ui_em(3.75f, 1.f)) - UI_Row - UI_Padding(ui_pct(1, 0)) - UI_TextAlignment(UI_TextAlign_Center) - UI_PrefWidth(ui_em(22.f, 1.f)) - UI_CornerRadius(ui_top_font_size()/2.f) - DF_Palette(ws, DF_PaletteCode_NeutralPopButton) - if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_Add, 0, "Add Target"))) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.cmd_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_AddTarget); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); - } - }break; - - //- rjf: user has 1 target. build helper for launching it - case 1: - { - DF_Entity *target = df_first_entity_from_list(&targets); - String8 target_full_path = target->name; - String8 target_name = str8_skip_last_slash(target_full_path); - UI_PrefHeight(ui_em(3.75f, 1.f)) - UI_Row - UI_Padding(ui_pct(1, 0)) - UI_TextAlignment(UI_TextAlign_Center) - UI_PrefWidth(ui_em(22.f, 1.f)) - UI_CornerRadius(ui_top_font_size()/2.f) - DF_Palette(ws, DF_PaletteCode_PositivePopButton) - { - if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_Play, 0, "Launch %S", target_name))) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.entity = df_handle_from_entity(target); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_LaunchAndRun)); - } - ui_spacer(ui_em(1.5f, 1)); - if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_Play, 0, "Step Into %S", target_name))) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.entity = df_handle_from_entity(target); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_LaunchAndInit)); - } - } - }break; - - //- rjf: user has N targets. - default: - { - helper_built = 0; - }break; - } - } - - //- rjf: or text - if(helper_built) - { - UI_PrefHeight(ui_em(2.25f, 1.f)) - UI_Row - UI_Padding(ui_pct(1, 0)) - UI_TextAlignment(UI_TextAlign_Center) - UI_WidthFill - ui_labelf("- or -"); - } - - //- rjf: helper text for command lister activation - UI_PrefHeight(ui_em(2.25f, 1.f)) UI_Row - UI_PrefWidth(ui_text_dim(10, 1)) - UI_TextAlignment(UI_TextAlign_Center) - UI_Padding(ui_pct(1, 0)) - { - ui_labelf("use"); - DF_CmdSpec *spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand); - UI_Flags(UI_BoxFlag_DrawBorder) UI_TextAlignment(UI_TextAlign_Center) df_cmd_binding_buttons(ws, spec); - ui_labelf("to open command menu"); - } - } - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Commands @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Commands) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Commands) { return str8_lit(""); } -DF_VIEW_CMD_FUNCTION_DEF(Commands) {} -DF_VIEW_UI_FUNCTION_DEF(Commands) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - - //- rjf: grab state - typedef struct DF_CmdsViewState DF_CmdsViewState; - struct DF_CmdsViewState - { - DF_CmdSpec *selected_cmd_spec; - }; - DF_CmdsViewState *cv = df_view_user_state(view, DF_CmdsViewState); - - //- rjf: build filtered array of commands - String8 query = str8(view->query_buffer, view->query_string_size); - DF_CmdListerItemList cmd_list = df_cmd_lister_item_list_from_needle(scratch.arena, query); - DF_CmdListerItemArray cmd_array = df_cmd_lister_item_array_from_list(scratch.arena, cmd_list); - df_cmd_lister_item_array_sort_by_strength__in_place(cmd_array); - - //- rjf: submit best match when hitting enter w/ no selection - if(cv->selected_cmd_spec == &df_g_nil_cmd_spec && ui_slot_press(UI_EventActionSlot_Accept)) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - if(cmd_array.count > 0) - { - DF_CmdListerItem *item = &cmd_array.v[0]; - params.cmd_spec = item->cmd_spec; - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); - } - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); - } - - //- rjf: selected kind -> cursor - Vec2S64 cursor = {0}; - { - for(U64 idx = 0; idx < cmd_array.count; idx += 1) - { - if(cmd_array.v[idx].cmd_spec == cv->selected_cmd_spec) - { - cursor.y = (S64)idx+1; - break; - } - } - } - - //- rjf: build contents - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*6.5f); - scroll_list_params.dim_px = dim_2f32(rect); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, cmd_array.count)); - scroll_list_params.item_range = r1s64(0, cmd_array.count); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - &cursor, - 0, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - { - //- rjf: build buttons - for(S64 row_idx = visible_row_range.min; - row_idx <= visible_row_range.max && row_idx < cmd_array.count; - row_idx += 1) - { - DF_CmdListerItem *item = &cmd_array.v[row_idx]; - - //- rjf: build row contents - ui_set_next_hover_cursor(OS_Cursor_HandPoint); - ui_set_next_child_layout_axis(Axis2_X); - UI_Box *box = &ui_g_nil_box; - UI_Focus(cursor.y == row_idx+1 ? UI_FocusKind_On : UI_FocusKind_Off) - { - box = ui_build_box_from_stringf(UI_BoxFlag_Clickable| - UI_BoxFlag_DrawBorder| - UI_BoxFlag_DrawBackground| - UI_BoxFlag_DrawHotEffects| - UI_BoxFlag_DrawActiveEffects, - "###cmd_button_%p", item->cmd_spec); - } - UI_Parent(box) UI_PrefHeight(ui_em(1.65f, 1.f)) - { - //- rjf: icon - UI_PrefWidth(ui_em(3.f, 1.f)) - UI_HeightFill - UI_Column - DF_Font(ws, DF_FontSlot_Icons) - UI_FontSize(df_font_size_from_slot(ws, DF_FontSlot_Icons)) - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - UI_HeightFill - UI_TextAlignment(UI_TextAlign_Center) - { - DF_IconKind icon = item->cmd_spec->info.canonical_icon_kind; - if(icon != DF_IconKind_Null) - { - ui_label(df_g_icon_kind_text_table[icon]); - } - } - - //- rjf: name + description - ui_set_next_pref_height(ui_pct(1, 0)); - UI_Column UI_Padding(ui_pct(1, 0)) - { - F_Tag font = ui_top_font(); - F32 font_size = ui_top_font_size(); - F_Metrics font_metrics = f_metrics_from_tag_size(font, font_size); - F32 font_line_height = f_line_height_from_metrics(&font_metrics); - String8 cmd_display_name = item->cmd_spec->info.display_name; - String8 cmd_desc = item->cmd_spec->info.description; - UI_Box *name_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S##name_%p", cmd_display_name, item->cmd_spec); - UI_Box *desc_box = &ui_g_nil_box; - UI_PrefHeight(ui_em(1.8f, 1.f)) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - { - desc_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S##desc_%p", cmd_desc, item->cmd_spec); - } - ui_box_equip_fuzzy_match_ranges(name_box, &item->name_match_ranges); - ui_box_equip_fuzzy_match_ranges(desc_box, &item->desc_match_ranges); - } - - //- rjf: bindings - ui_set_next_flags(UI_BoxFlag_Clickable); - UI_PrefWidth(ui_children_sum(1.f)) UI_HeightFill UI_NamedColumn(str8_lit("binding_column")) UI_Padding(ui_em(1.5f, 1.f)) - { - ui_set_next_flags(UI_BoxFlag_Clickable); - UI_NamedRow(str8_lit("binding_row")) UI_Padding(ui_em(1.f, 1.f)) - { - df_cmd_binding_buttons(ws, item->cmd_spec); - } - } - } - - //- rjf: interact - UI_Signal sig = ui_signal_from_box(box); - if(ui_clicked(sig)) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.cmd_spec = item->cmd_spec; - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); - } - } - } - - //- rjf: map selected num -> selected kind - if(1 <= cursor.y && cursor.y <= cmd_array.count) - { - cv->selected_cmd_spec = cmd_array.v[cursor.y-1].cmd_spec; - } - else - { - cv->selected_cmd_spec = &df_g_nil_cmd_spec; - } - - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: FileSystem @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(FileSystem) -{ -} - -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(FileSystem) -{ - return str8_lit(""); -} - -DF_VIEW_CMD_FUNCTION_DEF(FileSystem) -{ -} - -DF_VIEW_UI_FUNCTION_DEF(FileSystem) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - String8 query = str8(view->query_buffer, view->query_string_size); - String8 query_normalized = path_normalized_from_string(scratch.arena, query); - B32 query_has_slash = (query.size != 0 && char_to_correct_slash(query.str[query.size-1]) == '/'); - String8 query_normalized_with_opt_slash = push_str8f(scratch.arena, "%S%s", query_normalized, query_has_slash ? "/" : ""); - DF_PathQuery path_query = df_path_query_from_string(query_normalized_with_opt_slash); - F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); - F32 scroll_bar_dim = floor_f32(ui_top_font_size()*1.5f); - B32 file_selection = !!(ws->query_cmd_spec->info.query.flags & DF_CmdQueryFlag_AllowFiles); - B32 dir_selection = !!(ws->query_cmd_spec->info.query.flags & DF_CmdQueryFlag_AllowFolders); - - //- rjf: get extra state for this view - DF_FileSystemViewState *fs = df_view_user_state(view, DF_FileSystemViewState); - if(fs->initialized == 0) - { - fs->initialized = 1; - fs->path_state_table_size = 256; - fs->path_state_table = push_array(view->arena, DF_FileSystemViewPathState *, fs->path_state_table_size); - fs->cached_files_arena = df_view_push_arena_ext(view); - fs->col_pcts[0] = 0.60f; - fs->col_pcts[1] = 0.20f; - fs->col_pcts[2] = 0.20f; - } - - //- rjf: grab state for the current path - DF_FileSystemViewPathState *ps = 0; - { - String8 key = query_normalized; - U64 hash = df_hash_from_string(key); - U64 slot = hash % fs->path_state_table_size; - for(DF_FileSystemViewPathState *p = fs->path_state_table[slot]; p != 0; p = p->hash_next) - { - if(str8_match(p->normalized_path, key, 0)) - { - ps = p; - break; - } - } - if(ps == 0) - { - ps = push_array(view->arena, DF_FileSystemViewPathState, 1); - ps->hash_next = fs->path_state_table[slot]; - fs->path_state_table[slot] = ps; - ps->normalized_path = push_str8_copy(view->arena, key); - } - } - - //- rjf: get file array from the current path - U64 file_count = fs->cached_file_count; - DF_FileInfo *files = fs->cached_files; - if(!str8_match(fs->cached_files_path, query_normalized_with_opt_slash, 0) || - fs->cached_files_sort_kind != fs->sort_kind || - fs->cached_files_sort_side != fs->sort_side) - { - arena_clear(fs->cached_files_arena); - - //- rjf: store off path that we're gathering from - fs->cached_files_path = push_str8_copy(fs->cached_files_arena, query_normalized_with_opt_slash); - fs->cached_files_sort_kind = fs->sort_kind; - fs->cached_files_sort_side = fs->sort_side; - - //- rjf: use stored path as the new browse path for the whole frontend - // (multiple file system views may conflict here. that's okay. we'll just always - // choose the most recent change to a file browser path, and live with the - // consequences). - { - DF_CmdParams p = df_cmd_params_zero(); - p.file_path = path_query.path; - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_FilePath); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SetCurrentPath)); - } - - //- rjf: get files, filtered - U64 new_file_count = 0; - DF_FileInfoNode *first_file = 0; - DF_FileInfoNode *last_file = 0; - { - OS_FileIter *it = os_file_iter_begin(scratch.arena, path_query.path, 0); - for(OS_FileInfo info = {0}; os_file_iter_next(scratch.arena, it, &info);) - { - FuzzyMatchRangeList match_ranges = fuzzy_match_find(fs->cached_files_arena, path_query.search, info.name); - B32 fits_search = (path_query.search.size == 0 || match_ranges.count == match_ranges.needle_part_count); - B32 fits_dir_only = !!(info.props.flags & FilePropertyFlag_IsFolder) || !dir_selection; - if(fits_search && fits_dir_only) - { - DF_FileInfoNode *node = push_array(scratch.arena, DF_FileInfoNode, 1); - node->file_info.filename = push_str8_copy(fs->cached_files_arena, info.name); - node->file_info.props = info.props; - node->file_info.match_ranges = match_ranges; - SLLQueuePush(first_file, last_file, node); - new_file_count += 1; - } - } - os_file_iter_end(it); - } - - //- rjf: convert list to array - DF_FileInfo *new_files = push_array(fs->cached_files_arena, DF_FileInfo, new_file_count); - { - U64 idx = 0; - for(DF_FileInfoNode *n = first_file; n != 0; n = n->next, idx += 1) - { - new_files[idx] = n->file_info; - } - } - - //- rjf: apply sort - switch(fs->sort_kind) - { - default: - { - if(path_query.search.size != 0) - { - quick_sort(new_files, new_file_count, sizeof(DF_FileInfo), df_qsort_compare_file_info__default_filtered); - } - else - { - quick_sort(new_files, new_file_count, sizeof(DF_FileInfo), df_qsort_compare_file_info__default); - } - }break; - case DF_FileSortKind_Filename: - { - quick_sort(new_files, new_file_count, sizeof(DF_FileInfo), df_qsort_compare_file_info__filename); - }break; - case DF_FileSortKind_LastModified: - { - quick_sort(new_files, new_file_count, sizeof(DF_FileInfo), df_qsort_compare_file_info__last_modified); - }break; - case DF_FileSortKind_Size: - { - quick_sort(new_files, new_file_count, sizeof(DF_FileInfo), df_qsort_compare_file_info__size); - }break; - } - - //- rjf: apply reverse - if(fs->sort_kind != DF_FileSortKind_Null && fs->sort_side == Side_Max) - { - for(U64 idx = 0; idx < new_file_count/2; idx += 1) - { - U64 rev_idx = new_file_count - idx - 1; - Swap(DF_FileInfo, new_files[idx], new_files[rev_idx]); - } - } - - fs->cached_file_count = file_count = new_file_count; - fs->cached_files = files = new_files; - } - - //- rjf: submit best match when hitting enter w/ no selection - if(ps->cursor.y == 0 && ui_slot_press(UI_EventActionSlot_Accept)) - { - FileProperties query_normalized_with_opt_slash_props = os_properties_from_file_path(query_normalized_with_opt_slash); - FileProperties path_query_path_props = os_properties_from_file_path(path_query.path); - - // rjf: command search part is empty, but directory matches some file: - if(path_query_path_props.created != 0 && path_query.search.size == 0) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.file_path = query_normalized_with_opt_slash; - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); - } - - // rjf: command argument exactly matches some file: - else if(query_normalized_with_opt_slash_props.created != 0 && path_query.search.size != 0) - { - // rjf: is a folder -> autocomplete to slash - if(query_normalized_with_opt_slash_props.flags & FilePropertyFlag_IsFolder) - { - String8 new_path = push_str8f(scratch.arena, "%S%S/", path_query.path, path_query.search); - df_view_equip_spec(ws, view, view->spec, df_entity_from_handle(view->entity), new_path, &df_g_nil_cfg_node); - } - - // rjf: is a file -> complete view - else - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.file_path = query_normalized_with_opt_slash; - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); - } - } - - // rjf: command argument is empty, picking folders -> use current folder - else if(path_query.search.size == 0 && dir_selection) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.file_path = path_query.path; - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); - } - - // rjf: command argument does not exactly match any file, but lister results are in: - else if(file_count != 0) - { - String8 filename = files[0].filename; - if(files[0].props.flags & FilePropertyFlag_IsFolder) - { - String8 existing_path = str8_chop_last_slash(path_query.path); - String8 new_path = push_str8f(scratch.arena, "%S/%S/", existing_path, files[0].filename); - df_view_equip_spec(ws, view, view->spec, df_entity_from_handle(view->entity), new_path, &df_g_nil_cfg_node); - } - else - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.file_path = push_str8f(scratch.arena, "%S%S", path_query.path, filename); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); - } - } - - // rjf: command argument does not match any file, and lister is empty (new file) - else - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.file_path = query; - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); - } - } - - //- rjf: build non-scrolled table header - U64 row_num = 1; - F32 **col_pcts = push_array(scratch.arena, F32 *, ArrayCount(fs->col_pcts)); - for(U64 idx = 0; idx < ArrayCount(fs->col_pcts); idx += 1) - { - col_pcts[idx] = &fs->col_pcts[idx]; - } - UI_PrefHeight(ui_px(row_height_px, 1)) UI_Focus(UI_FocusKind_Off) UI_TableF(ArrayCount(fs->col_pcts), col_pcts, "###fs_tbl") - { - UI_TableVector - { - struct - { - DF_FileSortKind kind; - String8 string; - } - kinds[] = - { - { DF_FileSortKind_Filename, str8_lit_comp("Filename") }, - { DF_FileSortKind_LastModified, str8_lit_comp("Last Modified") }, - { DF_FileSortKind_Size, str8_lit_comp("Size") }, - }; - for(U64 idx = 0; idx < ArrayCount(kinds); idx += 1) - { - B32 sorting = (fs->sort_kind == kinds[idx].kind); - UI_TableCell UI_FlagsAdd(sorting ? 0 : UI_BoxFlag_DrawTextWeak) - { - UI_Signal sig = ui_sort_header(sorting, - fs->cached_files_sort_side == Side_Min, - kinds[idx].string); - if(ui_clicked(sig)) - { - if(fs->sort_kind != kinds[idx].kind) - { - fs->sort_kind = kinds[idx].kind; - fs->sort_side = Side_Max; - } - else if(fs->sort_kind == kinds[idx].kind && fs->sort_side == Side_Max) - { - fs->sort_side = Side_Min; - } - else if(fs->sort_kind == kinds[idx].kind && fs->sort_side == Side_Min) - { - fs->sort_kind = DF_FileSortKind_Null; - } - } - } - } - } - } - - //- rjf: build file list - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - Vec2F32 content_dim = dim_2f32(rect); - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = row_height_px; - scroll_list_params.dim_px = v2f32(content_dim.x, content_dim.y-row_height_px); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, file_count+1)); - scroll_list_params.item_range = r1s64(0, file_count+1); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - &ps->cursor, - 0, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - { - // rjf: up-one-directory button (at idx 0) - if(visible_row_range.min == 0) - { - // rjf: build - UI_Signal sig = {0}; - UI_FocusHot(ps->cursor.y == row_num ? UI_FocusKind_On : UI_FocusKind_Off) - { - sig = ui_buttonf("###up_one"); - } - - // rjf: make content - UI_Parent(sig.box) - { - // rjf: icons - DF_Font(ws, DF_FontSlot_Icons) - UI_FontSize(df_font_size_from_slot(ws, DF_FontSlot_Icons)) - UI_PrefWidth(ui_em(3.f, 1.f)) - UI_TextAlignment(UI_TextAlign_Center) - { - ui_label(df_g_icon_kind_text_table[DF_IconKind_LeftArrow]); - } - - // rjf: text - { - ui_label(str8_lit("Up One Directory")); - } - - row_num += 1; - } - - // rjf: click => up one directory - if(ui_clicked(sig)) - { - String8 new_path = str8_chop_last_slash(str8_chop_last_slash(path_query.path)); - new_path = path_normalized_from_string(scratch.arena, new_path); - String8 new_cmd = push_str8f(scratch.arena, "%S%s", new_path, new_path.size != 0 ? "/" : ""); - df_view_equip_spec(ws, view, view->spec, df_entity_from_handle(view->entity), new_cmd, &df_g_nil_cfg_node); - } - } - - // rjf: file buttons - for(U64 row_idx = Max(visible_row_range.min, 1); - row_idx <= visible_row_range.max && row_idx <= file_count; - row_idx += 1, row_num += 1) - { - U64 file_idx = row_idx-1; - DF_FileInfo *file = &files[file_idx]; - B32 file_kb_focus = (ps->cursor.y == (row_idx+1)); - - // rjf: make button - UI_Signal file_sig = {0}; - UI_FocusHot(file_kb_focus ? UI_FocusKind_On : UI_FocusKind_Off) - { - file_sig = ui_buttonf("##%S_%p", file->filename, view); - } - - // rjf: make content - UI_Parent(file_sig.box) - { - UI_PrefWidth(ui_pct(fs->col_pcts[0], 1)) UI_Row - { - // rjf: icon to signify directory - DF_Font(ws, DF_FontSlot_Icons) - UI_FontSize(df_font_size_from_slot(ws, DF_FontSlot_Icons)) - UI_PrefWidth(ui_em(3.f, 1.f)) - UI_TextAlignment(UI_TextAlign_Center) - { - if(file->props.flags & FilePropertyFlag_IsFolder) - { - ui_label((ui_key_match(ui_hot_key(), file_sig.box->key) || file_kb_focus) - ? df_g_icon_kind_text_table[DF_IconKind_FolderOpenFilled] - : df_g_icon_kind_text_table[DF_IconKind_FolderClosedFilled]); - } - else - { - ui_label(df_g_icon_kind_text_table[DF_IconKind_FileOutline]); - } - } - - // rjf: filename - UI_PrefWidth(ui_pct(1, 0)) - { - UI_Box *box = ui_build_box_from_string(UI_BoxFlag_DrawText|UI_BoxFlag_DisableIDString, file->filename); - ui_box_equip_fuzzy_match_ranges(box, &file->match_ranges); - } - } - - // rjf: last-modified time - UI_PrefWidth(ui_pct(fs->col_pcts[1], 1)) UI_Row - UI_PrefWidth(ui_pct(1, 0)) - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - { - DateTime time = date_time_from_dense_time(file->props.modified); - DateTime time_local = os_local_time_from_universal_time(&time); - String8 string = push_date_time_string(scratch.arena, &time_local); - ui_label(string); - } - - // rjf: file size - UI_PrefWidth(ui_pct(fs->col_pcts[2], 1)) UI_Row - UI_PrefWidth(ui_pct(1, 0)) - { - if(file->props.size != 0) - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(str8_from_memory_size(scratch.arena, file->props.size)); - } - } - } - - // rjf: click => activate this file - if(ui_clicked(file_sig)) - { - String8 existing_path = str8_chop_last_slash(path_query.path); - String8 new_path = push_str8f(scratch.arena, "%S%s%S/", existing_path, existing_path.size != 0 ? "/" : "", file->filename); - new_path = path_normalized_from_string(scratch.arena, new_path); - if(file->props.flags & FilePropertyFlag_IsFolder) - { - String8 new_cmd = push_str8f(scratch.arena, "%S%s", new_path, new_path.size != 0 ? "/" : ""); - df_view_equip_spec(ws, view, view->spec, df_entity_from_handle(view->entity), new_cmd, &df_g_nil_cfg_node); - } - else - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.file_path = new_path; - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); - } - } - } - } - - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: SystemProcesses @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(SystemProcesses) -{ -} - -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(SystemProcesses) -{ - return str8_lit(""); -} - -DF_VIEW_CMD_FUNCTION_DEF(SystemProcesses) -{ -} - -DF_VIEW_UI_FUNCTION_DEF(SystemProcesses) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); - - //- rjf: grab state - typedef struct DF_SystemProcessesViewState DF_SystemProcessesViewState; - struct DF_SystemProcessesViewState - { - B32 initialized; - B32 need_initial_gather; - U32 selected_pid; - Arena *cached_process_arena; - String8 cached_process_arg; - DF_ProcessInfoArray cached_process_array; - }; - DF_SystemProcessesViewState *sp = df_view_user_state(view, DF_SystemProcessesViewState); - if(sp->initialized == 0) - { - sp->initialized = 1; - sp->need_initial_gather = 1; - sp->cached_process_arena = df_view_push_arena_ext(view); - } - - //- rjf: gather list of filtered process infos - String8 query = str8(view->query_buffer, view->query_string_size); - DF_ProcessInfoArray process_info_array = sp->cached_process_array; - if(sp->need_initial_gather || !str8_match(sp->cached_process_arg, query, 0)) - { - arena_clear(sp->cached_process_arena); - sp->need_initial_gather = 0; - sp->cached_process_arg = push_str8_copy(sp->cached_process_arena, query); - DF_ProcessInfoList list = df_process_info_list_from_query(sp->cached_process_arena, query); - sp->cached_process_array = df_process_info_array_from_list(sp->cached_process_arena, list); - process_info_array = sp->cached_process_array; - df_process_info_array_sort_by_strength__in_place(process_info_array); - } - - //- rjf: submit best match when hitting enter w/ no selection - if(sp->selected_pid == 0 && process_info_array.count > 0 && ui_slot_press(UI_EventActionSlot_Accept)) - { - DF_ProcessInfo *info = &process_info_array.v[0]; - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.id = info->info.pid; - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_ID); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); - } - - //- rjf: selected PID -> cursor - Vec2S64 cursor = {0}; - { - for(U64 idx = 0; idx < process_info_array.count; idx += 1) - { - if(process_info_array.v[idx].info.pid == sp->selected_pid) - { - cursor.y = idx+1; - break; - } - } - } - - //- rjf: build contents - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - Vec2F32 content_dim = dim_2f32(rect); - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = row_height_px; - scroll_list_params.dim_px = v2f32(content_dim.x, content_dim.y); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, process_info_array.count)); - scroll_list_params.item_range = r1s64(0, process_info_array.count); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - &cursor, - 0, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - { - //- rjf: build rows - for(U64 idx = visible_row_range.min; - idx <= visible_row_range.max && idx < process_info_array.count; - idx += 1) - { - DF_ProcessInfo *info = &process_info_array.v[idx]; - B32 is_attached = info->is_attached; - UI_Signal sig = {0}; - UI_FocusHot(cursor.y == idx+1 ? UI_FocusKind_On : UI_FocusKind_Off) - { - sig = ui_buttonf("###proc_%i", info->info.pid); - } - UI_Parent(sig.box) - { - // rjf: icon - DF_Font(ws, DF_FontSlot_Icons) - UI_FontSize(df_font_size_from_slot(ws, DF_FontSlot_Icons)) - UI_PrefWidth(ui_em(3.f, 1.f)) - UI_TextAlignment(UI_TextAlign_Center) - { - ui_label(df_g_icon_kind_text_table[DF_IconKind_Threads]); - } - - // rjf: attached indicator - if(is_attached) UI_PrefWidth(ui_text_dim(10, 1)) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - { - UI_Box *attached_label = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "[attached]##attached_label_%i", (int)info->info.pid); - ui_box_equip_fuzzy_match_ranges(attached_label, &info->attached_match_ranges); - } - - // rjf: process name - UI_PrefWidth(ui_text_dim(10, 1)) - { - UI_Box *name_label = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S##name_label_%i", info->info.name, (int)info->info.pid); - ui_box_equip_fuzzy_match_ranges(name_label, &info->name_match_ranges); - } - - // rjf: process number - UI_PrefWidth(ui_text_dim(1, 1)) UI_TextAlignment(UI_TextAlign_Center) - { - ui_labelf("[PID: "); - UI_Box *pid_label = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%i##pid_label", info->info.pid); - ui_box_equip_fuzzy_match_ranges(pid_label, &info->pid_match_ranges); - ui_labelf("]"); - } - } - - // rjf: click => activate this specific process - if(ui_clicked(sig)) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.id = info->info.pid; - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_ID); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); - } - } - } - - //- rjf: selected num -> selected PID - { - if(1 <= cursor.y && cursor.y <= process_info_array.count) - { - sp->selected_pid = process_info_array.v[cursor.y-1].info.pid; - } - else - { - sp->selected_pid = 0; - } - } - - - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: EntityLister @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(EntityLister) -{ -} - -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(EntityLister) -{ - return str8_lit(""); -} - -DF_VIEW_CMD_FUNCTION_DEF(EntityLister) -{ -} - -DF_VIEW_UI_FUNCTION_DEF(EntityLister) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DF_CmdSpec *spec = ws->query_cmd_spec; - DF_EntityKind entity_kind = spec->info.query.entity_kind; - DF_EntityFlags entity_flags_omit = DF_EntityFlag_IsFolder; - F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); - F32 scroll_bar_dim = floor_f32(ui_top_font_size()*1.5f); - - //- rjf: grab state - typedef struct DF_EntityListerViewState DF_EntityListerViewState; - struct DF_EntityListerViewState - { - DF_Handle selected_entity_handle; - }; - DF_EntityListerViewState *fev = df_view_user_state(view, DF_EntityListerViewState); - DF_Handle selected_entity_handle = fev->selected_entity_handle; - DF_Entity *selected_entity = df_entity_from_handle(selected_entity_handle); - - //- rjf: build filtered array of entities - String8 query = str8(view->query_buffer, view->query_string_size); - DF_EntityListerItemList ent_list = df_entity_lister_item_list_from_needle(scratch.arena, entity_kind, entity_flags_omit, query); - DF_EntityListerItemArray ent_arr = df_entity_lister_item_array_from_list(scratch.arena, ent_list); - df_entity_lister_item_array_sort_by_strength__in_place(ent_arr); - - //- rjf: submit best match when hitting enter w/ no selection - if(df_entity_is_nil(df_entity_from_handle(fev->selected_entity_handle)) && ent_arr.count != 0 && ui_slot_press(UI_EventActionSlot_Accept)) - { - DF_Entity *ent = ent_arr.v[0].entity; - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.entity = df_handle_from_entity(ent); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_EntityList); - df_handle_list_push(scratch.arena, ¶ms.entity_list, params.entity); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); - } - - //- rjf: selected entity -> cursor - Vec2S64 cursor = {0}; - { - for(U64 idx = 0; idx < ent_arr.count; idx += 1) - { - if(ent_arr.v[idx].entity == selected_entity) - { - cursor.y = (S64)(idx+1); - break; - } - } - } - - //- rjf: build list - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - Vec2F32 content_dim = dim_2f32(rect); - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = row_height_px; - scroll_list_params.dim_px = v2f32(content_dim.x, content_dim.y); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, ent_arr.count)); - scroll_list_params.item_range = r1s64(0, ent_arr.count); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - &cursor, - 0, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - { - for(S64 idx = visible_row_range.min; - idx <= visible_row_range.max && idx < ent_arr.count; - idx += 1) - { - DF_EntityListerItem item = ent_arr.v[idx]; - DF_Entity *ent = item.entity; - ui_set_next_hover_cursor(OS_Cursor_HandPoint); - ui_set_next_child_layout_axis(Axis2_X); - UI_Box *box = &ui_g_nil_box; - UI_FocusHot(idx+1 == cursor.y ? UI_FocusKind_On : UI_FocusKind_Off) - { - box = ui_build_box_from_stringf(UI_BoxFlag_Clickable| - UI_BoxFlag_DrawBorder| - UI_BoxFlag_DrawBackground| - UI_BoxFlag_DrawHotEffects| - UI_BoxFlag_DrawActiveEffects, - "###ent_btn_%p", ent); - } - UI_Parent(box) - { - DF_IconKind icon_kind = df_g_entity_kind_icon_kind_table[ent->kind]; - if(icon_kind != DF_IconKind_Null) - { - UI_TextAlignment(UI_TextAlign_Center) - DF_Font(ws, DF_FontSlot_Icons) - UI_FontSize(df_font_size_from_slot(ws, DF_FontSlot_Icons)) - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - UI_PrefWidth(ui_text_dim(10, 1)) - ui_label(df_g_icon_kind_text_table[icon_kind]); - } - String8 display_string = df_display_string_from_entity(scratch.arena, ent); - Vec4F32 color = df_rgba_from_entity(ent); - if(color.w != 0) - { - ui_set_next_palette(ui_build_palette(ui_top_palette(), .text = color)); - } - UI_Box *name_label = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S##label_%p", display_string, ent); - ui_box_equip_fuzzy_match_ranges(name_label, &item.name_match_ranges); - } - if(ui_clicked(ui_signal_from_box(box))) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.entity = df_handle_from_entity(ent); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_EntityList); - df_handle_list_push(scratch.arena, ¶ms.entity_list, params.entity); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); - } - } - } - - //- rjf: selected entity num -> handle - { - fev->selected_entity_handle = (1 <= cursor.y && cursor.y <= ent_arr.count) ? df_handle_from_entity(ent_arr.v[cursor.y-1].entity) : df_handle_zero(); - } - - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: SymbolLister @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(SymbolLister) -{ -} - -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(SymbolLister) -{ - return str8_lit(""); -} - -DF_VIEW_CMD_FUNCTION_DEF(SymbolLister) -{ -} - -DF_VIEW_UI_FUNCTION_DEF(SymbolLister) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DI_Scope *di_scope = di_scope_open(); - FZY_Scope *fzy_scope = fzy_scope_open(); - F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); - String8 query = str8(view->query_buffer, view->query_string_size); - DI_KeyList dbgi_keys_list = df_push_active_dbgi_key_list(scratch.arena); - DI_KeyArray dbgi_keys = di_key_array_from_list(scratch.arena, &dbgi_keys_list); - FZY_Params params = {FZY_Target_Procedures, dbgi_keys}; - U64 endt_us = os_now_microseconds()+200; - - //- rjf: grab rdis, make type graphs for each - U64 rdis_count = dbgi_keys.count; - RDI_Parsed **rdis = push_array(scratch.arena, RDI_Parsed *, rdis_count); - TG_Graph **graphs = push_array(scratch.arena, TG_Graph *, rdis_count); - { - for(U64 idx = 0; idx < rdis_count; idx += 1) - { - RDI_Parsed *rdi = di_rdi_from_key(di_scope, &dbgi_keys.v[idx], endt_us); - RDI_TopLevelInfo *tli = rdi_element_from_name_idx(rdi, TopLevelInfo, 0); - rdis[idx] = rdi; - graphs[idx] = tg_graph_begin(rdi_addr_size_from_arch(tli->arch), 256); - } - } - - //- rjf: grab state - typedef struct DF_SymbolListerViewState DF_SymbolListerViewState; - struct DF_SymbolListerViewState - { - Vec2S64 cursor; - }; - DF_SymbolListerViewState *slv = df_view_user_state(view, DF_SymbolListerViewState); - - //- rjf: query -> raddbg, filtered items - U128 fuzzy_search_key = {(U64)view, df_hash_from_string(str8_struct(&view))}; - B32 items_stale = 0; - FZY_ItemArray items = fzy_items_from_key_params_query(fzy_scope, fuzzy_search_key, ¶ms, query, endt_us, &items_stale); - if(items_stale) - { - df_gfx_request_frame(); - } - - //- rjf: submit best match when hitting enter w/ no selection - if(slv->cursor.y == 0 && items.count != 0 && ui_slot_press(UI_EventActionSlot_Accept)) - { - FZY_Item *item = &items.v[0]; - U64 base_idx = 0; - for(U64 rdi_idx = 0; rdi_idx < rdis_count; rdi_idx += 1) - { - RDI_Parsed *rdi = rdis[rdi_idx]; - U64 rdi_procedures_count = 0; - rdi_section_raw_table_from_kind(rdi, RDI_SectionKind_Procedures, &rdi_procedures_count); - if(base_idx <= item->idx && item->idx < base_idx + rdi_procedures_count) - { - RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, item->idx-base_idx); - U64 name_size = 0; - U8 *name_base = rdi_string_from_idx(rdi, procedure->name_string_idx, &name_size); - String8 name = str8(name_base, name_size); - if(name.size != 0) - { - DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); - p.string = name; - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); - } - break; - } - base_idx += rdi_procedures_count; - } - } - - //- rjf: build contents - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - Vec2F32 content_dim = dim_2f32(rect); - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = row_height_px; - scroll_list_params.dim_px = v2f32(content_dim.x, content_dim.y); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, items.count)); - scroll_list_params.item_range = r1s64(0, items.count); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - &slv->cursor, - 0, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - UI_TextRasterFlags(df_raster_flags_from_slot(ws, DF_FontSlot_Code)) - { - //- rjf: build rows - for(U64 idx = visible_row_range.min; - idx <= visible_row_range.max && idx < items.count; - idx += 1) - UI_Focus((slv->cursor.y == idx+1) ? UI_FocusKind_On : UI_FocusKind_Off) - { - FZY_Item *item = &items.v[idx]; - - //- rjf: determine dbgi/rdi to which this item belongs - DI_Key dbgi_key = {0}; - RDI_Parsed *rdi = &di_rdi_parsed_nil; - TG_Graph *graph = 0; - U64 base_idx = 0; - { - for(U64 rdi_idx = 0; rdi_idx < rdis_count; rdi_idx += 1) - { - U64 procedures_count = 0; - rdi_section_raw_table_from_kind(rdis[rdi_idx], RDI_SectionKind_Procedures, &procedures_count); - if(base_idx <= item->idx && item->idx < base_idx + procedures_count) - { - dbgi_key = dbgi_keys.v[rdi_idx]; - rdi = rdis[rdi_idx]; - graph = graphs[rdi_idx]; - break; - } - base_idx += procedures_count; - } - } - - //- rjf: unpack this item's info - RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, item->idx-base_idx); - U64 name_size = 0; - U8 *name_base = rdi_string_from_idx(rdi, procedure->name_string_idx, &name_size); - String8 name = str8(name_base, name_size); - RDI_TypeNode *type_node = rdi_element_from_name_idx(rdi, TypeNodes, procedure->type_idx); - TG_Key type_key = tg_key_ext(tg_kind_from_rdi_type_kind(type_node->kind), procedure->type_idx); - - //- rjf: build item button - ui_set_next_hover_cursor(OS_Cursor_HandPoint); - UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable| - UI_BoxFlag_DrawBackground| - UI_BoxFlag_DrawBorder| - UI_BoxFlag_DrawText| - UI_BoxFlag_DrawHotEffects| - UI_BoxFlag_DrawActiveEffects, - "###procedure_%I64x", item->idx); - UI_Parent(box) UI_PrefWidth(ui_text_dim(10, 1)) - { - UI_Box *box = df_code_label(1.f, 0, df_rgba_from_theme_color(DF_ThemeColor_CodeSymbol), name); - ui_box_equip_fuzzy_match_ranges(box, &item->match_ranges); - if(!tg_key_match(tg_key_zero(), type_key) && graph != 0) - { - String8 type_string = tg_string_from_key(scratch.arena, graph, rdi, type_key); - df_code_label(0.5f, 0, df_rgba_from_theme_color(DF_ThemeColor_TextWeak), type_string); - } - } - - //- rjf: interact - UI_Signal sig = ui_signal_from_box(box); - if(ui_clicked(sig)) - { - DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); - p.string = name; - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); - } - if(ui_hovering(sig)) UI_Tooltip - { - df_code_label(1.f, 0, df_rgba_from_theme_color(DF_ThemeColor_CodeSymbol), name); - DF_Font(ws, DF_FontSlot_Main) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - ui_labelf("Procedure #%I64u", item->idx); - U64 binary_voff = df_voff_from_dbgi_key_symbol_name(&dbgi_key, name); - DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, binary_voff); - if(lines.first != 0) - { - String8 file_path = df_full_path_from_entity(scratch.arena, df_entity_from_handle(lines.first->v.file)); - S64 line_num = lines.first->v.pt.line; - DF_Font(ws, DF_FontSlot_Main) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - ui_labelf("%S:%I64d", file_path, line_num); - } - else - { - DF_Font(ws, DF_FontSlot_Main) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - ui_label(str8_lit("(No source code location found)")); - } - } - } - } - - fzy_scope_close(fzy_scope); - di_scope_close(di_scope); - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Target @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Target) -{ - DF_TargetViewState *tv = df_view_user_state(view, DF_TargetViewState); -} - -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Target) -{ - DF_TargetViewState *tv = df_view_user_state(view, DF_TargetViewState); - return str8_lit(""); -} - -DF_VIEW_CMD_FUNCTION_DEF(Target) -{ - DF_TargetViewState *tv = df_view_user_state(view, DF_TargetViewState); - DF_Entity *entity = df_entity_from_handle(view->entity); - - // rjf: process commands - for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) - { - DF_Cmd *cmd = &n->cmd; - - // rjf: mismatched window/panel => skip - if(df_window_from_handle(cmd->params.window) != ws || - df_panel_from_handle(cmd->params.panel) != panel) - { - continue; - } - - // rjf: process command - DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); - switch(core_cmd_kind) - { - default:break; - case DF_CoreCmdKind_PickFile: - case DF_CoreCmdKind_PickFolder: - { - String8 pick_string = cmd->params.file_path; - DF_Entity *storage_entity = entity; - if(tv->pick_dst_kind != DF_EntityKind_Nil) - { - DF_Entity *child = df_entity_child_from_kind(entity, tv->pick_dst_kind); - if(df_entity_is_nil(child)) - { - child = df_entity_alloc(0, entity, tv->pick_dst_kind); - } - storage_entity = child; - } - df_entity_equip_name(0, storage_entity, pick_string); - }break; - } - } -} - -DF_VIEW_UI_FUNCTION_DEF(Target) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DF_Entity *entity = df_entity_from_handle(view->entity); - DF_EntityList custom_entry_points = df_push_entity_child_list_with_kind(scratch.arena, entity, DF_EntityKind_EntryPointName); - F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); - - //- rjf: grab state - DF_TargetViewState *tv = df_view_user_state(view, DF_TargetViewState); - if(tv->initialized == 0) - { - tv->initialized = 1; - tv->key_pct = 0.2f; - tv->value_pct = 0.8f; - } - - //- rjf: set up key-value-pair info - struct - { - B32 fill_with_file; - B32 fill_with_folder; - B32 use_code_font; - String8 key; - DF_EntityKind storage_child_kind; - String8 current_text; - } - kv_info[] = - { - { 0, 0, 0, str8_lit("Label"), DF_EntityKind_Nil, entity->name }, - { 1, 0, 0, str8_lit("Executable"), DF_EntityKind_Executable, df_entity_child_from_kind(entity, DF_EntityKind_Executable)->name }, - { 0, 0, 0, str8_lit("Arguments"), DF_EntityKind_Arguments, df_entity_child_from_kind(entity, DF_EntityKind_Arguments)->name }, - { 0, 1, 0, str8_lit("Working Directory"), DF_EntityKind_ExecutionPath, df_entity_child_from_kind(entity, DF_EntityKind_ExecutionPath)->name }, - { 0, 0, 1, str8_lit("Entry Point Override"), DF_EntityKind_EntryPointName, df_entity_child_from_kind(entity, DF_EntityKind_EntryPointName)->name }, - }; - - //- rjf: take controls to start/end editing - B32 edit_begin = 0; - B32 edit_end = 0; - B32 edit_commit = 0; - B32 edit_submit = 0; - UI_Focus(UI_FocusKind_On) if(ui_is_focus_active()) - { - if(!tv->input_editing) - { - UI_EventList *events = ui_events(); - for(UI_EventNode *n = events->first; n != 0; n = n->next) - { - if(n->v.string.size != 0 || n->v.flags & UI_EventFlag_Paste) - { - edit_begin = 1; - break; - } - } - if(ui_slot_press(UI_EventActionSlot_Edit)) - { - edit_begin = 1; - } - if(ui_slot_press(UI_EventActionSlot_Accept)) - { - edit_begin = 1; - } - } - if(tv->input_editing) - { - if(ui_slot_press(UI_EventActionSlot_Cancel)) - { - edit_end = 1; - edit_commit = 0; - } - if(ui_slot_press(UI_EventActionSlot_Accept)) - { - edit_end = 1; - edit_commit = 1; - edit_submit = 1; - } - } - } - - //- rjf: build - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = row_height_px; - scroll_list_params.dim_px = dim_2f32(rect); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, (S64)ArrayCount(kv_info))); - scroll_list_params.item_range = r1s64(0, (S64)ArrayCount(kv_info)); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - DF_EntityKind commit_storage_child_kind = DF_EntityKind_Nil; - Vec2S64 next_cursor = tv->cursor; - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - tv->input_editing ? 0 : &tv->cursor, - 0, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - { - next_cursor = tv->cursor; - F32 *col_pcts[] = {&tv->key_pct, &tv->value_pct}; - UI_TableF(ArrayCount(col_pcts), col_pcts, "###target_%p", view) - { - //- rjf: build fixed rows - S64 row_idx = 0; - for(S64 idx = visible_row_range.min; - idx <= visible_row_range.max && idx < ArrayCount(kv_info); - idx += 1, row_idx += 1) - UI_TableVector - { - B32 row_selected = (tv->cursor.y == idx+1); - B32 has_browse = kv_info[idx].fill_with_file || kv_info[idx].fill_with_folder; - - //- rjf: key (label) - UI_TableCell UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - { - if(kv_info[idx].storage_child_kind == DF_EntityKind_EntryPointName) - { - if(df_help_label(str8_lit("Custom Entry Point"))) UI_Tooltip - { - ui_label_multiline(ui_top_font_size()*30.f, str8_lit("By default, the debugger attempts to find a target's entry point with a set of default names, such as:")); - ui_spacer(ui_em(1.5f, 1.f)); - DF_Font(ws, DF_FontSlot_Code) UI_Palette(ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_CodeSymbol))) - { - ui_label(str8_lit("WinMain")); - ui_label(str8_lit("wWinMain")); - ui_label(str8_lit("main")); - ui_label(str8_lit("wmain")); - ui_label(str8_lit("WinMainCRTStartup")); - ui_label(str8_lit("wWinMainCRTStartup")); - } - ui_spacer(ui_em(1.5f, 1.f)); - ui_label_multiline(ui_top_font_size()*30.f, str8_lit("A Custom Entry Point can be used to override these default symbol names with a symbol name of your choosing. If a symbol matching the Custom Entry Point is not found, the debugger will fall back to its default rules.")); - } - } - else - { - ui_build_box_from_string(UI_BoxFlag_Clickable|UI_BoxFlag_DrawText, kv_info[idx].key); - } - } - - //- rjf: value - UI_TableCell - { - // rjf: value editor - UI_WidthFill DF_Font(ws, kv_info[idx].use_code_font ? DF_FontSlot_Code : DF_FontSlot_Main) - { - // rjf: * => focus - B32 value_selected = row_selected && (next_cursor.x == 0 || !has_browse); - - // rjf: begin editing - if(value_selected && edit_begin) - { - tv->input_editing = 1; - tv->input_size = Min(sizeof(tv->input_buffer), kv_info[idx].current_text.size); - MemoryCopy(tv->input_buffer, kv_info[idx].current_text.str, tv->input_size); - tv->input_cursor = txt_pt(1, 1+tv->input_size); - tv->input_mark = txt_pt(1, 1); - } - - // rjf: build main editor ui - UI_Signal sig = {0}; - UI_FocusHot(value_selected ? UI_FocusKind_On : UI_FocusKind_Off) - UI_FocusActive((value_selected && tv->input_editing) ? UI_FocusKind_On : UI_FocusKind_Off) - { - sig = df_line_editf(ws, DF_LineEditFlag_NoBackground, 0, 0, &tv->input_cursor, &tv->input_mark, tv->input_buffer, sizeof(tv->input_buffer), &tv->input_size, 0, kv_info[idx].current_text, "###kv_editor_%i", (S32)idx); - edit_commit = edit_commit || ui_committed(sig); - } - - // rjf: focus panel on press - if(ui_pressed(sig)) - { - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - - // rjf: begin editing on double-click - if(!tv->input_editing && ui_double_clicked(sig)) - { - ui_kill_action(); - tv->input_editing = 1; - tv->input_size = Min(sizeof(tv->input_buffer), kv_info[idx].current_text.size); - MemoryCopy(tv->input_buffer, kv_info[idx].current_text.str, tv->input_size); - tv->input_cursor = txt_pt(1, 1+tv->input_size); - tv->input_mark = txt_pt(1, 1); - } - - // rjf: press on non-selected => commit edit, change selected cell - if(ui_pressed(sig) && !value_selected) - { - edit_end = 1; - edit_commit = tv->input_editing; - next_cursor = v2s64(0, idx+1); - } - - // rjf: apply commit deltas - if(ui_committed(sig)) - { - next_cursor.y += 1; - } - - // rjf: grab commit destination - if(value_selected) - { - commit_storage_child_kind = kv_info[idx].storage_child_kind; - } - } - - // rjf: browse button to fill text field - if(has_browse) UI_PrefWidth(ui_text_dim(10, 1)) - { - UI_FocusHot((row_selected && next_cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) - UI_TextAlignment(UI_TextAlign_Center) - if(ui_clicked(ui_buttonf("Browse..."))) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.cmd_spec = df_cmd_spec_from_core_cmd_kind(kv_info[idx].fill_with_file ? DF_CoreCmdKind_PickFile : DF_CoreCmdKind_PickFolder); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); - tv->pick_dst_kind = kv_info[idx].storage_child_kind; - } - } - } - } - } - } - - //- rjf: apply commit - if(edit_commit) - { - String8 new_string = str8(tv->input_buffer, tv->input_size); - df_state_delta_history_push_batch(df_state_delta_history(), 0); - switch(commit_storage_child_kind) - { - default: - { - DF_Entity *child = df_entity_child_from_kind(entity, commit_storage_child_kind); - if(df_entity_is_nil(child)) - { - child = df_entity_alloc(df_state_delta_history(), entity, commit_storage_child_kind); - } - df_entity_equip_name(df_state_delta_history(), child, new_string); - }break; - case DF_EntityKind_Nil: - { - df_entity_equip_name(df_state_delta_history(), entity, new_string); - }break; - } - } - - //- rjf: apply editing finish - if(edit_end) - { - tv->input_editing = 0; - } - if(edit_submit) - { - next_cursor.y += 1; - } - - //- rjf: apply moves to selection - tv->cursor = next_cursor; - - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Targets @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Targets) -{ -} - -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Targets) -{ - return str8_lit(""); -} - -DF_VIEW_CMD_FUNCTION_DEF(Targets) -{ -} - -DF_VIEW_UI_FUNCTION_DEF(Targets) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DF_EntityList targets_list = df_query_cached_entity_list_with_kind(DF_EntityKind_Target); - String8 query = str8(view->query_buffer, view->query_string_size); - DF_EntityFuzzyItemArray targets = df_entity_fuzzy_item_array_from_entity_list_needle(scratch.arena, &targets_list, query); - F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); - - //- rjf: grab state - typedef struct DF_TargetsViewState DF_TargetsViewState; - struct DF_TargetsViewState - { - B32 selected_add; - DF_Handle selected_target_handle; - S64 selected_column; - }; - DF_TargetsViewState *tv = df_view_user_state(view, DF_TargetsViewState); - - //- rjf: determine table bounds - Vec2S64 table_bounds = {5, (S64)targets.count+1}; - - //- rjf: selection state => cursor - // NOTE(rjf): 0 => nothing, 1 => add new, 2 => first target - Vec2S64 cursor = {0}; - { - DF_Entity *selected_target = df_entity_from_handle(tv->selected_target_handle); - for(U64 idx = 0; idx < targets.count; idx += 1) - { - if(selected_target == targets.v[idx].entity) - { - cursor.y = (S64)idx+2; - break; - } - } - if(tv->selected_add) - { - cursor.y = 1; - } - cursor.x = tv->selected_column; - } - - //- rjf: build - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = row_height_px; - scroll_list_params.dim_px = dim_2f32(rect); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(5, Max(0, (S64)targets.count+1))); - scroll_list_params.item_range = r1s64(0, Max(0, (S64)targets.count+1)); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - &cursor, - 0, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - { - // rjf: add new ctrl - if(visible_row_range.min == 0) - { - UI_Signal add_sig = {0}; - UI_FocusHot(cursor.y == 1 ? UI_FocusKind_On : UI_FocusKind_Off) - add_sig = df_icon_buttonf(ws, DF_IconKind_Add, 0, "Add New Target"); - if(ui_clicked(add_sig)) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.cmd_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_AddTarget); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); - } - } - - // rjf: target rows - for(S64 row_idx = Max(1, visible_row_range.min); - row_idx <= visible_row_range.max && row_idx <= targets.count; - row_idx += 1) - UI_Row - { - DF_Entity *target = targets.v[row_idx-1].entity; - B32 row_selected = ((U64)cursor.y == row_idx+1); - - // rjf: enabled - UI_PrefWidth(ui_em(2.25f, 1)) - UI_FocusHot((row_selected && cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off) - { - UI_Signal sig = df_icon_buttonf(ws, target->b32 ? DF_IconKind_CheckFilled : DF_IconKind_CheckHollow, 0, "###ebl_%p", target); - if(ui_clicked(sig)) - { - DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); - p.entity = df_handle_from_entity(target); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(target->b32 ? DF_CoreCmdKind_DisableTarget : DF_CoreCmdKind_EnableTarget)); - } - } - - // rjf: target name - UI_WidthFill UI_FocusHot((row_selected && cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) - { - df_entity_desc_button(ws, target, &targets.v[row_idx-1].matches, query, 0); - } - - // rjf: controls - UI_PrefWidth(ui_em(2.25f, 1.f)) - { - struct - { - DF_IconKind icon; - String8 text; - DF_CoreCmdKind cmd; - } - ctrls[] = - { - { DF_IconKind_PlayStepForward, str8_lit("Launch and Initialize"), DF_CoreCmdKind_LaunchAndInit }, - { DF_IconKind_Play, str8_lit("Launch and Run"), DF_CoreCmdKind_LaunchAndRun }, - { DF_IconKind_Pencil, str8_lit("Edit"), DF_CoreCmdKind_Target }, - { DF_IconKind_Trash, str8_lit("Delete"), DF_CoreCmdKind_RemoveTarget }, - }; - for(U64 ctrl_idx = 0; ctrl_idx < ArrayCount(ctrls); ctrl_idx += 1) - { - UI_Signal sig = {0}; - UI_FocusHot((row_selected && cursor.x == 2+ctrl_idx) ? UI_FocusKind_On : UI_FocusKind_Off) - { - sig = df_icon_buttonf(ws, ctrls[ctrl_idx].icon, 0, "###%p_ctrl_%i", target, (int)ctrl_idx); - } - if(ui_hovering(sig)) UI_Tooltip - { - ui_label(ctrls[ctrl_idx].text); - } - if(ui_clicked(sig)) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.entity = df_handle_from_entity(target); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); - df_handle_list_push(scratch.arena, ¶ms.entity_list, params.entity); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(ctrls[ctrl_idx].cmd)); - } - } - } - } - } - - //- rjf: commit cursor to selection state - { - tv->selected_column = cursor.x; - tv->selected_target_handle = (1 < cursor.y && cursor.y < targets.count+2) ? df_handle_from_entity(targets.v[cursor.y-2].entity) : df_handle_zero(); - tv->selected_add = (cursor.y == 1); - } - - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: FilePathMap @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(FilePathMap) -{ - DF_FilePathMapViewState *fpms = df_view_user_state(view, DF_FilePathMapViewState); - if(fpms->initialized == 0) - { - fpms->initialized = 1; - fpms->src_column_pct = 0.5f; - fpms->dst_column_pct = 0.5f; - } -} - -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(FilePathMap) -{ - DF_FilePathMapViewState *fpms = df_view_user_state(view, DF_FilePathMapViewState); - return str8_lit(""); -} - -DF_VIEW_CMD_FUNCTION_DEF(FilePathMap) -{ - DF_FilePathMapViewState *fpms = df_view_user_state(view, DF_FilePathMapViewState); - - // rjf: process commands - for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) - { - DF_Cmd *cmd = &n->cmd; - - // rjf: mismatched window/panel => skip - if(df_window_from_handle(cmd->params.window) != ws || - df_panel_from_handle(cmd->params.panel) != panel) - { - continue; - } - - //rjf: process - DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); - switch(core_cmd_kind) - { - default:break; - case DF_CoreCmdKind_PickFile: - case DF_CoreCmdKind_PickFolder: - case DF_CoreCmdKind_PickFileOrFolder: - { - String8 pick_string = cmd->params.file_path; - Side pick_side = fpms->pick_file_dst_side; - DF_Entity *storage_entity = df_entity_from_handle(fpms->pick_file_dst_map); - DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); - p.entity = df_handle_from_entity(storage_entity); - p.file_path = pick_string; - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_Entity); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_FilePath); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(pick_side == Side_Min ? - DF_CoreCmdKind_SetFileOverrideLinkSrc : - DF_CoreCmdKind_SetFileOverrideLinkDst)); - }break; - } - } -} - -DF_VIEW_UI_FUNCTION_DEF(FilePathMap) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DF_EntityList maps_list = df_query_cached_entity_list_with_kind(DF_EntityKind_OverrideFileLink); - DF_EntityArray maps = df_entity_array_from_list(scratch.arena, &maps_list); - F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); - - //- rjf: grab state - DF_FilePathMapViewState *fpms = df_view_user_state(view, DF_FilePathMapViewState); - - //- rjf: take controls to start/end editing - B32 edit_begin = 0; - B32 edit_end = 0; - B32 edit_commit = 0; - B32 edit_submit = 0; - UI_Focus(UI_FocusKind_On) if(ui_is_focus_active()) - { - if(!fpms->input_editing) - { - UI_EventList *events = ui_events(); - for(UI_EventNode *n = events->first; n != 0; n = n->next) - { - if(n->v.string.size != 0 || n->v.flags & UI_EventFlag_Paste) - { - edit_begin = 1; - break; - } - } - if(ui_slot_press(UI_EventActionSlot_Edit)) - { - edit_begin = 1; - } - } - if(fpms->input_editing) - { - if(ui_slot_press(UI_EventActionSlot_Cancel)) - { - edit_end = 1; - edit_commit = 0; - } - if(ui_slot_press(UI_EventActionSlot_Accept)) - { - edit_end = 1; - edit_commit = 1; - edit_submit = 1; - } - } - } - - //- rjf: build - DF_Handle commit_map = df_handle_zero(); - Side commit_side = Side_Invalid; - F32 *col_pcts[] = { &fpms->src_column_pct, &fpms->dst_column_pct }; - Vec2S64 next_cursor = fpms->cursor; - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = row_height_px; - scroll_list_params.dim_px = dim_2f32(rect); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(3, maps.count + 1)); - scroll_list_params.item_range = r1s64(0, maps.count+2); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - fpms->input_editing ? 0 : &fpms->cursor, - 0, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - UI_TableF(ArrayCount(col_pcts), col_pcts, "###tbl") - { - next_cursor = fpms->cursor; - - //- rjf: header - if(visible_row_range.min == 0) UI_TableVector UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - { - UI_TableCell if(df_help_label(str8_lit("Source Path"))) UI_Tooltip - { - ui_label_multiline(ui_top_font_size()*30, str8_lit("When the debugger attempts to open a file or folder at a Source Path specified in this table, it will redirect to the file or folder specified by the Destination Path.")); - } - UI_TableCell ui_label(str8_lit("Destination Path")); - } - - //- rjf: map rows - for(S64 row_idx = Max(1, visible_row_range.min); - row_idx <= visible_row_range.max && row_idx <= maps.count+1; - row_idx += 1) UI_TableVector - { - U64 map_idx = row_idx-1; - DF_Entity *map = (map_idx < maps.count ? maps.v[map_idx] : &df_g_nil_entity); - DF_Entity *map_link = df_entity_from_handle(map->entity_handle); - String8 map_src_path = df_full_path_from_entity(scratch.arena, map); - String8 map_dst_path = df_full_path_from_entity(scratch.arena, map_link); - B32 row_selected = (fpms->cursor.y == row_idx); - - //- rjf: src - UI_TableCell UI_WidthFill - { - //- rjf: editor - { - B32 value_selected = (row_selected && fpms->cursor.x == 0); - - // rjf: begin editing - if(value_selected && edit_begin) - { - fpms->input_editing = 1; - fpms->input_size = Min(sizeof(fpms->input_buffer), map_src_path.size); - MemoryCopy(fpms->input_buffer, map_src_path.str, fpms->input_size); - fpms->input_cursor = txt_pt(1, 1+fpms->input_size); - fpms->input_mark = txt_pt(1, 1); - } - - // rjf: build - UI_Signal sig = {0}; - UI_FocusHot(value_selected ? UI_FocusKind_On : UI_FocusKind_Off) - UI_FocusActive((value_selected && fpms->input_editing) ? UI_FocusKind_On : UI_FocusKind_Off) - { - sig = df_line_editf(ws, DF_LineEditFlag_NoBackground, 0, 0, &fpms->input_cursor, &fpms->input_mark, fpms->input_buffer, sizeof(fpms->input_buffer), &fpms->input_size, 0, map_src_path, "###src_editor_%p", map); - edit_commit = edit_commit || ui_committed(sig); - } - - // rjf: focus panel on press - if(ui_pressed(sig)) - { - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - - // rjf: begin editing on double-click - if(!fpms->input_editing && ui_double_clicked(sig)) - { - fpms->input_editing = 1; - fpms->input_size = Min(sizeof(fpms->input_buffer), map_src_path.size); - MemoryCopy(fpms->input_buffer, map_src_path.str, fpms->input_size); - fpms->input_cursor = txt_pt(1, 1+fpms->input_size); - fpms->input_mark = txt_pt(1, 1); - } - - // rjf: press on non-selected => commit edit, change selected cell - if(ui_pressed(sig) && !value_selected) - { - edit_end = 1; - edit_commit = fpms->input_editing; - next_cursor.x = 0; - next_cursor.y = map_idx+1; - } - - // rjf: store commit information - if(value_selected) - { - commit_side = Side_Min; - commit_map = df_handle_from_entity(map); - } - } - - //- rjf: browse button - UI_FocusHot((row_selected && fpms->cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) - UI_PrefWidth(ui_text_dim(10, 1)) - if(ui_clicked(ui_buttonf("Browse..."))) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.cmd_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_PickFileOrFolder); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); - fpms->pick_file_dst_map = df_handle_from_entity(map); - fpms->pick_file_dst_side = Side_Min; - } - } - - //- rjf: dst - UI_TableCell UI_WidthFill - { - //- rjf: editor - { - B32 value_selected = (row_selected && fpms->cursor.x == 2); - - // rjf: begin editing - if(value_selected && edit_begin) - { - fpms->input_editing = 1; - fpms->input_size = Min(sizeof(fpms->input_buffer), map_dst_path.size); - MemoryCopy(fpms->input_buffer, map_dst_path.str, fpms->input_size); - fpms->input_cursor = txt_pt(1, 1+fpms->input_size); - fpms->input_mark = txt_pt(1, 1); - } - - // rjf: build - UI_Signal sig = {0}; - UI_FocusHot(value_selected ? UI_FocusKind_On : UI_FocusKind_Off) - UI_FocusActive((value_selected && fpms->input_editing) ? UI_FocusKind_On : UI_FocusKind_Off) - { - sig = df_line_editf(ws, DF_LineEditFlag_NoBackground, 0, 0, &fpms->input_cursor, &fpms->input_mark, fpms->input_buffer, sizeof(fpms->input_buffer), &fpms->input_size, 0, map_dst_path, "###dst_editor_%p", map); - edit_commit = edit_commit || ui_committed(sig); - } - - // rjf: focus panel on press - if(ui_pressed(sig)) - { - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - - // rjf: begin editing on double-click - if(!fpms->input_editing && ui_double_clicked(sig)) - { - fpms->input_editing = 1; - fpms->input_size = Min(sizeof(fpms->input_buffer), map_dst_path.size); - MemoryCopy(fpms->input_buffer, map_dst_path.str, fpms->input_size); - fpms->input_cursor = txt_pt(1, 1+fpms->input_size); - fpms->input_mark = txt_pt(1, 1); - } - - // rjf: press on non-selected => commit edit, change selected cell - if(ui_pressed(sig) && !value_selected) - { - edit_end = 1; - edit_commit = fpms->input_editing; - next_cursor.x = 2; - next_cursor.y = map_idx+1; - } - - // rjf: store commit information - if(value_selected) - { - commit_side = Side_Max; - commit_map = df_handle_from_entity(map); - } - } - - //- rjf: browse button - { - UI_FocusHot((row_selected && fpms->cursor.x == 3) ? UI_FocusKind_On : UI_FocusKind_Off) - UI_PrefWidth(ui_text_dim(10, 1)) - if(ui_clicked(ui_buttonf("Browse..."))) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.cmd_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_PickFileOrFolder); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); - fpms->pick_file_dst_map = df_handle_from_entity(map); - fpms->pick_file_dst_side = Side_Max; - } - } - } - } - } - - //- rjf: apply commit - if(edit_commit && commit_side != Side_Invalid) - { - String8 new_string = str8(fpms->input_buffer, fpms->input_size); - DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); - p.entity = commit_map; - p.file_path = new_string; - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_Entity); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_FilePath); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(commit_side == Side_Min ? - DF_CoreCmdKind_SetFileOverrideLinkSrc : - DF_CoreCmdKind_SetFileOverrideLinkDst)); - } - - //- rjf: apply editing finish - if(edit_end) - { - fpms->input_editing = 0; - } - - //- rjf: move down one row if submitted - if(edit_submit) - { - next_cursor.y += 1; - } - - //- rjf: apply moves to selection - fpms->cursor = next_cursor; - - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: AutoViewRules @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(AutoViewRules) -{ - DF_AutoViewRulesViewState *avrs = df_view_user_state(view, DF_AutoViewRulesViewState); - if(avrs->initialized == 0) - { - avrs->initialized = 1; - avrs->src_column_pct = 0.5f; - avrs->dst_column_pct = 0.5f; - } -} - -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(AutoViewRules) -{ - DF_AutoViewRulesViewState *avrs = df_view_user_state(view, DF_AutoViewRulesViewState); - return str8_lit(""); -} - -DF_VIEW_CMD_FUNCTION_DEF(AutoViewRules) -{ - DF_AutoViewRulesViewState *avrs = df_view_user_state(view, DF_AutoViewRulesViewState); - - // rjf: process commands - for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) - { - DF_Cmd *cmd = &n->cmd; - - // rjf: mismatched window/panel => skip - if(df_window_from_handle(cmd->params.window) != ws || - df_panel_from_handle(cmd->params.panel) != panel) - { - continue; - } - } -} - -DF_VIEW_UI_FUNCTION_DEF(AutoViewRules) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DF_EntityList maps_list = df_query_cached_entity_list_with_kind(DF_EntityKind_AutoViewRule); - DF_EntityArray maps = df_entity_array_from_list(scratch.arena, &maps_list); - F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); - - //- rjf: grab state - DF_AutoViewRulesViewState *avrs = df_view_user_state(view, DF_AutoViewRulesViewState); - - //- rjf: take controls to start/end editing - B32 edit_begin = 0; - B32 edit_end = 0; - B32 edit_commit = 0; - B32 edit_submit = 0; - UI_Focus(UI_FocusKind_On) if(ui_is_focus_active()) - { - if(!avrs->input_editing) - { - UI_EventList *events = ui_events(); - for(UI_EventNode *n = events->first; n != 0; n = n->next) - { - if(n->v.string.size != 0 || n->v.flags & UI_EventFlag_Paste) - { - edit_begin = 1; - break; - } - } - if(ui_slot_press(UI_EventActionSlot_Edit)) - { - edit_begin = 1; - } - } - if(avrs->input_editing) - { - if(ui_slot_press(UI_EventActionSlot_Cancel)) - { - edit_end = 1; - edit_commit = 0; - } - if(ui_slot_press(UI_EventActionSlot_Accept)) - { - edit_end = 1; - edit_commit = 1; - edit_submit = 1; - } - } - } - - //- rjf: build - DF_Handle commit_map = df_handle_zero(); - Side commit_side = Side_Invalid; - F32 *col_pcts[] = { &avrs->src_column_pct, &avrs->dst_column_pct }; - Vec2S64 next_cursor = avrs->cursor; - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = row_height_px; - scroll_list_params.dim_px = dim_2f32(rect); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(1, maps.count + 1)); - scroll_list_params.item_range = r1s64(0, maps.count+2); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - avrs->input_editing ? 0 : &avrs->cursor, - 0, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - UI_TableF(ArrayCount(col_pcts), col_pcts, "###tbl") - { - next_cursor = avrs->cursor; - - //- rjf: header - if(visible_row_range.min == 0) UI_TableVector UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - { - UI_TableCell ui_label(str8_lit("Type")); - UI_TableCell ui_label(str8_lit("View Rule")); - } - - //- rjf: map rows - for(S64 row_idx = Max(1, visible_row_range.min); - row_idx <= visible_row_range.max && row_idx <= maps.count+1; - row_idx += 1) UI_TableVector - { - U64 map_idx = row_idx-1; - DF_Entity *map = (map_idx < maps.count ? maps.v[map_idx] : &df_g_nil_entity); - DF_Entity *source = df_entity_child_from_kind(map, DF_EntityKind_Source); - DF_Entity *dest = df_entity_child_from_kind(map, DF_EntityKind_Dest); - String8 type = source->name; - String8 view_rule = dest->name; - B32 row_selected = (avrs->cursor.y == row_idx); - - //- rjf: type - UI_TableCell UI_WidthFill - { - //- rjf: editor - { - B32 value_selected = (row_selected && avrs->cursor.x == 0); - - // rjf: begin editing - if(value_selected && edit_begin) - { - avrs->input_editing = 1; - avrs->input_size = Min(sizeof(avrs->input_buffer), type.size); - MemoryCopy(avrs->input_buffer, type.str, avrs->input_size); - avrs->input_cursor = txt_pt(1, 1+avrs->input_size); - avrs->input_mark = txt_pt(1, 1); - } - - // rjf: build - UI_Signal sig = {0}; - UI_FocusHot(value_selected ? UI_FocusKind_On : UI_FocusKind_Off) - UI_FocusActive((value_selected && avrs->input_editing) ? UI_FocusKind_On : UI_FocusKind_Off) - DF_Font(ws, DF_FontSlot_Code) - { - sig = df_line_editf(ws, DF_LineEditFlag_CodeContents|DF_LineEditFlag_NoBackground|DF_LineEditFlag_DisplayStringIsCode, 0, 0, &avrs->input_cursor, &avrs->input_mark, avrs->input_buffer, sizeof(avrs->input_buffer), &avrs->input_size, 0, type, "###src_editor_%p", map); - edit_commit = edit_commit || ui_committed(sig); - } - - // rjf: focus panel on press - if(ui_pressed(sig)) - { - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - - // rjf: begin editing on double-click - if(!avrs->input_editing && ui_double_clicked(sig)) - { - avrs->input_editing = 1; - avrs->input_size = Min(sizeof(avrs->input_buffer), type.size); - MemoryCopy(avrs->input_buffer, type.str, avrs->input_size); - avrs->input_cursor = txt_pt(1, 1+avrs->input_size); - avrs->input_mark = txt_pt(1, 1); - } - - // rjf: press on non-selected => commit edit, change selected cell - if(ui_pressed(sig) && !value_selected) - { - edit_end = 1; - edit_commit = avrs->input_editing; - next_cursor.x = 0; - next_cursor.y = map_idx+1; - } - - // rjf: store commit information - if(value_selected) - { - commit_side = Side_Min; - commit_map = df_handle_from_entity(map); - } - } - } - - //- rjf: dst - UI_TableCell UI_WidthFill - { - //- rjf: editor - { - B32 value_selected = (row_selected && avrs->cursor.x == 1); - - // rjf: begin editing - if(value_selected && edit_begin) - { - avrs->input_editing = 1; - avrs->input_size = Min(sizeof(avrs->input_buffer), view_rule.size); - MemoryCopy(avrs->input_buffer, view_rule.str, avrs->input_size); - avrs->input_cursor = txt_pt(1, 1+avrs->input_size); - avrs->input_mark = txt_pt(1, 1); - } - - // rjf: build - UI_Signal sig = {0}; - UI_FocusHot(value_selected ? UI_FocusKind_On : UI_FocusKind_Off) - UI_FocusActive((value_selected && avrs->input_editing) ? UI_FocusKind_On : UI_FocusKind_Off) - DF_Font(ws, DF_FontSlot_Code) - { - sig = df_line_editf(ws, DF_LineEditFlag_CodeContents|DF_LineEditFlag_NoBackground|DF_LineEditFlag_DisplayStringIsCode, 0, 0, &avrs->input_cursor, &avrs->input_mark, avrs->input_buffer, sizeof(avrs->input_buffer), &avrs->input_size, 0, view_rule, "###dst_editor_%p", map); - edit_commit = edit_commit || ui_committed(sig); - } - - // rjf: focus panel on press - if(ui_pressed(sig)) - { - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - - // rjf: begin editing on double-click - if(!avrs->input_editing && ui_double_clicked(sig)) - { - avrs->input_editing = 1; - avrs->input_size = Min(sizeof(avrs->input_buffer), view_rule.size); - MemoryCopy(avrs->input_buffer, view_rule.str, avrs->input_size); - avrs->input_cursor = txt_pt(1, 1+avrs->input_size); - avrs->input_mark = txt_pt(1, 1); - } - - // rjf: press on non-selected => commit edit, change selected cell - if(ui_pressed(sig) && !value_selected) - { - edit_end = 1; - edit_commit = avrs->input_editing; - next_cursor.x = 1; - next_cursor.y = map_idx+1; - } - - // rjf: store commit information - if(value_selected) - { - commit_side = Side_Max; - commit_map = df_handle_from_entity(map); - } - } - } - } - } - - //- rjf: apply commit - if(edit_commit && commit_side != Side_Invalid) - { - String8 new_string = str8(avrs->input_buffer, avrs->input_size); - DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); - p.entity = commit_map; - p.string = new_string; - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_Entity); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_FilePath); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(commit_side == Side_Min ? - DF_CoreCmdKind_SetAutoViewRuleType : - DF_CoreCmdKind_SetAutoViewRuleViewRule)); - } - - //- rjf: apply editing finish - if(edit_end) - { - avrs->input_editing = 0; - } - - //- rjf: move down one row if submitted - if(edit_submit) - { - next_cursor.y += 1; - } - - //- rjf: apply moves to selection - avrs->cursor = next_cursor; - - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Scheduler @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Scheduler) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Scheduler) {return str8_lit("");} -DF_VIEW_CMD_FUNCTION_DEF(Scheduler) {} -DF_VIEW_UI_FUNCTION_DEF(Scheduler) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - String8 query = str8(view->query_buffer, view->query_string_size); - DF_CtrlCtx ctrl_ctx = df_ctrl_ctx_from_view(ws, view); - - //- rjf: get state - typedef struct DF_SchedulerViewState DF_SchedulerViewState; - struct DF_SchedulerViewState - { - DF_Handle selected_entity; - S64 selected_column; - }; - DF_SchedulerViewState *sv = df_view_user_state(view, DF_SchedulerViewState); - - //- rjf: get entities - DF_EntityList machines = df_query_cached_entity_list_with_kind(DF_EntityKind_Machine); - DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); - DF_EntityList threads = df_query_cached_entity_list_with_kind(DF_EntityKind_Thread); - - //- rjf: produce list of items; no query -> all entities, in tree; query -> only show threads - DF_EntityFuzzyItemArray items = {0}; - ProfScope("query -> entities") - { - if(query.size == 0) - { - //- rjf: build flat array of entities, arranged into row order - DF_EntityArray entities = {0}; - { - entities.count = machines.count+processes.count+threads.count; - entities.v = push_array_no_zero(scratch.arena, DF_Entity *, entities.count); - U64 idx = 0; - for(DF_EntityNode *machine_n = machines.first; machine_n != 0; machine_n = machine_n->next) - { - DF_Entity *machine = machine_n->entity; - entities.v[idx] = machine; - idx += 1; - for(DF_EntityNode *process_n = processes.first; process_n != 0; process_n = process_n->next) - { - DF_Entity *process = process_n->entity; - if(df_entity_ancestor_from_kind(process, DF_EntityKind_Machine) != machine) - { - continue; - } - entities.v[idx] = process; - idx += 1; - for(DF_EntityNode *thread_n = threads.first; thread_n != 0; thread_n = thread_n->next) - { - DF_Entity *thread = thread_n->entity; - if(df_entity_ancestor_from_kind(thread, DF_EntityKind_Process) != process) - { - continue; - } - entities.v[idx] = thread; - idx += 1; - } - } - } - } - - //- rjf: entities -> fuzzy-filtered entities - items = df_entity_fuzzy_item_array_from_entity_array_needle(scratch.arena, &entities, query); - } - else - { - items = df_entity_fuzzy_item_array_from_entity_list_needle(scratch.arena, &threads, query); - } - } - - //- rjf: selected column/entity -> selected cursor - Vec2S64 cursor = {sv->selected_column}; - for(U64 idx = 0; idx < items.count; idx += 1) - { - if(items.v[idx].entity == df_entity_from_handle(sv->selected_entity)) - { - cursor.y = (S64)(idx+1); - break; - } - } - - //- rjf: build table - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*2.5f); - scroll_list_params.dim_px = dim_2f32(rect); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(4, items.count)); - scroll_list_params.item_range = r1s64(0, items.count); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - &cursor, - 0, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - UI_TableF(0, 0, "scheduler_table") - { - Vec2S64 next_cursor = cursor; - for(U64 idx = visible_row_range.min; - idx <= visible_row_range.max && idx < items.count; - idx += 1) - { - DF_Entity *entity = items.v[idx].entity; - B32 row_is_selected = (cursor.y == (S64)(idx+1)); - F32 depth = 0.f; - if(query.size == 0) switch(entity->kind) - { - default:{}break; - case DF_EntityKind_Machine:{depth = 0.f;}break; - case DF_EntityKind_Process:{depth = 1.f;}break; - case DF_EntityKind_Thread: {depth = 2.f;}break; - } - Rng1S64 desc_col_rng = r1s64(1, 1); - switch(entity->kind) - { - default:{}break; - case DF_EntityKind_Machine:{desc_col_rng = r1s64(1, 4);}break; - case DF_EntityKind_Process:{desc_col_rng = r1s64(1, 1);}break; - case DF_EntityKind_Thread: {desc_col_rng = r1s64(1, 1);}break; - } - UI_NamedTableVectorF("entity_row_%p", entity) - { - UI_TableCellSized(ui_em(1.5f*depth, 1.f)) {} - UI_TableCellSized(ui_em(2.25f, 1.f)) UI_FocusHot((row_is_selected && cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off) - { - B32 frozen = df_entity_is_frozen(entity); - UI_Palette *palette = ui_top_palette(); - if(frozen) - { - palette = df_palette_from_code(ws, DF_PaletteCode_NegativePopButton); - } - else - { - palette = df_palette_from_code(ws, DF_PaletteCode_PositivePopButton); - } - UI_Signal sig = {0}; - UI_Palette(palette) sig = df_icon_buttonf(ws, frozen ? DF_IconKind_Locked : DF_IconKind_Unlocked, 0, "###lock_%p", entity); - if(ui_clicked(sig)) - { - DF_CoreCmdKind cmd_kind = frozen ? DF_CoreCmdKind_ThawEntity : DF_CoreCmdKind_FreezeEntity; - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.entity = df_handle_from_entity(entity); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(cmd_kind)); - } - } - UI_TableCellSized(ui_pct(1, 0)) - UI_FocusHot((row_is_selected && desc_col_rng.min <= cursor.x && cursor.x <= desc_col_rng.max) ? UI_FocusKind_On : UI_FocusKind_Off) - { - df_entity_desc_button(ws, entity, &items.v[idx].matches, query, 0); - } - switch(entity->kind) - { - default:{}break; - case DF_EntityKind_Machine: - { - - }break; - case DF_EntityKind_Process: - { - UI_TableCellSized(ui_children_sum(1.f)) UI_FocusHot((row_is_selected && cursor.x == 2) ? UI_FocusKind_On : UI_FocusKind_Off) - { - UI_PrefWidth(ui_text_dim(10, 1)) - UI_TextAlignment(UI_TextAlign_Center) - if(ui_clicked(ui_buttonf("Detach"))) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.entity = df_handle_from_entity(entity); - df_handle_list_push(scratch.arena, ¶ms.entity_list, df_handle_from_entity(entity)); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_EntityList); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Detach)); - } - } - UI_TableCellSized(ui_em(2.25f, 1.f)) UI_FocusHot((row_is_selected && cursor.x == 3) ? UI_FocusKind_On : UI_FocusKind_Off) - { - if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_Redo, 0, "###retry"))) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - df_handle_list_push(scratch.arena, ¶ms.entity_list, df_handle_from_entity(entity)); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_EntityList); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Restart)); - } - } - UI_TableCellSized(ui_em(2.25f, 1.f)) UI_FocusHot((row_is_selected && cursor.x == 4) ? UI_FocusKind_On : UI_FocusKind_Off) - { - DF_Palette(ws, DF_PaletteCode_NegativePopButton) - if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_X, 0, "###kill"))) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - df_handle_list_push(scratch.arena, ¶ms.entity_list, df_handle_from_entity(entity)); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_EntityList); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Kill)); - } - } - }break; - case DF_EntityKind_Thread: - { - UI_TableCellSized(ui_children_sum(1.f)) UI_FocusHot((row_is_selected && cursor.x >= 2) ? UI_FocusKind_On : UI_FocusKind_Off) - { - DF_Entity *process = df_entity_ancestor_from_kind(entity, DF_EntityKind_Process); - U64 rip_vaddr = df_query_cached_rip_from_thread(entity); - DF_Entity *module = df_module_from_process_vaddr(process, rip_vaddr); - U64 rip_voff = df_voff_from_vaddr(module, rip_vaddr); - DI_Key dbgi_key = df_dbgi_key_from_module(module); - DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, rip_voff); - if(lines.first != 0) - { - DF_Entity *file = df_entity_from_handle(lines.first->v.file); - if(!df_entity_is_nil(file)) - { - UI_PrefWidth(ui_children_sum(0)) df_entity_src_loc_button(ws, file, lines.first->v.pt); - } - } - } - }break; - } - } - } - cursor = next_cursor; - } - - //- rjf: selected num -> selected entity - sv->selected_column = cursor.x; - sv->selected_entity = (1 <= cursor.y && cursor.y <= items.count) ? df_handle_from_entity(items.v[cursor.y-1].entity) : df_handle_zero(); - - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: CallStack @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(CallStack) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(CallStack) { return str8_lit(""); } -DF_VIEW_CMD_FUNCTION_DEF(CallStack) {} -DF_VIEW_UI_FUNCTION_DEF(CallStack) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DI_Scope *scope = di_scope_open(); - DF_CtrlCtx ctrl_ctx = df_ctrl_ctx_from_view(ws, view); - DF_Entity *thread = df_entity_from_handle(ctrl_ctx.thread); - Architecture arch = df_architecture_from_entity(thread); - DF_Entity *process = thread->parent; - Vec4F32 thread_color = ui_top_palette()->text; - if(thread->flags & DF_EntityFlag_HasColor) - { - thread_color = df_rgba_from_entity(thread); - } - CTRL_Unwind base_unwind = df_query_cached_unwind_from_thread(thread); - DF_Unwind rich_unwind = df_unwind_from_ctrl_unwind(scratch.arena, scope, process, &base_unwind); - - //- rjf: grab state - typedef struct DF_CallStackViewState DF_CallStackViewState; - struct DF_CallStackViewState - { - B32 initialized; - Vec2S64 cursor; - Vec2S64 mark; - F32 selection_col_pct; - F32 module_col_pct; - F32 function_name_col_pct; - F32 addr_col_pct; - }; - DF_CallStackViewState *cs = df_view_user_state(view, DF_CallStackViewState); - if(cs->initialized == 0) - { - cs->initialized = 1; - cs->selection_col_pct = 0.05f; - cs->module_col_pct = 0.35f; - cs->function_name_col_pct = 0.4f; - cs->addr_col_pct = 0.2f; - } - - //- rjf: build ui - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*2.5f); - scroll_list_params.dim_px = dim_2f32(rect); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(3, rich_unwind.frames.count)); - scroll_list_params.item_range = r1s64(0, rich_unwind.frames.count+1); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - &cs->cursor, - &cs->mark, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - { - Vec2S64 next_cursor = cs->cursor; - - //- rjf: build table - if(df_ctrl_targets_running()) - { - ui_set_next_flags(UI_BoxFlag_Disabled); - } - F32 *col_pcts[] = - { - &cs->selection_col_pct, - &cs->function_name_col_pct, - &cs->addr_col_pct, - &cs->module_col_pct, - }; - UI_TableF(ArrayCount(col_pcts), col_pcts, "###tbl") - { - //- rjf: header - if(visible_row_range.min == 0) UI_TableVector UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - { - UI_TableCell {} - UI_TableCell ui_label(str8_lit("Function Name")); - UI_TableCell ui_label(str8_lit("Address")); - UI_TableCell ui_label(str8_lit("Module")); - } - - //- rjf: frame rows - for(S64 row_num = visible_row_range.min; - row_num <= visible_row_range.max && row_num <= rich_unwind.frames.count; - row_num += 1) - { - if(row_num == 0) - { - continue; - } - B32 row_selected = (cs->cursor.y == row_num); - - // rjf: unpack frame - U64 frame_idx = row_num-1; - DF_UnwindFrame *frame = &rich_unwind.frames.v[frame_idx]; - U64 rip_vaddr = regs_rip_from_arch_block(thread->arch, frame->regs); - DF_Entity *module = df_module_from_process_vaddr(process, rip_vaddr); - B32 frame_valid = (rip_vaddr != 0); - TG_Graph *graph = tg_graph_begin(bit_size_from_arch(thread->arch)/8, 256); - String8 symbol_name = {0}; - String8 symbol_type_string = {0}; - if(frame->procedure != 0) - { - symbol_name.str = rdi_name_from_procedure(frame->rdi, frame->procedure, &symbol_name.size); - RDI_TypeNode *type = rdi_element_from_name_idx(frame->rdi, TypeNodes, frame->procedure->type_idx); - symbol_type_string = tg_string_from_key(scratch.arena, graph, frame->rdi, tg_key_ext(tg_kind_from_rdi_type_kind(type->kind), frame->procedure->type_idx)); - } - if(frame->inline_site != 0) - { - symbol_name.str = rdi_string_from_idx(frame->rdi, frame->inline_site->name_string_idx, &symbol_name.size); - RDI_TypeNode *type = rdi_element_from_name_idx(frame->rdi, TypeNodes, frame->inline_site->type_idx); - symbol_type_string = tg_string_from_key(scratch.arena, graph, frame->rdi, tg_key_ext(tg_kind_from_rdi_type_kind(type->kind), frame->inline_site->type_idx)); - } - - // rjf: build row - if(frame_valid) UI_NamedTableVectorF("###callstack_%p_%I64x", view, frame_idx) - { - // rjf: build cell for selection - UI_TableCell - DF_Font(ws, DF_FontSlot_Icons) - UI_FontSize(df_font_size_from_slot(ws, DF_FontSlot_Icons)) - UI_WidthFill - UI_TextAlignment(UI_TextAlign_Center) - UI_FocusHot((row_selected && cs->cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off) - { - String8 selected_string = {0}; - if(ctrl_ctx.unwind_count == frame->base_unwind_idx && - ctrl_ctx.inline_unwind_count == frame->inline_unwind_idx) - { - selected_string = df_g_icon_kind_text_table[DF_IconKind_RightArrow]; - ui_set_next_palette(ui_build_palette(ui_top_palette(), .text = thread_color)); - } - UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|UI_BoxFlag_DrawText, "%S###selection_%i", selected_string, - (int)frame_idx); - UI_Signal sig = ui_signal_from_box(box); - if(ui_pressed(sig)) - { - next_cursor = v2s64(0, (S64)frame_idx+1); - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - if(ui_double_clicked(sig) || sig.f&UI_SignalFlag_KeyboardPressed) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_BaseUnwindIndex); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_InlineUnwindIndex); - params.base_unwind_index = frame->base_unwind_idx; - params.inline_unwind_index = frame->inline_unwind_idx; - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectUnwind)); - } - } - - // rjf: build cell for function header - UI_TableCell DF_Font(ws, DF_FontSlot_Code) - UI_FocusHot((row_selected && cs->cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) - { - ui_set_next_child_layout_axis(Axis2_X); - UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|UI_BoxFlag_Clip, "frame_%I64x", frame_idx); - UI_Parent(box) - { - if(frame->inline_site != 0) - { - UI_PrefWidth(ui_text_dim(10, 1)) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - { - ui_label(str8_lit("[inlined]")); - } - } - if(symbol_name.size == 0) - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(str8_lit("[unknown symbol]")); - } - else UI_WidthFill - { - D_FancyStringList symbol_name_fstrs = df_fancy_string_list_from_code_string(scratch.arena, 1.f, 0, df_rgba_from_theme_color(DF_ThemeColor_CodeSymbol), symbol_name); - D_FancyStringList symbol_type_fstrs = df_fancy_string_list_from_code_string(scratch.arena, 0.5f, 0, df_rgba_from_theme_color(DF_ThemeColor_CodeDefault), symbol_type_string); - D_FancyStringList fstrs = {0}; - d_fancy_string_list_concat_in_place(&fstrs, &symbol_name_fstrs); - D_FancyString sep = {ui_top_font(), str8_lit(": "), ui_top_palette()->colors[UI_ColorCode_TextWeak], ui_top_font_size()}; - d_fancy_string_list_push(scratch.arena, &fstrs, &sep); - d_fancy_string_list_concat_in_place(&fstrs, &symbol_type_fstrs); - UI_Box *label = ui_build_box_from_key(UI_BoxFlag_DrawText, ui_key_zero()); - ui_box_equip_display_fancy_strings(label, &fstrs); - } - } - UI_Signal sig = ui_signal_from_box(box); - if(ui_pressed(sig)) - { - next_cursor = v2s64(1, (S64)frame_idx+1); - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - if(ui_double_clicked(sig) || sig.f&UI_SignalFlag_KeyboardPressed) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_BaseUnwindIndex); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_InlineUnwindIndex); - params.base_unwind_index = frame->base_unwind_idx; - params.inline_unwind_index = frame->inline_unwind_idx; - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectUnwind)); - } - } - - // rjf: build cell for rip - UI_TableCell - UI_FocusHot((row_selected && cs->cursor.x == 2) ? UI_FocusKind_On : UI_FocusKind_Off) - { - UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_DrawText|UI_BoxFlag_Clickable, "0x%I64x", rip_vaddr); - UI_Signal sig = ui_signal_from_box(box); - if(ui_pressed(sig)) - { - next_cursor = v2s64(2, (S64)frame_idx+1); - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - if(ui_double_clicked(sig) || sig.f&UI_SignalFlag_KeyboardPressed) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_BaseUnwindIndex); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_InlineUnwindIndex); - params.base_unwind_index = frame->base_unwind_idx; - params.inline_unwind_index = frame->inline_unwind_idx; - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectUnwind)); - } - } - - // rjf: build cell for module - UI_TableCell UI_FocusHot((row_selected && cs->cursor.x == 3) ? UI_FocusKind_On : UI_FocusKind_Off) - { - UI_Signal sig = {0}; - if(df_entity_is_nil(module)) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - { - UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_DrawText|UI_BoxFlag_Clickable, "(No Module)###moduleless_frame_%I64x", frame_idx); - sig = ui_signal_from_box(box); - } - else - { - sig = df_entity_desc_button(ws, module, 0, str8_zero(), 1); - } - if(ui_pressed(sig)) - { - next_cursor = v2s64(3, (S64)frame_idx+1); - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - if(ui_double_clicked(sig) || sig.f&UI_SignalFlag_KeyboardPressed) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_BaseUnwindIndex); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_InlineUnwindIndex); - params.base_unwind_index = frame->base_unwind_idx; - params.inline_unwind_index = frame->inline_unwind_idx; - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectUnwind)); - } - } - } - - // rjf: end if hit invalid frame - if(frame_valid == 0) - { - break; - } - } - - // rjf: apply moves to selection - cs->cursor = next_cursor; - } - } - - di_scope_close(scope); - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Modules @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Modules) -{ - DF_ModulesViewState *mv = df_view_user_state(view, DF_ModulesViewState); - if(mv->initialized == 0) - { - mv->initialized = 1; - mv->idx_col_pct = 0.05f; - mv->desc_col_pct = 0.15f; - mv->range_col_pct = 0.30f; - mv->dbg_col_pct = 0.50f; - } -} - -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Modules) {return str8_lit("");} - -DF_VIEW_CMD_FUNCTION_DEF(Modules) -{ - DF_ModulesViewState *mv = df_view_user_state(view, DF_ModulesViewState); - for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) - { - DF_Cmd *cmd = &n->cmd; - - // rjf: mismatched window/panel => skip - if(df_window_from_handle(cmd->params.window) != ws || - df_panel_from_handle(cmd->params.panel) != panel) - { - continue; - } - - //rjf: process - DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); - switch(core_cmd_kind) - { - default:break; - case DF_CoreCmdKind_PickFile: - { - Temp scratch = scratch_begin(0, 0); - String8 pick_string = cmd->params.file_path; - DF_Entity *module = df_entity_from_handle(mv->pick_file_dst_entity); - if(module->kind == DF_EntityKind_Module) - { - String8 exe_path = module->name; - String8 dbg_path = pick_string; - // TODO(rjf) - } - scratch_end(scratch); - }break; - } - } -} - -DF_VIEW_UI_FUNCTION_DEF(Modules) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - DI_Scope *scope = di_scope_open(); - String8 query = str8(view->query_buffer, view->query_string_size); - - //- rjf: get state - DF_ModulesViewState *mv = df_view_user_state(view, DF_ModulesViewState); - F32 *col_pcts[] = {&mv->idx_col_pct, &mv->desc_col_pct, &mv->range_col_pct, &mv->dbg_col_pct}; - - //- rjf: get entities - DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); - DF_EntityList modules = df_query_cached_entity_list_with_kind(DF_EntityKind_Module); - - //- rjf: make filtered item array - DF_EntityFuzzyItemArray items = {0}; - if(query.size == 0) - { - DF_EntityArray entities = {0}; - { - entities.count = processes.count+modules.count; - entities.v = push_array_no_zero(scratch.arena, DF_Entity *, entities.count); - U64 idx = 0; - for(DF_EntityNode *process_n = processes.first; process_n != 0; process_n = process_n->next) - { - DF_Entity *process = process_n->entity; - entities.v[idx] = process; - idx += 1; - for(DF_EntityNode *module_n = modules.first; module_n != 0; module_n = module_n->next) - { - DF_Entity *module = module_n->entity; - if(df_entity_ancestor_from_kind(module, DF_EntityKind_Process) != process) - { - continue; - } - entities.v[idx] = module; - idx += 1; - } - } - } - items = df_entity_fuzzy_item_array_from_entity_array_needle(scratch.arena, &entities, query); - } - else - { - items = df_entity_fuzzy_item_array_from_entity_list_needle(scratch.arena, &modules, query); - } - - //- rjf: selected column/entity -> selected cursor - Vec2S64 cursor = {mv->selected_column}; - for(U64 idx = 0; idx < items.count; idx += 1) - { - if(items.v[idx].entity == df_entity_from_handle(mv->selected_entity)) - { - cursor.y = (S64)(idx+1); - break; - } - } - - ////////////////////////////// - //- rjf: do start/end editing interaction - // - B32 edit_begin = 0; - B32 edit_commit = 0; - B32 edit_end = 0; - B32 edit_submit = 0; - if(!mv->txt_editing && ui_is_focus_active()) - { - UI_EventList *events = ui_events(); - for(UI_EventNode *n = events->first; n != 0; n = n->next) - { - if(n->v.string.size != 0 || n->v.flags & UI_EventFlag_Paste) - { - edit_begin = 1; - break; - } - } - if(ui_slot_press(UI_EventActionSlot_Edit)) - { - edit_begin = 1; - } - } - if(mv->txt_editing && ui_is_focus_active()) - { - if(ui_slot_press(UI_EventActionSlot_Cancel)) - { - edit_end = 1; - edit_commit = 0; - } - if(ui_slot_press(UI_EventActionSlot_Accept)) - { - edit_end = 1; - edit_commit = 1; - edit_submit = 1; - } - } - - //- rjf: build table - DF_Entity *commit_module = &df_g_nil_entity; - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*2.5f); - scroll_list_params.dim_px = dim_2f32(rect); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(3, items.count)); - scroll_list_params.item_range = r1s64(0, items.count); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - mv->txt_editing ? 0 : &cursor, - 0, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - UI_TableF(ArrayCount(col_pcts), col_pcts, "modules_table") - { - Vec2S64 next_cursor = cursor; - U64 idx_in_process = 0; - for(U64 idx = 0; idx < items.count; idx += 1) - { - DF_Entity *entity = items.v[idx].entity; - B32 row_is_selected = (cursor.y == (S64)(idx+1)); - idx_in_process += (entity->kind == DF_EntityKind_Module); - if(visible_row_range.min <= idx && idx <= visible_row_range.max) - { - switch(entity->kind) - { - default:{}break; - case DF_EntityKind_Process: - { - UI_NamedTableVectorF("process_%p", entity) - { - UI_TableCellSized(ui_pct(1, 0)) UI_FocusHot((row_is_selected) ? UI_FocusKind_On : UI_FocusKind_Off) - { - df_entity_desc_button(ws, entity, &items.v[idx].matches, query, 0); - } - } - idx_in_process = 0; - }break; - case DF_EntityKind_Module: - UI_NamedTableVectorF("module_%p", entity) - { - UI_TableCell UI_TextAlignment(UI_TextAlign_Center) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - { - ui_labelf("%I64u", idx_in_process); - } - UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off) - { - df_entity_desc_button(ws, entity, &items.v[idx].matches, query, 1); - } - UI_TableCell DF_Font(ws, DF_FontSlot_Code) UI_FocusHot((row_is_selected && cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) - { - UI_Box *range_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|UI_BoxFlag_DrawText, "[0x%I64x, 0x%I64x)###vaddr_range_%p", entity->vaddr_rng.min, entity->vaddr_rng.max, entity); - UI_Signal sig = ui_signal_from_box(range_box); - if(ui_pressed(sig)) - { - next_cursor = v2s64(1, (S64)idx+1); - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - } - UI_TableCell - { - B32 txt_is_selected = (row_is_selected && cursor.x == 2); - B32 brw_is_selected = (row_is_selected && cursor.x == 3); - - // rjf: unpack module info - DI_Key dbgi_key = df_dbgi_key_from_module(entity); - String8 dbgi_path = dbgi_key.path; - RDI_Parsed *rdi = di_rdi_from_key(scope, &dbgi_key, 0); - B32 dbgi_is_valid = (rdi != &di_rdi_parsed_nil); - - // rjf: begin editing - if(txt_is_selected && edit_begin) - { - mv->txt_editing = 1; - mv->txt_size = Min(sizeof(mv->txt_buffer), dbgi_path.size); - MemoryCopy(mv->txt_buffer, dbgi_path.str, mv->txt_size); - mv->txt_cursor = txt_pt(1, 1+mv->txt_size); - mv->txt_mark = txt_pt(1, 1); - } - - // rjf: build - UI_Signal sig = {0}; - UI_FocusHot(txt_is_selected ? UI_FocusKind_On : UI_FocusKind_Off) - UI_FocusActive((txt_is_selected && mv->txt_editing) ? UI_FocusKind_On : UI_FocusKind_Off) - UI_WidthFill - { - UI_Palette(dbgi_is_valid ? ui_top_palette() : ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_TextNegative))) - sig = df_line_editf(ws, DF_LineEditFlag_NoBackground, 0, 0, &mv->txt_cursor, &mv->txt_mark, mv->txt_buffer, sizeof(mv->txt_buffer), &mv->txt_size, 0, dbgi_path, "###dbg_path_%p", entity); - edit_commit = (edit_commit || ui_committed(sig)); - } - - // rjf: press -> focus - if(ui_pressed(sig)) - { - edit_commit = (mv->txt_editing && !txt_is_selected); - next_cursor = v2s64(2, (S64)idx+1); - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - - // rjf: double-click -> begin editing - if(ui_double_clicked(sig) && !mv->txt_editing) - { - ui_kill_action(); - mv->txt_editing = 1; - mv->txt_size = Min(sizeof(mv->txt_buffer), dbgi_path.size); - MemoryCopy(mv->txt_buffer, dbgi_path.str, mv->txt_size); - mv->txt_cursor = txt_pt(1, 1+mv->txt_size); - mv->txt_mark = txt_pt(1, 1); - } - - // rjf: store commit info - if(txt_is_selected && edit_commit) - { - commit_module = entity; - } - - // rjf: build browse button - UI_FocusHot(brw_is_selected ? UI_FocusKind_On : UI_FocusKind_Off) UI_PrefWidth(ui_text_dim(10, 1)) - { - if(ui_clicked(ui_buttonf("Browse...")) || (brw_is_selected && edit_begin)) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.cmd_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_PickFile); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); - mv->pick_file_dst_entity = df_handle_from_entity(entity); - } - } - } - }break; - } - } - } - cursor = next_cursor; - } - - //- rjf: apply commits - if(edit_commit) - { - mv->txt_editing = 0; - if(!df_entity_is_nil(commit_module)) - { - String8 exe_path = commit_module->name; - String8 dbg_path = str8(mv->txt_buffer, mv->txt_size); - // TODO(rjf) - } - if(edit_submit) - { - cursor.y += 1; - } - } - - //- rjf: apply edit state changes - if(edit_end) - { - mv->txt_editing = 0; - } - - //- rjf: selected num -> selected entity - mv->selected_column = cursor.x; - mv->selected_entity = (1 <= cursor.y && cursor.y <= items.count) ? df_handle_from_entity(items.v[cursor.y-1].entity) : df_handle_zero(); - - di_scope_close(scope); - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: PendingEntity @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(PendingEntity) -{ - DF_PendingEntityViewState *pves = df_view_user_state(view, DF_PendingEntityViewState); - pves->deferred_cmd_arena = df_view_push_arena_ext(view); - pves->complete_cfg_arena = df_view_push_arena_ext(view); - pves->complete_cfg_root = df_cfg_tree_copy(pves->complete_cfg_arena, cfg_root); -} - -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(PendingEntity) -{ - return str8_lit(""); -} - -DF_VIEW_CMD_FUNCTION_DEF(PendingEntity) -{ - Temp scratch = scratch_begin(0, 0); - DF_PendingEntityViewState *pves = df_view_user_state(view, DF_PendingEntityViewState); - - //- rjf: process commands - for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) - { - DF_Cmd *cmd = &n->cmd; - - // rjf: mismatched window/panel => skip - if(df_window_from_handle(cmd->params.window) != ws || - df_panel_from_handle(cmd->params.panel) != panel) - { - continue; - } - - // rjf: process - DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); - switch(core_cmd_kind) - { - default:break; - - // rjf: gather deferred commands to redispatch when entity is ready - case DF_CoreCmdKind_GoToLine: - case DF_CoreCmdKind_GoToAddress: - case DF_CoreCmdKind_CenterCursor: - case DF_CoreCmdKind_ContainCursor: - { - df_cmd_list_push(pves->deferred_cmd_arena, &pves->deferred_cmds, &cmd->params, cmd->spec); - }break; - } - } - - //- rjf: determine if entity is ready, and which viewer to use - DF_Entity *entity = df_entity_from_handle(view->entity); - DF_GfxViewKind viewer_kind = DF_GfxViewKind_Null; - B32 entity_is_ready = 0; - switch(entity->kind) - { - default:{}break; - case DF_EntityKind_File: - { - entity_is_ready = 1; - viewer_kind = DF_GfxViewKind_Code; - }break; - } - - //- rjf: if entity is ready, dispatch all deferred commands - if(entity_is_ready) - { - for(DF_CmdNode *cmd_node = pves->deferred_cmds.first; cmd_node != 0; cmd_node = cmd_node->next) - { - DF_Cmd *cmd = &cmd_node->cmd; - df_push_cmd__root(&cmd->params, cmd->spec); - } - arena_clear(pves->deferred_cmd_arena); - MemoryZeroStruct(&pves->deferred_cmds); - } - - //- rjf: if entity is ready, move cfg tree to scratch for new command - DF_CfgNode *cfg_root = &df_g_nil_cfg_node; - if(entity_is_ready) - { - cfg_root = df_cfg_tree_copy(scratch.arena, pves->complete_cfg_root); - } - - //- rjf: if entity is ready, replace this view with the correct one, if any viewer is specified - if(entity_is_ready && viewer_kind != DF_GfxViewKind_Null) - { - DF_ViewSpec *view_spec = df_view_spec_from_string(cfg_root->string); - if(view_spec == &df_g_nil_view_spec) - { - view_spec = df_view_spec_from_gfx_view_kind(viewer_kind); - } - df_view_equip_spec(ws, view, view_spec, entity, str8_lit(""), cfg_root); - df_panel_notify_mutation(ws, panel); - } - - //- rjf: if entity is ready, but we have no viewer for it, then just close this tab - if(entity_is_ready && viewer_kind == DF_GfxViewKind_Null) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CloseTab)); - } - - scratch_end(scratch); -} - -DF_VIEW_UI_FUNCTION_DEF(PendingEntity) -{ - view->loading_t = view->loading_t_target = 1.f; - df_gfx_request_frame(); -} - -//////////////////////////////// -//~ rjf: Code @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Code) -{ - // rjf: set up state - DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState); - df_code_view_init(cv, view); - - // rjf: deserialize cursor - DF_CfgNode *cursor_cfg = df_cfg_node_child_from_string(cfg_root, str8_lit("cursor"), StringMatchFlag_CaseInsensitive); - if(cursor_cfg != &df_g_nil_cfg_node) - { - TxtPt cursor = txt_pt(1, 1); - cursor.line = s64_from_str8(cursor_cfg->first->string, 10); - cursor.column = s64_from_str8(cursor_cfg->first->first->string, 10); - if(cursor.line == 0) { cursor.line = 1; } - if(cursor.column == 0) { cursor.column = 1; } - cv->center_cursor = 1; - view->cursor = view->mark = cursor; - } - - // rjf: default to loading - df_view_equip_loading_info(view, 1, 0, 0); - view->loading_t = view->loading_t_target = 1.f; -} - -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Code) -{ - String8 string = push_str8f(arena, " cursor:%I64d:%I64d", view->cursor.line, view->cursor.column); - return string; -} - -DF_VIEW_CMD_FUNCTION_DEF(Code) -{ - DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState); - Temp scratch = scratch_begin(0, 0); - HS_Scope *hs_scope = hs_scope_open(); - TXT_Scope *txt_scope = txt_scope_open(); - DF_Entity *entity = df_entity_from_handle(df_interact_regs()->file); - String8 path = df_full_path_from_entity(scratch.arena, entity); - df_interact_regs()->text_key = fs_key_from_path(path); - df_interact_regs()->lang_kind = txt_lang_kind_from_extension(str8_skip_last_dot(path)); - U128 hash = {0}; - TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, df_interact_regs()->text_key, df_interact_regs()->lang_kind, &hash); - String8 data = hs_data_from_hash(hs_scope, hash); - - //- rjf: process general code-view commands - df_code_view_cmds(ws, panel, view, cv, cmds, data, &info, 0, r1u64(0, 0), di_key_zero()); - - //- rjf: process code-file commands - for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) - { - DF_Cmd *cmd = &n->cmd; - - // rjf: mismatched window/panel => skip - if(df_window_from_handle(cmd->params.window) != ws || - df_panel_from_handle(cmd->params.panel) != panel) - { - continue; - } - - // rjf: process - DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); - switch(core_cmd_kind) - { - default:{}break; - case DF_CoreCmdKind_PickFile: - { - DF_Entity *missing_file = df_entity_from_handle(cv->pick_file_override_target); - String8 pick_string = cmd->params.file_path; - if(!df_entity_is_nil(missing_file) && pick_string.size != 0) - { - DF_Entity *replacement = df_entity_from_path(pick_string, DF_EntityFromPathFlag_OpenAsNeeded|DF_EntityFromPathFlag_OpenMissing); - view->entity = df_handle_from_entity(replacement); - DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); - p.entity = df_handle_from_entity(missing_file); - p.file_path = pick_string; - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_Entity); - df_cmd_params_mark_slot(&p, DF_CmdParamSlot_FilePath); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SetFileReplacementPath)); - } - }break; - } - } - - txt_scope_close(txt_scope); - hs_scope_close(hs_scope); - scratch_end(scratch); -} - -DF_VIEW_UI_FUNCTION_DEF(Code) -{ - DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState); - Temp scratch = scratch_begin(0, 0); - HS_Scope *hs_scope = hs_scope_open(); - TXT_Scope *txt_scope = txt_scope_open(); - - ////////////////////////////// - //- rjf: set up invariants - // - F32 bottom_bar_height = ui_top_font_size()*2.f; - Rng2F32 code_area_rect = r2f32p(rect.x0, rect.y0, rect.x1, rect.y1 - bottom_bar_height); - Rng2F32 bottom_bar_rect = r2f32p(rect.x0, rect.y1 - bottom_bar_height, rect.x1, rect.y1); - - ////////////////////////////// - //- rjf: unpack entity info - // - DF_Entity *entity = df_entity_from_handle(df_interact_regs()->file); - String8 path = df_full_path_from_entity(scratch.arena, entity); - df_interact_regs()->text_key = fs_key_from_path(path); - df_interact_regs()->lang_kind = txt_lang_kind_from_extension(str8_skip_last_dot(path)); - U128 hash = {0}; - TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, df_interact_regs()->text_key, df_interact_regs()->lang_kind, &hash); - String8 data = hs_data_from_hash(hs_scope, hash); - B32 entity_is_missing = !!(entity->flags & DF_EntityFlag_IsMissing); - B32 key_has_data = !u128_match(hash, u128_zero()) && info.lines_count; - - ////////////////////////////// - //- rjf: build missing file interface - // - if(entity_is_missing && !key_has_data) - { - UI_WidthFill UI_HeightFill UI_Column UI_Padding(ui_pct(1, 0)) - { - Temp scratch = scratch_begin(0, 0); - String8 full_path = df_full_path_from_entity(scratch.arena, entity); - UI_PrefWidth(ui_children_sum(1)) UI_PrefHeight(ui_em(3, 1)) - UI_Row UI_Padding(ui_pct(1, 0)) - UI_PrefWidth(ui_text_dim(10, 1)) - UI_Palette(ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_TextNegative))) - { - DF_Font(ws, DF_FontSlot_Icons) ui_label(df_g_icon_kind_text_table[DF_IconKind_WarningBig]); - ui_labelf("Could not find \"%S\".", full_path); - } - UI_PrefHeight(ui_em(3, 1)) - UI_Row UI_Padding(ui_pct(1, 0)) - UI_PrefWidth(ui_text_dim(10, 1)) - UI_CornerRadius(ui_top_font_size()/3) - UI_PrefWidth(ui_text_dim(10, 1)) - UI_Focus(UI_FocusKind_On) - DF_Palette(ws, DF_PaletteCode_NeutralPopButton) - if(ui_clicked(ui_buttonf("Find alternative..."))) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.cmd_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_PickFile); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); - cv->pick_file_override_target = view->entity; - } - scratch_end(scratch); - } - } - - ////////////////////////////// - //- rjf: code is not missing, but not ready -> equip loading info to this view - // - if(!entity_is_missing && info.lines_count == 0) - { - df_view_equip_loading_info(view, 1, info.bytes_processed, info.bytes_to_process); - } - - ////////////////////////////// - //- rjf: build code contents - // - DI_KeyList dbgi_keys = {0}; - if(!entity_is_missing && key_has_data) - { - DF_CodeViewBuildResult result = df_code_view_build(scratch.arena, ws, panel, view, cv, DF_CodeViewBuildFlag_All, code_area_rect, data, &info, 0, r1u64(0, 0), di_key_zero()); - dbgi_keys = result.dbgi_keys; - } - - ////////////////////////////// - //- rjf: unpack cursor info - // - { - df_interact_regs()->lines = df_lines_from_file_line_num(df_frame_arena(), entity, df_interact_regs()->cursor.line); - } - - ////////////////////////////// - //- rjf: determine if file is out-of-date - // - B32 file_is_out_of_date = 0; - String8 out_of_date_dbgi_name = {0}; - { - for(DI_KeyNode *n = dbgi_keys.first; n != 0; n = n->next) - { - DI_Key key = n->v; - U64 file_timestamp = fs_timestamp_from_path(path); - if(key.min_timestamp < file_timestamp) - { - file_is_out_of_date = 1; - out_of_date_dbgi_name = str8_skip_last_slash(key.path); - break; - } - } - } - - ////////////////////////////// - //- rjf: build bottom bar - // - if(!entity_is_missing && key_has_data) - { - ui_set_next_rect(shift_2f32(bottom_bar_rect, scale_2f32(rect.p0, -1.f))); - ui_set_next_flags(UI_BoxFlag_DrawBackground); - UI_Palette *palette = ui_top_palette(); - if(file_is_out_of_date) - { - palette = df_palette_from_code(ws, DF_PaletteCode_NegativePopButton); - } - UI_Palette(palette) - UI_Row - UI_TextAlignment(UI_TextAlign_Center) - UI_PrefWidth(ui_text_dim(10, 1)) - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - { - if(file_is_out_of_date) - { - UI_Box *box = &ui_g_nil_box; - UI_Palette(ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_TextNegative))) - DF_Font(ws, DF_FontSlot_Icons) - { - box = ui_build_box_from_stringf(UI_BoxFlag_DrawText|UI_BoxFlag_Clickable, "%S###file_ood_warning", df_g_icon_kind_text_table[DF_IconKind_WarningBig]); - } - UI_Signal sig = ui_signal_from_box(box); - if(ui_hovering(sig)) UI_Tooltip - { - UI_PrefWidth(ui_children_sum(1)) UI_Row UI_PrefWidth(ui_text_dim(1, 1)) - { - ui_labelf("This file has changed since ", out_of_date_dbgi_name); - UI_Palette(ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_TextNeutral))) ui_label(out_of_date_dbgi_name); - ui_labelf(" was produced."); - } - } - } - DF_Font(ws, DF_FontSlot_Code) - { - ui_label(path); - ui_spacer(ui_em(1.5f, 1)); - ui_labelf("Line: %I64d, Column: %I64d", view->cursor.line, view->cursor.column); - ui_spacer(ui_pct(1, 0)); - ui_labelf("(read only)"); - ui_labelf("%s", - info.line_end_kind == TXT_LineEndKind_LF ? "lf" : - info.line_end_kind == TXT_LineEndKind_CRLF ? "crlf" : - "bin"); - } - } - } - - txt_scope_close(txt_scope); - hs_scope_close(hs_scope); - scratch_end(scratch); -} - -//////////////////////////////// -//~ rjf: Disassembly @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Disassembly) -{ - DF_DisasmViewState *dv = df_view_user_state(view, DF_DisasmViewState); - if(dv->initialized == 0) - { - dv->initialized = 1; - dv->style_flags = DASM_StyleFlag_Addresses|DASM_StyleFlag_SourceFilesNames|DASM_StyleFlag_SourceLines|DASM_StyleFlag_SymbolNames; - df_code_view_init(&dv->cv, view); - } -} - -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Disassembly) -{ - return str8_zero(); -} - -DF_VIEW_CMD_FUNCTION_DEF(Disassembly) -{ - DF_DisasmViewState *dv = df_view_user_state(view, DF_DisasmViewState); - Temp scratch = scratch_begin(0, 0); - DASM_Scope *dasm_scope = dasm_scope_open(); - HS_Scope *hs_scope = hs_scope_open(); - TXT_Scope *txt_scope = txt_scope_open(); - - //- rjf: unpack disasm info - DF_Entity *process = df_entity_from_handle(dv->process); - Architecture arch = df_architecture_from_entity(process); - U64 dasm_base_vaddr = AlignDownPow2(dv->base_vaddr, KB(16)); - DF_Entity *dasm_module = df_module_from_process_vaddr(process, dasm_base_vaddr); - DI_Key dasm_dbgi_key = df_dbgi_key_from_module(dasm_module); - Rng1U64 dasm_vaddr_range = r1u64(dasm_base_vaddr, dasm_base_vaddr+KB(16)); - U128 dasm_key = ctrl_hash_store_key_from_process_vaddr_range(process->ctrl_machine_id, process->ctrl_handle, dasm_vaddr_range, 0); - U128 dasm_data_hash = {0}; - DASM_Params dasm_params = {0}; - { - dasm_params.vaddr = dasm_vaddr_range.min; - dasm_params.arch = arch; - dasm_params.style_flags = dv->style_flags; - dasm_params.syntax = DASM_Syntax_Intel; - dasm_params.base_vaddr = dasm_module->vaddr_rng.min; - dasm_params.dbgi_key = dasm_dbgi_key; - } - DASM_Info dasm_info = dasm_info_from_key_params(dasm_scope, dasm_key, &dasm_params, &dasm_data_hash); - df_interact_regs()->text_key = dasm_info.text_key; - df_interact_regs()->lang_kind = txt_lang_kind_from_architecture(arch); - U128 dasm_text_hash = {0}; - TXT_TextInfo dasm_text_info = txt_text_info_from_key_lang(txt_scope, df_interact_regs()->text_key, df_interact_regs()->lang_kind, &dasm_text_hash); - String8 dasm_text_data = hs_data_from_hash(hs_scope, dasm_text_hash); - - //- rjf: process general code-view commands - df_code_view_cmds(ws, panel, view, &dv->cv, cmds, dasm_text_data, &dasm_text_info, &dasm_info.insts, dasm_vaddr_range, dasm_dbgi_key); - - //- rjf: process disassembly-specific commands - for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) - { - DF_Cmd *cmd = &n->cmd; - DF_CmdParams params = cmd->params; - - // rjf: mismatched window/panel => skip - if(df_window_from_handle(cmd->params.window) != ws || - df_panel_from_handle(cmd->params.panel) != panel) - { - continue; - } - - // rjf: process - DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); - switch(core_cmd_kind) - { - default: break; - case DF_CoreCmdKind_GoToAddress: - { - DF_Entity *process = &df_g_nil_entity; - { - DF_Entity *entity = df_entity_from_handle(params.entity); - if(!df_entity_is_nil(entity) && - (entity->kind == DF_EntityKind_Process || - entity->kind == DF_EntityKind_Thread || - entity->kind == DF_EntityKind_Module)) - { - process = entity; - if(entity->kind == DF_EntityKind_Thread || - entity->kind == DF_EntityKind_Module) - { - process = df_entity_ancestor_from_kind(process, DF_EntityKind_Process); - } - } - } - dv->process = df_handle_from_entity(process); - dv->base_vaddr = params.vaddr; - dv->goto_vaddr = params.vaddr; - }break; - case DF_CoreCmdKind_ToggleCodeBytesVisibility: {dv->style_flags ^= DASM_StyleFlag_CodeBytes;}break; - case DF_CoreCmdKind_ToggleAddressVisibility: {dv->style_flags ^= DASM_StyleFlag_Addresses;}break; - } - } - - txt_scope_close(txt_scope); - hs_scope_close(hs_scope); - dasm_scope_close(dasm_scope); - scratch_end(scratch); -} - -DF_VIEW_UI_FUNCTION_DEF(Disassembly) -{ - DF_DisasmViewState *dv = df_view_user_state(view, DF_DisasmViewState); - DF_CodeViewState *cv = &dv->cv; - Temp scratch = scratch_begin(0, 0); - HS_Scope *hs_scope = hs_scope_open(); - DASM_Scope *dasm_scope = dasm_scope_open(); - TXT_Scope *txt_scope = txt_scope_open(); - - ////////////////////////////// - //- rjf: set up invariants - // - F32 bottom_bar_height = ui_top_font_size()*2.f; - Rng2F32 code_area_rect = r2f32p(rect.x0, rect.y0, rect.x1, rect.y1 - bottom_bar_height); - Rng2F32 bottom_bar_rect = r2f32p(rect.x0, rect.y1 - bottom_bar_height, rect.x1, rect.y1); - - ////////////////////////////// - //- rjf: no disasm process open? -> snap to selected thread - // - if(df_entity_is_nil(df_entity_from_handle(dv->process))) - { - DF_Entity *thread = df_entity_from_handle(df_interact_regs()->thread); - U64 rip_vaddr = df_query_cached_rip_from_thread_unwind(thread, df_interact_regs()->unwind_count); - dv->process = df_handle_from_entity(df_entity_ancestor_from_kind(thread, DF_EntityKind_Process)); - dv->base_vaddr = rip_vaddr; - dv->goto_vaddr = rip_vaddr; - } - - ////////////////////////////// - //- rjf: unpack disassembly info - // - DF_Entity *process = df_entity_from_handle(dv->process); - Architecture arch = df_architecture_from_entity(process); - U64 dasm_base_vaddr = AlignDownPow2(dv->base_vaddr, KB(16)); - DF_Entity *dasm_module = df_module_from_process_vaddr(process, dasm_base_vaddr); - DI_Key dasm_dbgi_key = df_dbgi_key_from_module(dasm_module); - Rng1U64 dasm_vaddr_range = r1u64(dasm_base_vaddr, dasm_base_vaddr+KB(16)); - U128 dasm_key = ctrl_hash_store_key_from_process_vaddr_range(process->ctrl_machine_id, process->ctrl_handle, dasm_vaddr_range, 0); - U128 dasm_data_hash = {0}; - DASM_Params dasm_params = {0}; - { - dasm_params.vaddr = dasm_vaddr_range.min; - dasm_params.arch = arch; - dasm_params.style_flags = dv->style_flags; - dasm_params.syntax = DASM_Syntax_Intel; - dasm_params.base_vaddr = dasm_module->vaddr_rng.min; - dasm_params.dbgi_key = dasm_dbgi_key; - } - DASM_Info dasm_info = dasm_info_from_key_params(dasm_scope, dasm_key, &dasm_params, &dasm_data_hash); - df_interact_regs()->text_key = dasm_info.text_key; - df_interact_regs()->lang_kind = txt_lang_kind_from_architecture(arch); - U128 dasm_text_hash = {0}; - TXT_TextInfo dasm_text_info = txt_text_info_from_key_lang(txt_scope, df_interact_regs()->text_key, df_interact_regs()->lang_kind, &dasm_text_hash); - String8 dasm_text_data = hs_data_from_hash(hs_scope, dasm_text_hash); - B32 has_disasm = (dasm_info.insts.count != 0 && dasm_text_info.lines_count != 0); - B32 is_loading = (!has_disasm && !df_entity_is_nil(process) && dim_1u64(dasm_vaddr_range) != 0); - - ////////////////////////////// - //- rjf: is loading -> equip view with loading information - // - if(is_loading && !df_ctrl_targets_running()) - { - df_view_equip_loading_info(view, is_loading, 0, 0); - } - - ////////////////////////////// - //- rjf: do goto vaddr - // - if(!is_loading && has_disasm && dv->goto_vaddr != 0) - { - U64 vaddr = dv->goto_vaddr; - dv->goto_vaddr = 0; - U64 line_idx = dasm_inst_array_idx_from_code_off__linear_scan(&dasm_info.insts, vaddr-dasm_vaddr_range.min); - S64 line_num = (S64)(line_idx+1); - cv->goto_line_num = line_num; - } - - ////////////////////////////// - //- rjf: build code contents - // - if(!is_loading && has_disasm) - { - df_code_view_build(scratch.arena, ws, panel, view, cv, DF_CodeViewBuildFlag_All, code_area_rect, dasm_text_data, &dasm_text_info, &dasm_info.insts, dasm_vaddr_range, dasm_dbgi_key); - } - - ////////////////////////////// - //- rjf: unpack cursor info - // - if(!is_loading && has_disasm) - { - U64 off = dasm_inst_array_code_off_from_idx(&dasm_info.insts, df_interact_regs()->cursor.line-1); - df_interact_regs()->vaddr_range = r1u64(dasm_base_vaddr+off, dasm_base_vaddr+off); - df_interact_regs()->voff_range = df_voff_range_from_vaddr_range(dasm_module, df_interact_regs()->vaddr_range); - df_interact_regs()->lines = df_lines_from_dbgi_key_voff(df_frame_arena(), &dasm_dbgi_key, df_interact_regs()->voff_range.min); - } - - ////////////////////////////// - //- rjf: build bottom bar - // - if(!is_loading && has_disasm) - { - ui_set_next_rect(shift_2f32(bottom_bar_rect, scale_2f32(rect.p0, -1.f))); - ui_set_next_flags(UI_BoxFlag_DrawBackground); - UI_Row - UI_TextAlignment(UI_TextAlign_Center) - UI_PrefWidth(ui_text_dim(10, 1)) - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - DF_Font(ws, DF_FontSlot_Code) - { - DF_Entity *module = df_module_from_process_vaddr(process, dasm_vaddr_range.min); - U64 cursor_vaddr = (1 <= view->cursor.line && view->cursor.line <= dasm_info.insts.count) ? (dasm_vaddr_range.min+dasm_info.insts.v[view->cursor.line-1].code_off) : 0; - ui_labelf("%S", path_normalized_from_string(scratch.arena, module->name)); - ui_spacer(ui_em(1.5f, 1)); - ui_labelf("Address: 0x%I64x, Line: %I64d, Column: %I64d", cursor_vaddr, view->cursor.line, view->cursor.column); - ui_spacer(ui_pct(1, 0)); - ui_labelf("(read only)"); - ui_labelf("bin"); - } - } - - txt_scope_close(txt_scope); - dasm_scope_close(dasm_scope); - hs_scope_close(hs_scope); - scratch_end(scratch); -} - -//////////////////////////////// -//~ rjf: Watch @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Watch) -{ - ProfBeginFunction(); - DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); - df_watch_view_init(ewv, view, DF_WatchViewFillKind_Mutable); - - // rjf: add roots for watches - { - Temp scratch = scratch_begin(0, 0); - DF_EvalViewKey eval_view_key = df_eval_view_key_from_eval_watch_view(ewv); - DF_EvalView *eval_view = df_eval_view_from_key(eval_view_key); - for(DF_CfgNode *expr = cfg_root->first; expr != &df_g_nil_cfg_node; expr = expr->next) - { - if(expr->flags & DF_CfgNodeFlag_StringLiteral) - { - DF_EvalRoot *root = df_eval_root_alloc(view, ewv); - DF_ExpandKey key = df_expand_key_from_eval_root(root); - String8 expr_raw = df_cfg_raw_from_escaped_string(scratch.arena, expr->string); - df_eval_root_equip_string(root, expr_raw); - if(expr->first != &df_g_nil_cfg_node) - { - String8 view_rule_raw = df_cfg_raw_from_escaped_string(scratch.arena, expr->first->string); - df_eval_view_set_key_rule(eval_view, key, view_rule_raw); - } - } - } - scratch_end(scratch); - } - - ProfEnd(); -} - -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Watch) -{ - Temp scratch = scratch_begin(&arena, 1); - String8List strs = {0}; - DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); - DF_EvalViewKey eval_view_key = df_eval_view_key_from_eval_watch_view(ewv); - DF_EvalView *eval_view = df_eval_view_from_key(eval_view_key); - { - for(DF_EvalRoot *root = ewv->first_root; root != 0; root = root->next) - { - DF_ExpandKey key = df_expand_key_from_eval_root(root); - String8 string = df_string_from_eval_root(root); - String8 string_escaped = df_cfg_escaped_from_raw_string(scratch.arena, string); - str8_list_pushf(arena, &strs, "\"%S\"", string_escaped); - String8 view_rule = df_eval_view_rule_from_key(eval_view, key); - String8 view_rule_escaped = df_cfg_escaped_from_raw_string(scratch.arena, view_rule); - if(view_rule_escaped.size != 0) - { - str8_list_pushf(arena, &strs, ":{\"%S\"}", view_rule_escaped); - } - if(root->next != 0) - { - str8_list_pushf(arena, &strs, " "); - } - } - } - String8 string = str8_list_join(arena, &strs, 0); - scratch_end(scratch); - return string; -} - -DF_VIEW_CMD_FUNCTION_DEF(Watch) -{ - ProfBeginFunction(); - DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); - df_watch_view_cmds(ws, panel, view, ewv, cmds); - ProfEnd(); -} - -DF_VIEW_UI_FUNCTION_DEF(Watch) -{ - ProfBeginFunction(); - DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); - df_watch_view_build(ws, panel, view, ewv, 1*(view->query_string_size == 0), 10, rect); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Locals @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Locals) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Locals) { return str8_lit(""); } -DF_VIEW_CMD_FUNCTION_DEF(Locals) {} -DF_VIEW_UI_FUNCTION_DEF(Locals) -{ - ProfBeginFunction(); - DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); - df_watch_view_init(ewv, view, DF_WatchViewFillKind_Locals); - df_watch_view_build(ws, panel, view, ewv, 0, 10, rect); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Registers @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Registers) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Registers) { return str8_lit(""); } -DF_VIEW_CMD_FUNCTION_DEF(Registers) {} -DF_VIEW_UI_FUNCTION_DEF(Registers) -{ - ProfBeginFunction(); - DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); - df_watch_view_init(ewv, view, DF_WatchViewFillKind_Registers); - df_watch_view_build(ws, panel, view, ewv, 0, 16, rect); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Globals @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Globals) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Globals) { return str8_lit(""); } -DF_VIEW_CMD_FUNCTION_DEF(Globals) {} -DF_VIEW_UI_FUNCTION_DEF(Globals) -{ - ProfBeginFunction(); - DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); - df_watch_view_init(ewv, view, DF_WatchViewFillKind_Globals); - df_watch_view_build(ws, panel, view, ewv, 0, 10, rect); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: ThreadLocals @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(ThreadLocals) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(ThreadLocals) { return str8_lit(""); } -DF_VIEW_CMD_FUNCTION_DEF(ThreadLocals) {} -DF_VIEW_UI_FUNCTION_DEF(ThreadLocals) -{ - ProfBeginFunction(); - DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); - df_watch_view_init(ewv, view, DF_WatchViewFillKind_ThreadLocals); - df_watch_view_build(ws, panel, view, ewv, 0, 10, rect); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Types @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Types) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Types) { return str8_lit(""); } -DF_VIEW_CMD_FUNCTION_DEF(Types) {} -DF_VIEW_UI_FUNCTION_DEF(Types) -{ - ProfBeginFunction(); - DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); - df_watch_view_init(ewv, view, DF_WatchViewFillKind_Types); - df_watch_view_build(ws, panel, view, ewv, 0, 10, rect); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Procedures @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Procedures) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Procedures) { return str8_lit(""); } -DF_VIEW_CMD_FUNCTION_DEF(Procedures) {} -DF_VIEW_UI_FUNCTION_DEF(Procedures) -{ - ProfBeginFunction(); - DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); - df_watch_view_init(ewv, view, DF_WatchViewFillKind_Procedures); - df_watch_view_build(ws, panel, view, ewv, 0, 10, rect); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Output @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Output) -{ - DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState); - df_code_view_init(cv, view); -} - -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Output) -{ - return str8_zero(); -} - -DF_VIEW_CMD_FUNCTION_DEF(Output) -{ - DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState); - Temp scratch = scratch_begin(0, 0); - HS_Scope *hs_scope = hs_scope_open(); - TXT_Scope *txt_scope = txt_scope_open(); - df_interact_regs()->text_key = df_state->output_log_key; - df_interact_regs()->lang_kind = TXT_LangKind_Null; - U128 hash = {0}; - TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, df_interact_regs()->text_key, df_interact_regs()->lang_kind, &hash); - String8 data = hs_data_from_hash(hs_scope, hash); - df_code_view_cmds(ws, panel, view, cv, cmds, data, &info, 0, r1u64(0, 0), di_key_zero()); - txt_scope_close(txt_scope); - hs_scope_close(hs_scope); - scratch_end(scratch); -} - -DF_VIEW_UI_FUNCTION_DEF(Output) -{ - DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState); - Temp scratch = scratch_begin(0, 0); - HS_Scope *hs_scope = hs_scope_open(); - TXT_Scope *txt_scope = txt_scope_open(); - - ////////////////////////////// - //- rjf: set up invariants - // - F32 bottom_bar_height = ui_top_font_size()*2.f; - Rng2F32 code_area_rect = r2f32p(rect.x0, rect.y0, rect.x1, rect.y1 - bottom_bar_height); - Rng2F32 bottom_bar_rect = r2f32p(rect.x0, rect.y1 - bottom_bar_height, rect.x1, rect.y1); - - ////////////////////////////// - //- rjf: unpack text info - // - U128 key = df_state->output_log_key; - TXT_LangKind lang_kind = TXT_LangKind_Null; - U128 hash = {0}; - TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, key, lang_kind, &hash); - String8 data = hs_data_from_hash(hs_scope, hash); - Rng1U64 empty_range = {0}; - if(info.lines_count == 0) - { - info.lines_count = 1; - info.lines_ranges = &empty_range; - } - - ////////////////////////////// - //- rjf: build code contents - // - { - df_code_view_build(scratch.arena, ws, panel, view, cv, 0, code_area_rect, data, &info, 0, r1u64(0, 0), di_key_zero()); - } - - ////////////////////////////// - //- rjf: build bottom bar - // - { - ui_set_next_rect(shift_2f32(bottom_bar_rect, scale_2f32(rect.p0, -1.f))); - ui_set_next_flags(UI_BoxFlag_DrawBackground); - UI_Row - UI_TextAlignment(UI_TextAlign_Center) - UI_PrefWidth(ui_text_dim(10, 1)) - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - DF_Font(ws, DF_FontSlot_Code) - { - ui_labelf("(Debug String Output)"); - ui_spacer(ui_em(1.5f, 1)); - ui_labelf("Line: %I64d, Column: %I64d", view->cursor.line, view->cursor.column); - ui_spacer(ui_pct(1, 0)); - ui_labelf("(read only)"); - } - } - - txt_scope_close(txt_scope); - hs_scope_close(hs_scope); - scratch_end(scratch); -} - -//////////////////////////////// -//~ rjf: Memory @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Memory) -{ - DF_MemoryViewState *mv = df_view_user_state(view, DF_MemoryViewState); - if(mv->initialized == 0) - { - mv->initialized = 1; - mv->num_columns = 16; - mv->bytes_per_cell = 1; - mv->last_viewed_memory_cache_arena = df_view_push_arena_ext(view); - } -} - -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Memory) -{ - return str8_lit(""); -} - -DF_VIEW_CMD_FUNCTION_DEF(Memory) -{ - DF_MemoryViewState *mv = df_view_user_state(view, DF_MemoryViewState); - for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) - { - DF_Cmd *cmd = &n->cmd; - DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); - DF_CmdParams *params = &cmd->params; - switch(core_cmd_kind) - { - default: break; - case DF_CoreCmdKind_CenterCursor: - if(df_view_from_handle(params->view) == view) - { - mv->center_cursor = 1; - }break; - case DF_CoreCmdKind_ContainCursor: - if(df_view_from_handle(params->view) == view) - { - mv->contain_cursor = 1; - }break; - case DF_CoreCmdKind_GoToAddress: - { - // NOTE(rjf): go-to-address occurs with disassembly snaps, and we don't - // generally want to respond to those in thise view, so just skip any - // go-to-address commands that haven't been *explicitly* parameterized - // with this view. - if(df_view_from_handle(params->view) == view) - { - mv->cursor = mv->mark = params->vaddr; - mv->center_cursor = 1; - } - }break; - case DF_CoreCmdKind_SetColumns: - if(df_view_from_handle(params->view) == view) - { - U64 num_columns = params->index; - mv->num_columns = Clamp(1, num_columns, 64); - if(mv->num_columns % mv->bytes_per_cell != 0) - { - mv->bytes_per_cell = 1; - } - mv->center_cursor = 1; - }break; - } - } -} - -DF_VIEW_UI_FUNCTION_DEF(Memory) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - HS_Scope *hs_scope = hs_scope_open(); - - ////////////////////////////// - //- rjf: unpack state - // - DF_MemoryViewState *mv = df_view_user_state(view, DF_MemoryViewState); - - ////////////////////////////// - //- rjf: unpack entity params - // - DF_CtrlCtx ctrl_ctx = df_ctrl_ctx_from_view(ws, view); - DF_Entity *thread = df_entity_from_handle(ctrl_ctx.thread); - DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); - - ////////////////////////////// - //- rjf: unpack visual params - // - F_Tag font = df_font_from_slot(DF_FontSlot_Code); - F32 font_size = df_font_size_from_slot(ws, DF_FontSlot_Code); - F32 big_glyph_advance = f_dim_from_tag_size_string(font, font_size, 0, 0, str8_lit("H")).x; - F32 row_height_px = floor_f32(font_size*2.f); - F32 cell_width_px = floor_f32(font_size*2.f * mv->bytes_per_cell); - F32 scroll_bar_dim = floor_f32(ui_top_font_size()*1.5f); - Vec2F32 panel_dim = dim_2f32(rect); - F32 footer_dim = font_size*10.f; - Rng2F32 header_rect = r2f32p(0, 0, panel_dim.x, row_height_px); - Rng2F32 footer_rect = r2f32p(0, panel_dim.y-footer_dim, panel_dim.x, panel_dim.y); - Rng2F32 content_rect = r2f32p(0, row_height_px, panel_dim.x-scroll_bar_dim, footer_rect.y0); - - ////////////////////////////// - //- rjf: determine legal scroll range - // - Rng1S64 scroll_idx_rng = r1s64(0, 0x7FFFFFFFFFFFull/mv->num_columns); - - ////////////////////////////// - //- rjf: determine info about visible range of rows - // - Rng1S64 viz_range_rows = {0}; - Rng1U64 viz_range_bytes = {0}; - S64 num_possible_visible_rows = 0; - { - num_possible_visible_rows = dim_2f32(content_rect).y/row_height_px; - viz_range_rows.min = view->scroll_pos.y.idx + (S64)view->scroll_pos.y.off - !!(view->scroll_pos.y.off<0); - viz_range_rows.max = view->scroll_pos.y.idx + (S64)view->scroll_pos.y.off + num_possible_visible_rows, - viz_range_rows.min = clamp_1s64(scroll_idx_rng, viz_range_rows.min); - viz_range_rows.max = clamp_1s64(scroll_idx_rng, viz_range_rows.max); - viz_range_bytes.min = viz_range_rows.min*mv->num_columns; - viz_range_bytes.max = (viz_range_rows.max+1)*mv->num_columns+1; - if(viz_range_bytes.min > viz_range_bytes.max) - { - Swap(U64, viz_range_bytes.min, viz_range_bytes.max); - } - } - - ////////////////////////////// - //- rjf: take keyboard controls - // - UI_Focus(UI_FocusKind_On) if(ui_is_focus_active()) - { - U64 next_cursor = mv->cursor; - U64 next_mark = mv->mark; - UI_EventList *events = ui_events(); - for(UI_EventNode *n = events->first, *next = 0; n != 0; n = next) - { - next = n->next; - UI_Event *evt = &n->v; - Vec2S64 cell_delta = {0}; - switch(evt->delta_unit) - { - default:{}break; - case UI_EventDeltaUnit_Char: - { - cell_delta.x = (S64)evt->delta_2s32.x; - cell_delta.y = (S64)evt->delta_2s32.y; - }break; - case UI_EventDeltaUnit_Word: - case UI_EventDeltaUnit_Page: - { - if(evt->delta_2s32.x < 0) - { - cell_delta.x = -(S64)(mv->cursor%mv->num_columns); - } - else if(evt->delta_2s32.x > 0) - { - cell_delta.x = (mv->num_columns-1) - (S64)(mv->cursor%mv->num_columns); - } - if(evt->delta_2s32.y < 0) - { - cell_delta.y = -4; - } - else if(evt->delta_2s32.y > 0) - { - cell_delta.y = +4; - } - }break; - } - B32 good_action = 0; - if(evt->delta_2s32.x != 0 || evt->delta_2s32.y != 0) - { - good_action = 1; - } - if(good_action && evt->flags & UI_EventFlag_ZeroDeltaOnSelect && mv->cursor != mv->mark) - { - MemoryZeroStruct(&cell_delta); - } - if(good_action) - { - cell_delta.x = ClampBot(cell_delta.x, (S64)-next_cursor); - cell_delta.y = ClampBot(cell_delta.y, (S64)-(next_cursor/mv->num_columns)); - next_cursor += cell_delta.x; - next_cursor += cell_delta.y*mv->num_columns; - next_cursor = ClampTop(0x7FFFFFFFFFFFull, next_cursor); - } - if(good_action && evt->flags & UI_EventFlag_PickSelectSide && mv->cursor != mv->mark) - { - if(evt->delta_2s32.x < 0 || evt->delta_2s32.y < 0) - { - next_cursor = Min(mv->cursor, mv->mark); - } - else - { - next_cursor = Max(mv->cursor, mv->mark); - } - } - if(good_action && !(evt->flags & UI_EventFlag_KeepMark)) - { - next_mark = next_cursor; - } - if(good_action) - { - mv->contain_cursor = 1; - ui_eat_event(events, n); - } - } - mv->cursor = next_cursor; - mv->mark = next_mark; - } - - ////////////////////////////// - //- rjf: clamp cursor - // - { - Rng1U64 cursor_valid_rng = r1u64(0, 0x7FFFFFFFFFFFull); - mv->cursor = clamp_1u64(cursor_valid_rng, mv->cursor); - mv->mark = clamp_1u64(cursor_valid_rng, mv->mark); - } - - ////////////////////////////// - //- rjf: center cursor - // - if(mv->center_cursor) - { - mv->center_cursor = 0; - S64 cursor_row_idx = mv->cursor/mv->num_columns; - S64 new_idx = (cursor_row_idx-num_possible_visible_rows/2+1); - new_idx = clamp_1s64(scroll_idx_rng, new_idx); - ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx); - } - - ////////////////////////////// - //- rjf: contain cursor - // - if(mv->contain_cursor) - { - mv->contain_cursor = 0; - S64 cursor_row_idx = mv->cursor/mv->num_columns; - Rng1S64 cursor_viz_range = r1s64(clamp_1s64(scroll_idx_rng, cursor_row_idx-2), clamp_1s64(scroll_idx_rng, cursor_row_idx+3)); - S64 min_delta = Min(0, cursor_viz_range.min-viz_range_rows.min); - S64 max_delta = Max(0, cursor_viz_range.max-viz_range_rows.max); - S64 new_idx = view->scroll_pos.y.idx+min_delta+max_delta; - new_idx = clamp_1s64(scroll_idx_rng, new_idx); - ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx); - } - - ////////////////////////////// - //- rjf: produce fancy string runs for all possible byte values in all cells - // - D_FancyStringList byte_fancy_strings[256] = {0}; - { - Vec4F32 full_color = df_rgba_from_theme_color(DF_ThemeColor_TextPositive); - Vec4F32 zero_color = df_rgba_from_theme_color(DF_ThemeColor_TextWeak); - for(U64 idx = 0; idx < ArrayCount(byte_fancy_strings); idx += 1) - { - U8 byte = (U8)idx; - F32 pct = (byte/255.f); - Vec4F32 text_color = mix_4f32(zero_color, full_color, pct); - if(byte == 0) - { - text_color.w *= 0.5f; - } - D_FancyString fstr = {font, push_str8f(scratch.arena, "%02x", byte), text_color, font_size, 0, 0}; - d_fancy_string_list_push(scratch.arena, &byte_fancy_strings[idx], &fstr); - } - } - - ////////////////////////////// - //- rjf: grab windowed memory - // - U64 visible_memory_size = dim_1u64(viz_range_bytes); - U8 *visible_memory = 0; - { - Rng1U64 chunk_aligned_range_bytes = r1u64(AlignDownPow2(viz_range_bytes.min, KB(4)), AlignPow2(viz_range_bytes.max, KB(4))); - U64 current_memgen_idx = ctrl_mem_gen(); - B32 range_changed = (chunk_aligned_range_bytes.min != mv->last_viewed_memory_cache_range.min || - chunk_aligned_range_bytes.max != mv->last_viewed_memory_cache_range.max); - B32 mem_changed = (current_memgen_idx != mv->last_viewed_memory_cache_memgen_idx); - if(range_changed || mem_changed) - { - Temp scratch = scratch_begin(0, 0); - - // rjf: try to read new memory for this range - U64 bytes_to_read = dim_1u64(chunk_aligned_range_bytes); - U8 *buffer = push_array_no_zero(scratch.arena, U8, bytes_to_read); - U64 half1_bytes_read = dmn_process_read(process->ctrl_handle, r1u64(chunk_aligned_range_bytes.min, chunk_aligned_range_bytes.min+bytes_to_read/2), buffer+0); - U64 half2_bytes_read = dmn_process_read(process->ctrl_handle, r1u64(chunk_aligned_range_bytes.min+bytes_to_read/2, chunk_aligned_range_bytes.max), buffer+bytes_to_read/2); - - // rjf: worked? -> clear cache & store - if(half1_bytes_read+half2_bytes_read >= bytes_to_read) - { - arena_clear(mv->last_viewed_memory_cache_arena); - mv->last_viewed_memory_cache_buffer = push_array_no_zero(mv->last_viewed_memory_cache_arena, U8, bytes_to_read); - MemoryCopy(mv->last_viewed_memory_cache_buffer, buffer, bytes_to_read); - } - - // rjf: didn't work, but range didn't change? -> no-op - if(half1_bytes_read == 0 && half2_bytes_read == 0 && !range_changed) - { - // NOTE(rjf): nothing - use stale memory from cache. - } - - // rjf: didn't work, but range DID change? -> clear cache - if(half1_bytes_read == 0 && half2_bytes_read == 0 && range_changed) - { - arena_clear(mv->last_viewed_memory_cache_arena); - mv->last_viewed_memory_cache_buffer = push_array(mv->last_viewed_memory_cache_arena, U8, bytes_to_read); - } - - // rjf: didn't fully work, but changed? -> clear cache memory, fill what we can, zero the rest. - if(half1_bytes_read+half2_bytes_read < bytes_to_read && half1_bytes_read+half2_bytes_read != 0) - { - arena_clear(mv->last_viewed_memory_cache_arena); - mv->last_viewed_memory_cache_buffer = push_array(mv->last_viewed_memory_cache_arena, U8, bytes_to_read); - MemoryCopy(mv->last_viewed_memory_cache_buffer+0, buffer+0, half1_bytes_read); - MemoryCopy(mv->last_viewed_memory_cache_buffer+bytes_to_read/2, buffer+bytes_to_read/2, half2_bytes_read); - } - - // rjf: update cache stamps - if(!df_ctrl_targets_running()) - { - mv->last_viewed_memory_cache_range = chunk_aligned_range_bytes; - mv->last_viewed_memory_cache_memgen_idx = current_memgen_idx; - } - - scratch_end(scratch); - } - visible_memory = mv->last_viewed_memory_cache_buffer + viz_range_bytes.min-chunk_aligned_range_bytes.min; - } - - ////////////////////////////// - //- rjf: grab annotations for windowed range of memory - // - typedef struct Annotation Annotation; - struct Annotation - { - Annotation *next; - String8 name_string; - String8 kind_string; - String8 type_string; - Vec4F32 color; - Rng1U64 vaddr_range; - }; - typedef struct AnnotationList AnnotationList; - struct AnnotationList - { - Annotation *first; - Annotation *last; - }; - AnnotationList *visible_memory_annotations = push_array(scratch.arena, AnnotationList, visible_memory_size); - { - CTRL_Unwind unwind = df_query_cached_unwind_from_thread(thread); - - //- rjf: fill unwind frame annotations - if(unwind.frames.count != 0) - { - U64 last_stack_top = regs_rsp_from_arch_block(thread->arch, unwind.frames.v[0].regs); - for(U64 idx = 1; idx < unwind.frames.count; idx += 1) - { - CTRL_UnwindFrame *f = &unwind.frames.v[idx]; - U64 f_stack_top = regs_rsp_from_arch_block(thread->arch, f->regs); - Rng1U64 frame_vaddr_range = r1u64(last_stack_top, f_stack_top); - Rng1U64 frame_vaddr_range_in_viz = intersect_1u64(frame_vaddr_range, viz_range_bytes); - last_stack_top = f_stack_top; - if(dim_1u64(frame_vaddr_range_in_viz) != 0) - { - U64 f_rip = regs_rip_from_arch_block(thread->arch, f->regs); - DF_Entity *module = df_module_from_process_vaddr(process, f_rip); - DI_Key dbgi_key = df_dbgi_key_from_module(module); - U64 rip_voff = df_voff_from_vaddr(module, f_rip); - String8 symbol_name = df_symbol_name_from_dbgi_key_voff(scratch.arena, &dbgi_key, rip_voff); - Annotation *annotation = push_array(scratch.arena, Annotation, 1); - annotation->name_string = symbol_name.size != 0 ? symbol_name : str8_lit("[external code]"); - annotation->kind_string = str8_lit("Call Stack Frame"); - annotation->color = symbol_name.size != 0 ? df_rgba_from_theme_color(DF_ThemeColor_CodeSymbol) : df_rgba_from_theme_color(DF_ThemeColor_TextWeak); - annotation->vaddr_range = frame_vaddr_range; - for(U64 vaddr = frame_vaddr_range_in_viz.min; vaddr < frame_vaddr_range_in_viz.max; vaddr += 1) - { - U64 visible_byte_idx = vaddr - viz_range_bytes.min; - SLLQueuePush(visible_memory_annotations[visible_byte_idx].first, visible_memory_annotations[visible_byte_idx].last, annotation); - } - } - } - } - - //- rjf: fill selected thread stack range annotation - if(unwind.frames.count > 0) - { - U64 stack_base_vaddr = thread->stack_base; - U64 stack_top_vaddr = regs_rsp_from_arch_block(thread->arch, unwind.frames.v[0].regs); - Rng1U64 stack_vaddr_range = r1u64(stack_base_vaddr, stack_top_vaddr); - Rng1U64 stack_vaddr_range_in_viz = intersect_1u64(stack_vaddr_range, viz_range_bytes); - if(dim_1u64(stack_vaddr_range_in_viz) != 0) - { - Annotation *annotation = push_array(scratch.arena, Annotation, 1); - annotation->name_string = df_display_string_from_entity(scratch.arena, thread); - annotation->kind_string = str8_lit("Stack"); - annotation->color = thread->flags & DF_EntityFlag_HasColor ? df_rgba_from_entity(thread) : df_rgba_from_theme_color(DF_ThemeColor_Text); - annotation->vaddr_range = stack_vaddr_range; - for(U64 vaddr = stack_vaddr_range_in_viz.min; vaddr < stack_vaddr_range_in_viz.max; vaddr += 1) - { - U64 visible_byte_idx = vaddr - viz_range_bytes.min; - SLLQueuePush(visible_memory_annotations[visible_byte_idx].first, visible_memory_annotations[visible_byte_idx].last, annotation); - } - } - } - - //- rjf: fill local variable annotations - { - Vec4F32 color_gen_table[] = - { - df_rgba_from_theme_color(DF_ThemeColor_Thread0), - df_rgba_from_theme_color(DF_ThemeColor_Thread1), - df_rgba_from_theme_color(DF_ThemeColor_Thread2), - df_rgba_from_theme_color(DF_ThemeColor_Thread3), - df_rgba_from_theme_color(DF_ThemeColor_Thread4), - df_rgba_from_theme_color(DF_ThemeColor_Thread5), - df_rgba_from_theme_color(DF_ThemeColor_Thread6), - df_rgba_from_theme_color(DF_ThemeColor_Thread7), - }; - DI_Scope *scope = di_scope_open(); - U64 thread_rip_vaddr = df_query_cached_rip_from_thread_unwind(thread, ctrl_ctx.unwind_count); - EVAL_ParseCtx parse_ctx = df_eval_parse_ctx_from_process_vaddr(scope, process, thread_rip_vaddr); - RDI_Parsed *rdi = parse_ctx.rdi; - for(EVAL_String2NumMapNode *n = parse_ctx.locals_map->first; n != 0; n = n->order_next) - { - String8 local_name = n->string; - DF_Eval local_eval = df_eval_from_string(scratch.arena, scope, &ctrl_ctx, &parse_ctx, &eval_string2expr_map_nil, local_name); - if(local_eval.mode == EVAL_EvalMode_Addr) - { - TG_Kind local_eval_type_kind = tg_kind_from_key(local_eval.type_key); - U64 local_eval_type_size = tg_byte_size_from_graph_rdi_key(parse_ctx.type_graph, rdi, local_eval.type_key); - Rng1U64 vaddr_rng = r1u64(local_eval.offset, local_eval.offset+local_eval_type_size); - Rng1U64 vaddr_rng_in_visible = intersect_1u64(viz_range_bytes, vaddr_rng); - if(vaddr_rng_in_visible.max != vaddr_rng_in_visible.min) - { - Annotation *annotation = push_array(scratch.arena, Annotation, 1); - { - annotation->name_string = push_str8_copy(scratch.arena, local_name); - annotation->kind_string = str8_lit("Local"); - annotation->type_string = tg_string_from_key(scratch.arena, parse_ctx.type_graph, parse_ctx.rdi, local_eval.type_key); - annotation->color = color_gen_table[(vaddr_rng.min/8)%ArrayCount(color_gen_table)]; - annotation->vaddr_range = vaddr_rng; - } - for(U64 vaddr = vaddr_rng_in_visible.min; vaddr < vaddr_rng_in_visible.max; vaddr += 1) - { - SLLQueuePushFront(visible_memory_annotations[vaddr-viz_range_bytes.min].first, visible_memory_annotations[vaddr-viz_range_bytes.min].last, annotation); - } - } - } - } - di_scope_close(scope); - } - } - - ////////////////////////////// - //- rjf: build main container - // - UI_Box *container_box = &ui_g_nil_box; - { - Vec2F32 dim = dim_2f32(rect); - ui_set_next_fixed_width(dim.x); - ui_set_next_fixed_height(dim.y); - ui_set_next_child_layout_axis(Axis2_Y); - container_box = ui_build_box_from_stringf(0, "memory_view_container_%p", view); - } - - ////////////////////////////// - //- rjf: build header - // - UI_Box *header_box = &ui_g_nil_box; - UI_Parent(container_box) - { - UI_WidthFill UI_PrefHeight(ui_px(row_height_px, 1.f)) UI_Row - header_box = ui_build_box_from_stringf(UI_BoxFlag_DrawSideBottom, "table_header"); - UI_Parent(header_box) - DF_Font(ws, DF_FontSlot_Code) - UI_FontSize(font_size) - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - { - UI_PrefWidth(ui_px(big_glyph_advance*18.f, 1.f)) ui_labelf("Address"); - UI_PrefWidth(ui_px(cell_width_px, 1.f)) - UI_TextAlignment(UI_TextAlign_Center) - { - Rng1U64 col_selection_rng = r1u64(mv->cursor%mv->num_columns, mv->mark%mv->num_columns); - for(U64 row_off = 0; row_off < mv->num_columns*mv->bytes_per_cell; row_off += mv->bytes_per_cell) - { - if(!(col_selection_rng.min <= row_off && row_off <= col_selection_rng.max)) - { - ui_set_next_flags(UI_BoxFlag_DrawTextWeak); - } - ui_labelf("%I64X", row_off); - } - } - ui_spacer(ui_px(big_glyph_advance*1.5f, 1.f)); - UI_WidthFill ui_labelf("ASCII"); - } - } - - ////////////////////////////// - //- rjf: build scroll bar - // - UI_Parent(container_box) - { - ui_set_next_fixed_x(content_rect.x1); - ui_set_next_fixed_y(content_rect.y0); - ui_set_next_fixed_width(scroll_bar_dim); - ui_set_next_fixed_height(dim_2f32(content_rect).y); - { - view->scroll_pos.y = ui_scroll_bar(Axis2_Y, - ui_px(scroll_bar_dim, 1.f), - view->scroll_pos.y, - scroll_idx_rng, - num_possible_visible_rows); - } - } - - ////////////////////////////// - //- rjf: build scrollable box - // - UI_Box *scrollable_box = &ui_g_nil_box; - UI_Parent(container_box) - { - ui_set_next_fixed_x(content_rect.x0); - ui_set_next_fixed_y(content_rect.y0); - ui_set_next_fixed_width(dim_2f32(content_rect).x); - ui_set_next_fixed_height(dim_2f32(content_rect).y); - ui_set_next_child_layout_axis(Axis2_Y); - scrollable_box = ui_build_box_from_stringf(UI_BoxFlag_Clip| - UI_BoxFlag_Scroll| - UI_BoxFlag_AllowOverflowX| - UI_BoxFlag_AllowOverflowY, - "scrollable_box"); - container_box->view_off.x = container_box->view_off_target.x = view->scroll_pos.x.idx + view->scroll_pos.x.off; - scrollable_box->view_off.y = scrollable_box->view_off_target.y = floor_f32(row_height_px*mod_f32(view->scroll_pos.y.off, 1.f) + row_height_px*(view->scroll_pos.y.off < 0)); - } - - ////////////////////////////// - //- rjf: build row container/overlay - // - UI_Box *row_container_box = &ui_g_nil_box; - UI_Box *row_overlay_box = &ui_g_nil_box; - UI_Parent(scrollable_box) UI_WidthFill UI_HeightFill - { - ui_set_next_child_layout_axis(Axis2_Y); - ui_set_next_hover_cursor(OS_Cursor_IBar); - row_container_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable, "row_container"); - UI_Parent(row_container_box) - { - row_overlay_box = ui_build_box_from_stringf(UI_BoxFlag_Floating, "row_overlay"); - } - } - - ////////////////////////////// - //- rjf: interact with row container - // - U64 mouse_hover_byte_num = 0; - { - UI_Signal sig = ui_signal_from_box(row_container_box); - - // rjf: calculate hovered byte - if(ui_hovering(sig) || ui_dragging(sig)) - { - Vec2F32 mouse_rel = sub_2f32(ui_mouse(), row_container_box->rect.p0); - U64 row_idx = ClampBot(0, mouse_rel.y) / row_height_px; - - // rjf: try from cells - if(mouse_hover_byte_num == 0) - { - U64 col_idx = ClampBot(mouse_rel.x-big_glyph_advance*18.f, 0)/cell_width_px; - if(col_idx < mv->num_columns) - { - mouse_hover_byte_num = viz_range_bytes.min + row_idx*mv->num_columns + col_idx + 1; - } - } - - // rjf: try from ascii - if(mouse_hover_byte_num == 0) - { - U64 col_idx = ClampBot(mouse_rel.x - (big_glyph_advance*18.f + cell_width_px*mv->num_columns + big_glyph_advance*1.5f), 0)/big_glyph_advance; - col_idx = ClampTop(col_idx, mv->num_columns-1); - mouse_hover_byte_num = viz_range_bytes.min + row_idx*mv->num_columns + col_idx + 1; - } - - mouse_hover_byte_num = Clamp(1, mouse_hover_byte_num, 0x7FFFFFFFFFFFull+1); - } - - // rjf: press -> focus panel - if(ui_pressed(sig)) - { - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - - // rjf: click & drag -> select - if(ui_dragging(sig) && mouse_hover_byte_num != 0) - { - if(!contains_2f32(sig.box->rect, ui_mouse())) - { - mv->contain_cursor = 1; - } - mv->cursor = mouse_hover_byte_num-1; - if(ui_pressed(sig)) - { - mv->mark = mv->cursor; - } - } - - // rjf: ctrl+scroll -> change font size - if(ui_hovering(sig)) - { - UI_EventList *events = ui_events(); - for(UI_EventNode *n = events->first, *next = 0; n != 0; n = next) - { - next = n->next; - UI_Event *event = &n->v; - if(event->kind == UI_EventKind_Scroll && event->modifiers & OS_EventFlag_Ctrl) - { - ui_eat_event(events, n); - if(event->delta_2f32.y < 0) - { - DF_CmdParams params = df_cmd_params_from_window(ws); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_IncCodeFontScale)); - } - else if(event->delta_2f32.y > 0) - { - DF_CmdParams params = df_cmd_params_from_window(ws); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_DecCodeFontScale)); - } - } - } - } - } - - ////////////////////////////// - //- rjf: build rows - // - UI_Parent(row_container_box) DF_Font(ws, DF_FontSlot_Code) UI_FontSize(font_size) - { - Rng1U64 selection = r1u64(mv->cursor, mv->mark); - U8 *row_ascii_buffer = push_array(scratch.arena, U8, mv->num_columns); - UI_WidthFill UI_PrefHeight(ui_px(row_height_px, 1.f)) - for(S64 row_idx = viz_range_rows.min; row_idx <= viz_range_rows.max; row_idx += 1) - { - Rng1U64 row_range_bytes = r1u64(row_idx*mv->num_columns, (row_idx+1)*mv->num_columns); - B32 row_is_boundary = 0; - Vec4F32 row_boundary_color = {0}; - if(row_range_bytes.min%64 == 0) - { - row_is_boundary = 1; - row_boundary_color = df_rgba_from_theme_color(DF_ThemeColor_BaseBorder); - } - UI_Box *row = ui_build_box_from_stringf(UI_BoxFlag_DrawSideTop*!!row_is_boundary, "row_%I64x", row_range_bytes.min); - UI_Parent(row) - { - UI_PrefWidth(ui_px(big_glyph_advance*18.f, 1.f)) - { - if(!(selection.max >= row_range_bytes.min && selection.min < row_range_bytes.max)) - { - ui_set_next_flags(UI_BoxFlag_DrawTextWeak); - } - ui_labelf("%016I64X", row_range_bytes.min); - } - UI_PrefWidth(ui_px(cell_width_px, 1.f)) - UI_TextAlignment(UI_TextAlign_Center) - UI_CornerRadius(0) - { - for(U64 col_idx = 0; col_idx < mv->num_columns; col_idx += 1) - { - U64 visible_byte_idx = (row_idx-viz_range_rows.min)*mv->num_columns + col_idx; - U64 global_byte_idx = viz_range_bytes.min+visible_byte_idx; - U64 global_byte_num = global_byte_idx+1; - U8 byte_value = visible_memory[visible_byte_idx]; - Annotation *annotation = visible_memory_annotations[visible_byte_idx].first; - UI_BoxFlags cell_flags = 0; - Vec4F32 cell_border_rgba = {0}; - Vec4F32 cell_bg_rgba = {0}; - if(global_byte_num == mouse_hover_byte_num) - { - cell_flags |= UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawSideTop|UI_BoxFlag_DrawSideBottom|UI_BoxFlag_DrawSideLeft|UI_BoxFlag_DrawSideRight; - cell_border_rgba = df_rgba_from_theme_color(DF_ThemeColor_Hover); - } - if(annotation != 0) - { - cell_flags |= UI_BoxFlag_DrawBackground; - cell_bg_rgba = annotation->color; - if(contains_1u64(annotation->vaddr_range, mouse_hover_byte_num-1)) - { - cell_bg_rgba.w *= 0.15f; - } - else - { - cell_bg_rgba.w *= 0.08f; - } - } - if(selection.min <= global_byte_idx && global_byte_idx <= selection.max) - { - cell_flags |= UI_BoxFlag_DrawBackground; - cell_bg_rgba = df_rgba_from_theme_color(DF_ThemeColor_SelectionOverlay); - } - ui_set_next_palette(ui_build_palette(ui_top_palette(), .background = cell_bg_rgba)); - UI_Box *cell_box = ui_build_box_from_key(UI_BoxFlag_DrawText|cell_flags, ui_key_zero()); - ui_box_equip_display_fancy_strings(cell_box, &byte_fancy_strings[byte_value]); - { - F32 off = 0; - for(Annotation *a = annotation; a != 0; a = a->next) - { - if(global_byte_idx == a->vaddr_range.min) UI_Parent(row_overlay_box) - { - ui_set_next_palette(ui_build_palette(ui_top_palette(), .background = annotation->color)); - ui_set_next_fixed_x(big_glyph_advance*18.f + col_idx*cell_width_px + -cell_width_px/8.f + off); - ui_set_next_fixed_y((row_idx-viz_range_rows.min)*row_height_px + -cell_width_px/8.f); - ui_set_next_fixed_width(cell_width_px/4.f); - ui_set_next_fixed_height(cell_width_px/4.f); - ui_set_next_corner_radius_00(cell_width_px/8.f); - ui_set_next_corner_radius_01(cell_width_px/8.f); - ui_set_next_corner_radius_10(cell_width_px/8.f); - ui_set_next_corner_radius_11(cell_width_px/8.f); - ui_build_box_from_key(UI_BoxFlag_Floating|UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawDropShadow, ui_key_zero()); - off += cell_width_px/8.f + cell_width_px/16.f; - } - } - } - if(annotation != 0 && mouse_hover_byte_num == global_byte_num) UI_Tooltip UI_FontSize(ui_top_font_size()) UI_PrefHeight(ui_px(ui_top_font_size()*1.75f, 1.f)) - { - for(Annotation *a = annotation; a != 0; a = a->next) - { - UI_PrefWidth(ui_children_sum(1)) UI_Row UI_PrefWidth(ui_text_dim(10, 1)) - { - DF_Font(ws, DF_FontSlot_Code) ui_label(a->name_string); - DF_Font(ws, DF_FontSlot_Main) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(a->kind_string); - } - if(a->type_string.size != 0) - { - df_code_label(1.f, 1, df_rgba_from_theme_color(DF_ThemeColor_CodeType), a->type_string); - } - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(str8_from_memory_size(scratch.arena, dim_1u64(a->vaddr_range))); - if(a->next != 0) - { - ui_spacer(ui_em(1.5f, 1.f)); - } - } - } - } - } - ui_spacer(ui_px(big_glyph_advance*1.5f, 1.f)); - UI_WidthFill - { - MemoryZero(row_ascii_buffer, mv->num_columns); - for(U64 col_idx = 0; col_idx < mv->num_columns; col_idx += 1) - { - U8 byte_value = visible_memory[(row_idx-viz_range_rows.min)*mv->num_columns + col_idx]; - row_ascii_buffer[col_idx] = byte_value; - if(byte_value <= 32 || 127 < byte_value) - { - row_ascii_buffer[col_idx] = '.'; - } - } - String8 ascii_text = str8(row_ascii_buffer, mv->num_columns); - UI_Box *ascii_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S###ascii_row_%I64x", ascii_text, row_range_bytes.min); - if(selection.max >= row_range_bytes.min && selection.min < row_range_bytes.max) - { - Rng1U64 selection_in_row = intersect_1u64(row_range_bytes, selection); - D_Bucket *bucket = d_bucket_make(); - D_BucketScope(bucket) - { - Vec2F32 text_pos = ui_box_text_position(ascii_box); - d_rect(r2f32p(text_pos.x + f_dim_from_tag_size_string(font, font_size, 0, 0, str8_prefix(ascii_text, selection_in_row.min+0-row_range_bytes.min)).x - font_size/8.f, - ascii_box->rect.y0, - text_pos.x + f_dim_from_tag_size_string(font, font_size, 0, 0, str8_prefix(ascii_text, selection_in_row.max+1-row_range_bytes.min)).x + font_size/4.f, - ascii_box->rect.y1), - df_rgba_from_theme_color(DF_ThemeColor_SelectionOverlay), - 0, 0, 1.f); - } - ui_box_equip_draw_bucket(ascii_box, bucket); - } - if(mouse_hover_byte_num != 0 && contains_1u64(row_range_bytes, mouse_hover_byte_num-1)) - { - D_Bucket *bucket = d_bucket_make(); - D_BucketScope(bucket) - { - Vec2F32 text_pos = ui_box_text_position(ascii_box); - Vec4F32 color = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlay); - d_rect(r2f32p(text_pos.x + f_dim_from_tag_size_string(font, font_size, 0, 0, str8_prefix(ascii_text, mouse_hover_byte_num-1-row_range_bytes.min)).x - font_size/8.f, - ascii_box->rect.y0, - text_pos.x + f_dim_from_tag_size_string(font, font_size, 0, 0, str8_prefix(ascii_text, mouse_hover_byte_num+0-row_range_bytes.min)).x + font_size/4.f, - ascii_box->rect.y1), - color, - 1.f, 3.f, 1.f); - } - ui_box_equip_draw_bucket(ascii_box, bucket); - } - } - } - } - } - - ////////////////////////////// - //- rjf: build footer - // - UI_Box *footer_box = &ui_g_nil_box; - UI_Parent(container_box) - { - ui_set_next_fixed_x(footer_rect.x0); - ui_set_next_fixed_y(footer_rect.y0); - ui_set_next_fixed_width(dim_2f32(footer_rect).x); - ui_set_next_fixed_height(dim_2f32(footer_rect).y); - footer_box = ui_build_box_from_stringf(UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawDropShadow, "footer"); - UI_Parent(footer_box) DF_Font(ws, DF_FontSlot_Code) UI_FontSize(font_size) - { - UI_PrefWidth(ui_em(7.5f, 1.f)) UI_HeightFill UI_Column UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - UI_PrefHeight(ui_px(row_height_px, 0.f)) - { - ui_labelf("Address:"); - ui_labelf("U8:"); - ui_labelf("U16:"); - ui_labelf("U32:"); - ui_labelf("U64:"); - } - UI_PrefWidth(ui_em(45.f, 1.f)) UI_HeightFill UI_Column - UI_PrefHeight(ui_px(row_height_px, 0.f)) - { - B32 cursor_in_range = (viz_range_bytes.min <= mv->cursor && mv->cursor+8 <= viz_range_bytes.max); - ui_labelf("%016I64X", mv->cursor); - if(cursor_in_range) - { - U64 as_u8 = 0; - U64 as_u16 = 0; - U64 as_u32 = 0; - U64 as_u64 = 0; - U64 cursor_off = mv->cursor-viz_range_bytes.min; - as_u8 = (U64)*(U8 *)(visible_memory + cursor_off); - as_u16 = (U64)*(U16*)(visible_memory + cursor_off); - as_u32 = (U64)*(U32*)(visible_memory + cursor_off); - as_u64 = (U64)*(U64*)(visible_memory + cursor_off); - ui_labelf("%02X (%I64u)", as_u8, as_u8); - ui_labelf("%04X (%I64u)", as_u16, as_u16); - ui_labelf("%08X (%I64u)", as_u32, as_u32); - ui_labelf("%016I64X (%I64u)", as_u64, as_u64); - } - } - } - } - - ////////////////////////////// - //- rjf: scroll - // - { - UI_Signal sig = ui_signal_from_box(scrollable_box); - if(sig.scroll.y != 0) - { - S64 new_idx = view->scroll_pos.y.idx + sig.scroll.y; - new_idx = clamp_1s64(scroll_idx_rng, new_idx); - ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx); - } - } - - hs_scope_close(hs_scope); - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Breakpoints @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Breakpoints) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Breakpoints) {return str8_lit("");} -DF_VIEW_CMD_FUNCTION_DEF(Breakpoints) {} -DF_VIEW_UI_FUNCTION_DEF(Breakpoints) -{ - Temp scratch = scratch_begin(0, 0); - String8 query = str8(view->query_buffer, view->query_string_size); - - //- rjf: get state - typedef struct DF_BreakpointsViewState DF_BreakpointsViewState; - struct DF_BreakpointsViewState - { - B32 initialized; - DF_Handle selected_entity; - S64 selected_column; - F32 enabled_col_pct; - F32 desc_col_pct; - F32 loc_col_pct; - F32 hit_col_pct; - F32 del_col_pct; - }; - DF_BreakpointsViewState *bv = df_view_user_state(view, DF_BreakpointsViewState); - if(bv->initialized == 0) - { - bv->initialized = 1; - bv->enabled_col_pct = 0.05f; - bv->desc_col_pct = 0.25f; - bv->loc_col_pct = 0.50f; - bv->hit_col_pct = 0.15f; - bv->del_col_pct = 0.05f; - } - F32 *col_pcts[] = {&bv->enabled_col_pct, &bv->desc_col_pct, &bv->loc_col_pct, &bv->hit_col_pct, &bv->del_col_pct}; - - //- rjf: get entities - DF_EntityList entities_list = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); - DF_EntityFuzzyItemArray entities = df_entity_fuzzy_item_array_from_entity_list_needle(scratch.arena, &entities_list, query); - - //- rjf: selected column/entity -> selected cursor - Vec2S64 cursor = {bv->selected_column}; - for(U64 idx = 0; idx < entities.count; idx += 1) - { - if(entities.v[idx].entity == df_entity_from_handle(bv->selected_entity)) - { - cursor.y = (S64)(idx+1); - break; - } - } - - //- rjf: build table - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*2.5f); - scroll_list_params.dim_px = dim_2f32(rect); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(4, entities.count)); - scroll_list_params.item_range = r1s64(0, entities.count+1); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - &cursor, - 0, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - UI_TableF(ArrayCount(col_pcts), col_pcts, "breakpoints_table") - { - if(visible_row_range.min == 0) UI_TableVector UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - { - UI_TableCell{} - UI_TableCell{ui_labelf("Name");} - UI_TableCell{ui_labelf("Location");} - UI_TableCell{ui_labelf("Hit Count");} - UI_TableCell{} - } - Vec2S64 next_cursor = cursor; - for(U64 idx = Max(1, visible_row_range.min); idx <= visible_row_range.max && idx <= entities.count; idx += 1) - { - DF_Entity *entity = entities.v[idx-1].entity; - B32 row_is_selected = (cursor.y == (S64)(idx)); - UI_NamedTableVectorF("breakpoint_%p", entity) - { - UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off) - { - if(ui_clicked(df_icon_buttonf(ws, entity->b32 ? DF_IconKind_CheckFilled : DF_IconKind_CheckHollow, 0, "###ebl_%p", entity))) - { - df_entity_equip_b32(entity, !entity->b32); - } - } - UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) - { - df_entity_desc_button(ws, entity, &entities.v[idx-1].matches, query, 1); - } - UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 2) ? UI_FocusKind_On : UI_FocusKind_Off) - { - B32 loc_is_code = 0; - String8 loc_string = {0}; - DF_Entity *file_parent = df_entity_ancestor_from_kind(entity, DF_EntityKind_File); - DF_Entity *symbol_name = df_entity_child_from_kind(entity, DF_EntityKind_EntryPointName); - if(!df_entity_is_nil(file_parent)) - { - loc_string = push_str8f(scratch.arena, "%S:%I64u:%I64u", file_parent->name, entity->text_point.line, entity->text_point.column); - } - else if(!df_entity_is_nil(symbol_name)) - { - loc_string = symbol_name->name; - loc_is_code = 1; - } - else if(entity->flags & DF_EntityFlag_HasVAddr) - { - loc_string = push_str8f(scratch.arena, "0x%016I64x", entity->vaddr); - loc_is_code = 1; - } - UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable, "loc_%p", entity); - UI_Parent(box) - { - DF_Font(ws, loc_is_code ? DF_FontSlot_Code : DF_FontSlot_Main) ui_label(loc_string); - } - UI_Signal sig = ui_signal_from_box(box); - if(ui_double_clicked(sig) || sig.f&UI_SignalFlag_KeyboardPressed) - { - DF_CmdParams params = df_cmd_params_from_window(ws); - params.file_path = df_full_path_from_entity(scratch.arena, file_parent); - params.text_point = entity->text_point; - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_TextPoint); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FindCodeLocation)); - } - if(ui_pressed(sig)) - { - next_cursor = v2s64(2, (S64)(idx)); - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - } - UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 3) ? UI_FocusKind_On : UI_FocusKind_Off) - { - UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable, "###cnd_%p", entity); - UI_Parent(box) - { - String8 hit_count_string = str8_from_u64(scratch.arena, entity->u64, 10, 0, 0); - DF_Font(ws, DF_FontSlot_Code) df_code_label(1.f, 1, df_rgba_from_theme_color(DF_ThemeColor_CodeDefault), hit_count_string); - } - UI_Signal sig = ui_signal_from_box(box); - if(ui_pressed(sig)) - { - next_cursor = v2s64(3, (S64)(idx)); - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - } - UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 4) ? UI_FocusKind_On : UI_FocusKind_Off) - { - if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_Trash, 0, "###del_%p", entity))) - { - df_entity_mark_for_deletion(entity); - } - } - } - } - cursor = next_cursor; - } - - //- rjf: selected num -> selected entity - bv->selected_column = cursor.x; - bv->selected_entity = (1 <= cursor.y && cursor.y <= entities.count) ? df_handle_from_entity(entities.v[cursor.y-1].entity) : df_handle_zero(); - - scratch_end(scratch); -} - -//////////////////////////////// -//~ rjf: WatchPins @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(WatchPins) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(WatchPins) {return str8_lit("");} -DF_VIEW_CMD_FUNCTION_DEF(WatchPins) {} -DF_VIEW_UI_FUNCTION_DEF(WatchPins) -{ - Temp scratch = scratch_begin(0, 0); - String8 query = str8(view->query_buffer, view->query_string_size); - - //- rjf: get state - typedef struct DF_WatchPinsViewState DF_WatchPinsViewState; - struct DF_WatchPinsViewState - { - B32 initialized; - DF_Handle selected_entity; - S64 selected_column; - F32 desc_col_pct; - F32 loc_col_pct; - F32 del_col_pct; - }; - DF_WatchPinsViewState *pv = df_view_user_state(view, DF_WatchPinsViewState); - if(pv->initialized == 0) - { - pv->initialized = 1; - pv->desc_col_pct = 0.35f; - pv->loc_col_pct = 0.60f; - pv->del_col_pct = 0.05f; - } - F32 *col_pcts[] = {&pv->desc_col_pct, &pv->loc_col_pct, &pv->del_col_pct}; - - //- rjf: get entities - DF_EntityList entities_list = df_query_cached_entity_list_with_kind(DF_EntityKind_WatchPin); - DF_EntityFuzzyItemArray entities = df_entity_fuzzy_item_array_from_entity_list_needle(scratch.arena, &entities_list, query); - - //- rjf: selected column/entity -> selected cursor - Vec2S64 cursor = {pv->selected_column}; - for(U64 idx = 0; idx < entities.count; idx += 1) - { - if(entities.v[idx].entity == df_entity_from_handle(pv->selected_entity)) - { - cursor.y = (S64)(idx+1); - break; - } - } - - //- rjf: build table - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*2.5f); - scroll_list_params.dim_px = dim_2f32(rect); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(2, entities.count)); - scroll_list_params.item_range = r1s64(0, entities.count+1); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - &cursor, - 0, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - UI_TableF(ArrayCount(col_pcts), col_pcts, "pins_table") - { - if(visible_row_range.min == 0) UI_TableVector UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) - { - UI_TableCell{ui_labelf("Name");} - UI_TableCell{ui_labelf("Location");} - UI_TableCell{} - } - Vec2S64 next_cursor = cursor; - for(U64 idx = Max(1, visible_row_range.min); idx <= visible_row_range.max && idx <= entities.count; idx += 1) - { - DF_Entity *entity = entities.v[idx-1].entity; - B32 row_is_selected = (cursor.y == (S64)(idx)); - UI_NamedTableVectorF("pin_%p", entity) - { - UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off) - { - df_entity_desc_button(ws, entity, &entities.v[idx-1].matches, query, 1); - } - UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) - { - String8 loc_string = {0}; - DF_Entity *file_parent = df_entity_ancestor_from_kind(entity, DF_EntityKind_File); - if(!df_entity_is_nil(file_parent)) - { - loc_string = push_str8f(scratch.arena, "%S:%I64u:%I64u", file_parent->name, entity->text_point.line, entity->text_point.column); - } - else if(entity->flags & DF_EntityFlag_HasVAddr) - { - loc_string = push_str8f(scratch.arena, "0x%016I64x", entity->vaddr); - } - UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|UI_BoxFlag_DrawText, "%S###loc_%p", loc_string, entity); - UI_Signal sig = ui_signal_from_box(box); - if(ui_double_clicked(sig) || sig.f&UI_SignalFlag_KeyboardPressed) - { - DF_CmdParams params = df_cmd_params_from_window(ws); - params.file_path = df_full_path_from_entity(scratch.arena, file_parent); - params.text_point = entity->text_point; - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_TextPoint); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FindCodeLocation)); - } - if(ui_pressed(sig)) - { - next_cursor = v2s64(1, (S64)(idx)); - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - } - UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 2) ? UI_FocusKind_On : UI_FocusKind_Off) - { - if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_Trash, 0, "###del_%p", entity))) - { - df_entity_mark_for_deletion(entity); - } - } - } - } - cursor = next_cursor; - } - - //- rjf: selected num -> selected entity - pv->selected_column = cursor.x; - pv->selected_entity = (1 <= cursor.y && cursor.y <= entities.count) ? df_handle_from_entity(entities.v[cursor.y-1].entity) : df_handle_zero(); - - scratch_end(scratch); -} - -//////////////////////////////// -//~ rjf: ExceptionFilters @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(ExceptionFilters) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(ExceptionFilters) {return str8_lit("");} -DF_VIEW_CMD_FUNCTION_DEF(ExceptionFilters) {} -DF_VIEW_UI_FUNCTION_DEF(ExceptionFilters) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); - String8 query = str8(view->query_buffer, view->query_string_size); - - //- rjf: get state - typedef struct DF_ExceptionFiltersViewState DF_ExceptionFiltersViewState; - struct DF_ExceptionFiltersViewState - { - Vec2S64 cursor; - }; - DF_ExceptionFiltersViewState *sv = df_view_user_state(view, DF_ExceptionFiltersViewState); - - //- rjf: get list of options - typedef struct DF_ExceptionFiltersOption DF_ExceptionFiltersOption; - struct DF_ExceptionFiltersOption - { - String8 name; - FuzzyMatchRangeList matches; - B32 is_enabled; - CTRL_ExceptionCodeKind exception_code_kind; - }; - typedef struct DF_ExceptionFiltersOptionChunkNode DF_ExceptionFiltersOptionChunkNode; - struct DF_ExceptionFiltersOptionChunkNode - { - DF_ExceptionFiltersOptionChunkNode *next; - DF_ExceptionFiltersOption *v; - U64 cap; - U64 count; - }; - typedef struct DF_ExceptionFiltersOptionChunkList DF_ExceptionFiltersOptionChunkList; - struct DF_ExceptionFiltersOptionChunkList - { - DF_ExceptionFiltersOptionChunkNode *first; - DF_ExceptionFiltersOptionChunkNode *last; - U64 option_count; - U64 node_count; - }; - typedef struct DF_ExceptionFiltersOptionArray DF_ExceptionFiltersOptionArray; - struct DF_ExceptionFiltersOptionArray - { - DF_ExceptionFiltersOption *v; - U64 count; - }; - DF_ExceptionFiltersOptionChunkList opts_list = {0}; - for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)(CTRL_ExceptionCodeKind_Null+1); - k < CTRL_ExceptionCodeKind_COUNT; - k = (CTRL_ExceptionCodeKind)(k+1)) - { - DF_ExceptionFiltersOptionChunkNode *node = opts_list.last; - String8 name = push_str8f(scratch.arena, "0x%x %S", ctrl_exception_code_kind_code_table[k], ctrl_exception_code_kind_display_string_table[k]); - FuzzyMatchRangeList matches = fuzzy_match_find(scratch.arena, query, name); - if(matches.count >= matches.needle_part_count) - { - if(node == 0 || node->count >= node->cap) - { - node = push_array(scratch.arena, DF_ExceptionFiltersOptionChunkNode, 1); - node->cap = 256; - node->v = push_array_no_zero(scratch.arena, DF_ExceptionFiltersOption, node->cap); - SLLQueuePush(opts_list.first, opts_list.last, node); - opts_list.node_count += 1; - } - node->v[node->count].name = name; - node->v[node->count].matches = matches; - node->v[node->count].is_enabled = !!(df_state->ctrl_exception_code_filters[k/64] & (1ull<<(k%64))); - node->v[node->count].exception_code_kind = k; - node->count += 1; - opts_list.option_count += 1; - } - } - DF_ExceptionFiltersOptionArray opts = {0}; - { - opts.count = opts_list.option_count; - opts.v = push_array_no_zero(scratch.arena, DF_ExceptionFiltersOption, opts.count); - U64 idx = 0; - for(DF_ExceptionFiltersOptionChunkNode *n = opts_list.first; n != 0; n = n->next) - { - MemoryCopy(opts.v+idx, n->v, n->count*sizeof(DF_ExceptionFiltersOption)); - idx += n->count; - } - } - - //- rjf: build option table - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - Vec2F32 rect_dim = dim_2f32(rect); - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = row_height_px; - scroll_list_params.dim_px = rect_dim; - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, opts.count)); - scroll_list_params.item_range = r1s64(0, opts.count); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - &sv->cursor, - 0, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - { - for(S64 row = visible_row_range.min; row <= visible_row_range.max && row < opts.count; row += 1) - UI_FocusHot(sv->cursor.y == row+1 ? UI_FocusKind_On : UI_FocusKind_Off) - { - DF_ExceptionFiltersOption *opt = &opts.v[row]; - UI_Signal sig = df_icon_buttonf(ws, opt->is_enabled ? DF_IconKind_CheckFilled : DF_IconKind_CheckHollow, &opt->matches, "%S", opt->name); - if(ui_clicked(sig)) - { - if(opt->exception_code_kind != CTRL_ExceptionCodeKind_Null) - { - CTRL_ExceptionCodeKind k = opt->exception_code_kind; - if(opt->is_enabled) - { - df_state->ctrl_exception_code_filters[k/64] &= ~(1ull<<(k%64)); - } - else - { - df_state->ctrl_exception_code_filters[k/64] |= (1ull<<(k%64)); - } - } - } - } - } - - scratch_end(scratch); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Settings @view_hook_impl - -DF_VIEW_SETUP_FUNCTION_DEF(Settings) {} -DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Settings) {return str8_zero();} - -DF_VIEW_CMD_FUNCTION_DEF(Settings) -{ - for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) - { - DF_Cmd *cmd = &n->cmd; - - // rjf: mismatched window/panel => skip - if(df_window_from_handle(cmd->params.window) != ws || - df_panel_from_handle(cmd->params.panel) != panel) - { - continue; - } - - // rjf: process - DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); - switch(core_cmd_kind) - { - default:break; - case DF_CoreCmdKind_PickFile: - { - Temp scratch = scratch_begin(0, 0); - String8 path = cmd->params.file_path; - String8 data = os_data_from_file_path(scratch.arena, path); - DF_CfgTable cfg_table = {0}; - df_cfg_table_push_unparsed_string(scratch.arena, &cfg_table, data, DF_CfgSrc_User); - DF_CfgVal *colors = df_cfg_val_from_string(&cfg_table, str8_lit("colors")); - for(DF_CfgNode *colors_set = colors->first; - colors_set != &df_g_nil_cfg_node; - colors_set = colors_set->next) - { - for(DF_CfgNode *color = colors_set->first; - color != &df_g_nil_cfg_node; - color = color->next) - { - String8 color_name = color->string; - DF_ThemeColor color_code = DF_ThemeColor_Null; - for(DF_ThemeColor c = DF_ThemeColor_Null; c < DF_ThemeColor_COUNT; c = (DF_ThemeColor)(c+1)) - { - if(str8_match(df_g_theme_color_cfg_string_table[c], color_name, StringMatchFlag_CaseInsensitive)) - { - color_code = c; - break; - } - } - if(color_code != DF_ThemeColor_Null) - { - DF_CfgNode *hex_cfg = color->first; - String8 hex_string = hex_cfg->string; - U64 hex_val = 0; - try_u64_from_str8_c_rules(hex_string, &hex_val); - Vec4F32 color_rgba = rgba_from_u32((U32)hex_val); - df_gfx_state->cfg_theme_target.colors[color_code] = color_rgba; - } - } - } - scratch_end(scratch); - }break; - } - } -} - -DF_VIEW_UI_FUNCTION_DEF(Settings) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); - String8 query = str8(view->query_buffer, view->query_string_size); - - ////////////////////////////// - //- rjf: get state - // - typedef struct DF_SettingsViewState DF_SettingsViewState; - struct DF_SettingsViewState - { - B32 initialized; - Vec2S64 cursor; - TxtPt txt_cursor; - TxtPt txt_mark; - U8 txt_buffer[1024]; - U64 txt_size; - DF_ThemeColor color_ctx_menu_color; - Vec4F32 color_ctx_menu_color_hsva; - DF_ThemePreset preset_apply_confirm; - B32 category_opened[DF_SettingsItemKind_COUNT]; - }; - DF_SettingsViewState *sv = df_view_user_state(view, DF_SettingsViewState); - if(!sv->initialized) - { - sv->initialized = 1; - sv->preset_apply_confirm = DF_ThemePreset_COUNT; - } - - ////////////////////////////// - //- rjf: gather all filtered settings items - // - DF_SettingsItemArray items = {0}; - { - DF_SettingsItemList items_list = {0}; - - //- rjf: global settings header - if(query.size == 0) - { - DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); - SLLQueuePush(items_list.first, items_list.last, n); - items_list.count += 1; - n->v.kind = DF_SettingsItemKind_CategoryHeader; - n->v.string = str8_lit("Global Interface Settings"); - n->v.icon_kind = sv->category_opened[DF_SettingsItemKind_GlobalSetting] ? DF_IconKind_DownCaret : DF_IconKind_RightCaret; - n->v.category = DF_SettingsItemKind_GlobalSetting; - } - - //- rjf: gather all global settings - if(sv->category_opened[DF_SettingsItemKind_GlobalSetting] || query.size != 0) - { - for(EachEnumVal(DF_SettingCode, code)) - { - if(df_g_setting_code_default_is_per_window_table[code]) - { - continue; - } - String8 kind_string = str8_lit("Global Interface Setting"); - String8 string = df_g_setting_code_display_string_table[code]; - FuzzyMatchRangeList kind_string_matches = fuzzy_match_find(scratch.arena, query, kind_string); - FuzzyMatchRangeList string_matches = fuzzy_match_find(scratch.arena, query, string); - if(string_matches.count == string_matches.needle_part_count || - kind_string_matches.count == kind_string_matches.needle_part_count) - { - DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); - SLLQueuePush(items_list.first, items_list.last, n); - items_list.count += 1; - n->v.kind = DF_SettingsItemKind_GlobalSetting; - n->v.kind_string = kind_string; - n->v.string = string; - n->v.kind_string_matches = kind_string_matches; - n->v.string_matches = string_matches; - n->v.icon_kind = DF_IconKind_Window; - n->v.code = code; - } - } - } - - //- rjf: window settings header - if(query.size == 0) - { - DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); - SLLQueuePush(items_list.first, items_list.last, n); - items_list.count += 1; - n->v.kind = DF_SettingsItemKind_CategoryHeader; - n->v.string = str8_lit("Window Interface Settings"); - n->v.icon_kind = sv->category_opened[DF_SettingsItemKind_WindowSetting] ? DF_IconKind_DownCaret : DF_IconKind_RightCaret; - n->v.category = DF_SettingsItemKind_WindowSetting; - } - - //- rjf: gather all window settings - if(sv->category_opened[DF_SettingsItemKind_WindowSetting] || query.size != 0) - { - for(EachEnumVal(DF_SettingCode, code)) - { - if(!df_g_setting_code_default_is_per_window_table[code]) - { - continue; - } - String8 kind_string = str8_lit("Window Interface Setting"); - String8 string = df_g_setting_code_display_string_table[code]; - FuzzyMatchRangeList kind_string_matches = fuzzy_match_find(scratch.arena, query, kind_string); - FuzzyMatchRangeList string_matches = fuzzy_match_find(scratch.arena, query, string); - if(string_matches.count == string_matches.needle_part_count || - kind_string_matches.count == kind_string_matches.needle_part_count) - { - DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); - SLLQueuePush(items_list.first, items_list.last, n); - items_list.count += 1; - n->v.kind = DF_SettingsItemKind_WindowSetting; - n->v.kind_string = kind_string; - n->v.string = string; - n->v.kind_string_matches = kind_string_matches; - n->v.string_matches = string_matches; - n->v.icon_kind = DF_IconKind_Window; - n->v.code = code; - } - } - } - - //- rjf: theme presets header - if(query.size == 0) - { - DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); - SLLQueuePush(items_list.first, items_list.last, n); - items_list.count += 1; - n->v.kind = DF_SettingsItemKind_CategoryHeader; - n->v.string = str8_lit("Theme Presets"); - n->v.icon_kind = sv->category_opened[DF_SettingsItemKind_ThemePreset] ? DF_IconKind_DownCaret : DF_IconKind_RightCaret; - n->v.category = DF_SettingsItemKind_ThemePreset; - } - - //- rjf: gather theme presets - if(sv->category_opened[DF_SettingsItemKind_ThemePreset] || query.size != 0) - { - for(EachEnumVal(DF_ThemePreset, preset)) - { - String8 kind_string = str8_lit("Theme Preset"); - String8 string = df_g_theme_preset_display_string_table[preset]; - FuzzyMatchRangeList kind_string_matches = fuzzy_match_find(scratch.arena, query, kind_string); - FuzzyMatchRangeList string_matches = fuzzy_match_find(scratch.arena, query, string); - if(string_matches.count == string_matches.needle_part_count || - kind_string_matches.count == kind_string_matches.needle_part_count) - { - DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); - SLLQueuePush(items_list.first, items_list.last, n); - items_list.count += 1; - n->v.kind = DF_SettingsItemKind_ThemePreset; - n->v.kind_string = kind_string; - n->v.string = string; - n->v.kind_string_matches = kind_string_matches; - n->v.string_matches = string_matches; - n->v.icon_kind = DF_IconKind_Palette; - n->v.preset = preset; - } - } - } - - //- rjf: theme colors header - if(query.size == 0) - { - DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); - SLLQueuePush(items_list.first, items_list.last, n); - items_list.count += 1; - n->v.kind = DF_SettingsItemKind_CategoryHeader; - n->v.string = str8_lit("Theme Colors"); - n->v.icon_kind = sv->category_opened[DF_SettingsItemKind_ThemeColor] ? DF_IconKind_DownCaret : DF_IconKind_RightCaret; - n->v.category = DF_SettingsItemKind_ThemeColor; - } - - //- rjf: gather all theme colors - if(sv->category_opened[DF_SettingsItemKind_ThemeColor] || query.size != 0) - { - for(EachNonZeroEnumVal(DF_ThemeColor, color)) - { - String8 kind_string = str8_lit("Theme Color"); - String8 string = df_g_theme_color_display_string_table[color]; - FuzzyMatchRangeList kind_string_matches = fuzzy_match_find(scratch.arena, query, kind_string); - FuzzyMatchRangeList string_matches = fuzzy_match_find(scratch.arena, query, string); - if(string_matches.count == string_matches.needle_part_count || - kind_string_matches.count == kind_string_matches.needle_part_count) - { - DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); - SLLQueuePush(items_list.first, items_list.last, n); - items_list.count += 1; - n->v.kind = DF_SettingsItemKind_ThemeColor; - n->v.kind_string = kind_string; - n->v.string = string; - n->v.kind_string_matches = kind_string_matches; - n->v.string_matches = string_matches; - n->v.icon_kind = DF_IconKind_Palette; - n->v.color = color; - } - } - } - - //- rjf: convert to array - items.count = items_list.count; - items.v = push_array(scratch.arena, DF_SettingsItem, items.count); - { - U64 idx = 0; - for(DF_SettingsItemNode *n = items_list.first; n != 0; n = n->next, idx += 1) - { - items.v[idx] = n->v; - } - } - } - - ////////////////////////////// - //- rjf: sort filtered settings item list - // - if(query.size != 0) - { - quick_sort(items.v, items.count, sizeof(items.v[0]), df_qsort_compare_settings_item); - } - - ////////////////////////////// - //- rjf: produce per-color context menu keys - // - UI_Key *color_ctx_menu_keys = push_array(scratch.arena, UI_Key, DF_ThemeColor_COUNT); - { - for(DF_ThemeColor color = (DF_ThemeColor)(DF_ThemeColor_Null+1); - color < DF_ThemeColor_COUNT; - color = (DF_ThemeColor)(color+1)) - { - color_ctx_menu_keys[color] = ui_key_from_stringf(ui_key_zero(), "###settings_color_ctx_menu_%I64x", (U64)color); - } - } - - ////////////////////////////// - //- rjf: build color context menus - // - for(DF_ThemeColor color = (DF_ThemeColor)(DF_ThemeColor_Null+1); - color < DF_ThemeColor_COUNT; - color = (DF_ThemeColor)(color+1)) - { - DF_Palette(ws, DF_PaletteCode_Floating) - UI_CtxMenu(color_ctx_menu_keys[color]) - UI_Padding(ui_em(1.5f, 1.f)) - UI_PrefWidth(ui_em(28.5f, 1)) UI_PrefHeight(ui_children_sum(1.f)) - { - // rjf: build title - UI_Row - { - ui_spacer(ui_em(1.5f, 1.f)); - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(df_g_theme_color_display_string_table[color]); - } - - ui_spacer(ui_em(1.5f, 1.f)); - - // rjf: build picker - { - ui_set_next_pref_height(ui_em(22.f, 1.f)); - UI_Row UI_Padding(ui_pct(1, 0)) - { - UI_PrefWidth(ui_em(22.f, 1.f)) UI_PrefHeight(ui_em(22.f, 1.f)) UI_Flags(UI_BoxFlag_FocusNavSkip) - { - ui_sat_val_pickerf(sv->color_ctx_menu_color_hsva.x, &sv->color_ctx_menu_color_hsva.y, &sv->color_ctx_menu_color_hsva.z, "###settings_satval_picker"); - } - - ui_spacer(ui_em(0.75f, 1.f)); - - UI_PrefWidth(ui_em(1.5f, 1.f)) UI_PrefHeight(ui_em(22.f, 1.f)) UI_Flags(UI_BoxFlag_FocusNavSkip) - ui_hue_pickerf(&sv->color_ctx_menu_color_hsva.x, sv->color_ctx_menu_color_hsva.y, sv->color_ctx_menu_color_hsva.z, "###settings_hue_picker"); - - UI_PrefWidth(ui_em(1.5f, 1.f)) UI_PrefHeight(ui_em(22.f, 1.f)) UI_Flags(UI_BoxFlag_FocusNavSkip) - ui_alpha_pickerf(&sv->color_ctx_menu_color_hsva.w, "###settings_alpha_picker"); - } - } - - ui_spacer(ui_em(1.5f, 1.f)); - - // rjf: build line edits - UI_Row - UI_WidthFill - UI_Padding(ui_em(1.5f, 1.f)) - UI_PrefHeight(ui_children_sum(1.f)) - UI_Column - UI_PrefHeight(ui_em(2.25f, 1.f)) - { - Vec4F32 hsva = sv->color_ctx_menu_color_hsva; - Vec3F32 hsv = v3f32(hsva.x, hsva.y, hsva.z); - Vec3F32 rgb = rgb_from_hsv(hsv); - Vec4F32 rgba = v4f32(rgb.x, rgb.y, rgb.z, sv->color_ctx_menu_color_hsva.w); - String8 hex_string = hex_string_from_rgba_4f32(scratch.arena, rgba); - hex_string = push_str8f(scratch.arena, "#%S", hex_string); - String8 r_string = push_str8f(scratch.arena, "%.2f", rgba.x); - String8 g_string = push_str8f(scratch.arena, "%.2f", rgba.y); - String8 b_string = push_str8f(scratch.arena, "%.2f", rgba.z); - String8 h_string = push_str8f(scratch.arena, "%.2f", hsva.x); - String8 s_string = push_str8f(scratch.arena, "%.2f", hsva.y); - String8 v_string = push_str8f(scratch.arena, "%.2f", hsva.z); - String8 a_string = push_str8f(scratch.arena, "%.2f", rgba.w); - UI_Row DF_Font(ws, DF_FontSlot_Code) - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("Hex"); - UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, hex_string, "###hex_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_rgba = rgba_from_hex_string_4f32(string); - Vec4F32 new_hsva = hsva_from_rgba(new_rgba); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - ui_spacer(ui_em(0.75f, 1.f)); - UI_Row - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("R"); - UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, r_string, "###r_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_rgba = v4f32((F32)f64_from_str8(string), rgba.y, rgba.z, rgba.w); - Vec4F32 new_hsva = hsva_from_rgba(new_rgba); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - UI_Row - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("G"); - UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, g_string, "###g_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_rgba = v4f32(rgba.x, (F32)f64_from_str8(string), rgba.z, rgba.w); - Vec4F32 new_hsva = hsva_from_rgba(new_rgba); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - UI_Row - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("B"); - UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, b_string, "###b_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_rgba = v4f32(rgba.x, rgba.y, (F32)f64_from_str8(string), rgba.w); - Vec4F32 new_hsva = hsva_from_rgba(new_rgba); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - ui_spacer(ui_em(0.75f, 1.f)); - UI_Row - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("H"); - UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, h_string, "###h_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_hsva = v4f32((F32)f64_from_str8(string), hsva.y, hsva.z, hsva.w); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - UI_Row - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("S"); - UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, s_string, "###s_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_hsva = v4f32(hsva.x, (F32)f64_from_str8(string), hsva.z, hsva.w); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - UI_Row - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("V"); - UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, v_string, "###v_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_hsva = v4f32(hsva.x, hsva.y, (F32)f64_from_str8(string), hsva.w); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - ui_spacer(ui_em(0.75f, 1.f)); - UI_Row - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("A"); - UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, a_string, "###a_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_hsva = v4f32(hsva.x, hsva.y, hsva.z, (F32)f64_from_str8(string)); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - } - - // rjf: commit state to theme - Vec4F32 hsva = sv->color_ctx_menu_color_hsva; - Vec3F32 hsv = v3f32(hsva.x, hsva.y, hsva.z); - Vec3F32 rgb = rgb_from_hsv(hsv); - Vec4F32 rgba = v4f32(rgb.x, rgb.y, rgb.z, sv->color_ctx_menu_color_hsva.w); - df_gfx_state->cfg_theme_target.colors[sv->color_ctx_menu_color] = rgba; - } - } - - ////////////////////////////// - //- rjf: cancels - // - UI_Focus(UI_FocusKind_On) if(ui_is_focus_active() && sv->preset_apply_confirm < DF_ThemePreset_COUNT && ui_slot_press(UI_EventActionSlot_Cancel)) - { - sv->preset_apply_confirm = DF_ThemePreset_COUNT; - } - - ////////////////////////////// - //- rjf: build items list - // - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - Vec2F32 rect_dim = dim_2f32(rect); - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = row_height_px; - scroll_list_params.dim_px = v2f32(rect_dim.x, rect_dim.y); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, items.count)); - scroll_list_params.item_range = r1s64(0, items.count); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, &view->scroll_pos.y, &sv->cursor, 0, &visible_row_range, &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - { - for(S64 row_num = visible_row_range.min; row_num <= visible_row_range.max && row_num < items.count; row_num += 1) - { - //- rjf: unpack item - DF_SettingsItem *item = &items.v[row_num]; - UI_Palette *palette = ui_top_palette(); - Vec4F32 rgba = ui_top_palette()->text_weak; - OS_Cursor cursor = OS_Cursor_HandPoint; - Rng1S32 s32_range = {0}; - B32 is_toggler = 0; - B32 is_toggled = 0; - B32 is_slider = 0; - S32 slider_s32_val = 0; - F32 slider_pct = 0.f; - UI_BoxFlags flags = UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawHotEffects|UI_BoxFlag_DrawActiveEffects; - DF_SettingVal *val_table = &df_gfx_state->cfg_setting_vals[DF_CfgSrc_User][0]; - switch(item->kind) - { - case DF_SettingsItemKind_COUNT:{}break; - case DF_SettingsItemKind_CategoryHeader: - { - cursor = OS_Cursor_HandPoint; - flags = UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawHotEffects; - }break; - case DF_SettingsItemKind_ThemePreset: - { - Vec4F32 *colors = df_g_theme_preset_colors_table[item->preset]; - Vec4F32 bg_color = colors[DF_ThemeColor_BaseBackground]; - Vec4F32 tx_color = colors[DF_ThemeColor_Text]; - Vec4F32 tw_color = colors[DF_ThemeColor_TextWeak]; - Vec4F32 bd_color = colors[DF_ThemeColor_BaseBorder]; - palette = ui_build_palette(ui_top_palette(), - .text = tx_color, - .text_weak = tw_color, - .border = bd_color, - .background = bg_color); - }break; - case DF_SettingsItemKind_ThemeColor: - { - rgba = df_rgba_from_theme_color(item->color); - }break; - case DF_SettingsItemKind_WindowSetting: {val_table = &ws->setting_vals[0];}goto setting; - case DF_SettingsItemKind_GlobalSetting:{}goto setting; - setting:; - { - s32_range = df_g_setting_code_s32_range_table[item->code]; - if(s32_range.min != 0 || s32_range.max != 1) - { - cursor = OS_Cursor_LeftRight; - is_slider = 1; - slider_s32_val = val_table[item->code].s32; - slider_pct = (F32)(slider_s32_val - s32_range.min) / dim_1s32(s32_range); - } - else - { - is_toggler = 1; - is_toggled = !!val_table[item->code].s32; - } - }break; - } - - //- rjf: build item widget - UI_Box *item_box = &ui_g_nil_box; - UI_Row - { - if(query.size == 0 && item->kind != DF_SettingsItemKind_CategoryHeader) - { - ui_set_next_flags(UI_BoxFlag_DrawSideLeft); - ui_spacer(ui_em(2.f, 1.f)); - } - UI_Focus(row_num+1 == sv->cursor.y ? UI_FocusKind_On : UI_FocusKind_Off) UI_Palette(palette) - { - ui_set_next_hover_cursor(cursor); - item_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|flags, "###option_%S_%S", item->kind_string, item->string); - UI_Parent(item_box) - { - if(item->icon_kind != DF_IconKind_Null) - { - UI_PrefWidth(ui_em(3.f, 1.f)) - DF_Font(ws, DF_FontSlot_Icons) - UI_Palette(ui_build_palette(ui_top_palette(), .text = rgba)) - UI_TextAlignment(UI_TextAlign_Center) - ui_label(df_g_icon_kind_text_table[item->icon_kind]); - } - if(query.size != 0 && item->kind_string.size != 0) UI_PrefWidth(ui_text_dim(10, 1)) - { - UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_DrawText|UI_BoxFlag_DrawTextWeak, "%S", item->kind_string); - ui_box_equip_fuzzy_match_ranges(box, &item->kind_string_matches); - } - UI_PrefWidth(ui_text_dim(10, 1)) - { - UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S", item->string); - ui_box_equip_fuzzy_match_ranges(box, &item->string_matches); - } - if(is_slider) UI_PrefWidth(ui_text_dim(10, 1)) - { - UI_Flags(UI_BoxFlag_DrawTextWeak) - ui_labelf("(%i)", slider_s32_val); - UI_PrefWidth(ui_pct(slider_pct, 1.f)) UI_HeightFill UI_FixedX(0) UI_FixedY(0) - UI_Palette(ui_build_palette(ui_top_palette(), .background = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlay))) - ui_build_box_from_key(UI_BoxFlag_DrawBackground, ui_key_zero()); - } - if(is_toggler) - { - ui_spacer(ui_pct(1, 0)); - UI_PrefWidth(ui_em(2.5f, 1.f)) - DF_Font(ws, DF_FontSlot_Icons) - UI_Flags(UI_BoxFlag_DrawTextWeak) - ui_label(df_g_icon_kind_text_table[is_toggled ? DF_IconKind_CheckFilled : DF_IconKind_CheckHollow]); - } - if(item->kind == DF_SettingsItemKind_ThemePreset && sv->preset_apply_confirm == item->preset) - { - ui_spacer(ui_pct(1, 0)); - UI_PrefWidth(ui_text_dim(10, 1)) - DF_Palette(ws, DF_PaletteCode_NegativePopButton) - UI_CornerRadius(ui_top_font_size()*0.5f) - UI_FontSize(ui_top_font_size()*0.9f) - ui_build_box_from_stringf(UI_BoxFlag_DrawText|UI_BoxFlag_DrawBackground, "Click Again To Apply"); - } - } - } - } - - //- rjf: interact - UI_Signal sig = ui_signal_from_box(item_box); - if(item->kind == DF_SettingsItemKind_ThemeColor && ui_clicked(sig)) - { - Vec3F32 rgb = v3f32(rgba.x, rgba.y, rgba.z); - Vec3F32 hsv = hsv_from_rgb(rgb); - Vec4F32 hsva = v4f32(hsv.x, hsv.y, hsv.z, rgba.w); - ui_ctx_menu_open(color_ctx_menu_keys[item->color], item_box->key, v2f32(0, dim_2f32(item_box->rect).y)); - sv->color_ctx_menu_color = item->color; - sv->color_ctx_menu_color_hsva = v4f32(hsv.x, hsv.y, hsv.z, rgba.w); - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - if((item->kind == DF_SettingsItemKind_GlobalSetting || item->kind == DF_SettingsItemKind_WindowSetting) && - is_toggler && ui_clicked(sig)) - { - val_table[item->code].s32 ^= 1; - val_table[item->code].set = 1; - } - if((item->kind == DF_SettingsItemKind_GlobalSetting || item->kind == DF_SettingsItemKind_WindowSetting) && - is_slider && ui_dragging(sig)) - { - if(ui_pressed(sig)) - { - ui_store_drag_struct(&slider_s32_val); - } - S32 pre_drag_val = *ui_get_drag_struct(S32); - Vec2F32 delta = ui_drag_delta(); - S32 pst_drag_val = pre_drag_val + (S32)(delta.x/(ui_top_font_size()*2.f)); - pst_drag_val = clamp_1s32(s32_range, pst_drag_val); - val_table[item->code].s32 = pst_drag_val; - val_table[item->code].set = 1; - } - if(item->kind == DF_SettingsItemKind_ThemePreset && ui_clicked(sig)) - { - if(sv->preset_apply_confirm == item->preset) - { - Vec4F32 *colors = df_g_theme_preset_colors_table[item->preset]; - MemoryCopy(df_gfx_state->cfg_theme_target.colors, colors, sizeof(df_gfx_state->cfg_theme_target.colors)); - sv->preset_apply_confirm = DF_ThemePreset_COUNT; - } - else - { - sv->preset_apply_confirm = item->preset; - } - } - if(item->kind != DF_SettingsItemKind_ThemePreset && ui_pressed(sig)) - { - sv->preset_apply_confirm = DF_ThemePreset_COUNT; - } - if(item->kind != DF_SettingsItemKind_ThemePreset && ui_pressed(sig)) - { - sv->preset_apply_confirm = DF_ThemePreset_COUNT; - } - if(item->kind == DF_SettingsItemKind_CategoryHeader && ui_pressed(sig)) - { - sv->category_opened[item->category] ^= 1; - } - } - } - - scratch_end(scratch); - ProfEnd(); - - //~ TODO(rjf): OLD vvvvvvvvvvvvvvvvvvvvvvvvvv -#if 0 - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); - - //- rjf: get state - typedef struct DF_ThemeViewState DF_ThemeViewState; - struct DF_ThemeViewState - { - Vec2S64 cursor; - TxtPt txt_cursor; - TxtPt txt_mark; - U8 txt_buffer[1024]; - U64 txt_size; - DF_ThemeColor color_ctx_menu_color; - Vec4F32 color_ctx_menu_color_hsva; - }; - DF_ThemeViewState *sv = df_view_user_state(view, DF_ThemeViewState); - - //- rjf: build preset ctx menu - UI_Key preset_ctx_menu_key = ui_key_from_stringf(ui_key_zero(), "%p_preset_ctx_menu", view); - DF_Palette(ws, DF_PaletteCode_Floating) UI_CtxMenu(preset_ctx_menu_key) UI_PrefWidth(ui_em(30.f, 1.f)) - { - for(DF_ThemePreset preset = (DF_ThemePreset)0; - preset < DF_ThemePreset_COUNT; - preset = (DF_ThemePreset)(preset+1)) - { - Vec4F32 *colors = df_g_theme_preset_colors_table[preset]; - Vec4F32 bg_color = colors[DF_ThemeColor_BaseBackground]; - Vec4F32 tx_color = colors[DF_ThemeColor_Text]; - Vec4F32 bd_color = colors[DF_ThemeColor_BaseBorder]; - ui_set_next_palette(ui_build_palette(ui_top_palette(), .text = tx_color, - .border = bd_color, - .background = bg_color)); - if(ui_clicked(ui_buttonf("%S", df_g_theme_preset_display_string_table[preset]))) - { - MemoryCopy(df_gfx_state->cfg_theme_target.colors, colors, sizeof(df_gfx_state->cfg_theme_target.colors)); - } - } - } - - //- rjf: produce per-color context menu keys - UI_Key *color_ctx_menu_keys = push_array(scratch.arena, UI_Key, DF_ThemeColor_COUNT); - { - for(DF_ThemeColor color = (DF_ThemeColor)(DF_ThemeColor_Null+1); - color < DF_ThemeColor_COUNT; - color = (DF_ThemeColor)(color+1)) - { - color_ctx_menu_keys[color] = ui_key_from_stringf(ui_key_zero(), "###settings_color_ctx_menu_%I64x", (U64)color); - } - } - - //- rjf: do color context menus - for(DF_ThemeColor color = (DF_ThemeColor)(DF_ThemeColor_Null+1); - color < DF_ThemeColor_COUNT; - color = (DF_ThemeColor)(color+1)) - { - DF_Palette(ws, DF_PaletteCode_Floating) - UI_CtxMenu(color_ctx_menu_keys[color]) - UI_Padding(ui_em(1.5f, 1.f)) - UI_PrefWidth(ui_em(28.5f, 1)) UI_PrefHeight(ui_children_sum(1.f)) - { - // rjf: build title - UI_Row - { - ui_spacer(ui_em(1.5f, 1.f)); - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(df_g_theme_color_display_string_table[color]); - } - - ui_spacer(ui_em(1.5f, 1.f)); - - // rjf: build picker - { - ui_set_next_pref_height(ui_em(22.f, 1.f)); - UI_Row UI_Padding(ui_pct(1, 0)) - { - UI_PrefWidth(ui_em(22.f, 1.f)) UI_PrefHeight(ui_em(22.f, 1.f)) UI_Flags(UI_BoxFlag_FocusNavSkip) - { - ui_sat_val_pickerf(sv->color_ctx_menu_color_hsva.x, &sv->color_ctx_menu_color_hsva.y, &sv->color_ctx_menu_color_hsva.z, "###settings_satval_picker"); - } - - ui_spacer(ui_em(0.75f, 1.f)); - - UI_PrefWidth(ui_em(1.5f, 1.f)) UI_PrefHeight(ui_em(22.f, 1.f)) UI_Flags(UI_BoxFlag_FocusNavSkip) - ui_hue_pickerf(&sv->color_ctx_menu_color_hsva.x, sv->color_ctx_menu_color_hsva.y, sv->color_ctx_menu_color_hsva.z, "###settings_hue_picker"); - - UI_PrefWidth(ui_em(1.5f, 1.f)) UI_PrefHeight(ui_em(22.f, 1.f)) UI_Flags(UI_BoxFlag_FocusNavSkip) - ui_alpha_pickerf(&sv->color_ctx_menu_color_hsva.w, "###settings_alpha_picker"); - } - } - - ui_spacer(ui_em(1.5f, 1.f)); - - // rjf: build line edits - UI_Row - UI_WidthFill - UI_Padding(ui_em(1.5f, 1.f)) - UI_PrefHeight(ui_children_sum(1.f)) - UI_Column - UI_PrefHeight(ui_em(2.25f, 1.f)) - { - Vec4F32 hsva = sv->color_ctx_menu_color_hsva; - Vec3F32 hsv = v3f32(hsva.x, hsva.y, hsva.z); - Vec3F32 rgb = rgb_from_hsv(hsv); - Vec4F32 rgba = v4f32(rgb.x, rgb.y, rgb.z, sv->color_ctx_menu_color_hsva.w); - String8 hex_string = hex_string_from_rgba_4f32(scratch.arena, rgba); - hex_string = push_str8f(scratch.arena, "#%S", hex_string); - String8 r_string = push_str8f(scratch.arena, "%.2f", rgba.x); - String8 g_string = push_str8f(scratch.arena, "%.2f", rgba.y); - String8 b_string = push_str8f(scratch.arena, "%.2f", rgba.z); - String8 h_string = push_str8f(scratch.arena, "%.2f", hsva.x); - String8 s_string = push_str8f(scratch.arena, "%.2f", hsva.y); - String8 v_string = push_str8f(scratch.arena, "%.2f", hsva.z); - String8 a_string = push_str8f(scratch.arena, "%.2f", rgba.w); - UI_Row DF_Font(ws, DF_FontSlot_Code) - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("Hex"); - UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, hex_string, "###hex_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_rgba = rgba_from_hex_string_4f32(string); - Vec4F32 new_hsva = hsva_from_rgba(new_rgba); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - ui_spacer(ui_em(0.75f, 1.f)); - UI_Row - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("R"); - UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, r_string, "###r_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_rgba = v4f32((F32)f64_from_str8(string), rgba.y, rgba.z, rgba.w); - Vec4F32 new_hsva = hsva_from_rgba(new_rgba); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - UI_Row - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("G"); - UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, g_string, "###g_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_rgba = v4f32(rgba.x, (F32)f64_from_str8(string), rgba.z, rgba.w); - Vec4F32 new_hsva = hsva_from_rgba(new_rgba); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - UI_Row - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("B"); - UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, b_string, "###b_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_rgba = v4f32(rgba.x, rgba.y, (F32)f64_from_str8(string), rgba.w); - Vec4F32 new_hsva = hsva_from_rgba(new_rgba); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - ui_spacer(ui_em(0.75f, 1.f)); - UI_Row - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("H"); - UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, h_string, "###h_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_hsva = v4f32((F32)f64_from_str8(string), hsva.y, hsva.z, hsva.w); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - UI_Row - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("S"); - UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, s_string, "###s_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_hsva = v4f32(hsva.x, (F32)f64_from_str8(string), hsva.z, hsva.w); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - UI_Row - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("V"); - UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, v_string, "###v_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_hsva = v4f32(hsva.x, hsva.y, (F32)f64_from_str8(string), hsva.w); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - ui_spacer(ui_em(0.75f, 1.f)); - UI_Row - { - UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("A"); - UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, a_string, "###a_edit"); - if(ui_committed(sig)) - { - String8 string = str8(sv->txt_buffer, sv->txt_size); - Vec4F32 new_hsva = v4f32(hsva.x, hsva.y, hsva.z, (F32)f64_from_str8(string)); - sv->color_ctx_menu_color_hsva = new_hsva; - } - } - } - - // rjf: commit state to theme - Vec4F32 hsva = sv->color_ctx_menu_color_hsva; - Vec3F32 hsv = v3f32(hsva.x, hsva.y, hsva.z); - Vec3F32 rgb = rgb_from_hsv(hsv); - Vec4F32 rgba = v4f32(rgb.x, rgb.y, rgb.z, sv->color_ctx_menu_color_hsva.w); - df_gfx_state->cfg_theme_target.colors[sv->color_ctx_menu_color] = rgba; - } - } - - //- rjf: build non-scrolled header - UI_PrefHeight(ui_px(row_height_px, 1.f)) UI_Row - { - // rjf: preset selector - UI_FocusHot((sv->cursor.y == 1 && sv->cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off) - { - UI_Signal preset_sig = df_icon_buttonf(ws, DF_IconKind_Palette, 0, "Apply Preset"); - if(ui_clicked(preset_sig)) - { - ui_ctx_menu_open(preset_ctx_menu_key, preset_sig.box->key, v2f32(0, dim_2f32(preset_sig.box->rect).y)); - } - } - - // rjf: load-from-file - UI_FocusHot((sv->cursor.y == 1 && sv->cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) - { - if(ui_clicked(df_icon_buttonf(DF_IconKind_FileOutline, 0, "Load From File"))) - { - DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); - params.cmd_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_PickFile); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); - } - } - } - - //- rjf: build palette table - Rng1S64 visible_row_range = {0}; - UI_ScrollListParams scroll_list_params = {0}; - { - Vec2F32 rect_dim = dim_2f32(rect); - scroll_list_params.flags = UI_ScrollListFlag_All; - scroll_list_params.row_height_px = row_height_px; - scroll_list_params.dim_px = v2f32(rect_dim.x, rect_dim.y-row_height_px); - scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(1, DF_ThemeColor_COUNT)); - scroll_list_params.item_range = r1s64(0, DF_ThemeColor_COUNT-1); - scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; - } - UI_ScrollListSignal scroll_list_sig = {0}; - UI_Focus(UI_FocusKind_On) - UI_ScrollList(&scroll_list_params, - &view->scroll_pos.y, - &sv->cursor, - 0, - &visible_row_range, - &scroll_list_sig) - UI_Focus(UI_FocusKind_Null) - { - for(S64 row = visible_row_range.min; row <= visible_row_range.max; row += 1) - { - DF_ThemeColor color = (DF_ThemeColor)(row+1); - if(DF_ThemeColor_Null < color && color < DF_ThemeColor_COUNT) - UI_FocusHot(sv->cursor.y == row+2 ? UI_FocusKind_On : UI_FocusKind_Off) - { - Vec4F32 rgba = df_rgba_from_theme_color(color); - Vec3F32 rgb = v3f32(rgba.x, rgba.y, rgba.z); - Vec3F32 hsv = hsv_from_rgb(rgb); - Vec4F32 hsva = v4f32(hsv.x, hsv.y, hsv.z, rgba.w); - ui_set_next_pref_width(ui_pct(1, 0)); - ui_set_next_hover_cursor(OS_Cursor_HandPoint); - UI_Box *color_row = ui_build_box_from_stringf(UI_BoxFlag_DrawBorder| - UI_BoxFlag_DrawBackground| - UI_BoxFlag_DrawHotEffects| - UI_BoxFlag_DrawActiveEffects| - UI_BoxFlag_Clickable, - "###color_%I64x", (U64)color); - UI_Parent(color_row) - { - Vec4F32 bg_color = ui_top_palette()->background; - Vec4F32 default_text_color = ui_top_palette()->text; - F32 default_fallback_factor = clamp_1f32(r1f32(0.3f, 1), dot_4f32(normalize_4f32(rgba), normalize_4f32(bg_color))) - 0.3f; - Vec4F32 text_rgba = mix_4f32(rgba, default_text_color, default_fallback_factor); - UI_WidthFill UI_Palette(ui_build_palette(ui_top_palette(), .text = text_rgba)) ui_label(df_g_theme_color_display_string_table[color]); - ui_set_next_pref_width(ui_top_pref_height()); - UI_HeightFill UI_Column UI_Padding(ui_em(0.3f, 1)) - { - ui_set_next_palette(ui_build_palette(ui_top_palette(), .background = rgba)); - ui_set_next_corner_radius_00(ui_top_font_size()/4.f); - ui_set_next_corner_radius_01(ui_top_font_size()/4.f); - ui_set_next_corner_radius_10(ui_top_font_size()/4.f); - ui_set_next_corner_radius_11(ui_top_font_size()/4.f); - UI_Box *color_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawBackground, "###color_box"); - UI_Signal color_sig = ui_signal_from_box(color_box); - if(ui_hovering(color_sig)) - { - ui_do_color_tooltip_hsva(hsva); - } - } - ui_spacer(ui_em(0.3f, 1)); - } - UI_Signal color_row_sig = ui_signal_from_box(color_row); - if(ui_clicked(color_row_sig) || ui_right_clicked(color_row_sig)) - { - ui_ctx_menu_open(color_ctx_menu_keys[color], color_row->key, v2f32(0, color_row->rect.y1-color_row->rect.y0)); - sv->color_ctx_menu_color = color; - sv->color_ctx_menu_color_hsva = v4f32(hsv.x, hsv.y, hsv.z, rgba.w); - DF_CmdParams p = df_cmd_params_from_panel(ws, panel); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); - } - if(ui_hovering(color_row_sig)) UI_Tooltip - { - ui_label(df_g_theme_color_display_string_table[color]); - } - } - } - } - - scratch_end(scratch); - ProfEnd(); -#endif -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Quick Sort Comparisons + +internal int +df_qsort_compare_file_info__default(DF_FileInfo *a, DF_FileInfo *b) +{ + int result = 0; + if(a->props.flags & FilePropertyFlag_IsFolder && !(b->props.flags & FilePropertyFlag_IsFolder)) + { + result = -1; + } + else if(b->props.flags & FilePropertyFlag_IsFolder && !(a->props.flags & FilePropertyFlag_IsFolder)) + { + result = +1; + } + else + { + result = df_qsort_compare_file_info__filename(a, b); + } + return result; +} + +internal int +df_qsort_compare_file_info__default_filtered(DF_FileInfo *a, DF_FileInfo *b) +{ + int result = 0; + if(a->filename.size < b->filename.size) + { + result = -1; + } + else if(a->filename.size > b->filename.size) + { + result = +1; + } + return result; +} + +internal int +df_qsort_compare_file_info__filename(DF_FileInfo *a, DF_FileInfo *b) +{ + return strncmp((char *)a->filename.str, (char *)b->filename.str, Min(a->filename.size, b->filename.size)); +} + +internal int +df_qsort_compare_file_info__last_modified(DF_FileInfo *a, DF_FileInfo *b) +{ + return ((a->props.modified < b->props.modified) ? -1 : + (a->props.modified > b->props.modified) ? +1 : + 0); +} + +internal int +df_qsort_compare_file_info__size(DF_FileInfo *a, DF_FileInfo *b) +{ + return ((a->props.size < b->props.size) ? -1 : + (a->props.size > b->props.size) ? +1 : + 0); +} + +internal int +df_qsort_compare_process_info(DF_ProcessInfo *a, DF_ProcessInfo *b) +{ + int result = 0; + if(a->pid_match_ranges.count > b->pid_match_ranges.count) + { + result = -1; + } + else if(a->pid_match_ranges.count < b->pid_match_ranges.count) + { + result = +1; + } + else if(a->name_match_ranges.count < b->name_match_ranges.count) + { + result = -1; + } + else if(a->name_match_ranges.count > b->name_match_ranges.count) + { + result = +1; + } + else if(a->attached_match_ranges.count < b->attached_match_ranges.count) + { + result = -1; + } + else if(a->attached_match_ranges.count > b->attached_match_ranges.count) + { + result = +1; + } + return result; +} + +internal int +df_qsort_compare_cmd_lister__strength(DF_CmdListerItem *a, DF_CmdListerItem *b) +{ + int result = 0; + if(a->name_match_ranges.count > b->name_match_ranges.count) + { + result = -1; + } + else if(a->name_match_ranges.count < b->name_match_ranges.count) + { + result = +1; + } + else if(a->desc_match_ranges.count > b->desc_match_ranges.count) + { + result = -1; + } + else if(a->desc_match_ranges.count < b->desc_match_ranges.count) + { + result = +1; + } + else if(a->tags_match_ranges.count > b->tags_match_ranges.count) + { + result = -1; + } + else if(a->tags_match_ranges.count < b->tags_match_ranges.count) + { + result = +1; + } + else if(a->registrar_idx < b->registrar_idx) + { + result = -1; + } + else if(a->registrar_idx > b->registrar_idx) + { + result = +1; + } + else if(a->ordering_idx < b->ordering_idx) + { + result = -1; + } + else if(a->ordering_idx > b->ordering_idx) + { + result = +1; + } + return result; +} + +internal int +df_qsort_compare_entity_lister__strength(DF_EntityListerItem *a, DF_EntityListerItem *b) +{ + int result = 0; + if(a->name_match_ranges.count > b->name_match_ranges.count) + { + result = -1; + } + else if(a->name_match_ranges.count < b->name_match_ranges.count) + { + result = +1; + } + return result; +} + +internal int +df_qsort_compare_settings_item(DF_SettingsItem *a, DF_SettingsItem *b) +{ + int result = 0; + if(a->string_matches.count > b->string_matches.count) + { + result = -1; + } + else if(a->string_matches.count < b->string_matches.count) + { + result = +1; + } + else if(a->kind_string_matches.count > b->kind_string_matches.count) + { + result = -1; + } + else if(a->kind_string_matches.count < b->kind_string_matches.count) + { + result = +1; + } + return result; +} + +//////////////////////////////// +//~ rjf: Command Lister + +internal DF_CmdListerItemList +df_cmd_lister_item_list_from_needle(Arena *arena, String8 needle) +{ + Temp scratch = scratch_begin(&arena, 1); + DF_CmdSpecList specs = df_push_cmd_spec_list(scratch.arena); + DF_CmdListerItemList result = {0}; + for(DF_CmdSpecNode *n = specs.first; n != 0; n = n->next) + { + DF_CmdSpec *spec = n->spec; + if(!(spec->info.flags & DF_CmdSpecFlag_OmitFromLists)) + { + String8 cmd_display_name = spec->info.display_name; + String8 cmd_desc = spec->info.description; + String8 cmd_tags = spec->info.search_tags; + FuzzyMatchRangeList name_matches = fuzzy_match_find(arena, needle, cmd_display_name); + FuzzyMatchRangeList desc_matches = fuzzy_match_find(arena, needle, cmd_desc); + FuzzyMatchRangeList tags_matches = fuzzy_match_find(arena, needle, cmd_tags); + if(name_matches.count == name_matches.needle_part_count || + desc_matches.count == name_matches.needle_part_count || + tags_matches.count > 0 || + name_matches.needle_part_count == 0) + { + DF_CmdListerItemNode *node = push_array(arena, DF_CmdListerItemNode, 1); + node->item.cmd_spec = spec; + node->item.registrar_idx = spec->registrar_index; + node->item.ordering_idx = spec->ordering_index; + node->item.name_match_ranges = name_matches; + node->item.desc_match_ranges = desc_matches; + node->item.tags_match_ranges = tags_matches; + SLLQueuePush(result.first, result.last, node); + result.count += 1; + } + } + } + scratch_end(scratch); + return result; +} + +internal DF_CmdListerItemArray +df_cmd_lister_item_array_from_list(Arena *arena, DF_CmdListerItemList list) +{ + DF_CmdListerItemArray result = {0}; + result.count = list.count; + result.v = push_array(arena, DF_CmdListerItem, result.count); + U64 idx = 0; + for(DF_CmdListerItemNode *n = list.first; n != 0; n = n->next, idx += 1) + { + result.v[idx] = n->item; + } + return result; +} + +internal void +df_cmd_lister_item_array_sort_by_strength__in_place(DF_CmdListerItemArray array) +{ + quick_sort(array.v, array.count, sizeof(DF_CmdListerItem), df_qsort_compare_cmd_lister__strength); +} + +//////////////////////////////// +//~ rjf: System Process Lister + +internal DF_ProcessInfoList +df_process_info_list_from_query(Arena *arena, String8 query) +{ + Temp scratch = scratch_begin(&arena, 1); + + //- rjf: gather PIDs that we're currently attached to + U64 attached_process_count = 0; + U32 *attached_process_pids = 0; + { + DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); + attached_process_count = processes.count; + attached_process_pids = push_array(scratch.arena, U32, attached_process_count); + U64 idx = 0; + for(DF_EntityNode *n = processes.first; n != 0; n = n->next, idx += 1) + { + DF_Entity *process = n->entity; + attached_process_pids[idx] = process->ctrl_id; + } + } + + //- rjf: build list + DF_ProcessInfoList list = {0}; + { + DMN_ProcessIter iter = {0}; + dmn_process_iter_begin(&iter); + for(DMN_ProcessInfo info = {0}; dmn_process_iter_next(scratch.arena, &iter, &info);) + { + // rjf: skip root-level or otherwise 0-pid processes + if(info.pid == 0) + { + continue; + } + + // rjf: determine if this process is attached + B32 is_attached = 0; + for(U64 attached_idx = 0; attached_idx < attached_process_count; attached_idx += 1) + { + if(attached_process_pids[attached_idx] == info.pid) + { + is_attached = 1; + break; + } + } + + // rjf: gather fuzzy matches + FuzzyMatchRangeList attached_match_ranges = {0}; + FuzzyMatchRangeList name_match_ranges = fuzzy_match_find(arena, query, info.name); + FuzzyMatchRangeList pid_match_ranges = fuzzy_match_find(arena, query, push_str8f(scratch.arena, "%i", info.pid)); + if(is_attached) + { + attached_match_ranges = fuzzy_match_find(arena, query, str8_lit("[attached]")); + } + + // rjf: determine if this item is filtered out + B32 matches_query = (query.size == 0 || + (attached_match_ranges.needle_part_count != 0 && attached_match_ranges.count >= attached_match_ranges.needle_part_count) || + (name_match_ranges.count != 0 && name_match_ranges.count >= name_match_ranges.needle_part_count) || + (pid_match_ranges.count != 0 && pid_match_ranges.count >= pid_match_ranges.needle_part_count)); + + // rjf: push if unfiltered + if(matches_query) + { + DF_ProcessInfoNode *n = push_array(arena, DF_ProcessInfoNode, 1); + n->info.info = info; + n->info.info.name = push_str8_copy(arena, info.name); + n->info.is_attached = is_attached; + n->info.attached_match_ranges = attached_match_ranges; + n->info.name_match_ranges = name_match_ranges; + n->info.pid_match_ranges = pid_match_ranges; + SLLQueuePush(list.first, list.last, n); + list.count += 1; + } + } + dmn_process_iter_end(&iter); + } + + scratch_end(scratch); + return list; +} + +internal DF_ProcessInfoArray +df_process_info_array_from_list(Arena *arena, DF_ProcessInfoList list) +{ + DF_ProcessInfoArray array = {0}; + array.count = list.count; + array.v = push_array(arena, DF_ProcessInfo, array.count); + U64 idx = 0; + for(DF_ProcessInfoNode *n = list.first; n != 0; n = n->next, idx += 1) + { + array.v[idx] = n->info; + } + return array; +} + +internal void +df_process_info_array_sort_by_strength__in_place(DF_ProcessInfoArray array) +{ + quick_sort(array.v, array.count, sizeof(DF_ProcessInfo), df_qsort_compare_process_info); +} + +//////////////////////////////// +//~ rjf: Entity Lister + +internal DF_EntityListerItemList +df_entity_lister_item_list_from_needle(Arena *arena, DF_EntityKind kind, DF_EntityFlags omit_flags, String8 needle) +{ + Temp scratch = scratch_begin(&arena, 1); + DF_EntityListerItemList result = {0}; + DF_EntityList ent_list = df_query_cached_entity_list_with_kind(kind); + for(DF_EntityNode *n = ent_list.first; n != 0; n = n->next) + { + DF_Entity *entity = n->entity; + if(!(entity->flags & omit_flags)) + { + String8 display_string = df_display_string_from_entity(scratch.arena, entity); + FuzzyMatchRangeList match_rngs = fuzzy_match_find(arena, needle, display_string); + if(match_rngs.count != 0 || needle.size == 0) + { + DF_EntityListerItemNode *item_n = push_array(arena, DF_EntityListerItemNode, 1); + item_n->item.entity = entity; + item_n->item.name_match_ranges = match_rngs; + SLLQueuePush(result.first, result.last, item_n); + result.count += 1; + } + } + } + scratch_end(scratch); + return result; +} + +internal DF_EntityListerItemArray +df_entity_lister_item_array_from_list(Arena *arena, DF_EntityListerItemList list) +{ + DF_EntityListerItemArray result = {0}; + result.count = list.count; + result.v = push_array(arena, DF_EntityListerItem, result.count); + { + U64 idx = 0; + for(DF_EntityListerItemNode *n = list.first; n != 0; n = n->next, idx += 1) + { + result.v[idx] = n->item; + } + } + return result; +} + +internal void +df_entity_lister_item_array_sort_by_strength__in_place(DF_EntityListerItemArray array) +{ + quick_sort(array.v, array.count, sizeof(DF_EntityListerItem), df_qsort_compare_entity_lister__strength); +} + +//////////////////////////////// +//~ rjf: Code Views + +internal void +df_code_view_init(DF_CodeViewState *cv, DF_View *view) +{ + if(cv->initialized == 0) + { + cv->initialized = 1; + cv->preferred_column = 1; + cv->find_text_arena = df_view_push_arena_ext(view); + view->cursor = view->mark = txt_pt(1, 1); + } + df_view_equip_loading_info(view, 1, 0, 0); + view->loading_t = view->loading_t_target = 1.f; +} + +internal void +df_code_view_cmds(DF_Window *ws, DF_Panel *panel, DF_View *view, DF_CodeViewState *cv, DF_CmdList *cmds, String8 text_data, TXT_TextInfo *text_info, DASM_InstArray *dasm_insts, Rng1U64 dasm_vaddr_range, DI_Key dasm_dbgi_key) +{ + for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) + { + DF_Cmd *cmd = &n->cmd; + + // rjf: mismatched window/panel => skip + if(df_window_from_handle(cmd->params.window) != ws || + df_panel_from_handle(cmd->params.panel) != panel) + { + continue; + } + + // rjf: process + DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); + switch(core_cmd_kind) + { + default: break; + case DF_CoreCmdKind_GoToLine: + { + cv->goto_line_num = cmd->params.text_point.line; + }break; + case DF_CoreCmdKind_CenterCursor: + { + cv->center_cursor = 1; + }break; + case DF_CoreCmdKind_ContainCursor: + { + cv->contain_cursor = 1; + }break; + case DF_CoreCmdKind_FindTextForward: + { + arena_clear(cv->find_text_arena); + cv->find_text_fwd = push_str8_copy(cv->find_text_arena, cmd->params.string); + }break; + case DF_CoreCmdKind_FindTextBackward: + { + arena_clear(cv->find_text_arena); + cv->find_text_bwd = push_str8_copy(cv->find_text_arena, cmd->params.string); + }break; + case DF_CoreCmdKind_ToggleWatchExpressionAtMouse: + { + cv->watch_expr_at_mouse = 1; + }break; + } + } +} + +internal DF_CodeViewBuildResult +df_code_view_build(Arena *arena, DF_Window *ws, DF_Panel *panel, DF_View *view, DF_CodeViewState *cv, DF_CodeViewBuildFlags flags, Rng2F32 rect, String8 text_data, TXT_TextInfo *text_info, DASM_InstArray *dasm_insts, Rng1U64 dasm_vaddr_range, DI_Key dasm_dbgi_key) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(&arena, 1); + HS_Scope *hs_scope = hs_scope_open(); + DI_Scope *di_scope = di_scope_open(); + TXT_Scope *txt_scope = txt_scope_open(); + + ////////////////////////////// + //- rjf: extract invariants + // + DF_CtrlCtx ctrl_ctx = df_ctrl_ctx_from_view(ws, view); + F_Tag code_font = df_font_from_slot(DF_FontSlot_Code); + F32 code_font_size = df_font_size_from_slot(ws, DF_FontSlot_Code); + F32 code_tab_size = f_column_size_from_tag_size(code_font, code_font_size)*df_setting_val_from_code(ws, DF_SettingCode_TabWidth).s32; + F_Metrics code_font_metrics = f_metrics_from_tag_size(code_font, code_font_size); + F32 code_line_height = ceil_f32(f_line_height_from_metrics(&code_font_metrics) * 1.5f); + F32 big_glyph_advance = f_dim_from_tag_size_string(code_font, code_font_size, 0, 0, str8_lit("H")).x; + Vec2F32 panel_box_dim = dim_2f32(rect); + F32 scroll_bar_dim = floor_f32(ui_top_font_size()*1.5f); + Vec2F32 code_area_dim = v2f32(panel_box_dim.x - scroll_bar_dim, panel_box_dim.y - scroll_bar_dim); + S64 num_possible_visible_lines = (S64)(code_area_dim.y/code_line_height)+1; + + ////////////////////////////// + //- rjf: unpack ctrl ctx & make parse ctx + // + DF_Entity *thread = df_entity_from_handle(ctrl_ctx.thread); + U64 unwind_count = ctrl_ctx.unwind_count; + U64 rip_vaddr = df_query_cached_rip_from_thread_unwind(thread, unwind_count); + DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); + EVAL_ParseCtx parse_ctx = df_eval_parse_ctx_from_process_vaddr(di_scope, process, rip_vaddr); + + ////////////////////////////// + //- rjf: determine visible line range / count + // + Rng1S64 visible_line_num_range = r1s64(view->scroll_pos.y.idx + (S64)(view->scroll_pos.y.off) + 1 - !!(view->scroll_pos.y.off < 0), + view->scroll_pos.y.idx + (S64)(view->scroll_pos.y.off) + 1 + num_possible_visible_lines); + Rng1S64 target_visible_line_num_range = r1s64(view->scroll_pos.y.idx + 1, + view->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 = 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 = 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; + } + + ////////////////////////////// + //- rjf: calculate scroll bounds + // + S64 line_size_x = 0; + Rng1S64 scroll_idx_rng[Axis2_COUNT] = {0}; + { + line_size_x = (text_info->lines_max_size*big_glyph_advance*3)/2; + 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); + } + + ////////////////////////////// + //- rjf: calculate line-range-dependent info + // + F32 line_num_width_px = big_glyph_advance * (log10(visible_line_num_range.max) + 3); + F32 priority_margin_width_px = 0; + F32 catchall_margin_width_px = 0; + if(flags & DF_CodeViewBuildFlag_Margins) + { + priority_margin_width_px = big_glyph_advance*3.5f; + catchall_margin_width_px = 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); + + ////////////////////////////// + //- rjf: get active search query + // + String8 search_query = {0}; + Side search_query_side = Side_Invalid; + B32 search_query_is_active = 0; + { + DF_CoreCmdKind query_cmd_kind = df_core_cmd_kind_from_string(ws->query_cmd_spec->info.string); + if(query_cmd_kind == DF_CoreCmdKind_FindTextForward || + query_cmd_kind == DF_CoreCmdKind_FindTextBackward) + { + search_query = str8(ws->query_view_stack_top->query_buffer, ws->query_view_stack_top->query_string_size); + search_query_is_active = 1; + search_query_side = (query_cmd_kind == DF_CoreCmdKind_FindTextForward) ? Side_Max : Side_Min; + } + } + + ////////////////////////////// + //- rjf: prepare code slice info bundle, for the viewable region of text + // + DF_CodeSliceParams code_slice_params = {0}; + { + // rjf: fill basics + code_slice_params.flags = DF_CodeSliceFlag_LineNums|DF_CodeSliceFlag_Clickable; + if(flags & DF_CodeViewBuildFlag_Margins) + { + code_slice_params.flags |= DF_CodeSliceFlag_PriorityMargin|DF_CodeSliceFlag_CatchallMargin; + } + code_slice_params.line_num_range = visible_line_num_range; + code_slice_params.line_text = push_array(scratch.arena, String8, visible_line_count); + code_slice_params.line_ranges = push_array(scratch.arena, Rng1U64, visible_line_count); + code_slice_params.line_tokens = push_array(scratch.arena, TXT_TokenArray, visible_line_count); + code_slice_params.line_bps = push_array(scratch.arena, DF_EntityList, visible_line_count); + code_slice_params.line_ips = push_array(scratch.arena, DF_EntityList, visible_line_count); + code_slice_params.line_pins = push_array(scratch.arena, DF_EntityList, visible_line_count); + code_slice_params.line_vaddrs = push_array(scratch.arena, U64, visible_line_count); + code_slice_params.line_infos = push_array(scratch.arena, DF_LineList, visible_line_count); + code_slice_params.font = code_font; + code_slice_params.font_size = code_font_size; + code_slice_params.tab_size = code_tab_size; + code_slice_params.line_height_px = code_line_height; + code_slice_params.search_query = search_query; + code_slice_params.priority_margin_width_px = priority_margin_width_px; + code_slice_params.catchall_margin_width_px = catchall_margin_width_px; + code_slice_params.line_num_width_px = line_num_width_px; + code_slice_params.line_text_max_width_px = (F32)line_size_x; + code_slice_params.margin_float_off_px = view->scroll_pos.x.idx + view->scroll_pos.x.off; + + // rjf: fill text info + { + 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; visible_line_idx += 1, line_idx += 1, line_num += 1) + { + code_slice_params.line_text[visible_line_idx] = str8_substr(text_data, text_info->lines_ranges[line_idx]); + code_slice_params.line_ranges[visible_line_idx] = text_info->lines_ranges[line_idx]; + code_slice_params.line_tokens[visible_line_idx] = slice.line_tokens[visible_line_idx]; + } + } + + // rjf: find visible breakpoints for source code + ProfScope("find visible breakpoints") + { + DF_Entity *file = df_entity_from_handle(df_interact_regs()->file); + for(DF_Entity *bp = file->first; !df_entity_is_nil(bp); bp = bp->next) + { + if(bp->deleted || bp->kind != DF_EntityKind_Breakpoint) { continue; } + if(visible_line_num_range.min <= bp->text_point.line && bp->text_point.line <= visible_line_num_range.max) + { + U64 slice_line_idx = (bp->text_point.line-visible_line_num_range.min); + df_entity_list_push(scratch.arena, &code_slice_params.line_bps[slice_line_idx], bp); + } + } + } + + // rjf: find live threads mapping to source code + ProfScope("find live threads mapping to this file") + { + DF_Entity *file = df_entity_from_handle(df_interact_regs()->file); + DF_Entity *selected_thread = df_entity_from_handle(ctrl_ctx.thread); + DF_EntityList threads = df_query_cached_entity_list_with_kind(DF_EntityKind_Thread); + for(DF_EntityNode *thread_n = threads.first; thread_n != 0; thread_n = thread_n->next) + { + DF_Entity *thread = thread_n->entity; + DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); + U64 base_unwind_count = (thread == selected_thread) ? ctrl_ctx.unwind_count : 0; + U64 inline_unwind_count = (thread == selected_thread) ? ctrl_ctx.inline_unwind_count : 0; + U64 rip_vaddr = df_query_cached_rip_from_thread_unwind(thread, unwind_count); + U64 last_inst_on_unwound_rip_vaddr = rip_vaddr - !!unwind_count; + DF_Entity *module = df_module_from_process_vaddr(process, last_inst_on_unwound_rip_vaddr); + U64 rip_voff = df_voff_from_vaddr(module, last_inst_on_unwound_rip_vaddr); + DI_Key dbgi_key = df_dbgi_key_from_module(module); + DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, rip_voff); + for(DF_LineNode *n = lines.first; n != 0; n = n->next) + { + if(df_entity_from_handle(n->v.file) == file && visible_line_num_range.min <= n->v.pt.line && n->v.pt.line <= visible_line_num_range.max) + { + U64 slice_line_idx = n->v.pt.line-visible_line_num_range.min; + df_entity_list_push(scratch.arena, &code_slice_params.line_ips[slice_line_idx], thread); + } + } + } + } + + // rjf: find visible watch pins for source code + ProfScope("find visible watch pins") + { + DF_Entity *file = df_entity_from_handle(df_interact_regs()->file); + for(DF_Entity *wp = file->first; !df_entity_is_nil(wp); wp = wp->next) + { + if(wp->deleted || wp->kind != DF_EntityKind_WatchPin) { continue; } + if(visible_line_num_range.min <= wp->text_point.line && wp->text_point.line <= visible_line_num_range.max) + { + U64 slice_line_idx = (wp->text_point.line-visible_line_num_range.min); + df_entity_list_push(scratch.arena, &code_slice_params.line_pins[slice_line_idx], wp); + } + } + } + + // rjf: find all src -> dasm info + ProfScope("find all src -> dasm info") + { + DF_Entity *file = df_entity_from_handle(df_interact_regs()->file); + DF_LineListArray lines_array = df_lines_array_from_file_line_range(scratch.arena, file, visible_line_num_range); + if(lines_array.count != 0) + { + MemoryCopy(code_slice_params.line_infos, lines_array.v, sizeof(DF_LineList)*lines_array.count); + } + code_slice_params.relevant_dbgi_keys = lines_array.dbgi_keys; + } + + // rjf: find live threads mapping to disasm + if(dasm_insts) ProfScope("find live threads mapping to this disassembly") + { + DF_Entity *selected_thread = df_entity_from_handle(ctrl_ctx.thread); + DF_EntityList threads = df_query_cached_entity_list_with_kind(DF_EntityKind_Thread); + for(DF_EntityNode *thread_n = threads.first; thread_n != 0; thread_n = thread_n->next) + { + DF_Entity *thread = thread_n->entity; + U64 unwind_count = (thread == selected_thread) ? ctrl_ctx.unwind_count : 0; + U64 rip_vaddr = df_query_cached_rip_from_thread_unwind(thread, unwind_count); + if(df_entity_ancestor_from_kind(thread, DF_EntityKind_Process) == process && contains_1u64(dasm_vaddr_range, rip_vaddr)) + { + U64 rip_off = rip_vaddr - dasm_vaddr_range.min; + S64 line_num = dasm_inst_array_idx_from_code_off__linear_scan(dasm_insts, rip_off)+1; + if(contains_1s64(visible_line_num_range, line_num)) + { + U64 slice_line_idx = (line_num-visible_line_num_range.min); + df_entity_list_push(scratch.arena, &code_slice_params.line_ips[slice_line_idx], thread); + } + } + } + } + + // rjf: find breakpoints mapping to this disasm + if(dasm_insts) ProfScope("find breakpoints mapping to this disassembly") + { + DF_EntityList bps = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); + for(DF_EntityNode *n = bps.first; n != 0; n = n->next) + { + DF_Entity *bp = n->entity; + if(bp->flags & DF_EntityFlag_HasVAddr && contains_1u64(dasm_vaddr_range, bp->vaddr)) + { + U64 off = bp->vaddr-dasm_vaddr_range.min; + U64 idx = dasm_inst_array_idx_from_code_off__linear_scan(dasm_insts, off); + S64 line_num = (S64)(idx+1); + if(contains_1s64(visible_line_num_range, line_num)) + { + U64 slice_line_idx = (line_num-visible_line_num_range.min); + df_entity_list_push(scratch.arena, &code_slice_params.line_bps[slice_line_idx], bp); + } + } + } + } + + // rjf: find watch pins mapping to this disasm + if(dasm_insts) ProfScope("find watch pins mapping to this disassembly") + { + DF_EntityList pins = df_query_cached_entity_list_with_kind(DF_EntityKind_WatchPin); + for(DF_EntityNode *n = pins.first; n != 0; n = n->next) + { + DF_Entity *pin = n->entity; + if(pin->flags & DF_EntityFlag_HasVAddr && contains_1u64(dasm_vaddr_range, pin->vaddr)) + { + U64 off = pin->vaddr-dasm_vaddr_range.min; + U64 idx = dasm_inst_array_idx_from_code_off__linear_scan(dasm_insts, off); + S64 line_num = (S64)(idx+1); + if(contains_1s64(visible_line_num_range, line_num)) + { + U64 slice_line_idx = (line_num-visible_line_num_range.min); + df_entity_list_push(scratch.arena, &code_slice_params.line_pins[slice_line_idx], pin); + } + } + } + } + + // rjf: fill dasm -> src info + if(dasm_insts) + { + DF_Entity *module = df_module_from_process_vaddr(process, dasm_vaddr_range.min); + DI_Key dbgi_key = df_dbgi_key_from_module(module); + for(S64 line_num = visible_line_num_range.min; line_num < visible_line_num_range.max; line_num += 1) + { + U64 vaddr = dasm_vaddr_range.min + dasm_inst_array_code_off_from_idx(dasm_insts, line_num-1); + U64 voff = df_voff_from_vaddr(module, vaddr); + U64 slice_idx = line_num-visible_line_num_range.min; + code_slice_params.line_vaddrs[slice_idx] = vaddr; + code_slice_params.line_infos[slice_idx] = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, voff); + } + } + + // rjf: add dasm dbgi key to relevant dbgis + if(dasm_insts != 0) + { + di_key_list_push(scratch.arena, &code_slice_params.relevant_dbgi_keys, &dasm_dbgi_key); + } + } + + ////////////////////////////// + //- rjf: build container + // + UI_Box *container_box = &ui_g_nil_box; + { + ui_set_next_pref_width(ui_px(code_area_dim.x, 1)); + ui_set_next_pref_height(ui_px(code_area_dim.y, 1)); + ui_set_next_child_layout_axis(Axis2_Y); + container_box = ui_build_box_from_stringf(UI_BoxFlag_Clip| + UI_BoxFlag_Scroll| + UI_BoxFlag_AllowOverflowX| + UI_BoxFlag_AllowOverflowY, + "###code_area_%p", view); + } + + ////////////////////////////// + //- rjf: cancelled search query -> center cursor + // + if(!search_query_is_active && cv->drifted_for_search) + { + cv->drifted_for_search = 0; + cv->center_cursor = 1; + } + + ////////////////////////////// + //- rjf: do searching operations + // + { + //- rjf: find text (forward) + if(cv->find_text_fwd.size != 0) + { + Temp scratch = scratch_begin(0, 0); + B32 found = 0; + B32 first = 1; + S64 line_num_start = view->cursor.line; + S64 line_num_last = (S64)text_info->lines_count; + for(S64 line_num = line_num_start;; first = 0) + { + // rjf: pop scratch + temp_end(scratch); + + // rjf: gather line info + String8 line_string = str8_substr(text_data, text_info->lines_ranges[line_num-1]); + U64 search_start = 0; + if(view->cursor.line == line_num && first) + { + search_start = view->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) + { + view->cursor.line = line_num; + view->cursor.column = needle_pos+1; + view->mark = view->cursor; + 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; + } + } + cv->center_cursor = found; + if(found == 0) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.string = push_str8f(scratch.arena, "Could not find \"%S\"", cv->find_text_fwd); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + scratch_end(scratch); + } + + //- rjf: find text (backward) + if(cv->find_text_bwd.size != 0) + { + Temp scratch = scratch_begin(0, 0); + B32 found = 0; + B32 first = 1; + S64 line_num_start = view->cursor.line; + S64 line_num_last = (S64)text_info->lines_count; + for(S64 line_num = line_num_start;; first = 0) + { + // rjf: pop scratch + temp_end(scratch); + + // rjf: gather line info + String8 line_string = str8_substr(text_data, text_info->lines_ranges[line_num-1]); + if(view->cursor.line == line_num && first) + { + line_string = str8_prefix(line_string, view->cursor.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) + { + next_needle_pos = needle_pos; + needle_pos += 1; + } + } + if(next_needle_pos < line_string.size) + { + view->cursor.line = line_num; + view->cursor.column = next_needle_pos+1; + view->mark = view->cursor; + 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; + } + } + cv->center_cursor = found; + if(found == 0) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.string = push_str8f(scratch.arena, "Could not find \"%S\"", cv->find_text_bwd); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + scratch_end(scratch); + } + + MemoryZeroStruct(&cv->find_text_fwd); + MemoryZeroStruct(&cv->find_text_bwd); + arena_clear(cv->find_text_arena); + } + + ////////////////////////////// + //- rjf: do goto line + // + if(cv->goto_line_num != 0) + { + S64 line_num = cv->goto_line_num; + cv->goto_line_num = 0; + line_num = Clamp(1, line_num, text_info->lines_count); + view->cursor = view->mark = txt_pt(line_num, 1); + cv->center_cursor = !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] = df_do_txt_controls(text_info, text_data, ClampBot(num_possible_visible_lines, 10) - 10, &view->cursor, &view->mark, &cv->preferred_column); + } + } + + ////////////////////////////// + //- rjf: build container contents + // + UI_Parent(container_box) + { + //- rjf: build fractional space + container_box->view_off.x = container_box->view_off_target.x = view->scroll_pos.x.idx + view->scroll_pos.x.off; + container_box->view_off.y = container_box->view_off_target.y = code_line_height*mod_f32(view->scroll_pos.y.off, 1.f) + code_line_height*(view->scroll_pos.y.off < 0) - code_line_height*(view->scroll_pos.y.off == -1.f && view->scroll_pos.y.idx == 1); + + //- rjf: build code slice + DF_CodeSliceSignal sig = {0}; + UI_Focus(UI_FocusKind_On) + { + sig = df_code_slicef(ws, &ctrl_ctx, &parse_ctx, &code_slice_params, &view->cursor, &view->mark, &cv->preferred_column, "txt_view_%p", view); + } + + //- rjf: press code slice? -> focus panel + if(ui_pressed(sig.base)) + { + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + + //- rjf: dragging & outside region? -> contain cursor + if(ui_dragging(sig.base) && sig.base.event_flags == 0) + { + if(!contains_2f32(sig.base.box->rect, ui_mouse())) + { + cv->contain_cursor = 1; + } + else + { + snap[Axis2_X] = 1; + } + } + + //- rjf: ctrl+pressed? -> go to name + if(ui_pressed(sig.base) && sig.base.event_flags & OS_EventFlag_Ctrl) + { + ui_kill_action(); + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.string = txt_string_from_info_data_txt_rng(text_info, text_data, sig.mouse_expr_rng); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_GoToName)); + } + + //- rjf: watch expr at mouse + if(cv->watch_expr_at_mouse) + { + cv->watch_expr_at_mouse = 0; + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.string = txt_string_from_info_data_txt_rng(text_info, text_data, sig.mouse_expr_rng); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_ToggleWatchExpression)); + } + + //- rjf: selected text on single line, no query? -> set search text + if(!txt_pt_match(view->cursor, view->mark) && view->cursor.line == view->mark.line && search_query.size == 0) + { + String8 text = txt_string_from_info_data_txt_rng(text_info, text_data, txt_rng(view->cursor, view->mark)); + df_set_search_string(text); + } + } + + ////////////////////////////// + //- rjf: apply post-build view snapping rules + // + { + // rjf: contain => snap + if(cv->contain_cursor) + { + cv->contain_cursor = 0; + snap[Axis2_X] = 1; + snap[Axis2_Y] = 1; + } + + // rjf: center cursor + if(cv->center_cursor) + { + cv->center_cursor = 0; + String8 cursor_line = str8_substr(text_data, text_info->lines_ranges[view->cursor.line-1]); + F32 cursor_advance = f_dim_from_tag_size_string(code_font, code_font_size, 0, code_tab_size, str8_prefix(cursor_line, view->cursor.column-1)).x; + + // rjf: scroll x + { + S64 new_idx = (S64)(cursor_advance - code_area_dim.x/2); + new_idx = Clamp(scroll_idx_rng[Axis2_X].min, new_idx, scroll_idx_rng[Axis2_X].max); + ui_scroll_pt_target_idx(&view->scroll_pos.x, new_idx); + snap[Axis2_X] = 0; + } + + // rjf: scroll y + { + S64 new_idx = (view->cursor.line-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(&view->scroll_pos.y, new_idx); + snap[Axis2_Y] = 0; + } + } + + // rjf: snap in X + if(snap[Axis2_X]) + { + String8 cursor_line = str8_substr(text_data, text_info->lines_ranges[view->cursor.line-1]); + S64 cursor_off = (S64)(f_dim_from_tag_size_string(code_font, code_font_size, 0, code_tab_size, str8_prefix(cursor_line, view->cursor.column-1)).x + priority_margin_width_px + catchall_margin_width_px + line_num_width_px); + Rng1S64 visible_pixel_range = + { + view->scroll_pos.x.idx, + view->scroll_pos.x.idx + (S64)code_area_dim.x, + }; + Rng1S64 cursor_pixel_range = + { + cursor_off - (S64)(big_glyph_advance*4) - (S64)(priority_margin_width_px + catchall_margin_width_px + line_num_width_px), + cursor_off + (S64)(big_glyph_advance*4), + }; + S64 min_delta = Min(0, cursor_pixel_range.min - visible_pixel_range.min); + S64 max_delta = Max(0, cursor_pixel_range.max - visible_pixel_range.max); + S64 new_idx = view->scroll_pos.x.idx+min_delta+max_delta; + new_idx = Clamp(scroll_idx_rng[Axis2_X].min, new_idx, scroll_idx_rng[Axis2_X].max); + ui_scroll_pt_target_idx(&view->scroll_pos.x, new_idx); + } + + // rjf: snap in Y + if(snap[Axis2_Y]) + { + Rng1S64 cursor_visibility_range = r1s64(view->cursor.line-4, view->cursor.line+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 = view->scroll_pos.y.idx+min_delta+max_delta; + new_idx = Clamp(0, new_idx, (S64)text_info->lines_count-1); + ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx); + } + } + + ////////////////////////////// + //- rjf: build horizontal scroll bar + // + { + ui_set_next_fixed_x(0); + ui_set_next_fixed_y(code_area_dim.y); + ui_set_next_fixed_width(panel_box_dim.x - scroll_bar_dim); + ui_set_next_fixed_height(scroll_bar_dim); + { + view->scroll_pos.x = ui_scroll_bar(Axis2_X, + ui_px(scroll_bar_dim, 1.f), + view->scroll_pos.x, + scroll_idx_rng[Axis2_X], + (S64)code_area_dim.x); + } + } + + ////////////////////////////// + //- rjf: build vertical scroll bar + // + { + ui_set_next_fixed_x(code_area_dim.x); + ui_set_next_fixed_y(0); + ui_set_next_fixed_width(scroll_bar_dim); + ui_set_next_fixed_height(panel_box_dim.y - scroll_bar_dim); + { + view->scroll_pos.y = ui_scroll_bar(Axis2_Y, + ui_px(scroll_bar_dim, 1.f), + view->scroll_pos.y, + scroll_idx_rng[Axis2_Y], + num_possible_visible_lines); + } + } + + ////////////////////////////// + //- rjf: top-level container interaction (scrolling) + // + { + UI_Signal sig = ui_signal_from_box(container_box); + if(sig.scroll.x != 0) + { + S64 new_idx = view->scroll_pos.x.idx+sig.scroll.x*big_glyph_advance; + new_idx = clamp_1s64(scroll_idx_rng[Axis2_X], new_idx); + ui_scroll_pt_target_idx(&view->scroll_pos.x, new_idx); + } + if(sig.scroll.y != 0) + { + S64 new_idx = view->scroll_pos.y.idx + sig.scroll.y; + new_idx = clamp_1s64(scroll_idx_rng[Axis2_Y], new_idx); + ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx); + } + ui_scroll_pt_clamp_idx(&view->scroll_pos.x, scroll_idx_rng[Axis2_X]); + ui_scroll_pt_clamp_idx(&view->scroll_pos.y, scroll_idx_rng[Axis2_Y]); + if(ui_mouse_over(sig)) + { + UI_EventList *events = ui_events(); + for(UI_EventNode *n = events->first, *next = 0; n != 0; n = next) + { + next = n->next; + UI_Event *event = &n->v; + if(event->kind == UI_EventKind_Scroll && event->modifiers & OS_EventFlag_Ctrl) + { + ui_eat_event(events, n); + if(event->delta_2f32.y < 0) + { + DF_CmdParams params = df_cmd_params_from_window(ws); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_IncCodeFontScale)); + } + else if(event->delta_2f32.y > 0) + { + DF_CmdParams params = df_cmd_params_from_window(ws); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_DecCodeFontScale)); + } + } + } + } + } + + ////////////////////////////// + //- rjf: build result + // + DF_CodeViewBuildResult result = {0}; + { + for(DI_KeyNode *n = code_slice_params.relevant_dbgi_keys.first; n != 0; n = n->next) + { + DI_Key copy = di_key_copy(arena, &n->v); + di_key_list_push(arena, &result.dbgi_keys, ©); + } + } + + txt_scope_close(txt_scope); + di_scope_close(di_scope); + hs_scope_close(hs_scope); + scratch_end(scratch); + ProfEnd(); + return result; +} + +//////////////////////////////// +//~ rjf: Watch Views + +//- rjf: eval watch view instance -> eval view key + +internal DF_EvalViewKey +df_eval_view_key_from_eval_watch_view(DF_WatchViewState *ewv) +{ + DF_EvalViewKey key = df_eval_view_key_make((U64)ewv, df_hash_from_string(str8_struct(&ewv))); + return key; +} + +//- rjf: root allocation/deallocation/mutation + +internal DF_EvalRoot * +df_eval_root_alloc(DF_View *view, DF_WatchViewState *ews) +{ + DF_EvalRoot *result = ews->first_free_root; + if(result != 0) + { + SLLStackPop(ews->first_free_root); + result->expr_buffer_string_size = 0; + } + else + { + result = push_array(view->arena, DF_EvalRoot, 1); + result->expr_buffer_cap = 1024; + result->expr_buffer = push_array_no_zero(view->arena, U8, result->expr_buffer_cap); + } + DLLPushBack(ews->first_root, ews->last_root, result); + ews->root_count += 1; + return result; +} + +internal void +df_eval_root_release(DF_WatchViewState *ews, DF_EvalRoot *root) +{ + DLLRemove(ews->first_root, ews->last_root, root); + SLLStackPush(ews->first_free_root, root); + ews->root_count -= 1; +} + +internal void +df_eval_root_equip_string(DF_EvalRoot *root, String8 string) +{ + root->expr_buffer_string_size = Min(string.size, root->expr_buffer_cap); + MemoryCopy(root->expr_buffer, string.str, root->expr_buffer_string_size); +} + +internal DF_EvalRoot * +df_eval_root_from_string(DF_WatchViewState *ews, String8 string) +{ + DF_EvalRoot *root = 0; + for(DF_EvalRoot *r = ews->first_root; r != 0; r = r->next) + { + String8 r_string = df_string_from_eval_root(r); + if(str8_match(r_string, string, 0)) + { + root = r; + break; + } + } + return root; +} + +internal DF_EvalRoot * +df_eval_root_from_expand_key(DF_WatchViewState *ews, DF_EvalView *eval_view, DF_ExpandKey expand_key) +{ + DF_EvalRoot *root = 0; + for(DF_EvalRoot *r = ews->first_root; r != 0; r = r->next) + { + DF_ExpandKey key = df_expand_key_from_eval_root(r); + if(df_expand_key_match(key, expand_key)) + { + root = r; + break; + } + } + return root; +} + +internal String8 +df_string_from_eval_root(DF_EvalRoot *root) +{ + String8 string = str8(root->expr_buffer, root->expr_buffer_string_size); + return string; +} + +internal DF_ExpandKey +df_parent_expand_key_from_eval_root(DF_EvalRoot *root) +{ + DF_ExpandKey parent_key = df_expand_key_make(5381, (U64)root); + return parent_key; +} + +internal DF_ExpandKey +df_expand_key_from_eval_root(DF_EvalRoot *root) +{ + DF_ExpandKey parent_key = df_parent_expand_key_from_eval_root(root); + DF_ExpandKey key = df_expand_key_make(df_hash_from_expand_key(parent_key), (U64)root); + return key; +} + +//- rjf: watch view points <-> table coordinates + +internal B32 +df_watch_view_point_match(DF_WatchViewPoint a, DF_WatchViewPoint b) +{ + return (a.column_kind == b.column_kind && + df_expand_key_match(a.parent_key, b.parent_key) && + df_expand_key_match(a.key, b.key)); +} + +internal DF_WatchViewPoint +df_watch_view_point_from_tbl(DF_EvalVizBlockList *blocks, Vec2S64 tbl) +{ + DF_WatchViewPoint pt = zero_struct; + pt.column_kind = (DF_WatchViewColumnKind)(tbl.x%DF_WatchViewColumnKind_COUNT); + pt.key = df_key_from_viz_block_list_row_num(blocks, tbl.y); + pt.parent_key = df_parent_key_from_viz_block_list_row_num(blocks, tbl.y); + return pt; +} + +internal Vec2S64 +df_tbl_from_watch_view_point(DF_EvalVizBlockList *blocks, DF_WatchViewPoint pt) +{ + Vec2S64 tbl = {0}; + tbl.x = (S64)pt.column_kind; + tbl.y = df_row_num_from_viz_block_list_key(blocks, pt.key); + return tbl; +} + +//- rjf: table coordinates -> strings + +internal String8 +df_string_from_eval_viz_row_column_kind(Arena *arena, DF_EvalView *ev, TG_Graph *graph, RDI_Parsed *rdi, DF_EvalVizRow *row, DF_WatchViewColumnKind col_kind, B32 editable) +{ + String8 result = {0}; + switch(col_kind) + { + default:{}break; + case DF_WatchViewColumnKind_Expr: {result = editable ? row->edit_expr : row->display_expr;}break; + case DF_WatchViewColumnKind_Value: {result = editable ? row->edit_value : row->display_value;}break; + case DF_WatchViewColumnKind_Type: {result = !tg_key_match(row->eval.type_key, tg_key_zero()) ? tg_string_from_key(arena, graph, rdi, row->eval.type_key) : str8_zero();}break; + case DF_WatchViewColumnKind_ViewRule:{result = df_eval_view_rule_from_key(ev, row->key);}break; + } + return result; +} + +//- rjf: table coordinates -> text edit state + +internal DF_WatchViewTextEditState * +df_watch_view_text_edit_state_from_pt(DF_WatchViewState *wv, DF_WatchViewPoint pt) +{ + DF_WatchViewTextEditState *result = &wv->dummy_text_edit_state; + if(wv->text_edit_state_slots_count != 0 && wv->text_editing != 0) + { + U64 hash = df_hash_from_expand_key(pt.key); + U64 slot_idx = hash%wv->text_edit_state_slots_count; + for(DF_WatchViewTextEditState *s = wv->text_edit_state_slots[slot_idx]; s != 0; s = s->pt_hash_next) + { + if(df_watch_view_point_match(pt, s->pt)) + { + result = s; + break; + } + } + } + return result; +} + +//- rjf: windowed watch tree visualization (both single-line and multi-line) + +internal DF_EvalVizBlockList +df_eval_viz_block_list_from_watch_view_state(Arena *arena, DI_Scope *di_scope, FZY_Scope *fzy_scope, DF_CtrlCtx *ctrl_ctx, EVAL_ParseCtx *parse_ctx, EVAL_String2ExprMap *macro_map, DF_View *view, DF_WatchViewState *ews) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(&arena, 1); + DF_EvalVizBlockList blocks = {0}; + DF_EvalViewKey eval_view_key = df_eval_view_key_from_eval_watch_view(ews); + DF_EvalView *eval_view = df_eval_view_from_key(eval_view_key); + String8 filter = str8(view->query_buffer, view->query_string_size); + FZY_Target fzy_target = FZY_Target_UDTs; + switch(ews->fill_kind) + { + //////////////////////////// + //- rjf: mutable watch fill -> build blocks from top-level mutable root expressions + // + default: + case DF_WatchViewFillKind_Mutable: + { + for(DF_EvalRoot *root = ews->first_root; root != 0; root = root->next) + { + String8 root_expr_string = df_string_from_eval_root(root); + FuzzyMatchRangeList matches = fuzzy_match_find(arena, filter, root_expr_string); + if(matches.count == matches.needle_part_count) + { + DF_ExpandKey parent_key = df_parent_expand_key_from_eval_root(root); + DF_ExpandKey key = df_expand_key_from_eval_root(root); + DF_EvalVizBlockList root_blocks = df_eval_viz_block_list_from_eval_view_expr_keys(arena, di_scope, ctrl_ctx, parse_ctx, macro_map, eval_view, root_expr_string, parent_key, key); + df_eval_viz_block_list_concat__in_place(&blocks, &root_blocks); + } + } + }break; + + //////////////////////////// + //- rjf: registers fill -> build blocks via iterating all registers/aliases as root-level expressions + // + case DF_WatchViewFillKind_Registers: + { + DF_Entity *thread = df_entity_from_handle(ctrl_ctx->thread); + Architecture arch = df_architecture_from_entity(thread); + U64 reg_count = regs_reg_code_count_from_architecture(arch); + String8 *reg_strings = regs_reg_code_string_table_from_architecture(arch); + U64 alias_count = regs_alias_code_count_from_architecture(arch); + String8 *alias_strings = regs_alias_code_string_table_from_architecture(arch); + U64 num = 1; + for(U64 reg_idx = 1; reg_idx < reg_count; reg_idx += 1, num += 1) + { + String8 root_expr_string = reg_strings[reg_idx]; + FuzzyMatchRangeList matches = fuzzy_match_find(arena, filter, root_expr_string); + if(matches.count == matches.needle_part_count) + { + DF_ExpandKey parent_key = df_expand_key_make(5381, 0); + DF_ExpandKey key = df_expand_key_make(df_hash_from_expand_key(parent_key), num); + DF_EvalVizBlockList root_blocks = df_eval_viz_block_list_from_eval_view_expr_keys(arena, di_scope, ctrl_ctx, parse_ctx, macro_map, eval_view, root_expr_string, parent_key, key); + df_eval_viz_block_list_concat__in_place(&blocks, &root_blocks); + } + } + for(U64 alias_idx = 1; alias_idx < alias_count; alias_idx += 1, num += 1) + { + String8 root_expr_string = alias_strings[alias_idx]; + FuzzyMatchRangeList matches = fuzzy_match_find(arena, filter, root_expr_string); + if(matches.count == matches.needle_part_count) + { + DF_ExpandKey parent_key = df_expand_key_make(5381, 0); + DF_ExpandKey key = df_expand_key_make(df_hash_from_expand_key(parent_key), num); + DF_EvalVizBlockList root_blocks = df_eval_viz_block_list_from_eval_view_expr_keys(arena, di_scope, ctrl_ctx, parse_ctx, macro_map, eval_view, root_expr_string, parent_key, key); + df_eval_viz_block_list_concat__in_place(&blocks, &root_blocks); + } + } + }break; + + //////////////////////////// + //- rjf: locals fill -> build blocks via iterating all locals as root-level expressions + // + case DF_WatchViewFillKind_Locals: + { + EVAL_String2NumMapNodeArray nodes = eval_string2num_map_node_array_from_map(scratch.arena, parse_ctx->locals_map); + eval_string2num_map_node_array_sort__in_place(&nodes); + for(U64 idx = 0; idx < nodes.count; idx += 1) + { + EVAL_String2NumMapNode *n = nodes.v[idx]; + String8 root_expr_string = n->string; + FuzzyMatchRangeList matches = fuzzy_match_find(arena, filter, root_expr_string); + if(matches.count == matches.needle_part_count) + { + DF_ExpandKey parent_key = df_expand_key_make(5381, 0); + DF_ExpandKey key = df_expand_key_make(df_hash_from_expand_key(parent_key), idx+1); + DF_EvalVizBlockList root_blocks = df_eval_viz_block_list_from_eval_view_expr_keys(arena, di_scope, ctrl_ctx, parse_ctx, macro_map, eval_view, root_expr_string, parent_key, key); + df_eval_viz_block_list_concat__in_place(&blocks, &root_blocks); + } + } + }break; + + //////////////////////////// + //- rjf: debug info table fill -> build split debug info table blocks + // + case DF_WatchViewFillKind_Globals: fzy_target = FZY_Target_GlobalVariables; goto dbgi_table; + case DF_WatchViewFillKind_ThreadLocals: fzy_target = FZY_Target_ThreadVariables; goto dbgi_table; + case DF_WatchViewFillKind_Types: fzy_target = FZY_Target_UDTs; goto dbgi_table; + case DF_WatchViewFillKind_Procedures: fzy_target = FZY_Target_Procedures; goto dbgi_table; + dbgi_table:; + { + //- rjf: unpack context + DF_Entity *thread = df_entity_from_handle(ctrl_ctx->thread); + DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); + U64 thread_rip_unwind_vaddr = df_query_cached_rip_from_thread_unwind(thread, ctrl_ctx->unwind_count); + DF_Entity *module = df_module_from_process_vaddr(process, thread_rip_unwind_vaddr); + DI_Key dbgi_key = df_dbgi_key_from_module(module); + + //- rjf: calculate top-level keys, expand root-level, grab root expansion node + DF_ExpandKey parent_key = df_expand_key_make(5381, 0); + DF_ExpandKey root_key = df_expand_key_make(df_hash_from_expand_key(parent_key), 0); + df_expand_set_expansion(eval_view->arena, &eval_view->expand_tree_table, df_expand_key_zero(), parent_key, 1); + DF_ExpandNode *root_node = df_expand_node_from_key(&eval_view->expand_tree_table, parent_key); + + //- rjf: query all filtered items from dbgi searching system + U128 fuzzy_search_key = {(U64)view, df_hash_from_string(str8_struct(&view))}; + B32 items_stale = 0; + FZY_Params params = {fzy_target}; + { + params.dbgi_keys.count = 1; + params.dbgi_keys.v = &dbgi_key; + } + FZY_ItemArray items = fzy_items_from_key_params_query(fzy_scope, fuzzy_search_key, ¶ms, filter, os_now_microseconds()+100, &items_stale); + if(items_stale) + { + df_gfx_request_frame(); + } + + //- rjf: gather unsorted child expansion keys + // + // Nodes are sorted in the underlying expansion tree data structure, but + // ONLY by THEIR ORDER IN THE UNDERLYING DEBUG INFO TABLE. This is + // because debug info watch rows use the DEBUG INFO TABLE INDEX to form + // their key - this provides more stable/predictable behavior as rows + // are reordered, filtered, and shuffled around, as the user filters. + // + // When we actually build viz blocks, however, we want to produce viz + // blocks BY THE ORDER OF SUB-EXPANSIONS IN THE FILTERED ITEM ARRAY + // SPACE, so that all of the expansions come out in the right order. + // + DF_ExpandKey *sub_expand_keys = 0; + U64 *sub_expand_item_idxs = 0; + U64 sub_expand_keys_count = 0; + { + for(DF_ExpandNode *child = root_node->first; child != 0; child = child->next) + { + sub_expand_keys_count += 1; + } + sub_expand_keys = push_array(scratch.arena, DF_ExpandKey, sub_expand_keys_count); + sub_expand_item_idxs = push_array(scratch.arena, U64, sub_expand_keys_count); + U64 idx = 0; + for(DF_ExpandNode *child = root_node->first; child != 0; child = child->next) + { + U64 item_num = fzy_item_num_from_array_element_idx__linear_search(&items, child->key.child_num); + if(item_num != 0) + { + sub_expand_keys[idx] = child->key; + sub_expand_item_idxs[idx] = item_num-1; + idx += 1; + } + else + { + sub_expand_keys_count -= 1; + } + } + } + + //- rjf: sort child expansion keys + { + for(U64 idx1 = 0; idx1 < sub_expand_keys_count; idx1 += 1) + { + U64 min_idx2 = 0; + U64 min_item_idx = sub_expand_item_idxs[idx1]; + for(U64 idx2 = idx1+1; idx2 < sub_expand_keys_count; idx2 += 1) + { + if(sub_expand_item_idxs[idx2] < min_item_idx) + { + min_idx2 = idx2; + min_item_idx = sub_expand_item_idxs[idx2]; + } + } + if(min_idx2 != 0) + { + Swap(DF_ExpandKey, sub_expand_keys[idx1], sub_expand_keys[min_idx2]); + Swap(U64, sub_expand_item_idxs[idx1], sub_expand_item_idxs[min_idx2]); + } + } + } + + //- rjf: build blocks for all table items, split by sorted sub-expansions + DF_EvalVizBlock *last_vb = df_eval_viz_block_begin(arena, DF_EvalVizBlockKind_DebugInfoTable, parent_key, root_key, 0); + { + last_vb->visual_idx_range = last_vb->semantic_idx_range = r1u64(0, items.count); + last_vb->fzy_target = fzy_target; + last_vb->fzy_backing_items = items; + } + for(U64 sub_expand_idx = 0; sub_expand_idx < sub_expand_keys_count; sub_expand_idx += 1) + { + // rjf: form split: truncate & complete last block; begin next block + last_vb = df_eval_viz_block_split_and_continue(arena, &blocks, last_vb, sub_expand_item_idxs[sub_expand_idx]); + + // rjf: grab name for the expanded row + String8 name = fzy_item_string_from_rdi_target_element_idx(parse_ctx->rdi, fzy_target, sub_expand_keys[sub_expand_idx].child_num); + + // rjf: recurse for sub-expansion + { + DF_CfgTable child_cfg = {0}; + { + String8 view_rule_string = df_eval_view_rule_from_key(eval_view, df_expand_key_make(df_hash_from_expand_key(parent_key), sub_expand_keys[sub_expand_idx].child_num)); + if(view_rule_string.size != 0) + { + df_cfg_table_push_unparsed_string(arena, &child_cfg, view_rule_string, DF_CfgSrc_User); + } + } + DF_Eval eval = df_eval_from_string(arena, di_scope, ctrl_ctx, parse_ctx, macro_map, name); + df_append_viz_blocks_for_parent__rec(arena, di_scope, eval_view, ctrl_ctx, parse_ctx, macro_map, parent_key, sub_expand_keys[sub_expand_idx], name, eval, 0, &child_cfg, 0, &blocks); + } + } + df_eval_viz_block_end(&blocks, last_vb); + }break; + } + scratch_end(scratch); + ProfEnd(); + return blocks; +} + +//- rjf: eval/watch views main hooks + +internal void +df_watch_view_init(DF_WatchViewState *ewv, DF_View *view, DF_WatchViewFillKind fill_kind) +{ + if(ewv->initialized == 0) + { + ewv->initialized = 1; + ewv->expr_column_pct = 0.25f; + ewv->value_column_pct = 0.3f; + ewv->type_column_pct = 0.15f; + ewv->view_rule_column_pct = 0.30f; + ewv->fill_kind = fill_kind; + ewv->text_edit_arena = df_view_push_arena_ext(view); + } +} + +internal void +df_watch_view_cmds(DF_Window *ws, DF_Panel *panel, DF_View *view, DF_WatchViewState *ewv, DF_CmdList *cmds) +{ + for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) + { + DF_Cmd *cmd = &n->cmd; + DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); + + // rjf: process + switch(core_cmd_kind) + { + default:break; + + //- rjf: watch expression toggling + case DF_CoreCmdKind_ToggleWatchExpression: + if(cmd->params.string.size != 0) + { + DF_EvalRoot *already_existing_root = df_eval_root_from_string(ewv, cmd->params.string); + if(already_existing_root != 0) + { + df_eval_root_release(ewv, already_existing_root); + } + else + { + DF_EvalRoot *root = df_eval_root_alloc(view, ewv); + df_eval_root_equip_string(root, cmd->params.string); + } + }break; + } + } +} + +internal void +df_watch_view_build(DF_Window *ws, DF_Panel *panel, DF_View *view, DF_WatchViewState *ewv, B32 modifiable, U32 default_radix, Rng2F32 rect) +{ + ProfBeginFunction(); + DI_Scope *di_scope = di_scope_open(); + FZY_Scope *fzy_scope = fzy_scope_open(); + Temp scratch = scratch_begin(0, 0); + + ////////////////////////////// + //- rjf: unpack arguments + // + F_Tag code_font = df_font_from_slot(DF_FontSlot_Code); + DF_CtrlCtx ctrl_ctx = df_ctrl_ctx_from_view(ws, view); + DF_Entity *thread = df_entity_from_handle(ctrl_ctx.thread); + DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); + U64 thread_ip_vaddr = df_query_cached_rip_from_thread_unwind(thread, ctrl_ctx.unwind_count); + DF_EvalViewKey eval_view_key = df_eval_view_key_from_eval_watch_view(ewv); + DF_EvalView *eval_view = df_eval_view_from_key(eval_view_key); + String8 filter = str8(view->query_buffer, view->query_string_size); + F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); + S64 num_possible_visible_rows = (S64)(dim_2f32(rect).y/row_height_px); + + ////////////////////////////// + //- rjf: process * thread info -> parse_ctx + // + EVAL_ParseCtx parse_ctx = df_eval_parse_ctx_from_process_vaddr(di_scope, process, thread_ip_vaddr); + + ////////////////////////////// + //- rjf: determine autocompletion string + // + String8 autocomplete_hint_string = {0}; + { + UI_EventList *events = ui_events(); + for(UI_EventNode *n = events->first; n != 0; n = n->next) + { + if(n->v.kind == UI_EventKind_AutocompleteHint) + { + autocomplete_hint_string = n->v.string; + break; + } + } + } + + ////////////////////////////// + //- rjf: consume events & perform navigations/edits - calculate state + // + EVAL_String2ExprMap macro_map = {0}; + DF_EvalVizBlockList blocks = {0}; + UI_ScrollListRowBlockArray row_blocks = {0}; + Vec2S64 cursor_tbl = {0}; + Vec2S64 mark_tbl = {0}; + Rng2S64 selection_tbl = {0}; + UI_Focus(UI_FocusKind_On) + { + UI_EventList *events = ui_events(); + B32 state_dirty = 1; + B32 cursor_dirty__tbl = 0; + B32 take_autocomplete = 0; + for(UI_EventNode *event_n = events->first, *next = 0;; event_n = next) + { + ////////////////////////// + //- rjf: state -> macro map + // + if(state_dirty) + { + macro_map = eval_string2expr_map_make(scratch.arena, 256); + for(DF_EvalRoot *root = ewv->first_root; root != 0; root = root->next) + { + String8 root_expr = str8(root->expr_buffer, root->expr_buffer_string_size); + + //- rjf: unpack arguments + DF_Entity *process = thread->parent; + U64 unwind_count = ctrl_ctx.unwind_count; + CTRL_Unwind unwind = df_query_cached_unwind_from_thread(thread); + Architecture arch = df_architecture_from_entity(thread); + U64 reg_size = regs_block_size_from_architecture(arch); + void *thread_unwind_regs_block = push_array(scratch.arena, U8, reg_size); + U64 thread_unwind_ip_vaddr = 0; + if(unwind.frames.count != 0) + { + thread_unwind_regs_block = unwind.frames.v[unwind_count%unwind.frames.count].regs; + thread_unwind_ip_vaddr = regs_rip_from_arch_block(arch, thread_unwind_regs_block); + } + + //- rjf: lex & parse + EVAL_TokenArray tokens = eval_token_array_from_text(scratch.arena, root_expr); + EVAL_ParseResult parse = eval_parse_expr_from_text_tokens(scratch.arena, &parse_ctx, root_expr, &tokens); + EVAL_ErrorList errors = parse.errors; + if(errors.count == 0) + { + eval_push_leaf_ident_exprs_from_expr__in_place(scratch.arena, ¯o_map, parse.expr, &errors); + } + } + } + + ////////////////////////// + //- rjf: state -> viz blocks + // + if(state_dirty) + { + blocks = df_eval_viz_block_list_from_watch_view_state(scratch.arena, di_scope, fzy_scope, &ctrl_ctx, &parse_ctx, ¯o_map, view, ewv); + } + + ////////////////////////// + //- rjf: does this eval watch view allow mutation? -> add extra block for editable empty row + // + DF_ExpandKey empty_row_parent_key = df_expand_key_make(max_U64, max_U64); + DF_ExpandKey empty_row_key = df_expand_key_make(df_hash_from_expand_key(empty_row_parent_key), 1); + if(state_dirty && modifiable) + { + DF_EvalVizBlock *b = df_eval_viz_block_begin(scratch.arena, DF_EvalVizBlockKind_Null, empty_row_parent_key, empty_row_key, 0); + b->visual_idx_range = b->semantic_idx_range = r1u64(0, 1); + df_eval_viz_block_end(&blocks, b); + } + + ////////////////////////// + //- rjf: viz blocks -> ui row blocks + // + { + UI_ScrollListRowBlockChunkList row_block_chunks = {0}; + for(DF_EvalVizBlockNode *n = blocks.first; n != 0; n = n->next) + { + DF_EvalVizBlock *vb = &n->v; + UI_ScrollListRowBlock block = {0}; + block.row_count = dim_1u64(vb->visual_idx_range); + block.item_count = dim_1u64(vb->semantic_idx_range); + ui_scroll_list_row_block_chunk_list_push(scratch.arena, &row_block_chunks, 256, &block); + } + row_blocks = ui_scroll_list_row_block_array_from_chunk_list(scratch.arena, &row_block_chunks); + } + + ////////////////////////// + //- rjf: conclude state update + // + if(state_dirty) + { + state_dirty = 0; + } + + ////////////////////////////// + //- rjf: 2D table coordinates * blocks -> stable cursor state + // + if(cursor_dirty__tbl) + { + cursor_dirty__tbl = 0; + struct + { + DF_WatchViewPoint *pt_state; + Vec2S64 pt_tbl; + } + points[] = + { + {&ewv->cursor, cursor_tbl}, + {&ewv->mark, mark_tbl}, + }; + for(U64 point_idx = 0; point_idx < ArrayCount(points); point_idx += 1) + { + DF_ExpandKey last_key = points[point_idx].pt_state->key; + DF_ExpandKey last_parent_key = points[point_idx].pt_state->parent_key; + points[point_idx].pt_state[0] = df_watch_view_point_from_tbl(&blocks, points[point_idx].pt_tbl); + if(df_expand_key_match(df_expand_key_zero(), points[point_idx].pt_state->key)) + { + points[point_idx].pt_state->key = last_parent_key; + DF_ExpandNode *node = df_expand_node_from_key(&eval_view->expand_tree_table, last_parent_key); + for(DF_ExpandNode *n = node; n != 0; n = n->parent) + { + points[point_idx].pt_state->key = n->key; + if(n->expanded == 0) + { + break; + } + } + } + if(point_idx == 0 && + (!df_expand_key_match(ewv->cursor.key, last_key) || + !df_expand_key_match(ewv->cursor.parent_key, last_parent_key))) + { + ewv->text_editing = 0; + } + } + ewv->next_cursor = ewv->cursor; + ewv->next_mark = ewv->mark; + } + + ////////////////////////// + //- rjf: stable cursor state * blocks -> 2D table coordinates + // + { + cursor_tbl = df_tbl_from_watch_view_point(&blocks, ewv->cursor); + mark_tbl = df_tbl_from_watch_view_point(&blocks, ewv->mark); + selection_tbl = r2s64p(Min(cursor_tbl.x, mark_tbl.x), Min(cursor_tbl.y, mark_tbl.y), + Max(cursor_tbl.x, mark_tbl.x), Max(cursor_tbl.y, mark_tbl.y)); + } + + ////////////////////////////// + //- rjf: apply cursor/mark rugpull change + // + B32 cursor_rugpull = 0; + if(!df_watch_view_point_match(ewv->cursor, ewv->next_cursor)) + { + cursor_rugpull = 1; + ewv->cursor = ewv->next_cursor; + ewv->mark = ewv->next_mark; + } + + ////////////////////////// + //- rjf: grab next event, if any - otherwise exit the loop, as we now have + // the most up-to-date state + // + if(!cursor_rugpull && (event_n == 0 || !ui_is_focus_active())) + { + break; + } + UI_Event dummy_evt = zero_struct; + UI_Event *evt = &dummy_evt; + if(event_n != 0) + { + evt = &event_n->v; + next = event_n->next; + } + B32 taken = 0; + + ////////////////////////// + //- rjf: begin editing on some operations + // + if(!ewv->text_editing && + (evt->kind == UI_EventKind_Text || + evt->flags & UI_EventFlag_Paste || + (evt->kind == UI_EventKind_Press && evt->slot == UI_EventActionSlot_Edit)) && + selection_tbl.min.x == selection_tbl.max.x && + (selection_tbl.min.x != 0 || modifiable)) + { + Vec2S64 selection_dim = dim_2s64(selection_tbl); + ewv->text_editing = 1; + arena_clear(ewv->text_edit_arena); + ewv->text_edit_state_slots_count = u64_up_to_pow2(selection_dim.y+1); + ewv->text_edit_state_slots_count = Max(ewv->text_edit_state_slots_count, 64); + ewv->text_edit_state_slots = push_array(ewv->text_edit_arena, DF_WatchViewTextEditState*, ewv->text_edit_state_slots_count); + DF_EvalVizWindowedRowList rows = df_eval_viz_windowed_row_list_from_viz_block_list(scratch.arena, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, eval_view, default_radix, code_font, ui_top_font_size(), + r1s64(ui_scroll_list_row_from_item(&row_blocks, selection_tbl.min.y-1), + ui_scroll_list_row_from_item(&row_blocks, selection_tbl.max.y-1)+1), &blocks); + DF_EvalVizRow *row = rows.first; + for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y; y += 1, row = row->next) + { + for(S64 x = selection_tbl.min.x; x <= selection_tbl.max.x; x += 1) + { + String8 string = df_string_from_eval_viz_row_column_kind(scratch.arena, eval_view, parse_ctx.type_graph, parse_ctx.rdi, row, (DF_WatchViewColumnKind)x, 1); + string.size = Min(string.size, sizeof(ewv->dummy_text_edit_state.input_buffer)); + DF_WatchViewPoint pt = {(DF_WatchViewColumnKind)x, row->parent_key, row->key}; + U64 hash = df_hash_from_expand_key(pt.key); + U64 slot_idx = hash%ewv->text_edit_state_slots_count; + DF_WatchViewTextEditState *edit_state = push_array(ewv->text_edit_arena, DF_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->input_size = string.size; + MemoryCopy(edit_state->input_buffer, string.str, string.size); + edit_state->initial_size = string.size; + MemoryCopy(edit_state->initial_buffer, string.str, string.size); + } + } + } + + ////////////////////////// + //- rjf: [table] do cell-granularity expansions + // + if(!ewv->text_editing && evt->slot == UI_EventActionSlot_Accept && selection_tbl.min.x <= 0) + { + taken = 1; + DF_EvalVizWindowedRowList rows = df_eval_viz_windowed_row_list_from_viz_block_list(scratch.arena, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, eval_view, default_radix, code_font, ui_top_font_size(), + r1s64(ui_scroll_list_row_from_item(&row_blocks, selection_tbl.min.y-1), + ui_scroll_list_row_from_item(&row_blocks, selection_tbl.max.y-1)+1), &blocks); + DF_EvalVizRow *row = rows.first; + for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y && row != 0; y += 1, row = row->next) + { + if(row->flags & DF_EvalVizRowFlag_CanExpand) + { + B32 is_expanded = df_expand_key_is_set(&eval_view->expand_tree_table, row->key); + df_expand_set_expansion(eval_view->arena, &eval_view->expand_tree_table, row->parent_key, row->key, !is_expanded); + } + if(row->flags & DF_EvalVizRowFlag_Canvas) + { + DF_CfgNode *cfg = df_cfg_tree_copy(scratch.arena, row->expand_ui_rule_node); + DF_CfgNode *cfg_root = push_array(scratch.arena, DF_CfgNode, 1); + cfg_root->first = cfg_root->last = cfg; + cfg_root->next = cfg_root->parent = &df_g_nil_cfg_node; + if(cfg != &df_g_nil_cfg_node) + { + cfg->parent = cfg_root; + } + DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); + p.string = row->edit_expr; + p.view_spec = df_tab_view_spec_from_gfx_view_rule_spec(row->expand_ui_rule_spec); + p.cfg_node = cfg_root; + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_ViewSpec); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_CfgNode); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_OpenTab)); + } + } + } + + ////////////////////////// + //- rjf: [table] do cell-granularity go-to-locations + // + if(!ewv->text_editing && evt->slot == UI_EventActionSlot_Accept && + selection_tbl.min.x == selection_tbl.max.x && + selection_tbl.min.y == selection_tbl.max.y && + selection_tbl.min.x == 1) + { + taken = 1; + DF_EvalVizWindowedRowList rows = df_eval_viz_windowed_row_list_from_viz_block_list(scratch.arena, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, eval_view, default_radix, code_font, ui_top_font_size(), + r1s64(ui_scroll_list_row_from_item(&row_blocks, selection_tbl.min.y-1), + ui_scroll_list_row_from_item(&row_blocks, selection_tbl.max.y-1)+1), &blocks); + DF_EvalVizRow *row = rows.first; + if(!(row->flags & DF_EvalVizRowFlag_CanEditValue)) + { + U64 vaddr = 0; + if(vaddr == 0) { vaddr = row->eval.offset; } + if(vaddr == 0) { vaddr = row->eval.imm_u64; } + DF_Entity *module = df_module_from_process_vaddr(process, vaddr); + DI_Key dbgi_key = df_dbgi_key_from_module(module); + U64 voff = df_voff_from_vaddr(module, vaddr); + DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, voff); + DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); + p.entity = df_handle_from_entity(process); + p.vaddr = vaddr; + if(lines.first != 0) + { + p.file_path = df_full_path_from_entity(scratch.arena, df_entity_from_handle(lines.first->v.file)); + p.text_point = lines.first->v.pt; + } + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FindCodeLocation)); + } + } + + ////////////////////////// + //- rjf: [text] apply textual edits + // + if(ewv->text_editing) + { + B32 editing_complete = ((evt->kind == UI_EventKind_Press && (evt->slot == UI_EventActionSlot_Cancel || evt->slot == UI_EventActionSlot_Accept)) || + (evt->kind == UI_EventKind_Navigate && evt->delta_2s32.y != 0) || + cursor_rugpull); + if(editing_complete || + ((evt->kind == UI_EventKind_Edit || + evt->kind == UI_EventKind_Navigate || + evt->kind == UI_EventKind_Text) && + evt->delta_2s32.y == 0)) + { + taken = 1; + for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y; y += 1) + { + for(S64 x = selection_tbl.min.x; x <= selection_tbl.max.x; x += 1) + { + DF_WatchViewPoint pt = df_watch_view_point_from_tbl(&blocks, v2s64(x, y)); + DF_WatchViewTextEditState *edit_state = df_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); + + // rjf: copy + if(op.flags & UI_TxtOpFlag_Copy && selection_tbl.min.x == selection_tbl.max.x && selection_tbl.min.y == selection_tbl.max.y) + { + os_set_clipboard_text(op.copy); + } + + // rjf: any valid op & autocomplete hint? -> perform autocomplete first, then re-compute op + if(autocomplete_hint_string.size != 0) + { + take_autocomplete = 1; + String8 word_query = df_autocomp_query_word_from_input_string_off(string, edit_state->cursor.column-1); + U64 word_off = (U64)(word_query.str - string.str); + String8 new_string = ui_push_string_replace_range(scratch.arena, string, r1s64(word_off+1, word_off+1+word_query.size), autocomplete_hint_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, word_off+1+autocomplete_hint_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); + } + + // rjf: cancel? -> revert to initial string + if(editing_complete && evt->slot == UI_EventActionSlot_Cancel) + { + string = str8(edit_state->initial_buffer, edit_state->initial_size); + } + + // rjf: obtain edited string + String8 new_string = string; + if(!txt_pt_match(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); + } + + // rjf: commit to edit state + new_string.size = Min(new_string.size, sizeof(edit_state->input_buffer)); + MemoryCopy(edit_state->input_buffer, new_string.str, new_string.size); + edit_state->input_size = new_string.size; + edit_state->cursor = op.cursor; + edit_state->mark = op.mark; + + // rjf: commit edited cell string + Vec2S64 tbl = v2s64(x, y); + switch((DF_WatchViewColumnKind)x) + { + default:{}break; + case DF_WatchViewColumnKind_Expr: + { + DF_WatchViewPoint pt = df_watch_view_point_from_tbl(&blocks, tbl); + DF_EvalRoot *root = df_eval_root_from_expand_key(ewv, eval_view, pt.key); + if(root != 0) + { + df_eval_root_equip_string(root, new_string); + state_dirty = 1; + } + else if(editing_complete && new_string.size != 0 && df_expand_key_match(pt.key, empty_row_key)) + { + root = df_eval_root_alloc(view, ewv); + df_eval_root_equip_string(root, new_string); + DF_ExpandKey key = df_expand_key_from_eval_root(root); + df_eval_view_set_key_rule(eval_view, key, str8_zero()); + state_dirty = 1; + } + }break; + case DF_WatchViewColumnKind_Value: + if(editing_complete && evt->slot != UI_EventActionSlot_Cancel) + { + DF_EvalVizWindowedRowList rows = df_eval_viz_windowed_row_list_from_viz_block_list(scratch.arena, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, eval_view, default_radix, code_font, ui_top_font_size(), + r1s64(ui_scroll_list_row_from_item(&row_blocks, y-1), + ui_scroll_list_row_from_item(&row_blocks, y-1)+1), &blocks); + B32 success = 0; + if(rows.first != 0) + { + DF_Eval write_eval = df_eval_from_string(scratch.arena, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, new_string); + success = df_commit_eval_value(parse_ctx.type_graph, parse_ctx.rdi, &ctrl_ctx, rows.first->eval, write_eval); + } + if(!success) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.string = str8_lit("Could not commit value successfully."); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + }break; + case DF_WatchViewColumnKind_Type:{}break; + case DF_WatchViewColumnKind_ViewRule: + if(editing_complete) + { + DF_WatchViewPoint pt = df_watch_view_point_from_tbl(&blocks, tbl); + df_eval_view_set_key_rule(eval_view, pt.key, new_string); + state_dirty = 1; + }break; + } + } + } + } + if(editing_complete) + { + ewv->text_editing = 0; + } + } + + ////////////////////////// + //- rjf: [table] do cell-granularity copies + // + if(!ewv->text_editing && evt->flags & UI_EventFlag_Copy) + { + taken = 1; + String8List strs = {0}; + DF_EvalVizWindowedRowList rows = df_eval_viz_windowed_row_list_from_viz_block_list(scratch.arena, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, eval_view, default_radix, code_font, ui_top_font_size(), + r1s64(ui_scroll_list_row_from_item(&row_blocks, selection_tbl.min.y-1), + ui_scroll_list_row_from_item(&row_blocks, selection_tbl.max.y-1)+1), &blocks); + DF_EvalVizRow *row = rows.first; + for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y && row != 0; y += 1, row = row->next) + { + for(S64 x = selection_tbl.min.x; x <= selection_tbl.max.x; x += 1) + { + String8 cell_string = df_string_from_eval_viz_row_column_kind(scratch.arena, eval_view, parse_ctx.type_graph, parse_ctx.rdi, row, (DF_WatchViewColumnKind)x, 0); + cell_string = str8_skip_chop_whitespace(cell_string); + U64 comma_pos = str8_find_needle(cell_string, 0, str8_lit(","), 0); + if(selection_tbl.min.x != selection_tbl.max.x || selection_tbl.min.y != selection_tbl.max.y) + { + str8_list_pushf(scratch.arena, &strs, "%s%S%s%s", + comma_pos < cell_string.size ? "\"" : "", + cell_string, + comma_pos < cell_string.size ? "\"" : "", + x+1 <= selection_tbl.max.x ? "," : ""); + } + else + { + str8_list_push(scratch.arena, &strs, cell_string); + } + } + if(y+1 <= selection_tbl.max.y) + { + str8_list_push(scratch.arena, &strs, str8_lit("\n")); + } + } + String8 string = str8_list_join(scratch.arena, &strs, 0); + os_set_clipboard_text(string); + } + + ////////////////////////// + //- rjf: [table] do cell-granularity deletions + // + if(!ewv->text_editing && evt->flags & UI_EventFlag_Delete) + { + taken = 1; + state_dirty = 1; + for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y; y += 1) + { + DF_WatchViewPoint pt = df_watch_view_point_from_tbl(&blocks, v2s64(0, y)); + + // rjf: row deletions + if(selection_tbl.min.x <= 0) + { + DF_EvalRoot *root = df_eval_root_from_expand_key(ewv, eval_view, pt.key); + if(root != 0) + { + DF_ExpandKey new_cursor_key = empty_row_key; + DF_ExpandKey new_cursor_parent_key = empty_row_parent_key; + if((evt->delta_2s32.x < 0 || evt->delta_2s32.y < 0) && root->prev != 0) + { + new_cursor_key = df_expand_key_from_eval_root(root->prev); + new_cursor_parent_key = df_parent_expand_key_from_eval_root(root->prev); + } + else if(root->next != 0) + { + new_cursor_key = df_expand_key_from_eval_root(root->next); + new_cursor_parent_key = df_parent_expand_key_from_eval_root(root->next); + } + DF_WatchViewPoint new_cursor_pt = {DF_WatchViewColumnKind_Expr, new_cursor_parent_key, new_cursor_key}; + df_eval_root_release(ewv, root); + ewv->cursor = ewv->mark = ewv->next_cursor = ewv->next_mark = new_cursor_pt; + } + } + + // rjf: view rule deletions + else if(selection_tbl.min.x <= DF_WatchViewColumnKind_ViewRule && DF_WatchViewColumnKind_ViewRule <= selection_tbl.max.x) + { + df_eval_view_set_key_rule(eval_view, pt.key, str8_zero()); + } + } + } + + ////////////////////////// + //- rjf: [table] apply deltas to cursor & mark + // + if(!ewv->text_editing && !(evt->flags & UI_EventFlag_Delete) && !(evt->flags & UI_EventFlag_Reorder)) + { + B32 cursor_tbl_min_is_empty_selection[Axis2_COUNT] = {0, 1}; + Rng2S64 cursor_tbl_range = r2s64(v2s64(0, 0), v2s64(3, blocks.total_semantic_row_count)); + Rng1S64 item_range = r1s64(0, 1 + blocks.total_visual_row_count); + Vec2S32 delta = evt->delta_2s32; + if(evt->flags & UI_EventFlag_PickSelectSide && !MemoryMatchStruct(&selection_tbl.min, &selection_tbl.max)) + { + if(delta.x > 0 || delta.y > 0) + { + cursor_tbl.x = selection_tbl.max.x; + cursor_tbl.y = selection_tbl.max.y; + } + else if(delta.x < 0 || delta.y < 0) + { + cursor_tbl.x = selection_tbl.min.x; + cursor_tbl.y = selection_tbl.min.y; + } + } + if(evt->flags & UI_EventFlag_ZeroDeltaOnSelect && !MemoryMatchStruct(&selection_tbl.min, &selection_tbl.max)) + { + MemoryZeroStruct(&delta); + } + B32 moved = 1; + switch(evt->delta_unit) + { + default:{moved = 0;}break; + case UI_EventDeltaUnit_Char: + { + for(EachEnumVal(Axis2, axis)) + { + cursor_tbl.v[axis] += delta.v[axis]; + if(cursor_tbl.v[axis] < cursor_tbl_range.min.v[axis]) + { + cursor_tbl.v[axis] = cursor_tbl_range.max.v[axis]; + } + if(cursor_tbl.v[axis] > cursor_tbl_range.max.v[axis]) + { + cursor_tbl.v[axis] = cursor_tbl_range.min.v[axis]; + } + cursor_tbl.v[axis] = clamp_1s64(r1s64(cursor_tbl_range.min.v[axis], cursor_tbl_range.max.v[axis]), cursor_tbl.v[axis]); + } + }break; + case UI_EventDeltaUnit_Word: + case UI_EventDeltaUnit_Line: + case UI_EventDeltaUnit_Page: + { + cursor_tbl.x = (delta.x>0 ? (cursor_tbl_range.max.x) : + delta.x<0 ? (cursor_tbl_range.min.x + !!cursor_tbl_min_is_empty_selection[Axis2_X]) : + cursor_tbl.x); + cursor_tbl.y += ((delta.y>0 ? +(num_possible_visible_rows-3) : + delta.y<0 ? -(num_possible_visible_rows-3) : + 0)); + cursor_tbl.y = clamp_1s64(r1s64(cursor_tbl_range.min.y + !!cursor_tbl_min_is_empty_selection[Axis2_Y], + cursor_tbl_range.max.y), + cursor_tbl.y); + }break; + case UI_EventDeltaUnit_Whole: + { + for(EachEnumVal(Axis2, axis)) + { + cursor_tbl.v[axis] = (delta.v[axis]>0 ? cursor_tbl_range.max.v[axis] : delta.v[axis]<0 ? cursor_tbl_range.min.v[axis] + !!cursor_tbl_min_is_empty_selection[axis] : cursor_tbl.v[axis]); + } + }break; + } + if(moved) + { + taken = 1; + cursor_dirty__tbl = 1; + { + Rng1S64 scroll_row_idx_range = r1s64(item_range.min, ClampBot(item_range.min, item_range.max-1)); + S64 cursor_item_idx = cursor_tbl.y-1; + if(item_range.min <= cursor_item_idx && cursor_item_idx <= item_range.max) + { + UI_ScrollPt *scroll_pt = &view->scroll_pos.y; + + //- rjf: compute visible row range + Rng1S64 visible_row_range = r1s64(scroll_pt->idx + 0 - !!(scroll_pt->off < 0), + scroll_pt->idx + 0 + num_possible_visible_rows + 1); + + //- rjf: compute cursor row range from cursor item + Rng1S64 cursor_visibility_row_range = {0}; + if(row_blocks.count == 0) + { + cursor_visibility_row_range = r1s64(cursor_item_idx-1, cursor_item_idx+3); + } + else + { + cursor_visibility_row_range.min = (S64)ui_scroll_list_row_from_item(&row_blocks, (U64)cursor_item_idx); + cursor_visibility_row_range.max = cursor_visibility_row_range.min + 4; + } + + //- rjf: compute deltas & apply + S64 min_delta = Min(0, cursor_visibility_row_range.min-visible_row_range.min); + S64 max_delta = Max(0, cursor_visibility_row_range.max-visible_row_range.max); + S64 new_idx = scroll_pt->idx+min_delta+max_delta; + new_idx = clamp_1s64(scroll_row_idx_range, new_idx); + ui_scroll_pt_target_idx(scroll_pt, new_idx); + } + } + + } + } + + ////////////////////////// + //- rjf: [table] stick table mark to cursor if needed + // + if(!ewv->text_editing) + { + if(taken && !(evt->flags & UI_EventFlag_KeepMark)) + { + mark_tbl = cursor_tbl; + } + } + + ////////////////////////// + //- rjf: [table] do cell-granularity reorders + // + if(!ewv->text_editing && evt->flags & UI_EventFlag_Reorder) + { + taken = 1; + DF_ExpandKey first_root_key = df_key_from_viz_block_list_row_num(&blocks, selection_tbl.min.y); + DF_EvalRoot *first_root = df_eval_root_from_expand_key(ewv, eval_view, first_root_key); + DF_EvalRoot *last_root = first_root; + if(first_root != 0) + { + for(S64 y = selection_tbl.min.y+1; y <= selection_tbl.max.y; y += 1) + { + DF_ExpandKey key = df_key_from_viz_block_list_row_num(&blocks, y); + DF_EvalRoot *new_root = df_eval_root_from_expand_key(ewv, eval_view, key); + if(new_root != 0) + { + last_root = new_root; + } + } + } + if(evt->delta_2s32.y < 0 && first_root != 0 && first_root->prev != 0) + { + state_dirty = 1; + DF_EvalRoot *reordered = first_root->prev; + DLLRemove(ewv->first_root, ewv->last_root, reordered); + DLLInsert(ewv->first_root, ewv->last_root, last_root, reordered); + } + if(evt->delta_2s32.y > 0 && last_root != 0 && last_root->next != 0) + { + state_dirty = 1; + DF_EvalRoot *prev_child = first_root->prev; + DF_EvalRoot *reordered = last_root->next; + DLLRemove(ewv->first_root, ewv->last_root, reordered); + DLLInsert(ewv->first_root, ewv->last_root, prev_child, reordered); + } + } + + ////////////////////////// + //- rjf: consume event, if taken + // + if(taken && evt != &dummy_evt) + { + ui_eat_event(events, event_n); + } + } + if(take_autocomplete) + { + for(UI_EventNode *n = events->first; n != 0; n = n->next) + { + if(n->v.kind == UI_EventKind_AutocompleteHint) + { + ui_eat_event(events, n); + break; + } + } + } + } + + ////////////////////////////// + //- rjf: build ui + // + F32 *col_pcts[] = + { + &ewv->expr_column_pct, + &ewv->value_column_pct, + &ewv->type_column_pct, + &ewv->view_rule_column_pct, + }; + B32 pressed = 0; + Rng1S64 visible_row_rng = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*2.5f); + scroll_list_params.dim_px = dim_2f32(rect); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(3, blocks.total_semantic_row_count)); + scroll_list_params.item_range = r1s64(0, 1 + blocks.total_visual_row_count); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + UI_ScrollListRowBlockChunkList row_block_chunks = {0}; + for(DF_EvalVizBlockNode *n = blocks.first; n != 0; n = n->next) + { + DF_EvalVizBlock *vb = &n->v; + UI_ScrollListRowBlock block = {0}; + block.row_count = dim_1u64(vb->visual_idx_range); + block.item_count = dim_1u64(vb->semantic_idx_range); + ui_scroll_list_row_block_chunk_list_push(scratch.arena, &row_block_chunks, 256, &block); + } + scroll_list_params.row_blocks = ui_scroll_list_row_block_array_from_chunk_list(scratch.arena, &row_block_chunks); + } + UI_BoxFlags disabled_flags = ui_top_flags(); + if(df_ctrl_targets_running()) + { + disabled_flags |= UI_BoxFlag_Disabled; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, &view->scroll_pos.y, + 0, + 0, + &visible_row_rng, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + UI_TableF(ArrayCount(col_pcts), col_pcts, "table_header") + { + //////////////////////////// + //- rjf: build table header + // + if(visible_row_rng.min == 0) UI_TableVector UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + { + UI_TableCell ui_label(str8_lit("Expression")); + UI_TableCell ui_label(str8_lit("Value")); + UI_TableCell ui_label(str8_lit("Type")); + UI_TableCell if(df_help_label(str8_lit("View Rule"))) UI_Tooltip + { + F32 max_width = ui_top_font_size()*35; + ui_label_multiline(max_width, str8_lit("View rules are used to tweak the way evaluated expressions are visualized. Multiple rules can be specified on each row. They are specified in a key:(value) form. Some examples follow:")); + ui_spacer(ui_em(1.5f, 1)); + DF_Font(ws, DF_FontSlot_Code) ui_labelf("array:(N)"); + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that a pointer points to N elements, rather than only 1.")); + ui_spacer(ui_em(1.5f, 1)); + DF_Font(ws, DF_FontSlot_Code) ui_labelf("omit:(member_1 ... member_n)"); + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Omits a list of member names from appearing in struct, union, or class evaluations.")); + ui_spacer(ui_em(1.5f, 1)); + DF_Font(ws, DF_FontSlot_Code) ui_labelf("only:(member_1 ... member_n)"); + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that only the specified members should appear in struct, union, or class evaluations.")); + ui_spacer(ui_em(1.5f, 1)); + DF_Font(ws, DF_FontSlot_Code) ui_labelf("list:(next_link_member_name)"); + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that some struct, union, or class forms the top of a linked list, with next_link_member_name being the member which points at the next element in the list.")); + ui_spacer(ui_em(1.5f, 1)); + DF_Font(ws, DF_FontSlot_Code) ui_labelf("dec"); + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that all integral evaluations should appear in base-10 form.")); + ui_spacer(ui_em(1.5f, 1)); + DF_Font(ws, DF_FontSlot_Code) ui_labelf("hex"); + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that all integral evaluations should appear in base-16 form.")); + ui_spacer(ui_em(1.5f, 1)); + DF_Font(ws, DF_FontSlot_Code) ui_labelf("oct"); + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that all integral evaluations should appear in base-8 form.")); + ui_spacer(ui_em(1.5f, 1)); + DF_Font(ws, DF_FontSlot_Code) ui_labelf("bin"); + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that all integral evaluations should appear in base-2 form.")); + ui_spacer(ui_em(1.5f, 1)); + DF_Font(ws, DF_FontSlot_Code) ui_labelf("no_addr"); + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Displays only what pointers point to, if possible, without the pointer's address value.")); + ui_spacer(ui_em(1.5f, 1)); + } + } + + //////////////////////////// + //- rjf: viz blocks -> rows + // + DF_EvalVizWindowedRowList rows = {0}; + { + rows = df_eval_viz_windowed_row_list_from_viz_block_list(scratch.arena, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, eval_view, default_radix, code_font, ui_top_font_size(), r1s64(visible_row_rng.min-1, visible_row_rng.max), &blocks); + } + + //////////////////////////// + //- rjf: build table + // + ProfScope("build table") + { + U64 semantic_idx = rows.count_before_semantic; + for(DF_EvalVizRow *row = rows.first; row != 0; row = row->next, semantic_idx += 1) + { + //////////////////////// + //- rjf: unpack row info + // + U64 row_hash = df_hash_from_expand_key(row->key); + U64 expr_hash = df_hash_from_string(row->display_expr); + B32 row_selected = (selection_tbl.min.y <= (semantic_idx+1) && (semantic_idx+1) <= selection_tbl.max.y); + B32 row_expanded = df_expand_key_is_set(&eval_view->expand_tree_table, row->key); + + //////////////////////// + //- rjf: determine if row's data is fresh and/or bad + // + B32 row_is_fresh = 0; + B32 row_is_bad = 0; + switch(row->eval.mode) + { + default:{}break; + case EVAL_EvalMode_Addr: + { + U64 size = tg_byte_size_from_graph_rdi_key(parse_ctx.type_graph, parse_ctx.rdi, row->eval.type_key); + size = Min(size, 64); + Rng1U64 vaddr_rng = r1u64(row->eval.offset, row->eval.offset+size); + CTRL_ProcessMemorySlice slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, process->ctrl_machine_id, process->ctrl_handle, vaddr_rng, 0); + for(U64 idx = 0; idx < (slice.data.size+63)/64; idx += 1) + { + if(slice.byte_changed_flags[idx] != 0) + { + row_is_fresh = 1; + } + if(slice.byte_bad_flags[idx] != 0) + { + row_is_bad = 1; + } + } + }break; + } + + //////////////////////// + //- rjf: determine row's color palette + // + UI_BoxFlags row_flags = 0; + UI_Palette *palette = ui_top_palette(); + { + if(row_is_fresh) + { + palette = ui_build_palette(ui_top_palette(), .background = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlay)); + row_flags |= UI_BoxFlag_DrawBackground; + } + } + + //////////////////////// + //- rjf: build row box + // + ui_set_next_palette(palette); + ui_set_next_flags(disabled_flags); + ui_set_next_pref_width(ui_pct(1, 0)); + ui_set_next_pref_height(ui_px(scroll_list_params.row_height_px*row->size_in_rows, 1.f)); + ui_set_next_focus_hot(row_selected ? UI_FocusKind_On : UI_FocusKind_Off); + UI_Box *row_box = ui_build_box_from_stringf(row_flags|UI_BoxFlag_DrawSideBottom|UI_BoxFlag_Clickable|(!(row->flags & DF_EvalVizRowFlag_Canvas) * UI_BoxFlag_DisableFocusOverlay), "row_%I64x", row_hash); + ui_ts_vector_idx += 1; + ui_ts_cell_idx = 0; + + //////////////////////// + //- rjf: canvas row -> fill with canvas ui build + // + if(row->flags & DF_EvalVizRowFlag_Canvas) UI_Parent(row_box) UI_FocusHot(row_selected ? UI_FocusKind_On : UI_FocusKind_Off) + { + //- rjf: build canvas row contents + ui_set_next_fixed_y(-1.f * (row->skipped_size_in_rows) * scroll_list_params.row_height_px); + ui_set_next_fixed_height((row->skipped_size_in_rows + row->size_in_rows + row->chopped_size_in_rows) * scroll_list_params.row_height_px); + ui_set_next_child_layout_axis(Axis2_X); + UI_Box *canvas_box = ui_build_box_from_stringf(UI_BoxFlag_FloatingY, "###canvas_%I64x", row_hash); + if(row->expand_ui_rule_spec != &df_g_nil_gfx_view_rule_spec && row->expand_ui_rule_spec->info.block_ui) + { + UI_Parent(canvas_box) UI_WidthFill UI_HeightFill + { + Vec2F32 canvas_dim = v2f32(scroll_list_params.dim_px.x - ui_top_font_size()*1.5f, + (row->skipped_size_in_rows+row->size_in_rows+row->chopped_size_in_rows)*scroll_list_params.row_height_px); + row->expand_ui_rule_spec->info.block_ui(ws, row->key, row->eval, row->edit_expr, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, row->expand_ui_rule_node, canvas_dim); + } + } + + //- rjf: do canvas row interactions + { + DF_WatchViewPoint pt = {DF_WatchViewColumnKind_Expr, row->parent_key, row->key}; + UI_Signal sig = ui_signal_from_box(row_box); + + // rjf: press -> focus + if(ui_pressed(sig)) + { + ewv->next_cursor = ewv->next_mark = pt; + pressed = 1; + } + + // rjf: double clicked -> open dedicated tab + if(ui_double_clicked(sig)) + { + DF_CfgNode *cfg = df_cfg_tree_copy(scratch.arena, row->expand_ui_rule_node); + DF_CfgNode *cfg_root = push_array(scratch.arena, DF_CfgNode, 1); + cfg_root->first = cfg_root->last = cfg; + cfg_root->next = cfg_root->parent = &df_g_nil_cfg_node; + if(cfg != &df_g_nil_cfg_node) + { + cfg->parent = cfg_root; + } + DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); + p.string = row->edit_expr; + p.view_spec = df_tab_view_spec_from_gfx_view_rule_spec(row->expand_ui_rule_spec); + p.cfg_node = cfg_root; + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_ViewSpec); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_CfgNode); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_OpenTab)); + } + } + } + + //////////////////////// + //- rjf: build non-canvas row contents + // + if(!(row->flags & DF_EvalVizRowFlag_Canvas)) UI_Parent(row_box) UI_HeightFill + { + //////////////////// + //- rjf: draw start of cache lines in expansions + // + if((row->eval.mode == EVAL_EvalMode_Addr || row->eval.mode == EVAL_EvalMode_NULL) && + row->eval.errors.count == 0 && + row->eval.offset%64 == 0 && row->depth > 0 && + !row_expanded) + { + ui_set_next_fixed_x(0); + ui_set_next_fixed_y(0); + ui_set_next_fixed_height(ui_top_font_size()*0.1f); + ui_set_next_palette(ui_build_palette(ui_top_palette(), .background = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlay))); + ui_build_box_from_key(UI_BoxFlag_Floating|UI_BoxFlag_DrawBackground, ui_key_zero()); + } + + //////////////////// + //- rjf: draw mid-row cache line boundaries in expansions + // + if((row->eval.mode == EVAL_EvalMode_Addr || row->eval.mode == EVAL_EvalMode_NULL) && + row->eval.errors.count == 0 && + row->eval.offset%64 != 0 && + row->depth > 0 && + !row_expanded) + { + U64 next_off = (row->eval.offset + tg_byte_size_from_graph_rdi_key(parse_ctx.type_graph, parse_ctx.rdi, row->eval.type_key)); + if(next_off%64 != 0 && row->eval.offset/64 < next_off/64) + { + ui_set_next_fixed_x(0); + ui_set_next_fixed_y(scroll_list_params.row_height_px - ui_top_font_size()*0.5f); + ui_set_next_fixed_height(ui_top_font_size()*1.f); + Vec4F32 boundary_color = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlay); + ui_set_next_palette(ui_build_palette(ui_top_palette(), .background = boundary_color)); + ui_build_box_from_key(UI_BoxFlag_Floating|UI_BoxFlag_DrawBackground, ui_key_zero()); + } + } + + //////////////////// + //- rjf: expression + // + ProfScope("expr") + { + DF_WatchViewPoint pt = {DF_WatchViewColumnKind_Expr, row->parent_key, row->key}; + DF_WatchViewTextEditState *edit_state = df_watch_view_text_edit_state_from_pt(ewv, pt); + B32 cell_selected = (row_selected && selection_tbl.min.x <= pt.column_kind && pt.column_kind <= selection_tbl.max.x); + B32 can_edit_expr = !(row->depth > 0 || modifiable == 0); + + // rjf: build + UI_Signal sig = {0}; + B32 next_expanded = row_expanded; + UI_Palette(palette) UI_TableCell + UI_FocusHot(cell_selected ? UI_FocusKind_On : UI_FocusKind_Off) + UI_FocusActive((cell_selected && ewv->text_editing) ? UI_FocusKind_On : UI_FocusKind_Off) + { + B32 expr_editing_active = ui_is_focus_active(); + B32 is_inherited = (row->inherited_type_key_chain.count != 0); + DF_Font(ws, DF_FontSlot_Code) UI_FlagsAdd(row->depth > 0 ? UI_BoxFlag_DrawTextWeak : 0) + { + FuzzyMatchRangeList matches = {0}; + if(filter.size != 0) + { + matches = fuzzy_match_find(scratch.arena, filter, row->display_expr); + } + sig = df_line_editf(ws, + (DF_LineEditFlag_CodeContents*(!(row->flags & DF_EvalVizRowFlag_ExprIsSpecial))| + DF_LineEditFlag_NoBackground*(!is_inherited)| + DF_LineEditFlag_DisableEdit*(!can_edit_expr)| + DF_LineEditFlag_Expander*!!(row->flags & DF_EvalVizRowFlag_CanExpand)| + DF_LineEditFlag_ExpanderPlaceholder*(row->depth==0)| + DF_LineEditFlag_ExpanderSpace*(row->depth!=0)), + row->depth, + filter.size ? &matches : 0, + &edit_state->cursor, &edit_state->mark, edit_state->input_buffer, sizeof(edit_state->input_buffer), &edit_state->input_size, &next_expanded, + row->display_expr, + "###row_%I64x", row_hash); + } + if(is_inherited && ui_hovering(sig)) UI_Tooltip + { + String8List inheritance_chain_type_names = {0}; + for(TG_KeyNode *n = row->inherited_type_key_chain.first; n != 0; n = n->next) + { + String8 inherited_type_name = tg_string_from_key(scratch.arena, parse_ctx.type_graph, parse_ctx.rdi, n->v); + inherited_type_name = str8_skip_chop_whitespace(inherited_type_name); + str8_list_push(scratch.arena, &inheritance_chain_type_names, inherited_type_name); + } + StringJoin join = {0}; + join.sep = str8_lit("::"); + String8 inheritance_type = str8_list_join(scratch.arena, &inheritance_chain_type_names, &join); + ui_set_next_pref_width(ui_children_sum(1)); + UI_Row + { + ui_labelf("Inherited from "); + DF_Font(ws, DF_FontSlot_Code) df_code_label(1.f, 1.f, df_rgba_from_theme_color(DF_ThemeColor_CodeType), inheritance_type); + } + } + if(DEV_eval_watch_key_tooltips && ui_hovering(sig)) UI_Tooltip DF_Font(ws, DF_FontSlot_Code) + { + ui_labelf("Parent Key: %I64x, %I64x", row->parent_key.parent_hash, row->parent_key.child_num); + ui_labelf("Hover Key: %I64x, %I64x", row->key.parent_hash, row->key.child_num); + ui_labelf("Cursor Key: %I64x, %I64x", ewv->cursor.key.parent_hash, ewv->cursor.key.child_num); + } + if(DEV_eval_compiler_tooltips && row->depth == 0 && ui_hovering(sig)) UI_Tooltip + { + Temp scratch = scratch_begin(0, 0); + String8 string = row->display_expr; + + // rjf: lex & parse + EVAL_TokenArray tokens = eval_token_array_from_text(scratch.arena, string); + EVAL_ParseResult parse = eval_parse_expr_from_text_tokens(scratch.arena, &parse_ctx, string, &tokens); + EVAL_ErrorList errors = parse.errors; + ui_labelf("Tokens:"); + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) for(U64 idx = 0; idx < tokens.count; idx += 1) + { + EVAL_Token *token = tokens.v+idx; + String8 token_string = str8_substr(string, token->range); + String8 token_kind_name = str8_lit("Token"); + switch(token->kind) + { + default:break; + case EVAL_TokenKind_Identifier: {token_kind_name = str8_lit("Identifier");}break; + case EVAL_TokenKind_Numeric: {token_kind_name = str8_lit("Numeric");}break; + case EVAL_TokenKind_StringLiteral:{token_kind_name = str8_lit("StringLiteral");}break; + case EVAL_TokenKind_CharLiteral: {token_kind_name = str8_lit("CharLiteral");}break; + case EVAL_TokenKind_Symbol: {token_kind_name = str8_lit("Symbol");}break; + } + ui_labelf("%S -> \"%S\"", token_kind_name, token_string); + } + + // rjf: produce IR tree & type + EVAL_IRTreeAndType ir_tree_and_type = {&eval_irtree_nil}; + if(parse.expr != &eval_expr_nil && errors.count == 0) + { + ui_labelf("Type:"); + ir_tree_and_type = eval_irtree_and_type_from_expr(scratch.arena, parse_ctx.type_graph, parse_ctx.rdi, &eval_string2expr_map_nil, parse.expr, &errors); + TG_Key type_key = ir_tree_and_type.type_key; + String8 type_string = tg_string_from_key(scratch.arena, parse_ctx.type_graph, parse_ctx.rdi, type_key); + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + ui_label(type_string); + } + + scratch_end(scratch); + } + + // rjf: autocomplete lister + if(expr_editing_active && + selection_tbl.min.x == selection_tbl.max.x && selection_tbl.min.y == selection_tbl.max.y && + txt_pt_match(edit_state->cursor, edit_state->mark)) + { + String8 input = str8(edit_state->input_buffer, edit_state->input_size); + DF_AutoCompListerParams params = {DF_AutoCompListerFlag_Locals}; + df_set_autocomp_lister_query(ws, sig.box->key, ctrl_ctx, ¶ms, input, edit_state->cursor.column-1); + } + } + + // rjf: press -> commit if editing & select + if(ui_pressed(sig)) + { + ewv->next_cursor = ewv->next_mark = pt; + pressed = 1; + } + + // rjf: double-click -> start editing + if(ui_double_clicked(sig) && can_edit_expr) + { + ui_kill_action(); + DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Edit)); + } + + // rjf: commit expansion state + if(next_expanded != row_expanded) + { + df_expand_set_expansion(eval_view->arena, &eval_view->expand_tree_table, row->parent_key, row->key, next_expanded); + } + } + + //////////////////// + //- rjf: value + // + ProfScope("value") + { + DF_WatchViewPoint pt = {DF_WatchViewColumnKind_Value, row->parent_key, row->key}; + DF_WatchViewTextEditState *edit_state = df_watch_view_text_edit_state_from_pt(ewv, pt); + B32 cell_selected = (row_selected && selection_tbl.min.x <= pt.column_kind && pt.column_kind <= selection_tbl.max.x); + B32 value_is_error = (row->eval.errors.count != 0); + B32 value_is_hook = (!value_is_error && row->value_ui_rule_spec != &df_g_nil_gfx_view_rule_spec && row->value_ui_rule_spec != 0); + B32 value_is_complex = (!value_is_error && !value_is_hook && !(row->flags & DF_EvalVizRowFlag_CanEditValue)); + B32 value_is_simple = (!value_is_error && !value_is_hook && (row->flags & DF_EvalVizRowFlag_CanEditValue)); + + // rjf: unpack palette + UI_BoxFlags cell_flags = 0; + UI_Palette *palette = ui_top_palette(); + { + if(row_is_bad || value_is_error) + { + palette = ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_TextNegative), .text_weak = df_rgba_from_theme_color(DF_ThemeColor_TextNegative), .background = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlayError)); + cell_flags |= UI_BoxFlag_DrawBackground; + } + } + + // rjf: build + UI_Signal sig = {0}; + ui_set_next_flags(cell_flags); + UI_Palette(palette) UI_TableCell DF_Font(ws, DF_FontSlot_Code) + UI_FocusHot(cell_selected ? UI_FocusKind_On : UI_FocusKind_Off) + UI_FocusActive((cell_selected && ewv->text_editing) ? UI_FocusKind_On : UI_FocusKind_Off) + { + // rjf: errors? -> show errors + if(value_is_error) DF_Font(ws, DF_FontSlot_Main) + { + String8List strings = {0}; + for(EVAL_Error *error = row->eval.errors.first; error != 0; error = error->next) + { + str8_list_push(scratch.arena, &strings, error->text); + } + StringJoin join = {str8_lit(""), str8_lit(" "), str8_lit("")}; + String8 error_string = str8_list_join(scratch.arena, &strings, &join); + sig = df_error_label(error_string); + } + + // rjf: hook -> call hook + if(value_is_hook) DF_Font(ws, DF_FontSlot_Main) + { + UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clip|UI_BoxFlag_Clickable, "###val_%I64x", row_hash); + UI_Parent(box) + { + row->value_ui_rule_spec->info.row_ui(ws, row->key, row->eval, di_scope, &ctrl_ctx, &parse_ctx, ¯o_map, row->value_ui_rule_node); + } + sig = ui_signal_from_box(box); + } + + // rjf: complex values + if(value_is_complex) + { + UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clip|UI_BoxFlag_Clickable, "###val_%I64x", row_hash); + UI_Parent(box) + { + df_code_label(1.f, 1, df_rgba_from_theme_color(DF_ThemeColor_CodeDefault), row->display_value); + } + sig = ui_signal_from_box(box); + } + + // rjf: simple values (editable) + if(value_is_simple) UI_TextRasterFlags(df_raster_flags_from_slot(ws, DF_FontSlot_Code)) + { + sig = df_line_editf(ws, DF_LineEditFlag_CodeContents|DF_LineEditFlag_NoBackground, 0, 0, &edit_state->cursor, &edit_state->mark, edit_state->input_buffer, sizeof(edit_state->input_buffer), &edit_state->input_size, 0, row->display_value, "%S###val_%I64x", row->display_value, row_hash); + } + } + + // rjf: bad & hovering -> display + if(row_is_bad && ui_hovering(sig)) UI_Tooltip + { + UI_PrefWidth(ui_children_sum(1)) df_error_label(str8_lit("Could not read process memory successfully.")); + } + + // rjf: press -> focus & commit if editing & not selected + if(ui_pressed(sig)) + { + ewv->next_cursor = ewv->next_mark = pt; + pressed = 1; + } + + // rjf: double-click -> start editing + if(ui_double_clicked(sig) && value_is_simple) + { + ui_kill_action(); + DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Edit)); + } + + // rjf: double-click, not editable -> go-to-location + if(ui_double_clicked(sig) && !(row->flags & DF_EvalVizRowFlag_CanEditValue)) + { + U64 vaddr = 0; + if(vaddr == 0) { vaddr = row->eval.offset; } + if(vaddr == 0) { vaddr = row->eval.imm_u64; } + DF_Entity *module = df_module_from_process_vaddr(process, vaddr); + DI_Key dbgi_key = df_dbgi_key_from_module(module); + U64 voff = df_voff_from_vaddr(module, vaddr); + DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, voff); + DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); + p.entity = df_handle_from_entity(process); + p.vaddr = vaddr; + if(lines.first != 0) + { + p.file_path = df_full_path_from_entity(scratch.arena, df_entity_from_handle(lines.first->v.file)); + p.text_point = lines.first->v.pt; + } + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FindCodeLocation)); + } + } + + //////////////////// + //- rjf: type + // + ProfScope("type") + { + DF_WatchViewPoint pt = {DF_WatchViewColumnKind_Type, row->parent_key, row->key}; + DF_WatchViewTextEditState *edit_state = df_watch_view_text_edit_state_from_pt(ewv, pt); + B32 cell_selected = (row_selected && selection_tbl.min.x <= pt.column_kind && pt.column_kind <= selection_tbl.max.x); + UI_TableCell DF_Font(ws, DF_FontSlot_Code) + UI_FocusHot(cell_selected ? UI_FocusKind_On : UI_FocusKind_Off) + UI_FocusActive((cell_selected && ewv->text_editing) ? UI_FocusKind_On : UI_FocusKind_Off) + { + TG_Key key = row->eval.type_key; + String8 string = tg_string_from_key(scratch.arena, parse_ctx.type_graph, parse_ctx.rdi, key); + string = str8_skip_chop_whitespace(string); + UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clip|UI_BoxFlag_Clickable, "###type_%I64x", row_hash); + if(!tg_key_match(key, tg_key_zero())) UI_Parent(box) + { + df_code_label(1.f, 1, df_rgba_from_theme_color(DF_ThemeColor_CodeType), string); + } + UI_Signal sig = ui_signal_from_box(box); + if(ui_pressed(sig)) + { + ewv->next_cursor = ewv->next_mark = pt; + pressed = 1; + } + } + } + + //////////////////// + //- rjf: view rule + // + ProfScope("view rule") + { + DF_WatchViewPoint pt = {DF_WatchViewColumnKind_ViewRule, row->parent_key, row->key}; + DF_WatchViewTextEditState *edit_state = df_watch_view_text_edit_state_from_pt(ewv, pt); + B32 cell_selected = (row_selected && selection_tbl.min.x <= pt.column_kind && pt.column_kind <= selection_tbl.max.x); + String8 view_rule = df_eval_view_rule_from_key(eval_view, row->key); + + // rjf: build + UI_Signal sig = {0}; + B32 rule_editing_active = 0; + UI_TableCell DF_Font(ws, DF_FontSlot_Code) + UI_FocusHot(cell_selected ? UI_FocusKind_On : UI_FocusKind_Off) + UI_FocusActive((cell_selected && ewv->text_editing) ? UI_FocusKind_On : UI_FocusKind_Off) + UI_TextRasterFlags(df_raster_flags_from_slot(ws, DF_FontSlot_Code)) + { + rule_editing_active = ui_is_focus_active(); + sig = df_line_editf(ws, DF_LineEditFlag_CodeContents|DF_LineEditFlag_NoBackground, 0, 0, &edit_state->cursor, &edit_state->mark, edit_state->input_buffer, sizeof(edit_state->input_buffer), &edit_state->input_size, 0, view_rule, "###view_rule_%I64x", row_hash); + } + + // rjf: press -> commit if not selected, select this cell + if(ui_pressed(sig)) + { + ewv->next_cursor = ewv->next_mark = pt; + pressed = 1; + } + + // rjf: autocomplete lister + if(rule_editing_active && + selection_tbl.min.x == selection_tbl.max.x && selection_tbl.min.y == selection_tbl.max.y && + txt_pt_match(edit_state->cursor, edit_state->mark)) + { + String8 input = str8(edit_state->input_buffer, edit_state->input_size); + DF_AutoCompListerParams params = df_view_rule_autocomp_lister_params_from_input_cursor(scratch.arena, input, edit_state->cursor.column-1); + if(params.flags == 0) + { + params.flags = DF_AutoCompListerFlag_ViewRules; + } + df_set_autocomp_lister_query(ws, sig.box->key, ctrl_ctx, ¶ms, input, edit_state->cursor.column-1); + } + + // rjf: double-click -> begin editing + if(ui_double_clicked(sig) && !ewv->text_editing) + { + ui_kill_action(); + DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Edit)); + } + } + } + } + } + } + + ////////////////////////////// + //- rjf: general table-wide press logic + // + if(pressed) + { + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + + scratch_end(scratch); + fzy_scope_close(fzy_scope); + di_scope_close(di_scope); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Null @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Null) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Null) { return str8_lit(""); } +DF_VIEW_CMD_FUNCTION_DEF(Null) {} +DF_VIEW_UI_FUNCTION_DEF(Null) {} + +//////////////////////////////// +//~ rjf: Empty @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Empty) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Empty) { return str8_lit(""); } +DF_VIEW_CMD_FUNCTION_DEF(Empty) {} +DF_VIEW_UI_FUNCTION_DEF(Empty) +{ + ui_set_next_flags(UI_BoxFlag_DefaultFocusNav); + UI_Focus(UI_FocusKind_On) UI_WidthFill UI_HeightFill UI_NamedColumn(str8_lit("empty_view")) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + UI_Padding(ui_pct(1, 0)) UI_Focus(UI_FocusKind_Null) + { + UI_PrefHeight(ui_em(3.f, 1.f)) + UI_Row + UI_Padding(ui_pct(1, 0)) + UI_TextAlignment(UI_TextAlign_Center) + UI_PrefWidth(ui_em(15.f, 1.f)) + UI_CornerRadius(ui_top_font_size()/2.f) + DF_Palette(ws, DF_PaletteCode_NegativePopButton) + { + if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_X, 0, "Close Panel"))) + { + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_ClosePanel)); + } + } + } +} + +//////////////////////////////// +//~ rjf: GettingStarted @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(GettingStarted) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(GettingStarted) { return str8_lit(""); } +DF_VIEW_CMD_FUNCTION_DEF(GettingStarted) {} +DF_VIEW_UI_FUNCTION_DEF(GettingStarted) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + ui_set_next_flags(UI_BoxFlag_DefaultFocusNav); + UI_Focus(UI_FocusKind_On) UI_WidthFill UI_HeightFill UI_NamedColumn(str8_lit("empty_view")) + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + UI_Padding(ui_pct(1, 0)) UI_Focus(UI_FocusKind_Null) + { + DF_EntityList targets = df_push_active_target_list(scratch.arena); + DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); + + //- rjf: icon & info + UI_Padding(ui_em(2.f, 1.f)) + { + //- rjf: icon + { + F32 icon_dim = ui_top_font_size()*10.f; + UI_PrefHeight(ui_px(icon_dim, 1.f)) + UI_Row + UI_Padding(ui_pct(1, 0)) + UI_PrefWidth(ui_px(icon_dim, 1.f)) + { + R_Handle texture = df_gfx_state->icon_texture; + Vec2S32 texture_dim = r_size_from_tex2d(texture); + ui_image(texture, R_Tex2DSampleKind_Linear, r2f32p(0, 0, texture_dim.x, texture_dim.y), v4f32(1, 1, 1, 1), 0, str8_lit("")); + } + } + + //- rjf: info + UI_Padding(ui_em(2.f, 1.f)) + UI_WidthFill UI_PrefHeight(ui_em(2.f, 1.f)) + UI_Row + UI_Padding(ui_pct(1, 0)) + UI_TextAlignment(UI_TextAlign_Center) + UI_PrefWidth(ui_text_dim(10, 1)) + { + ui_label(str8_lit(BUILD_TITLE_STRING_LITERAL)); + } + } + + //- rjf: targets state dependent helper + B32 helper_built = 0; + if(processes.count == 0) + { + helper_built = 1; + switch(targets.count) + { + //- rjf: user has no targets. build helper for adding them + case 0: + { + UI_PrefHeight(ui_em(3.75f, 1.f)) + UI_Row + UI_Padding(ui_pct(1, 0)) + UI_TextAlignment(UI_TextAlign_Center) + UI_PrefWidth(ui_em(22.f, 1.f)) + UI_CornerRadius(ui_top_font_size()/2.f) + DF_Palette(ws, DF_PaletteCode_NeutralPopButton) + if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_Add, 0, "Add Target"))) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.cmd_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_AddTarget); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); + } + }break; + + //- rjf: user has 1 target. build helper for launching it + case 1: + { + DF_Entity *target = df_first_entity_from_list(&targets); + String8 target_full_path = target->name; + String8 target_name = str8_skip_last_slash(target_full_path); + UI_PrefHeight(ui_em(3.75f, 1.f)) + UI_Row + UI_Padding(ui_pct(1, 0)) + UI_TextAlignment(UI_TextAlign_Center) + UI_PrefWidth(ui_em(22.f, 1.f)) + UI_CornerRadius(ui_top_font_size()/2.f) + DF_Palette(ws, DF_PaletteCode_PositivePopButton) + { + if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_Play, 0, "Launch %S", target_name))) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.entity = df_handle_from_entity(target); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_LaunchAndRun)); + } + ui_spacer(ui_em(1.5f, 1)); + if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_Play, 0, "Step Into %S", target_name))) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.entity = df_handle_from_entity(target); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_LaunchAndInit)); + } + } + }break; + + //- rjf: user has N targets. + default: + { + helper_built = 0; + }break; + } + } + + //- rjf: or text + if(helper_built) + { + UI_PrefHeight(ui_em(2.25f, 1.f)) + UI_Row + UI_Padding(ui_pct(1, 0)) + UI_TextAlignment(UI_TextAlign_Center) + UI_WidthFill + ui_labelf("- or -"); + } + + //- rjf: helper text for command lister activation + UI_PrefHeight(ui_em(2.25f, 1.f)) UI_Row + UI_PrefWidth(ui_text_dim(10, 1)) + UI_TextAlignment(UI_TextAlign_Center) + UI_Padding(ui_pct(1, 0)) + { + ui_labelf("use"); + DF_CmdSpec *spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand); + UI_Flags(UI_BoxFlag_DrawBorder) UI_TextAlignment(UI_TextAlign_Center) df_cmd_binding_buttons(ws, spec); + ui_labelf("to open command menu"); + } + } + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Commands @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Commands) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Commands) { return str8_lit(""); } +DF_VIEW_CMD_FUNCTION_DEF(Commands) {} +DF_VIEW_UI_FUNCTION_DEF(Commands) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + + //- rjf: grab state + typedef struct DF_CmdsViewState DF_CmdsViewState; + struct DF_CmdsViewState + { + DF_CmdSpec *selected_cmd_spec; + }; + DF_CmdsViewState *cv = df_view_user_state(view, DF_CmdsViewState); + + //- rjf: build filtered array of commands + String8 query = str8(view->query_buffer, view->query_string_size); + DF_CmdListerItemList cmd_list = df_cmd_lister_item_list_from_needle(scratch.arena, query); + DF_CmdListerItemArray cmd_array = df_cmd_lister_item_array_from_list(scratch.arena, cmd_list); + df_cmd_lister_item_array_sort_by_strength__in_place(cmd_array); + + //- rjf: submit best match when hitting enter w/ no selection + if(cv->selected_cmd_spec == &df_g_nil_cmd_spec && ui_slot_press(UI_EventActionSlot_Accept)) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + if(cmd_array.count > 0) + { + DF_CmdListerItem *item = &cmd_array.v[0]; + params.cmd_spec = item->cmd_spec; + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); + } + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); + } + + //- rjf: selected kind -> cursor + Vec2S64 cursor = {0}; + { + for(U64 idx = 0; idx < cmd_array.count; idx += 1) + { + if(cmd_array.v[idx].cmd_spec == cv->selected_cmd_spec) + { + cursor.y = (S64)idx+1; + break; + } + } + } + + //- rjf: build contents + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*6.5f); + scroll_list_params.dim_px = dim_2f32(rect); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, cmd_array.count)); + scroll_list_params.item_range = r1s64(0, cmd_array.count); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + &cursor, + 0, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + { + //- rjf: build buttons + for(S64 row_idx = visible_row_range.min; + row_idx <= visible_row_range.max && row_idx < cmd_array.count; + row_idx += 1) + { + DF_CmdListerItem *item = &cmd_array.v[row_idx]; + + //- rjf: build row contents + ui_set_next_hover_cursor(OS_Cursor_HandPoint); + ui_set_next_child_layout_axis(Axis2_X); + UI_Box *box = &ui_g_nil_box; + UI_Focus(cursor.y == row_idx+1 ? UI_FocusKind_On : UI_FocusKind_Off) + { + box = ui_build_box_from_stringf(UI_BoxFlag_Clickable| + UI_BoxFlag_DrawBorder| + UI_BoxFlag_DrawBackground| + UI_BoxFlag_DrawHotEffects| + UI_BoxFlag_DrawActiveEffects, + "###cmd_button_%p", item->cmd_spec); + } + UI_Parent(box) UI_PrefHeight(ui_em(1.65f, 1.f)) + { + //- rjf: icon + UI_PrefWidth(ui_em(3.f, 1.f)) + UI_HeightFill + UI_Column + DF_Font(ws, DF_FontSlot_Icons) + UI_FontSize(df_font_size_from_slot(ws, DF_FontSlot_Icons)) + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + UI_HeightFill + UI_TextAlignment(UI_TextAlign_Center) + { + DF_IconKind icon = item->cmd_spec->info.canonical_icon_kind; + if(icon != DF_IconKind_Null) + { + ui_label(df_g_icon_kind_text_table[icon]); + } + } + + //- rjf: name + description + ui_set_next_pref_height(ui_pct(1, 0)); + UI_Column UI_Padding(ui_pct(1, 0)) + { + F_Tag font = ui_top_font(); + F32 font_size = ui_top_font_size(); + F_Metrics font_metrics = f_metrics_from_tag_size(font, font_size); + F32 font_line_height = f_line_height_from_metrics(&font_metrics); + String8 cmd_display_name = item->cmd_spec->info.display_name; + String8 cmd_desc = item->cmd_spec->info.description; + UI_Box *name_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S##name_%p", cmd_display_name, item->cmd_spec); + UI_Box *desc_box = &ui_g_nil_box; + UI_PrefHeight(ui_em(1.8f, 1.f)) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + { + desc_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S##desc_%p", cmd_desc, item->cmd_spec); + } + ui_box_equip_fuzzy_match_ranges(name_box, &item->name_match_ranges); + ui_box_equip_fuzzy_match_ranges(desc_box, &item->desc_match_ranges); + } + + //- rjf: bindings + ui_set_next_flags(UI_BoxFlag_Clickable); + UI_PrefWidth(ui_children_sum(1.f)) UI_HeightFill UI_NamedColumn(str8_lit("binding_column")) UI_Padding(ui_em(1.5f, 1.f)) + { + ui_set_next_flags(UI_BoxFlag_Clickable); + UI_NamedRow(str8_lit("binding_row")) UI_Padding(ui_em(1.f, 1.f)) + { + df_cmd_binding_buttons(ws, item->cmd_spec); + } + } + } + + //- rjf: interact + UI_Signal sig = ui_signal_from_box(box); + if(ui_clicked(sig)) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.cmd_spec = item->cmd_spec; + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); + } + } + } + + //- rjf: map selected num -> selected kind + if(1 <= cursor.y && cursor.y <= cmd_array.count) + { + cv->selected_cmd_spec = cmd_array.v[cursor.y-1].cmd_spec; + } + else + { + cv->selected_cmd_spec = &df_g_nil_cmd_spec; + } + + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: FileSystem @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(FileSystem) +{ +} + +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(FileSystem) +{ + return str8_lit(""); +} + +DF_VIEW_CMD_FUNCTION_DEF(FileSystem) +{ +} + +DF_VIEW_UI_FUNCTION_DEF(FileSystem) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + String8 query = str8(view->query_buffer, view->query_string_size); + String8 query_normalized = path_normalized_from_string(scratch.arena, query); + B32 query_has_slash = (query.size != 0 && char_to_correct_slash(query.str[query.size-1]) == '/'); + String8 query_normalized_with_opt_slash = push_str8f(scratch.arena, "%S%s", query_normalized, query_has_slash ? "/" : ""); + DF_PathQuery path_query = df_path_query_from_string(query_normalized_with_opt_slash); + F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); + F32 scroll_bar_dim = floor_f32(ui_top_font_size()*1.5f); + B32 file_selection = !!(ws->query_cmd_spec->info.query.flags & DF_CmdQueryFlag_AllowFiles); + B32 dir_selection = !!(ws->query_cmd_spec->info.query.flags & DF_CmdQueryFlag_AllowFolders); + + //- rjf: get extra state for this view + DF_FileSystemViewState *fs = df_view_user_state(view, DF_FileSystemViewState); + if(fs->initialized == 0) + { + fs->initialized = 1; + fs->path_state_table_size = 256; + fs->path_state_table = push_array(view->arena, DF_FileSystemViewPathState *, fs->path_state_table_size); + fs->cached_files_arena = df_view_push_arena_ext(view); + fs->col_pcts[0] = 0.60f; + fs->col_pcts[1] = 0.20f; + fs->col_pcts[2] = 0.20f; + } + + //- rjf: grab state for the current path + DF_FileSystemViewPathState *ps = 0; + { + String8 key = query_normalized; + U64 hash = df_hash_from_string(key); + U64 slot = hash % fs->path_state_table_size; + for(DF_FileSystemViewPathState *p = fs->path_state_table[slot]; p != 0; p = p->hash_next) + { + if(str8_match(p->normalized_path, key, 0)) + { + ps = p; + break; + } + } + if(ps == 0) + { + ps = push_array(view->arena, DF_FileSystemViewPathState, 1); + ps->hash_next = fs->path_state_table[slot]; + fs->path_state_table[slot] = ps; + ps->normalized_path = push_str8_copy(view->arena, key); + } + } + + //- rjf: get file array from the current path + U64 file_count = fs->cached_file_count; + DF_FileInfo *files = fs->cached_files; + if(!str8_match(fs->cached_files_path, query_normalized_with_opt_slash, 0) || + fs->cached_files_sort_kind != fs->sort_kind || + fs->cached_files_sort_side != fs->sort_side) + { + arena_clear(fs->cached_files_arena); + + //- rjf: store off path that we're gathering from + fs->cached_files_path = push_str8_copy(fs->cached_files_arena, query_normalized_with_opt_slash); + fs->cached_files_sort_kind = fs->sort_kind; + fs->cached_files_sort_side = fs->sort_side; + + //- rjf: use stored path as the new browse path for the whole frontend + // (multiple file system views may conflict here. that's okay. we'll just always + // choose the most recent change to a file browser path, and live with the + // consequences). + { + DF_CmdParams p = df_cmd_params_zero(); + p.file_path = path_query.path; + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_FilePath); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SetCurrentPath)); + } + + //- rjf: get files, filtered + U64 new_file_count = 0; + DF_FileInfoNode *first_file = 0; + DF_FileInfoNode *last_file = 0; + { + OS_FileIter *it = os_file_iter_begin(scratch.arena, path_query.path, 0); + for(OS_FileInfo info = {0}; os_file_iter_next(scratch.arena, it, &info);) + { + FuzzyMatchRangeList match_ranges = fuzzy_match_find(fs->cached_files_arena, path_query.search, info.name); + B32 fits_search = (path_query.search.size == 0 || match_ranges.count == match_ranges.needle_part_count); + B32 fits_dir_only = !!(info.props.flags & FilePropertyFlag_IsFolder) || !dir_selection; + if(fits_search && fits_dir_only) + { + DF_FileInfoNode *node = push_array(scratch.arena, DF_FileInfoNode, 1); + node->file_info.filename = push_str8_copy(fs->cached_files_arena, info.name); + node->file_info.props = info.props; + node->file_info.match_ranges = match_ranges; + SLLQueuePush(first_file, last_file, node); + new_file_count += 1; + } + } + os_file_iter_end(it); + } + + //- rjf: convert list to array + DF_FileInfo *new_files = push_array(fs->cached_files_arena, DF_FileInfo, new_file_count); + { + U64 idx = 0; + for(DF_FileInfoNode *n = first_file; n != 0; n = n->next, idx += 1) + { + new_files[idx] = n->file_info; + } + } + + //- rjf: apply sort + switch(fs->sort_kind) + { + default: + { + if(path_query.search.size != 0) + { + quick_sort(new_files, new_file_count, sizeof(DF_FileInfo), df_qsort_compare_file_info__default_filtered); + } + else + { + quick_sort(new_files, new_file_count, sizeof(DF_FileInfo), df_qsort_compare_file_info__default); + } + }break; + case DF_FileSortKind_Filename: + { + quick_sort(new_files, new_file_count, sizeof(DF_FileInfo), df_qsort_compare_file_info__filename); + }break; + case DF_FileSortKind_LastModified: + { + quick_sort(new_files, new_file_count, sizeof(DF_FileInfo), df_qsort_compare_file_info__last_modified); + }break; + case DF_FileSortKind_Size: + { + quick_sort(new_files, new_file_count, sizeof(DF_FileInfo), df_qsort_compare_file_info__size); + }break; + } + + //- rjf: apply reverse + if(fs->sort_kind != DF_FileSortKind_Null && fs->sort_side == Side_Max) + { + for(U64 idx = 0; idx < new_file_count/2; idx += 1) + { + U64 rev_idx = new_file_count - idx - 1; + Swap(DF_FileInfo, new_files[idx], new_files[rev_idx]); + } + } + + fs->cached_file_count = file_count = new_file_count; + fs->cached_files = files = new_files; + } + + //- rjf: submit best match when hitting enter w/ no selection + if(ps->cursor.y == 0 && ui_slot_press(UI_EventActionSlot_Accept)) + { + FileProperties query_normalized_with_opt_slash_props = os_properties_from_file_path(query_normalized_with_opt_slash); + FileProperties path_query_path_props = os_properties_from_file_path(path_query.path); + + // rjf: command search part is empty, but directory matches some file: + if(path_query_path_props.created != 0 && path_query.search.size == 0) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.file_path = query_normalized_with_opt_slash; + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); + } + + // rjf: command argument exactly matches some file: + else if(query_normalized_with_opt_slash_props.created != 0 && path_query.search.size != 0) + { + // rjf: is a folder -> autocomplete to slash + if(query_normalized_with_opt_slash_props.flags & FilePropertyFlag_IsFolder) + { + String8 new_path = push_str8f(scratch.arena, "%S%S/", path_query.path, path_query.search); + df_view_equip_spec(ws, view, view->spec, df_entity_from_handle(view->entity), new_path, &df_g_nil_cfg_node); + } + + // rjf: is a file -> complete view + else + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.file_path = query_normalized_with_opt_slash; + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); + } + } + + // rjf: command argument is empty, picking folders -> use current folder + else if(path_query.search.size == 0 && dir_selection) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.file_path = path_query.path; + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); + } + + // rjf: command argument does not exactly match any file, but lister results are in: + else if(file_count != 0) + { + String8 filename = files[0].filename; + if(files[0].props.flags & FilePropertyFlag_IsFolder) + { + String8 existing_path = str8_chop_last_slash(path_query.path); + String8 new_path = push_str8f(scratch.arena, "%S/%S/", existing_path, files[0].filename); + df_view_equip_spec(ws, view, view->spec, df_entity_from_handle(view->entity), new_path, &df_g_nil_cfg_node); + } + else + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.file_path = push_str8f(scratch.arena, "%S%S", path_query.path, filename); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); + } + } + + // rjf: command argument does not match any file, and lister is empty (new file) + else + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.file_path = query; + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); + } + } + + //- rjf: build non-scrolled table header + U64 row_num = 1; + F32 **col_pcts = push_array(scratch.arena, F32 *, ArrayCount(fs->col_pcts)); + for(U64 idx = 0; idx < ArrayCount(fs->col_pcts); idx += 1) + { + col_pcts[idx] = &fs->col_pcts[idx]; + } + UI_PrefHeight(ui_px(row_height_px, 1)) UI_Focus(UI_FocusKind_Off) UI_TableF(ArrayCount(fs->col_pcts), col_pcts, "###fs_tbl") + { + UI_TableVector + { + struct + { + DF_FileSortKind kind; + String8 string; + } + kinds[] = + { + { DF_FileSortKind_Filename, str8_lit_comp("Filename") }, + { DF_FileSortKind_LastModified, str8_lit_comp("Last Modified") }, + { DF_FileSortKind_Size, str8_lit_comp("Size") }, + }; + for(U64 idx = 0; idx < ArrayCount(kinds); idx += 1) + { + B32 sorting = (fs->sort_kind == kinds[idx].kind); + UI_TableCell UI_FlagsAdd(sorting ? 0 : UI_BoxFlag_DrawTextWeak) + { + UI_Signal sig = ui_sort_header(sorting, + fs->cached_files_sort_side == Side_Min, + kinds[idx].string); + if(ui_clicked(sig)) + { + if(fs->sort_kind != kinds[idx].kind) + { + fs->sort_kind = kinds[idx].kind; + fs->sort_side = Side_Max; + } + else if(fs->sort_kind == kinds[idx].kind && fs->sort_side == Side_Max) + { + fs->sort_side = Side_Min; + } + else if(fs->sort_kind == kinds[idx].kind && fs->sort_side == Side_Min) + { + fs->sort_kind = DF_FileSortKind_Null; + } + } + } + } + } + } + + //- rjf: build file list + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + Vec2F32 content_dim = dim_2f32(rect); + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = row_height_px; + scroll_list_params.dim_px = v2f32(content_dim.x, content_dim.y-row_height_px); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, file_count+1)); + scroll_list_params.item_range = r1s64(0, file_count+1); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + &ps->cursor, + 0, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + { + // rjf: up-one-directory button (at idx 0) + if(visible_row_range.min == 0) + { + // rjf: build + UI_Signal sig = {0}; + UI_FocusHot(ps->cursor.y == row_num ? UI_FocusKind_On : UI_FocusKind_Off) + { + sig = ui_buttonf("###up_one"); + } + + // rjf: make content + UI_Parent(sig.box) + { + // rjf: icons + DF_Font(ws, DF_FontSlot_Icons) + UI_FontSize(df_font_size_from_slot(ws, DF_FontSlot_Icons)) + UI_PrefWidth(ui_em(3.f, 1.f)) + UI_TextAlignment(UI_TextAlign_Center) + { + ui_label(df_g_icon_kind_text_table[DF_IconKind_LeftArrow]); + } + + // rjf: text + { + ui_label(str8_lit("Up One Directory")); + } + + row_num += 1; + } + + // rjf: click => up one directory + if(ui_clicked(sig)) + { + String8 new_path = str8_chop_last_slash(str8_chop_last_slash(path_query.path)); + new_path = path_normalized_from_string(scratch.arena, new_path); + String8 new_cmd = push_str8f(scratch.arena, "%S%s", new_path, new_path.size != 0 ? "/" : ""); + df_view_equip_spec(ws, view, view->spec, df_entity_from_handle(view->entity), new_cmd, &df_g_nil_cfg_node); + } + } + + // rjf: file buttons + for(U64 row_idx = Max(visible_row_range.min, 1); + row_idx <= visible_row_range.max && row_idx <= file_count; + row_idx += 1, row_num += 1) + { + U64 file_idx = row_idx-1; + DF_FileInfo *file = &files[file_idx]; + B32 file_kb_focus = (ps->cursor.y == (row_idx+1)); + + // rjf: make button + UI_Signal file_sig = {0}; + UI_FocusHot(file_kb_focus ? UI_FocusKind_On : UI_FocusKind_Off) + { + file_sig = ui_buttonf("##%S_%p", file->filename, view); + } + + // rjf: make content + UI_Parent(file_sig.box) + { + UI_PrefWidth(ui_pct(fs->col_pcts[0], 1)) UI_Row + { + // rjf: icon to signify directory + DF_Font(ws, DF_FontSlot_Icons) + UI_FontSize(df_font_size_from_slot(ws, DF_FontSlot_Icons)) + UI_PrefWidth(ui_em(3.f, 1.f)) + UI_TextAlignment(UI_TextAlign_Center) + { + if(file->props.flags & FilePropertyFlag_IsFolder) + { + ui_label((ui_key_match(ui_hot_key(), file_sig.box->key) || file_kb_focus) + ? df_g_icon_kind_text_table[DF_IconKind_FolderOpenFilled] + : df_g_icon_kind_text_table[DF_IconKind_FolderClosedFilled]); + } + else + { + ui_label(df_g_icon_kind_text_table[DF_IconKind_FileOutline]); + } + } + + // rjf: filename + UI_PrefWidth(ui_pct(1, 0)) + { + UI_Box *box = ui_build_box_from_string(UI_BoxFlag_DrawText|UI_BoxFlag_DisableIDString, file->filename); + ui_box_equip_fuzzy_match_ranges(box, &file->match_ranges); + } + } + + // rjf: last-modified time + UI_PrefWidth(ui_pct(fs->col_pcts[1], 1)) UI_Row + UI_PrefWidth(ui_pct(1, 0)) + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + { + DateTime time = date_time_from_dense_time(file->props.modified); + DateTime time_local = os_local_time_from_universal(&time); + String8 string = push_date_time_string(scratch.arena, &time_local); + ui_label(string); + } + + // rjf: file size + UI_PrefWidth(ui_pct(fs->col_pcts[2], 1)) UI_Row + UI_PrefWidth(ui_pct(1, 0)) + { + if(file->props.size != 0) + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(str8_from_memory_size(scratch.arena, file->props.size)); + } + } + } + + // rjf: click => activate this file + if(ui_clicked(file_sig)) + { + String8 existing_path = str8_chop_last_slash(path_query.path); + String8 new_path = push_str8f(scratch.arena, "%S%s%S/", existing_path, existing_path.size != 0 ? "/" : "", file->filename); + new_path = path_normalized_from_string(scratch.arena, new_path); + if(file->props.flags & FilePropertyFlag_IsFolder) + { + String8 new_cmd = push_str8f(scratch.arena, "%S%s", new_path, new_path.size != 0 ? "/" : ""); + df_view_equip_spec(ws, view, view->spec, df_entity_from_handle(view->entity), new_cmd, &df_g_nil_cfg_node); + } + else + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.file_path = new_path; + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); + } + } + } + } + + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: SystemProcesses @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(SystemProcesses) +{ +} + +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(SystemProcesses) +{ + return str8_lit(""); +} + +DF_VIEW_CMD_FUNCTION_DEF(SystemProcesses) +{ +} + +DF_VIEW_UI_FUNCTION_DEF(SystemProcesses) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); + + //- rjf: grab state + typedef struct DF_SystemProcessesViewState DF_SystemProcessesViewState; + struct DF_SystemProcessesViewState + { + B32 initialized; + B32 need_initial_gather; + U32 selected_pid; + Arena *cached_process_arena; + String8 cached_process_arg; + DF_ProcessInfoArray cached_process_array; + }; + DF_SystemProcessesViewState *sp = df_view_user_state(view, DF_SystemProcessesViewState); + if(sp->initialized == 0) + { + sp->initialized = 1; + sp->need_initial_gather = 1; + sp->cached_process_arena = df_view_push_arena_ext(view); + } + + //- rjf: gather list of filtered process infos + String8 query = str8(view->query_buffer, view->query_string_size); + DF_ProcessInfoArray process_info_array = sp->cached_process_array; + if(sp->need_initial_gather || !str8_match(sp->cached_process_arg, query, 0)) + { + arena_clear(sp->cached_process_arena); + sp->need_initial_gather = 0; + sp->cached_process_arg = push_str8_copy(sp->cached_process_arena, query); + DF_ProcessInfoList list = df_process_info_list_from_query(sp->cached_process_arena, query); + sp->cached_process_array = df_process_info_array_from_list(sp->cached_process_arena, list); + process_info_array = sp->cached_process_array; + df_process_info_array_sort_by_strength__in_place(process_info_array); + } + + //- rjf: submit best match when hitting enter w/ no selection + if(sp->selected_pid == 0 && process_info_array.count > 0 && ui_slot_press(UI_EventActionSlot_Accept)) + { + DF_ProcessInfo *info = &process_info_array.v[0]; + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.id = info->info.pid; + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_ID); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); + } + + //- rjf: selected PID -> cursor + Vec2S64 cursor = {0}; + { + for(U64 idx = 0; idx < process_info_array.count; idx += 1) + { + if(process_info_array.v[idx].info.pid == sp->selected_pid) + { + cursor.y = idx+1; + break; + } + } + } + + //- rjf: build contents + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + Vec2F32 content_dim = dim_2f32(rect); + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = row_height_px; + scroll_list_params.dim_px = v2f32(content_dim.x, content_dim.y); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, process_info_array.count)); + scroll_list_params.item_range = r1s64(0, process_info_array.count); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + &cursor, + 0, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + { + //- rjf: build rows + for(U64 idx = visible_row_range.min; + idx <= visible_row_range.max && idx < process_info_array.count; + idx += 1) + { + DF_ProcessInfo *info = &process_info_array.v[idx]; + B32 is_attached = info->is_attached; + UI_Signal sig = {0}; + UI_FocusHot(cursor.y == idx+1 ? UI_FocusKind_On : UI_FocusKind_Off) + { + sig = ui_buttonf("###proc_%i", info->info.pid); + } + UI_Parent(sig.box) + { + // rjf: icon + DF_Font(ws, DF_FontSlot_Icons) + UI_FontSize(df_font_size_from_slot(ws, DF_FontSlot_Icons)) + UI_PrefWidth(ui_em(3.f, 1.f)) + UI_TextAlignment(UI_TextAlign_Center) + { + ui_label(df_g_icon_kind_text_table[DF_IconKind_Threads]); + } + + // rjf: attached indicator + if(is_attached) UI_PrefWidth(ui_text_dim(10, 1)) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + { + UI_Box *attached_label = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "[attached]##attached_label_%i", (int)info->info.pid); + ui_box_equip_fuzzy_match_ranges(attached_label, &info->attached_match_ranges); + } + + // rjf: process name + UI_PrefWidth(ui_text_dim(10, 1)) + { + UI_Box *name_label = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S##name_label_%i", info->info.name, (int)info->info.pid); + ui_box_equip_fuzzy_match_ranges(name_label, &info->name_match_ranges); + } + + // rjf: process number + UI_PrefWidth(ui_text_dim(1, 1)) UI_TextAlignment(UI_TextAlign_Center) + { + ui_labelf("[PID: "); + UI_Box *pid_label = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%i##pid_label", info->info.pid); + ui_box_equip_fuzzy_match_ranges(pid_label, &info->pid_match_ranges); + ui_labelf("]"); + } + } + + // rjf: click => activate this specific process + if(ui_clicked(sig)) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.id = info->info.pid; + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_ID); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); + } + } + } + + //- rjf: selected num -> selected PID + { + if(1 <= cursor.y && cursor.y <= process_info_array.count) + { + sp->selected_pid = process_info_array.v[cursor.y-1].info.pid; + } + else + { + sp->selected_pid = 0; + } + } + + + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: EntityLister @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(EntityLister) +{ +} + +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(EntityLister) +{ + return str8_lit(""); +} + +DF_VIEW_CMD_FUNCTION_DEF(EntityLister) +{ +} + +DF_VIEW_UI_FUNCTION_DEF(EntityLister) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DF_CmdSpec *spec = ws->query_cmd_spec; + DF_EntityKind entity_kind = spec->info.query.entity_kind; + DF_EntityFlags entity_flags_omit = DF_EntityFlag_IsFolder; + F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); + F32 scroll_bar_dim = floor_f32(ui_top_font_size()*1.5f); + + //- rjf: grab state + typedef struct DF_EntityListerViewState DF_EntityListerViewState; + struct DF_EntityListerViewState + { + DF_Handle selected_entity_handle; + }; + DF_EntityListerViewState *fev = df_view_user_state(view, DF_EntityListerViewState); + DF_Handle selected_entity_handle = fev->selected_entity_handle; + DF_Entity *selected_entity = df_entity_from_handle(selected_entity_handle); + + //- rjf: build filtered array of entities + String8 query = str8(view->query_buffer, view->query_string_size); + DF_EntityListerItemList ent_list = df_entity_lister_item_list_from_needle(scratch.arena, entity_kind, entity_flags_omit, query); + DF_EntityListerItemArray ent_arr = df_entity_lister_item_array_from_list(scratch.arena, ent_list); + df_entity_lister_item_array_sort_by_strength__in_place(ent_arr); + + //- rjf: submit best match when hitting enter w/ no selection + if(df_entity_is_nil(df_entity_from_handle(fev->selected_entity_handle)) && ent_arr.count != 0 && ui_slot_press(UI_EventActionSlot_Accept)) + { + DF_Entity *ent = ent_arr.v[0].entity; + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.entity = df_handle_from_entity(ent); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_EntityList); + df_handle_list_push(scratch.arena, ¶ms.entity_list, params.entity); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); + } + + //- rjf: selected entity -> cursor + Vec2S64 cursor = {0}; + { + for(U64 idx = 0; idx < ent_arr.count; idx += 1) + { + if(ent_arr.v[idx].entity == selected_entity) + { + cursor.y = (S64)(idx+1); + break; + } + } + } + + //- rjf: build list + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + Vec2F32 content_dim = dim_2f32(rect); + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = row_height_px; + scroll_list_params.dim_px = v2f32(content_dim.x, content_dim.y); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, ent_arr.count)); + scroll_list_params.item_range = r1s64(0, ent_arr.count); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + &cursor, + 0, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + { + for(S64 idx = visible_row_range.min; + idx <= visible_row_range.max && idx < ent_arr.count; + idx += 1) + { + DF_EntityListerItem item = ent_arr.v[idx]; + DF_Entity *ent = item.entity; + ui_set_next_hover_cursor(OS_Cursor_HandPoint); + ui_set_next_child_layout_axis(Axis2_X); + UI_Box *box = &ui_g_nil_box; + UI_FocusHot(idx+1 == cursor.y ? UI_FocusKind_On : UI_FocusKind_Off) + { + box = ui_build_box_from_stringf(UI_BoxFlag_Clickable| + UI_BoxFlag_DrawBorder| + UI_BoxFlag_DrawBackground| + UI_BoxFlag_DrawHotEffects| + UI_BoxFlag_DrawActiveEffects, + "###ent_btn_%p", ent); + } + UI_Parent(box) + { + DF_IconKind icon_kind = df_g_entity_kind_icon_kind_table[ent->kind]; + if(icon_kind != DF_IconKind_Null) + { + UI_TextAlignment(UI_TextAlign_Center) + DF_Font(ws, DF_FontSlot_Icons) + UI_FontSize(df_font_size_from_slot(ws, DF_FontSlot_Icons)) + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + UI_PrefWidth(ui_text_dim(10, 1)) + ui_label(df_g_icon_kind_text_table[icon_kind]); + } + String8 display_string = df_display_string_from_entity(scratch.arena, ent); + Vec4F32 color = df_rgba_from_entity(ent); + if(color.w != 0) + { + ui_set_next_palette(ui_build_palette(ui_top_palette(), .text = color)); + } + UI_Box *name_label = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S##label_%p", display_string, ent); + ui_box_equip_fuzzy_match_ranges(name_label, &item.name_match_ranges); + } + if(ui_clicked(ui_signal_from_box(box))) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.entity = df_handle_from_entity(ent); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_EntityList); + df_handle_list_push(scratch.arena, ¶ms.entity_list, params.entity); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); + } + } + } + + //- rjf: selected entity num -> handle + { + fev->selected_entity_handle = (1 <= cursor.y && cursor.y <= ent_arr.count) ? df_handle_from_entity(ent_arr.v[cursor.y-1].entity) : df_handle_zero(); + } + + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: SymbolLister @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(SymbolLister) +{ +} + +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(SymbolLister) +{ + return str8_lit(""); +} + +DF_VIEW_CMD_FUNCTION_DEF(SymbolLister) +{ +} + +DF_VIEW_UI_FUNCTION_DEF(SymbolLister) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DI_Scope *di_scope = di_scope_open(); + FZY_Scope *fzy_scope = fzy_scope_open(); + F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); + String8 query = str8(view->query_buffer, view->query_string_size); + DI_KeyList dbgi_keys_list = df_push_active_dbgi_key_list(scratch.arena); + DI_KeyArray dbgi_keys = di_key_array_from_list(scratch.arena, &dbgi_keys_list); + FZY_Params params = {FZY_Target_Procedures, dbgi_keys}; + U64 endt_us = os_now_microseconds()+200; + + //- rjf: grab rdis, make type graphs for each + U64 rdis_count = dbgi_keys.count; + RDI_Parsed **rdis = push_array(scratch.arena, RDI_Parsed *, rdis_count); + TG_Graph **graphs = push_array(scratch.arena, TG_Graph *, rdis_count); + { + for(U64 idx = 0; idx < rdis_count; idx += 1) + { + RDI_Parsed *rdi = di_rdi_from_key(di_scope, &dbgi_keys.v[idx], endt_us); + RDI_TopLevelInfo *tli = rdi_element_from_name_idx(rdi, TopLevelInfo, 0); + rdis[idx] = rdi; + graphs[idx] = tg_graph_begin(rdi_addr_size_from_arch(tli->arch), 256); + } + } + + //- rjf: grab state + typedef struct DF_SymbolListerViewState DF_SymbolListerViewState; + struct DF_SymbolListerViewState + { + Vec2S64 cursor; + }; + DF_SymbolListerViewState *slv = df_view_user_state(view, DF_SymbolListerViewState); + + //- rjf: query -> raddbg, filtered items + U128 fuzzy_search_key = {(U64)view, df_hash_from_string(str8_struct(&view))}; + B32 items_stale = 0; + FZY_ItemArray items = fzy_items_from_key_params_query(fzy_scope, fuzzy_search_key, ¶ms, query, endt_us, &items_stale); + if(items_stale) + { + df_gfx_request_frame(); + } + + //- rjf: submit best match when hitting enter w/ no selection + if(slv->cursor.y == 0 && items.count != 0 && ui_slot_press(UI_EventActionSlot_Accept)) + { + FZY_Item *item = &items.v[0]; + U64 base_idx = 0; + for(U64 rdi_idx = 0; rdi_idx < rdis_count; rdi_idx += 1) + { + RDI_Parsed *rdi = rdis[rdi_idx]; + U64 rdi_procedures_count = 0; + rdi_section_raw_table_from_kind(rdi, RDI_SectionKind_Procedures, &rdi_procedures_count); + if(base_idx <= item->idx && item->idx < base_idx + rdi_procedures_count) + { + RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, item->idx-base_idx); + U64 name_size = 0; + U8 *name_base = rdi_string_from_idx(rdi, procedure->name_string_idx, &name_size); + String8 name = str8(name_base, name_size); + if(name.size != 0) + { + DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); + p.string = name; + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); + } + break; + } + base_idx += rdi_procedures_count; + } + } + + //- rjf: build contents + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + Vec2F32 content_dim = dim_2f32(rect); + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = row_height_px; + scroll_list_params.dim_px = v2f32(content_dim.x, content_dim.y); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, items.count)); + scroll_list_params.item_range = r1s64(0, items.count); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + &slv->cursor, + 0, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + UI_TextRasterFlags(df_raster_flags_from_slot(ws, DF_FontSlot_Code)) + { + //- rjf: build rows + for(U64 idx = visible_row_range.min; + idx <= visible_row_range.max && idx < items.count; + idx += 1) + UI_Focus((slv->cursor.y == idx+1) ? UI_FocusKind_On : UI_FocusKind_Off) + { + FZY_Item *item = &items.v[idx]; + + //- rjf: determine dbgi/rdi to which this item belongs + DI_Key dbgi_key = {0}; + RDI_Parsed *rdi = &di_rdi_parsed_nil; + TG_Graph *graph = 0; + U64 base_idx = 0; + { + for(U64 rdi_idx = 0; rdi_idx < rdis_count; rdi_idx += 1) + { + U64 procedures_count = 0; + rdi_section_raw_table_from_kind(rdis[rdi_idx], RDI_SectionKind_Procedures, &procedures_count); + if(base_idx <= item->idx && item->idx < base_idx + procedures_count) + { + dbgi_key = dbgi_keys.v[rdi_idx]; + rdi = rdis[rdi_idx]; + graph = graphs[rdi_idx]; + break; + } + base_idx += procedures_count; + } + } + + //- rjf: unpack this item's info + RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, item->idx-base_idx); + U64 name_size = 0; + U8 *name_base = rdi_string_from_idx(rdi, procedure->name_string_idx, &name_size); + String8 name = str8(name_base, name_size); + RDI_TypeNode *type_node = rdi_element_from_name_idx(rdi, TypeNodes, procedure->type_idx); + TG_Key type_key = tg_key_ext(tg_kind_from_rdi_type_kind(type_node->kind), procedure->type_idx); + + //- rjf: build item button + ui_set_next_hover_cursor(OS_Cursor_HandPoint); + UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable| + UI_BoxFlag_DrawBackground| + UI_BoxFlag_DrawBorder| + UI_BoxFlag_DrawText| + UI_BoxFlag_DrawHotEffects| + UI_BoxFlag_DrawActiveEffects, + "###procedure_%I64x", item->idx); + UI_Parent(box) UI_PrefWidth(ui_text_dim(10, 1)) + { + UI_Box *box = df_code_label(1.f, 0, df_rgba_from_theme_color(DF_ThemeColor_CodeSymbol), name); + ui_box_equip_fuzzy_match_ranges(box, &item->match_ranges); + if(!tg_key_match(tg_key_zero(), type_key) && graph != 0) + { + String8 type_string = tg_string_from_key(scratch.arena, graph, rdi, type_key); + df_code_label(0.5f, 0, df_rgba_from_theme_color(DF_ThemeColor_TextWeak), type_string); + } + } + + //- rjf: interact + UI_Signal sig = ui_signal_from_box(box); + if(ui_clicked(sig)) + { + DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); + p.string = name; + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_String); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CompleteQuery)); + } + if(ui_hovering(sig)) UI_Tooltip + { + df_code_label(1.f, 0, df_rgba_from_theme_color(DF_ThemeColor_CodeSymbol), name); + DF_Font(ws, DF_FontSlot_Main) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + ui_labelf("Procedure #%I64u", item->idx); + U64 binary_voff = df_voff_from_dbgi_key_symbol_name(&dbgi_key, name); + DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, binary_voff); + if(lines.first != 0) + { + String8 file_path = df_full_path_from_entity(scratch.arena, df_entity_from_handle(lines.first->v.file)); + S64 line_num = lines.first->v.pt.line; + DF_Font(ws, DF_FontSlot_Main) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + ui_labelf("%S:%I64d", file_path, line_num); + } + else + { + DF_Font(ws, DF_FontSlot_Main) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + ui_label(str8_lit("(No source code location found)")); + } + } + } + } + + fzy_scope_close(fzy_scope); + di_scope_close(di_scope); + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Target @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Target) +{ + DF_TargetViewState *tv = df_view_user_state(view, DF_TargetViewState); +} + +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Target) +{ + DF_TargetViewState *tv = df_view_user_state(view, DF_TargetViewState); + return str8_lit(""); +} + +DF_VIEW_CMD_FUNCTION_DEF(Target) +{ + DF_TargetViewState *tv = df_view_user_state(view, DF_TargetViewState); + DF_Entity *entity = df_entity_from_handle(view->entity); + + // rjf: process commands + for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) + { + DF_Cmd *cmd = &n->cmd; + + // rjf: mismatched window/panel => skip + if(df_window_from_handle(cmd->params.window) != ws || + df_panel_from_handle(cmd->params.panel) != panel) + { + continue; + } + + // rjf: process command + DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); + switch(core_cmd_kind) + { + default:break; + case DF_CoreCmdKind_PickFile: + case DF_CoreCmdKind_PickFolder: + { + String8 pick_string = cmd->params.file_path; + DF_Entity *storage_entity = entity; + if(tv->pick_dst_kind != DF_EntityKind_Nil) + { + DF_Entity *child = df_entity_child_from_kind(entity, tv->pick_dst_kind); + if(df_entity_is_nil(child)) + { + child = df_entity_alloc(0, entity, tv->pick_dst_kind); + } + storage_entity = child; + } + df_entity_equip_name(0, storage_entity, pick_string); + }break; + } + } +} + +DF_VIEW_UI_FUNCTION_DEF(Target) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DF_Entity *entity = df_entity_from_handle(view->entity); + DF_EntityList custom_entry_points = df_push_entity_child_list_with_kind(scratch.arena, entity, DF_EntityKind_EntryPointName); + F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); + + //- rjf: grab state + DF_TargetViewState *tv = df_view_user_state(view, DF_TargetViewState); + if(tv->initialized == 0) + { + tv->initialized = 1; + tv->key_pct = 0.2f; + tv->value_pct = 0.8f; + } + + //- rjf: set up key-value-pair info + struct + { + B32 fill_with_file; + B32 fill_with_folder; + B32 use_code_font; + String8 key; + DF_EntityKind storage_child_kind; + String8 current_text; + } + kv_info[] = + { + { 0, 0, 0, str8_lit("Label"), DF_EntityKind_Nil, entity->name }, + { 1, 0, 0, str8_lit("Executable"), DF_EntityKind_Executable, df_entity_child_from_kind(entity, DF_EntityKind_Executable)->name }, + { 0, 0, 0, str8_lit("Arguments"), DF_EntityKind_Arguments, df_entity_child_from_kind(entity, DF_EntityKind_Arguments)->name }, + { 0, 1, 0, str8_lit("Working Directory"), DF_EntityKind_ExecutionPath, df_entity_child_from_kind(entity, DF_EntityKind_ExecutionPath)->name }, + { 0, 0, 1, str8_lit("Entry Point Override"), DF_EntityKind_EntryPointName, df_entity_child_from_kind(entity, DF_EntityKind_EntryPointName)->name }, + }; + + //- rjf: take controls to start/end editing + B32 edit_begin = 0; + B32 edit_end = 0; + B32 edit_commit = 0; + B32 edit_submit = 0; + UI_Focus(UI_FocusKind_On) if(ui_is_focus_active()) + { + if(!tv->input_editing) + { + UI_EventList *events = ui_events(); + for(UI_EventNode *n = events->first; n != 0; n = n->next) + { + if(n->v.string.size != 0 || n->v.flags & UI_EventFlag_Paste) + { + edit_begin = 1; + break; + } + } + if(ui_slot_press(UI_EventActionSlot_Edit)) + { + edit_begin = 1; + } + if(ui_slot_press(UI_EventActionSlot_Accept)) + { + edit_begin = 1; + } + } + if(tv->input_editing) + { + if(ui_slot_press(UI_EventActionSlot_Cancel)) + { + edit_end = 1; + edit_commit = 0; + } + if(ui_slot_press(UI_EventActionSlot_Accept)) + { + edit_end = 1; + edit_commit = 1; + edit_submit = 1; + } + } + } + + //- rjf: build + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = row_height_px; + scroll_list_params.dim_px = dim_2f32(rect); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, (S64)ArrayCount(kv_info))); + scroll_list_params.item_range = r1s64(0, (S64)ArrayCount(kv_info)); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + DF_EntityKind commit_storage_child_kind = DF_EntityKind_Nil; + Vec2S64 next_cursor = tv->cursor; + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + tv->input_editing ? 0 : &tv->cursor, + 0, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + { + next_cursor = tv->cursor; + F32 *col_pcts[] = {&tv->key_pct, &tv->value_pct}; + UI_TableF(ArrayCount(col_pcts), col_pcts, "###target_%p", view) + { + //- rjf: build fixed rows + S64 row_idx = 0; + for(S64 idx = visible_row_range.min; + idx <= visible_row_range.max && idx < ArrayCount(kv_info); + idx += 1, row_idx += 1) + UI_TableVector + { + B32 row_selected = (tv->cursor.y == idx+1); + B32 has_browse = kv_info[idx].fill_with_file || kv_info[idx].fill_with_folder; + + //- rjf: key (label) + UI_TableCell UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + { + if(kv_info[idx].storage_child_kind == DF_EntityKind_EntryPointName) + { + if(df_help_label(str8_lit("Custom Entry Point"))) UI_Tooltip + { + ui_label_multiline(ui_top_font_size()*30.f, str8_lit("By default, the debugger attempts to find a target's entry point with a set of default names, such as:")); + ui_spacer(ui_em(1.5f, 1.f)); + DF_Font(ws, DF_FontSlot_Code) UI_Palette(ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_CodeSymbol))) + { + ui_label(str8_lit("WinMain")); + ui_label(str8_lit("wWinMain")); + ui_label(str8_lit("main")); + ui_label(str8_lit("wmain")); + ui_label(str8_lit("WinMainCRTStartup")); + ui_label(str8_lit("wWinMainCRTStartup")); + } + ui_spacer(ui_em(1.5f, 1.f)); + ui_label_multiline(ui_top_font_size()*30.f, str8_lit("A Custom Entry Point can be used to override these default symbol names with a symbol name of your choosing. If a symbol matching the Custom Entry Point is not found, the debugger will fall back to its default rules.")); + } + } + else + { + ui_build_box_from_string(UI_BoxFlag_Clickable|UI_BoxFlag_DrawText, kv_info[idx].key); + } + } + + //- rjf: value + UI_TableCell + { + // rjf: value editor + UI_WidthFill DF_Font(ws, kv_info[idx].use_code_font ? DF_FontSlot_Code : DF_FontSlot_Main) + { + // rjf: * => focus + B32 value_selected = row_selected && (next_cursor.x == 0 || !has_browse); + + // rjf: begin editing + if(value_selected && edit_begin) + { + tv->input_editing = 1; + tv->input_size = Min(sizeof(tv->input_buffer), kv_info[idx].current_text.size); + MemoryCopy(tv->input_buffer, kv_info[idx].current_text.str, tv->input_size); + tv->input_cursor = txt_pt(1, 1+tv->input_size); + tv->input_mark = txt_pt(1, 1); + } + + // rjf: build main editor ui + UI_Signal sig = {0}; + UI_FocusHot(value_selected ? UI_FocusKind_On : UI_FocusKind_Off) + UI_FocusActive((value_selected && tv->input_editing) ? UI_FocusKind_On : UI_FocusKind_Off) + { + sig = df_line_editf(ws, DF_LineEditFlag_NoBackground, 0, 0, &tv->input_cursor, &tv->input_mark, tv->input_buffer, sizeof(tv->input_buffer), &tv->input_size, 0, kv_info[idx].current_text, "###kv_editor_%i", (S32)idx); + edit_commit = edit_commit || ui_committed(sig); + } + + // rjf: focus panel on press + if(ui_pressed(sig)) + { + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + + // rjf: begin editing on double-click + if(!tv->input_editing && ui_double_clicked(sig)) + { + ui_kill_action(); + tv->input_editing = 1; + tv->input_size = Min(sizeof(tv->input_buffer), kv_info[idx].current_text.size); + MemoryCopy(tv->input_buffer, kv_info[idx].current_text.str, tv->input_size); + tv->input_cursor = txt_pt(1, 1+tv->input_size); + tv->input_mark = txt_pt(1, 1); + } + + // rjf: press on non-selected => commit edit, change selected cell + if(ui_pressed(sig) && !value_selected) + { + edit_end = 1; + edit_commit = tv->input_editing; + next_cursor = v2s64(0, idx+1); + } + + // rjf: apply commit deltas + if(ui_committed(sig)) + { + next_cursor.y += 1; + } + + // rjf: grab commit destination + if(value_selected) + { + commit_storage_child_kind = kv_info[idx].storage_child_kind; + } + } + + // rjf: browse button to fill text field + if(has_browse) UI_PrefWidth(ui_text_dim(10, 1)) + { + UI_FocusHot((row_selected && next_cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) + UI_TextAlignment(UI_TextAlign_Center) + if(ui_clicked(ui_buttonf("Browse..."))) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.cmd_spec = df_cmd_spec_from_core_cmd_kind(kv_info[idx].fill_with_file ? DF_CoreCmdKind_PickFile : DF_CoreCmdKind_PickFolder); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); + tv->pick_dst_kind = kv_info[idx].storage_child_kind; + } + } + } + } + } + } + + //- rjf: apply commit + if(edit_commit) + { + String8 new_string = str8(tv->input_buffer, tv->input_size); + df_state_delta_history_push_batch(df_state_delta_history(), 0); + switch(commit_storage_child_kind) + { + default: + { + DF_Entity *child = df_entity_child_from_kind(entity, commit_storage_child_kind); + if(df_entity_is_nil(child)) + { + child = df_entity_alloc(df_state_delta_history(), entity, commit_storage_child_kind); + } + df_entity_equip_name(df_state_delta_history(), child, new_string); + }break; + case DF_EntityKind_Nil: + { + df_entity_equip_name(df_state_delta_history(), entity, new_string); + }break; + } + } + + //- rjf: apply editing finish + if(edit_end) + { + tv->input_editing = 0; + } + if(edit_submit) + { + next_cursor.y += 1; + } + + //- rjf: apply moves to selection + tv->cursor = next_cursor; + + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Targets @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Targets) +{ +} + +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Targets) +{ + return str8_lit(""); +} + +DF_VIEW_CMD_FUNCTION_DEF(Targets) +{ +} + +DF_VIEW_UI_FUNCTION_DEF(Targets) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DF_EntityList targets_list = df_query_cached_entity_list_with_kind(DF_EntityKind_Target); + String8 query = str8(view->query_buffer, view->query_string_size); + DF_EntityFuzzyItemArray targets = df_entity_fuzzy_item_array_from_entity_list_needle(scratch.arena, &targets_list, query); + F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); + + //- rjf: grab state + typedef struct DF_TargetsViewState DF_TargetsViewState; + struct DF_TargetsViewState + { + B32 selected_add; + DF_Handle selected_target_handle; + S64 selected_column; + }; + DF_TargetsViewState *tv = df_view_user_state(view, DF_TargetsViewState); + + //- rjf: determine table bounds + Vec2S64 table_bounds = {5, (S64)targets.count+1}; + + //- rjf: selection state => cursor + // NOTE(rjf): 0 => nothing, 1 => add new, 2 => first target + Vec2S64 cursor = {0}; + { + DF_Entity *selected_target = df_entity_from_handle(tv->selected_target_handle); + for(U64 idx = 0; idx < targets.count; idx += 1) + { + if(selected_target == targets.v[idx].entity) + { + cursor.y = (S64)idx+2; + break; + } + } + if(tv->selected_add) + { + cursor.y = 1; + } + cursor.x = tv->selected_column; + } + + //- rjf: build + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = row_height_px; + scroll_list_params.dim_px = dim_2f32(rect); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(5, Max(0, (S64)targets.count+1))); + scroll_list_params.item_range = r1s64(0, Max(0, (S64)targets.count+1)); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + &cursor, + 0, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + { + // rjf: add new ctrl + if(visible_row_range.min == 0) + { + UI_Signal add_sig = {0}; + UI_FocusHot(cursor.y == 1 ? UI_FocusKind_On : UI_FocusKind_Off) + add_sig = df_icon_buttonf(ws, DF_IconKind_Add, 0, "Add New Target"); + if(ui_clicked(add_sig)) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.cmd_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_AddTarget); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); + } + } + + // rjf: target rows + for(S64 row_idx = Max(1, visible_row_range.min); + row_idx <= visible_row_range.max && row_idx <= targets.count; + row_idx += 1) + UI_Row + { + DF_Entity *target = targets.v[row_idx-1].entity; + B32 row_selected = ((U64)cursor.y == row_idx+1); + + // rjf: enabled + UI_PrefWidth(ui_em(2.25f, 1)) + UI_FocusHot((row_selected && cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off) + { + UI_Signal sig = df_icon_buttonf(ws, target->b32 ? DF_IconKind_CheckFilled : DF_IconKind_CheckHollow, 0, "###ebl_%p", target); + if(ui_clicked(sig)) + { + DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); + p.entity = df_handle_from_entity(target); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(target->b32 ? DF_CoreCmdKind_DisableTarget : DF_CoreCmdKind_EnableTarget)); + } + } + + // rjf: target name + UI_WidthFill UI_FocusHot((row_selected && cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) + { + df_entity_desc_button(ws, target, &targets.v[row_idx-1].matches, query, 0); + } + + // rjf: controls + UI_PrefWidth(ui_em(2.25f, 1.f)) + { + struct + { + DF_IconKind icon; + String8 text; + DF_CoreCmdKind cmd; + } + ctrls[] = + { + { DF_IconKind_PlayStepForward, str8_lit("Launch and Initialize"), DF_CoreCmdKind_LaunchAndInit }, + { DF_IconKind_Play, str8_lit("Launch and Run"), DF_CoreCmdKind_LaunchAndRun }, + { DF_IconKind_Pencil, str8_lit("Edit"), DF_CoreCmdKind_Target }, + { DF_IconKind_Trash, str8_lit("Delete"), DF_CoreCmdKind_RemoveTarget }, + }; + for(U64 ctrl_idx = 0; ctrl_idx < ArrayCount(ctrls); ctrl_idx += 1) + { + UI_Signal sig = {0}; + UI_FocusHot((row_selected && cursor.x == 2+ctrl_idx) ? UI_FocusKind_On : UI_FocusKind_Off) + { + sig = df_icon_buttonf(ws, ctrls[ctrl_idx].icon, 0, "###%p_ctrl_%i", target, (int)ctrl_idx); + } + if(ui_hovering(sig)) UI_Tooltip + { + ui_label(ctrls[ctrl_idx].text); + } + if(ui_clicked(sig)) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.entity = df_handle_from_entity(target); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); + df_handle_list_push(scratch.arena, ¶ms.entity_list, params.entity); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(ctrls[ctrl_idx].cmd)); + } + } + } + } + } + + //- rjf: commit cursor to selection state + { + tv->selected_column = cursor.x; + tv->selected_target_handle = (1 < cursor.y && cursor.y < targets.count+2) ? df_handle_from_entity(targets.v[cursor.y-2].entity) : df_handle_zero(); + tv->selected_add = (cursor.y == 1); + } + + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: FilePathMap @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(FilePathMap) +{ + DF_FilePathMapViewState *fpms = df_view_user_state(view, DF_FilePathMapViewState); + if(fpms->initialized == 0) + { + fpms->initialized = 1; + fpms->src_column_pct = 0.5f; + fpms->dst_column_pct = 0.5f; + } +} + +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(FilePathMap) +{ + DF_FilePathMapViewState *fpms = df_view_user_state(view, DF_FilePathMapViewState); + return str8_lit(""); +} + +DF_VIEW_CMD_FUNCTION_DEF(FilePathMap) +{ + DF_FilePathMapViewState *fpms = df_view_user_state(view, DF_FilePathMapViewState); + + // rjf: process commands + for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) + { + DF_Cmd *cmd = &n->cmd; + + // rjf: mismatched window/panel => skip + if(df_window_from_handle(cmd->params.window) != ws || + df_panel_from_handle(cmd->params.panel) != panel) + { + continue; + } + + //rjf: process + DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); + switch(core_cmd_kind) + { + default:break; + case DF_CoreCmdKind_PickFile: + case DF_CoreCmdKind_PickFolder: + case DF_CoreCmdKind_PickFileOrFolder: + { + String8 pick_string = cmd->params.file_path; + Side pick_side = fpms->pick_file_dst_side; + DF_Entity *storage_entity = df_entity_from_handle(fpms->pick_file_dst_map); + DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); + p.entity = df_handle_from_entity(storage_entity); + p.file_path = pick_string; + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_Entity); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_FilePath); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(pick_side == Side_Min ? + DF_CoreCmdKind_SetFileOverrideLinkSrc : + DF_CoreCmdKind_SetFileOverrideLinkDst)); + }break; + } + } +} + +DF_VIEW_UI_FUNCTION_DEF(FilePathMap) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DF_EntityList maps_list = df_query_cached_entity_list_with_kind(DF_EntityKind_OverrideFileLink); + DF_EntityArray maps = df_entity_array_from_list(scratch.arena, &maps_list); + F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); + + //- rjf: grab state + DF_FilePathMapViewState *fpms = df_view_user_state(view, DF_FilePathMapViewState); + + //- rjf: take controls to start/end editing + B32 edit_begin = 0; + B32 edit_end = 0; + B32 edit_commit = 0; + B32 edit_submit = 0; + UI_Focus(UI_FocusKind_On) if(ui_is_focus_active()) + { + if(!fpms->input_editing) + { + UI_EventList *events = ui_events(); + for(UI_EventNode *n = events->first; n != 0; n = n->next) + { + if(n->v.string.size != 0 || n->v.flags & UI_EventFlag_Paste) + { + edit_begin = 1; + break; + } + } + if(ui_slot_press(UI_EventActionSlot_Edit)) + { + edit_begin = 1; + } + } + if(fpms->input_editing) + { + if(ui_slot_press(UI_EventActionSlot_Cancel)) + { + edit_end = 1; + edit_commit = 0; + } + if(ui_slot_press(UI_EventActionSlot_Accept)) + { + edit_end = 1; + edit_commit = 1; + edit_submit = 1; + } + } + } + + //- rjf: build + DF_Handle commit_map = df_handle_zero(); + Side commit_side = Side_Invalid; + F32 *col_pcts[] = { &fpms->src_column_pct, &fpms->dst_column_pct }; + Vec2S64 next_cursor = fpms->cursor; + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = row_height_px; + scroll_list_params.dim_px = dim_2f32(rect); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(3, maps.count + 1)); + scroll_list_params.item_range = r1s64(0, maps.count+2); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + fpms->input_editing ? 0 : &fpms->cursor, + 0, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + UI_TableF(ArrayCount(col_pcts), col_pcts, "###tbl") + { + next_cursor = fpms->cursor; + + //- rjf: header + if(visible_row_range.min == 0) UI_TableVector UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + { + UI_TableCell if(df_help_label(str8_lit("Source Path"))) UI_Tooltip + { + ui_label_multiline(ui_top_font_size()*30, str8_lit("When the debugger attempts to open a file or folder at a Source Path specified in this table, it will redirect to the file or folder specified by the Destination Path.")); + } + UI_TableCell ui_label(str8_lit("Destination Path")); + } + + //- rjf: map rows + for(S64 row_idx = Max(1, visible_row_range.min); + row_idx <= visible_row_range.max && row_idx <= maps.count+1; + row_idx += 1) UI_TableVector + { + U64 map_idx = row_idx-1; + DF_Entity *map = (map_idx < maps.count ? maps.v[map_idx] : &df_g_nil_entity); + DF_Entity *map_link = df_entity_from_handle(map->entity_handle); + String8 map_src_path = df_full_path_from_entity(scratch.arena, map); + String8 map_dst_path = df_full_path_from_entity(scratch.arena, map_link); + B32 row_selected = (fpms->cursor.y == row_idx); + + //- rjf: src + UI_TableCell UI_WidthFill + { + //- rjf: editor + { + B32 value_selected = (row_selected && fpms->cursor.x == 0); + + // rjf: begin editing + if(value_selected && edit_begin) + { + fpms->input_editing = 1; + fpms->input_size = Min(sizeof(fpms->input_buffer), map_src_path.size); + MemoryCopy(fpms->input_buffer, map_src_path.str, fpms->input_size); + fpms->input_cursor = txt_pt(1, 1+fpms->input_size); + fpms->input_mark = txt_pt(1, 1); + } + + // rjf: build + UI_Signal sig = {0}; + UI_FocusHot(value_selected ? UI_FocusKind_On : UI_FocusKind_Off) + UI_FocusActive((value_selected && fpms->input_editing) ? UI_FocusKind_On : UI_FocusKind_Off) + { + sig = df_line_editf(ws, DF_LineEditFlag_NoBackground, 0, 0, &fpms->input_cursor, &fpms->input_mark, fpms->input_buffer, sizeof(fpms->input_buffer), &fpms->input_size, 0, map_src_path, "###src_editor_%p", map); + edit_commit = edit_commit || ui_committed(sig); + } + + // rjf: focus panel on press + if(ui_pressed(sig)) + { + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + + // rjf: begin editing on double-click + if(!fpms->input_editing && ui_double_clicked(sig)) + { + fpms->input_editing = 1; + fpms->input_size = Min(sizeof(fpms->input_buffer), map_src_path.size); + MemoryCopy(fpms->input_buffer, map_src_path.str, fpms->input_size); + fpms->input_cursor = txt_pt(1, 1+fpms->input_size); + fpms->input_mark = txt_pt(1, 1); + } + + // rjf: press on non-selected => commit edit, change selected cell + if(ui_pressed(sig) && !value_selected) + { + edit_end = 1; + edit_commit = fpms->input_editing; + next_cursor.x = 0; + next_cursor.y = map_idx+1; + } + + // rjf: store commit information + if(value_selected) + { + commit_side = Side_Min; + commit_map = df_handle_from_entity(map); + } + } + + //- rjf: browse button + UI_FocusHot((row_selected && fpms->cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) + UI_PrefWidth(ui_text_dim(10, 1)) + if(ui_clicked(ui_buttonf("Browse..."))) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.cmd_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_PickFileOrFolder); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); + fpms->pick_file_dst_map = df_handle_from_entity(map); + fpms->pick_file_dst_side = Side_Min; + } + } + + //- rjf: dst + UI_TableCell UI_WidthFill + { + //- rjf: editor + { + B32 value_selected = (row_selected && fpms->cursor.x == 2); + + // rjf: begin editing + if(value_selected && edit_begin) + { + fpms->input_editing = 1; + fpms->input_size = Min(sizeof(fpms->input_buffer), map_dst_path.size); + MemoryCopy(fpms->input_buffer, map_dst_path.str, fpms->input_size); + fpms->input_cursor = txt_pt(1, 1+fpms->input_size); + fpms->input_mark = txt_pt(1, 1); + } + + // rjf: build + UI_Signal sig = {0}; + UI_FocusHot(value_selected ? UI_FocusKind_On : UI_FocusKind_Off) + UI_FocusActive((value_selected && fpms->input_editing) ? UI_FocusKind_On : UI_FocusKind_Off) + { + sig = df_line_editf(ws, DF_LineEditFlag_NoBackground, 0, 0, &fpms->input_cursor, &fpms->input_mark, fpms->input_buffer, sizeof(fpms->input_buffer), &fpms->input_size, 0, map_dst_path, "###dst_editor_%p", map); + edit_commit = edit_commit || ui_committed(sig); + } + + // rjf: focus panel on press + if(ui_pressed(sig)) + { + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + + // rjf: begin editing on double-click + if(!fpms->input_editing && ui_double_clicked(sig)) + { + fpms->input_editing = 1; + fpms->input_size = Min(sizeof(fpms->input_buffer), map_dst_path.size); + MemoryCopy(fpms->input_buffer, map_dst_path.str, fpms->input_size); + fpms->input_cursor = txt_pt(1, 1+fpms->input_size); + fpms->input_mark = txt_pt(1, 1); + } + + // rjf: press on non-selected => commit edit, change selected cell + if(ui_pressed(sig) && !value_selected) + { + edit_end = 1; + edit_commit = fpms->input_editing; + next_cursor.x = 2; + next_cursor.y = map_idx+1; + } + + // rjf: store commit information + if(value_selected) + { + commit_side = Side_Max; + commit_map = df_handle_from_entity(map); + } + } + + //- rjf: browse button + { + UI_FocusHot((row_selected && fpms->cursor.x == 3) ? UI_FocusKind_On : UI_FocusKind_Off) + UI_PrefWidth(ui_text_dim(10, 1)) + if(ui_clicked(ui_buttonf("Browse..."))) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.cmd_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_PickFileOrFolder); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); + fpms->pick_file_dst_map = df_handle_from_entity(map); + fpms->pick_file_dst_side = Side_Max; + } + } + } + } + } + + //- rjf: apply commit + if(edit_commit && commit_side != Side_Invalid) + { + String8 new_string = str8(fpms->input_buffer, fpms->input_size); + DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); + p.entity = commit_map; + p.file_path = new_string; + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_Entity); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_FilePath); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(commit_side == Side_Min ? + DF_CoreCmdKind_SetFileOverrideLinkSrc : + DF_CoreCmdKind_SetFileOverrideLinkDst)); + } + + //- rjf: apply editing finish + if(edit_end) + { + fpms->input_editing = 0; + } + + //- rjf: move down one row if submitted + if(edit_submit) + { + next_cursor.y += 1; + } + + //- rjf: apply moves to selection + fpms->cursor = next_cursor; + + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: AutoViewRules @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(AutoViewRules) +{ + DF_AutoViewRulesViewState *avrs = df_view_user_state(view, DF_AutoViewRulesViewState); + if(avrs->initialized == 0) + { + avrs->initialized = 1; + avrs->src_column_pct = 0.5f; + avrs->dst_column_pct = 0.5f; + } +} + +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(AutoViewRules) +{ + DF_AutoViewRulesViewState *avrs = df_view_user_state(view, DF_AutoViewRulesViewState); + return str8_lit(""); +} + +DF_VIEW_CMD_FUNCTION_DEF(AutoViewRules) +{ + DF_AutoViewRulesViewState *avrs = df_view_user_state(view, DF_AutoViewRulesViewState); + + // rjf: process commands + for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) + { + DF_Cmd *cmd = &n->cmd; + + // rjf: mismatched window/panel => skip + if(df_window_from_handle(cmd->params.window) != ws || + df_panel_from_handle(cmd->params.panel) != panel) + { + continue; + } + } +} + +DF_VIEW_UI_FUNCTION_DEF(AutoViewRules) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DF_EntityList maps_list = df_query_cached_entity_list_with_kind(DF_EntityKind_AutoViewRule); + DF_EntityArray maps = df_entity_array_from_list(scratch.arena, &maps_list); + F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); + + //- rjf: grab state + DF_AutoViewRulesViewState *avrs = df_view_user_state(view, DF_AutoViewRulesViewState); + + //- rjf: take controls to start/end editing + B32 edit_begin = 0; + B32 edit_end = 0; + B32 edit_commit = 0; + B32 edit_submit = 0; + UI_Focus(UI_FocusKind_On) if(ui_is_focus_active()) + { + if(!avrs->input_editing) + { + UI_EventList *events = ui_events(); + for(UI_EventNode *n = events->first; n != 0; n = n->next) + { + if(n->v.string.size != 0 || n->v.flags & UI_EventFlag_Paste) + { + edit_begin = 1; + break; + } + } + if(ui_slot_press(UI_EventActionSlot_Edit)) + { + edit_begin = 1; + } + } + if(avrs->input_editing) + { + if(ui_slot_press(UI_EventActionSlot_Cancel)) + { + edit_end = 1; + edit_commit = 0; + } + if(ui_slot_press(UI_EventActionSlot_Accept)) + { + edit_end = 1; + edit_commit = 1; + edit_submit = 1; + } + } + } + + //- rjf: build + DF_Handle commit_map = df_handle_zero(); + Side commit_side = Side_Invalid; + F32 *col_pcts[] = { &avrs->src_column_pct, &avrs->dst_column_pct }; + Vec2S64 next_cursor = avrs->cursor; + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = row_height_px; + scroll_list_params.dim_px = dim_2f32(rect); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(1, maps.count + 1)); + scroll_list_params.item_range = r1s64(0, maps.count+2); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + avrs->input_editing ? 0 : &avrs->cursor, + 0, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + UI_TableF(ArrayCount(col_pcts), col_pcts, "###tbl") + { + next_cursor = avrs->cursor; + + //- rjf: header + if(visible_row_range.min == 0) UI_TableVector UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + { + UI_TableCell ui_label(str8_lit("Type")); + UI_TableCell ui_label(str8_lit("View Rule")); + } + + //- rjf: map rows + for(S64 row_idx = Max(1, visible_row_range.min); + row_idx <= visible_row_range.max && row_idx <= maps.count+1; + row_idx += 1) UI_TableVector + { + U64 map_idx = row_idx-1; + DF_Entity *map = (map_idx < maps.count ? maps.v[map_idx] : &df_g_nil_entity); + DF_Entity *source = df_entity_child_from_kind(map, DF_EntityKind_Source); + DF_Entity *dest = df_entity_child_from_kind(map, DF_EntityKind_Dest); + String8 type = source->name; + String8 view_rule = dest->name; + B32 row_selected = (avrs->cursor.y == row_idx); + + //- rjf: type + UI_TableCell UI_WidthFill + { + //- rjf: editor + { + B32 value_selected = (row_selected && avrs->cursor.x == 0); + + // rjf: begin editing + if(value_selected && edit_begin) + { + avrs->input_editing = 1; + avrs->input_size = Min(sizeof(avrs->input_buffer), type.size); + MemoryCopy(avrs->input_buffer, type.str, avrs->input_size); + avrs->input_cursor = txt_pt(1, 1+avrs->input_size); + avrs->input_mark = txt_pt(1, 1); + } + + // rjf: build + UI_Signal sig = {0}; + UI_FocusHot(value_selected ? UI_FocusKind_On : UI_FocusKind_Off) + UI_FocusActive((value_selected && avrs->input_editing) ? UI_FocusKind_On : UI_FocusKind_Off) + DF_Font(ws, DF_FontSlot_Code) + { + sig = df_line_editf(ws, DF_LineEditFlag_CodeContents|DF_LineEditFlag_NoBackground|DF_LineEditFlag_DisplayStringIsCode, 0, 0, &avrs->input_cursor, &avrs->input_mark, avrs->input_buffer, sizeof(avrs->input_buffer), &avrs->input_size, 0, type, "###src_editor_%p", map); + edit_commit = edit_commit || ui_committed(sig); + } + + // rjf: focus panel on press + if(ui_pressed(sig)) + { + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + + // rjf: begin editing on double-click + if(!avrs->input_editing && ui_double_clicked(sig)) + { + avrs->input_editing = 1; + avrs->input_size = Min(sizeof(avrs->input_buffer), type.size); + MemoryCopy(avrs->input_buffer, type.str, avrs->input_size); + avrs->input_cursor = txt_pt(1, 1+avrs->input_size); + avrs->input_mark = txt_pt(1, 1); + } + + // rjf: press on non-selected => commit edit, change selected cell + if(ui_pressed(sig) && !value_selected) + { + edit_end = 1; + edit_commit = avrs->input_editing; + next_cursor.x = 0; + next_cursor.y = map_idx+1; + } + + // rjf: store commit information + if(value_selected) + { + commit_side = Side_Min; + commit_map = df_handle_from_entity(map); + } + } + } + + //- rjf: dst + UI_TableCell UI_WidthFill + { + //- rjf: editor + { + B32 value_selected = (row_selected && avrs->cursor.x == 1); + + // rjf: begin editing + if(value_selected && edit_begin) + { + avrs->input_editing = 1; + avrs->input_size = Min(sizeof(avrs->input_buffer), view_rule.size); + MemoryCopy(avrs->input_buffer, view_rule.str, avrs->input_size); + avrs->input_cursor = txt_pt(1, 1+avrs->input_size); + avrs->input_mark = txt_pt(1, 1); + } + + // rjf: build + UI_Signal sig = {0}; + UI_FocusHot(value_selected ? UI_FocusKind_On : UI_FocusKind_Off) + UI_FocusActive((value_selected && avrs->input_editing) ? UI_FocusKind_On : UI_FocusKind_Off) + DF_Font(ws, DF_FontSlot_Code) + { + sig = df_line_editf(ws, DF_LineEditFlag_CodeContents|DF_LineEditFlag_NoBackground|DF_LineEditFlag_DisplayStringIsCode, 0, 0, &avrs->input_cursor, &avrs->input_mark, avrs->input_buffer, sizeof(avrs->input_buffer), &avrs->input_size, 0, view_rule, "###dst_editor_%p", map); + edit_commit = edit_commit || ui_committed(sig); + } + + // rjf: focus panel on press + if(ui_pressed(sig)) + { + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + + // rjf: begin editing on double-click + if(!avrs->input_editing && ui_double_clicked(sig)) + { + avrs->input_editing = 1; + avrs->input_size = Min(sizeof(avrs->input_buffer), view_rule.size); + MemoryCopy(avrs->input_buffer, view_rule.str, avrs->input_size); + avrs->input_cursor = txt_pt(1, 1+avrs->input_size); + avrs->input_mark = txt_pt(1, 1); + } + + // rjf: press on non-selected => commit edit, change selected cell + if(ui_pressed(sig) && !value_selected) + { + edit_end = 1; + edit_commit = avrs->input_editing; + next_cursor.x = 1; + next_cursor.y = map_idx+1; + } + + // rjf: store commit information + if(value_selected) + { + commit_side = Side_Max; + commit_map = df_handle_from_entity(map); + } + } + } + } + } + + //- rjf: apply commit + if(edit_commit && commit_side != Side_Invalid) + { + String8 new_string = str8(avrs->input_buffer, avrs->input_size); + DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); + p.entity = commit_map; + p.string = new_string; + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_Entity); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_FilePath); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(commit_side == Side_Min ? + DF_CoreCmdKind_SetAutoViewRuleType : + DF_CoreCmdKind_SetAutoViewRuleViewRule)); + } + + //- rjf: apply editing finish + if(edit_end) + { + avrs->input_editing = 0; + } + + //- rjf: move down one row if submitted + if(edit_submit) + { + next_cursor.y += 1; + } + + //- rjf: apply moves to selection + avrs->cursor = next_cursor; + + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Scheduler @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Scheduler) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Scheduler) {return str8_lit("");} +DF_VIEW_CMD_FUNCTION_DEF(Scheduler) {} +DF_VIEW_UI_FUNCTION_DEF(Scheduler) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + String8 query = str8(view->query_buffer, view->query_string_size); + DF_CtrlCtx ctrl_ctx = df_ctrl_ctx_from_view(ws, view); + + //- rjf: get state + typedef struct DF_SchedulerViewState DF_SchedulerViewState; + struct DF_SchedulerViewState + { + DF_Handle selected_entity; + S64 selected_column; + }; + DF_SchedulerViewState *sv = df_view_user_state(view, DF_SchedulerViewState); + + //- rjf: get entities + DF_EntityList machines = df_query_cached_entity_list_with_kind(DF_EntityKind_Machine); + DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); + DF_EntityList threads = df_query_cached_entity_list_with_kind(DF_EntityKind_Thread); + + //- rjf: produce list of items; no query -> all entities, in tree; query -> only show threads + DF_EntityFuzzyItemArray items = {0}; + ProfScope("query -> entities") + { + if(query.size == 0) + { + //- rjf: build flat array of entities, arranged into row order + DF_EntityArray entities = {0}; + { + entities.count = machines.count+processes.count+threads.count; + entities.v = push_array_no_zero(scratch.arena, DF_Entity *, entities.count); + U64 idx = 0; + for(DF_EntityNode *machine_n = machines.first; machine_n != 0; machine_n = machine_n->next) + { + DF_Entity *machine = machine_n->entity; + entities.v[idx] = machine; + idx += 1; + for(DF_EntityNode *process_n = processes.first; process_n != 0; process_n = process_n->next) + { + DF_Entity *process = process_n->entity; + if(df_entity_ancestor_from_kind(process, DF_EntityKind_Machine) != machine) + { + continue; + } + entities.v[idx] = process; + idx += 1; + for(DF_EntityNode *thread_n = threads.first; thread_n != 0; thread_n = thread_n->next) + { + DF_Entity *thread = thread_n->entity; + if(df_entity_ancestor_from_kind(thread, DF_EntityKind_Process) != process) + { + continue; + } + entities.v[idx] = thread; + idx += 1; + } + } + } + } + + //- rjf: entities -> fuzzy-filtered entities + items = df_entity_fuzzy_item_array_from_entity_array_needle(scratch.arena, &entities, query); + } + else + { + items = df_entity_fuzzy_item_array_from_entity_list_needle(scratch.arena, &threads, query); + } + } + + //- rjf: selected column/entity -> selected cursor + Vec2S64 cursor = {sv->selected_column}; + for(U64 idx = 0; idx < items.count; idx += 1) + { + if(items.v[idx].entity == df_entity_from_handle(sv->selected_entity)) + { + cursor.y = (S64)(idx+1); + break; + } + } + + //- rjf: build table + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*2.5f); + scroll_list_params.dim_px = dim_2f32(rect); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(4, items.count)); + scroll_list_params.item_range = r1s64(0, items.count); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + &cursor, + 0, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + UI_TableF(0, 0, "scheduler_table") + { + Vec2S64 next_cursor = cursor; + for(U64 idx = visible_row_range.min; + idx <= visible_row_range.max && idx < items.count; + idx += 1) + { + DF_Entity *entity = items.v[idx].entity; + B32 row_is_selected = (cursor.y == (S64)(idx+1)); + F32 depth = 0.f; + if(query.size == 0) switch(entity->kind) + { + default:{}break; + case DF_EntityKind_Machine:{depth = 0.f;}break; + case DF_EntityKind_Process:{depth = 1.f;}break; + case DF_EntityKind_Thread: {depth = 2.f;}break; + } + Rng1S64 desc_col_rng = r1s64(1, 1); + switch(entity->kind) + { + default:{}break; + case DF_EntityKind_Machine:{desc_col_rng = r1s64(1, 4);}break; + case DF_EntityKind_Process:{desc_col_rng = r1s64(1, 1);}break; + case DF_EntityKind_Thread: {desc_col_rng = r1s64(1, 1);}break; + } + UI_NamedTableVectorF("entity_row_%p", entity) + { + UI_TableCellSized(ui_em(1.5f*depth, 1.f)) {} + UI_TableCellSized(ui_em(2.25f, 1.f)) UI_FocusHot((row_is_selected && cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off) + { + B32 frozen = df_entity_is_frozen(entity); + UI_Palette *palette = ui_top_palette(); + if(frozen) + { + palette = df_palette_from_code(ws, DF_PaletteCode_NegativePopButton); + } + else + { + palette = df_palette_from_code(ws, DF_PaletteCode_PositivePopButton); + } + UI_Signal sig = {0}; + UI_Palette(palette) sig = df_icon_buttonf(ws, frozen ? DF_IconKind_Locked : DF_IconKind_Unlocked, 0, "###lock_%p", entity); + if(ui_clicked(sig)) + { + DF_CoreCmdKind cmd_kind = frozen ? DF_CoreCmdKind_ThawEntity : DF_CoreCmdKind_FreezeEntity; + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.entity = df_handle_from_entity(entity); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(cmd_kind)); + } + } + UI_TableCellSized(ui_pct(1, 0)) + UI_FocusHot((row_is_selected && desc_col_rng.min <= cursor.x && cursor.x <= desc_col_rng.max) ? UI_FocusKind_On : UI_FocusKind_Off) + { + df_entity_desc_button(ws, entity, &items.v[idx].matches, query, 0); + } + switch(entity->kind) + { + default:{}break; + case DF_EntityKind_Machine: + { + + }break; + case DF_EntityKind_Process: + { + UI_TableCellSized(ui_children_sum(1.f)) UI_FocusHot((row_is_selected && cursor.x == 2) ? UI_FocusKind_On : UI_FocusKind_Off) + { + UI_PrefWidth(ui_text_dim(10, 1)) + UI_TextAlignment(UI_TextAlign_Center) + if(ui_clicked(ui_buttonf("Detach"))) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.entity = df_handle_from_entity(entity); + df_handle_list_push(scratch.arena, ¶ms.entity_list, df_handle_from_entity(entity)); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_Entity); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_EntityList); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Detach)); + } + } + UI_TableCellSized(ui_em(2.25f, 1.f)) UI_FocusHot((row_is_selected && cursor.x == 3) ? UI_FocusKind_On : UI_FocusKind_Off) + { + if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_Redo, 0, "###retry"))) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + df_handle_list_push(scratch.arena, ¶ms.entity_list, df_handle_from_entity(entity)); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_EntityList); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Restart)); + } + } + UI_TableCellSized(ui_em(2.25f, 1.f)) UI_FocusHot((row_is_selected && cursor.x == 4) ? UI_FocusKind_On : UI_FocusKind_Off) + { + DF_Palette(ws, DF_PaletteCode_NegativePopButton) + if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_X, 0, "###kill"))) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + df_handle_list_push(scratch.arena, ¶ms.entity_list, df_handle_from_entity(entity)); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_EntityList); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Kill)); + } + } + }break; + case DF_EntityKind_Thread: + { + UI_TableCellSized(ui_children_sum(1.f)) UI_FocusHot((row_is_selected && cursor.x >= 2) ? UI_FocusKind_On : UI_FocusKind_Off) + { + DF_Entity *process = df_entity_ancestor_from_kind(entity, DF_EntityKind_Process); + U64 rip_vaddr = df_query_cached_rip_from_thread(entity); + DF_Entity *module = df_module_from_process_vaddr(process, rip_vaddr); + U64 rip_voff = df_voff_from_vaddr(module, rip_vaddr); + DI_Key dbgi_key = df_dbgi_key_from_module(module); + DF_LineList lines = df_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, rip_voff); + if(lines.first != 0) + { + DF_Entity *file = df_entity_from_handle(lines.first->v.file); + if(!df_entity_is_nil(file)) + { + UI_PrefWidth(ui_children_sum(0)) df_entity_src_loc_button(ws, file, lines.first->v.pt); + } + } + } + }break; + } + } + } + cursor = next_cursor; + } + + //- rjf: selected num -> selected entity + sv->selected_column = cursor.x; + sv->selected_entity = (1 <= cursor.y && cursor.y <= items.count) ? df_handle_from_entity(items.v[cursor.y-1].entity) : df_handle_zero(); + + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: CallStack @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(CallStack) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(CallStack) { return str8_lit(""); } +DF_VIEW_CMD_FUNCTION_DEF(CallStack) {} +DF_VIEW_UI_FUNCTION_DEF(CallStack) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DI_Scope *scope = di_scope_open(); + DF_CtrlCtx ctrl_ctx = df_ctrl_ctx_from_view(ws, view); + DF_Entity *thread = df_entity_from_handle(ctrl_ctx.thread); + Architecture arch = df_architecture_from_entity(thread); + DF_Entity *process = thread->parent; + Vec4F32 thread_color = ui_top_palette()->text; + if(thread->flags & DF_EntityFlag_HasColor) + { + thread_color = df_rgba_from_entity(thread); + } + CTRL_Unwind base_unwind = df_query_cached_unwind_from_thread(thread); + DF_Unwind rich_unwind = df_unwind_from_ctrl_unwind(scratch.arena, scope, process, &base_unwind); + + //- rjf: grab state + typedef struct DF_CallStackViewState DF_CallStackViewState; + struct DF_CallStackViewState + { + B32 initialized; + Vec2S64 cursor; + Vec2S64 mark; + F32 selection_col_pct; + F32 module_col_pct; + F32 function_name_col_pct; + F32 addr_col_pct; + }; + DF_CallStackViewState *cs = df_view_user_state(view, DF_CallStackViewState); + if(cs->initialized == 0) + { + cs->initialized = 1; + cs->selection_col_pct = 0.05f; + cs->module_col_pct = 0.35f; + cs->function_name_col_pct = 0.4f; + cs->addr_col_pct = 0.2f; + } + + //- rjf: build ui + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*2.5f); + scroll_list_params.dim_px = dim_2f32(rect); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(3, rich_unwind.frames.count)); + scroll_list_params.item_range = r1s64(0, rich_unwind.frames.count+1); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + &cs->cursor, + &cs->mark, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + { + Vec2S64 next_cursor = cs->cursor; + + //- rjf: build table + if(df_ctrl_targets_running()) + { + ui_set_next_flags(UI_BoxFlag_Disabled); + } + F32 *col_pcts[] = + { + &cs->selection_col_pct, + &cs->function_name_col_pct, + &cs->addr_col_pct, + &cs->module_col_pct, + }; + UI_TableF(ArrayCount(col_pcts), col_pcts, "###tbl") + { + //- rjf: header + if(visible_row_range.min == 0) UI_TableVector UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + { + UI_TableCell {} + UI_TableCell ui_label(str8_lit("Function Name")); + UI_TableCell ui_label(str8_lit("Address")); + UI_TableCell ui_label(str8_lit("Module")); + } + + //- rjf: frame rows + for(S64 row_num = visible_row_range.min; + row_num <= visible_row_range.max && row_num <= rich_unwind.frames.count; + row_num += 1) + { + if(row_num == 0) + { + continue; + } + B32 row_selected = (cs->cursor.y == row_num); + + // rjf: unpack frame + U64 frame_idx = row_num-1; + DF_UnwindFrame *frame = &rich_unwind.frames.v[frame_idx]; + U64 rip_vaddr = regs_rip_from_arch_block(thread->arch, frame->regs); + DF_Entity *module = df_module_from_process_vaddr(process, rip_vaddr); + B32 frame_valid = (rip_vaddr != 0); + TG_Graph *graph = tg_graph_begin(bit_size_from_arch(thread->arch)/8, 256); + String8 symbol_name = {0}; + String8 symbol_type_string = {0}; + if(frame->procedure != 0) + { + symbol_name.str = rdi_name_from_procedure(frame->rdi, frame->procedure, &symbol_name.size); + RDI_TypeNode *type = rdi_element_from_name_idx(frame->rdi, TypeNodes, frame->procedure->type_idx); + symbol_type_string = tg_string_from_key(scratch.arena, graph, frame->rdi, tg_key_ext(tg_kind_from_rdi_type_kind(type->kind), frame->procedure->type_idx)); + } + if(frame->inline_site != 0) + { + symbol_name.str = rdi_string_from_idx(frame->rdi, frame->inline_site->name_string_idx, &symbol_name.size); + RDI_TypeNode *type = rdi_element_from_name_idx(frame->rdi, TypeNodes, frame->inline_site->type_idx); + symbol_type_string = tg_string_from_key(scratch.arena, graph, frame->rdi, tg_key_ext(tg_kind_from_rdi_type_kind(type->kind), frame->inline_site->type_idx)); + } + + // rjf: build row + if(frame_valid) UI_NamedTableVectorF("###callstack_%p_%I64x", view, frame_idx) + { + // rjf: build cell for selection + UI_TableCell + DF_Font(ws, DF_FontSlot_Icons) + UI_FontSize(df_font_size_from_slot(ws, DF_FontSlot_Icons)) + UI_WidthFill + UI_TextAlignment(UI_TextAlign_Center) + UI_FocusHot((row_selected && cs->cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off) + { + String8 selected_string = {0}; + if(ctrl_ctx.unwind_count == frame->base_unwind_idx && + ctrl_ctx.inline_unwind_count == frame->inline_unwind_idx) + { + selected_string = df_g_icon_kind_text_table[DF_IconKind_RightArrow]; + ui_set_next_palette(ui_build_palette(ui_top_palette(), .text = thread_color)); + } + UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|UI_BoxFlag_DrawText, "%S###selection_%i", selected_string, + (int)frame_idx); + UI_Signal sig = ui_signal_from_box(box); + if(ui_pressed(sig)) + { + next_cursor = v2s64(0, (S64)frame_idx+1); + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + if(ui_double_clicked(sig) || sig.f&UI_SignalFlag_KeyboardPressed) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_BaseUnwindIndex); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_InlineUnwindIndex); + params.base_unwind_index = frame->base_unwind_idx; + params.inline_unwind_index = frame->inline_unwind_idx; + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectUnwind)); + } + } + + // rjf: build cell for function header + UI_TableCell DF_Font(ws, DF_FontSlot_Code) + UI_FocusHot((row_selected && cs->cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) + { + ui_set_next_child_layout_axis(Axis2_X); + UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|UI_BoxFlag_Clip, "frame_%I64x", frame_idx); + UI_Parent(box) + { + if(frame->inline_site != 0) + { + UI_PrefWidth(ui_text_dim(10, 1)) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + { + ui_label(str8_lit("[inlined]")); + } + } + if(symbol_name.size == 0) + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(str8_lit("[unknown symbol]")); + } + else UI_WidthFill + { + D_FancyStringList symbol_name_fstrs = df_fancy_string_list_from_code_string(scratch.arena, 1.f, 0, df_rgba_from_theme_color(DF_ThemeColor_CodeSymbol), symbol_name); + D_FancyStringList symbol_type_fstrs = df_fancy_string_list_from_code_string(scratch.arena, 0.5f, 0, df_rgba_from_theme_color(DF_ThemeColor_CodeDefault), symbol_type_string); + D_FancyStringList fstrs = {0}; + d_fancy_string_list_concat_in_place(&fstrs, &symbol_name_fstrs); + D_FancyString sep = {ui_top_font(), str8_lit(": "), ui_top_palette()->colors[UI_ColorCode_TextWeak], ui_top_font_size()}; + d_fancy_string_list_push(scratch.arena, &fstrs, &sep); + d_fancy_string_list_concat_in_place(&fstrs, &symbol_type_fstrs); + UI_Box *label = ui_build_box_from_key(UI_BoxFlag_DrawText, ui_key_zero()); + ui_box_equip_display_fancy_strings(label, &fstrs); + } + } + UI_Signal sig = ui_signal_from_box(box); + if(ui_pressed(sig)) + { + next_cursor = v2s64(1, (S64)frame_idx+1); + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + if(ui_double_clicked(sig) || sig.f&UI_SignalFlag_KeyboardPressed) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_BaseUnwindIndex); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_InlineUnwindIndex); + params.base_unwind_index = frame->base_unwind_idx; + params.inline_unwind_index = frame->inline_unwind_idx; + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectUnwind)); + } + } + + // rjf: build cell for rip + UI_TableCell + UI_FocusHot((row_selected && cs->cursor.x == 2) ? UI_FocusKind_On : UI_FocusKind_Off) + { + UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_DrawText|UI_BoxFlag_Clickable, "0x%I64x", rip_vaddr); + UI_Signal sig = ui_signal_from_box(box); + if(ui_pressed(sig)) + { + next_cursor = v2s64(2, (S64)frame_idx+1); + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + if(ui_double_clicked(sig) || sig.f&UI_SignalFlag_KeyboardPressed) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_BaseUnwindIndex); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_InlineUnwindIndex); + params.base_unwind_index = frame->base_unwind_idx; + params.inline_unwind_index = frame->inline_unwind_idx; + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectUnwind)); + } + } + + // rjf: build cell for module + UI_TableCell UI_FocusHot((row_selected && cs->cursor.x == 3) ? UI_FocusKind_On : UI_FocusKind_Off) + { + UI_Signal sig = {0}; + if(df_entity_is_nil(module)) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + { + UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_DrawText|UI_BoxFlag_Clickable, "(No Module)###moduleless_frame_%I64x", frame_idx); + sig = ui_signal_from_box(box); + } + else + { + sig = df_entity_desc_button(ws, module, 0, str8_zero(), 1); + } + if(ui_pressed(sig)) + { + next_cursor = v2s64(3, (S64)frame_idx+1); + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + if(ui_double_clicked(sig) || sig.f&UI_SignalFlag_KeyboardPressed) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_BaseUnwindIndex); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_InlineUnwindIndex); + params.base_unwind_index = frame->base_unwind_idx; + params.inline_unwind_index = frame->inline_unwind_idx; + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SelectUnwind)); + } + } + } + + // rjf: end if hit invalid frame + if(frame_valid == 0) + { + break; + } + } + + // rjf: apply moves to selection + cs->cursor = next_cursor; + } + } + + di_scope_close(scope); + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Modules @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Modules) +{ + DF_ModulesViewState *mv = df_view_user_state(view, DF_ModulesViewState); + if(mv->initialized == 0) + { + mv->initialized = 1; + mv->idx_col_pct = 0.05f; + mv->desc_col_pct = 0.15f; + mv->range_col_pct = 0.30f; + mv->dbg_col_pct = 0.50f; + } +} + +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Modules) {return str8_lit("");} + +DF_VIEW_CMD_FUNCTION_DEF(Modules) +{ + DF_ModulesViewState *mv = df_view_user_state(view, DF_ModulesViewState); + for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) + { + DF_Cmd *cmd = &n->cmd; + + // rjf: mismatched window/panel => skip + if(df_window_from_handle(cmd->params.window) != ws || + df_panel_from_handle(cmd->params.panel) != panel) + { + continue; + } + + //rjf: process + DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); + switch(core_cmd_kind) + { + default:break; + case DF_CoreCmdKind_PickFile: + { + Temp scratch = scratch_begin(0, 0); + String8 pick_string = cmd->params.file_path; + DF_Entity *module = df_entity_from_handle(mv->pick_file_dst_entity); + if(module->kind == DF_EntityKind_Module) + { + String8 exe_path = module->name; + String8 dbg_path = pick_string; + // TODO(rjf) + } + scratch_end(scratch); + }break; + } + } +} + +DF_VIEW_UI_FUNCTION_DEF(Modules) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + DI_Scope *scope = di_scope_open(); + String8 query = str8(view->query_buffer, view->query_string_size); + + //- rjf: get state + DF_ModulesViewState *mv = df_view_user_state(view, DF_ModulesViewState); + F32 *col_pcts[] = {&mv->idx_col_pct, &mv->desc_col_pct, &mv->range_col_pct, &mv->dbg_col_pct}; + + //- rjf: get entities + DF_EntityList processes = df_query_cached_entity_list_with_kind(DF_EntityKind_Process); + DF_EntityList modules = df_query_cached_entity_list_with_kind(DF_EntityKind_Module); + + //- rjf: make filtered item array + DF_EntityFuzzyItemArray items = {0}; + if(query.size == 0) + { + DF_EntityArray entities = {0}; + { + entities.count = processes.count+modules.count; + entities.v = push_array_no_zero(scratch.arena, DF_Entity *, entities.count); + U64 idx = 0; + for(DF_EntityNode *process_n = processes.first; process_n != 0; process_n = process_n->next) + { + DF_Entity *process = process_n->entity; + entities.v[idx] = process; + idx += 1; + for(DF_EntityNode *module_n = modules.first; module_n != 0; module_n = module_n->next) + { + DF_Entity *module = module_n->entity; + if(df_entity_ancestor_from_kind(module, DF_EntityKind_Process) != process) + { + continue; + } + entities.v[idx] = module; + idx += 1; + } + } + } + items = df_entity_fuzzy_item_array_from_entity_array_needle(scratch.arena, &entities, query); + } + else + { + items = df_entity_fuzzy_item_array_from_entity_list_needle(scratch.arena, &modules, query); + } + + //- rjf: selected column/entity -> selected cursor + Vec2S64 cursor = {mv->selected_column}; + for(U64 idx = 0; idx < items.count; idx += 1) + { + if(items.v[idx].entity == df_entity_from_handle(mv->selected_entity)) + { + cursor.y = (S64)(idx+1); + break; + } + } + + ////////////////////////////// + //- rjf: do start/end editing interaction + // + B32 edit_begin = 0; + B32 edit_commit = 0; + B32 edit_end = 0; + B32 edit_submit = 0; + if(!mv->txt_editing && ui_is_focus_active()) + { + UI_EventList *events = ui_events(); + for(UI_EventNode *n = events->first; n != 0; n = n->next) + { + if(n->v.string.size != 0 || n->v.flags & UI_EventFlag_Paste) + { + edit_begin = 1; + break; + } + } + if(ui_slot_press(UI_EventActionSlot_Edit)) + { + edit_begin = 1; + } + } + if(mv->txt_editing && ui_is_focus_active()) + { + if(ui_slot_press(UI_EventActionSlot_Cancel)) + { + edit_end = 1; + edit_commit = 0; + } + if(ui_slot_press(UI_EventActionSlot_Accept)) + { + edit_end = 1; + edit_commit = 1; + edit_submit = 1; + } + } + + //- rjf: build table + DF_Entity *commit_module = &df_g_nil_entity; + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*2.5f); + scroll_list_params.dim_px = dim_2f32(rect); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(3, items.count)); + scroll_list_params.item_range = r1s64(0, items.count); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + mv->txt_editing ? 0 : &cursor, + 0, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + UI_TableF(ArrayCount(col_pcts), col_pcts, "modules_table") + { + Vec2S64 next_cursor = cursor; + U64 idx_in_process = 0; + for(U64 idx = 0; idx < items.count; idx += 1) + { + DF_Entity *entity = items.v[idx].entity; + B32 row_is_selected = (cursor.y == (S64)(idx+1)); + idx_in_process += (entity->kind == DF_EntityKind_Module); + if(visible_row_range.min <= idx && idx <= visible_row_range.max) + { + switch(entity->kind) + { + default:{}break; + case DF_EntityKind_Process: + { + UI_NamedTableVectorF("process_%p", entity) + { + UI_TableCellSized(ui_pct(1, 0)) UI_FocusHot((row_is_selected) ? UI_FocusKind_On : UI_FocusKind_Off) + { + df_entity_desc_button(ws, entity, &items.v[idx].matches, query, 0); + } + } + idx_in_process = 0; + }break; + case DF_EntityKind_Module: + UI_NamedTableVectorF("module_%p", entity) + { + UI_TableCell UI_TextAlignment(UI_TextAlign_Center) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + { + ui_labelf("%I64u", idx_in_process); + } + UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off) + { + df_entity_desc_button(ws, entity, &items.v[idx].matches, query, 1); + } + UI_TableCell DF_Font(ws, DF_FontSlot_Code) UI_FocusHot((row_is_selected && cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) + { + UI_Box *range_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|UI_BoxFlag_DrawText, "[0x%I64x, 0x%I64x)###vaddr_range_%p", entity->vaddr_rng.min, entity->vaddr_rng.max, entity); + UI_Signal sig = ui_signal_from_box(range_box); + if(ui_pressed(sig)) + { + next_cursor = v2s64(1, (S64)idx+1); + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + } + UI_TableCell + { + B32 txt_is_selected = (row_is_selected && cursor.x == 2); + B32 brw_is_selected = (row_is_selected && cursor.x == 3); + + // rjf: unpack module info + DI_Key dbgi_key = df_dbgi_key_from_module(entity); + String8 dbgi_path = dbgi_key.path; + RDI_Parsed *rdi = di_rdi_from_key(scope, &dbgi_key, 0); + B32 dbgi_is_valid = (rdi != &di_rdi_parsed_nil); + + // rjf: begin editing + if(txt_is_selected && edit_begin) + { + mv->txt_editing = 1; + mv->txt_size = Min(sizeof(mv->txt_buffer), dbgi_path.size); + MemoryCopy(mv->txt_buffer, dbgi_path.str, mv->txt_size); + mv->txt_cursor = txt_pt(1, 1+mv->txt_size); + mv->txt_mark = txt_pt(1, 1); + } + + // rjf: build + UI_Signal sig = {0}; + UI_FocusHot(txt_is_selected ? UI_FocusKind_On : UI_FocusKind_Off) + UI_FocusActive((txt_is_selected && mv->txt_editing) ? UI_FocusKind_On : UI_FocusKind_Off) + UI_WidthFill + { + UI_Palette(dbgi_is_valid ? ui_top_palette() : ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_TextNegative))) + sig = df_line_editf(ws, DF_LineEditFlag_NoBackground, 0, 0, &mv->txt_cursor, &mv->txt_mark, mv->txt_buffer, sizeof(mv->txt_buffer), &mv->txt_size, 0, dbgi_path, "###dbg_path_%p", entity); + edit_commit = (edit_commit || ui_committed(sig)); + } + + // rjf: press -> focus + if(ui_pressed(sig)) + { + edit_commit = (mv->txt_editing && !txt_is_selected); + next_cursor = v2s64(2, (S64)idx+1); + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + + // rjf: double-click -> begin editing + if(ui_double_clicked(sig) && !mv->txt_editing) + { + ui_kill_action(); + mv->txt_editing = 1; + mv->txt_size = Min(sizeof(mv->txt_buffer), dbgi_path.size); + MemoryCopy(mv->txt_buffer, dbgi_path.str, mv->txt_size); + mv->txt_cursor = txt_pt(1, 1+mv->txt_size); + mv->txt_mark = txt_pt(1, 1); + } + + // rjf: store commit info + if(txt_is_selected && edit_commit) + { + commit_module = entity; + } + + // rjf: build browse button + UI_FocusHot(brw_is_selected ? UI_FocusKind_On : UI_FocusKind_Off) UI_PrefWidth(ui_text_dim(10, 1)) + { + if(ui_clicked(ui_buttonf("Browse...")) || (brw_is_selected && edit_begin)) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.cmd_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_PickFile); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); + mv->pick_file_dst_entity = df_handle_from_entity(entity); + } + } + } + }break; + } + } + } + cursor = next_cursor; + } + + //- rjf: apply commits + if(edit_commit) + { + mv->txt_editing = 0; + if(!df_entity_is_nil(commit_module)) + { + String8 exe_path = commit_module->name; + String8 dbg_path = str8(mv->txt_buffer, mv->txt_size); + // TODO(rjf) + } + if(edit_submit) + { + cursor.y += 1; + } + } + + //- rjf: apply edit state changes + if(edit_end) + { + mv->txt_editing = 0; + } + + //- rjf: selected num -> selected entity + mv->selected_column = cursor.x; + mv->selected_entity = (1 <= cursor.y && cursor.y <= items.count) ? df_handle_from_entity(items.v[cursor.y-1].entity) : df_handle_zero(); + + di_scope_close(scope); + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: PendingEntity @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(PendingEntity) +{ + DF_PendingEntityViewState *pves = df_view_user_state(view, DF_PendingEntityViewState); + pves->deferred_cmd_arena = df_view_push_arena_ext(view); + pves->complete_cfg_arena = df_view_push_arena_ext(view); + pves->complete_cfg_root = df_cfg_tree_copy(pves->complete_cfg_arena, cfg_root); +} + +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(PendingEntity) +{ + return str8_lit(""); +} + +DF_VIEW_CMD_FUNCTION_DEF(PendingEntity) +{ + Temp scratch = scratch_begin(0, 0); + DF_PendingEntityViewState *pves = df_view_user_state(view, DF_PendingEntityViewState); + + //- rjf: process commands + for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) + { + DF_Cmd *cmd = &n->cmd; + + // rjf: mismatched window/panel => skip + if(df_window_from_handle(cmd->params.window) != ws || + df_panel_from_handle(cmd->params.panel) != panel) + { + continue; + } + + // rjf: process + DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); + switch(core_cmd_kind) + { + default:break; + + // rjf: gather deferred commands to redispatch when entity is ready + case DF_CoreCmdKind_GoToLine: + case DF_CoreCmdKind_GoToAddress: + case DF_CoreCmdKind_CenterCursor: + case DF_CoreCmdKind_ContainCursor: + { + df_cmd_list_push(pves->deferred_cmd_arena, &pves->deferred_cmds, &cmd->params, cmd->spec); + }break; + } + } + + //- rjf: determine if entity is ready, and which viewer to use + DF_Entity *entity = df_entity_from_handle(view->entity); + DF_GfxViewKind viewer_kind = DF_GfxViewKind_Null; + B32 entity_is_ready = 0; + switch(entity->kind) + { + default:{}break; + case DF_EntityKind_File: + { + entity_is_ready = 1; + viewer_kind = DF_GfxViewKind_Code; + }break; + } + + //- rjf: if entity is ready, dispatch all deferred commands + if(entity_is_ready) + { + for(DF_CmdNode *cmd_node = pves->deferred_cmds.first; cmd_node != 0; cmd_node = cmd_node->next) + { + DF_Cmd *cmd = &cmd_node->cmd; + df_push_cmd__root(&cmd->params, cmd->spec); + } + arena_clear(pves->deferred_cmd_arena); + MemoryZeroStruct(&pves->deferred_cmds); + } + + //- rjf: if entity is ready, move cfg tree to scratch for new command + DF_CfgNode *cfg_root = &df_g_nil_cfg_node; + if(entity_is_ready) + { + cfg_root = df_cfg_tree_copy(scratch.arena, pves->complete_cfg_root); + } + + //- rjf: if entity is ready, replace this view with the correct one, if any viewer is specified + if(entity_is_ready && viewer_kind != DF_GfxViewKind_Null) + { + DF_ViewSpec *view_spec = df_view_spec_from_string(cfg_root->string); + if(view_spec == &df_g_nil_view_spec) + { + view_spec = df_view_spec_from_gfx_view_kind(viewer_kind); + } + df_view_equip_spec(ws, view, view_spec, entity, str8_lit(""), cfg_root); + df_panel_notify_mutation(ws, panel); + } + + //- rjf: if entity is ready, but we have no viewer for it, then just close this tab + if(entity_is_ready && viewer_kind == DF_GfxViewKind_Null) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CloseTab)); + } + + scratch_end(scratch); +} + +DF_VIEW_UI_FUNCTION_DEF(PendingEntity) +{ + view->loading_t = view->loading_t_target = 1.f; + df_gfx_request_frame(); +} + +//////////////////////////////// +//~ rjf: Code @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Code) +{ + // rjf: set up state + DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState); + df_code_view_init(cv, view); + + // rjf: deserialize cursor + DF_CfgNode *cursor_cfg = df_cfg_node_child_from_string(cfg_root, str8_lit("cursor"), StringMatchFlag_CaseInsensitive); + if(cursor_cfg != &df_g_nil_cfg_node) + { + TxtPt cursor = txt_pt(1, 1); + cursor.line = s64_from_str8(cursor_cfg->first->string, 10); + cursor.column = s64_from_str8(cursor_cfg->first->first->string, 10); + if(cursor.line == 0) { cursor.line = 1; } + if(cursor.column == 0) { cursor.column = 1; } + cv->center_cursor = 1; + view->cursor = view->mark = cursor; + } + + // rjf: default to loading + df_view_equip_loading_info(view, 1, 0, 0); + view->loading_t = view->loading_t_target = 1.f; +} + +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Code) +{ + String8 string = push_str8f(arena, " cursor:%I64d:%I64d", view->cursor.line, view->cursor.column); + return string; +} + +DF_VIEW_CMD_FUNCTION_DEF(Code) +{ + DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState); + Temp scratch = scratch_begin(0, 0); + HS_Scope *hs_scope = hs_scope_open(); + TXT_Scope *txt_scope = txt_scope_open(); + DF_Entity *entity = df_entity_from_handle(df_interact_regs()->file); + String8 path = df_full_path_from_entity(scratch.arena, entity); + df_interact_regs()->text_key = fs_key_from_path(path); + df_interact_regs()->lang_kind = txt_lang_kind_from_extension(str8_skip_last_dot(path)); + U128 hash = {0}; + TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, df_interact_regs()->text_key, df_interact_regs()->lang_kind, &hash); + String8 data = hs_data_from_hash(hs_scope, hash); + + //- rjf: process general code-view commands + df_code_view_cmds(ws, panel, view, cv, cmds, data, &info, 0, r1u64(0, 0), di_key_zero()); + + //- rjf: process code-file commands + for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) + { + DF_Cmd *cmd = &n->cmd; + + // rjf: mismatched window/panel => skip + if(df_window_from_handle(cmd->params.window) != ws || + df_panel_from_handle(cmd->params.panel) != panel) + { + continue; + } + + // rjf: process + DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); + switch(core_cmd_kind) + { + default:{}break; + case DF_CoreCmdKind_PickFile: + { + DF_Entity *missing_file = df_entity_from_handle(cv->pick_file_override_target); + String8 pick_string = cmd->params.file_path; + if(!df_entity_is_nil(missing_file) && pick_string.size != 0) + { + DF_Entity *replacement = df_entity_from_path(pick_string, DF_EntityFromPathFlag_OpenAsNeeded|DF_EntityFromPathFlag_OpenMissing); + view->entity = df_handle_from_entity(replacement); + DF_CmdParams p = df_cmd_params_from_view(ws, panel, view); + p.entity = df_handle_from_entity(missing_file); + p.file_path = pick_string; + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_Entity); + df_cmd_params_mark_slot(&p, DF_CmdParamSlot_FilePath); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_SetFileReplacementPath)); + } + }break; + } + } + + txt_scope_close(txt_scope); + hs_scope_close(hs_scope); + scratch_end(scratch); +} + +DF_VIEW_UI_FUNCTION_DEF(Code) +{ + DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState); + Temp scratch = scratch_begin(0, 0); + HS_Scope *hs_scope = hs_scope_open(); + TXT_Scope *txt_scope = txt_scope_open(); + + ////////////////////////////// + //- rjf: set up invariants + // + F32 bottom_bar_height = ui_top_font_size()*2.f; + Rng2F32 code_area_rect = r2f32p(rect.x0, rect.y0, rect.x1, rect.y1 - bottom_bar_height); + Rng2F32 bottom_bar_rect = r2f32p(rect.x0, rect.y1 - bottom_bar_height, rect.x1, rect.y1); + + ////////////////////////////// + //- rjf: unpack entity info + // + DF_Entity *entity = df_entity_from_handle(df_interact_regs()->file); + String8 path = df_full_path_from_entity(scratch.arena, entity); + df_interact_regs()->text_key = fs_key_from_path(path); + df_interact_regs()->lang_kind = txt_lang_kind_from_extension(str8_skip_last_dot(path)); + U128 hash = {0}; + TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, df_interact_regs()->text_key, df_interact_regs()->lang_kind, &hash); + String8 data = hs_data_from_hash(hs_scope, hash); + B32 entity_is_missing = !!(entity->flags & DF_EntityFlag_IsMissing); + B32 key_has_data = !u128_match(hash, u128_zero()) && info.lines_count; + + ////////////////////////////// + //- rjf: build missing file interface + // + if(entity_is_missing && !key_has_data) + { + UI_WidthFill UI_HeightFill UI_Column UI_Padding(ui_pct(1, 0)) + { + Temp scratch = scratch_begin(0, 0); + String8 full_path = df_full_path_from_entity(scratch.arena, entity); + UI_PrefWidth(ui_children_sum(1)) UI_PrefHeight(ui_em(3, 1)) + UI_Row UI_Padding(ui_pct(1, 0)) + UI_PrefWidth(ui_text_dim(10, 1)) + UI_Palette(ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_TextNegative))) + { + DF_Font(ws, DF_FontSlot_Icons) ui_label(df_g_icon_kind_text_table[DF_IconKind_WarningBig]); + ui_labelf("Could not find \"%S\".", full_path); + } + UI_PrefHeight(ui_em(3, 1)) + UI_Row UI_Padding(ui_pct(1, 0)) + UI_PrefWidth(ui_text_dim(10, 1)) + UI_CornerRadius(ui_top_font_size()/3) + UI_PrefWidth(ui_text_dim(10, 1)) + UI_Focus(UI_FocusKind_On) + DF_Palette(ws, DF_PaletteCode_NeutralPopButton) + if(ui_clicked(ui_buttonf("Find alternative..."))) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.cmd_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_PickFile); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); + cv->pick_file_override_target = view->entity; + } + scratch_end(scratch); + } + } + + ////////////////////////////// + //- rjf: code is not missing, but not ready -> equip loading info to this view + // + if(!entity_is_missing && info.lines_count == 0) + { + df_view_equip_loading_info(view, 1, info.bytes_processed, info.bytes_to_process); + } + + ////////////////////////////// + //- rjf: build code contents + // + DI_KeyList dbgi_keys = {0}; + if(!entity_is_missing && key_has_data) + { + DF_CodeViewBuildResult result = df_code_view_build(scratch.arena, ws, panel, view, cv, DF_CodeViewBuildFlag_All, code_area_rect, data, &info, 0, r1u64(0, 0), di_key_zero()); + dbgi_keys = result.dbgi_keys; + } + + ////////////////////////////// + //- rjf: unpack cursor info + // + { + df_interact_regs()->lines = df_lines_from_file_line_num(df_frame_arena(), entity, df_interact_regs()->cursor.line); + } + + ////////////////////////////// + //- rjf: determine if file is out-of-date + // + B32 file_is_out_of_date = 0; + String8 out_of_date_dbgi_name = {0}; + { + for(DI_KeyNode *n = dbgi_keys.first; n != 0; n = n->next) + { + DI_Key key = n->v; + U64 file_timestamp = fs_timestamp_from_path(path); + if(key.min_timestamp < file_timestamp) + { + file_is_out_of_date = 1; + out_of_date_dbgi_name = str8_skip_last_slash(key.path); + break; + } + } + } + + ////////////////////////////// + //- rjf: build bottom bar + // + if(!entity_is_missing && key_has_data) + { + ui_set_next_rect(shift_2f32(bottom_bar_rect, scale_2f32(rect.p0, -1.f))); + ui_set_next_flags(UI_BoxFlag_DrawBackground); + UI_Palette *palette = ui_top_palette(); + if(file_is_out_of_date) + { + palette = df_palette_from_code(ws, DF_PaletteCode_NegativePopButton); + } + UI_Palette(palette) + UI_Row + UI_TextAlignment(UI_TextAlign_Center) + UI_PrefWidth(ui_text_dim(10, 1)) + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + { + if(file_is_out_of_date) + { + UI_Box *box = &ui_g_nil_box; + UI_Palette(ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_TextNegative))) + DF_Font(ws, DF_FontSlot_Icons) + { + box = ui_build_box_from_stringf(UI_BoxFlag_DrawText|UI_BoxFlag_Clickable, "%S###file_ood_warning", df_g_icon_kind_text_table[DF_IconKind_WarningBig]); + } + UI_Signal sig = ui_signal_from_box(box); + if(ui_hovering(sig)) UI_Tooltip + { + UI_PrefWidth(ui_children_sum(1)) UI_Row UI_PrefWidth(ui_text_dim(1, 1)) + { + ui_labelf("This file has changed since ", out_of_date_dbgi_name); + UI_Palette(ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_TextNeutral))) ui_label(out_of_date_dbgi_name); + ui_labelf(" was produced."); + } + } + } + DF_Font(ws, DF_FontSlot_Code) + { + ui_label(path); + ui_spacer(ui_em(1.5f, 1)); + ui_labelf("Line: %I64d, Column: %I64d", view->cursor.line, view->cursor.column); + ui_spacer(ui_pct(1, 0)); + ui_labelf("(read only)"); + ui_labelf("%s", + info.line_end_kind == TXT_LineEndKind_LF ? "lf" : + info.line_end_kind == TXT_LineEndKind_CRLF ? "crlf" : + "bin"); + } + } + } + + txt_scope_close(txt_scope); + hs_scope_close(hs_scope); + scratch_end(scratch); +} + +//////////////////////////////// +//~ rjf: Disassembly @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Disassembly) +{ + DF_DisasmViewState *dv = df_view_user_state(view, DF_DisasmViewState); + if(dv->initialized == 0) + { + dv->initialized = 1; + dv->style_flags = DASM_StyleFlag_Addresses|DASM_StyleFlag_SourceFilesNames|DASM_StyleFlag_SourceLines|DASM_StyleFlag_SymbolNames; + df_code_view_init(&dv->cv, view); + } +} + +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Disassembly) +{ + return str8_zero(); +} + +DF_VIEW_CMD_FUNCTION_DEF(Disassembly) +{ + DF_DisasmViewState *dv = df_view_user_state(view, DF_DisasmViewState); + Temp scratch = scratch_begin(0, 0); + DASM_Scope *dasm_scope = dasm_scope_open(); + HS_Scope *hs_scope = hs_scope_open(); + TXT_Scope *txt_scope = txt_scope_open(); + + //- rjf: unpack disasm info + DF_Entity *process = df_entity_from_handle(dv->process); + Architecture arch = df_architecture_from_entity(process); + U64 dasm_base_vaddr = AlignDownPow2(dv->base_vaddr, KB(16)); + DF_Entity *dasm_module = df_module_from_process_vaddr(process, dasm_base_vaddr); + DI_Key dasm_dbgi_key = df_dbgi_key_from_module(dasm_module); + Rng1U64 dasm_vaddr_range = r1u64(dasm_base_vaddr, dasm_base_vaddr+KB(16)); + U128 dasm_key = ctrl_hash_store_key_from_process_vaddr_range(process->ctrl_machine_id, process->ctrl_handle, dasm_vaddr_range, 0); + U128 dasm_data_hash = {0}; + DASM_Params dasm_params = {0}; + { + dasm_params.vaddr = dasm_vaddr_range.min; + dasm_params.arch = arch; + dasm_params.style_flags = dv->style_flags; + dasm_params.syntax = DASM_Syntax_Intel; + dasm_params.base_vaddr = dasm_module->vaddr_rng.min; + dasm_params.dbgi_key = dasm_dbgi_key; + } + DASM_Info dasm_info = dasm_info_from_key_params(dasm_scope, dasm_key, &dasm_params, &dasm_data_hash); + df_interact_regs()->text_key = dasm_info.text_key; + df_interact_regs()->lang_kind = txt_lang_kind_from_architecture(arch); + U128 dasm_text_hash = {0}; + TXT_TextInfo dasm_text_info = txt_text_info_from_key_lang(txt_scope, df_interact_regs()->text_key, df_interact_regs()->lang_kind, &dasm_text_hash); + String8 dasm_text_data = hs_data_from_hash(hs_scope, dasm_text_hash); + + //- rjf: process general code-view commands + df_code_view_cmds(ws, panel, view, &dv->cv, cmds, dasm_text_data, &dasm_text_info, &dasm_info.insts, dasm_vaddr_range, dasm_dbgi_key); + + //- rjf: process disassembly-specific commands + for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) + { + DF_Cmd *cmd = &n->cmd; + DF_CmdParams params = cmd->params; + + // rjf: mismatched window/panel => skip + if(df_window_from_handle(cmd->params.window) != ws || + df_panel_from_handle(cmd->params.panel) != panel) + { + continue; + } + + // rjf: process + DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); + switch(core_cmd_kind) + { + default: break; + case DF_CoreCmdKind_GoToAddress: + { + DF_Entity *process = &df_g_nil_entity; + { + DF_Entity *entity = df_entity_from_handle(params.entity); + if(!df_entity_is_nil(entity) && + (entity->kind == DF_EntityKind_Process || + entity->kind == DF_EntityKind_Thread || + entity->kind == DF_EntityKind_Module)) + { + process = entity; + if(entity->kind == DF_EntityKind_Thread || + entity->kind == DF_EntityKind_Module) + { + process = df_entity_ancestor_from_kind(process, DF_EntityKind_Process); + } + } + } + dv->process = df_handle_from_entity(process); + dv->base_vaddr = params.vaddr; + dv->goto_vaddr = params.vaddr; + }break; + case DF_CoreCmdKind_ToggleCodeBytesVisibility: {dv->style_flags ^= DASM_StyleFlag_CodeBytes;}break; + case DF_CoreCmdKind_ToggleAddressVisibility: {dv->style_flags ^= DASM_StyleFlag_Addresses;}break; + } + } + + txt_scope_close(txt_scope); + hs_scope_close(hs_scope); + dasm_scope_close(dasm_scope); + scratch_end(scratch); +} + +DF_VIEW_UI_FUNCTION_DEF(Disassembly) +{ + DF_DisasmViewState *dv = df_view_user_state(view, DF_DisasmViewState); + DF_CodeViewState *cv = &dv->cv; + Temp scratch = scratch_begin(0, 0); + HS_Scope *hs_scope = hs_scope_open(); + DASM_Scope *dasm_scope = dasm_scope_open(); + TXT_Scope *txt_scope = txt_scope_open(); + + ////////////////////////////// + //- rjf: set up invariants + // + F32 bottom_bar_height = ui_top_font_size()*2.f; + Rng2F32 code_area_rect = r2f32p(rect.x0, rect.y0, rect.x1, rect.y1 - bottom_bar_height); + Rng2F32 bottom_bar_rect = r2f32p(rect.x0, rect.y1 - bottom_bar_height, rect.x1, rect.y1); + + ////////////////////////////// + //- rjf: no disasm process open? -> snap to selected thread + // + if(df_entity_is_nil(df_entity_from_handle(dv->process))) + { + DF_Entity *thread = df_entity_from_handle(df_interact_regs()->thread); + U64 rip_vaddr = df_query_cached_rip_from_thread_unwind(thread, df_interact_regs()->unwind_count); + dv->process = df_handle_from_entity(df_entity_ancestor_from_kind(thread, DF_EntityKind_Process)); + dv->base_vaddr = rip_vaddr; + dv->goto_vaddr = rip_vaddr; + } + + ////////////////////////////// + //- rjf: unpack disassembly info + // + DF_Entity *process = df_entity_from_handle(dv->process); + Architecture arch = df_architecture_from_entity(process); + U64 dasm_base_vaddr = AlignDownPow2(dv->base_vaddr, KB(16)); + DF_Entity *dasm_module = df_module_from_process_vaddr(process, dasm_base_vaddr); + DI_Key dasm_dbgi_key = df_dbgi_key_from_module(dasm_module); + Rng1U64 dasm_vaddr_range = r1u64(dasm_base_vaddr, dasm_base_vaddr+KB(16)); + U128 dasm_key = ctrl_hash_store_key_from_process_vaddr_range(process->ctrl_machine_id, process->ctrl_handle, dasm_vaddr_range, 0); + U128 dasm_data_hash = {0}; + DASM_Params dasm_params = {0}; + { + dasm_params.vaddr = dasm_vaddr_range.min; + dasm_params.arch = arch; + dasm_params.style_flags = dv->style_flags; + dasm_params.syntax = DASM_Syntax_Intel; + dasm_params.base_vaddr = dasm_module->vaddr_rng.min; + dasm_params.dbgi_key = dasm_dbgi_key; + } + DASM_Info dasm_info = dasm_info_from_key_params(dasm_scope, dasm_key, &dasm_params, &dasm_data_hash); + df_interact_regs()->text_key = dasm_info.text_key; + df_interact_regs()->lang_kind = txt_lang_kind_from_architecture(arch); + U128 dasm_text_hash = {0}; + TXT_TextInfo dasm_text_info = txt_text_info_from_key_lang(txt_scope, df_interact_regs()->text_key, df_interact_regs()->lang_kind, &dasm_text_hash); + String8 dasm_text_data = hs_data_from_hash(hs_scope, dasm_text_hash); + B32 has_disasm = (dasm_info.insts.count != 0 && dasm_text_info.lines_count != 0); + B32 is_loading = (!has_disasm && !df_entity_is_nil(process) && dim_1u64(dasm_vaddr_range) != 0); + + ////////////////////////////// + //- rjf: is loading -> equip view with loading information + // + if(is_loading && !df_ctrl_targets_running()) + { + df_view_equip_loading_info(view, is_loading, 0, 0); + } + + ////////////////////////////// + //- rjf: do goto vaddr + // + if(!is_loading && has_disasm && dv->goto_vaddr != 0) + { + U64 vaddr = dv->goto_vaddr; + dv->goto_vaddr = 0; + U64 line_idx = dasm_inst_array_idx_from_code_off__linear_scan(&dasm_info.insts, vaddr-dasm_vaddr_range.min); + S64 line_num = (S64)(line_idx+1); + cv->goto_line_num = line_num; + } + + ////////////////////////////// + //- rjf: build code contents + // + if(!is_loading && has_disasm) + { + df_code_view_build(scratch.arena, ws, panel, view, cv, DF_CodeViewBuildFlag_All, code_area_rect, dasm_text_data, &dasm_text_info, &dasm_info.insts, dasm_vaddr_range, dasm_dbgi_key); + } + + ////////////////////////////// + //- rjf: unpack cursor info + // + if(!is_loading && has_disasm) + { + U64 off = dasm_inst_array_code_off_from_idx(&dasm_info.insts, df_interact_regs()->cursor.line-1); + df_interact_regs()->vaddr_range = r1u64(dasm_base_vaddr+off, dasm_base_vaddr+off); + df_interact_regs()->voff_range = df_voff_range_from_vaddr_range(dasm_module, df_interact_regs()->vaddr_range); + df_interact_regs()->lines = df_lines_from_dbgi_key_voff(df_frame_arena(), &dasm_dbgi_key, df_interact_regs()->voff_range.min); + } + + ////////////////////////////// + //- rjf: build bottom bar + // + if(!is_loading && has_disasm) + { + ui_set_next_rect(shift_2f32(bottom_bar_rect, scale_2f32(rect.p0, -1.f))); + ui_set_next_flags(UI_BoxFlag_DrawBackground); + UI_Row + UI_TextAlignment(UI_TextAlign_Center) + UI_PrefWidth(ui_text_dim(10, 1)) + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + DF_Font(ws, DF_FontSlot_Code) + { + DF_Entity *module = df_module_from_process_vaddr(process, dasm_vaddr_range.min); + U64 cursor_vaddr = (1 <= view->cursor.line && view->cursor.line <= dasm_info.insts.count) ? (dasm_vaddr_range.min+dasm_info.insts.v[view->cursor.line-1].code_off) : 0; + ui_labelf("%S", path_normalized_from_string(scratch.arena, module->name)); + ui_spacer(ui_em(1.5f, 1)); + ui_labelf("Address: 0x%I64x, Line: %I64d, Column: %I64d", cursor_vaddr, view->cursor.line, view->cursor.column); + ui_spacer(ui_pct(1, 0)); + ui_labelf("(read only)"); + ui_labelf("bin"); + } + } + + txt_scope_close(txt_scope); + dasm_scope_close(dasm_scope); + hs_scope_close(hs_scope); + scratch_end(scratch); +} + +//////////////////////////////// +//~ rjf: Watch @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Watch) +{ + ProfBeginFunction(); + DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); + df_watch_view_init(ewv, view, DF_WatchViewFillKind_Mutable); + + // rjf: add roots for watches + { + Temp scratch = scratch_begin(0, 0); + DF_EvalViewKey eval_view_key = df_eval_view_key_from_eval_watch_view(ewv); + DF_EvalView *eval_view = df_eval_view_from_key(eval_view_key); + for(DF_CfgNode *expr = cfg_root->first; expr != &df_g_nil_cfg_node; expr = expr->next) + { + if(expr->flags & DF_CfgNodeFlag_StringLiteral) + { + DF_EvalRoot *root = df_eval_root_alloc(view, ewv); + DF_ExpandKey key = df_expand_key_from_eval_root(root); + String8 expr_raw = df_cfg_raw_from_escaped_string(scratch.arena, expr->string); + df_eval_root_equip_string(root, expr_raw); + if(expr->first != &df_g_nil_cfg_node) + { + String8 view_rule_raw = df_cfg_raw_from_escaped_string(scratch.arena, expr->first->string); + df_eval_view_set_key_rule(eval_view, key, view_rule_raw); + } + } + } + scratch_end(scratch); + } + + ProfEnd(); +} + +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Watch) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List strs = {0}; + DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); + DF_EvalViewKey eval_view_key = df_eval_view_key_from_eval_watch_view(ewv); + DF_EvalView *eval_view = df_eval_view_from_key(eval_view_key); + { + for(DF_EvalRoot *root = ewv->first_root; root != 0; root = root->next) + { + DF_ExpandKey key = df_expand_key_from_eval_root(root); + String8 string = df_string_from_eval_root(root); + String8 string_escaped = df_cfg_escaped_from_raw_string(scratch.arena, string); + str8_list_pushf(arena, &strs, "\"%S\"", string_escaped); + String8 view_rule = df_eval_view_rule_from_key(eval_view, key); + String8 view_rule_escaped = df_cfg_escaped_from_raw_string(scratch.arena, view_rule); + if(view_rule_escaped.size != 0) + { + str8_list_pushf(arena, &strs, ":{\"%S\"}", view_rule_escaped); + } + if(root->next != 0) + { + str8_list_pushf(arena, &strs, " "); + } + } + } + String8 string = str8_list_join(arena, &strs, 0); + scratch_end(scratch); + return string; +} + +DF_VIEW_CMD_FUNCTION_DEF(Watch) +{ + ProfBeginFunction(); + DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); + df_watch_view_cmds(ws, panel, view, ewv, cmds); + ProfEnd(); +} + +DF_VIEW_UI_FUNCTION_DEF(Watch) +{ + ProfBeginFunction(); + DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); + df_watch_view_build(ws, panel, view, ewv, 1*(view->query_string_size == 0), 10, rect); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Locals @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Locals) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Locals) { return str8_lit(""); } +DF_VIEW_CMD_FUNCTION_DEF(Locals) {} +DF_VIEW_UI_FUNCTION_DEF(Locals) +{ + ProfBeginFunction(); + DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); + df_watch_view_init(ewv, view, DF_WatchViewFillKind_Locals); + df_watch_view_build(ws, panel, view, ewv, 0, 10, rect); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Registers @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Registers) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Registers) { return str8_lit(""); } +DF_VIEW_CMD_FUNCTION_DEF(Registers) {} +DF_VIEW_UI_FUNCTION_DEF(Registers) +{ + ProfBeginFunction(); + DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); + df_watch_view_init(ewv, view, DF_WatchViewFillKind_Registers); + df_watch_view_build(ws, panel, view, ewv, 0, 16, rect); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Globals @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Globals) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Globals) { return str8_lit(""); } +DF_VIEW_CMD_FUNCTION_DEF(Globals) {} +DF_VIEW_UI_FUNCTION_DEF(Globals) +{ + ProfBeginFunction(); + DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); + df_watch_view_init(ewv, view, DF_WatchViewFillKind_Globals); + df_watch_view_build(ws, panel, view, ewv, 0, 10, rect); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: ThreadLocals @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(ThreadLocals) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(ThreadLocals) { return str8_lit(""); } +DF_VIEW_CMD_FUNCTION_DEF(ThreadLocals) {} +DF_VIEW_UI_FUNCTION_DEF(ThreadLocals) +{ + ProfBeginFunction(); + DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); + df_watch_view_init(ewv, view, DF_WatchViewFillKind_ThreadLocals); + df_watch_view_build(ws, panel, view, ewv, 0, 10, rect); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Types @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Types) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Types) { return str8_lit(""); } +DF_VIEW_CMD_FUNCTION_DEF(Types) {} +DF_VIEW_UI_FUNCTION_DEF(Types) +{ + ProfBeginFunction(); + DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); + df_watch_view_init(ewv, view, DF_WatchViewFillKind_Types); + df_watch_view_build(ws, panel, view, ewv, 0, 10, rect); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Procedures @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Procedures) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Procedures) { return str8_lit(""); } +DF_VIEW_CMD_FUNCTION_DEF(Procedures) {} +DF_VIEW_UI_FUNCTION_DEF(Procedures) +{ + ProfBeginFunction(); + DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState); + df_watch_view_init(ewv, view, DF_WatchViewFillKind_Procedures); + df_watch_view_build(ws, panel, view, ewv, 0, 10, rect); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Output @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Output) +{ + DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState); + df_code_view_init(cv, view); +} + +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Output) +{ + return str8_zero(); +} + +DF_VIEW_CMD_FUNCTION_DEF(Output) +{ + DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState); + Temp scratch = scratch_begin(0, 0); + HS_Scope *hs_scope = hs_scope_open(); + TXT_Scope *txt_scope = txt_scope_open(); + df_interact_regs()->text_key = df_state->output_log_key; + df_interact_regs()->lang_kind = TXT_LangKind_Null; + U128 hash = {0}; + TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, df_interact_regs()->text_key, df_interact_regs()->lang_kind, &hash); + String8 data = hs_data_from_hash(hs_scope, hash); + df_code_view_cmds(ws, panel, view, cv, cmds, data, &info, 0, r1u64(0, 0), di_key_zero()); + txt_scope_close(txt_scope); + hs_scope_close(hs_scope); + scratch_end(scratch); +} + +DF_VIEW_UI_FUNCTION_DEF(Output) +{ + DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState); + Temp scratch = scratch_begin(0, 0); + HS_Scope *hs_scope = hs_scope_open(); + TXT_Scope *txt_scope = txt_scope_open(); + + ////////////////////////////// + //- rjf: set up invariants + // + F32 bottom_bar_height = ui_top_font_size()*2.f; + Rng2F32 code_area_rect = r2f32p(rect.x0, rect.y0, rect.x1, rect.y1 - bottom_bar_height); + Rng2F32 bottom_bar_rect = r2f32p(rect.x0, rect.y1 - bottom_bar_height, rect.x1, rect.y1); + + ////////////////////////////// + //- rjf: unpack text info + // + U128 key = df_state->output_log_key; + TXT_LangKind lang_kind = TXT_LangKind_Null; + U128 hash = {0}; + TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, key, lang_kind, &hash); + String8 data = hs_data_from_hash(hs_scope, hash); + Rng1U64 empty_range = {0}; + if(info.lines_count == 0) + { + info.lines_count = 1; + info.lines_ranges = &empty_range; + } + + ////////////////////////////// + //- rjf: build code contents + // + { + df_code_view_build(scratch.arena, ws, panel, view, cv, 0, code_area_rect, data, &info, 0, r1u64(0, 0), di_key_zero()); + } + + ////////////////////////////// + //- rjf: build bottom bar + // + { + ui_set_next_rect(shift_2f32(bottom_bar_rect, scale_2f32(rect.p0, -1.f))); + ui_set_next_flags(UI_BoxFlag_DrawBackground); + UI_Row + UI_TextAlignment(UI_TextAlign_Center) + UI_PrefWidth(ui_text_dim(10, 1)) + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + DF_Font(ws, DF_FontSlot_Code) + { + ui_labelf("(Debug String Output)"); + ui_spacer(ui_em(1.5f, 1)); + ui_labelf("Line: %I64d, Column: %I64d", view->cursor.line, view->cursor.column); + ui_spacer(ui_pct(1, 0)); + ui_labelf("(read only)"); + } + } + + txt_scope_close(txt_scope); + hs_scope_close(hs_scope); + scratch_end(scratch); +} + +//////////////////////////////// +//~ rjf: Memory @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Memory) +{ + DF_MemoryViewState *mv = df_view_user_state(view, DF_MemoryViewState); + if(mv->initialized == 0) + { + mv->initialized = 1; + mv->num_columns = 16; + mv->bytes_per_cell = 1; + mv->last_viewed_memory_cache_arena = df_view_push_arena_ext(view); + } +} + +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Memory) +{ + return str8_lit(""); +} + +DF_VIEW_CMD_FUNCTION_DEF(Memory) +{ + DF_MemoryViewState *mv = df_view_user_state(view, DF_MemoryViewState); + for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) + { + DF_Cmd *cmd = &n->cmd; + DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); + DF_CmdParams *params = &cmd->params; + switch(core_cmd_kind) + { + default: break; + case DF_CoreCmdKind_CenterCursor: + if(df_view_from_handle(params->view) == view) + { + mv->center_cursor = 1; + }break; + case DF_CoreCmdKind_ContainCursor: + if(df_view_from_handle(params->view) == view) + { + mv->contain_cursor = 1; + }break; + case DF_CoreCmdKind_GoToAddress: + { + // NOTE(rjf): go-to-address occurs with disassembly snaps, and we don't + // generally want to respond to those in thise view, so just skip any + // go-to-address commands that haven't been *explicitly* parameterized + // with this view. + if(df_view_from_handle(params->view) == view) + { + mv->cursor = mv->mark = params->vaddr; + mv->center_cursor = 1; + } + }break; + case DF_CoreCmdKind_SetColumns: + if(df_view_from_handle(params->view) == view) + { + U64 num_columns = params->index; + mv->num_columns = Clamp(1, num_columns, 64); + if(mv->num_columns % mv->bytes_per_cell != 0) + { + mv->bytes_per_cell = 1; + } + mv->center_cursor = 1; + }break; + } + } +} + +DF_VIEW_UI_FUNCTION_DEF(Memory) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + HS_Scope *hs_scope = hs_scope_open(); + + ////////////////////////////// + //- rjf: unpack state + // + DF_MemoryViewState *mv = df_view_user_state(view, DF_MemoryViewState); + + ////////////////////////////// + //- rjf: unpack entity params + // + DF_CtrlCtx ctrl_ctx = df_ctrl_ctx_from_view(ws, view); + DF_Entity *thread = df_entity_from_handle(ctrl_ctx.thread); + DF_Entity *process = df_entity_ancestor_from_kind(thread, DF_EntityKind_Process); + + ////////////////////////////// + //- rjf: unpack visual params + // + F_Tag font = df_font_from_slot(DF_FontSlot_Code); + F32 font_size = df_font_size_from_slot(ws, DF_FontSlot_Code); + F32 big_glyph_advance = f_dim_from_tag_size_string(font, font_size, 0, 0, str8_lit("H")).x; + F32 row_height_px = floor_f32(font_size*2.f); + F32 cell_width_px = floor_f32(font_size*2.f * mv->bytes_per_cell); + F32 scroll_bar_dim = floor_f32(ui_top_font_size()*1.5f); + Vec2F32 panel_dim = dim_2f32(rect); + F32 footer_dim = font_size*10.f; + Rng2F32 header_rect = r2f32p(0, 0, panel_dim.x, row_height_px); + Rng2F32 footer_rect = r2f32p(0, panel_dim.y-footer_dim, panel_dim.x, panel_dim.y); + Rng2F32 content_rect = r2f32p(0, row_height_px, panel_dim.x-scroll_bar_dim, footer_rect.y0); + + ////////////////////////////// + //- rjf: determine legal scroll range + // + Rng1S64 scroll_idx_rng = r1s64(0, 0x7FFFFFFFFFFFull/mv->num_columns); + + ////////////////////////////// + //- rjf: determine info about visible range of rows + // + Rng1S64 viz_range_rows = {0}; + Rng1U64 viz_range_bytes = {0}; + S64 num_possible_visible_rows = 0; + { + num_possible_visible_rows = dim_2f32(content_rect).y/row_height_px; + viz_range_rows.min = view->scroll_pos.y.idx + (S64)view->scroll_pos.y.off - !!(view->scroll_pos.y.off<0); + viz_range_rows.max = view->scroll_pos.y.idx + (S64)view->scroll_pos.y.off + num_possible_visible_rows, + viz_range_rows.min = clamp_1s64(scroll_idx_rng, viz_range_rows.min); + viz_range_rows.max = clamp_1s64(scroll_idx_rng, viz_range_rows.max); + viz_range_bytes.min = viz_range_rows.min*mv->num_columns; + viz_range_bytes.max = (viz_range_rows.max+1)*mv->num_columns+1; + if(viz_range_bytes.min > viz_range_bytes.max) + { + Swap(U64, viz_range_bytes.min, viz_range_bytes.max); + } + } + + ////////////////////////////// + //- rjf: take keyboard controls + // + UI_Focus(UI_FocusKind_On) if(ui_is_focus_active()) + { + U64 next_cursor = mv->cursor; + U64 next_mark = mv->mark; + UI_EventList *events = ui_events(); + for(UI_EventNode *n = events->first, *next = 0; n != 0; n = next) + { + next = n->next; + UI_Event *evt = &n->v; + Vec2S64 cell_delta = {0}; + switch(evt->delta_unit) + { + default:{}break; + case UI_EventDeltaUnit_Char: + { + cell_delta.x = (S64)evt->delta_2s32.x; + cell_delta.y = (S64)evt->delta_2s32.y; + }break; + case UI_EventDeltaUnit_Word: + case UI_EventDeltaUnit_Page: + { + if(evt->delta_2s32.x < 0) + { + cell_delta.x = -(S64)(mv->cursor%mv->num_columns); + } + else if(evt->delta_2s32.x > 0) + { + cell_delta.x = (mv->num_columns-1) - (S64)(mv->cursor%mv->num_columns); + } + if(evt->delta_2s32.y < 0) + { + cell_delta.y = -4; + } + else if(evt->delta_2s32.y > 0) + { + cell_delta.y = +4; + } + }break; + } + B32 good_action = 0; + if(evt->delta_2s32.x != 0 || evt->delta_2s32.y != 0) + { + good_action = 1; + } + if(good_action && evt->flags & UI_EventFlag_ZeroDeltaOnSelect && mv->cursor != mv->mark) + { + MemoryZeroStruct(&cell_delta); + } + if(good_action) + { + cell_delta.x = ClampBot(cell_delta.x, (S64)-next_cursor); + cell_delta.y = ClampBot(cell_delta.y, (S64)-(next_cursor/mv->num_columns)); + next_cursor += cell_delta.x; + next_cursor += cell_delta.y*mv->num_columns; + next_cursor = ClampTop(0x7FFFFFFFFFFFull, next_cursor); + } + if(good_action && evt->flags & UI_EventFlag_PickSelectSide && mv->cursor != mv->mark) + { + if(evt->delta_2s32.x < 0 || evt->delta_2s32.y < 0) + { + next_cursor = Min(mv->cursor, mv->mark); + } + else + { + next_cursor = Max(mv->cursor, mv->mark); + } + } + if(good_action && !(evt->flags & UI_EventFlag_KeepMark)) + { + next_mark = next_cursor; + } + if(good_action) + { + mv->contain_cursor = 1; + ui_eat_event(events, n); + } + } + mv->cursor = next_cursor; + mv->mark = next_mark; + } + + ////////////////////////////// + //- rjf: clamp cursor + // + { + Rng1U64 cursor_valid_rng = r1u64(0, 0x7FFFFFFFFFFFull); + mv->cursor = clamp_1u64(cursor_valid_rng, mv->cursor); + mv->mark = clamp_1u64(cursor_valid_rng, mv->mark); + } + + ////////////////////////////// + //- rjf: center cursor + // + if(mv->center_cursor) + { + mv->center_cursor = 0; + S64 cursor_row_idx = mv->cursor/mv->num_columns; + S64 new_idx = (cursor_row_idx-num_possible_visible_rows/2+1); + new_idx = clamp_1s64(scroll_idx_rng, new_idx); + ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx); + } + + ////////////////////////////// + //- rjf: contain cursor + // + if(mv->contain_cursor) + { + mv->contain_cursor = 0; + S64 cursor_row_idx = mv->cursor/mv->num_columns; + Rng1S64 cursor_viz_range = r1s64(clamp_1s64(scroll_idx_rng, cursor_row_idx-2), clamp_1s64(scroll_idx_rng, cursor_row_idx+3)); + S64 min_delta = Min(0, cursor_viz_range.min-viz_range_rows.min); + S64 max_delta = Max(0, cursor_viz_range.max-viz_range_rows.max); + S64 new_idx = view->scroll_pos.y.idx+min_delta+max_delta; + new_idx = clamp_1s64(scroll_idx_rng, new_idx); + ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx); + } + + ////////////////////////////// + //- rjf: produce fancy string runs for all possible byte values in all cells + // + D_FancyStringList byte_fancy_strings[256] = {0}; + { + Vec4F32 full_color = df_rgba_from_theme_color(DF_ThemeColor_TextPositive); + Vec4F32 zero_color = df_rgba_from_theme_color(DF_ThemeColor_TextWeak); + for(U64 idx = 0; idx < ArrayCount(byte_fancy_strings); idx += 1) + { + U8 byte = (U8)idx; + F32 pct = (byte/255.f); + Vec4F32 text_color = mix_4f32(zero_color, full_color, pct); + if(byte == 0) + { + text_color.w *= 0.5f; + } + D_FancyString fstr = {font, push_str8f(scratch.arena, "%02x", byte), text_color, font_size, 0, 0}; + d_fancy_string_list_push(scratch.arena, &byte_fancy_strings[idx], &fstr); + } + } + + ////////////////////////////// + //- rjf: grab windowed memory + // + U64 visible_memory_size = dim_1u64(viz_range_bytes); + U8 *visible_memory = 0; + { + Rng1U64 chunk_aligned_range_bytes = r1u64(AlignDownPow2(viz_range_bytes.min, KB(4)), AlignPow2(viz_range_bytes.max, KB(4))); + U64 current_memgen_idx = ctrl_mem_gen(); + B32 range_changed = (chunk_aligned_range_bytes.min != mv->last_viewed_memory_cache_range.min || + chunk_aligned_range_bytes.max != mv->last_viewed_memory_cache_range.max); + B32 mem_changed = (current_memgen_idx != mv->last_viewed_memory_cache_memgen_idx); + if(range_changed || mem_changed) + { + Temp scratch = scratch_begin(0, 0); + + // rjf: try to read new memory for this range + U64 bytes_to_read = dim_1u64(chunk_aligned_range_bytes); + U8 *buffer = push_array_no_zero(scratch.arena, U8, bytes_to_read); + U64 half1_bytes_read = dmn_process_read(process->ctrl_handle, r1u64(chunk_aligned_range_bytes.min, chunk_aligned_range_bytes.min+bytes_to_read/2), buffer+0); + U64 half2_bytes_read = dmn_process_read(process->ctrl_handle, r1u64(chunk_aligned_range_bytes.min+bytes_to_read/2, chunk_aligned_range_bytes.max), buffer+bytes_to_read/2); + + // rjf: worked? -> clear cache & store + if(half1_bytes_read+half2_bytes_read >= bytes_to_read) + { + arena_clear(mv->last_viewed_memory_cache_arena); + mv->last_viewed_memory_cache_buffer = push_array_no_zero(mv->last_viewed_memory_cache_arena, U8, bytes_to_read); + MemoryCopy(mv->last_viewed_memory_cache_buffer, buffer, bytes_to_read); + } + + // rjf: didn't work, but range didn't change? -> no-op + if(half1_bytes_read == 0 && half2_bytes_read == 0 && !range_changed) + { + // NOTE(rjf): nothing - use stale memory from cache. + } + + // rjf: didn't work, but range DID change? -> clear cache + if(half1_bytes_read == 0 && half2_bytes_read == 0 && range_changed) + { + arena_clear(mv->last_viewed_memory_cache_arena); + mv->last_viewed_memory_cache_buffer = push_array(mv->last_viewed_memory_cache_arena, U8, bytes_to_read); + } + + // rjf: didn't fully work, but changed? -> clear cache memory, fill what we can, zero the rest. + if(half1_bytes_read+half2_bytes_read < bytes_to_read && half1_bytes_read+half2_bytes_read != 0) + { + arena_clear(mv->last_viewed_memory_cache_arena); + mv->last_viewed_memory_cache_buffer = push_array(mv->last_viewed_memory_cache_arena, U8, bytes_to_read); + MemoryCopy(mv->last_viewed_memory_cache_buffer+0, buffer+0, half1_bytes_read); + MemoryCopy(mv->last_viewed_memory_cache_buffer+bytes_to_read/2, buffer+bytes_to_read/2, half2_bytes_read); + } + + // rjf: update cache stamps + if(!df_ctrl_targets_running()) + { + mv->last_viewed_memory_cache_range = chunk_aligned_range_bytes; + mv->last_viewed_memory_cache_memgen_idx = current_memgen_idx; + } + + scratch_end(scratch); + } + visible_memory = mv->last_viewed_memory_cache_buffer + viz_range_bytes.min-chunk_aligned_range_bytes.min; + } + + ////////////////////////////// + //- rjf: grab annotations for windowed range of memory + // + typedef struct Annotation Annotation; + struct Annotation + { + Annotation *next; + String8 name_string; + String8 kind_string; + String8 type_string; + Vec4F32 color; + Rng1U64 vaddr_range; + }; + typedef struct AnnotationList AnnotationList; + struct AnnotationList + { + Annotation *first; + Annotation *last; + }; + AnnotationList *visible_memory_annotations = push_array(scratch.arena, AnnotationList, visible_memory_size); + { + CTRL_Unwind unwind = df_query_cached_unwind_from_thread(thread); + + //- rjf: fill unwind frame annotations + if(unwind.frames.count != 0) + { + U64 last_stack_top = regs_rsp_from_arch_block(thread->arch, unwind.frames.v[0].regs); + for(U64 idx = 1; idx < unwind.frames.count; idx += 1) + { + CTRL_UnwindFrame *f = &unwind.frames.v[idx]; + U64 f_stack_top = regs_rsp_from_arch_block(thread->arch, f->regs); + Rng1U64 frame_vaddr_range = r1u64(last_stack_top, f_stack_top); + Rng1U64 frame_vaddr_range_in_viz = intersect_1u64(frame_vaddr_range, viz_range_bytes); + last_stack_top = f_stack_top; + if(dim_1u64(frame_vaddr_range_in_viz) != 0) + { + U64 f_rip = regs_rip_from_arch_block(thread->arch, f->regs); + DF_Entity *module = df_module_from_process_vaddr(process, f_rip); + DI_Key dbgi_key = df_dbgi_key_from_module(module); + U64 rip_voff = df_voff_from_vaddr(module, f_rip); + String8 symbol_name = df_symbol_name_from_dbgi_key_voff(scratch.arena, &dbgi_key, rip_voff); + Annotation *annotation = push_array(scratch.arena, Annotation, 1); + annotation->name_string = symbol_name.size != 0 ? symbol_name : str8_lit("[external code]"); + annotation->kind_string = str8_lit("Call Stack Frame"); + annotation->color = symbol_name.size != 0 ? df_rgba_from_theme_color(DF_ThemeColor_CodeSymbol) : df_rgba_from_theme_color(DF_ThemeColor_TextWeak); + annotation->vaddr_range = frame_vaddr_range; + for(U64 vaddr = frame_vaddr_range_in_viz.min; vaddr < frame_vaddr_range_in_viz.max; vaddr += 1) + { + U64 visible_byte_idx = vaddr - viz_range_bytes.min; + SLLQueuePush(visible_memory_annotations[visible_byte_idx].first, visible_memory_annotations[visible_byte_idx].last, annotation); + } + } + } + } + + //- rjf: fill selected thread stack range annotation + if(unwind.frames.count > 0) + { + U64 stack_base_vaddr = thread->stack_base; + U64 stack_top_vaddr = regs_rsp_from_arch_block(thread->arch, unwind.frames.v[0].regs); + Rng1U64 stack_vaddr_range = r1u64(stack_base_vaddr, stack_top_vaddr); + Rng1U64 stack_vaddr_range_in_viz = intersect_1u64(stack_vaddr_range, viz_range_bytes); + if(dim_1u64(stack_vaddr_range_in_viz) != 0) + { + Annotation *annotation = push_array(scratch.arena, Annotation, 1); + annotation->name_string = df_display_string_from_entity(scratch.arena, thread); + annotation->kind_string = str8_lit("Stack"); + annotation->color = thread->flags & DF_EntityFlag_HasColor ? df_rgba_from_entity(thread) : df_rgba_from_theme_color(DF_ThemeColor_Text); + annotation->vaddr_range = stack_vaddr_range; + for(U64 vaddr = stack_vaddr_range_in_viz.min; vaddr < stack_vaddr_range_in_viz.max; vaddr += 1) + { + U64 visible_byte_idx = vaddr - viz_range_bytes.min; + SLLQueuePush(visible_memory_annotations[visible_byte_idx].first, visible_memory_annotations[visible_byte_idx].last, annotation); + } + } + } + + //- rjf: fill local variable annotations + { + Vec4F32 color_gen_table[] = + { + df_rgba_from_theme_color(DF_ThemeColor_Thread0), + df_rgba_from_theme_color(DF_ThemeColor_Thread1), + df_rgba_from_theme_color(DF_ThemeColor_Thread2), + df_rgba_from_theme_color(DF_ThemeColor_Thread3), + df_rgba_from_theme_color(DF_ThemeColor_Thread4), + df_rgba_from_theme_color(DF_ThemeColor_Thread5), + df_rgba_from_theme_color(DF_ThemeColor_Thread6), + df_rgba_from_theme_color(DF_ThemeColor_Thread7), + }; + DI_Scope *scope = di_scope_open(); + U64 thread_rip_vaddr = df_query_cached_rip_from_thread_unwind(thread, ctrl_ctx.unwind_count); + EVAL_ParseCtx parse_ctx = df_eval_parse_ctx_from_process_vaddr(scope, process, thread_rip_vaddr); + RDI_Parsed *rdi = parse_ctx.rdi; + for(EVAL_String2NumMapNode *n = parse_ctx.locals_map->first; n != 0; n = n->order_next) + { + String8 local_name = n->string; + DF_Eval local_eval = df_eval_from_string(scratch.arena, scope, &ctrl_ctx, &parse_ctx, &eval_string2expr_map_nil, local_name); + if(local_eval.mode == EVAL_EvalMode_Addr) + { + TG_Kind local_eval_type_kind = tg_kind_from_key(local_eval.type_key); + U64 local_eval_type_size = tg_byte_size_from_graph_rdi_key(parse_ctx.type_graph, rdi, local_eval.type_key); + Rng1U64 vaddr_rng = r1u64(local_eval.offset, local_eval.offset+local_eval_type_size); + Rng1U64 vaddr_rng_in_visible = intersect_1u64(viz_range_bytes, vaddr_rng); + if(vaddr_rng_in_visible.max != vaddr_rng_in_visible.min) + { + Annotation *annotation = push_array(scratch.arena, Annotation, 1); + { + annotation->name_string = push_str8_copy(scratch.arena, local_name); + annotation->kind_string = str8_lit("Local"); + annotation->type_string = tg_string_from_key(scratch.arena, parse_ctx.type_graph, parse_ctx.rdi, local_eval.type_key); + annotation->color = color_gen_table[(vaddr_rng.min/8)%ArrayCount(color_gen_table)]; + annotation->vaddr_range = vaddr_rng; + } + for(U64 vaddr = vaddr_rng_in_visible.min; vaddr < vaddr_rng_in_visible.max; vaddr += 1) + { + SLLQueuePushFront(visible_memory_annotations[vaddr-viz_range_bytes.min].first, visible_memory_annotations[vaddr-viz_range_bytes.min].last, annotation); + } + } + } + } + di_scope_close(scope); + } + } + + ////////////////////////////// + //- rjf: build main container + // + UI_Box *container_box = &ui_g_nil_box; + { + Vec2F32 dim = dim_2f32(rect); + ui_set_next_fixed_width(dim.x); + ui_set_next_fixed_height(dim.y); + ui_set_next_child_layout_axis(Axis2_Y); + container_box = ui_build_box_from_stringf(0, "memory_view_container_%p", view); + } + + ////////////////////////////// + //- rjf: build header + // + UI_Box *header_box = &ui_g_nil_box; + UI_Parent(container_box) + { + UI_WidthFill UI_PrefHeight(ui_px(row_height_px, 1.f)) UI_Row + header_box = ui_build_box_from_stringf(UI_BoxFlag_DrawSideBottom, "table_header"); + UI_Parent(header_box) + DF_Font(ws, DF_FontSlot_Code) + UI_FontSize(font_size) + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + { + UI_PrefWidth(ui_px(big_glyph_advance*18.f, 1.f)) ui_labelf("Address"); + UI_PrefWidth(ui_px(cell_width_px, 1.f)) + UI_TextAlignment(UI_TextAlign_Center) + { + Rng1U64 col_selection_rng = r1u64(mv->cursor%mv->num_columns, mv->mark%mv->num_columns); + for(U64 row_off = 0; row_off < mv->num_columns*mv->bytes_per_cell; row_off += mv->bytes_per_cell) + { + if(!(col_selection_rng.min <= row_off && row_off <= col_selection_rng.max)) + { + ui_set_next_flags(UI_BoxFlag_DrawTextWeak); + } + ui_labelf("%I64X", row_off); + } + } + ui_spacer(ui_px(big_glyph_advance*1.5f, 1.f)); + UI_WidthFill ui_labelf("ASCII"); + } + } + + ////////////////////////////// + //- rjf: build scroll bar + // + UI_Parent(container_box) + { + ui_set_next_fixed_x(content_rect.x1); + ui_set_next_fixed_y(content_rect.y0); + ui_set_next_fixed_width(scroll_bar_dim); + ui_set_next_fixed_height(dim_2f32(content_rect).y); + { + view->scroll_pos.y = ui_scroll_bar(Axis2_Y, + ui_px(scroll_bar_dim, 1.f), + view->scroll_pos.y, + scroll_idx_rng, + num_possible_visible_rows); + } + } + + ////////////////////////////// + //- rjf: build scrollable box + // + UI_Box *scrollable_box = &ui_g_nil_box; + UI_Parent(container_box) + { + ui_set_next_fixed_x(content_rect.x0); + ui_set_next_fixed_y(content_rect.y0); + ui_set_next_fixed_width(dim_2f32(content_rect).x); + ui_set_next_fixed_height(dim_2f32(content_rect).y); + ui_set_next_child_layout_axis(Axis2_Y); + scrollable_box = ui_build_box_from_stringf(UI_BoxFlag_Clip| + UI_BoxFlag_Scroll| + UI_BoxFlag_AllowOverflowX| + UI_BoxFlag_AllowOverflowY, + "scrollable_box"); + container_box->view_off.x = container_box->view_off_target.x = view->scroll_pos.x.idx + view->scroll_pos.x.off; + scrollable_box->view_off.y = scrollable_box->view_off_target.y = floor_f32(row_height_px*mod_f32(view->scroll_pos.y.off, 1.f) + row_height_px*(view->scroll_pos.y.off < 0)); + } + + ////////////////////////////// + //- rjf: build row container/overlay + // + UI_Box *row_container_box = &ui_g_nil_box; + UI_Box *row_overlay_box = &ui_g_nil_box; + UI_Parent(scrollable_box) UI_WidthFill UI_HeightFill + { + ui_set_next_child_layout_axis(Axis2_Y); + ui_set_next_hover_cursor(OS_Cursor_IBar); + row_container_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable, "row_container"); + UI_Parent(row_container_box) + { + row_overlay_box = ui_build_box_from_stringf(UI_BoxFlag_Floating, "row_overlay"); + } + } + + ////////////////////////////// + //- rjf: interact with row container + // + U64 mouse_hover_byte_num = 0; + { + UI_Signal sig = ui_signal_from_box(row_container_box); + + // rjf: calculate hovered byte + if(ui_hovering(sig) || ui_dragging(sig)) + { + Vec2F32 mouse_rel = sub_2f32(ui_mouse(), row_container_box->rect.p0); + U64 row_idx = ClampBot(0, mouse_rel.y) / row_height_px; + + // rjf: try from cells + if(mouse_hover_byte_num == 0) + { + U64 col_idx = ClampBot(mouse_rel.x-big_glyph_advance*18.f, 0)/cell_width_px; + if(col_idx < mv->num_columns) + { + mouse_hover_byte_num = viz_range_bytes.min + row_idx*mv->num_columns + col_idx + 1; + } + } + + // rjf: try from ascii + if(mouse_hover_byte_num == 0) + { + U64 col_idx = ClampBot(mouse_rel.x - (big_glyph_advance*18.f + cell_width_px*mv->num_columns + big_glyph_advance*1.5f), 0)/big_glyph_advance; + col_idx = ClampTop(col_idx, mv->num_columns-1); + mouse_hover_byte_num = viz_range_bytes.min + row_idx*mv->num_columns + col_idx + 1; + } + + mouse_hover_byte_num = Clamp(1, mouse_hover_byte_num, 0x7FFFFFFFFFFFull+1); + } + + // rjf: press -> focus panel + if(ui_pressed(sig)) + { + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + + // rjf: click & drag -> select + if(ui_dragging(sig) && mouse_hover_byte_num != 0) + { + if(!contains_2f32(sig.box->rect, ui_mouse())) + { + mv->contain_cursor = 1; + } + mv->cursor = mouse_hover_byte_num-1; + if(ui_pressed(sig)) + { + mv->mark = mv->cursor; + } + } + + // rjf: ctrl+scroll -> change font size + if(ui_hovering(sig)) + { + UI_EventList *events = ui_events(); + for(UI_EventNode *n = events->first, *next = 0; n != 0; n = next) + { + next = n->next; + UI_Event *event = &n->v; + if(event->kind == UI_EventKind_Scroll && event->modifiers & OS_EventFlag_Ctrl) + { + ui_eat_event(events, n); + if(event->delta_2f32.y < 0) + { + DF_CmdParams params = df_cmd_params_from_window(ws); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_IncCodeFontScale)); + } + else if(event->delta_2f32.y > 0) + { + DF_CmdParams params = df_cmd_params_from_window(ws); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_DecCodeFontScale)); + } + } + } + } + } + + ////////////////////////////// + //- rjf: build rows + // + UI_Parent(row_container_box) DF_Font(ws, DF_FontSlot_Code) UI_FontSize(font_size) + { + Rng1U64 selection = r1u64(mv->cursor, mv->mark); + U8 *row_ascii_buffer = push_array(scratch.arena, U8, mv->num_columns); + UI_WidthFill UI_PrefHeight(ui_px(row_height_px, 1.f)) + for(S64 row_idx = viz_range_rows.min; row_idx <= viz_range_rows.max; row_idx += 1) + { + Rng1U64 row_range_bytes = r1u64(row_idx*mv->num_columns, (row_idx+1)*mv->num_columns); + B32 row_is_boundary = 0; + Vec4F32 row_boundary_color = {0}; + if(row_range_bytes.min%64 == 0) + { + row_is_boundary = 1; + row_boundary_color = df_rgba_from_theme_color(DF_ThemeColor_BaseBorder); + } + UI_Box *row = ui_build_box_from_stringf(UI_BoxFlag_DrawSideTop*!!row_is_boundary, "row_%I64x", row_range_bytes.min); + UI_Parent(row) + { + UI_PrefWidth(ui_px(big_glyph_advance*18.f, 1.f)) + { + if(!(selection.max >= row_range_bytes.min && selection.min < row_range_bytes.max)) + { + ui_set_next_flags(UI_BoxFlag_DrawTextWeak); + } + ui_labelf("%016I64X", row_range_bytes.min); + } + UI_PrefWidth(ui_px(cell_width_px, 1.f)) + UI_TextAlignment(UI_TextAlign_Center) + UI_CornerRadius(0) + { + for(U64 col_idx = 0; col_idx < mv->num_columns; col_idx += 1) + { + U64 visible_byte_idx = (row_idx-viz_range_rows.min)*mv->num_columns + col_idx; + U64 global_byte_idx = viz_range_bytes.min+visible_byte_idx; + U64 global_byte_num = global_byte_idx+1; + U8 byte_value = visible_memory[visible_byte_idx]; + Annotation *annotation = visible_memory_annotations[visible_byte_idx].first; + UI_BoxFlags cell_flags = 0; + Vec4F32 cell_border_rgba = {0}; + Vec4F32 cell_bg_rgba = {0}; + if(global_byte_num == mouse_hover_byte_num) + { + cell_flags |= UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawSideTop|UI_BoxFlag_DrawSideBottom|UI_BoxFlag_DrawSideLeft|UI_BoxFlag_DrawSideRight; + cell_border_rgba = df_rgba_from_theme_color(DF_ThemeColor_Hover); + } + if(annotation != 0) + { + cell_flags |= UI_BoxFlag_DrawBackground; + cell_bg_rgba = annotation->color; + if(contains_1u64(annotation->vaddr_range, mouse_hover_byte_num-1)) + { + cell_bg_rgba.w *= 0.15f; + } + else + { + cell_bg_rgba.w *= 0.08f; + } + } + if(selection.min <= global_byte_idx && global_byte_idx <= selection.max) + { + cell_flags |= UI_BoxFlag_DrawBackground; + cell_bg_rgba = df_rgba_from_theme_color(DF_ThemeColor_SelectionOverlay); + } + ui_set_next_palette(ui_build_palette(ui_top_palette(), .background = cell_bg_rgba)); + UI_Box *cell_box = ui_build_box_from_key(UI_BoxFlag_DrawText|cell_flags, ui_key_zero()); + ui_box_equip_display_fancy_strings(cell_box, &byte_fancy_strings[byte_value]); + { + F32 off = 0; + for(Annotation *a = annotation; a != 0; a = a->next) + { + if(global_byte_idx == a->vaddr_range.min) UI_Parent(row_overlay_box) + { + ui_set_next_palette(ui_build_palette(ui_top_palette(), .background = annotation->color)); + ui_set_next_fixed_x(big_glyph_advance*18.f + col_idx*cell_width_px + -cell_width_px/8.f + off); + ui_set_next_fixed_y((row_idx-viz_range_rows.min)*row_height_px + -cell_width_px/8.f); + ui_set_next_fixed_width(cell_width_px/4.f); + ui_set_next_fixed_height(cell_width_px/4.f); + ui_set_next_corner_radius_00(cell_width_px/8.f); + ui_set_next_corner_radius_01(cell_width_px/8.f); + ui_set_next_corner_radius_10(cell_width_px/8.f); + ui_set_next_corner_radius_11(cell_width_px/8.f); + ui_build_box_from_key(UI_BoxFlag_Floating|UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawDropShadow, ui_key_zero()); + off += cell_width_px/8.f + cell_width_px/16.f; + } + } + } + if(annotation != 0 && mouse_hover_byte_num == global_byte_num) UI_Tooltip UI_FontSize(ui_top_font_size()) UI_PrefHeight(ui_px(ui_top_font_size()*1.75f, 1.f)) + { + for(Annotation *a = annotation; a != 0; a = a->next) + { + UI_PrefWidth(ui_children_sum(1)) UI_Row UI_PrefWidth(ui_text_dim(10, 1)) + { + DF_Font(ws, DF_FontSlot_Code) ui_label(a->name_string); + DF_Font(ws, DF_FontSlot_Main) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(a->kind_string); + } + if(a->type_string.size != 0) + { + df_code_label(1.f, 1, df_rgba_from_theme_color(DF_ThemeColor_CodeType), a->type_string); + } + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(str8_from_memory_size(scratch.arena, dim_1u64(a->vaddr_range))); + if(a->next != 0) + { + ui_spacer(ui_em(1.5f, 1.f)); + } + } + } + } + } + ui_spacer(ui_px(big_glyph_advance*1.5f, 1.f)); + UI_WidthFill + { + MemoryZero(row_ascii_buffer, mv->num_columns); + for(U64 col_idx = 0; col_idx < mv->num_columns; col_idx += 1) + { + U8 byte_value = visible_memory[(row_idx-viz_range_rows.min)*mv->num_columns + col_idx]; + row_ascii_buffer[col_idx] = byte_value; + if(byte_value <= 32 || 127 < byte_value) + { + row_ascii_buffer[col_idx] = '.'; + } + } + String8 ascii_text = str8(row_ascii_buffer, mv->num_columns); + UI_Box *ascii_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S###ascii_row_%I64x", ascii_text, row_range_bytes.min); + if(selection.max >= row_range_bytes.min && selection.min < row_range_bytes.max) + { + Rng1U64 selection_in_row = intersect_1u64(row_range_bytes, selection); + D_Bucket *bucket = d_bucket_make(); + D_BucketScope(bucket) + { + Vec2F32 text_pos = ui_box_text_position(ascii_box); + d_rect(r2f32p(text_pos.x + f_dim_from_tag_size_string(font, font_size, 0, 0, str8_prefix(ascii_text, selection_in_row.min+0-row_range_bytes.min)).x - font_size/8.f, + ascii_box->rect.y0, + text_pos.x + f_dim_from_tag_size_string(font, font_size, 0, 0, str8_prefix(ascii_text, selection_in_row.max+1-row_range_bytes.min)).x + font_size/4.f, + ascii_box->rect.y1), + df_rgba_from_theme_color(DF_ThemeColor_SelectionOverlay), + 0, 0, 1.f); + } + ui_box_equip_draw_bucket(ascii_box, bucket); + } + if(mouse_hover_byte_num != 0 && contains_1u64(row_range_bytes, mouse_hover_byte_num-1)) + { + D_Bucket *bucket = d_bucket_make(); + D_BucketScope(bucket) + { + Vec2F32 text_pos = ui_box_text_position(ascii_box); + Vec4F32 color = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlay); + d_rect(r2f32p(text_pos.x + f_dim_from_tag_size_string(font, font_size, 0, 0, str8_prefix(ascii_text, mouse_hover_byte_num-1-row_range_bytes.min)).x - font_size/8.f, + ascii_box->rect.y0, + text_pos.x + f_dim_from_tag_size_string(font, font_size, 0, 0, str8_prefix(ascii_text, mouse_hover_byte_num+0-row_range_bytes.min)).x + font_size/4.f, + ascii_box->rect.y1), + color, + 1.f, 3.f, 1.f); + } + ui_box_equip_draw_bucket(ascii_box, bucket); + } + } + } + } + } + + ////////////////////////////// + //- rjf: build footer + // + UI_Box *footer_box = &ui_g_nil_box; + UI_Parent(container_box) + { + ui_set_next_fixed_x(footer_rect.x0); + ui_set_next_fixed_y(footer_rect.y0); + ui_set_next_fixed_width(dim_2f32(footer_rect).x); + ui_set_next_fixed_height(dim_2f32(footer_rect).y); + footer_box = ui_build_box_from_stringf(UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawDropShadow, "footer"); + UI_Parent(footer_box) DF_Font(ws, DF_FontSlot_Code) UI_FontSize(font_size) + { + UI_PrefWidth(ui_em(7.5f, 1.f)) UI_HeightFill UI_Column UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + UI_PrefHeight(ui_px(row_height_px, 0.f)) + { + ui_labelf("Address:"); + ui_labelf("U8:"); + ui_labelf("U16:"); + ui_labelf("U32:"); + ui_labelf("U64:"); + } + UI_PrefWidth(ui_em(45.f, 1.f)) UI_HeightFill UI_Column + UI_PrefHeight(ui_px(row_height_px, 0.f)) + { + B32 cursor_in_range = (viz_range_bytes.min <= mv->cursor && mv->cursor+8 <= viz_range_bytes.max); + ui_labelf("%016I64X", mv->cursor); + if(cursor_in_range) + { + U64 as_u8 = 0; + U64 as_u16 = 0; + U64 as_u32 = 0; + U64 as_u64 = 0; + U64 cursor_off = mv->cursor-viz_range_bytes.min; + as_u8 = (U64)*(U8 *)(visible_memory + cursor_off); + as_u16 = (U64)*(U16*)(visible_memory + cursor_off); + as_u32 = (U64)*(U32*)(visible_memory + cursor_off); + as_u64 = (U64)*(U64*)(visible_memory + cursor_off); + ui_labelf("%02X (%I64u)", as_u8, as_u8); + ui_labelf("%04X (%I64u)", as_u16, as_u16); + ui_labelf("%08X (%I64u)", as_u32, as_u32); + ui_labelf("%016I64X (%I64u)", as_u64, as_u64); + } + } + } + } + + ////////////////////////////// + //- rjf: scroll + // + { + UI_Signal sig = ui_signal_from_box(scrollable_box); + if(sig.scroll.y != 0) + { + S64 new_idx = view->scroll_pos.y.idx + sig.scroll.y; + new_idx = clamp_1s64(scroll_idx_rng, new_idx); + ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx); + } + } + + hs_scope_close(hs_scope); + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Breakpoints @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Breakpoints) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Breakpoints) {return str8_lit("");} +DF_VIEW_CMD_FUNCTION_DEF(Breakpoints) {} +DF_VIEW_UI_FUNCTION_DEF(Breakpoints) +{ + Temp scratch = scratch_begin(0, 0); + String8 query = str8(view->query_buffer, view->query_string_size); + + //- rjf: get state + typedef struct DF_BreakpointsViewState DF_BreakpointsViewState; + struct DF_BreakpointsViewState + { + B32 initialized; + DF_Handle selected_entity; + S64 selected_column; + F32 enabled_col_pct; + F32 desc_col_pct; + F32 loc_col_pct; + F32 hit_col_pct; + F32 del_col_pct; + }; + DF_BreakpointsViewState *bv = df_view_user_state(view, DF_BreakpointsViewState); + if(bv->initialized == 0) + { + bv->initialized = 1; + bv->enabled_col_pct = 0.05f; + bv->desc_col_pct = 0.25f; + bv->loc_col_pct = 0.50f; + bv->hit_col_pct = 0.15f; + bv->del_col_pct = 0.05f; + } + F32 *col_pcts[] = {&bv->enabled_col_pct, &bv->desc_col_pct, &bv->loc_col_pct, &bv->hit_col_pct, &bv->del_col_pct}; + + //- rjf: get entities + DF_EntityList entities_list = df_query_cached_entity_list_with_kind(DF_EntityKind_Breakpoint); + DF_EntityFuzzyItemArray entities = df_entity_fuzzy_item_array_from_entity_list_needle(scratch.arena, &entities_list, query); + + //- rjf: selected column/entity -> selected cursor + Vec2S64 cursor = {bv->selected_column}; + for(U64 idx = 0; idx < entities.count; idx += 1) + { + if(entities.v[idx].entity == df_entity_from_handle(bv->selected_entity)) + { + cursor.y = (S64)(idx+1); + break; + } + } + + //- rjf: build table + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*2.5f); + scroll_list_params.dim_px = dim_2f32(rect); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(4, entities.count)); + scroll_list_params.item_range = r1s64(0, entities.count+1); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + &cursor, + 0, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + UI_TableF(ArrayCount(col_pcts), col_pcts, "breakpoints_table") + { + if(visible_row_range.min == 0) UI_TableVector UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + { + UI_TableCell{} + UI_TableCell{ui_labelf("Name");} + UI_TableCell{ui_labelf("Location");} + UI_TableCell{ui_labelf("Hit Count");} + UI_TableCell{} + } + Vec2S64 next_cursor = cursor; + for(U64 idx = Max(1, visible_row_range.min); idx <= visible_row_range.max && idx <= entities.count; idx += 1) + { + DF_Entity *entity = entities.v[idx-1].entity; + B32 row_is_selected = (cursor.y == (S64)(idx)); + UI_NamedTableVectorF("breakpoint_%p", entity) + { + UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off) + { + if(ui_clicked(df_icon_buttonf(ws, entity->b32 ? DF_IconKind_CheckFilled : DF_IconKind_CheckHollow, 0, "###ebl_%p", entity))) + { + df_entity_equip_b32(entity, !entity->b32); + } + } + UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) + { + df_entity_desc_button(ws, entity, &entities.v[idx-1].matches, query, 1); + } + UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 2) ? UI_FocusKind_On : UI_FocusKind_Off) + { + B32 loc_is_code = 0; + String8 loc_string = {0}; + DF_Entity *file_parent = df_entity_ancestor_from_kind(entity, DF_EntityKind_File); + DF_Entity *symbol_name = df_entity_child_from_kind(entity, DF_EntityKind_EntryPointName); + if(!df_entity_is_nil(file_parent)) + { + loc_string = push_str8f(scratch.arena, "%S:%I64u:%I64u", file_parent->name, entity->text_point.line, entity->text_point.column); + } + else if(!df_entity_is_nil(symbol_name)) + { + loc_string = symbol_name->name; + loc_is_code = 1; + } + else if(entity->flags & DF_EntityFlag_HasVAddr) + { + loc_string = push_str8f(scratch.arena, "0x%016I64x", entity->vaddr); + loc_is_code = 1; + } + UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable, "loc_%p", entity); + UI_Parent(box) + { + DF_Font(ws, loc_is_code ? DF_FontSlot_Code : DF_FontSlot_Main) ui_label(loc_string); + } + UI_Signal sig = ui_signal_from_box(box); + if(ui_double_clicked(sig) || sig.f&UI_SignalFlag_KeyboardPressed) + { + DF_CmdParams params = df_cmd_params_from_window(ws); + params.file_path = df_full_path_from_entity(scratch.arena, file_parent); + params.text_point = entity->text_point; + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_TextPoint); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FindCodeLocation)); + } + if(ui_pressed(sig)) + { + next_cursor = v2s64(2, (S64)(idx)); + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + } + UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 3) ? UI_FocusKind_On : UI_FocusKind_Off) + { + UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable, "###cnd_%p", entity); + UI_Parent(box) + { + String8 hit_count_string = str8_from_u64(scratch.arena, entity->u64, 10, 0, 0); + DF_Font(ws, DF_FontSlot_Code) df_code_label(1.f, 1, df_rgba_from_theme_color(DF_ThemeColor_CodeDefault), hit_count_string); + } + UI_Signal sig = ui_signal_from_box(box); + if(ui_pressed(sig)) + { + next_cursor = v2s64(3, (S64)(idx)); + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + } + UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 4) ? UI_FocusKind_On : UI_FocusKind_Off) + { + if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_Trash, 0, "###del_%p", entity))) + { + df_entity_mark_for_deletion(entity); + } + } + } + } + cursor = next_cursor; + } + + //- rjf: selected num -> selected entity + bv->selected_column = cursor.x; + bv->selected_entity = (1 <= cursor.y && cursor.y <= entities.count) ? df_handle_from_entity(entities.v[cursor.y-1].entity) : df_handle_zero(); + + scratch_end(scratch); +} + +//////////////////////////////// +//~ rjf: WatchPins @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(WatchPins) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(WatchPins) {return str8_lit("");} +DF_VIEW_CMD_FUNCTION_DEF(WatchPins) {} +DF_VIEW_UI_FUNCTION_DEF(WatchPins) +{ + Temp scratch = scratch_begin(0, 0); + String8 query = str8(view->query_buffer, view->query_string_size); + + //- rjf: get state + typedef struct DF_WatchPinsViewState DF_WatchPinsViewState; + struct DF_WatchPinsViewState + { + B32 initialized; + DF_Handle selected_entity; + S64 selected_column; + F32 desc_col_pct; + F32 loc_col_pct; + F32 del_col_pct; + }; + DF_WatchPinsViewState *pv = df_view_user_state(view, DF_WatchPinsViewState); + if(pv->initialized == 0) + { + pv->initialized = 1; + pv->desc_col_pct = 0.35f; + pv->loc_col_pct = 0.60f; + pv->del_col_pct = 0.05f; + } + F32 *col_pcts[] = {&pv->desc_col_pct, &pv->loc_col_pct, &pv->del_col_pct}; + + //- rjf: get entities + DF_EntityList entities_list = df_query_cached_entity_list_with_kind(DF_EntityKind_WatchPin); + DF_EntityFuzzyItemArray entities = df_entity_fuzzy_item_array_from_entity_list_needle(scratch.arena, &entities_list, query); + + //- rjf: selected column/entity -> selected cursor + Vec2S64 cursor = {pv->selected_column}; + for(U64 idx = 0; idx < entities.count; idx += 1) + { + if(entities.v[idx].entity == df_entity_from_handle(pv->selected_entity)) + { + cursor.y = (S64)(idx+1); + break; + } + } + + //- rjf: build table + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*2.5f); + scroll_list_params.dim_px = dim_2f32(rect); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(2, entities.count)); + scroll_list_params.item_range = r1s64(0, entities.count+1); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + &cursor, + 0, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + UI_TableF(ArrayCount(col_pcts), col_pcts, "pins_table") + { + if(visible_row_range.min == 0) UI_TableVector UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) + { + UI_TableCell{ui_labelf("Name");} + UI_TableCell{ui_labelf("Location");} + UI_TableCell{} + } + Vec2S64 next_cursor = cursor; + for(U64 idx = Max(1, visible_row_range.min); idx <= visible_row_range.max && idx <= entities.count; idx += 1) + { + DF_Entity *entity = entities.v[idx-1].entity; + B32 row_is_selected = (cursor.y == (S64)(idx)); + UI_NamedTableVectorF("pin_%p", entity) + { + UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off) + { + df_entity_desc_button(ws, entity, &entities.v[idx-1].matches, query, 1); + } + UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) + { + String8 loc_string = {0}; + DF_Entity *file_parent = df_entity_ancestor_from_kind(entity, DF_EntityKind_File); + if(!df_entity_is_nil(file_parent)) + { + loc_string = push_str8f(scratch.arena, "%S:%I64u:%I64u", file_parent->name, entity->text_point.line, entity->text_point.column); + } + else if(entity->flags & DF_EntityFlag_HasVAddr) + { + loc_string = push_str8f(scratch.arena, "0x%016I64x", entity->vaddr); + } + UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|UI_BoxFlag_DrawText, "%S###loc_%p", loc_string, entity); + UI_Signal sig = ui_signal_from_box(box); + if(ui_double_clicked(sig) || sig.f&UI_SignalFlag_KeyboardPressed) + { + DF_CmdParams params = df_cmd_params_from_window(ws); + params.file_path = df_full_path_from_entity(scratch.arena, file_parent); + params.text_point = entity->text_point; + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_FilePath); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_TextPoint); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FindCodeLocation)); + } + if(ui_pressed(sig)) + { + next_cursor = v2s64(1, (S64)(idx)); + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + } + UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 2) ? UI_FocusKind_On : UI_FocusKind_Off) + { + if(ui_clicked(df_icon_buttonf(ws, DF_IconKind_Trash, 0, "###del_%p", entity))) + { + df_entity_mark_for_deletion(entity); + } + } + } + } + cursor = next_cursor; + } + + //- rjf: selected num -> selected entity + pv->selected_column = cursor.x; + pv->selected_entity = (1 <= cursor.y && cursor.y <= entities.count) ? df_handle_from_entity(entities.v[cursor.y-1].entity) : df_handle_zero(); + + scratch_end(scratch); +} + +//////////////////////////////// +//~ rjf: ExceptionFilters @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(ExceptionFilters) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(ExceptionFilters) {return str8_lit("");} +DF_VIEW_CMD_FUNCTION_DEF(ExceptionFilters) {} +DF_VIEW_UI_FUNCTION_DEF(ExceptionFilters) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); + String8 query = str8(view->query_buffer, view->query_string_size); + + //- rjf: get state + typedef struct DF_ExceptionFiltersViewState DF_ExceptionFiltersViewState; + struct DF_ExceptionFiltersViewState + { + Vec2S64 cursor; + }; + DF_ExceptionFiltersViewState *sv = df_view_user_state(view, DF_ExceptionFiltersViewState); + + //- rjf: get list of options + typedef struct DF_ExceptionFiltersOption DF_ExceptionFiltersOption; + struct DF_ExceptionFiltersOption + { + String8 name; + FuzzyMatchRangeList matches; + B32 is_enabled; + CTRL_ExceptionCodeKind exception_code_kind; + }; + typedef struct DF_ExceptionFiltersOptionChunkNode DF_ExceptionFiltersOptionChunkNode; + struct DF_ExceptionFiltersOptionChunkNode + { + DF_ExceptionFiltersOptionChunkNode *next; + DF_ExceptionFiltersOption *v; + U64 cap; + U64 count; + }; + typedef struct DF_ExceptionFiltersOptionChunkList DF_ExceptionFiltersOptionChunkList; + struct DF_ExceptionFiltersOptionChunkList + { + DF_ExceptionFiltersOptionChunkNode *first; + DF_ExceptionFiltersOptionChunkNode *last; + U64 option_count; + U64 node_count; + }; + typedef struct DF_ExceptionFiltersOptionArray DF_ExceptionFiltersOptionArray; + struct DF_ExceptionFiltersOptionArray + { + DF_ExceptionFiltersOption *v; + U64 count; + }; + DF_ExceptionFiltersOptionChunkList opts_list = {0}; + for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)(CTRL_ExceptionCodeKind_Null+1); + k < CTRL_ExceptionCodeKind_COUNT; + k = (CTRL_ExceptionCodeKind)(k+1)) + { + DF_ExceptionFiltersOptionChunkNode *node = opts_list.last; + String8 name = push_str8f(scratch.arena, "0x%x %S", ctrl_exception_code_kind_code_table[k], ctrl_exception_code_kind_display_string_table[k]); + FuzzyMatchRangeList matches = fuzzy_match_find(scratch.arena, query, name); + if(matches.count >= matches.needle_part_count) + { + if(node == 0 || node->count >= node->cap) + { + node = push_array(scratch.arena, DF_ExceptionFiltersOptionChunkNode, 1); + node->cap = 256; + node->v = push_array_no_zero(scratch.arena, DF_ExceptionFiltersOption, node->cap); + SLLQueuePush(opts_list.first, opts_list.last, node); + opts_list.node_count += 1; + } + node->v[node->count].name = name; + node->v[node->count].matches = matches; + node->v[node->count].is_enabled = !!(df_state->ctrl_exception_code_filters[k/64] & (1ull<<(k%64))); + node->v[node->count].exception_code_kind = k; + node->count += 1; + opts_list.option_count += 1; + } + } + DF_ExceptionFiltersOptionArray opts = {0}; + { + opts.count = opts_list.option_count; + opts.v = push_array_no_zero(scratch.arena, DF_ExceptionFiltersOption, opts.count); + U64 idx = 0; + for(DF_ExceptionFiltersOptionChunkNode *n = opts_list.first; n != 0; n = n->next) + { + MemoryCopy(opts.v+idx, n->v, n->count*sizeof(DF_ExceptionFiltersOption)); + idx += n->count; + } + } + + //- rjf: build option table + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + Vec2F32 rect_dim = dim_2f32(rect); + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = row_height_px; + scroll_list_params.dim_px = rect_dim; + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, opts.count)); + scroll_list_params.item_range = r1s64(0, opts.count); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + &sv->cursor, + 0, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + { + for(S64 row = visible_row_range.min; row <= visible_row_range.max && row < opts.count; row += 1) + UI_FocusHot(sv->cursor.y == row+1 ? UI_FocusKind_On : UI_FocusKind_Off) + { + DF_ExceptionFiltersOption *opt = &opts.v[row]; + UI_Signal sig = df_icon_buttonf(ws, opt->is_enabled ? DF_IconKind_CheckFilled : DF_IconKind_CheckHollow, &opt->matches, "%S", opt->name); + if(ui_clicked(sig)) + { + if(opt->exception_code_kind != CTRL_ExceptionCodeKind_Null) + { + CTRL_ExceptionCodeKind k = opt->exception_code_kind; + if(opt->is_enabled) + { + df_state->ctrl_exception_code_filters[k/64] &= ~(1ull<<(k%64)); + } + else + { + df_state->ctrl_exception_code_filters[k/64] |= (1ull<<(k%64)); + } + } + } + } + } + + scratch_end(scratch); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Settings @view_hook_impl + +DF_VIEW_SETUP_FUNCTION_DEF(Settings) {} +DF_VIEW_STRING_FROM_STATE_FUNCTION_DEF(Settings) {return str8_zero();} + +DF_VIEW_CMD_FUNCTION_DEF(Settings) +{ + for(DF_CmdNode *n = cmds->first; n != 0; n = n->next) + { + DF_Cmd *cmd = &n->cmd; + + // rjf: mismatched window/panel => skip + if(df_window_from_handle(cmd->params.window) != ws || + df_panel_from_handle(cmd->params.panel) != panel) + { + continue; + } + + // rjf: process + DF_CoreCmdKind core_cmd_kind = df_core_cmd_kind_from_string(cmd->spec->info.string); + switch(core_cmd_kind) + { + default:break; + case DF_CoreCmdKind_PickFile: + { + Temp scratch = scratch_begin(0, 0); + String8 path = cmd->params.file_path; + String8 data = os_data_from_file_path(scratch.arena, path); + DF_CfgTable cfg_table = {0}; + df_cfg_table_push_unparsed_string(scratch.arena, &cfg_table, data, DF_CfgSrc_User); + DF_CfgVal *colors = df_cfg_val_from_string(&cfg_table, str8_lit("colors")); + for(DF_CfgNode *colors_set = colors->first; + colors_set != &df_g_nil_cfg_node; + colors_set = colors_set->next) + { + for(DF_CfgNode *color = colors_set->first; + color != &df_g_nil_cfg_node; + color = color->next) + { + String8 color_name = color->string; + DF_ThemeColor color_code = DF_ThemeColor_Null; + for(DF_ThemeColor c = DF_ThemeColor_Null; c < DF_ThemeColor_COUNT; c = (DF_ThemeColor)(c+1)) + { + if(str8_match(df_g_theme_color_cfg_string_table[c], color_name, StringMatchFlag_CaseInsensitive)) + { + color_code = c; + break; + } + } + if(color_code != DF_ThemeColor_Null) + { + DF_CfgNode *hex_cfg = color->first; + String8 hex_string = hex_cfg->string; + U64 hex_val = 0; + try_u64_from_str8_c_rules(hex_string, &hex_val); + Vec4F32 color_rgba = rgba_from_u32((U32)hex_val); + df_gfx_state->cfg_theme_target.colors[color_code] = color_rgba; + } + } + } + scratch_end(scratch); + }break; + } + } +} + +DF_VIEW_UI_FUNCTION_DEF(Settings) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); + String8 query = str8(view->query_buffer, view->query_string_size); + + ////////////////////////////// + //- rjf: get state + // + typedef struct DF_SettingsViewState DF_SettingsViewState; + struct DF_SettingsViewState + { + B32 initialized; + Vec2S64 cursor; + TxtPt txt_cursor; + TxtPt txt_mark; + U8 txt_buffer[1024]; + U64 txt_size; + DF_ThemeColor color_ctx_menu_color; + Vec4F32 color_ctx_menu_color_hsva; + DF_ThemePreset preset_apply_confirm; + B32 category_opened[DF_SettingsItemKind_COUNT]; + }; + DF_SettingsViewState *sv = df_view_user_state(view, DF_SettingsViewState); + if(!sv->initialized) + { + sv->initialized = 1; + sv->preset_apply_confirm = DF_ThemePreset_COUNT; + } + + ////////////////////////////// + //- rjf: gather all filtered settings items + // + DF_SettingsItemArray items = {0}; + { + DF_SettingsItemList items_list = {0}; + + //- rjf: global settings header + if(query.size == 0) + { + DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); + SLLQueuePush(items_list.first, items_list.last, n); + items_list.count += 1; + n->v.kind = DF_SettingsItemKind_CategoryHeader; + n->v.string = str8_lit("Global Interface Settings"); + n->v.icon_kind = sv->category_opened[DF_SettingsItemKind_GlobalSetting] ? DF_IconKind_DownCaret : DF_IconKind_RightCaret; + n->v.category = DF_SettingsItemKind_GlobalSetting; + } + + //- rjf: gather all global settings + if(sv->category_opened[DF_SettingsItemKind_GlobalSetting] || query.size != 0) + { + for(EachEnumVal(DF_SettingCode, code)) + { + if(df_g_setting_code_default_is_per_window_table[code]) + { + continue; + } + String8 kind_string = str8_lit("Global Interface Setting"); + String8 string = df_g_setting_code_display_string_table[code]; + FuzzyMatchRangeList kind_string_matches = fuzzy_match_find(scratch.arena, query, kind_string); + FuzzyMatchRangeList string_matches = fuzzy_match_find(scratch.arena, query, string); + if(string_matches.count == string_matches.needle_part_count || + kind_string_matches.count == kind_string_matches.needle_part_count) + { + DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); + SLLQueuePush(items_list.first, items_list.last, n); + items_list.count += 1; + n->v.kind = DF_SettingsItemKind_GlobalSetting; + n->v.kind_string = kind_string; + n->v.string = string; + n->v.kind_string_matches = kind_string_matches; + n->v.string_matches = string_matches; + n->v.icon_kind = DF_IconKind_Window; + n->v.code = code; + } + } + } + + //- rjf: window settings header + if(query.size == 0) + { + DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); + SLLQueuePush(items_list.first, items_list.last, n); + items_list.count += 1; + n->v.kind = DF_SettingsItemKind_CategoryHeader; + n->v.string = str8_lit("Window Interface Settings"); + n->v.icon_kind = sv->category_opened[DF_SettingsItemKind_WindowSetting] ? DF_IconKind_DownCaret : DF_IconKind_RightCaret; + n->v.category = DF_SettingsItemKind_WindowSetting; + } + + //- rjf: gather all window settings + if(sv->category_opened[DF_SettingsItemKind_WindowSetting] || query.size != 0) + { + for(EachEnumVal(DF_SettingCode, code)) + { + if(!df_g_setting_code_default_is_per_window_table[code]) + { + continue; + } + String8 kind_string = str8_lit("Window Interface Setting"); + String8 string = df_g_setting_code_display_string_table[code]; + FuzzyMatchRangeList kind_string_matches = fuzzy_match_find(scratch.arena, query, kind_string); + FuzzyMatchRangeList string_matches = fuzzy_match_find(scratch.arena, query, string); + if(string_matches.count == string_matches.needle_part_count || + kind_string_matches.count == kind_string_matches.needle_part_count) + { + DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); + SLLQueuePush(items_list.first, items_list.last, n); + items_list.count += 1; + n->v.kind = DF_SettingsItemKind_WindowSetting; + n->v.kind_string = kind_string; + n->v.string = string; + n->v.kind_string_matches = kind_string_matches; + n->v.string_matches = string_matches; + n->v.icon_kind = DF_IconKind_Window; + n->v.code = code; + } + } + } + + //- rjf: theme presets header + if(query.size == 0) + { + DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); + SLLQueuePush(items_list.first, items_list.last, n); + items_list.count += 1; + n->v.kind = DF_SettingsItemKind_CategoryHeader; + n->v.string = str8_lit("Theme Presets"); + n->v.icon_kind = sv->category_opened[DF_SettingsItemKind_ThemePreset] ? DF_IconKind_DownCaret : DF_IconKind_RightCaret; + n->v.category = DF_SettingsItemKind_ThemePreset; + } + + //- rjf: gather theme presets + if(sv->category_opened[DF_SettingsItemKind_ThemePreset] || query.size != 0) + { + for(EachEnumVal(DF_ThemePreset, preset)) + { + String8 kind_string = str8_lit("Theme Preset"); + String8 string = df_g_theme_preset_display_string_table[preset]; + FuzzyMatchRangeList kind_string_matches = fuzzy_match_find(scratch.arena, query, kind_string); + FuzzyMatchRangeList string_matches = fuzzy_match_find(scratch.arena, query, string); + if(string_matches.count == string_matches.needle_part_count || + kind_string_matches.count == kind_string_matches.needle_part_count) + { + DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); + SLLQueuePush(items_list.first, items_list.last, n); + items_list.count += 1; + n->v.kind = DF_SettingsItemKind_ThemePreset; + n->v.kind_string = kind_string; + n->v.string = string; + n->v.kind_string_matches = kind_string_matches; + n->v.string_matches = string_matches; + n->v.icon_kind = DF_IconKind_Palette; + n->v.preset = preset; + } + } + } + + //- rjf: theme colors header + if(query.size == 0) + { + DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); + SLLQueuePush(items_list.first, items_list.last, n); + items_list.count += 1; + n->v.kind = DF_SettingsItemKind_CategoryHeader; + n->v.string = str8_lit("Theme Colors"); + n->v.icon_kind = sv->category_opened[DF_SettingsItemKind_ThemeColor] ? DF_IconKind_DownCaret : DF_IconKind_RightCaret; + n->v.category = DF_SettingsItemKind_ThemeColor; + } + + //- rjf: gather all theme colors + if(sv->category_opened[DF_SettingsItemKind_ThemeColor] || query.size != 0) + { + for(EachNonZeroEnumVal(DF_ThemeColor, color)) + { + String8 kind_string = str8_lit("Theme Color"); + String8 string = df_g_theme_color_display_string_table[color]; + FuzzyMatchRangeList kind_string_matches = fuzzy_match_find(scratch.arena, query, kind_string); + FuzzyMatchRangeList string_matches = fuzzy_match_find(scratch.arena, query, string); + if(string_matches.count == string_matches.needle_part_count || + kind_string_matches.count == kind_string_matches.needle_part_count) + { + DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1); + SLLQueuePush(items_list.first, items_list.last, n); + items_list.count += 1; + n->v.kind = DF_SettingsItemKind_ThemeColor; + n->v.kind_string = kind_string; + n->v.string = string; + n->v.kind_string_matches = kind_string_matches; + n->v.string_matches = string_matches; + n->v.icon_kind = DF_IconKind_Palette; + n->v.color = color; + } + } + } + + //- rjf: convert to array + items.count = items_list.count; + items.v = push_array(scratch.arena, DF_SettingsItem, items.count); + { + U64 idx = 0; + for(DF_SettingsItemNode *n = items_list.first; n != 0; n = n->next, idx += 1) + { + items.v[idx] = n->v; + } + } + } + + ////////////////////////////// + //- rjf: sort filtered settings item list + // + if(query.size != 0) + { + quick_sort(items.v, items.count, sizeof(items.v[0]), df_qsort_compare_settings_item); + } + + ////////////////////////////// + //- rjf: produce per-color context menu keys + // + UI_Key *color_ctx_menu_keys = push_array(scratch.arena, UI_Key, DF_ThemeColor_COUNT); + { + for(DF_ThemeColor color = (DF_ThemeColor)(DF_ThemeColor_Null+1); + color < DF_ThemeColor_COUNT; + color = (DF_ThemeColor)(color+1)) + { + color_ctx_menu_keys[color] = ui_key_from_stringf(ui_key_zero(), "###settings_color_ctx_menu_%I64x", (U64)color); + } + } + + ////////////////////////////// + //- rjf: build color context menus + // + for(DF_ThemeColor color = (DF_ThemeColor)(DF_ThemeColor_Null+1); + color < DF_ThemeColor_COUNT; + color = (DF_ThemeColor)(color+1)) + { + DF_Palette(ws, DF_PaletteCode_Floating) + UI_CtxMenu(color_ctx_menu_keys[color]) + UI_Padding(ui_em(1.5f, 1.f)) + UI_PrefWidth(ui_em(28.5f, 1)) UI_PrefHeight(ui_children_sum(1.f)) + { + // rjf: build title + UI_Row + { + ui_spacer(ui_em(1.5f, 1.f)); + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(df_g_theme_color_display_string_table[color]); + } + + ui_spacer(ui_em(1.5f, 1.f)); + + // rjf: build picker + { + ui_set_next_pref_height(ui_em(22.f, 1.f)); + UI_Row UI_Padding(ui_pct(1, 0)) + { + UI_PrefWidth(ui_em(22.f, 1.f)) UI_PrefHeight(ui_em(22.f, 1.f)) UI_Flags(UI_BoxFlag_FocusNavSkip) + { + ui_sat_val_pickerf(sv->color_ctx_menu_color_hsva.x, &sv->color_ctx_menu_color_hsva.y, &sv->color_ctx_menu_color_hsva.z, "###settings_satval_picker"); + } + + ui_spacer(ui_em(0.75f, 1.f)); + + UI_PrefWidth(ui_em(1.5f, 1.f)) UI_PrefHeight(ui_em(22.f, 1.f)) UI_Flags(UI_BoxFlag_FocusNavSkip) + ui_hue_pickerf(&sv->color_ctx_menu_color_hsva.x, sv->color_ctx_menu_color_hsva.y, sv->color_ctx_menu_color_hsva.z, "###settings_hue_picker"); + + UI_PrefWidth(ui_em(1.5f, 1.f)) UI_PrefHeight(ui_em(22.f, 1.f)) UI_Flags(UI_BoxFlag_FocusNavSkip) + ui_alpha_pickerf(&sv->color_ctx_menu_color_hsva.w, "###settings_alpha_picker"); + } + } + + ui_spacer(ui_em(1.5f, 1.f)); + + // rjf: build line edits + UI_Row + UI_WidthFill + UI_Padding(ui_em(1.5f, 1.f)) + UI_PrefHeight(ui_children_sum(1.f)) + UI_Column + UI_PrefHeight(ui_em(2.25f, 1.f)) + { + Vec4F32 hsva = sv->color_ctx_menu_color_hsva; + Vec3F32 hsv = v3f32(hsva.x, hsva.y, hsva.z); + Vec3F32 rgb = rgb_from_hsv(hsv); + Vec4F32 rgba = v4f32(rgb.x, rgb.y, rgb.z, sv->color_ctx_menu_color_hsva.w); + String8 hex_string = hex_string_from_rgba_4f32(scratch.arena, rgba); + hex_string = push_str8f(scratch.arena, "#%S", hex_string); + String8 r_string = push_str8f(scratch.arena, "%.2f", rgba.x); + String8 g_string = push_str8f(scratch.arena, "%.2f", rgba.y); + String8 b_string = push_str8f(scratch.arena, "%.2f", rgba.z); + String8 h_string = push_str8f(scratch.arena, "%.2f", hsva.x); + String8 s_string = push_str8f(scratch.arena, "%.2f", hsva.y); + String8 v_string = push_str8f(scratch.arena, "%.2f", hsva.z); + String8 a_string = push_str8f(scratch.arena, "%.2f", rgba.w); + UI_Row DF_Font(ws, DF_FontSlot_Code) + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("Hex"); + UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, hex_string, "###hex_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_rgba = rgba_from_hex_string_4f32(string); + Vec4F32 new_hsva = hsva_from_rgba(new_rgba); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + ui_spacer(ui_em(0.75f, 1.f)); + UI_Row + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("R"); + UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, r_string, "###r_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_rgba = v4f32((F32)f64_from_str8(string), rgba.y, rgba.z, rgba.w); + Vec4F32 new_hsva = hsva_from_rgba(new_rgba); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + UI_Row + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("G"); + UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, g_string, "###g_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_rgba = v4f32(rgba.x, (F32)f64_from_str8(string), rgba.z, rgba.w); + Vec4F32 new_hsva = hsva_from_rgba(new_rgba); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + UI_Row + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("B"); + UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, b_string, "###b_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_rgba = v4f32(rgba.x, rgba.y, (F32)f64_from_str8(string), rgba.w); + Vec4F32 new_hsva = hsva_from_rgba(new_rgba); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + ui_spacer(ui_em(0.75f, 1.f)); + UI_Row + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("H"); + UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, h_string, "###h_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_hsva = v4f32((F32)f64_from_str8(string), hsva.y, hsva.z, hsva.w); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + UI_Row + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("S"); + UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, s_string, "###s_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_hsva = v4f32(hsva.x, (F32)f64_from_str8(string), hsva.z, hsva.w); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + UI_Row + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("V"); + UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, v_string, "###v_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_hsva = v4f32(hsva.x, hsva.y, (F32)f64_from_str8(string), hsva.w); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + ui_spacer(ui_em(0.75f, 1.f)); + UI_Row + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("A"); + UI_Signal sig = df_line_editf(ws, DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, a_string, "###a_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_hsva = v4f32(hsva.x, hsva.y, hsva.z, (F32)f64_from_str8(string)); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + } + + // rjf: commit state to theme + Vec4F32 hsva = sv->color_ctx_menu_color_hsva; + Vec3F32 hsv = v3f32(hsva.x, hsva.y, hsva.z); + Vec3F32 rgb = rgb_from_hsv(hsv); + Vec4F32 rgba = v4f32(rgb.x, rgb.y, rgb.z, sv->color_ctx_menu_color_hsva.w); + df_gfx_state->cfg_theme_target.colors[sv->color_ctx_menu_color] = rgba; + } + } + + ////////////////////////////// + //- rjf: cancels + // + UI_Focus(UI_FocusKind_On) if(ui_is_focus_active() && sv->preset_apply_confirm < DF_ThemePreset_COUNT && ui_slot_press(UI_EventActionSlot_Cancel)) + { + sv->preset_apply_confirm = DF_ThemePreset_COUNT; + } + + ////////////////////////////// + //- rjf: build items list + // + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + Vec2F32 rect_dim = dim_2f32(rect); + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = row_height_px; + scroll_list_params.dim_px = v2f32(rect_dim.x, rect_dim.y); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, items.count)); + scroll_list_params.item_range = r1s64(0, items.count); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, &view->scroll_pos.y, &sv->cursor, 0, &visible_row_range, &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + { + for(S64 row_num = visible_row_range.min; row_num <= visible_row_range.max && row_num < items.count; row_num += 1) + { + //- rjf: unpack item + DF_SettingsItem *item = &items.v[row_num]; + UI_Palette *palette = ui_top_palette(); + Vec4F32 rgba = ui_top_palette()->text_weak; + OS_Cursor cursor = OS_Cursor_HandPoint; + Rng1S32 s32_range = {0}; + B32 is_toggler = 0; + B32 is_toggled = 0; + B32 is_slider = 0; + S32 slider_s32_val = 0; + F32 slider_pct = 0.f; + UI_BoxFlags flags = UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawHotEffects|UI_BoxFlag_DrawActiveEffects; + DF_SettingVal *val_table = &df_gfx_state->cfg_setting_vals[DF_CfgSrc_User][0]; + switch(item->kind) + { + case DF_SettingsItemKind_COUNT:{}break; + case DF_SettingsItemKind_CategoryHeader: + { + cursor = OS_Cursor_HandPoint; + flags = UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawHotEffects; + }break; + case DF_SettingsItemKind_ThemePreset: + { + Vec4F32 *colors = df_g_theme_preset_colors_table[item->preset]; + Vec4F32 bg_color = colors[DF_ThemeColor_BaseBackground]; + Vec4F32 tx_color = colors[DF_ThemeColor_Text]; + Vec4F32 tw_color = colors[DF_ThemeColor_TextWeak]; + Vec4F32 bd_color = colors[DF_ThemeColor_BaseBorder]; + palette = ui_build_palette(ui_top_palette(), + .text = tx_color, + .text_weak = tw_color, + .border = bd_color, + .background = bg_color); + }break; + case DF_SettingsItemKind_ThemeColor: + { + rgba = df_rgba_from_theme_color(item->color); + }break; + case DF_SettingsItemKind_WindowSetting: {val_table = &ws->setting_vals[0];}goto setting; + case DF_SettingsItemKind_GlobalSetting:{}goto setting; + setting:; + { + s32_range = df_g_setting_code_s32_range_table[item->code]; + if(s32_range.min != 0 || s32_range.max != 1) + { + cursor = OS_Cursor_LeftRight; + is_slider = 1; + slider_s32_val = val_table[item->code].s32; + slider_pct = (F32)(slider_s32_val - s32_range.min) / dim_1s32(s32_range); + } + else + { + is_toggler = 1; + is_toggled = !!val_table[item->code].s32; + } + }break; + } + + //- rjf: build item widget + UI_Box *item_box = &ui_g_nil_box; + UI_Row + { + if(query.size == 0 && item->kind != DF_SettingsItemKind_CategoryHeader) + { + ui_set_next_flags(UI_BoxFlag_DrawSideLeft); + ui_spacer(ui_em(2.f, 1.f)); + } + UI_Focus(row_num+1 == sv->cursor.y ? UI_FocusKind_On : UI_FocusKind_Off) UI_Palette(palette) + { + ui_set_next_hover_cursor(cursor); + item_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|flags, "###option_%S_%S", item->kind_string, item->string); + UI_Parent(item_box) + { + if(item->icon_kind != DF_IconKind_Null) + { + UI_PrefWidth(ui_em(3.f, 1.f)) + DF_Font(ws, DF_FontSlot_Icons) + UI_Palette(ui_build_palette(ui_top_palette(), .text = rgba)) + UI_TextAlignment(UI_TextAlign_Center) + ui_label(df_g_icon_kind_text_table[item->icon_kind]); + } + if(query.size != 0 && item->kind_string.size != 0) UI_PrefWidth(ui_text_dim(10, 1)) + { + UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_DrawText|UI_BoxFlag_DrawTextWeak, "%S", item->kind_string); + ui_box_equip_fuzzy_match_ranges(box, &item->kind_string_matches); + } + UI_PrefWidth(ui_text_dim(10, 1)) + { + UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S", item->string); + ui_box_equip_fuzzy_match_ranges(box, &item->string_matches); + } + if(is_slider) UI_PrefWidth(ui_text_dim(10, 1)) + { + UI_Flags(UI_BoxFlag_DrawTextWeak) + ui_labelf("(%i)", slider_s32_val); + UI_PrefWidth(ui_pct(slider_pct, 1.f)) UI_HeightFill UI_FixedX(0) UI_FixedY(0) + UI_Palette(ui_build_palette(ui_top_palette(), .background = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlay))) + ui_build_box_from_key(UI_BoxFlag_DrawBackground, ui_key_zero()); + } + if(is_toggler) + { + ui_spacer(ui_pct(1, 0)); + UI_PrefWidth(ui_em(2.5f, 1.f)) + DF_Font(ws, DF_FontSlot_Icons) + UI_Flags(UI_BoxFlag_DrawTextWeak) + ui_label(df_g_icon_kind_text_table[is_toggled ? DF_IconKind_CheckFilled : DF_IconKind_CheckHollow]); + } + if(item->kind == DF_SettingsItemKind_ThemePreset && sv->preset_apply_confirm == item->preset) + { + ui_spacer(ui_pct(1, 0)); + UI_PrefWidth(ui_text_dim(10, 1)) + DF_Palette(ws, DF_PaletteCode_NegativePopButton) + UI_CornerRadius(ui_top_font_size()*0.5f) + UI_FontSize(ui_top_font_size()*0.9f) + ui_build_box_from_stringf(UI_BoxFlag_DrawText|UI_BoxFlag_DrawBackground, "Click Again To Apply"); + } + } + } + } + + //- rjf: interact + UI_Signal sig = ui_signal_from_box(item_box); + if(item->kind == DF_SettingsItemKind_ThemeColor && ui_clicked(sig)) + { + Vec3F32 rgb = v3f32(rgba.x, rgba.y, rgba.z); + Vec3F32 hsv = hsv_from_rgb(rgb); + Vec4F32 hsva = v4f32(hsv.x, hsv.y, hsv.z, rgba.w); + ui_ctx_menu_open(color_ctx_menu_keys[item->color], item_box->key, v2f32(0, dim_2f32(item_box->rect).y)); + sv->color_ctx_menu_color = item->color; + sv->color_ctx_menu_color_hsva = v4f32(hsv.x, hsv.y, hsv.z, rgba.w); + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + if((item->kind == DF_SettingsItemKind_GlobalSetting || item->kind == DF_SettingsItemKind_WindowSetting) && + is_toggler && ui_clicked(sig)) + { + val_table[item->code].s32 ^= 1; + val_table[item->code].set = 1; + } + if((item->kind == DF_SettingsItemKind_GlobalSetting || item->kind == DF_SettingsItemKind_WindowSetting) && + is_slider && ui_dragging(sig)) + { + if(ui_pressed(sig)) + { + ui_store_drag_struct(&slider_s32_val); + } + S32 pre_drag_val = *ui_get_drag_struct(S32); + Vec2F32 delta = ui_drag_delta(); + S32 pst_drag_val = pre_drag_val + (S32)(delta.x/(ui_top_font_size()*2.f)); + pst_drag_val = clamp_1s32(s32_range, pst_drag_val); + val_table[item->code].s32 = pst_drag_val; + val_table[item->code].set = 1; + } + if(item->kind == DF_SettingsItemKind_ThemePreset && ui_clicked(sig)) + { + if(sv->preset_apply_confirm == item->preset) + { + Vec4F32 *colors = df_g_theme_preset_colors_table[item->preset]; + MemoryCopy(df_gfx_state->cfg_theme_target.colors, colors, sizeof(df_gfx_state->cfg_theme_target.colors)); + sv->preset_apply_confirm = DF_ThemePreset_COUNT; + } + else + { + sv->preset_apply_confirm = item->preset; + } + } + if(item->kind != DF_SettingsItemKind_ThemePreset && ui_pressed(sig)) + { + sv->preset_apply_confirm = DF_ThemePreset_COUNT; + } + if(item->kind != DF_SettingsItemKind_ThemePreset && ui_pressed(sig)) + { + sv->preset_apply_confirm = DF_ThemePreset_COUNT; + } + if(item->kind == DF_SettingsItemKind_CategoryHeader && ui_pressed(sig)) + { + sv->category_opened[item->category] ^= 1; + } + } + } + + scratch_end(scratch); + ProfEnd(); + + //~ TODO(rjf): OLD vvvvvvvvvvvvvvvvvvvvvvvvvv +#if 0 + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + F32 row_height_px = floor_f32(ui_top_font_size()*2.5f); + + //- rjf: get state + typedef struct DF_ThemeViewState DF_ThemeViewState; + struct DF_ThemeViewState + { + Vec2S64 cursor; + TxtPt txt_cursor; + TxtPt txt_mark; + U8 txt_buffer[1024]; + U64 txt_size; + DF_ThemeColor color_ctx_menu_color; + Vec4F32 color_ctx_menu_color_hsva; + }; + DF_ThemeViewState *sv = df_view_user_state(view, DF_ThemeViewState); + + //- rjf: build preset ctx menu + UI_Key preset_ctx_menu_key = ui_key_from_stringf(ui_key_zero(), "%p_preset_ctx_menu", view); + DF_Palette(ws, DF_PaletteCode_Floating) UI_CtxMenu(preset_ctx_menu_key) UI_PrefWidth(ui_em(30.f, 1.f)) + { + for(DF_ThemePreset preset = (DF_ThemePreset)0; + preset < DF_ThemePreset_COUNT; + preset = (DF_ThemePreset)(preset+1)) + { + Vec4F32 *colors = df_g_theme_preset_colors_table[preset]; + Vec4F32 bg_color = colors[DF_ThemeColor_BaseBackground]; + Vec4F32 tx_color = colors[DF_ThemeColor_Text]; + Vec4F32 bd_color = colors[DF_ThemeColor_BaseBorder]; + ui_set_next_palette(ui_build_palette(ui_top_palette(), .text = tx_color, + .border = bd_color, + .background = bg_color)); + if(ui_clicked(ui_buttonf("%S", df_g_theme_preset_display_string_table[preset]))) + { + MemoryCopy(df_gfx_state->cfg_theme_target.colors, colors, sizeof(df_gfx_state->cfg_theme_target.colors)); + } + } + } + + //- rjf: produce per-color context menu keys + UI_Key *color_ctx_menu_keys = push_array(scratch.arena, UI_Key, DF_ThemeColor_COUNT); + { + for(DF_ThemeColor color = (DF_ThemeColor)(DF_ThemeColor_Null+1); + color < DF_ThemeColor_COUNT; + color = (DF_ThemeColor)(color+1)) + { + color_ctx_menu_keys[color] = ui_key_from_stringf(ui_key_zero(), "###settings_color_ctx_menu_%I64x", (U64)color); + } + } + + //- rjf: do color context menus + for(DF_ThemeColor color = (DF_ThemeColor)(DF_ThemeColor_Null+1); + color < DF_ThemeColor_COUNT; + color = (DF_ThemeColor)(color+1)) + { + DF_Palette(ws, DF_PaletteCode_Floating) + UI_CtxMenu(color_ctx_menu_keys[color]) + UI_Padding(ui_em(1.5f, 1.f)) + UI_PrefWidth(ui_em(28.5f, 1)) UI_PrefHeight(ui_children_sum(1.f)) + { + // rjf: build title + UI_Row + { + ui_spacer(ui_em(1.5f, 1.f)); + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(df_g_theme_color_display_string_table[color]); + } + + ui_spacer(ui_em(1.5f, 1.f)); + + // rjf: build picker + { + ui_set_next_pref_height(ui_em(22.f, 1.f)); + UI_Row UI_Padding(ui_pct(1, 0)) + { + UI_PrefWidth(ui_em(22.f, 1.f)) UI_PrefHeight(ui_em(22.f, 1.f)) UI_Flags(UI_BoxFlag_FocusNavSkip) + { + ui_sat_val_pickerf(sv->color_ctx_menu_color_hsva.x, &sv->color_ctx_menu_color_hsva.y, &sv->color_ctx_menu_color_hsva.z, "###settings_satval_picker"); + } + + ui_spacer(ui_em(0.75f, 1.f)); + + UI_PrefWidth(ui_em(1.5f, 1.f)) UI_PrefHeight(ui_em(22.f, 1.f)) UI_Flags(UI_BoxFlag_FocusNavSkip) + ui_hue_pickerf(&sv->color_ctx_menu_color_hsva.x, sv->color_ctx_menu_color_hsva.y, sv->color_ctx_menu_color_hsva.z, "###settings_hue_picker"); + + UI_PrefWidth(ui_em(1.5f, 1.f)) UI_PrefHeight(ui_em(22.f, 1.f)) UI_Flags(UI_BoxFlag_FocusNavSkip) + ui_alpha_pickerf(&sv->color_ctx_menu_color_hsva.w, "###settings_alpha_picker"); + } + } + + ui_spacer(ui_em(1.5f, 1.f)); + + // rjf: build line edits + UI_Row + UI_WidthFill + UI_Padding(ui_em(1.5f, 1.f)) + UI_PrefHeight(ui_children_sum(1.f)) + UI_Column + UI_PrefHeight(ui_em(2.25f, 1.f)) + { + Vec4F32 hsva = sv->color_ctx_menu_color_hsva; + Vec3F32 hsv = v3f32(hsva.x, hsva.y, hsva.z); + Vec3F32 rgb = rgb_from_hsv(hsv); + Vec4F32 rgba = v4f32(rgb.x, rgb.y, rgb.z, sv->color_ctx_menu_color_hsva.w); + String8 hex_string = hex_string_from_rgba_4f32(scratch.arena, rgba); + hex_string = push_str8f(scratch.arena, "#%S", hex_string); + String8 r_string = push_str8f(scratch.arena, "%.2f", rgba.x); + String8 g_string = push_str8f(scratch.arena, "%.2f", rgba.y); + String8 b_string = push_str8f(scratch.arena, "%.2f", rgba.z); + String8 h_string = push_str8f(scratch.arena, "%.2f", hsva.x); + String8 s_string = push_str8f(scratch.arena, "%.2f", hsva.y); + String8 v_string = push_str8f(scratch.arena, "%.2f", hsva.z); + String8 a_string = push_str8f(scratch.arena, "%.2f", rgba.w); + UI_Row DF_Font(ws, DF_FontSlot_Code) + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("Hex"); + UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, hex_string, "###hex_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_rgba = rgba_from_hex_string_4f32(string); + Vec4F32 new_hsva = hsva_from_rgba(new_rgba); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + ui_spacer(ui_em(0.75f, 1.f)); + UI_Row + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("R"); + UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, r_string, "###r_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_rgba = v4f32((F32)f64_from_str8(string), rgba.y, rgba.z, rgba.w); + Vec4F32 new_hsva = hsva_from_rgba(new_rgba); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + UI_Row + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("G"); + UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, g_string, "###g_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_rgba = v4f32(rgba.x, (F32)f64_from_str8(string), rgba.z, rgba.w); + Vec4F32 new_hsva = hsva_from_rgba(new_rgba); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + UI_Row + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("B"); + UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, b_string, "###b_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_rgba = v4f32(rgba.x, rgba.y, (F32)f64_from_str8(string), rgba.w); + Vec4F32 new_hsva = hsva_from_rgba(new_rgba); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + ui_spacer(ui_em(0.75f, 1.f)); + UI_Row + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("H"); + UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, h_string, "###h_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_hsva = v4f32((F32)f64_from_str8(string), hsva.y, hsva.z, hsva.w); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + UI_Row + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("S"); + UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, s_string, "###s_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_hsva = v4f32(hsva.x, (F32)f64_from_str8(string), hsva.z, hsva.w); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + UI_Row + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("V"); + UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, v_string, "###v_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_hsva = v4f32(hsva.x, hsva.y, (F32)f64_from_str8(string), hsva.w); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + ui_spacer(ui_em(0.75f, 1.f)); + UI_Row + { + UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("A"); + UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, a_string, "###a_edit"); + if(ui_committed(sig)) + { + String8 string = str8(sv->txt_buffer, sv->txt_size); + Vec4F32 new_hsva = v4f32(hsva.x, hsva.y, hsva.z, (F32)f64_from_str8(string)); + sv->color_ctx_menu_color_hsva = new_hsva; + } + } + } + + // rjf: commit state to theme + Vec4F32 hsva = sv->color_ctx_menu_color_hsva; + Vec3F32 hsv = v3f32(hsva.x, hsva.y, hsva.z); + Vec3F32 rgb = rgb_from_hsv(hsv); + Vec4F32 rgba = v4f32(rgb.x, rgb.y, rgb.z, sv->color_ctx_menu_color_hsva.w); + df_gfx_state->cfg_theme_target.colors[sv->color_ctx_menu_color] = rgba; + } + } + + //- rjf: build non-scrolled header + UI_PrefHeight(ui_px(row_height_px, 1.f)) UI_Row + { + // rjf: preset selector + UI_FocusHot((sv->cursor.y == 1 && sv->cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off) + { + UI_Signal preset_sig = df_icon_buttonf(ws, DF_IconKind_Palette, 0, "Apply Preset"); + if(ui_clicked(preset_sig)) + { + ui_ctx_menu_open(preset_ctx_menu_key, preset_sig.box->key, v2f32(0, dim_2f32(preset_sig.box->rect).y)); + } + } + + // rjf: load-from-file + UI_FocusHot((sv->cursor.y == 1 && sv->cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off) + { + if(ui_clicked(df_icon_buttonf(DF_IconKind_FileOutline, 0, "Load From File"))) + { + DF_CmdParams params = df_cmd_params_from_view(ws, panel, view); + params.cmd_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_PickFile); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand)); + } + } + } + + //- rjf: build palette table + Rng1S64 visible_row_range = {0}; + UI_ScrollListParams scroll_list_params = {0}; + { + Vec2F32 rect_dim = dim_2f32(rect); + scroll_list_params.flags = UI_ScrollListFlag_All; + scroll_list_params.row_height_px = row_height_px; + scroll_list_params.dim_px = v2f32(rect_dim.x, rect_dim.y-row_height_px); + scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(1, DF_ThemeColor_COUNT)); + scroll_list_params.item_range = r1s64(0, DF_ThemeColor_COUNT-1); + scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1; + } + UI_ScrollListSignal scroll_list_sig = {0}; + UI_Focus(UI_FocusKind_On) + UI_ScrollList(&scroll_list_params, + &view->scroll_pos.y, + &sv->cursor, + 0, + &visible_row_range, + &scroll_list_sig) + UI_Focus(UI_FocusKind_Null) + { + for(S64 row = visible_row_range.min; row <= visible_row_range.max; row += 1) + { + DF_ThemeColor color = (DF_ThemeColor)(row+1); + if(DF_ThemeColor_Null < color && color < DF_ThemeColor_COUNT) + UI_FocusHot(sv->cursor.y == row+2 ? UI_FocusKind_On : UI_FocusKind_Off) + { + Vec4F32 rgba = df_rgba_from_theme_color(color); + Vec3F32 rgb = v3f32(rgba.x, rgba.y, rgba.z); + Vec3F32 hsv = hsv_from_rgb(rgb); + Vec4F32 hsva = v4f32(hsv.x, hsv.y, hsv.z, rgba.w); + ui_set_next_pref_width(ui_pct(1, 0)); + ui_set_next_hover_cursor(OS_Cursor_HandPoint); + UI_Box *color_row = ui_build_box_from_stringf(UI_BoxFlag_DrawBorder| + UI_BoxFlag_DrawBackground| + UI_BoxFlag_DrawHotEffects| + UI_BoxFlag_DrawActiveEffects| + UI_BoxFlag_Clickable, + "###color_%I64x", (U64)color); + UI_Parent(color_row) + { + Vec4F32 bg_color = ui_top_palette()->background; + Vec4F32 default_text_color = ui_top_palette()->text; + F32 default_fallback_factor = clamp_1f32(r1f32(0.3f, 1), dot_4f32(normalize_4f32(rgba), normalize_4f32(bg_color))) - 0.3f; + Vec4F32 text_rgba = mix_4f32(rgba, default_text_color, default_fallback_factor); + UI_WidthFill UI_Palette(ui_build_palette(ui_top_palette(), .text = text_rgba)) ui_label(df_g_theme_color_display_string_table[color]); + ui_set_next_pref_width(ui_top_pref_height()); + UI_HeightFill UI_Column UI_Padding(ui_em(0.3f, 1)) + { + ui_set_next_palette(ui_build_palette(ui_top_palette(), .background = rgba)); + ui_set_next_corner_radius_00(ui_top_font_size()/4.f); + ui_set_next_corner_radius_01(ui_top_font_size()/4.f); + ui_set_next_corner_radius_10(ui_top_font_size()/4.f); + ui_set_next_corner_radius_11(ui_top_font_size()/4.f); + UI_Box *color_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawBackground, "###color_box"); + UI_Signal color_sig = ui_signal_from_box(color_box); + if(ui_hovering(color_sig)) + { + ui_do_color_tooltip_hsva(hsva); + } + } + ui_spacer(ui_em(0.3f, 1)); + } + UI_Signal color_row_sig = ui_signal_from_box(color_row); + if(ui_clicked(color_row_sig) || ui_right_clicked(color_row_sig)) + { + ui_ctx_menu_open(color_ctx_menu_keys[color], color_row->key, v2f32(0, color_row->rect.y1-color_row->rect.y0)); + sv->color_ctx_menu_color = color; + sv->color_ctx_menu_color_hsva = v4f32(hsv.x, hsv.y, hsv.z, rgba.w); + DF_CmdParams p = df_cmd_params_from_panel(ws, panel); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_FocusPanel)); + } + if(ui_hovering(color_row_sig)) UI_Tooltip + { + ui_label(df_g_theme_color_display_string_table[color]); + } + } + } + } + + scratch_end(scratch); + ProfEnd(); +#endif +} diff --git a/src/draw/draw.c b/src/draw/draw.c index 3768bc88..914f6465 100644 --- a/src/draw/draw.c +++ b/src/draw/draw.c @@ -1,641 +1,641 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Generated Code - -#define D_StackPushImpl(name_upper, name_lower, type, val) \ -D_Bucket *bucket = d_top_bucket();\ -type old_val = bucket->top_##name_lower->v;\ -D_##name_upper##Node *node = push_array(d_thread_ctx->arena, D_##name_upper##Node, 1);\ -node->v = (val);\ -SLLStackPush(bucket->top_##name_lower, node);\ -bucket->stack_gen += 1;\ -return old_val - -#define D_StackPopImpl(name_upper, name_lower, type) \ -D_Bucket *bucket = d_top_bucket();\ -type popped_val = bucket->top_##name_lower->v;\ -SLLStackPop(bucket->top_##name_lower);\ -bucket->stack_gen += 1;\ -return popped_val - -#define D_StackTopImpl(name_upper, name_lower, type) \ -D_Bucket *bucket = d_top_bucket();\ -type top_val = bucket->top_##name_lower->v;\ -return top_val - -#include "generated/draw.meta.c" - -//////////////////////////////// -//~ rjf: Basic Helpers - -internal U64 -d_hash_from_string(String8 string) -{ - U64 result = 5381; - for(U64 i = 0; i < string.size; i += 1) - { - result = ((result << 5) + result) + string.str[i]; - } - return result; -} - -//////////////////////////////// -//~ rjf: Fancy String Type Functions - -internal void -d_fancy_string_list_push(Arena *arena, D_FancyStringList *list, D_FancyString *str) -{ - D_FancyStringNode *n = push_array_no_zero(arena, D_FancyStringNode, 1); - MemoryCopyStruct(&n->v, str); - SLLQueuePush(list->first, list->last, n); - list->node_count += 1; - list->total_size += str->string.size; -} - -internal void -d_fancy_string_list_concat_in_place(D_FancyStringList *dst, D_FancyStringList *to_push) -{ - if(dst->last != 0 && to_push->first != 0) - { - dst->last->next = to_push->first; - dst->last = to_push->last; - dst->node_count += to_push->node_count; - dst->total_size += to_push->total_size; - } - else if(to_push->first != 0) - { - MemoryCopyStruct(dst, to_push); - } - MemoryZeroStruct(to_push); -} - -internal String8 -d_string_from_fancy_string_list(Arena *arena, D_FancyStringList *list) -{ - String8 result = {0}; - result.size = list->total_size; - result.str = push_array_no_zero(arena, U8, result.size); - U64 idx = 0; - for(D_FancyStringNode *n = list->first; n != 0; n = n->next) - { - MemoryCopy(result.str+idx, n->v.string.str, n->v.string.size); - idx += n->v.string.size; - } - return result; -} - -internal D_FancyRunList -d_fancy_run_list_from_fancy_string_list(Arena *arena, F32 tab_size_px, F_RasterFlags flags, D_FancyStringList *strs) -{ - ProfBeginFunction(); - D_FancyRunList run_list = {0}; - F32 base_align_px = 0; - for(D_FancyStringNode *n = strs->first; n != 0; n = n->next) - { - D_FancyRunNode *dst_n = push_array(arena, D_FancyRunNode, 1); - dst_n->v.run = f_push_run_from_string(arena, n->v.font, n->v.size, base_align_px, tab_size_px, flags, n->v.string); - dst_n->v.color = n->v.color; - dst_n->v.underline_thickness = n->v.underline_thickness; - dst_n->v.strikethrough_thickness = n->v.strikethrough_thickness; - SLLQueuePush(run_list.first, run_list.last, dst_n); - run_list.node_count += 1; - run_list.dim.x += dst_n->v.run.dim.x; - run_list.dim.y = Max(run_list.dim.y, dst_n->v.run.dim.y); - base_align_px += dst_n->v.run.dim.x; - } - ProfEnd(); - return run_list; -} - -internal D_FancyRunList -d_fancy_run_list_copy(Arena *arena, D_FancyRunList *src) -{ - D_FancyRunList dst = {0}; - for(D_FancyRunNode *src_n = src->first; src_n != 0; src_n = src_n->next) - { - D_FancyRunNode *dst_n = push_array(arena, D_FancyRunNode, 1); - SLLQueuePush(dst.first, dst.last, dst_n); - MemoryCopyStruct(&dst_n->v, &src_n->v); - dst_n->v.run.pieces = f_piece_array_copy(arena, &src_n->v.run.pieces); - dst.node_count += 1; - } - dst.dim = src->dim; - return dst; -} - -//////////////////////////////// -//~ rjf: Top-Level API -// -// (Frame boundaries) - -internal void -d_begin_frame(void) -{ - if(d_thread_ctx == 0) - { - Arena *arena = arena_alloc__sized(GB(64), MB(8)); - d_thread_ctx = push_array(arena, D_ThreadCtx, 1); - d_thread_ctx->arena = arena; - d_thread_ctx->arena_frame_start_pos = arena_pos(arena); - } - arena_pop_to(d_thread_ctx->arena, d_thread_ctx->arena_frame_start_pos); - d_thread_ctx->free_bucket_selection = 0; - d_thread_ctx->top_bucket = 0; -} - -internal void -d_submit_bucket(OS_Handle os_window, R_Handle r_window, D_Bucket *bucket) -{ - r_window_submit(os_window, r_window, &bucket->passes); -} - -//////////////////////////////// -//~ rjf: Bucket Construction & Selection API -// -// (Bucket: Handle to sequence of many render passes, constructed by this layer) - -internal D_Bucket * -d_bucket_make(void) -{ - D_Bucket *bucket = push_array(d_thread_ctx->arena, D_Bucket, 1); - D_BucketStackInits(bucket); - return bucket; -} - -internal void -d_push_bucket(D_Bucket *bucket) -{ - D_BucketSelectionNode *node = d_thread_ctx->free_bucket_selection; - if(node) - { - SLLStackPop(d_thread_ctx->free_bucket_selection); - } - else - { - node = push_array(d_thread_ctx->arena, D_BucketSelectionNode, 1); - } - SLLStackPush(d_thread_ctx->top_bucket, node); - node->bucket = bucket; -} - -internal void -d_pop_bucket(void) -{ - D_BucketSelectionNode *node = d_thread_ctx->top_bucket; - SLLStackPop(d_thread_ctx->top_bucket); - SLLStackPush(d_thread_ctx->free_bucket_selection, node); -} - -internal D_Bucket * -d_top_bucket(void) -{ - D_Bucket *bucket = 0; - if(d_thread_ctx->top_bucket != 0) - { - bucket = d_thread_ctx->top_bucket->bucket; - } - return bucket; -} - -//////////////////////////////// -//~ rjf: Bucket Stacks -// -// (Pushing/popping implicit draw parameters) - -// NOTE(rjf): (The implementation of the push/pop/top functions is auto-generated) - -//////////////////////////////// -//~ rjf: Draw Calls -// -// (Apply to the calling thread's currently selected bucket) - -//- rjf: rectangles - -internal inline R_Rect2DInst * -d_rect(Rng2F32 dst, Vec4F32 color, F32 corner_radius, F32 border_thickness, F32 edge_softness) -{ - Arena *arena = d_thread_ctx->arena; - D_Bucket *bucket = d_top_bucket(); - R_Pass *pass = r_pass_from_kind(arena, &bucket->passes, R_PassKind_UI); - R_PassParams_UI *params = pass->params_ui; - R_BatchGroup2DList *rects = ¶ms->rects; - R_BatchGroup2DNode *node = rects->last; - if(node == 0 || bucket->stack_gen != bucket->last_cmd_stack_gen) - { - node = push_array(arena, R_BatchGroup2DNode, 1); - SLLQueuePush(rects->first, rects->last, node); - rects->count += 1; - node->batches = r_batch_list_make(sizeof(R_Rect2DInst)); - node->params.tex = r_handle_zero(); - node->params.tex_sample_kind = bucket->top_tex2d_sample_kind->v; - node->params.xform = bucket->top_xform2d->v; - node->params.clip = bucket->top_clip->v; - node->params.transparency = bucket->top_transparency->v; - } - R_Rect2DInst *inst = (R_Rect2DInst *)r_batch_list_push_inst(arena, &node->batches, 256); - inst->dst = dst; - inst->src = r2f32p(0, 0, 0, 0); - inst->colors[Corner_00] = color; - inst->colors[Corner_01] = color; - inst->colors[Corner_10] = color; - inst->colors[Corner_11] = color; - inst->corner_radii[Corner_00] = corner_radius; - inst->corner_radii[Corner_01] = corner_radius; - inst->corner_radii[Corner_10] = corner_radius; - inst->corner_radii[Corner_11] = corner_radius; - inst->border_thickness = border_thickness; - inst->edge_softness = edge_softness; - inst->white_texture_override = 1.f; - bucket->last_cmd_stack_gen = bucket->stack_gen; - return inst; -} - -//- rjf: images - -internal inline R_Rect2DInst * -d_img(Rng2F32 dst, Rng2F32 src, R_Handle texture, Vec4F32 color, F32 corner_radius, F32 border_thickness, F32 edge_softness) -{ - Arena *arena = d_thread_ctx->arena; - D_Bucket *bucket = d_top_bucket(); - R_Pass *pass = r_pass_from_kind(arena, &bucket->passes, R_PassKind_UI); - R_PassParams_UI *params = pass->params_ui; - R_BatchGroup2DList *rects = ¶ms->rects; - R_BatchGroup2DNode *node = rects->last; - if(node != 0 && bucket->stack_gen == bucket->last_cmd_stack_gen && r_handle_match(node->params.tex, r_handle_zero())) - { - node->params.tex = texture; - } - else if(node == 0 || bucket->stack_gen != bucket->last_cmd_stack_gen || !r_handle_match(texture, node->params.tex)) - { - node = push_array(arena, R_BatchGroup2DNode, 1); - SLLQueuePush(rects->first, rects->last, node); - rects->count += 1; - node->batches = r_batch_list_make(sizeof(R_Rect2DInst)); - node->params.tex = texture; - node->params.tex_sample_kind = bucket->top_tex2d_sample_kind->v; - node->params.xform = bucket->top_xform2d->v; - node->params.clip = bucket->top_clip->v; - node->params.transparency = bucket->top_transparency->v; - } - R_Rect2DInst *inst = (R_Rect2DInst *)r_batch_list_push_inst(arena, &node->batches, 256); - inst->dst = dst; - inst->src = src; - inst->colors[Corner_00] = color; - inst->colors[Corner_01] = color; - inst->colors[Corner_10] = color; - inst->colors[Corner_11] = color; - inst->corner_radii[Corner_00] = corner_radius; - inst->corner_radii[Corner_01] = corner_radius; - inst->corner_radii[Corner_10] = corner_radius; - inst->corner_radii[Corner_11] = corner_radius; - inst->border_thickness = border_thickness; - inst->edge_softness = edge_softness; - inst->white_texture_override = 0.f; - bucket->last_cmd_stack_gen = bucket->stack_gen; - return inst; -} - -//- rjf: blurs - -internal R_PassParams_Blur * -d_blur(Rng2F32 rect, F32 blur_size, F32 corner_radius) -{ - Arena *arena = d_thread_ctx->arena; - D_Bucket *bucket = d_top_bucket(); - R_Pass *pass = r_pass_from_kind(arena, &bucket->passes, R_PassKind_Blur); - R_PassParams_Blur *params = pass->params_blur; - params->rect = rect; - params->clip = d_top_clip(); - params->blur_size = blur_size; - params->corner_radii[Corner_00] = corner_radius; - params->corner_radii[Corner_01] = corner_radius; - params->corner_radii[Corner_10] = corner_radius; - params->corner_radii[Corner_11] = corner_radius; - return params; -} - -//- rjf: 3d rendering pass params - -internal R_PassParams_Geo3D * -d_geo3d_begin(Rng2F32 viewport, Mat4x4F32 view, Mat4x4F32 projection) -{ - Arena *arena = d_thread_ctx->arena; - D_Bucket *bucket = d_top_bucket(); - R_Pass *pass = r_pass_from_kind(arena, &bucket->passes, R_PassKind_Geo3D); - R_PassParams_Geo3D *params = pass->params_geo3d; - params->viewport = viewport; - params->view = view; - params->projection = projection; - return params; -} - -//- rjf: meshes - -internal R_Mesh3DInst * -d_mesh(R_Handle mesh_vertices, R_Handle mesh_indices, R_GeoTopologyKind mesh_geo_topology, R_GeoVertexFlags mesh_geo_vertex_flags, R_Handle albedo_tex, Mat4x4F32 inst_xform) -{ - Arena *arena = d_thread_ctx->arena; - D_Bucket *bucket = d_top_bucket(); - R_Pass *pass = r_pass_from_kind(arena, &bucket->passes, R_PassKind_Geo3D); - R_PassParams_Geo3D *params = pass->params_geo3d; - - // rjf: mesh batch map not made yet -> make - if(params->mesh_batches.slots_count == 0) - { - params->mesh_batches.slots_count = 64; - params->mesh_batches.slots = push_array(arena, R_BatchGroup3DMapNode *, params->mesh_batches.slots_count); - } - - // rjf: hash batch group 3d params - U64 hash = 0; - U64 slot_idx = 0; - { - U64 buffer[] = - { - mesh_vertices.u64[0], - mesh_vertices.u64[1], - mesh_indices.u64[0], - mesh_indices.u64[1], - (U64)mesh_geo_topology, - (U64)mesh_geo_vertex_flags, - albedo_tex.u64[0], - albedo_tex.u64[1], - (U64)d_top_tex2d_sample_kind(), - }; - hash = d_hash_from_string(str8((U8 *)buffer, sizeof(buffer))); - slot_idx = hash%params->mesh_batches.slots_count; - } - - // rjf: map hash -> existing batch group node - R_BatchGroup3DMapNode *node = 0; - { - for(R_BatchGroup3DMapNode *n = params->mesh_batches.slots[slot_idx]; n != 0; n = n->next) - { - if(n->hash == hash) - { - node = n; - break; - } - } - } - - // rjf: no batch group node? -> make one - if(node == 0) - { - node = push_array(arena, R_BatchGroup3DMapNode, 1); - SLLStackPush(params->mesh_batches.slots[slot_idx], node); - node->hash = hash; - node->batches = r_batch_list_make(sizeof(R_Mesh3DInst)); - node->params.mesh_vertices = mesh_vertices; - node->params.mesh_indices = mesh_indices; - node->params.mesh_geo_topology = mesh_geo_topology; - node->params.mesh_geo_vertex_flags = mesh_geo_vertex_flags; - node->params.albedo_tex = albedo_tex; - node->params.albedo_tex_sample_kind = d_top_tex2d_sample_kind(); - node->params.xform = mat_4x4f32(1.f); - } - - // rjf: push new instance to batch group - R_Mesh3DInst *inst = (R_Mesh3DInst *)r_batch_list_push_inst(arena, &node->batches, 256); - inst->xform = inst_xform; - return inst; -} - -//- rjf: collating one pre-prepped bucket into parent bucket - -internal void -d_sub_bucket(D_Bucket *bucket) -{ - Arena *arena = d_thread_ctx->arena; - D_Bucket *src = bucket; - D_Bucket *dst = d_top_bucket(); - Rng2F32 dst_clip = d_top_clip(); - B32 dst_clip_is_set = !(dst_clip.x0 == 0 && dst_clip.x1 == 0 && - dst_clip.y0 == 0 && dst_clip.y1 == 0); - for(R_PassNode *n = src->passes.first; n != 0; n = n->next) - { - R_Pass *src_pass = &n->v; - R_Pass *dst_pass = r_pass_from_kind(arena, &dst->passes, src_pass->kind); - switch(dst_pass->kind) - { - default:{dst_pass->params = src_pass->params;}break; - case R_PassKind_UI: - { - R_PassParams_UI *src_ui = src_pass->params_ui; - R_PassParams_UI *dst_ui = dst_pass->params_ui; - for(R_BatchGroup2DNode *src_group_n = src_ui->rects.first; - src_group_n != 0; - src_group_n = src_group_n->next) - { - R_BatchGroup2DNode *dst_group_n = push_array(arena, R_BatchGroup2DNode, 1); - SLLQueuePush(dst_ui->rects.first, dst_ui->rects.last, dst_group_n); - dst_ui->rects.count += 1; - MemoryCopyStruct(&dst_group_n->params, &src_group_n->params); - dst_group_n->batches = src_group_n->batches; - dst_group_n->params.xform = d_top_xform2d(); - if(dst_clip_is_set) - { - B32 clip_is_set = !(dst_group_n->params.clip.x0 == 0 && - dst_group_n->params.clip.y0 == 0 && - dst_group_n->params.clip.x1 == 0 && - dst_group_n->params.clip.y1 == 0); - dst_group_n->params.clip = clip_is_set ? intersect_2f32(dst_clip, dst_group_n->params.clip) : dst_clip; - } - } - }break; - } - } -} - -//////////////////////////////// -//~ rjf: Draw Call Helpers - -//- rjf: text - -internal void -d_truncated_fancy_run_list(Vec2F32 p, D_FancyRunList *list, F32 max_x, F_Run trailer_run) -{ - ProfBeginFunction(); - - //- rjf: total advance > max? -> enable trailer - B32 trailer_enabled = (list->dim.x > max_x && trailer_run.dim.x < max_x); - - //- rjf: draw runs - F32 advance = 0; - B32 trailer_found = 0; - Vec4F32 last_color = {0}; - U64 byte_off = 0; - for(D_FancyRunNode *n = list->first; n != 0; n = n->next) - { - D_FancyRun *fr = &n->v; - Rng1F32 pixel_range = {0}; - { - pixel_range.min = 100000; - pixel_range.max = 0; - } - F_Piece *piece_first = fr->run.pieces.v; - F_Piece *piece_opl = piece_first + fr->run.pieces.count; - F32 pre_advance = advance; - last_color = fr->color; - for(F_Piece *piece = piece_first; - piece < piece_opl; - piece += 1) - { - if(trailer_enabled && advance + piece->advance > (max_x - trailer_run.dim.x)) - { - trailer_found = 1; - break; - } - if(!trailer_enabled && advance + piece->advance > max_x) - { - goto end_draw; - } - R_Handle texture = piece->texture; - Rng2F32 src = r2f32p((F32)piece->subrect.x0, (F32)piece->subrect.y0, (F32)piece->subrect.x1, (F32)piece->subrect.y1); - Vec2F32 size = dim_2f32(src); - Rng2F32 dst = r2f32p(p.x + piece->offset.x + advance, - p.y + piece->offset.y, - p.x + piece->offset.x + advance + size.x, - p.y + piece->offset.y + size.y); - if(!r_handle_match(texture, r_handle_zero())) - { - d_img(dst, src, texture, fr->color, 0, 0, 0); - //d_rect(dst, v4f32(0, 1, 0, 0.5f), 0, 1.f, 0.f); - } - advance += piece->advance; - pixel_range.min = Min(pre_advance, pixel_range.min); - pixel_range.max = Max(advance, pixel_range.max); - } - if(fr->underline_thickness > 0) - { - d_rect(r2f32p(p.x + pixel_range.min, - p.y+fr->run.descent+fr->run.descent/8, - p.x + pixel_range.max, - p.y+fr->run.descent+fr->run.descent/8+fr->underline_thickness), - fr->color, 0, 0, 0.8f); - } - if(fr->strikethrough_thickness > 0) - { - d_rect(r2f32p(p.x+pre_advance, p.y+fr->run.descent - fr->run.ascent/2, p.x+advance, p.y+fr->run.descent - fr->run.ascent/2 + fr->strikethrough_thickness), fr->color, 0, 0, 1.f); - } - if(trailer_found) - { - break; - } - } - end_draw:; - - //- rjf: draw trailer - if(trailer_found) - { - F_Piece *piece_first = trailer_run.pieces.v; - F_Piece *piece_opl = piece_first + trailer_run.pieces.count; - F32 pre_advance = advance; - Vec4F32 trailer_piece_color = last_color; - for(F_Piece *piece = piece_first; - piece < piece_opl; - piece += 1) - { - R_Handle texture = piece->texture; - Rng2F32 src = r2f32p((F32)piece->subrect.x0, (F32)piece->subrect.y0, (F32)piece->subrect.x1, (F32)piece->subrect.y1); - Vec2F32 size = dim_2f32(src); - Rng2F32 dst = r2f32p(p.x + piece->offset.x + advance, - p.y + piece->offset.y, - p.x + piece->offset.x + advance + size.x, - p.y + piece->offset.y + size.y); - if(!r_handle_match(texture, r_handle_zero())) - { - d_img(dst, src, texture, trailer_piece_color, 0, 0, 0); - trailer_piece_color.w *= 0.5f; - } - advance += piece->advance; - } - } - - ProfEnd(); -} - -internal void -d_truncated_fancy_run_fuzzy_matches(Vec2F32 p, D_FancyRunList *list, F32 max_x, FuzzyMatchRangeList *ranges, Vec4F32 color) -{ - for(FuzzyMatchRangeNode *match_n = ranges->first; match_n != 0; match_n = match_n->next) - { - Rng1U64 byte_range = match_n->range; - Rng1F32 pixel_range = {0}; - { - pixel_range.min = 100000; - pixel_range.max = 0; - } - F32 last_piece_end_pad = 0; - U64 byte_off = 0; - F32 advance = 0; - F32 ascent = 0; - F32 descent = 0; - for(D_FancyRunNode *fr_n = list->first; fr_n != 0; fr_n = fr_n->next) - { - D_FancyRun *fr = &fr_n->v; - F_Run *run = &fr->run; - ascent = run->ascent; - descent = run->descent; - for(U64 piece_idx = 0; piece_idx < run->pieces.count; piece_idx += 1) - { - F_Piece *piece = &run->pieces.v[piece_idx]; - if(contains_1u64(byte_range, byte_off)) - { - F32 pre_advance = advance + piece->offset.x; - F32 post_advance = advance + piece->advance; - pixel_range.min = Min(pre_advance, pixel_range.min); - pixel_range.max = Max(post_advance, pixel_range.max); - } - byte_off += piece->decode_size; - advance += piece->advance; - } - } - if(pixel_range.min < pixel_range.max) - { - Rng2F32 rect = r2f32p(p.x + pixel_range.min - ascent/4.f, - p.y - descent - ascent - ascent/8.f, - p.x + pixel_range.max + ascent/4.f, - p.y - descent - ascent + ascent/8.f + list->dim.y); - rect.x0 = Min(rect.x0, p.x+max_x); - rect.x1 = Min(rect.x1, p.x+max_x); - d_rect(rect, color, (descent+ascent)/4.f, 0, 1.f); - } - } -} - -internal void -d_text_run(Vec2F32 p, Vec4F32 color, F_Run run) -{ - F32 advance = 0; - F_Piece *piece_first = run.pieces.v; - F_Piece *piece_opl = piece_first + run.pieces.count; - for(F_Piece *piece = piece_first; - piece < piece_opl; - piece += 1) - { - R_Handle texture = piece->texture; - Rng2F32 src = r2f32p((F32)piece->subrect.x0, (F32)piece->subrect.y0, (F32)piece->subrect.x1, (F32)piece->subrect.y1); - Vec2F32 size = dim_2f32(src); - Rng2F32 dst = r2f32p(p.x + piece->offset.x + advance, - p.y + piece->offset.y, - p.x + piece->offset.x + advance + size.x, - p.y + piece->offset.y + size.y); - if(size.x != 0 && size.y != 0 && !r_handle_match(texture, r_handle_zero())) - { - d_img(dst, src, texture, color, 0, 0, 0); - } - advance += piece->advance; - } -} - -internal void -d_text(F_Tag font, F32 size, F32 base_align_px, F32 tab_size_px, F_RasterFlags flags, Vec2F32 p, Vec4F32 color, String8 string) -{ - Temp scratch = scratch_begin(0, 0); - F_Run run = f_push_run_from_string(scratch.arena, font, size, base_align_px, tab_size_px, flags, string); - d_text_run(p, color, run); - scratch_end(scratch); -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Generated Code + +#define D_StackPushImpl(name_upper, name_lower, type, val) \ +D_Bucket *bucket = d_top_bucket();\ +type old_val = bucket->top_##name_lower->v;\ +D_##name_upper##Node *node = push_array(d_thread_ctx->arena, D_##name_upper##Node, 1);\ +node->v = (val);\ +SLLStackPush(bucket->top_##name_lower, node);\ +bucket->stack_gen += 1;\ +return old_val + +#define D_StackPopImpl(name_upper, name_lower, type) \ +D_Bucket *bucket = d_top_bucket();\ +type popped_val = bucket->top_##name_lower->v;\ +SLLStackPop(bucket->top_##name_lower);\ +bucket->stack_gen += 1;\ +return popped_val + +#define D_StackTopImpl(name_upper, name_lower, type) \ +D_Bucket *bucket = d_top_bucket();\ +type top_val = bucket->top_##name_lower->v;\ +return top_val + +#include "generated/draw.meta.c" + +//////////////////////////////// +//~ rjf: Basic Helpers + +internal U64 +d_hash_from_string(String8 string) +{ + U64 result = 5381; + for(U64 i = 0; i < string.size; i += 1) + { + result = ((result << 5) + result) + string.str[i]; + } + return result; +} + +//////////////////////////////// +//~ rjf: Fancy String Type Functions + +internal void +d_fancy_string_list_push(Arena *arena, D_FancyStringList *list, D_FancyString *str) +{ + D_FancyStringNode *n = push_array_no_zero(arena, D_FancyStringNode, 1); + MemoryCopyStruct(&n->v, str); + SLLQueuePush(list->first, list->last, n); + list->node_count += 1; + list->total_size += str->string.size; +} + +internal void +d_fancy_string_list_concat_in_place(D_FancyStringList *dst, D_FancyStringList *to_push) +{ + if(dst->last != 0 && to_push->first != 0) + { + dst->last->next = to_push->first; + dst->last = to_push->last; + dst->node_count += to_push->node_count; + dst->total_size += to_push->total_size; + } + else if(to_push->first != 0) + { + MemoryCopyStruct(dst, to_push); + } + MemoryZeroStruct(to_push); +} + +internal String8 +d_string_from_fancy_string_list(Arena *arena, D_FancyStringList *list) +{ + String8 result = {0}; + result.size = list->total_size; + result.str = push_array_no_zero(arena, U8, result.size); + U64 idx = 0; + for(D_FancyStringNode *n = list->first; n != 0; n = n->next) + { + MemoryCopy(result.str+idx, n->v.string.str, n->v.string.size); + idx += n->v.string.size; + } + return result; +} + +internal D_FancyRunList +d_fancy_run_list_from_fancy_string_list(Arena *arena, F32 tab_size_px, F_RasterFlags flags, D_FancyStringList *strs) +{ + ProfBeginFunction(); + D_FancyRunList run_list = {0}; + F32 base_align_px = 0; + for(D_FancyStringNode *n = strs->first; n != 0; n = n->next) + { + D_FancyRunNode *dst_n = push_array(arena, D_FancyRunNode, 1); + dst_n->v.run = f_push_run_from_string(arena, n->v.font, n->v.size, base_align_px, tab_size_px, flags, n->v.string); + dst_n->v.color = n->v.color; + dst_n->v.underline_thickness = n->v.underline_thickness; + dst_n->v.strikethrough_thickness = n->v.strikethrough_thickness; + SLLQueuePush(run_list.first, run_list.last, dst_n); + run_list.node_count += 1; + run_list.dim.x += dst_n->v.run.dim.x; + run_list.dim.y = Max(run_list.dim.y, dst_n->v.run.dim.y); + base_align_px += dst_n->v.run.dim.x; + } + ProfEnd(); + return run_list; +} + +internal D_FancyRunList +d_fancy_run_list_copy(Arena *arena, D_FancyRunList *src) +{ + D_FancyRunList dst = {0}; + for(D_FancyRunNode *src_n = src->first; src_n != 0; src_n = src_n->next) + { + D_FancyRunNode *dst_n = push_array(arena, D_FancyRunNode, 1); + SLLQueuePush(dst.first, dst.last, dst_n); + MemoryCopyStruct(&dst_n->v, &src_n->v); + dst_n->v.run.pieces = f_piece_array_copy(arena, &src_n->v.run.pieces); + dst.node_count += 1; + } + dst.dim = src->dim; + return dst; +} + +//////////////////////////////// +//~ rjf: Top-Level API +// +// (Frame boundaries) + +internal void +d_begin_frame(void) +{ + if(d_thread_ctx == 0) + { + Arena *arena = arena_alloc(.reserve_size = GB(64), .commit_size = MB(8)); + d_thread_ctx = push_array(arena, D_ThreadCtx, 1); + d_thread_ctx->arena = arena; + d_thread_ctx->arena_frame_start_pos = arena_pos(arena); + } + arena_pop_to(d_thread_ctx->arena, d_thread_ctx->arena_frame_start_pos); + d_thread_ctx->free_bucket_selection = 0; + d_thread_ctx->top_bucket = 0; +} + +internal void +d_submit_bucket(OS_Handle os_window, R_Handle r_window, D_Bucket *bucket) +{ + r_window_submit(os_window, r_window, &bucket->passes); +} + +//////////////////////////////// +//~ rjf: Bucket Construction & Selection API +// +// (Bucket: Handle to sequence of many render passes, constructed by this layer) + +internal D_Bucket * +d_bucket_make(void) +{ + D_Bucket *bucket = push_array(d_thread_ctx->arena, D_Bucket, 1); + D_BucketStackInits(bucket); + return bucket; +} + +internal void +d_push_bucket(D_Bucket *bucket) +{ + D_BucketSelectionNode *node = d_thread_ctx->free_bucket_selection; + if(node) + { + SLLStackPop(d_thread_ctx->free_bucket_selection); + } + else + { + node = push_array(d_thread_ctx->arena, D_BucketSelectionNode, 1); + } + SLLStackPush(d_thread_ctx->top_bucket, node); + node->bucket = bucket; +} + +internal void +d_pop_bucket(void) +{ + D_BucketSelectionNode *node = d_thread_ctx->top_bucket; + SLLStackPop(d_thread_ctx->top_bucket); + SLLStackPush(d_thread_ctx->free_bucket_selection, node); +} + +internal D_Bucket * +d_top_bucket(void) +{ + D_Bucket *bucket = 0; + if(d_thread_ctx->top_bucket != 0) + { + bucket = d_thread_ctx->top_bucket->bucket; + } + return bucket; +} + +//////////////////////////////// +//~ rjf: Bucket Stacks +// +// (Pushing/popping implicit draw parameters) + +// NOTE(rjf): (The implementation of the push/pop/top functions is auto-generated) + +//////////////////////////////// +//~ rjf: Draw Calls +// +// (Apply to the calling thread's currently selected bucket) + +//- rjf: rectangles + +internal inline R_Rect2DInst * +d_rect(Rng2F32 dst, Vec4F32 color, F32 corner_radius, F32 border_thickness, F32 edge_softness) +{ + Arena *arena = d_thread_ctx->arena; + D_Bucket *bucket = d_top_bucket(); + R_Pass *pass = r_pass_from_kind(arena, &bucket->passes, R_PassKind_UI); + R_PassParams_UI *params = pass->params_ui; + R_BatchGroup2DList *rects = ¶ms->rects; + R_BatchGroup2DNode *node = rects->last; + if(node == 0 || bucket->stack_gen != bucket->last_cmd_stack_gen) + { + node = push_array(arena, R_BatchGroup2DNode, 1); + SLLQueuePush(rects->first, rects->last, node); + rects->count += 1; + node->batches = r_batch_list_make(sizeof(R_Rect2DInst)); + node->params.tex = r_handle_zero(); + node->params.tex_sample_kind = bucket->top_tex2d_sample_kind->v; + node->params.xform = bucket->top_xform2d->v; + node->params.clip = bucket->top_clip->v; + node->params.transparency = bucket->top_transparency->v; + } + R_Rect2DInst *inst = (R_Rect2DInst *)r_batch_list_push_inst(arena, &node->batches, 256); + inst->dst = dst; + inst->src = r2f32p(0, 0, 0, 0); + inst->colors[Corner_00] = color; + inst->colors[Corner_01] = color; + inst->colors[Corner_10] = color; + inst->colors[Corner_11] = color; + inst->corner_radii[Corner_00] = corner_radius; + inst->corner_radii[Corner_01] = corner_radius; + inst->corner_radii[Corner_10] = corner_radius; + inst->corner_radii[Corner_11] = corner_radius; + inst->border_thickness = border_thickness; + inst->edge_softness = edge_softness; + inst->white_texture_override = 1.f; + bucket->last_cmd_stack_gen = bucket->stack_gen; + return inst; +} + +//- rjf: images + +internal inline R_Rect2DInst * +d_img(Rng2F32 dst, Rng2F32 src, R_Handle texture, Vec4F32 color, F32 corner_radius, F32 border_thickness, F32 edge_softness) +{ + Arena *arena = d_thread_ctx->arena; + D_Bucket *bucket = d_top_bucket(); + R_Pass *pass = r_pass_from_kind(arena, &bucket->passes, R_PassKind_UI); + R_PassParams_UI *params = pass->params_ui; + R_BatchGroup2DList *rects = ¶ms->rects; + R_BatchGroup2DNode *node = rects->last; + if(node != 0 && bucket->stack_gen == bucket->last_cmd_stack_gen && r_handle_match(node->params.tex, r_handle_zero())) + { + node->params.tex = texture; + } + else if(node == 0 || bucket->stack_gen != bucket->last_cmd_stack_gen || !r_handle_match(texture, node->params.tex)) + { + node = push_array(arena, R_BatchGroup2DNode, 1); + SLLQueuePush(rects->first, rects->last, node); + rects->count += 1; + node->batches = r_batch_list_make(sizeof(R_Rect2DInst)); + node->params.tex = texture; + node->params.tex_sample_kind = bucket->top_tex2d_sample_kind->v; + node->params.xform = bucket->top_xform2d->v; + node->params.clip = bucket->top_clip->v; + node->params.transparency = bucket->top_transparency->v; + } + R_Rect2DInst *inst = (R_Rect2DInst *)r_batch_list_push_inst(arena, &node->batches, 256); + inst->dst = dst; + inst->src = src; + inst->colors[Corner_00] = color; + inst->colors[Corner_01] = color; + inst->colors[Corner_10] = color; + inst->colors[Corner_11] = color; + inst->corner_radii[Corner_00] = corner_radius; + inst->corner_radii[Corner_01] = corner_radius; + inst->corner_radii[Corner_10] = corner_radius; + inst->corner_radii[Corner_11] = corner_radius; + inst->border_thickness = border_thickness; + inst->edge_softness = edge_softness; + inst->white_texture_override = 0.f; + bucket->last_cmd_stack_gen = bucket->stack_gen; + return inst; +} + +//- rjf: blurs + +internal R_PassParams_Blur * +d_blur(Rng2F32 rect, F32 blur_size, F32 corner_radius) +{ + Arena *arena = d_thread_ctx->arena; + D_Bucket *bucket = d_top_bucket(); + R_Pass *pass = r_pass_from_kind(arena, &bucket->passes, R_PassKind_Blur); + R_PassParams_Blur *params = pass->params_blur; + params->rect = rect; + params->clip = d_top_clip(); + params->blur_size = blur_size; + params->corner_radii[Corner_00] = corner_radius; + params->corner_radii[Corner_01] = corner_radius; + params->corner_radii[Corner_10] = corner_radius; + params->corner_radii[Corner_11] = corner_radius; + return params; +} + +//- rjf: 3d rendering pass params + +internal R_PassParams_Geo3D * +d_geo3d_begin(Rng2F32 viewport, Mat4x4F32 view, Mat4x4F32 projection) +{ + Arena *arena = d_thread_ctx->arena; + D_Bucket *bucket = d_top_bucket(); + R_Pass *pass = r_pass_from_kind(arena, &bucket->passes, R_PassKind_Geo3D); + R_PassParams_Geo3D *params = pass->params_geo3d; + params->viewport = viewport; + params->view = view; + params->projection = projection; + return params; +} + +//- rjf: meshes + +internal R_Mesh3DInst * +d_mesh(R_Handle mesh_vertices, R_Handle mesh_indices, R_GeoTopologyKind mesh_geo_topology, R_GeoVertexFlags mesh_geo_vertex_flags, R_Handle albedo_tex, Mat4x4F32 inst_xform) +{ + Arena *arena = d_thread_ctx->arena; + D_Bucket *bucket = d_top_bucket(); + R_Pass *pass = r_pass_from_kind(arena, &bucket->passes, R_PassKind_Geo3D); + R_PassParams_Geo3D *params = pass->params_geo3d; + + // rjf: mesh batch map not made yet -> make + if(params->mesh_batches.slots_count == 0) + { + params->mesh_batches.slots_count = 64; + params->mesh_batches.slots = push_array(arena, R_BatchGroup3DMapNode *, params->mesh_batches.slots_count); + } + + // rjf: hash batch group 3d params + U64 hash = 0; + U64 slot_idx = 0; + { + U64 buffer[] = + { + mesh_vertices.u64[0], + mesh_vertices.u64[1], + mesh_indices.u64[0], + mesh_indices.u64[1], + (U64)mesh_geo_topology, + (U64)mesh_geo_vertex_flags, + albedo_tex.u64[0], + albedo_tex.u64[1], + (U64)d_top_tex2d_sample_kind(), + }; + hash = d_hash_from_string(str8((U8 *)buffer, sizeof(buffer))); + slot_idx = hash%params->mesh_batches.slots_count; + } + + // rjf: map hash -> existing batch group node + R_BatchGroup3DMapNode *node = 0; + { + for(R_BatchGroup3DMapNode *n = params->mesh_batches.slots[slot_idx]; n != 0; n = n->next) + { + if(n->hash == hash) + { + node = n; + break; + } + } + } + + // rjf: no batch group node? -> make one + if(node == 0) + { + node = push_array(arena, R_BatchGroup3DMapNode, 1); + SLLStackPush(params->mesh_batches.slots[slot_idx], node); + node->hash = hash; + node->batches = r_batch_list_make(sizeof(R_Mesh3DInst)); + node->params.mesh_vertices = mesh_vertices; + node->params.mesh_indices = mesh_indices; + node->params.mesh_geo_topology = mesh_geo_topology; + node->params.mesh_geo_vertex_flags = mesh_geo_vertex_flags; + node->params.albedo_tex = albedo_tex; + node->params.albedo_tex_sample_kind = d_top_tex2d_sample_kind(); + node->params.xform = mat_4x4f32(1.f); + } + + // rjf: push new instance to batch group + R_Mesh3DInst *inst = (R_Mesh3DInst *)r_batch_list_push_inst(arena, &node->batches, 256); + inst->xform = inst_xform; + return inst; +} + +//- rjf: collating one pre-prepped bucket into parent bucket + +internal void +d_sub_bucket(D_Bucket *bucket) +{ + Arena *arena = d_thread_ctx->arena; + D_Bucket *src = bucket; + D_Bucket *dst = d_top_bucket(); + Rng2F32 dst_clip = d_top_clip(); + B32 dst_clip_is_set = !(dst_clip.x0 == 0 && dst_clip.x1 == 0 && + dst_clip.y0 == 0 && dst_clip.y1 == 0); + for(R_PassNode *n = src->passes.first; n != 0; n = n->next) + { + R_Pass *src_pass = &n->v; + R_Pass *dst_pass = r_pass_from_kind(arena, &dst->passes, src_pass->kind); + switch(dst_pass->kind) + { + default:{dst_pass->params = src_pass->params;}break; + case R_PassKind_UI: + { + R_PassParams_UI *src_ui = src_pass->params_ui; + R_PassParams_UI *dst_ui = dst_pass->params_ui; + for(R_BatchGroup2DNode *src_group_n = src_ui->rects.first; + src_group_n != 0; + src_group_n = src_group_n->next) + { + R_BatchGroup2DNode *dst_group_n = push_array(arena, R_BatchGroup2DNode, 1); + SLLQueuePush(dst_ui->rects.first, dst_ui->rects.last, dst_group_n); + dst_ui->rects.count += 1; + MemoryCopyStruct(&dst_group_n->params, &src_group_n->params); + dst_group_n->batches = src_group_n->batches; + dst_group_n->params.xform = d_top_xform2d(); + if(dst_clip_is_set) + { + B32 clip_is_set = !(dst_group_n->params.clip.x0 == 0 && + dst_group_n->params.clip.y0 == 0 && + dst_group_n->params.clip.x1 == 0 && + dst_group_n->params.clip.y1 == 0); + dst_group_n->params.clip = clip_is_set ? intersect_2f32(dst_clip, dst_group_n->params.clip) : dst_clip; + } + } + }break; + } + } +} + +//////////////////////////////// +//~ rjf: Draw Call Helpers + +//- rjf: text + +internal void +d_truncated_fancy_run_list(Vec2F32 p, D_FancyRunList *list, F32 max_x, F_Run trailer_run) +{ + ProfBeginFunction(); + + //- rjf: total advance > max? -> enable trailer + B32 trailer_enabled = (list->dim.x > max_x && trailer_run.dim.x < max_x); + + //- rjf: draw runs + F32 advance = 0; + B32 trailer_found = 0; + Vec4F32 last_color = {0}; + U64 byte_off = 0; + for(D_FancyRunNode *n = list->first; n != 0; n = n->next) + { + D_FancyRun *fr = &n->v; + Rng1F32 pixel_range = {0}; + { + pixel_range.min = 100000; + pixel_range.max = 0; + } + F_Piece *piece_first = fr->run.pieces.v; + F_Piece *piece_opl = piece_first + fr->run.pieces.count; + F32 pre_advance = advance; + last_color = fr->color; + for(F_Piece *piece = piece_first; + piece < piece_opl; + piece += 1) + { + if(trailer_enabled && advance + piece->advance > (max_x - trailer_run.dim.x)) + { + trailer_found = 1; + break; + } + if(!trailer_enabled && advance + piece->advance > max_x) + { + goto end_draw; + } + R_Handle texture = piece->texture; + Rng2F32 src = r2f32p((F32)piece->subrect.x0, (F32)piece->subrect.y0, (F32)piece->subrect.x1, (F32)piece->subrect.y1); + Vec2F32 size = dim_2f32(src); + Rng2F32 dst = r2f32p(p.x + piece->offset.x + advance, + p.y + piece->offset.y, + p.x + piece->offset.x + advance + size.x, + p.y + piece->offset.y + size.y); + if(!r_handle_match(texture, r_handle_zero())) + { + d_img(dst, src, texture, fr->color, 0, 0, 0); + //d_rect(dst, v4f32(0, 1, 0, 0.5f), 0, 1.f, 0.f); + } + advance += piece->advance; + pixel_range.min = Min(pre_advance, pixel_range.min); + pixel_range.max = Max(advance, pixel_range.max); + } + if(fr->underline_thickness > 0) + { + d_rect(r2f32p(p.x + pixel_range.min, + p.y+fr->run.descent+fr->run.descent/8, + p.x + pixel_range.max, + p.y+fr->run.descent+fr->run.descent/8+fr->underline_thickness), + fr->color, 0, 0, 0.8f); + } + if(fr->strikethrough_thickness > 0) + { + d_rect(r2f32p(p.x+pre_advance, p.y+fr->run.descent - fr->run.ascent/2, p.x+advance, p.y+fr->run.descent - fr->run.ascent/2 + fr->strikethrough_thickness), fr->color, 0, 0, 1.f); + } + if(trailer_found) + { + break; + } + } + end_draw:; + + //- rjf: draw trailer + if(trailer_found) + { + F_Piece *piece_first = trailer_run.pieces.v; + F_Piece *piece_opl = piece_first + trailer_run.pieces.count; + F32 pre_advance = advance; + Vec4F32 trailer_piece_color = last_color; + for(F_Piece *piece = piece_first; + piece < piece_opl; + piece += 1) + { + R_Handle texture = piece->texture; + Rng2F32 src = r2f32p((F32)piece->subrect.x0, (F32)piece->subrect.y0, (F32)piece->subrect.x1, (F32)piece->subrect.y1); + Vec2F32 size = dim_2f32(src); + Rng2F32 dst = r2f32p(p.x + piece->offset.x + advance, + p.y + piece->offset.y, + p.x + piece->offset.x + advance + size.x, + p.y + piece->offset.y + size.y); + if(!r_handle_match(texture, r_handle_zero())) + { + d_img(dst, src, texture, trailer_piece_color, 0, 0, 0); + trailer_piece_color.w *= 0.5f; + } + advance += piece->advance; + } + } + + ProfEnd(); +} + +internal void +d_truncated_fancy_run_fuzzy_matches(Vec2F32 p, D_FancyRunList *list, F32 max_x, FuzzyMatchRangeList *ranges, Vec4F32 color) +{ + for(FuzzyMatchRangeNode *match_n = ranges->first; match_n != 0; match_n = match_n->next) + { + Rng1U64 byte_range = match_n->range; + Rng1F32 pixel_range = {0}; + { + pixel_range.min = 100000; + pixel_range.max = 0; + } + F32 last_piece_end_pad = 0; + U64 byte_off = 0; + F32 advance = 0; + F32 ascent = 0; + F32 descent = 0; + for(D_FancyRunNode *fr_n = list->first; fr_n != 0; fr_n = fr_n->next) + { + D_FancyRun *fr = &fr_n->v; + F_Run *run = &fr->run; + ascent = run->ascent; + descent = run->descent; + for(U64 piece_idx = 0; piece_idx < run->pieces.count; piece_idx += 1) + { + F_Piece *piece = &run->pieces.v[piece_idx]; + if(contains_1u64(byte_range, byte_off)) + { + F32 pre_advance = advance + piece->offset.x; + F32 post_advance = advance + piece->advance; + pixel_range.min = Min(pre_advance, pixel_range.min); + pixel_range.max = Max(post_advance, pixel_range.max); + } + byte_off += piece->decode_size; + advance += piece->advance; + } + } + if(pixel_range.min < pixel_range.max) + { + Rng2F32 rect = r2f32p(p.x + pixel_range.min - ascent/4.f, + p.y - descent - ascent - ascent/8.f, + p.x + pixel_range.max + ascent/4.f, + p.y - descent - ascent + ascent/8.f + list->dim.y); + rect.x0 = Min(rect.x0, p.x+max_x); + rect.x1 = Min(rect.x1, p.x+max_x); + d_rect(rect, color, (descent+ascent)/4.f, 0, 1.f); + } + } +} + +internal void +d_text_run(Vec2F32 p, Vec4F32 color, F_Run run) +{ + F32 advance = 0; + F_Piece *piece_first = run.pieces.v; + F_Piece *piece_opl = piece_first + run.pieces.count; + for(F_Piece *piece = piece_first; + piece < piece_opl; + piece += 1) + { + R_Handle texture = piece->texture; + Rng2F32 src = r2f32p((F32)piece->subrect.x0, (F32)piece->subrect.y0, (F32)piece->subrect.x1, (F32)piece->subrect.y1); + Vec2F32 size = dim_2f32(src); + Rng2F32 dst = r2f32p(p.x + piece->offset.x + advance, + p.y + piece->offset.y, + p.x + piece->offset.x + advance + size.x, + p.y + piece->offset.y + size.y); + if(size.x != 0 && size.y != 0 && !r_handle_match(texture, r_handle_zero())) + { + d_img(dst, src, texture, color, 0, 0, 0); + } + advance += piece->advance; + } +} + +internal void +d_text(F_Tag font, F32 size, F32 base_align_px, F32 tab_size_px, F_RasterFlags flags, Vec2F32 p, Vec4F32 color, String8 string) +{ + Temp scratch = scratch_begin(0, 0); + F_Run run = f_push_run_from_string(scratch.arena, font, size, base_align_px, tab_size_px, flags, string); + d_text_run(p, color, run); + scratch_end(scratch); +} diff --git a/src/file_stream/file_stream.c b/src/file_stream/file_stream.c index 990ff726..69913f9f 100644 --- a/src/file_stream/file_stream.c +++ b/src/file_stream/file_stream.c @@ -1,310 +1,310 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Top-Level API - -internal void -fs_init(void) -{ - Arena *arena = arena_alloc(); - fs_shared = push_array(arena, FS_Shared, 1); - fs_shared->arena = arena; - fs_shared->change_gen = 1; - fs_shared->slots_count = 1024; - fs_shared->stripes_count = os_logical_core_count(); - fs_shared->slots = push_array(arena, FS_Slot, fs_shared->slots_count); - fs_shared->stripes = push_array(arena, FS_Stripe, fs_shared->stripes_count); - for(U64 idx = 0; idx < fs_shared->stripes_count; idx += 1) - { - fs_shared->stripes[idx].arena = arena_alloc(); - fs_shared->stripes[idx].cv = os_condition_variable_alloc(); - fs_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); - } - fs_shared->u2s_ring_size = KB(64); - fs_shared->u2s_ring_base = push_array_no_zero(arena, U8, fs_shared->u2s_ring_size); - fs_shared->u2s_ring_cv = os_condition_variable_alloc(); - fs_shared->u2s_ring_mutex = os_mutex_alloc(); - fs_shared->streamer_count = Clamp(1, os_logical_core_count()-1, 4); - fs_shared->streamers = push_array(arena, OS_Handle, 1); - for(U64 idx = 0; idx < fs_shared->streamer_count; idx += 1) - { - fs_shared->streamers[idx] = os_launch_thread(fs_streamer_thread__entry_point, (void *)idx, 0); - } - fs_shared->detector_thread = os_launch_thread(fs_detector_thread__entry_point, 0, 0); -} - -//////////////////////////////// -//~ rjf: Change Generation - -internal U64 -fs_change_gen(void) -{ - return ins_atomic_u64_eval(&fs_shared->change_gen); -} - -//////////////////////////////// -//~ rjf: Cache Interaction - -internal U128 -fs_hash_from_path(String8 path, U64 endt_us) -{ - Temp scratch = scratch_begin(0, 0); - U128 result = {0}; - path = path_normalized_from_string(scratch.arena, path); - U128 path_key = hs_hash_from_data(path); - for(U64 rewind_idx = 0; rewind_idx < 2; rewind_idx += 1) - { - result = hs_hash_from_key(path_key, rewind_idx); - if(!u128_match(result, u128_zero())) - { - break; - } - else if(u128_match(result, u128_zero()) && rewind_idx == 0) - { - U64 slot_idx = path_key.u64[0]%fs_shared->slots_count; - U64 stripe_idx = slot_idx%fs_shared->stripes_count; - FS_Slot *slot = &fs_shared->slots[slot_idx]; - FS_Stripe *stripe = &fs_shared->stripes[stripe_idx]; - OS_MutexScopeR(stripe->rw_mutex) for(;;) - { - FS_Node *node = 0; - for(FS_Node *n = slot->first; n != 0; n = n->next) - { - if(str8_match(path, n->path, 0)) - { - node = n; - break; - } - } - if(node == 0) OS_MutexScopeRWPromote(stripe->rw_mutex) - { - node = push_array(stripe->arena, FS_Node, 1); - SLLQueuePush(slot->first, slot->last, node); - node->path = push_str8_copy(stripe->arena, path); - } - if(!ins_atomic_u32_eval_cond_assign(&node->is_working, 1, 0) && - !fs_u2s_enqueue_path(path, endt_us)) - { - ins_atomic_u32_eval_assign(&node->is_working, 0); - } - result = hs_hash_from_key(path_key, 0); - if(u128_match(result, u128_zero()) && os_now_microseconds() <= endt_us) - { - os_condition_variable_wait_rw_r(stripe->cv, stripe->rw_mutex, endt_us); - } - else - { - break; - } - } - } - } - scratch_end(scratch); - return result; -} - -internal U128 -fs_key_from_path(String8 path) -{ - U128 key = hs_hash_from_data(path); - fs_hash_from_path(path, 0); - return key; -} - -internal U64 -fs_timestamp_from_path(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - U64 result = 0; - path = path_normalized_from_string(scratch.arena, path); - U128 path_key = hs_hash_from_data(path); - U64 slot_idx = path_key.u64[0]%fs_shared->slots_count; - U64 stripe_idx = slot_idx%fs_shared->stripes_count; - FS_Slot *slot = &fs_shared->slots[slot_idx]; - FS_Stripe *stripe = &fs_shared->stripes[stripe_idx]; - OS_MutexScopeR(stripe->rw_mutex) - { - for(FS_Node *n = slot->first; n != 0; n = n->next) - { - if(str8_match(path, n->path, 0)) - { - result = n->timestamp; - break; - } - } - } - scratch_end(scratch); - return result; -} - -//////////////////////////////// -//~ rjf: Streamer Threads - -internal B32 -fs_u2s_enqueue_path(String8 path, U64 endt_us) -{ - B32 result = 0; - path.size = Min(path.size, fs_shared->u2s_ring_size); - OS_MutexScope(fs_shared->u2s_ring_mutex) for(;;) - { - U64 unconsumed_size = fs_shared->u2s_ring_write_pos - fs_shared->u2s_ring_read_pos; - U64 available_size = fs_shared->u2s_ring_size - unconsumed_size; - if(available_size >= sizeof(U64) + path.size) - { - result = 1; - fs_shared->u2s_ring_write_pos += ring_write_struct(fs_shared->u2s_ring_base, fs_shared->u2s_ring_size, fs_shared->u2s_ring_write_pos, &path.size); - fs_shared->u2s_ring_write_pos += ring_write(fs_shared->u2s_ring_base, fs_shared->u2s_ring_size, fs_shared->u2s_ring_write_pos, path.str, path.size); - fs_shared->u2s_ring_write_pos += 7; - fs_shared->u2s_ring_write_pos -= fs_shared->u2s_ring_write_pos%8; - break; - } - os_condition_variable_wait(fs_shared->u2s_ring_cv, fs_shared->u2s_ring_mutex, endt_us); - } - if(result) - { - os_condition_variable_broadcast(fs_shared->u2s_ring_cv); - } - return result; -} - -internal String8 -fs_u2s_dequeue_path(Arena *arena) -{ - String8 path = {0}; - OS_MutexScope(fs_shared->u2s_ring_mutex) for(;;) - { - U64 unconsumed_size = fs_shared->u2s_ring_write_pos - fs_shared->u2s_ring_read_pos; - if(unconsumed_size >= sizeof(U64)) - { - fs_shared->u2s_ring_read_pos += ring_read_struct(fs_shared->u2s_ring_base, fs_shared->u2s_ring_size, fs_shared->u2s_ring_read_pos, &path.size); - path.str = push_array(arena, U8, path.size); - fs_shared->u2s_ring_read_pos += ring_read(fs_shared->u2s_ring_base, fs_shared->u2s_ring_size, fs_shared->u2s_ring_read_pos, path.str, path.size); - fs_shared->u2s_ring_read_pos += 7; - fs_shared->u2s_ring_read_pos -= fs_shared->u2s_ring_read_pos%8; - break; - } - os_condition_variable_wait(fs_shared->u2s_ring_cv, fs_shared->u2s_ring_mutex, max_U64); - } - os_condition_variable_broadcast(fs_shared->u2s_ring_cv); - return path; -} - -internal void -fs_streamer_thread__entry_point(void *p) -{ - ThreadNameF("[fs] streamer #%I64u", (U64)p); - for(;;) - { - Temp scratch = scratch_begin(0, 0); - - //- rjf: unpack path - String8 path = fs_u2s_dequeue_path(scratch.arena); - U128 key = hs_hash_from_data(path); - U64 slot_idx = key.u64[0]%fs_shared->slots_count; - U64 stripe_idx = slot_idx%fs_shared->stripes_count; - FS_Slot *slot = &fs_shared->slots[slot_idx]; - FS_Stripe *stripe = &fs_shared->stripes[stripe_idx]; - - //- rjf: load - ProfBegin("load \"%.*s\"", str8_varg(path)); - FileProperties pre_props = os_properties_from_file_path(path); - OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead|OS_AccessFlag_ShareWrite, path); - U64 data_arena_size = pre_props.size+ARENA_HEADER_SIZE; - data_arena_size += KB(4)-1; - data_arena_size -= data_arena_size%KB(4); - ProfBegin("allocate"); - Arena *data_arena = arena_alloc__sized(data_arena_size, data_arena_size); - ProfEnd(); - ProfBegin("read"); - String8 data = os_string_from_file_range(data_arena, file, r1u64(0, pre_props.size)); - ProfEnd(); - os_file_close(file); - FileProperties post_props = os_properties_from_file_path(path); - - //- rjf: abort if modification timestamps differ - we did not successfully read the file - if(pre_props.modified != post_props.modified) - { - ProfScope("abort") - { - arena_release(data_arena); - MemoryZeroStruct(&data); - data_arena = 0; - } - } - - //- rjf: submit - else - { - ProfScope("submit") - { - hs_submit_data(key, &data_arena, data); - } - } - - //- rjf: commit info to cache - ProfScope("commit to cache") OS_MutexScopeW(stripe->rw_mutex) - { - FS_Node *node = 0; - for(FS_Node *n = slot->first; n != 0; n = n->next) - { - if(str8_match(n->path, path, 0)) - { - node = n; - break; - } - } - if(node != 0) - { - if(node->timestamp != 0) - { - ins_atomic_u64_inc_eval(&fs_shared->change_gen); - } - if(post_props.modified == pre_props.modified) - { - node->timestamp = post_props.modified; - } - ins_atomic_u32_eval_assign(&node->is_working, 0); - } - } - os_condition_variable_broadcast(stripe->cv); - - ProfEnd(); - scratch_end(scratch); - } -} - -//////////////////////////////// -//~ rjf: Change Detector Thread - -internal void -fs_detector_thread__entry_point(void *p) -{ - ThreadNameF("[fs] detector"); - for(;;) - { - U64 slots_per_stripe = fs_shared->slots_count/fs_shared->stripes_count; - for(U64 stripe_idx = 0; stripe_idx < fs_shared->stripes_count; stripe_idx += 1) - { - FS_Stripe *stripe = &fs_shared->stripes[stripe_idx]; - OS_MutexScopeR(stripe->rw_mutex) for(U64 slot_in_stripe_idx = 0; slot_in_stripe_idx < slots_per_stripe; slot_in_stripe_idx += 1) - { - U64 slot_idx = stripe_idx*slots_per_stripe + slot_in_stripe_idx; - FS_Slot *slot = &fs_shared->slots[slot_idx]; - for(FS_Node *n = slot->first; n != 0; n = n->next) - { - FileProperties props = os_properties_from_file_path(n->path); - if(props.modified != n->timestamp) - { - if(!ins_atomic_u32_eval_cond_assign(&n->is_working, 1, 0) && - !fs_u2s_enqueue_path(n->path, os_now_microseconds()+100000)) - { - ins_atomic_u32_eval_assign(&n->is_working, 0); - } - } - } - } - } - os_sleep_milliseconds(100); - } -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Top-Level API + +internal void +fs_init(void) +{ + Arena *arena = arena_alloc(); + fs_shared = push_array(arena, FS_Shared, 1); + fs_shared->arena = arena; + fs_shared->change_gen = 1; + fs_shared->slots_count = 1024; + fs_shared->stripes_count = os_get_system_info()->logical_processor_count; + fs_shared->slots = push_array(arena, FS_Slot, fs_shared->slots_count); + fs_shared->stripes = push_array(arena, FS_Stripe, fs_shared->stripes_count); + for(U64 idx = 0; idx < fs_shared->stripes_count; idx += 1) + { + fs_shared->stripes[idx].arena = arena_alloc(); + fs_shared->stripes[idx].cv = os_condition_variable_alloc(); + fs_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); + } + fs_shared->u2s_ring_size = KB(64); + fs_shared->u2s_ring_base = push_array_no_zero(arena, U8, fs_shared->u2s_ring_size); + fs_shared->u2s_ring_cv = os_condition_variable_alloc(); + fs_shared->u2s_ring_mutex = os_mutex_alloc(); + fs_shared->streamer_count = Clamp(1, os_get_system_info()->logical_processor_count-1, 4); + fs_shared->streamers = push_array(arena, OS_Handle, 1); + for(U64 idx = 0; idx < fs_shared->streamer_count; idx += 1) + { + fs_shared->streamers[idx] = os_thread_launch(fs_streamer_thread__entry_point, (void *)idx, 0); + } + fs_shared->detector_thread = os_thread_launch(fs_detector_thread__entry_point, 0, 0); +} + +//////////////////////////////// +//~ rjf: Change Generation + +internal U64 +fs_change_gen(void) +{ + return ins_atomic_u64_eval(&fs_shared->change_gen); +} + +//////////////////////////////// +//~ rjf: Cache Interaction + +internal U128 +fs_hash_from_path(String8 path, U64 endt_us) +{ + Temp scratch = scratch_begin(0, 0); + U128 result = {0}; + path = path_normalized_from_string(scratch.arena, path); + U128 path_key = hs_hash_from_data(path); + for(U64 rewind_idx = 0; rewind_idx < 2; rewind_idx += 1) + { + result = hs_hash_from_key(path_key, rewind_idx); + if(!u128_match(result, u128_zero())) + { + break; + } + else if(u128_match(result, u128_zero()) && rewind_idx == 0) + { + U64 slot_idx = path_key.u64[0]%fs_shared->slots_count; + U64 stripe_idx = slot_idx%fs_shared->stripes_count; + FS_Slot *slot = &fs_shared->slots[slot_idx]; + FS_Stripe *stripe = &fs_shared->stripes[stripe_idx]; + OS_MutexScopeR(stripe->rw_mutex) for(;;) + { + FS_Node *node = 0; + for(FS_Node *n = slot->first; n != 0; n = n->next) + { + if(str8_match(path, n->path, 0)) + { + node = n; + break; + } + } + if(node == 0) OS_MutexScopeRWPromote(stripe->rw_mutex) + { + node = push_array(stripe->arena, FS_Node, 1); + SLLQueuePush(slot->first, slot->last, node); + node->path = push_str8_copy(stripe->arena, path); + } + if(!ins_atomic_u32_eval_cond_assign(&node->is_working, 1, 0) && + !fs_u2s_enqueue_path(path, endt_us)) + { + ins_atomic_u32_eval_assign(&node->is_working, 0); + } + result = hs_hash_from_key(path_key, 0); + if(u128_match(result, u128_zero()) && os_now_microseconds() <= endt_us) + { + os_condition_variable_wait_rw_r(stripe->cv, stripe->rw_mutex, endt_us); + } + else + { + break; + } + } + } + } + scratch_end(scratch); + return result; +} + +internal U128 +fs_key_from_path(String8 path) +{ + U128 key = hs_hash_from_data(path); + fs_hash_from_path(path, 0); + return key; +} + +internal U64 +fs_timestamp_from_path(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + U64 result = 0; + path = path_normalized_from_string(scratch.arena, path); + U128 path_key = hs_hash_from_data(path); + U64 slot_idx = path_key.u64[0]%fs_shared->slots_count; + U64 stripe_idx = slot_idx%fs_shared->stripes_count; + FS_Slot *slot = &fs_shared->slots[slot_idx]; + FS_Stripe *stripe = &fs_shared->stripes[stripe_idx]; + OS_MutexScopeR(stripe->rw_mutex) + { + for(FS_Node *n = slot->first; n != 0; n = n->next) + { + if(str8_match(path, n->path, 0)) + { + result = n->timestamp; + break; + } + } + } + scratch_end(scratch); + return result; +} + +//////////////////////////////// +//~ rjf: Streamer Threads + +internal B32 +fs_u2s_enqueue_path(String8 path, U64 endt_us) +{ + B32 result = 0; + path.size = Min(path.size, fs_shared->u2s_ring_size); + OS_MutexScope(fs_shared->u2s_ring_mutex) for(;;) + { + U64 unconsumed_size = fs_shared->u2s_ring_write_pos - fs_shared->u2s_ring_read_pos; + U64 available_size = fs_shared->u2s_ring_size - unconsumed_size; + if(available_size >= sizeof(U64) + path.size) + { + result = 1; + fs_shared->u2s_ring_write_pos += ring_write_struct(fs_shared->u2s_ring_base, fs_shared->u2s_ring_size, fs_shared->u2s_ring_write_pos, &path.size); + fs_shared->u2s_ring_write_pos += ring_write(fs_shared->u2s_ring_base, fs_shared->u2s_ring_size, fs_shared->u2s_ring_write_pos, path.str, path.size); + fs_shared->u2s_ring_write_pos += 7; + fs_shared->u2s_ring_write_pos -= fs_shared->u2s_ring_write_pos%8; + break; + } + os_condition_variable_wait(fs_shared->u2s_ring_cv, fs_shared->u2s_ring_mutex, endt_us); + } + if(result) + { + os_condition_variable_broadcast(fs_shared->u2s_ring_cv); + } + return result; +} + +internal String8 +fs_u2s_dequeue_path(Arena *arena) +{ + String8 path = {0}; + OS_MutexScope(fs_shared->u2s_ring_mutex) for(;;) + { + U64 unconsumed_size = fs_shared->u2s_ring_write_pos - fs_shared->u2s_ring_read_pos; + if(unconsumed_size >= sizeof(U64)) + { + fs_shared->u2s_ring_read_pos += ring_read_struct(fs_shared->u2s_ring_base, fs_shared->u2s_ring_size, fs_shared->u2s_ring_read_pos, &path.size); + path.str = push_array(arena, U8, path.size); + fs_shared->u2s_ring_read_pos += ring_read(fs_shared->u2s_ring_base, fs_shared->u2s_ring_size, fs_shared->u2s_ring_read_pos, path.str, path.size); + fs_shared->u2s_ring_read_pos += 7; + fs_shared->u2s_ring_read_pos -= fs_shared->u2s_ring_read_pos%8; + break; + } + os_condition_variable_wait(fs_shared->u2s_ring_cv, fs_shared->u2s_ring_mutex, max_U64); + } + os_condition_variable_broadcast(fs_shared->u2s_ring_cv); + return path; +} + +internal void +fs_streamer_thread__entry_point(void *p) +{ + ThreadNameF("[fs] streamer #%I64u", (U64)p); + for(;;) + { + Temp scratch = scratch_begin(0, 0); + + //- rjf: unpack path + String8 path = fs_u2s_dequeue_path(scratch.arena); + U128 key = hs_hash_from_data(path); + U64 slot_idx = key.u64[0]%fs_shared->slots_count; + U64 stripe_idx = slot_idx%fs_shared->stripes_count; + FS_Slot *slot = &fs_shared->slots[slot_idx]; + FS_Stripe *stripe = &fs_shared->stripes[stripe_idx]; + + //- rjf: load + ProfBegin("load \"%.*s\"", str8_varg(path)); + FileProperties pre_props = os_properties_from_file_path(path); + OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead|OS_AccessFlag_ShareWrite, path); + U64 data_arena_size = pre_props.size+ARENA_HEADER_SIZE; + data_arena_size += KB(4)-1; + data_arena_size -= data_arena_size%KB(4); + ProfBegin("allocate"); + Arena *data_arena = arena_alloc(.reserve_size = data_arena_size, .commit_size = data_arena_size); + ProfEnd(); + ProfBegin("read"); + String8 data = os_string_from_file_range(data_arena, file, r1u64(0, pre_props.size)); + ProfEnd(); + os_file_close(file); + FileProperties post_props = os_properties_from_file_path(path); + + //- rjf: abort if modification timestamps differ - we did not successfully read the file + if(pre_props.modified != post_props.modified) + { + ProfScope("abort") + { + arena_release(data_arena); + MemoryZeroStruct(&data); + data_arena = 0; + } + } + + //- rjf: submit + else + { + ProfScope("submit") + { + hs_submit_data(key, &data_arena, data); + } + } + + //- rjf: commit info to cache + ProfScope("commit to cache") OS_MutexScopeW(stripe->rw_mutex) + { + FS_Node *node = 0; + for(FS_Node *n = slot->first; n != 0; n = n->next) + { + if(str8_match(n->path, path, 0)) + { + node = n; + break; + } + } + if(node != 0) + { + if(node->timestamp != 0) + { + ins_atomic_u64_inc_eval(&fs_shared->change_gen); + } + if(post_props.modified == pre_props.modified) + { + node->timestamp = post_props.modified; + } + ins_atomic_u32_eval_assign(&node->is_working, 0); + } + } + os_condition_variable_broadcast(stripe->cv); + + ProfEnd(); + scratch_end(scratch); + } +} + +//////////////////////////////// +//~ rjf: Change Detector Thread + +internal void +fs_detector_thread__entry_point(void *p) +{ + ThreadNameF("[fs] detector"); + for(;;) + { + U64 slots_per_stripe = fs_shared->slots_count/fs_shared->stripes_count; + for(U64 stripe_idx = 0; stripe_idx < fs_shared->stripes_count; stripe_idx += 1) + { + FS_Stripe *stripe = &fs_shared->stripes[stripe_idx]; + OS_MutexScopeR(stripe->rw_mutex) for(U64 slot_in_stripe_idx = 0; slot_in_stripe_idx < slots_per_stripe; slot_in_stripe_idx += 1) + { + U64 slot_idx = stripe_idx*slots_per_stripe + slot_in_stripe_idx; + FS_Slot *slot = &fs_shared->slots[slot_idx]; + for(FS_Node *n = slot->first; n != 0; n = n->next) + { + FileProperties props = os_properties_from_file_path(n->path); + if(props.modified != n->timestamp) + { + if(!ins_atomic_u32_eval_cond_assign(&n->is_working, 1, 0) && + !fs_u2s_enqueue_path(n->path, os_now_microseconds()+100000)) + { + ins_atomic_u32_eval_assign(&n->is_working, 0); + } + } + } + } + } + os_sleep_milliseconds(100); + } +} diff --git a/src/fuzzy_search/fuzzy_search.c b/src/fuzzy_search/fuzzy_search.c index 0b6e5c1d..e5a88e57 100644 --- a/src/fuzzy_search/fuzzy_search.c +++ b/src/fuzzy_search/fuzzy_search.c @@ -1,553 +1,553 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Helpers - -internal U64 -fzy_hash_from_string(U64 seed, String8 string) -{ - U64 result = seed; - for(U64 i = 0; i < string.size; i += 1) - { - result = ((result << 5) + result) + string.str[i]; - } - return result; -} - -internal U64 -fzy_hash_from_params(FZY_Params *params) -{ - U64 hash = 5381; - hash = fzy_hash_from_string(hash, str8_struct(¶ms->target)); - for(U64 idx = 0; idx < params->dbgi_keys.count; idx += 1) - { - hash = fzy_hash_from_string(hash, str8_struct(¶ms->dbgi_keys.v[idx].min_timestamp)); - hash = fzy_hash_from_string(hash, params->dbgi_keys.v[idx].path); - } - return hash; -} - -internal U64 -fzy_item_num_from_array_element_idx__linear_search(FZY_ItemArray *array, U64 element_idx) -{ - U64 fuzzy_item_num = 0; - for(U64 idx = 0; idx < array->count; idx += 1) - { - if(array->v[idx].idx == element_idx) - { - fuzzy_item_num = idx+1; - break; - } - } - return fuzzy_item_num; -} - -internal String8 -fzy_item_string_from_rdi_target_element_idx(RDI_Parsed *rdi, FZY_Target target, U64 element_idx) -{ - String8 result = {0}; - switch(target) - { - // NOTE(rjf): no default - warn if we miss a case - case FZY_Target_Procedures: - { - RDI_Procedure *proc = rdi_element_from_name_idx(rdi, Procedures, element_idx); - U64 name_size = 0; - U8 *name_base = rdi_string_from_idx(rdi, proc->name_string_idx, &name_size); - result = str8(name_base, name_size); - }break; - case FZY_Target_GlobalVariables: - { - RDI_GlobalVariable *gvar = rdi_element_from_name_idx(rdi, GlobalVariables, element_idx); - U64 name_size = 0; - U8 *name_base = rdi_string_from_idx(rdi, gvar->name_string_idx, &name_size); - result = str8(name_base, name_size); - }break; - case FZY_Target_ThreadVariables: - { - RDI_ThreadVariable *tvar = rdi_element_from_name_idx(rdi, ThreadVariables, element_idx); - U64 name_size = 0; - U8 *name_base = rdi_string_from_idx(rdi, tvar->name_string_idx, &name_size); - result = str8(name_base, name_size); - }break; - case FZY_Target_UDTs: - { - RDI_UDT *udt = rdi_element_from_name_idx(rdi, UDTs, element_idx); - RDI_TypeNode *type_node = rdi_element_from_name_idx(rdi, TypeNodes, udt->self_type_idx); - U64 name_size = 0; - U8 *name_base = rdi_string_from_idx(rdi, type_node->user_defined.name_string_idx, &name_size); - result = str8(name_base, name_size); - }break; - case FZY_Target_COUNT:{}break; - } - return result; -} - -internal FZY_Params -fzy_params_copy(Arena *arena, FZY_Params *src) -{ - FZY_Params dst = zero_struct; - MemoryCopyStruct(&dst, src); - dst.dbgi_keys.v = push_array(arena, DI_Key, dst.dbgi_keys.count); - MemoryCopy(dst.dbgi_keys.v, src->dbgi_keys.v, sizeof(DI_Key)*src->dbgi_keys.count); - for(U64 idx = 0; idx < dst.dbgi_keys.count; idx += 1) - { - dst.dbgi_keys.v[idx].path = push_str8_copy(arena, dst.dbgi_keys.v[idx].path); - } - return dst; -} - -//////////////////////////////// -//~ rjf: Main Layer Initialization - -internal void -fzy_init(void) -{ - Arena *arena = arena_alloc(); - fzy_shared = push_array(arena, FZY_Shared, 1); - fzy_shared->arena = arena; - fzy_shared->slots_count = 256; - fzy_shared->stripes_count = os_logical_core_count(); - fzy_shared->slots = push_array(arena, FZY_Slot, fzy_shared->slots_count); - fzy_shared->stripes = push_array(arena, FZY_Stripe, fzy_shared->stripes_count); - for(U64 idx = 0; idx < fzy_shared->stripes_count; idx += 1) - { - fzy_shared->stripes[idx].arena = arena_alloc(); - fzy_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); - fzy_shared->stripes[idx].cv = os_condition_variable_alloc(); - } - fzy_shared->thread_count = Min(os_logical_core_count(), 2); - fzy_shared->threads = push_array(arena, FZY_Thread, fzy_shared->thread_count); - for(U64 idx = 0; idx < fzy_shared->thread_count; idx += 1) - { - fzy_shared->threads[idx].u2f_ring_mutex = os_mutex_alloc(); - fzy_shared->threads[idx].u2f_ring_cv = os_condition_variable_alloc(); - fzy_shared->threads[idx].u2f_ring_size = KB(64); - fzy_shared->threads[idx].u2f_ring_base = push_array_no_zero(arena, U8, fzy_shared->threads[idx].u2f_ring_size); - fzy_shared->threads[idx].thread = os_launch_thread(fzy_search_thread__entry_point, (void *)idx, 0); - } -} - -//////////////////////////////// -//~ rjf: Scope Functions - -internal FZY_Scope * -fzy_scope_open(void) -{ - if(fzy_tctx == 0) - { - Arena *arena = arena_alloc(); - fzy_tctx = push_array(arena, FZY_TCTX, 1); - fzy_tctx->arena = arena; - } - FZY_Scope *scope = fzy_tctx->free_scope; - if(scope != 0) - { - SLLStackPop(fzy_tctx->free_scope); - } - else - { - scope = push_array_no_zero(fzy_tctx->arena, FZY_Scope, 1); - } - MemoryZeroStruct(scope); - return scope; -} - -internal void -fzy_scope_close(FZY_Scope *scope) -{ - for(FZY_Touch *t = scope->first_touch, *next = 0; t != 0; t = next) - { - next = t->next; - SLLStackPush(fzy_tctx->free_touch, t); - if(t->node != 0) - { - ins_atomic_u64_dec_eval(&t->node->touch_count); - } - } - SLLStackPush(fzy_tctx->free_scope, scope); -} - -internal void -fzy_scope_touch_node__stripe_mutex_r_guarded(FZY_Scope *scope, FZY_Node *node) -{ - if(node != 0) - { - ins_atomic_u64_inc_eval(&node->touch_count); - } - FZY_Touch *touch = fzy_tctx->free_touch; - if(touch != 0) - { - SLLStackPop(fzy_tctx->free_touch); - } - else - { - touch = push_array_no_zero(fzy_tctx->arena, FZY_Touch, 1); - } - MemoryZeroStruct(touch); - SLLQueuePush(scope->first_touch, scope->last_touch, touch); - touch->node = node; -} - -//////////////////////////////// -//~ rjf: Cache Lookup Functions - -internal FZY_ItemArray -fzy_items_from_key_params_query(FZY_Scope *scope, U128 key, FZY_Params *params, String8 query, U64 endt_us, B32 *stale_out) -{ - Temp scratch = scratch_begin(0, 0); - FZY_ItemArray items = {0}; - - //- rjf: hash parameters - U64 params_hash = fzy_hash_from_params(params); - - //- rjf: unpack key - U64 slot_idx = key.u64[1]%fzy_shared->slots_count; - U64 stripe_idx = slot_idx%fzy_shared->stripes_count; - FZY_Slot *slot = &fzy_shared->slots[slot_idx]; - FZY_Stripe *stripe = &fzy_shared->stripes[stripe_idx]; - - //- rjf: query and/or request - OS_MutexScopeR(stripe->rw_mutex) for(;;) - { - // rjf: map key -> node - FZY_Node *node = 0; - for(FZY_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(n->key, key)) - { - node = n; - break; - } - } - - // rjf: no node? -> allocate - if(node == 0) OS_MutexScopeRWPromote(stripe->rw_mutex) - { - node = push_array(stripe->arena, FZY_Node, 1); - SLLQueuePush(slot->first, slot->last, node); - node->key = key; - for(U64 idx = 0; idx < ArrayCount(node->buckets); idx += 1) - { - node->buckets[idx].arena = arena_alloc(); - } - } - - // rjf: try to grab last valid results for this key/query; determine if stale - B32 stale = 1; - if(params_hash == node->buckets[node->gen%ArrayCount(node->buckets)].params_hash && - node->gen != 0) - { - fzy_scope_touch_node__stripe_mutex_r_guarded(scope, node); - items = node->gen_items; - stale = !str8_match(query, node->buckets[node->gen%ArrayCount(node->buckets)].query, 0); - if(stale_out != 0) - { - *stale_out = stale; - } - } - - // rjf: if stale -> request again - if(stale) OS_MutexScopeRWPromote(stripe->rw_mutex) - { - if(node->gen <= node->submit_gen && node->submit_gen < node->gen + ArrayCount(node->buckets)-1) - { - node->submit_gen += 1; - arena_clear(node->buckets[node->submit_gen%ArrayCount(node->buckets)].arena); - node->buckets[node->submit_gen%ArrayCount(node->buckets)].query = push_str8_copy(node->buckets[node->submit_gen%ArrayCount(node->buckets)].arena, query); - node->buckets[node->submit_gen%ArrayCount(node->buckets)].params = fzy_params_copy(node->buckets[node->submit_gen%ArrayCount(node->buckets)].arena, params); - node->buckets[node->submit_gen%ArrayCount(node->buckets)].params_hash = params_hash; - } - if((node->submit_gen > node->gen+1 || os_now_microseconds() >= node->last_time_submitted_us+100000) && - fzy_u2s_enqueue_req(key, endt_us)) - { - node->last_time_submitted_us = os_now_microseconds(); - } - } - - // rjf: not stale, or timeout -> break - if(!stale || os_now_microseconds() >= endt_us) - { - break; - } - - // rjf: no results, but have time to wait -> wait - os_condition_variable_wait_rw_r(stripe->cv, stripe->rw_mutex, endt_us); - } - - scratch_end(scratch); - return items; -} - -//////////////////////////////// -//~ rjf: Searcher Threads - -internal B32 -fzy_u2s_enqueue_req(U128 key, U64 endt_us) -{ - B32 sent = 0; - FZY_Thread *thread = &fzy_shared->threads[key.u64[1]%fzy_shared->thread_count]; - OS_MutexScope(thread->u2f_ring_mutex) for(;;) - { - U64 unconsumed_size = thread->u2f_ring_write_pos - thread->u2f_ring_read_pos; - U64 available_size = thread->u2f_ring_size - unconsumed_size; - if(available_size >= sizeof(U128)) - { - sent = 1; - thread->u2f_ring_write_pos += ring_write_struct(thread->u2f_ring_base, thread->u2f_ring_size, thread->u2f_ring_write_pos, &key); - break; - } - os_condition_variable_wait(thread->u2f_ring_cv, thread->u2f_ring_mutex, endt_us); - } - if(sent) - { - os_condition_variable_broadcast(thread->u2f_ring_cv); - } - return sent; -} - -internal void -fzy_u2s_dequeue_req(Arena *arena, FZY_Thread *thread, U128 *key_out) -{ - OS_MutexScope(thread->u2f_ring_mutex) for(;;) - { - U64 unconsumed_size = thread->u2f_ring_write_pos - thread->u2f_ring_read_pos; - if(unconsumed_size >= sizeof(U128)) - { - thread->u2f_ring_read_pos += ring_read_struct(thread->u2f_ring_base, thread->u2f_ring_size, thread->u2f_ring_read_pos, key_out); - break; - } - os_condition_variable_wait(thread->u2f_ring_cv, thread->u2f_ring_mutex, max_U64); - } - os_condition_variable_broadcast(thread->u2f_ring_cv); -} - -internal int -fzy_qsort_compare_items(FZY_Item *a, FZY_Item *b) -{ - int result = 0; - if(a->match_ranges.count > b->match_ranges.count) - { - result = -1; - } - else if(a->match_ranges.count < b->match_ranges.count) - { - result = +1; - } - else if(a->missed_size < b->missed_size) - { - result = -1; - } - else if(a->missed_size > b->missed_size) - { - result = +1; - } - return result; -} - -internal void -fzy_search_thread__entry_point(void *p) -{ - ThreadNameF("[fzy] searcher #%I64u", (U64)p); - FZY_Thread *thread = &fzy_shared->threads[(U64)p]; - for(;;) - { - Temp scratch = scratch_begin(0, 0); - DI_Scope *di_scope = di_scope_open(); - - //////////////////////////// - //- rjf: dequeue next request - // - U128 key = {0}; - fzy_u2s_dequeue_req(scratch.arena, thread, &key); - U64 slot_idx = key.u64[1]%fzy_shared->slots_count; - U64 stripe_idx = slot_idx%fzy_shared->stripes_count; - FZY_Slot *slot = &fzy_shared->slots[slot_idx]; - FZY_Stripe *stripe = &fzy_shared->stripes[stripe_idx]; - - //////////////////////////// - //- rjf: grab next exe_path/query for this key - // - B32 task_is_good = 0; - Arena *task_arena = 0; - String8 query = {0}; - FZY_Params params = {FZY_Target_Procedures}; - U64 initial_submit_gen = 0; - OS_MutexScopeW(stripe->rw_mutex) - { - for(FZY_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(n->key, key)) - { - FZY_Bucket *bucket = &n->buckets[n->submit_gen%ArrayCount(n->buckets)]; - task_is_good = 1; - initial_submit_gen = n->submit_gen; - task_arena = bucket->arena; - query = bucket->query; - params = bucket->params; - break; - } - } - } - - //////////////////////////// - //- rjf: params -> look up all rdis - // - U64 rdis_count = params.dbgi_keys.count; - RDI_Parsed **rdis = push_array(scratch.arena, RDI_Parsed *, rdis_count); - if(task_is_good) - { - for(U64 idx = 0; idx < rdis_count; idx += 1) - { - rdis[idx] = di_rdi_from_key(di_scope, ¶ms.dbgi_keys.v[idx], max_U64); - } - } - - //////////////////////////// - //- rjf: search target -> info about search space - // - RDI_SectionKind section_kind = RDI_SectionKind_NULL; - U64 element_name_idx_off = 0; - if(task_is_good) - { - switch(params.target) - { - // NOTE(rjf): no default! - case FZY_Target_COUNT:{}break; - case FZY_Target_Procedures: - { - section_kind = RDI_SectionKind_Procedures; - element_name_idx_off = OffsetOf(RDI_Procedure, name_string_idx); - }break; - case FZY_Target_GlobalVariables: - { - section_kind = RDI_SectionKind_GlobalVariables; - element_name_idx_off = OffsetOf(RDI_GlobalVariable, name_string_idx); - }break; - case FZY_Target_ThreadVariables: - { - section_kind = RDI_SectionKind_ThreadVariables; - element_name_idx_off = OffsetOf(RDI_ThreadVariable, name_string_idx); - }break; - case FZY_Target_UDTs: - { - section_kind = RDI_SectionKind_UDTs; - }break; - } - } - - //////////////////////////// - //- rjf: rdis * query * params -> item list - // - FZY_ItemChunkList items_list = {0}; - if(task_is_good) - { - U64 base_idx = 0; - for(U64 rdi_idx = 0; rdi_idx < rdis_count; rdi_idx += 1) - { - RDI_Parsed *rdi = rdis[rdi_idx]; - U64 element_count = 0; - void *table_base = rdi_section_raw_table_from_kind(rdi, section_kind, &element_count); - U64 element_size = rdi_section_element_size_table[section_kind]; - for(U64 idx = 1; task_is_good && idx < element_count; idx += 1) - { - void *element = (U8 *)table_base + element_size*idx; - U32 *name_idx_ptr = (U32 *)((U8 *)element + element_name_idx_off); - if(params.target == FZY_Target_UDTs) - { - RDI_UDT *udt = (RDI_UDT *)element; - RDI_TypeNode *type_node = rdi_element_from_name_idx(rdi, TypeNodes, udt->self_type_idx); - name_idx_ptr = &type_node->user_defined.name_string_idx; - } - U32 name_idx = *name_idx_ptr; - U64 name_size = 0; - U8 *name_base = rdi_string_from_idx(rdi, name_idx, &name_size); - String8 name = str8(name_base, name_size); - if(name.size == 0) { continue; } - FuzzyMatchRangeList matches = fuzzy_match_find(task_arena, query, name); - if(matches.count == matches.needle_part_count) - { - FZY_ItemChunk *chunk = items_list.last; - if(chunk == 0 || chunk->count >= chunk->cap) - { - chunk = push_array(scratch.arena, FZY_ItemChunk, 1); - chunk->cap = 1024; - chunk->count = 0; - chunk->v = push_array_no_zero(scratch.arena, FZY_Item, chunk->cap); - SLLQueuePush(items_list.first, items_list.last, chunk); - items_list.chunk_count += 1; - } - chunk->v[chunk->count].idx = base_idx + idx; - chunk->v[chunk->count].match_ranges = matches; - chunk->v[chunk->count].missed_size = (name_size > matches.total_dim) ? (name_size-matches.total_dim) : 0; - chunk->count += 1; - items_list.total_count += 1; - } - if(idx%100 == 99) OS_MutexScopeR(stripe->rw_mutex) - { - for(FZY_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(n->key, key) && n->submit_gen > initial_submit_gen) - { - task_is_good = 0; - break; - } - } - } - } - base_idx += element_count; - } - } - - //- rjf: item list -> item array - FZY_ItemArray items = {0}; - if(task_is_good) - { - items.count = items_list.total_count; - items.v = push_array_no_zero(task_arena, FZY_Item, items.count); - U64 idx = 0; - for(FZY_ItemChunk *chunk = items_list.first; chunk != 0; chunk = chunk->next) - { - MemoryCopy(items.v+idx, chunk->v, sizeof(FZY_Item)*chunk->count); - idx += chunk->count; - } - } - - //- rjf: sort item array - if(items.count != 0 && query.size != 0) - { - quick_sort(items.v, items.count, sizeof(FZY_Item), fzy_qsort_compare_items); - } - - //- rjf: commit to cache - busyloop on scope touches - if(task_is_good) - { - for(B32 done = 0; !done;) - { - B32 found = 0; - OS_MutexScopeW(stripe->rw_mutex) for(FZY_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(n->key, key)) - { - if(n->touch_count == 0) - { - n->gen = initial_submit_gen; - n->gen_items = items; - done = 1; - } - found = 1; - break; - } - } - if(!found) - { - break; - } - } - } - - di_scope_close(di_scope); - scratch_end(scratch); - } -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Helpers + +internal U64 +fzy_hash_from_string(U64 seed, String8 string) +{ + U64 result = seed; + for(U64 i = 0; i < string.size; i += 1) + { + result = ((result << 5) + result) + string.str[i]; + } + return result; +} + +internal U64 +fzy_hash_from_params(FZY_Params *params) +{ + U64 hash = 5381; + hash = fzy_hash_from_string(hash, str8_struct(¶ms->target)); + for(U64 idx = 0; idx < params->dbgi_keys.count; idx += 1) + { + hash = fzy_hash_from_string(hash, str8_struct(¶ms->dbgi_keys.v[idx].min_timestamp)); + hash = fzy_hash_from_string(hash, params->dbgi_keys.v[idx].path); + } + return hash; +} + +internal U64 +fzy_item_num_from_array_element_idx__linear_search(FZY_ItemArray *array, U64 element_idx) +{ + U64 fuzzy_item_num = 0; + for(U64 idx = 0; idx < array->count; idx += 1) + { + if(array->v[idx].idx == element_idx) + { + fuzzy_item_num = idx+1; + break; + } + } + return fuzzy_item_num; +} + +internal String8 +fzy_item_string_from_rdi_target_element_idx(RDI_Parsed *rdi, FZY_Target target, U64 element_idx) +{ + String8 result = {0}; + switch(target) + { + // NOTE(rjf): no default - warn if we miss a case + case FZY_Target_Procedures: + { + RDI_Procedure *proc = rdi_element_from_name_idx(rdi, Procedures, element_idx); + U64 name_size = 0; + U8 *name_base = rdi_string_from_idx(rdi, proc->name_string_idx, &name_size); + result = str8(name_base, name_size); + }break; + case FZY_Target_GlobalVariables: + { + RDI_GlobalVariable *gvar = rdi_element_from_name_idx(rdi, GlobalVariables, element_idx); + U64 name_size = 0; + U8 *name_base = rdi_string_from_idx(rdi, gvar->name_string_idx, &name_size); + result = str8(name_base, name_size); + }break; + case FZY_Target_ThreadVariables: + { + RDI_ThreadVariable *tvar = rdi_element_from_name_idx(rdi, ThreadVariables, element_idx); + U64 name_size = 0; + U8 *name_base = rdi_string_from_idx(rdi, tvar->name_string_idx, &name_size); + result = str8(name_base, name_size); + }break; + case FZY_Target_UDTs: + { + RDI_UDT *udt = rdi_element_from_name_idx(rdi, UDTs, element_idx); + RDI_TypeNode *type_node = rdi_element_from_name_idx(rdi, TypeNodes, udt->self_type_idx); + U64 name_size = 0; + U8 *name_base = rdi_string_from_idx(rdi, type_node->user_defined.name_string_idx, &name_size); + result = str8(name_base, name_size); + }break; + case FZY_Target_COUNT:{}break; + } + return result; +} + +internal FZY_Params +fzy_params_copy(Arena *arena, FZY_Params *src) +{ + FZY_Params dst = zero_struct; + MemoryCopyStruct(&dst, src); + dst.dbgi_keys.v = push_array(arena, DI_Key, dst.dbgi_keys.count); + MemoryCopy(dst.dbgi_keys.v, src->dbgi_keys.v, sizeof(DI_Key)*src->dbgi_keys.count); + for(U64 idx = 0; idx < dst.dbgi_keys.count; idx += 1) + { + dst.dbgi_keys.v[idx].path = push_str8_copy(arena, dst.dbgi_keys.v[idx].path); + } + return dst; +} + +//////////////////////////////// +//~ rjf: Main Layer Initialization + +internal void +fzy_init(void) +{ + Arena *arena = arena_alloc(); + fzy_shared = push_array(arena, FZY_Shared, 1); + fzy_shared->arena = arena; + fzy_shared->slots_count = 256; + fzy_shared->stripes_count = os_get_system_info()->logical_processor_count; + fzy_shared->slots = push_array(arena, FZY_Slot, fzy_shared->slots_count); + fzy_shared->stripes = push_array(arena, FZY_Stripe, fzy_shared->stripes_count); + for(U64 idx = 0; idx < fzy_shared->stripes_count; idx += 1) + { + fzy_shared->stripes[idx].arena = arena_alloc(); + fzy_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); + fzy_shared->stripes[idx].cv = os_condition_variable_alloc(); + } + fzy_shared->thread_count = Min(os_get_system_info()->logical_processor_count, 2); + fzy_shared->threads = push_array(arena, FZY_Thread, fzy_shared->thread_count); + for(U64 idx = 0; idx < fzy_shared->thread_count; idx += 1) + { + fzy_shared->threads[idx].u2f_ring_mutex = os_mutex_alloc(); + fzy_shared->threads[idx].u2f_ring_cv = os_condition_variable_alloc(); + fzy_shared->threads[idx].u2f_ring_size = KB(64); + fzy_shared->threads[idx].u2f_ring_base = push_array_no_zero(arena, U8, fzy_shared->threads[idx].u2f_ring_size); + fzy_shared->threads[idx].thread = os_thread_launch(fzy_search_thread__entry_point, (void *)idx, 0); + } +} + +//////////////////////////////// +//~ rjf: Scope Functions + +internal FZY_Scope * +fzy_scope_open(void) +{ + if(fzy_tctx == 0) + { + Arena *arena = arena_alloc(); + fzy_tctx = push_array(arena, FZY_TCTX, 1); + fzy_tctx->arena = arena; + } + FZY_Scope *scope = fzy_tctx->free_scope; + if(scope != 0) + { + SLLStackPop(fzy_tctx->free_scope); + } + else + { + scope = push_array_no_zero(fzy_tctx->arena, FZY_Scope, 1); + } + MemoryZeroStruct(scope); + return scope; +} + +internal void +fzy_scope_close(FZY_Scope *scope) +{ + for(FZY_Touch *t = scope->first_touch, *next = 0; t != 0; t = next) + { + next = t->next; + SLLStackPush(fzy_tctx->free_touch, t); + if(t->node != 0) + { + ins_atomic_u64_dec_eval(&t->node->touch_count); + } + } + SLLStackPush(fzy_tctx->free_scope, scope); +} + +internal void +fzy_scope_touch_node__stripe_mutex_r_guarded(FZY_Scope *scope, FZY_Node *node) +{ + if(node != 0) + { + ins_atomic_u64_inc_eval(&node->touch_count); + } + FZY_Touch *touch = fzy_tctx->free_touch; + if(touch != 0) + { + SLLStackPop(fzy_tctx->free_touch); + } + else + { + touch = push_array_no_zero(fzy_tctx->arena, FZY_Touch, 1); + } + MemoryZeroStruct(touch); + SLLQueuePush(scope->first_touch, scope->last_touch, touch); + touch->node = node; +} + +//////////////////////////////// +//~ rjf: Cache Lookup Functions + +internal FZY_ItemArray +fzy_items_from_key_params_query(FZY_Scope *scope, U128 key, FZY_Params *params, String8 query, U64 endt_us, B32 *stale_out) +{ + Temp scratch = scratch_begin(0, 0); + FZY_ItemArray items = {0}; + + //- rjf: hash parameters + U64 params_hash = fzy_hash_from_params(params); + + //- rjf: unpack key + U64 slot_idx = key.u64[1]%fzy_shared->slots_count; + U64 stripe_idx = slot_idx%fzy_shared->stripes_count; + FZY_Slot *slot = &fzy_shared->slots[slot_idx]; + FZY_Stripe *stripe = &fzy_shared->stripes[stripe_idx]; + + //- rjf: query and/or request + OS_MutexScopeR(stripe->rw_mutex) for(;;) + { + // rjf: map key -> node + FZY_Node *node = 0; + for(FZY_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(n->key, key)) + { + node = n; + break; + } + } + + // rjf: no node? -> allocate + if(node == 0) OS_MutexScopeRWPromote(stripe->rw_mutex) + { + node = push_array(stripe->arena, FZY_Node, 1); + SLLQueuePush(slot->first, slot->last, node); + node->key = key; + for(U64 idx = 0; idx < ArrayCount(node->buckets); idx += 1) + { + node->buckets[idx].arena = arena_alloc(); + } + } + + // rjf: try to grab last valid results for this key/query; determine if stale + B32 stale = 1; + if(params_hash == node->buckets[node->gen%ArrayCount(node->buckets)].params_hash && + node->gen != 0) + { + fzy_scope_touch_node__stripe_mutex_r_guarded(scope, node); + items = node->gen_items; + stale = !str8_match(query, node->buckets[node->gen%ArrayCount(node->buckets)].query, 0); + if(stale_out != 0) + { + *stale_out = stale; + } + } + + // rjf: if stale -> request again + if(stale) OS_MutexScopeRWPromote(stripe->rw_mutex) + { + if(node->gen <= node->submit_gen && node->submit_gen < node->gen + ArrayCount(node->buckets)-1) + { + node->submit_gen += 1; + arena_clear(node->buckets[node->submit_gen%ArrayCount(node->buckets)].arena); + node->buckets[node->submit_gen%ArrayCount(node->buckets)].query = push_str8_copy(node->buckets[node->submit_gen%ArrayCount(node->buckets)].arena, query); + node->buckets[node->submit_gen%ArrayCount(node->buckets)].params = fzy_params_copy(node->buckets[node->submit_gen%ArrayCount(node->buckets)].arena, params); + node->buckets[node->submit_gen%ArrayCount(node->buckets)].params_hash = params_hash; + } + if((node->submit_gen > node->gen+1 || os_now_microseconds() >= node->last_time_submitted_us+100000) && + fzy_u2s_enqueue_req(key, endt_us)) + { + node->last_time_submitted_us = os_now_microseconds(); + } + } + + // rjf: not stale, or timeout -> break + if(!stale || os_now_microseconds() >= endt_us) + { + break; + } + + // rjf: no results, but have time to wait -> wait + os_condition_variable_wait_rw_r(stripe->cv, stripe->rw_mutex, endt_us); + } + + scratch_end(scratch); + return items; +} + +//////////////////////////////// +//~ rjf: Searcher Threads + +internal B32 +fzy_u2s_enqueue_req(U128 key, U64 endt_us) +{ + B32 sent = 0; + FZY_Thread *thread = &fzy_shared->threads[key.u64[1]%fzy_shared->thread_count]; + OS_MutexScope(thread->u2f_ring_mutex) for(;;) + { + U64 unconsumed_size = thread->u2f_ring_write_pos - thread->u2f_ring_read_pos; + U64 available_size = thread->u2f_ring_size - unconsumed_size; + if(available_size >= sizeof(U128)) + { + sent = 1; + thread->u2f_ring_write_pos += ring_write_struct(thread->u2f_ring_base, thread->u2f_ring_size, thread->u2f_ring_write_pos, &key); + break; + } + os_condition_variable_wait(thread->u2f_ring_cv, thread->u2f_ring_mutex, endt_us); + } + if(sent) + { + os_condition_variable_broadcast(thread->u2f_ring_cv); + } + return sent; +} + +internal void +fzy_u2s_dequeue_req(Arena *arena, FZY_Thread *thread, U128 *key_out) +{ + OS_MutexScope(thread->u2f_ring_mutex) for(;;) + { + U64 unconsumed_size = thread->u2f_ring_write_pos - thread->u2f_ring_read_pos; + if(unconsumed_size >= sizeof(U128)) + { + thread->u2f_ring_read_pos += ring_read_struct(thread->u2f_ring_base, thread->u2f_ring_size, thread->u2f_ring_read_pos, key_out); + break; + } + os_condition_variable_wait(thread->u2f_ring_cv, thread->u2f_ring_mutex, max_U64); + } + os_condition_variable_broadcast(thread->u2f_ring_cv); +} + +internal int +fzy_qsort_compare_items(FZY_Item *a, FZY_Item *b) +{ + int result = 0; + if(a->match_ranges.count > b->match_ranges.count) + { + result = -1; + } + else if(a->match_ranges.count < b->match_ranges.count) + { + result = +1; + } + else if(a->missed_size < b->missed_size) + { + result = -1; + } + else if(a->missed_size > b->missed_size) + { + result = +1; + } + return result; +} + +internal void +fzy_search_thread__entry_point(void *p) +{ + ThreadNameF("[fzy] searcher #%I64u", (U64)p); + FZY_Thread *thread = &fzy_shared->threads[(U64)p]; + for(;;) + { + Temp scratch = scratch_begin(0, 0); + DI_Scope *di_scope = di_scope_open(); + + //////////////////////////// + //- rjf: dequeue next request + // + U128 key = {0}; + fzy_u2s_dequeue_req(scratch.arena, thread, &key); + U64 slot_idx = key.u64[1]%fzy_shared->slots_count; + U64 stripe_idx = slot_idx%fzy_shared->stripes_count; + FZY_Slot *slot = &fzy_shared->slots[slot_idx]; + FZY_Stripe *stripe = &fzy_shared->stripes[stripe_idx]; + + //////////////////////////// + //- rjf: grab next exe_path/query for this key + // + B32 task_is_good = 0; + Arena *task_arena = 0; + String8 query = {0}; + FZY_Params params = {FZY_Target_Procedures}; + U64 initial_submit_gen = 0; + OS_MutexScopeW(stripe->rw_mutex) + { + for(FZY_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(n->key, key)) + { + FZY_Bucket *bucket = &n->buckets[n->submit_gen%ArrayCount(n->buckets)]; + task_is_good = 1; + initial_submit_gen = n->submit_gen; + task_arena = bucket->arena; + query = bucket->query; + params = bucket->params; + break; + } + } + } + + //////////////////////////// + //- rjf: params -> look up all rdis + // + U64 rdis_count = params.dbgi_keys.count; + RDI_Parsed **rdis = push_array(scratch.arena, RDI_Parsed *, rdis_count); + if(task_is_good) + { + for(U64 idx = 0; idx < rdis_count; idx += 1) + { + rdis[idx] = di_rdi_from_key(di_scope, ¶ms.dbgi_keys.v[idx], max_U64); + } + } + + //////////////////////////// + //- rjf: search target -> info about search space + // + RDI_SectionKind section_kind = RDI_SectionKind_NULL; + U64 element_name_idx_off = 0; + if(task_is_good) + { + switch(params.target) + { + // NOTE(rjf): no default! + case FZY_Target_COUNT:{}break; + case FZY_Target_Procedures: + { + section_kind = RDI_SectionKind_Procedures; + element_name_idx_off = OffsetOf(RDI_Procedure, name_string_idx); + }break; + case FZY_Target_GlobalVariables: + { + section_kind = RDI_SectionKind_GlobalVariables; + element_name_idx_off = OffsetOf(RDI_GlobalVariable, name_string_idx); + }break; + case FZY_Target_ThreadVariables: + { + section_kind = RDI_SectionKind_ThreadVariables; + element_name_idx_off = OffsetOf(RDI_ThreadVariable, name_string_idx); + }break; + case FZY_Target_UDTs: + { + section_kind = RDI_SectionKind_UDTs; + }break; + } + } + + //////////////////////////// + //- rjf: rdis * query * params -> item list + // + FZY_ItemChunkList items_list = {0}; + if(task_is_good) + { + U64 base_idx = 0; + for(U64 rdi_idx = 0; rdi_idx < rdis_count; rdi_idx += 1) + { + RDI_Parsed *rdi = rdis[rdi_idx]; + U64 element_count = 0; + void *table_base = rdi_section_raw_table_from_kind(rdi, section_kind, &element_count); + U64 element_size = rdi_section_element_size_table[section_kind]; + for(U64 idx = 1; task_is_good && idx < element_count; idx += 1) + { + void *element = (U8 *)table_base + element_size*idx; + U32 *name_idx_ptr = (U32 *)((U8 *)element + element_name_idx_off); + if(params.target == FZY_Target_UDTs) + { + RDI_UDT *udt = (RDI_UDT *)element; + RDI_TypeNode *type_node = rdi_element_from_name_idx(rdi, TypeNodes, udt->self_type_idx); + name_idx_ptr = &type_node->user_defined.name_string_idx; + } + U32 name_idx = *name_idx_ptr; + U64 name_size = 0; + U8 *name_base = rdi_string_from_idx(rdi, name_idx, &name_size); + String8 name = str8(name_base, name_size); + if(name.size == 0) { continue; } + FuzzyMatchRangeList matches = fuzzy_match_find(task_arena, query, name); + if(matches.count == matches.needle_part_count) + { + FZY_ItemChunk *chunk = items_list.last; + if(chunk == 0 || chunk->count >= chunk->cap) + { + chunk = push_array(scratch.arena, FZY_ItemChunk, 1); + chunk->cap = 1024; + chunk->count = 0; + chunk->v = push_array_no_zero(scratch.arena, FZY_Item, chunk->cap); + SLLQueuePush(items_list.first, items_list.last, chunk); + items_list.chunk_count += 1; + } + chunk->v[chunk->count].idx = base_idx + idx; + chunk->v[chunk->count].match_ranges = matches; + chunk->v[chunk->count].missed_size = (name_size > matches.total_dim) ? (name_size-matches.total_dim) : 0; + chunk->count += 1; + items_list.total_count += 1; + } + if(idx%100 == 99) OS_MutexScopeR(stripe->rw_mutex) + { + for(FZY_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(n->key, key) && n->submit_gen > initial_submit_gen) + { + task_is_good = 0; + break; + } + } + } + } + base_idx += element_count; + } + } + + //- rjf: item list -> item array + FZY_ItemArray items = {0}; + if(task_is_good) + { + items.count = items_list.total_count; + items.v = push_array_no_zero(task_arena, FZY_Item, items.count); + U64 idx = 0; + for(FZY_ItemChunk *chunk = items_list.first; chunk != 0; chunk = chunk->next) + { + MemoryCopy(items.v+idx, chunk->v, sizeof(FZY_Item)*chunk->count); + idx += chunk->count; + } + } + + //- rjf: sort item array + if(items.count != 0 && query.size != 0) + { + quick_sort(items.v, items.count, sizeof(FZY_Item), fzy_qsort_compare_items); + } + + //- rjf: commit to cache - busyloop on scope touches + if(task_is_good) + { + for(B32 done = 0; !done;) + { + B32 found = 0; + OS_MutexScopeW(stripe->rw_mutex) for(FZY_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(n->key, key)) + { + if(n->touch_count == 0) + { + n->gen = initial_submit_gen; + n->gen_items = items; + done = 1; + } + found = 1; + break; + } + } + if(!found) + { + break; + } + } + } + + di_scope_close(di_scope); + scratch_end(scratch); + } +} diff --git a/src/geo_cache/geo_cache.c b/src/geo_cache/geo_cache.c index 587a76fd..99534036 100644 --- a/src/geo_cache/geo_cache.c +++ b/src/geo_cache/geo_cache.c @@ -1,383 +1,383 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Main Layer Initialization - -internal void -geo_init(void) -{ - Arena *arena = arena_alloc(); - geo_shared = push_array(arena, GEO_Shared, 1); - geo_shared->arena = arena; - geo_shared->slots_count = 1024; - geo_shared->stripes_count = Min(geo_shared->slots_count, os_logical_core_count()); - geo_shared->slots = push_array(arena, GEO_Slot, geo_shared->slots_count); - geo_shared->stripes = push_array(arena, GEO_Stripe, geo_shared->stripes_count); - geo_shared->stripes_free_nodes = push_array(arena, GEO_Node *, geo_shared->stripes_count); - for(U64 idx = 0; idx < geo_shared->stripes_count; idx += 1) - { - geo_shared->stripes[idx].arena = arena_alloc(); - geo_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); - geo_shared->stripes[idx].cv = os_condition_variable_alloc(); - } - geo_shared->u2x_ring_size = KB(64); - geo_shared->u2x_ring_base = push_array_no_zero(arena, U8, geo_shared->u2x_ring_size); - geo_shared->u2x_ring_cv = os_condition_variable_alloc(); - geo_shared->u2x_ring_mutex = os_mutex_alloc(); - geo_shared->xfer_thread_count = Clamp(1, os_logical_core_count()-1, 4); - geo_shared->xfer_threads = push_array(arena, OS_Handle, geo_shared->xfer_thread_count); - for(U64 idx = 0; idx < geo_shared->xfer_thread_count; idx += 1) - { - geo_shared->xfer_threads[idx] = os_launch_thread(geo_xfer_thread__entry_point, (void *)idx, 0); - } - geo_shared->evictor_thread = os_launch_thread(geo_evictor_thread__entry_point, 0, 0); -} - -//////////////////////////////// -//~ rjf: Thread Context Initialization - -internal void -geo_tctx_ensure_inited(void) -{ - if(geo_tctx == 0) - { - Arena *arena = arena_alloc(); - geo_tctx = push_array(arena, GEO_TCTX, 1); - geo_tctx->arena = arena; - } -} - -//////////////////////////////// -//~ rjf: User Clock - -internal void -geo_user_clock_tick(void) -{ - ins_atomic_u64_inc_eval(&geo_shared->user_clock_idx); -} - -internal U64 -geo_user_clock_idx(void) -{ - return ins_atomic_u64_eval(&geo_shared->user_clock_idx); -} - -//////////////////////////////// -//~ rjf: Scoped Access - -internal GEO_Scope * -geo_scope_open(void) -{ - geo_tctx_ensure_inited(); - GEO_Scope *scope = geo_tctx->free_scope; - if(scope) - { - SLLStackPop(geo_tctx->free_scope); - } - else - { - scope = push_array_no_zero(geo_tctx->arena, GEO_Scope, 1); - } - MemoryZeroStruct(scope); - return scope; -} - -internal void -geo_scope_close(GEO_Scope *scope) -{ - for(GEO_Touch *touch = scope->top_touch, *next = 0; touch != 0; touch = next) - { - U128 hash = touch->hash; - next = touch->next; - U64 slot_idx = hash.u64[1]%geo_shared->slots_count; - U64 stripe_idx = slot_idx%geo_shared->stripes_count; - GEO_Slot *slot = &geo_shared->slots[slot_idx]; - GEO_Stripe *stripe = &geo_shared->stripes[stripe_idx]; - OS_MutexScopeR(stripe->rw_mutex) - { - for(GEO_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(hash, n->hash)) - { - ins_atomic_u64_dec_eval(&n->scope_ref_count); - break; - } - } - } - SLLStackPush(geo_tctx->free_touch, touch); - } - SLLStackPush(geo_tctx->free_scope, scope); -} - -internal void -geo_scope_touch_node__stripe_r_guarded(GEO_Scope *scope, GEO_Node *node) -{ - GEO_Touch *touch = geo_tctx->free_touch; - ins_atomic_u64_inc_eval(&node->scope_ref_count); - ins_atomic_u64_eval_assign(&node->last_time_touched_us, os_now_microseconds()); - ins_atomic_u64_eval_assign(&node->last_user_clock_idx_touched, geo_user_clock_idx()); - if(touch != 0) - { - SLLStackPop(geo_tctx->free_touch); - } - else - { - touch = push_array_no_zero(geo_tctx->arena, GEO_Touch, 1); - } - MemoryZeroStruct(touch); - touch->hash = node->hash; - SLLStackPush(scope->top_touch, touch); -} - -//////////////////////////////// -//~ rjf: Cache Lookups - -internal R_Handle -geo_buffer_from_hash(GEO_Scope *scope, U128 hash) -{ - R_Handle handle = {0}; - if(!u128_match(hash, u128_zero())) - { - U64 slot_idx = hash.u64[1]%geo_shared->slots_count; - U64 stripe_idx = slot_idx%geo_shared->stripes_count; - GEO_Slot *slot = &geo_shared->slots[slot_idx]; - GEO_Stripe *stripe = &geo_shared->stripes[stripe_idx]; - B32 found = 0; - OS_MutexScopeR(stripe->rw_mutex) - { - for(GEO_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(hash, n->hash)) - { - handle = n->buffer; - found = !r_handle_match(r_handle_zero(), handle); - geo_scope_touch_node__stripe_r_guarded(scope, n); - break; - } - } - } - B32 node_is_new = 0; - if(!found) - { - OS_MutexScopeW(stripe->rw_mutex) - { - GEO_Node *node = 0; - for(GEO_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(hash, n->hash)) - { - node = n; - break; - } - } - if(node == 0) - { - node = geo_shared->stripes_free_nodes[stripe_idx]; - if(node) - { - SLLStackPop(geo_shared->stripes_free_nodes[stripe_idx]); - } - else - { - node = push_array_no_zero(stripe->arena, GEO_Node, 1); - } - MemoryZeroStruct(node); - DLLPushBack(slot->first, slot->last, node); - node->hash = hash; - node_is_new = 1; - } - } - } - if(node_is_new) - { - geo_u2x_enqueue_req(hash, max_U64); - } - } - return handle; -} - -internal R_Handle -geo_buffer_from_key(GEO_Scope *scope, U128 key) -{ - R_Handle handle = {0}; - for(U64 rewind_idx = 0; rewind_idx < 2; rewind_idx += 1) - { - U128 hash = hs_hash_from_key(key, rewind_idx); - handle = geo_buffer_from_hash(scope, hash); - if(!r_handle_match(handle, r_handle_zero())) - { - break; - } - } - return handle; -} - -//////////////////////////////// -//~ rjf: Transfer Threads - -internal B32 -geo_u2x_enqueue_req(U128 hash, U64 endt_us) -{ - B32 good = 0; - OS_MutexScope(geo_shared->u2x_ring_mutex) for(;;) - { - U64 unconsumed_size = geo_shared->u2x_ring_write_pos-geo_shared->u2x_ring_read_pos; - U64 available_size = geo_shared->u2x_ring_size-unconsumed_size; - if(available_size >= sizeof(hash)) - { - good = 1; - geo_shared->u2x_ring_write_pos += ring_write_struct(geo_shared->u2x_ring_base, geo_shared->u2x_ring_size, geo_shared->u2x_ring_write_pos, &hash); - break; - } - if(os_now_microseconds() >= endt_us) - { - break; - } - os_condition_variable_wait(geo_shared->u2x_ring_cv, geo_shared->u2x_ring_mutex, endt_us); - } - if(good) - { - os_condition_variable_broadcast(geo_shared->u2x_ring_cv); - } - return good; -} - -internal void -geo_u2x_dequeue_req(U128 *hash_out) -{ - OS_MutexScope(geo_shared->u2x_ring_mutex) for(;;) - { - U64 unconsumed_size = geo_shared->u2x_ring_write_pos-geo_shared->u2x_ring_read_pos; - if(unconsumed_size >= sizeof(*hash_out)) - { - geo_shared->u2x_ring_read_pos += ring_read_struct(geo_shared->u2x_ring_base, geo_shared->u2x_ring_size, geo_shared->u2x_ring_read_pos, hash_out); - break; - } - os_condition_variable_wait(geo_shared->u2x_ring_cv, geo_shared->u2x_ring_mutex, max_U64); - } - os_condition_variable_broadcast(geo_shared->u2x_ring_cv); -} - -internal void -geo_xfer_thread__entry_point(void *p) -{ - for(;;) - { - HS_Scope *scope = hs_scope_open(); - - //- rjf: decode - U128 hash = {0}; - geo_u2x_dequeue_req(&hash); - - //- rjf: unpack hash - U64 slot_idx = hash.u64[1]%geo_shared->slots_count; - U64 stripe_idx = slot_idx%geo_shared->stripes_count; - GEO_Slot *slot = &geo_shared->slots[slot_idx]; - GEO_Stripe *stripe = &geo_shared->stripes[stripe_idx]; - - //- rjf: take task - B32 got_task = 0; - OS_MutexScopeR(stripe->rw_mutex) - { - for(GEO_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(n->hash, hash)) - { - got_task = !ins_atomic_u32_eval_cond_assign(&n->is_working, 1, 0); - break; - } - } - } - - //- rjf: hash -> data - String8 data = {0}; - if(got_task) - { - data = hs_data_from_hash(scope, hash); - } - - //- rjf: data -> buffer - R_Handle buffer = {0}; - if(got_task && data.size != 0) - { - buffer = r_buffer_alloc(R_ResourceKind_Static, data.size, data.str); - } - - //- rjf: commit results to cache - if(got_task) OS_MutexScopeW(stripe->rw_mutex) - { - for(GEO_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(n->hash, hash)) - { - n->buffer = buffer; - ins_atomic_u32_eval_assign(&n->is_working, 0); - ins_atomic_u64_inc_eval(&n->load_count); - break; - } - } - } - - hs_scope_close(scope); - } -} - -//////////////////////////////// -//~ rjf: Evictor Threads - -internal void -geo_evictor_thread__entry_point(void *p) -{ - for(;;) - { - U64 check_time_us = os_now_microseconds(); - U64 check_time_user_clocks = geo_user_clock_idx(); - U64 evict_threshold_us = 10*1000000; - U64 evict_threshold_user_clocks = 10; - for(U64 slot_idx = 0; slot_idx < geo_shared->slots_count; slot_idx += 1) - { - U64 stripe_idx = slot_idx%geo_shared->stripes_count; - GEO_Slot *slot = &geo_shared->slots[slot_idx]; - GEO_Stripe *stripe = &geo_shared->stripes[stripe_idx]; - B32 slot_has_work = 0; - OS_MutexScopeR(stripe->rw_mutex) - { - for(GEO_Node *n = slot->first; n != 0; n = n->next) - { - if(n->scope_ref_count == 0 && - n->last_time_touched_us+evict_threshold_us <= check_time_us && - n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && - n->load_count != 0 && - n->is_working == 0) - { - slot_has_work = 1; - break; - } - } - } - if(slot_has_work) OS_MutexScopeW(stripe->rw_mutex) - { - for(GEO_Node *n = slot->first, *next = 0; n != 0; n = next) - { - next = n->next; - if(n->scope_ref_count == 0 && - n->last_time_touched_us+evict_threshold_us <= check_time_us && - n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && - n->load_count != 0 && - n->is_working == 0) - { - DLLRemove(slot->first, slot->last, n); - if(!r_handle_match(n->buffer, r_handle_zero())) - { - r_buffer_release(n->buffer); - } - SLLStackPush(geo_shared->stripes_free_nodes[stripe_idx], n); - } - } - } - os_sleep_milliseconds(5); - } - os_sleep_milliseconds(1000); - } -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Main Layer Initialization + +internal void +geo_init(void) +{ + Arena *arena = arena_alloc(); + geo_shared = push_array(arena, GEO_Shared, 1); + geo_shared->arena = arena; + geo_shared->slots_count = 1024; + geo_shared->stripes_count = Min(geo_shared->slots_count, os_get_system_info()->logical_processor_count); + geo_shared->slots = push_array(arena, GEO_Slot, geo_shared->slots_count); + geo_shared->stripes = push_array(arena, GEO_Stripe, geo_shared->stripes_count); + geo_shared->stripes_free_nodes = push_array(arena, GEO_Node *, geo_shared->stripes_count); + for(U64 idx = 0; idx < geo_shared->stripes_count; idx += 1) + { + geo_shared->stripes[idx].arena = arena_alloc(); + geo_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); + geo_shared->stripes[idx].cv = os_condition_variable_alloc(); + } + geo_shared->u2x_ring_size = KB(64); + geo_shared->u2x_ring_base = push_array_no_zero(arena, U8, geo_shared->u2x_ring_size); + geo_shared->u2x_ring_cv = os_condition_variable_alloc(); + geo_shared->u2x_ring_mutex = os_mutex_alloc(); + geo_shared->xfer_thread_count = Clamp(1, os_get_system_info()->logical_processor_count-1, 4); + geo_shared->xfer_threads = push_array(arena, OS_Handle, geo_shared->xfer_thread_count); + for(U64 idx = 0; idx < geo_shared->xfer_thread_count; idx += 1) + { + geo_shared->xfer_threads[idx] = os_thread_launch(geo_xfer_thread__entry_point, (void *)idx, 0); + } + geo_shared->evictor_thread = os_thread_launch(geo_evictor_thread__entry_point, 0, 0); +} + +//////////////////////////////// +//~ rjf: Thread Context Initialization + +internal void +geo_tctx_ensure_inited(void) +{ + if(geo_tctx == 0) + { + Arena *arena = arena_alloc(); + geo_tctx = push_array(arena, GEO_TCTX, 1); + geo_tctx->arena = arena; + } +} + +//////////////////////////////// +//~ rjf: User Clock + +internal void +geo_user_clock_tick(void) +{ + ins_atomic_u64_inc_eval(&geo_shared->user_clock_idx); +} + +internal U64 +geo_user_clock_idx(void) +{ + return ins_atomic_u64_eval(&geo_shared->user_clock_idx); +} + +//////////////////////////////// +//~ rjf: Scoped Access + +internal GEO_Scope * +geo_scope_open(void) +{ + geo_tctx_ensure_inited(); + GEO_Scope *scope = geo_tctx->free_scope; + if(scope) + { + SLLStackPop(geo_tctx->free_scope); + } + else + { + scope = push_array_no_zero(geo_tctx->arena, GEO_Scope, 1); + } + MemoryZeroStruct(scope); + return scope; +} + +internal void +geo_scope_close(GEO_Scope *scope) +{ + for(GEO_Touch *touch = scope->top_touch, *next = 0; touch != 0; touch = next) + { + U128 hash = touch->hash; + next = touch->next; + U64 slot_idx = hash.u64[1]%geo_shared->slots_count; + U64 stripe_idx = slot_idx%geo_shared->stripes_count; + GEO_Slot *slot = &geo_shared->slots[slot_idx]; + GEO_Stripe *stripe = &geo_shared->stripes[stripe_idx]; + OS_MutexScopeR(stripe->rw_mutex) + { + for(GEO_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(hash, n->hash)) + { + ins_atomic_u64_dec_eval(&n->scope_ref_count); + break; + } + } + } + SLLStackPush(geo_tctx->free_touch, touch); + } + SLLStackPush(geo_tctx->free_scope, scope); +} + +internal void +geo_scope_touch_node__stripe_r_guarded(GEO_Scope *scope, GEO_Node *node) +{ + GEO_Touch *touch = geo_tctx->free_touch; + ins_atomic_u64_inc_eval(&node->scope_ref_count); + ins_atomic_u64_eval_assign(&node->last_time_touched_us, os_now_microseconds()); + ins_atomic_u64_eval_assign(&node->last_user_clock_idx_touched, geo_user_clock_idx()); + if(touch != 0) + { + SLLStackPop(geo_tctx->free_touch); + } + else + { + touch = push_array_no_zero(geo_tctx->arena, GEO_Touch, 1); + } + MemoryZeroStruct(touch); + touch->hash = node->hash; + SLLStackPush(scope->top_touch, touch); +} + +//////////////////////////////// +//~ rjf: Cache Lookups + +internal R_Handle +geo_buffer_from_hash(GEO_Scope *scope, U128 hash) +{ + R_Handle handle = {0}; + if(!u128_match(hash, u128_zero())) + { + U64 slot_idx = hash.u64[1]%geo_shared->slots_count; + U64 stripe_idx = slot_idx%geo_shared->stripes_count; + GEO_Slot *slot = &geo_shared->slots[slot_idx]; + GEO_Stripe *stripe = &geo_shared->stripes[stripe_idx]; + B32 found = 0; + OS_MutexScopeR(stripe->rw_mutex) + { + for(GEO_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(hash, n->hash)) + { + handle = n->buffer; + found = !r_handle_match(r_handle_zero(), handle); + geo_scope_touch_node__stripe_r_guarded(scope, n); + break; + } + } + } + B32 node_is_new = 0; + if(!found) + { + OS_MutexScopeW(stripe->rw_mutex) + { + GEO_Node *node = 0; + for(GEO_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(hash, n->hash)) + { + node = n; + break; + } + } + if(node == 0) + { + node = geo_shared->stripes_free_nodes[stripe_idx]; + if(node) + { + SLLStackPop(geo_shared->stripes_free_nodes[stripe_idx]); + } + else + { + node = push_array_no_zero(stripe->arena, GEO_Node, 1); + } + MemoryZeroStruct(node); + DLLPushBack(slot->first, slot->last, node); + node->hash = hash; + node_is_new = 1; + } + } + } + if(node_is_new) + { + geo_u2x_enqueue_req(hash, max_U64); + } + } + return handle; +} + +internal R_Handle +geo_buffer_from_key(GEO_Scope *scope, U128 key) +{ + R_Handle handle = {0}; + for(U64 rewind_idx = 0; rewind_idx < 2; rewind_idx += 1) + { + U128 hash = hs_hash_from_key(key, rewind_idx); + handle = geo_buffer_from_hash(scope, hash); + if(!r_handle_match(handle, r_handle_zero())) + { + break; + } + } + return handle; +} + +//////////////////////////////// +//~ rjf: Transfer Threads + +internal B32 +geo_u2x_enqueue_req(U128 hash, U64 endt_us) +{ + B32 good = 0; + OS_MutexScope(geo_shared->u2x_ring_mutex) for(;;) + { + U64 unconsumed_size = geo_shared->u2x_ring_write_pos-geo_shared->u2x_ring_read_pos; + U64 available_size = geo_shared->u2x_ring_size-unconsumed_size; + if(available_size >= sizeof(hash)) + { + good = 1; + geo_shared->u2x_ring_write_pos += ring_write_struct(geo_shared->u2x_ring_base, geo_shared->u2x_ring_size, geo_shared->u2x_ring_write_pos, &hash); + break; + } + if(os_now_microseconds() >= endt_us) + { + break; + } + os_condition_variable_wait(geo_shared->u2x_ring_cv, geo_shared->u2x_ring_mutex, endt_us); + } + if(good) + { + os_condition_variable_broadcast(geo_shared->u2x_ring_cv); + } + return good; +} + +internal void +geo_u2x_dequeue_req(U128 *hash_out) +{ + OS_MutexScope(geo_shared->u2x_ring_mutex) for(;;) + { + U64 unconsumed_size = geo_shared->u2x_ring_write_pos-geo_shared->u2x_ring_read_pos; + if(unconsumed_size >= sizeof(*hash_out)) + { + geo_shared->u2x_ring_read_pos += ring_read_struct(geo_shared->u2x_ring_base, geo_shared->u2x_ring_size, geo_shared->u2x_ring_read_pos, hash_out); + break; + } + os_condition_variable_wait(geo_shared->u2x_ring_cv, geo_shared->u2x_ring_mutex, max_U64); + } + os_condition_variable_broadcast(geo_shared->u2x_ring_cv); +} + +internal void +geo_xfer_thread__entry_point(void *p) +{ + for(;;) + { + HS_Scope *scope = hs_scope_open(); + + //- rjf: decode + U128 hash = {0}; + geo_u2x_dequeue_req(&hash); + + //- rjf: unpack hash + U64 slot_idx = hash.u64[1]%geo_shared->slots_count; + U64 stripe_idx = slot_idx%geo_shared->stripes_count; + GEO_Slot *slot = &geo_shared->slots[slot_idx]; + GEO_Stripe *stripe = &geo_shared->stripes[stripe_idx]; + + //- rjf: take task + B32 got_task = 0; + OS_MutexScopeR(stripe->rw_mutex) + { + for(GEO_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(n->hash, hash)) + { + got_task = !ins_atomic_u32_eval_cond_assign(&n->is_working, 1, 0); + break; + } + } + } + + //- rjf: hash -> data + String8 data = {0}; + if(got_task) + { + data = hs_data_from_hash(scope, hash); + } + + //- rjf: data -> buffer + R_Handle buffer = {0}; + if(got_task && data.size != 0) + { + buffer = r_buffer_alloc(R_ResourceKind_Static, data.size, data.str); + } + + //- rjf: commit results to cache + if(got_task) OS_MutexScopeW(stripe->rw_mutex) + { + for(GEO_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(n->hash, hash)) + { + n->buffer = buffer; + ins_atomic_u32_eval_assign(&n->is_working, 0); + ins_atomic_u64_inc_eval(&n->load_count); + break; + } + } + } + + hs_scope_close(scope); + } +} + +//////////////////////////////// +//~ rjf: Evictor Threads + +internal void +geo_evictor_thread__entry_point(void *p) +{ + for(;;) + { + U64 check_time_us = os_now_microseconds(); + U64 check_time_user_clocks = geo_user_clock_idx(); + U64 evict_threshold_us = 10*1000000; + U64 evict_threshold_user_clocks = 10; + for(U64 slot_idx = 0; slot_idx < geo_shared->slots_count; slot_idx += 1) + { + U64 stripe_idx = slot_idx%geo_shared->stripes_count; + GEO_Slot *slot = &geo_shared->slots[slot_idx]; + GEO_Stripe *stripe = &geo_shared->stripes[stripe_idx]; + B32 slot_has_work = 0; + OS_MutexScopeR(stripe->rw_mutex) + { + for(GEO_Node *n = slot->first; n != 0; n = n->next) + { + if(n->scope_ref_count == 0 && + n->last_time_touched_us+evict_threshold_us <= check_time_us && + n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && + n->load_count != 0 && + n->is_working == 0) + { + slot_has_work = 1; + break; + } + } + } + if(slot_has_work) OS_MutexScopeW(stripe->rw_mutex) + { + for(GEO_Node *n = slot->first, *next = 0; n != 0; n = next) + { + next = n->next; + if(n->scope_ref_count == 0 && + n->last_time_touched_us+evict_threshold_us <= check_time_us && + n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && + n->load_count != 0 && + n->is_working == 0) + { + DLLRemove(slot->first, slot->last, n); + if(!r_handle_match(n->buffer, r_handle_zero())) + { + r_buffer_release(n->buffer); + } + SLLStackPush(geo_shared->stripes_free_nodes[stripe_idx], n); + } + } + } + os_sleep_milliseconds(5); + } + os_sleep_milliseconds(1000); + } +} diff --git a/src/hash_store/hash_store.c b/src/hash_store/hash_store.c index 6d3d852d..01b3860b 100644 --- a/src/hash_store/hash_store.c +++ b/src/hash_store/hash_store.c @@ -1,339 +1,339 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Basic Helpers - -#if !defined(BLAKE2_H) -#define HAVE_SSE2 -#include "third_party/blake2/blake2.h" -#include "third_party/blake2/blake2b.c" -#endif - -internal U128 -hs_hash_from_data(String8 data) -{ - U128 u128 = {0}; - if(data.size != 0) - { - blake2b((U8 *)&u128.u64[0], sizeof(u128), data.str, data.size, 0, 0); - } - return u128; -} - -//////////////////////////////// -//~ rjf: Main Layer Initialization - -internal void -hs_init(void) -{ - Arena *arena = arena_alloc(); - hs_shared = push_array(arena, HS_Shared, 1); - hs_shared->arena = arena; - hs_shared->slots_count = 4096; - hs_shared->stripes_count = Min(hs_shared->slots_count, os_logical_core_count()); - hs_shared->slots = push_array(arena, HS_Slot, hs_shared->slots_count); - hs_shared->stripes = push_array(arena, HS_Stripe, hs_shared->stripes_count); - hs_shared->stripes_free_nodes = push_array(arena, HS_Node *, hs_shared->stripes_count); - for(U64 idx = 0; idx < hs_shared->stripes_count; idx += 1) - { - HS_Stripe *stripe = &hs_shared->stripes[idx]; - stripe->arena = arena_alloc(); - stripe->rw_mutex = os_rw_mutex_alloc(); - stripe->cv = os_condition_variable_alloc(); - } - hs_shared->key_slots_count = 4096; - hs_shared->key_stripes_count = Min(hs_shared->key_slots_count, os_logical_core_count()); - hs_shared->key_slots = push_array(arena, HS_KeySlot, hs_shared->key_slots_count); - hs_shared->key_stripes = push_array(arena, HS_Stripe, hs_shared->key_stripes_count); - for(U64 idx = 0; idx < hs_shared->key_stripes_count; idx += 1) - { - HS_Stripe *stripe = &hs_shared->key_stripes[idx]; - stripe->arena = arena_alloc(); - stripe->rw_mutex = os_rw_mutex_alloc(); - stripe->cv = os_condition_variable_alloc(); - } - hs_shared->evictor_thread = os_launch_thread(hs_evictor_thread__entry_point, 0, 0); -} - -//////////////////////////////// -//~ rjf: Thread Context Initialization - -internal void -hs_tctx_ensure_inited(void) -{ - if(hs_tctx == 0) - { - Arena *arena = arena_alloc(); - hs_tctx = push_array(arena, HS_TCTX, 1); - hs_tctx->arena = arena; - } -} - -//////////////////////////////// -//~ rjf: Cache Submission - -internal U128 -hs_submit_data(U128 key, Arena **data_arena, String8 data) -{ - U64 key_slot_idx = key.u64[1]%hs_shared->key_slots_count; - U64 key_stripe_idx = key_slot_idx%hs_shared->key_stripes_count; - HS_KeySlot *key_slot = &hs_shared->key_slots[key_slot_idx]; - HS_Stripe *key_stripe = &hs_shared->key_stripes[key_stripe_idx]; - U128 hash = hs_hash_from_data(data); - U64 slot_idx = hash.u64[1]%hs_shared->slots_count; - U64 stripe_idx = slot_idx%hs_shared->stripes_count; - HS_Slot *slot = &hs_shared->slots[slot_idx]; - HS_Stripe *stripe = &hs_shared->stripes[stripe_idx]; - - //- rjf: commit data to cache - if already there, just bump key refcount - ProfScope("commit data to cache - if already there, just bump key refcount") OS_MutexScopeW(stripe->rw_mutex) - { - HS_Node *existing_node = 0; - for(HS_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(n->hash, hash)) - { - existing_node = n; - break; - } - } - if(existing_node == 0) - { - HS_Node *node = hs_shared->stripes_free_nodes[stripe_idx]; - if(node) - { - SLLStackPop(hs_shared->stripes_free_nodes[stripe_idx]); - } - else - { - node = push_array(stripe->arena, HS_Node, 1); - } - node->hash = hash; - node->arena = *data_arena; - node->data = data; - node->scope_ref_count = 0; - node->key_ref_count = 1; - DLLPushBack(slot->first, slot->last, node); - } - else - { - existing_node->key_ref_count += 1; - arena_release(*data_arena); - } - *data_arena = 0; - } - - //- rjf: commit this hash to key cache - U128 key_expired_hash = {0}; - ProfScope("commit this hash to key cache") OS_MutexScopeW(key_stripe->rw_mutex) - { - HS_KeyNode *key_node = 0; - for(HS_KeyNode *n = key_slot->first; n != 0; n = n->next) - { - if(u128_match(n->key, key)) - { - key_node = n; - break; - } - } - if(!key_node) - { - key_node = push_array(key_stripe->arena, HS_KeyNode, 1); - key_node->key = key; - SLLQueuePush(key_slot->first, key_slot->last, key_node); - } - if(key_node) - { - if(key_node->hash_history_gen >= ArrayCount(key_node->hash_history)) - { - key_expired_hash = key_node->hash_history[key_node->hash_history_gen%ArrayCount(key_node->hash_history)]; - } - key_node->hash_history[key_node->hash_history_gen%ArrayCount(key_node->hash_history)] = hash; - key_node->hash_history_gen += 1; - } - } - - //- rjf: if this key's history cache was full, dec key ref count of oldest hash - ProfScope("if this key's history cache was full, dec key ref count of oldest hash") - if(!u128_match(key_expired_hash, u128_zero())) - { - U64 old_hash_slot_idx = key_expired_hash.u64[1]%hs_shared->slots_count; - U64 old_hash_stripe_idx = old_hash_slot_idx%hs_shared->stripes_count; - HS_Slot *old_hash_slot = &hs_shared->slots[old_hash_slot_idx]; - HS_Stripe *old_hash_stripe = &hs_shared->stripes[old_hash_stripe_idx]; - OS_MutexScopeR(old_hash_stripe->rw_mutex) - { - for(HS_Node *n = old_hash_slot->first; n != 0; n = n->next) - { - if(u128_match(n->hash, key_expired_hash)) - { - ins_atomic_u64_dec_eval(&n->key_ref_count); - break; - } - } - } - } - - return hash; -} - -//////////////////////////////// -//~ rjf: Scoped Access - -internal HS_Scope * -hs_scope_open(void) -{ - hs_tctx_ensure_inited(); - HS_Scope *scope = hs_tctx->free_scope; - if(scope) - { - SLLStackPop(hs_tctx->free_scope); - } - else - { - scope = push_array_no_zero(hs_tctx->arena, HS_Scope, 1); - } - MemoryZeroStruct(scope); - return scope; -} - -internal void -hs_scope_close(HS_Scope *scope) -{ - for(HS_Touch *touch = scope->top_touch, *next = 0; touch != 0; touch = next) - { - U128 hash = touch->hash; - next = touch->next; - U64 slot_idx = hash.u64[1]%hs_shared->slots_count; - U64 stripe_idx = slot_idx%hs_shared->stripes_count; - HS_Slot *slot = &hs_shared->slots[slot_idx]; - HS_Stripe *stripe = &hs_shared->stripes[stripe_idx]; - OS_MutexScopeR(stripe->rw_mutex) - { - for(HS_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(hash, n->hash)) - { - ins_atomic_u64_dec_eval(&n->scope_ref_count); - break; - } - } - } - SLLStackPush(hs_tctx->free_touch, touch); - } - SLLStackPush(hs_tctx->free_scope, scope); -} - -internal void -hs_scope_touch_node__stripe_r_guarded(HS_Scope *scope, HS_Node *node) -{ - HS_Touch *touch = hs_tctx->free_touch; - ins_atomic_u64_inc_eval(&node->scope_ref_count); - if(touch != 0) - { - SLLStackPop(hs_tctx->free_touch); - } - else - { - touch = push_array_no_zero(hs_tctx->arena, HS_Touch, 1); - } - MemoryZeroStruct(touch); - touch->hash = node->hash; - SLLStackPush(scope->top_touch, touch); -} - -//////////////////////////////// -//~ rjf: Cache Lookup - -internal U128 -hs_hash_from_key(U128 key, U64 rewind_count) -{ - U128 result = {0}; - U64 key_slot_idx = key.u64[1]%hs_shared->key_slots_count; - U64 key_stripe_idx = key_slot_idx%hs_shared->key_stripes_count; - HS_KeySlot *key_slot = &hs_shared->key_slots[key_slot_idx]; - HS_Stripe *key_stripe = &hs_shared->key_stripes[key_stripe_idx]; - OS_MutexScopeR(key_stripe->rw_mutex) - { - for(HS_KeyNode *n = key_slot->first; n != 0; n = n->next) - { - if(u128_match(n->key, key) && n->hash_history_gen > 0 && n->hash_history_gen-1 >= rewind_count) - { - result = n->hash_history[(n->hash_history_gen-1-rewind_count)%ArrayCount(n->hash_history)]; - break; - } - } - } - return result; -} - -internal String8 -hs_data_from_hash(HS_Scope *scope, U128 hash) -{ - String8 result = {0}; - U64 slot_idx = hash.u64[1]%hs_shared->slots_count; - U64 stripe_idx = slot_idx%hs_shared->stripes_count; - HS_Slot *slot = &hs_shared->slots[slot_idx]; - HS_Stripe *stripe = &hs_shared->stripes[stripe_idx]; - OS_MutexScopeR(stripe->rw_mutex) - { - for(HS_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(n->hash, hash)) - { - result = n->data; - hs_scope_touch_node__stripe_r_guarded(scope, n); - break; - } - } - } - return result; -} - -//////////////////////////////// -//~ rjf: Evictor Thread - -internal void -hs_evictor_thread__entry_point(void *p) -{ - for(;;) - { - for(U64 slot_idx = 0; slot_idx < hs_shared->slots_count; slot_idx += 1) - { - U64 stripe_idx = slot_idx%hs_shared->stripes_count; - HS_Slot *slot = &hs_shared->slots[slot_idx]; - HS_Stripe *stripe = &hs_shared->stripes[stripe_idx]; - B32 slot_has_work = 0; - OS_MutexScopeR(stripe->rw_mutex) - { - for(HS_Node *n = slot->first; n != 0; n = n->next) - { - U64 key_ref_count = ins_atomic_u64_eval(&n->key_ref_count); - U64 scope_ref_count = ins_atomic_u64_eval(&n->scope_ref_count); - if(key_ref_count == 0 && scope_ref_count == 0) - { - slot_has_work = 1; - break; - } - } - } - if(slot_has_work) OS_MutexScopeW(stripe->rw_mutex) - { - for(HS_Node *n = slot->first, *next = 0; n != 0; n = next) - { - next = n->next; - U64 key_ref_count = ins_atomic_u64_eval(&n->key_ref_count); - U64 scope_ref_count = ins_atomic_u64_eval(&n->scope_ref_count); - if(key_ref_count == 0 && scope_ref_count == 0) - { - DLLRemove(slot->first, slot->last, n); - SLLStackPush(hs_shared->stripes_free_nodes[stripe_idx], n); - arena_release(n->arena); - } - } - } - } - os_sleep_milliseconds(1000); - } -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Basic Helpers + +#if !defined(BLAKE2_H) +#define HAVE_SSE2 +#include "third_party/blake2/blake2.h" +#include "third_party/blake2/blake2b.c" +#endif + +internal U128 +hs_hash_from_data(String8 data) +{ + U128 u128 = {0}; + if(data.size != 0) + { + blake2b((U8 *)&u128.u64[0], sizeof(u128), data.str, data.size, 0, 0); + } + return u128; +} + +//////////////////////////////// +//~ rjf: Main Layer Initialization + +internal void +hs_init(void) +{ + Arena *arena = arena_alloc(); + hs_shared = push_array(arena, HS_Shared, 1); + hs_shared->arena = arena; + hs_shared->slots_count = 4096; + hs_shared->stripes_count = Min(hs_shared->slots_count, os_get_system_info()->logical_processor_count); + hs_shared->slots = push_array(arena, HS_Slot, hs_shared->slots_count); + hs_shared->stripes = push_array(arena, HS_Stripe, hs_shared->stripes_count); + hs_shared->stripes_free_nodes = push_array(arena, HS_Node *, hs_shared->stripes_count); + for(U64 idx = 0; idx < hs_shared->stripes_count; idx += 1) + { + HS_Stripe *stripe = &hs_shared->stripes[idx]; + stripe->arena = arena_alloc(); + stripe->rw_mutex = os_rw_mutex_alloc(); + stripe->cv = os_condition_variable_alloc(); + } + hs_shared->key_slots_count = 4096; + hs_shared->key_stripes_count = Min(hs_shared->key_slots_count, os_get_system_info()->logical_processor_count); + hs_shared->key_slots = push_array(arena, HS_KeySlot, hs_shared->key_slots_count); + hs_shared->key_stripes = push_array(arena, HS_Stripe, hs_shared->key_stripes_count); + for(U64 idx = 0; idx < hs_shared->key_stripes_count; idx += 1) + { + HS_Stripe *stripe = &hs_shared->key_stripes[idx]; + stripe->arena = arena_alloc(); + stripe->rw_mutex = os_rw_mutex_alloc(); + stripe->cv = os_condition_variable_alloc(); + } + hs_shared->evictor_thread = os_thread_launch(hs_evictor_thread__entry_point, 0, 0); +} + +//////////////////////////////// +//~ rjf: Thread Context Initialization + +internal void +hs_tctx_ensure_inited(void) +{ + if(hs_tctx == 0) + { + Arena *arena = arena_alloc(); + hs_tctx = push_array(arena, HS_TCTX, 1); + hs_tctx->arena = arena; + } +} + +//////////////////////////////// +//~ rjf: Cache Submission + +internal U128 +hs_submit_data(U128 key, Arena **data_arena, String8 data) +{ + U64 key_slot_idx = key.u64[1]%hs_shared->key_slots_count; + U64 key_stripe_idx = key_slot_idx%hs_shared->key_stripes_count; + HS_KeySlot *key_slot = &hs_shared->key_slots[key_slot_idx]; + HS_Stripe *key_stripe = &hs_shared->key_stripes[key_stripe_idx]; + U128 hash = hs_hash_from_data(data); + U64 slot_idx = hash.u64[1]%hs_shared->slots_count; + U64 stripe_idx = slot_idx%hs_shared->stripes_count; + HS_Slot *slot = &hs_shared->slots[slot_idx]; + HS_Stripe *stripe = &hs_shared->stripes[stripe_idx]; + + //- rjf: commit data to cache - if already there, just bump key refcount + ProfScope("commit data to cache - if already there, just bump key refcount") OS_MutexScopeW(stripe->rw_mutex) + { + HS_Node *existing_node = 0; + for(HS_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(n->hash, hash)) + { + existing_node = n; + break; + } + } + if(existing_node == 0) + { + HS_Node *node = hs_shared->stripes_free_nodes[stripe_idx]; + if(node) + { + SLLStackPop(hs_shared->stripes_free_nodes[stripe_idx]); + } + else + { + node = push_array(stripe->arena, HS_Node, 1); + } + node->hash = hash; + node->arena = *data_arena; + node->data = data; + node->scope_ref_count = 0; + node->key_ref_count = 1; + DLLPushBack(slot->first, slot->last, node); + } + else + { + existing_node->key_ref_count += 1; + arena_release(*data_arena); + } + *data_arena = 0; + } + + //- rjf: commit this hash to key cache + U128 key_expired_hash = {0}; + ProfScope("commit this hash to key cache") OS_MutexScopeW(key_stripe->rw_mutex) + { + HS_KeyNode *key_node = 0; + for(HS_KeyNode *n = key_slot->first; n != 0; n = n->next) + { + if(u128_match(n->key, key)) + { + key_node = n; + break; + } + } + if(!key_node) + { + key_node = push_array(key_stripe->arena, HS_KeyNode, 1); + key_node->key = key; + SLLQueuePush(key_slot->first, key_slot->last, key_node); + } + if(key_node) + { + if(key_node->hash_history_gen >= ArrayCount(key_node->hash_history)) + { + key_expired_hash = key_node->hash_history[key_node->hash_history_gen%ArrayCount(key_node->hash_history)]; + } + key_node->hash_history[key_node->hash_history_gen%ArrayCount(key_node->hash_history)] = hash; + key_node->hash_history_gen += 1; + } + } + + //- rjf: if this key's history cache was full, dec key ref count of oldest hash + ProfScope("if this key's history cache was full, dec key ref count of oldest hash") + if(!u128_match(key_expired_hash, u128_zero())) + { + U64 old_hash_slot_idx = key_expired_hash.u64[1]%hs_shared->slots_count; + U64 old_hash_stripe_idx = old_hash_slot_idx%hs_shared->stripes_count; + HS_Slot *old_hash_slot = &hs_shared->slots[old_hash_slot_idx]; + HS_Stripe *old_hash_stripe = &hs_shared->stripes[old_hash_stripe_idx]; + OS_MutexScopeR(old_hash_stripe->rw_mutex) + { + for(HS_Node *n = old_hash_slot->first; n != 0; n = n->next) + { + if(u128_match(n->hash, key_expired_hash)) + { + ins_atomic_u64_dec_eval(&n->key_ref_count); + break; + } + } + } + } + + return hash; +} + +//////////////////////////////// +//~ rjf: Scoped Access + +internal HS_Scope * +hs_scope_open(void) +{ + hs_tctx_ensure_inited(); + HS_Scope *scope = hs_tctx->free_scope; + if(scope) + { + SLLStackPop(hs_tctx->free_scope); + } + else + { + scope = push_array_no_zero(hs_tctx->arena, HS_Scope, 1); + } + MemoryZeroStruct(scope); + return scope; +} + +internal void +hs_scope_close(HS_Scope *scope) +{ + for(HS_Touch *touch = scope->top_touch, *next = 0; touch != 0; touch = next) + { + U128 hash = touch->hash; + next = touch->next; + U64 slot_idx = hash.u64[1]%hs_shared->slots_count; + U64 stripe_idx = slot_idx%hs_shared->stripes_count; + HS_Slot *slot = &hs_shared->slots[slot_idx]; + HS_Stripe *stripe = &hs_shared->stripes[stripe_idx]; + OS_MutexScopeR(stripe->rw_mutex) + { + for(HS_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(hash, n->hash)) + { + ins_atomic_u64_dec_eval(&n->scope_ref_count); + break; + } + } + } + SLLStackPush(hs_tctx->free_touch, touch); + } + SLLStackPush(hs_tctx->free_scope, scope); +} + +internal void +hs_scope_touch_node__stripe_r_guarded(HS_Scope *scope, HS_Node *node) +{ + HS_Touch *touch = hs_tctx->free_touch; + ins_atomic_u64_inc_eval(&node->scope_ref_count); + if(touch != 0) + { + SLLStackPop(hs_tctx->free_touch); + } + else + { + touch = push_array_no_zero(hs_tctx->arena, HS_Touch, 1); + } + MemoryZeroStruct(touch); + touch->hash = node->hash; + SLLStackPush(scope->top_touch, touch); +} + +//////////////////////////////// +//~ rjf: Cache Lookup + +internal U128 +hs_hash_from_key(U128 key, U64 rewind_count) +{ + U128 result = {0}; + U64 key_slot_idx = key.u64[1]%hs_shared->key_slots_count; + U64 key_stripe_idx = key_slot_idx%hs_shared->key_stripes_count; + HS_KeySlot *key_slot = &hs_shared->key_slots[key_slot_idx]; + HS_Stripe *key_stripe = &hs_shared->key_stripes[key_stripe_idx]; + OS_MutexScopeR(key_stripe->rw_mutex) + { + for(HS_KeyNode *n = key_slot->first; n != 0; n = n->next) + { + if(u128_match(n->key, key) && n->hash_history_gen > 0 && n->hash_history_gen-1 >= rewind_count) + { + result = n->hash_history[(n->hash_history_gen-1-rewind_count)%ArrayCount(n->hash_history)]; + break; + } + } + } + return result; +} + +internal String8 +hs_data_from_hash(HS_Scope *scope, U128 hash) +{ + String8 result = {0}; + U64 slot_idx = hash.u64[1]%hs_shared->slots_count; + U64 stripe_idx = slot_idx%hs_shared->stripes_count; + HS_Slot *slot = &hs_shared->slots[slot_idx]; + HS_Stripe *stripe = &hs_shared->stripes[stripe_idx]; + OS_MutexScopeR(stripe->rw_mutex) + { + for(HS_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(n->hash, hash)) + { + result = n->data; + hs_scope_touch_node__stripe_r_guarded(scope, n); + break; + } + } + } + return result; +} + +//////////////////////////////// +//~ rjf: Evictor Thread + +internal void +hs_evictor_thread__entry_point(void *p) +{ + for(;;) + { + for(U64 slot_idx = 0; slot_idx < hs_shared->slots_count; slot_idx += 1) + { + U64 stripe_idx = slot_idx%hs_shared->stripes_count; + HS_Slot *slot = &hs_shared->slots[slot_idx]; + HS_Stripe *stripe = &hs_shared->stripes[stripe_idx]; + B32 slot_has_work = 0; + OS_MutexScopeR(stripe->rw_mutex) + { + for(HS_Node *n = slot->first; n != 0; n = n->next) + { + U64 key_ref_count = ins_atomic_u64_eval(&n->key_ref_count); + U64 scope_ref_count = ins_atomic_u64_eval(&n->scope_ref_count); + if(key_ref_count == 0 && scope_ref_count == 0) + { + slot_has_work = 1; + break; + } + } + } + if(slot_has_work) OS_MutexScopeW(stripe->rw_mutex) + { + for(HS_Node *n = slot->first, *next = 0; n != 0; n = next) + { + next = n->next; + U64 key_ref_count = ins_atomic_u64_eval(&n->key_ref_count); + U64 scope_ref_count = ins_atomic_u64_eval(&n->scope_ref_count); + if(key_ref_count == 0 && scope_ref_count == 0) + { + DLLRemove(slot->first, slot->last, n); + SLLStackPush(hs_shared->stripes_free_nodes[stripe_idx], n); + arena_release(n->arena); + } + } + } + } + os_sleep_milliseconds(1000); + } +} diff --git a/src/lib_rdi_make/rdi_make.h b/src/lib_rdi_make/rdi_make.h index e2610ba0..0a8e7d9b 100644 --- a/src/lib_rdi_make/rdi_make.h +++ b/src/lib_rdi_make/rdi_make.h @@ -1,1519 +1,1532 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////////////////////////////////////// -// RAD Debug Info Make, (R)AD(D)BG(I) (M)ake Library -// -// Library for building loose data structures which contain -// RDI debug information, and baking that down into the -// proper flattened RDI format. -// -// Requires prior inclusion of the RAD Debug Info, (R)AD(D)BG(I) -// Format Library, in rdi_format.h. - -#ifndef RDI_MAKE_H -#define RDI_MAKE_H - -//////////////////////////////// -//~ rjf: Overrideable Memory Operations - -// To override the slow/default memset implementation used by the library, -// do the following: -// -// #define RDIM_MEMSET_OVERRIDE -// #define rdim_memset - -#if !defined(rdim_memset) -# define rdim_memset rdim_memset_fallback -#endif - -// To override the slow/default memcpy implementation used by the library, -// do the following: -// -// #define RDIM_MEMCPY_OVERRIDE -// #define rdim_memcpy - -#if !defined(rdim_memset) -# define rdim_memset rdim_memset_fallback -#endif - -#if !defined(rdim_memcpy) -# define rdim_memcpy rdim_memcpy_fallback -#endif - -//////////////////////////////// -//~ rjf: Overrideable sprintf Functions - -#if !defined(rdim_vsnprintf) -# include -# define rdim_vsnprintf vsnprintf -#endif - -//////////////////////////////// -//~ rjf: Overrideable String View Types - -// To override the string view type used by the library, do the following: -// -// #define RDIM_STRING8_OVERRIDE -// #define RDIM_String8 -// #define RDIM_String8_BaseMember -// #define RDIM_String8_SizeMember - -// To override the string view list type used by the library, do the following: -// -// #define RDIM_STRING8LIST_OVERRIDE -// #define RDIM_String8Node -// #define RDIM_String8_NextPtrMember -// #define RDIM_String8_StringMember -// #define RDIM_String8List -// #define RDIM_String8_FirstMember -// #define RDIM_String8_LastMember -// #define RDIM_String8_NodeCount -// #define RDIM_String8_TotalSizeMember - -#if !defined(RDIM_String8) -#define RDIM_String8 RDIM_String8 -#define RDIM_String8_BaseMember str -#define RDIM_String8_SizeMember size -typedef struct RDIM_String8 RDIM_String8; -struct RDIM_String8 -{ - RDI_U8 *str; - RDI_U64 size; -}; -#endif - -#if !defined(RDIM_String8Node) -#define RDIM_String8Node RDIM_String8Node -#define RDIM_String8Node_NextPtrMember next -#define RDIM_String8Node_StringMember string -typedef struct RDIM_String8Node RDIM_String8Node; -struct RDIM_String8Node -{ - RDIM_String8Node *next; - RDIM_String8 string; -}; -#endif - -#if !defined(RDIM_String8List) -#define RDIM_String8List RDIM_String8List -#define RDIM_String8List_FirstMember first -#define RDIM_String8List_LastMember last -#define RDIM_String8List_NodeCountMember node_count -#define RDIM_String8List_TotalSizeMember total_size -typedef struct RDIM_String8List RDIM_String8List; -struct RDIM_String8List -{ - RDIM_String8Node *first; - RDIM_String8Node *last; - RDI_U64 node_count; - RDI_U64 total_size; -}; -#endif - -typedef RDI_U32 RDIM_StringMatchFlags; -enum -{ - RDIM_StringMatchFlag_CaseInsensitive = (1<<0), -}; - -//////////////////////////////// -//~ rjf: Overrideable Arena Allocator Types - -// To override the arena allocator type used by the library, do the following: -// -// #define RDIM_ARENA_OVERRIDE -// #define RDIM_Arena -// #define rdim_arena_alloc Arena*> -// #define rdim_arena_release void> -// #define rdim_arena_pos U64> -// #define rdim_arena_push void*> -// #define rdim_arena_pop_to void> - -#if !defined(RDIM_Arena) -# define RDIM_Arena RDIM_Arena -typedef struct RDIM_Arena RDIM_Arena; -struct RDIM_Arena -{ - RDIM_Arena *prev; - RDIM_Arena *current; - RDI_U64 base_pos; - RDI_U64 pos; - RDI_U64 cmt; - RDI_U64 res; - RDI_U64 align; - RDI_S8 grow; -}; -#endif - -#if !defined(rdim_arena_alloc) -# define rdim_arena_alloc rdim_arena_alloc_fallback -#endif -#if !defined(rdim_arena_release) -# define rdim_arena_release rdim_arena_release_fallback -#endif -#if !defined(rdim_arena_pos) -# define rdim_arena_pos rdim_arena_pos_fallback -#endif -#if !defined(rdim_arena_push) -# define rdim_arena_push rdim_arena_push_fallback -#endif - -//////////////////////////////// -//~ rjf: Overrideable Thread-Local Scratch Arenas - -// To override the default thread-local scratch arenas used by the library, -// do the following: -// -// #define RDIM_SCRATCH_OVERRIDE -// #define RDIM_Temp arena implementation - must be (Temp) -> (Arena*)> -// #define rdim_scratch_begin Temp> -// #define rdim_scratch_end void - -#if !defined(RDIM_Temp) -# define RDIM_Temp RDIM_Temp -typedef struct RDIM_Temp RDIM_Temp; -struct RDIM_Temp -{ - RDIM_Arena *arena; - RDI_U64 pos; -}; -#define rdim_temp_arena(t) ((t).arena) -#endif - -#if !defined(rdim_scratch_begin) -# define rdim_scratch_begin rdim_scratch_begin_fallback -#endif -#if !defined(rdim_scratch_end) -# define rdim_scratch_end rdim_scratch_end_fallback -#endif - -//////////////////////////////// -//~ rjf: Overrideable Profile Markup - -// To override the default profiling markup, do the following: -// -// #define RDIM_ProfBegin(...) -// #define RDIM_ProfEnd() - -#if !defined(RDIM_ProfBegin) -# define RDIM_ProfBegin(...) ((void)0) -#endif -#if !defined(RDIM_ProfEnd) -# define RDIM_ProfEnd() ((void)0) -#endif - -#define RDIM_ProfScope(...) for(int _i_ = ((RDIM_ProfBegin(__VA_ARGS__)), 0); !_i_; _i_ += 1, (RDIM_ProfEnd())) - -//////////////////////////////// -//~ rjf: Linked List Helper Macros - -#define RDIM_CheckNil(nil,p) ((p) == 0 || (p) == nil) -#define RDIM_SetNil(nil,p) ((p) = nil) - -//- rjf: Base Doubly-Linked-List Macros -#define RDIM_DLLInsert_NPZ(nil,f,l,p,n,next,prev) (RDIM_CheckNil(nil,f) ? \ -((f) = (l) = (n), RDIM_SetNil(nil,(n)->next), RDIM_SetNil(nil,(n)->prev)) :\ -RDIM_CheckNil(nil,p) ? \ -((n)->next = (f), (f)->prev = (n), (f) = (n), RDIM_SetNil(nil,(n)->prev)) :\ -((p)==(l)) ? \ -((l)->next = (n), (n)->prev = (l), (l) = (n), RDIM_SetNil(nil, (n)->next)) :\ -(((!RDIM_CheckNil(nil,p) && RDIM_CheckNil(nil,(p)->next)) ? (0) : ((p)->next->prev = (n))), ((n)->next = (p)->next), ((p)->next = (n)), ((n)->prev = (p)))) -#define RDIM_DLLPushBack_NPZ(nil,f,l,n,next,prev) RDIM_DLLInsert_NPZ(nil,f,l,l,n,next,prev) -#define RDIM_DLLPushFront_NPZ(nil,f,l,n,next,prev) RDIM_DLLInsert_NPZ(nil,l,f,f,n,prev,next) -#define RDIM_DLLRemove_NPZ(nil,f,l,n,next,prev) (((n) == (f) ? (f) = (n)->next : (0)),\ -((n) == (l) ? (l) = (l)->prev : (0)),\ -(RDIM_CheckNil(nil,(n)->prev) ? (0) :\ -((n)->prev->next = (n)->next)),\ -(RDIM_CheckNil(nil,(n)->next) ? (0) :\ -((n)->next->prev = (n)->prev))) - -//- rjf: Base Singly-Linked-List Queue Macros -#define RDIM_SLLQueuePush_NZ(nil,f,l,n,next) (RDIM_CheckNil(nil,f)?\ -((f)=(l)=(n),RDIM_SetNil(nil,(n)->next)):\ -((l)->next=(n),(l)=(n),RDIM_SetNil(nil,(n)->next))) -#define RDIM_SLLQueuePushFront_NZ(nil,f,l,n,next) (RDIM_CheckNil(nil,f)?\ -((f)=(l)=(n),RDIM_SetNil(nil,(n)->next)):\ -((n)->next=(f),(f)=(n))) -#define RDIM_SLLQueuePop_NZ(nil,f,l,next) ((f)==(l)?\ -(RDIM_SetNil(nil,f), RDIM_SetNil(nil,l)):\ -((f)=(f)->next)) - -//- rjf: Base Singly-Linked-List Stack Macros -#define RDIM_SLLStackPush_N(f,n,next) ((n)->next=(f), (f)=(n)) -#define RDIM_SLLStackPop_N(f,next) ((f)=(f)->next) - -//////////////////////////////// -//~ rjf: Convenience Wrappers - -//- rjf: Doubly-Linked-List Wrappers -#define RDIM_DLLInsert_NP(f,l,p,n,next,prev) RDIM_DLLInsert_NPZ(0,f,l,p,n,next,prev) -#define RDIM_DLLPushBack_NP(f,l,n,next,prev) RDIM_DLLPushBack_NPZ(0,f,l,n,next,prev) -#define RDIM_DLLPushFront_NP(f,l,n,next,prev) RDIM_DLLPushFront_NPZ(0,f,l,n,next,prev) -#define RDIM_DLLRemove_NP(f,l,n,next,prev) RDIM_DLLRemove_NPZ(0,f,l,n,next,prev) -#define RDIM_DLLInsert(f,l,p,n) RDIM_DLLInsert_NPZ(0,f,l,p,n,next,prev) -#define RDIM_DLLPushBack(f,l,n) RDIM_DLLPushBack_NPZ(0,f,l,n,next,prev) -#define RDIM_DLLPushFront(f,l,n) RDIM_DLLPushFront_NPZ(0,f,l,n,next,prev) -#define RDIM_DLLRemove(f,l,n) RDIM_DLLRemove_NPZ(0,f,l,n,next,prev) - -//- rjf: Singly-Linked-List Queue Wrappers -#define RDIM_SLLQueuePush_N(f,l,n,next) RDIM_SLLQueuePush_NZ(0,f,l,n,next) -#define RDIM_SLLQueuePushFront_N(f,l,n,next) RDIM_SLLQueuePushFront_NZ(0,f,l,n,next) -#define RDIM_SLLQueuePop_N(f,l,next) RDIM_SLLQueuePop_NZ(0,f,l,next) -#define RDIM_SLLQueuePush(f,l,n) RDIM_SLLQueuePush_NZ(0,f,l,n,next) -#define RDIM_SLLQueuePushFront(f,l,n) RDIM_SLLQueuePushFront_NZ(0,f,l,n,next) -#define RDIM_SLLQueuePop(f,l) RDIM_SLLQueuePop_NZ(0,f,l,next) - -//- rjf: Singly-Linked-List Stack Wrappers -#define RDIM_SLLStackPush(f,n) RDIM_SLLStackPush_N(f,n,next) -#define RDIM_SLLStackPop(f) RDIM_SLLStackPop_N(f,next) - -//////////////////////////////// -//~ rjf: Helper Macros - -#if defined(_MSC_VER) -# define RDIM_THREAD_LOCAL __declspec(thread) -#elif defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) -# define RDIM_THREAD_LOCAL __thread -#else -# error RDIM_THREAD_LOCAL not defined for this compiler. -#endif - -#if defined(_MSC_VER) -# define rdim_trap() __debugbreak() -#elif defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) -# define rdim_trap() __builtin_trap() -#else -# error "rdim_trap not defined for this compiler." -#endif - -#define rdim_assert_always(x) do{if(!(x)) {rdim_trap();}}while(0) -#if !defined(NDEBUG) -# define rdim_assert(x) rdim_assert_always(x) -#else -# define rdim_assert(x) (void)(x) -#endif -#define rdim_noop ((void)0) - -//////////////////////////////// -//~ rjf: Auxiliary Data Structure Types - -//- rjf: 1-dimensional U64 ranges - -typedef struct RDIM_Rng1U64 RDIM_Rng1U64; -struct RDIM_Rng1U64 -{ - RDI_U64 min; - RDI_U64 max; -}; - -typedef struct RDIM_Rng1U64Node RDIM_Rng1U64Node; -struct RDIM_Rng1U64Node -{ - RDIM_Rng1U64Node *next; - RDIM_Rng1U64 v; -}; - -typedef struct RDIM_Rng1U64List RDIM_Rng1U64List; -struct RDIM_Rng1U64List -{ - RDIM_Rng1U64Node *first; - RDIM_Rng1U64Node *last; - RDI_U64 count; - RDI_U64 min; -}; - -//- rjf: u64 -> pointer map - -typedef struct RDIM_U64ToPtrNode RDIM_U64ToPtrNode; -struct RDIM_U64ToPtrNode -{ - RDIM_U64ToPtrNode *next; - RDI_U64 _padding_; - RDI_U64 key[1]; - void *ptr[1]; -}; - -typedef struct RDIM_U64ToPtrMap RDIM_U64ToPtrMap; -struct RDIM_U64ToPtrMap -{ - RDIM_U64ToPtrNode **buckets; - RDI_U64 buckets_count; - RDI_U64 bucket_collision_count; - RDI_U64 pair_count; -}; - -typedef struct RDIM_U64ToPtrLookup RDIM_U64ToPtrLookup; -struct RDIM_U64ToPtrLookup -{ - void *match; - RDIM_U64ToPtrNode *fill_node; - RDI_U32 fill_k; -}; - -//- rjf: string8 -> pointer map - -typedef struct RDIM_Str8ToPtrNode RDIM_Str8ToPtrNode; -struct RDIM_Str8ToPtrNode -{ - struct RDIM_Str8ToPtrNode *next; - RDIM_String8 key; - RDI_U64 hash; - void *ptr; -}; - -typedef struct RDIM_Str8ToPtrMap RDIM_Str8ToPtrMap; -struct RDIM_Str8ToPtrMap -{ - RDIM_Str8ToPtrNode **buckets; - RDI_U64 buckets_count; - RDI_U64 bucket_collision_count; - RDI_U64 pair_count; -}; - -//- rjf: sortable range data structure - -typedef struct RDIM_SortKey RDIM_SortKey; -struct RDIM_SortKey -{ - RDI_U64 key; - void *val; -}; - -typedef struct RDIM_OrderedRange RDIM_OrderedRange; -struct RDIM_OrderedRange -{ - RDIM_OrderedRange *next; - RDI_U64 first; - RDI_U64 opl; -}; - -//////////////////////////////// -//~ rjf: Error/Warning/Note Message Types - -typedef struct RDIM_Msg RDIM_Msg; -struct RDIM_Msg -{ - RDIM_Msg *next; - RDIM_String8 string; -}; - -typedef struct RDIM_MsgList RDIM_MsgList; -struct RDIM_MsgList -{ - RDIM_Msg *first; - RDIM_Msg *last; - RDI_U64 count; -}; - -//////////////////////////////// -//~ rjf: Top-Level Debug Info Types - -typedef struct RDIM_TopLevelInfo RDIM_TopLevelInfo; -struct RDIM_TopLevelInfo -{ - RDI_Arch arch; - RDIM_String8 exe_name; - RDI_U64 exe_hash; - RDI_U64 voff_max; - RDIM_String8 producer_name; -}; - -//////////////////////////////// -//~ rjf: Binary Section Types - -typedef struct RDIM_BinarySection RDIM_BinarySection; -struct RDIM_BinarySection -{ - RDIM_String8 name; - RDI_BinarySectionFlags flags; - RDI_U64 voff_first; - RDI_U64 voff_opl; - RDI_U64 foff_first; - RDI_U64 foff_opl; -}; - -typedef struct RDIM_BinarySectionNode RDIM_BinarySectionNode; -struct RDIM_BinarySectionNode -{ - RDIM_BinarySectionNode *next; - RDIM_BinarySection v; -}; - -typedef struct RDIM_BinarySectionList RDIM_BinarySectionList; -struct RDIM_BinarySectionList -{ - RDIM_BinarySectionNode *first; - RDIM_BinarySectionNode *last; - RDI_U64 count; -}; - -//////////////////////////////// -//~ rjf: Source File Info Types - -typedef struct RDIM_SrcFileLineMapFragment RDIM_SrcFileLineMapFragment; -struct RDIM_SrcFileLineMapFragment -{ - RDIM_SrcFileLineMapFragment *next; - struct RDIM_LineSequence *seq; -}; - -typedef struct RDIM_SrcFile RDIM_SrcFile; -struct RDIM_SrcFile -{ - struct RDIM_SrcFileChunkNode *chunk; - RDIM_String8 normal_full_path; - RDIM_SrcFileLineMapFragment *first_line_map_fragment; - RDIM_SrcFileLineMapFragment *last_line_map_fragment; -}; - -typedef struct RDIM_SrcFileChunkNode RDIM_SrcFileChunkNode; -struct RDIM_SrcFileChunkNode -{ - RDIM_SrcFileChunkNode *next; - RDIM_SrcFile *v; - RDI_U64 count; - RDI_U64 cap; - RDI_U64 base_idx; -}; - -typedef struct RDIM_SrcFileChunkList RDIM_SrcFileChunkList; -struct RDIM_SrcFileChunkList -{ - RDIM_SrcFileChunkNode *first; - RDIM_SrcFileChunkNode *last; - RDI_U64 chunk_count; - RDI_U64 total_count; - RDI_U64 source_line_map_count; - RDI_U64 total_line_count; -}; - -//////////////////////////////// -//~ rjf: Line Info Types - -typedef struct RDIM_LineSequence RDIM_LineSequence; -struct RDIM_LineSequence -{ - RDIM_SrcFile *src_file; - RDI_U64 *voffs; // [line_count + 1] (sorted) - RDI_U32 *line_nums; // [line_count] - RDI_U16 *col_nums; // [2*line_count] - RDI_U64 line_count; -}; - -typedef struct RDIM_LineSequenceNode RDIM_LineSequenceNode; -struct RDIM_LineSequenceNode -{ - RDIM_LineSequenceNode *next; - RDIM_LineSequence v; -}; - -typedef struct RDIM_LineTable RDIM_LineTable; -struct RDIM_LineTable -{ - struct RDIM_LineTableChunkNode *chunk; - RDIM_LineSequenceNode *first_seq; - RDIM_LineSequenceNode *last_seq; - RDI_U64 seq_count; - RDI_U64 line_count; - RDI_U64 col_count; -}; - -typedef struct RDIM_LineTableChunkNode RDIM_LineTableChunkNode; -struct RDIM_LineTableChunkNode -{ - RDIM_LineTableChunkNode *next; - RDIM_LineTable *v; - RDI_U64 count; - RDI_U64 cap; - RDI_U64 base_idx; -}; - -typedef struct RDIM_LineTableChunkList RDIM_LineTableChunkList; -struct RDIM_LineTableChunkList -{ - RDIM_LineTableChunkNode *first; - RDIM_LineTableChunkNode *last; - RDI_U64 chunk_count; - RDI_U64 total_count; - RDI_U64 total_seq_count; - RDI_U64 total_line_count; - RDI_U64 total_col_count; -}; - -//////////////////////////////// -//~ rjf: Per-Compilation-Unit Info Types - -typedef struct RDIM_Unit RDIM_Unit; -struct RDIM_Unit -{ - struct RDIM_UnitChunkNode *chunk; - RDIM_String8 unit_name; - RDIM_String8 compiler_name; - RDIM_String8 source_file; - RDIM_String8 object_file; - RDIM_String8 archive_file; - RDIM_String8 build_path; - RDI_Language language; - RDIM_LineTable *line_table; - RDIM_Rng1U64List voff_ranges; -}; - -typedef struct RDIM_UnitChunkNode RDIM_UnitChunkNode; -struct RDIM_UnitChunkNode -{ - RDIM_UnitChunkNode *next; - RDIM_Unit *v; - RDI_U64 count; - RDI_U64 cap; - RDI_U64 base_idx; -}; - -typedef struct RDIM_UnitChunkList RDIM_UnitChunkList; -struct RDIM_UnitChunkList -{ - RDIM_UnitChunkNode *first; - RDIM_UnitChunkNode *last; - RDI_U64 chunk_count; - RDI_U64 total_count; -}; - -//////////////////////////////// -//~ rjf: Type System Node Types - -typedef struct RDIM_Type RDIM_Type; -struct RDIM_Type -{ - struct RDIM_TypeChunkNode *chunk; - RDI_TypeKind kind; - RDI_U32 byte_size; - RDI_U32 flags; - RDI_U32 off; - RDI_U32 count; - RDIM_String8 name; - RDIM_Type *direct_type; - RDIM_Type **param_types; - struct RDIM_UDT *udt; -}; - -typedef struct RDIM_TypeChunkNode RDIM_TypeChunkNode; -struct RDIM_TypeChunkNode -{ - RDIM_TypeChunkNode *next; - RDIM_Type *v; - RDI_U64 count; - RDI_U64 cap; - RDI_U64 base_idx; -}; - -typedef struct RDIM_TypeChunkList RDIM_TypeChunkList; -struct RDIM_TypeChunkList -{ - RDIM_TypeChunkNode *first; - RDIM_TypeChunkNode *last; - RDI_U64 chunk_count; - RDI_U64 total_count; -}; - -//////////////////////////////// -//~ rjf: User-Defined-Type Info Types - -typedef struct RDIM_UDTMember RDIM_UDTMember; -struct RDIM_UDTMember -{ - RDIM_UDTMember *next; - RDI_MemberKind kind; - RDIM_String8 name; - RDIM_Type *type; - RDI_U32 off; -}; - -typedef struct RDIM_UDTEnumVal RDIM_UDTEnumVal; -struct RDIM_UDTEnumVal -{ - RDIM_UDTEnumVal *next; - RDIM_String8 name; - RDI_U64 val; -}; - -typedef struct RDIM_UDT RDIM_UDT; -struct RDIM_UDT -{ - struct RDIM_UDTChunkNode *chunk; - RDIM_Type *self_type; - RDIM_UDTMember *first_member; - RDIM_UDTMember *last_member; - RDIM_UDTEnumVal *first_enum_val; - RDIM_UDTEnumVal *last_enum_val; - RDI_U32 member_count; - RDI_U32 enum_val_count; - RDIM_SrcFile *src_file; - RDI_U32 line; - RDI_U32 col; -}; - -typedef struct RDIM_UDTChunkNode RDIM_UDTChunkNode; -struct RDIM_UDTChunkNode -{ - RDIM_UDTChunkNode *next; - RDIM_UDT *v; - RDI_U64 count; - RDI_U64 cap; - RDI_U64 base_idx; -}; - -typedef struct RDIM_UDTChunkList RDIM_UDTChunkList; -struct RDIM_UDTChunkList -{ - RDIM_UDTChunkNode *first; - RDIM_UDTChunkNode *last; - RDI_U64 chunk_count; - RDI_U64 total_count; - RDI_U64 total_member_count; - RDI_U64 total_enum_val_count; -}; - -//////////////////////////////// -//~ rjf: Location Info Types - -typedef struct RDIM_EvalBytecodeOp RDIM_EvalBytecodeOp; -struct RDIM_EvalBytecodeOp -{ - RDIM_EvalBytecodeOp *next; - RDI_EvalOp op; - RDI_U32 p_size; - RDI_U64 p; -}; - -typedef struct RDIM_EvalBytecode RDIM_EvalBytecode; -struct RDIM_EvalBytecode -{ - RDIM_EvalBytecodeOp *first_op; - RDIM_EvalBytecodeOp *last_op; - RDI_U32 op_count; - RDI_U32 encoded_size; -}; - -typedef struct RDIM_Location RDIM_Location; -struct RDIM_Location -{ - RDI_LocationKind kind; - RDI_U8 reg_code; - RDI_U16 offset; - RDIM_EvalBytecode bytecode; -}; - -typedef struct RDIM_LocationCase RDIM_LocationCase; -struct RDIM_LocationCase -{ - RDIM_LocationCase *next; - RDIM_Rng1U64 voff_range; - RDIM_Location *location; -}; - -typedef struct RDIM_LocationSet RDIM_LocationSet; -struct RDIM_LocationSet -{ - RDIM_LocationCase *first_location_case; - RDIM_LocationCase *last_location_case; - RDI_U64 location_case_count; -}; - -//////////////////////////////// -//~ rjf: Symbol Info Types - -typedef enum RDIM_SymbolKind -{ - RDIM_SymbolKind_NULL, - RDIM_SymbolKind_GlobalVariable, - RDIM_SymbolKind_ThreadVariable, - RDIM_SymbolKind_Procedure, - RDIM_SymbolKind_COUNT -} -RDIM_SymbolKind; - -typedef struct RDIM_Symbol RDIM_Symbol; -struct RDIM_Symbol -{ - struct RDIM_SymbolChunkNode *chunk; - RDI_S32 is_extern; - RDIM_String8 name; - RDIM_String8 link_name; - RDIM_Type *type; - RDI_U64 offset; - RDIM_Symbol *container_symbol; - RDIM_Type *container_type; - struct RDIM_Scope *root_scope; -}; - -typedef struct RDIM_SymbolChunkNode RDIM_SymbolChunkNode; -struct RDIM_SymbolChunkNode -{ - RDIM_SymbolChunkNode *next; - RDIM_Symbol *v; - RDI_U64 count; - RDI_U64 cap; - RDI_U64 base_idx; -}; - -typedef struct RDIM_SymbolChunkList RDIM_SymbolChunkList; -struct RDIM_SymbolChunkList -{ - RDIM_SymbolChunkNode *first; - RDIM_SymbolChunkNode *last; - RDI_U64 chunk_count; - RDI_U64 total_count; -}; - -//////////////////////////////// -//~ rjf: Inline Site Info Types - -typedef struct RDIM_InlineSite RDIM_InlineSite; -struct RDIM_InlineSite -{ - struct RDIM_InlineSiteChunkNode *chunk; - RDIM_String8 name; - RDIM_Type *type; - RDIM_Type *owner; - RDIM_LineTable *line_table; -}; - -typedef struct RDIM_InlineSiteChunkNode RDIM_InlineSiteChunkNode; -struct RDIM_InlineSiteChunkNode -{ - RDIM_InlineSiteChunkNode *next; - RDIM_InlineSite *v; - RDI_U64 count; - RDI_U64 cap; - RDI_U64 base_idx; -}; - -typedef struct RDIM_InlineSiteChunkList RDIM_InlineSiteChunkList; -struct RDIM_InlineSiteChunkList -{ - RDIM_InlineSiteChunkNode *first; - RDIM_InlineSiteChunkNode *last; - RDI_U64 chunk_count; - RDI_U64 total_count; -}; - -//////////////////////////////// -//~ rjf: Scope Info Types - -typedef struct RDIM_Local RDIM_Local; -struct RDIM_Local -{ - RDIM_Local *next; - RDI_LocalKind kind; - RDIM_String8 name; - RDIM_Type *type; - RDIM_LocationSet locset; -}; - -typedef struct RDIM_Scope RDIM_Scope; -struct RDIM_Scope -{ - struct RDIM_ScopeChunkNode *chunk; - RDIM_Symbol *symbol; - RDIM_Scope *parent_scope; - RDIM_Scope *first_child; - RDIM_Scope *last_child; - RDIM_Scope *next_sibling; - RDIM_Rng1U64List voff_ranges; - RDIM_Local *first_local; - RDIM_Local *last_local; - RDI_U32 local_count; - RDIM_InlineSite *inline_site; -}; - -typedef struct RDIM_ScopeChunkNode RDIM_ScopeChunkNode; -struct RDIM_ScopeChunkNode -{ - RDIM_ScopeChunkNode *next; - RDIM_Scope *v; - RDI_U64 count; - RDI_U64 cap; - RDI_U64 base_idx; -}; - -typedef struct RDIM_ScopeChunkList RDIM_ScopeChunkList; -struct RDIM_ScopeChunkList -{ - RDIM_ScopeChunkNode *first; - RDIM_ScopeChunkNode *last; - RDI_U64 chunk_count; - RDI_U64 total_count; - RDI_U64 scope_voff_count; - RDI_U64 local_count; - RDI_U64 location_count; -}; - -//////////////////////////////// -//~ rjf: Baking Types - -//- rjf: baking parameters - -typedef struct RDIM_BakeParams RDIM_BakeParams; -struct RDIM_BakeParams -{ - RDIM_TopLevelInfo top_level_info; - RDIM_BinarySectionList binary_sections; - RDIM_UnitChunkList units; - RDIM_TypeChunkList types; - RDIM_UDTChunkList udts; - RDIM_SrcFileChunkList src_files; - RDIM_LineTableChunkList line_tables; - RDIM_SymbolChunkList global_variables; - RDIM_SymbolChunkList thread_variables; - RDIM_SymbolChunkList procedures; - RDIM_ScopeChunkList scopes; - RDIM_InlineSiteChunkList inline_sites; -}; - -//- rjf: data sections - -typedef struct RDIM_BakeSection RDIM_BakeSection; -struct RDIM_BakeSection -{ - void *data; - RDI_SectionEncoding encoding; - RDI_U64 encoded_size; - RDI_U64 unpacked_size; - RDI_SectionKind tag; - RDI_U64 tag_idx; -}; - -typedef struct RDIM_BakeSectionNode RDIM_BakeSectionNode; -struct RDIM_BakeSectionNode -{ - RDIM_BakeSectionNode *next; - RDIM_BakeSection v; -}; - -typedef struct RDIM_BakeSectionList RDIM_BakeSectionList; -struct RDIM_BakeSectionList -{ - RDIM_BakeSectionNode *first; - RDIM_BakeSectionNode *last; - RDI_U64 count; -}; - -//- rjf: interned string type - -typedef struct RDIM_BakeString RDIM_BakeString; -struct RDIM_BakeString -{ - RDI_U64 hash; - RDIM_String8 string; -}; - -typedef struct RDIM_BakeStringChunkNode RDIM_BakeStringChunkNode; -struct RDIM_BakeStringChunkNode -{ - RDIM_BakeStringChunkNode *next; - RDIM_BakeString *v; - RDI_U64 count; - RDI_U64 cap; - RDI_U64 base_idx; -}; - -typedef struct RDIM_BakeStringChunkList RDIM_BakeStringChunkList; -struct RDIM_BakeStringChunkList -{ - RDIM_BakeStringChunkNode *first; - RDIM_BakeStringChunkNode *last; - RDI_U64 chunk_count; - RDI_U64 total_count; -}; - -typedef struct RDIM_BakeStringMapTopology RDIM_BakeStringMapTopology; -struct RDIM_BakeStringMapTopology -{ - RDI_U64 slots_count; -}; - -typedef struct RDIM_BakeStringMapBaseIndices RDIM_BakeStringMapBaseIndices; -struct RDIM_BakeStringMapBaseIndices -{ - RDI_U64 *slots_base_idxs; -}; - -typedef struct RDIM_BakeStringMapLoose RDIM_BakeStringMapLoose; -struct RDIM_BakeStringMapLoose -{ - RDIM_BakeStringChunkList **slots; -}; - -typedef struct RDIM_BakeStringMapTight RDIM_BakeStringMapTight; -struct RDIM_BakeStringMapTight -{ - RDIM_BakeStringChunkList *slots; - RDI_U64 *slots_base_idxs; - RDI_U64 slots_count; - RDI_U64 total_count; -}; - -//- rjf: index runs - -typedef struct RDIM_BakeIdxRunNode RDIM_BakeIdxRunNode; -struct RDIM_BakeIdxRunNode -{ - RDIM_BakeIdxRunNode *hash_next; - RDIM_BakeIdxRunNode *order_next; - RDI_U32 *idx_run; - RDI_U64 hash; - RDI_U32 count; - RDI_U32 first_idx; -}; - -typedef struct RDIM_BakeIdxRunMap RDIM_BakeIdxRunMap; -struct RDIM_BakeIdxRunMap -{ - RDIM_BakeIdxRunNode *order_first; - RDIM_BakeIdxRunNode *order_last; - RDIM_BakeIdxRunNode **slots; - RDI_U64 slots_count; - RDI_U64 slot_collision_count; - RDI_U32 count; - RDI_U32 idx_count; -}; - -//- rjf: source info & path tree - -typedef struct RDIM_BakePathNode RDIM_BakePathNode; -struct RDIM_BakePathNode -{ - RDIM_BakePathNode *next_order; - RDIM_BakePathNode *parent; - RDIM_BakePathNode *first_child; - RDIM_BakePathNode *last_child; - RDIM_BakePathNode *next_sibling; - RDIM_String8 name; - RDIM_SrcFile *src_file; - RDI_U32 idx; -}; - -typedef struct RDIM_BakeLineMapFragment RDIM_BakeLineMapFragment; -struct RDIM_BakeLineMapFragment -{ - RDIM_BakeLineMapFragment *next; - RDIM_LineSequence *seq; -}; - -typedef struct RDIM_BakePathTree RDIM_BakePathTree; -struct RDIM_BakePathTree -{ - RDIM_BakePathNode root; - RDIM_BakePathNode *first; - RDIM_BakePathNode *last; - RDI_U32 count; -}; - -//- rjf: name maps - -typedef struct RDIM_BakeNameMapValNode RDIM_BakeNameMapValNode; -struct RDIM_BakeNameMapValNode -{ - RDIM_BakeNameMapValNode *next; - RDI_U32 val[6]; -}; - -typedef struct RDIM_BakeNameMapNode RDIM_BakeNameMapNode; -struct RDIM_BakeNameMapNode -{ - RDIM_BakeNameMapNode *slot_next; - RDIM_BakeNameMapNode *order_next; - RDIM_String8 string; - RDIM_BakeNameMapValNode *val_first; - RDIM_BakeNameMapValNode *val_last; - RDI_U64 val_count; -}; - -typedef struct RDIM_BakeNameMap RDIM_BakeNameMap; -struct RDIM_BakeNameMap -{ - RDIM_BakeNameMapNode **slots; - RDI_U64 slots_count; - RDI_U64 slot_collision_count; - RDIM_BakeNameMapNode *first; - RDIM_BakeNameMapNode *last; - RDI_U64 name_count; -}; - -//- rjf: vmaps - -typedef struct RDIM_BakeVMap RDIM_BakeVMap; -struct RDIM_BakeVMap -{ - RDI_VMapEntry *vmap; // [count + 1] - RDI_U32 count; -}; - -typedef struct RDIM_VMapMarker RDIM_VMapMarker; -struct RDIM_VMapMarker -{ - RDI_U32 idx; - RDI_U32 begin_range; -}; - -//- rjf: baking results - -typedef struct RDIM_TopLevelInfoBakeResult RDIM_TopLevelInfoBakeResult; -struct RDIM_TopLevelInfoBakeResult -{ - RDI_TopLevelInfo *top_level_info; -}; - -typedef struct RDIM_BinarySectionBakeResult RDIM_BinarySectionBakeResult; -struct RDIM_BinarySectionBakeResult -{ - RDI_BinarySection *binary_sections; - RDI_U64 binary_sections_count; -}; - -typedef struct RDIM_UnitBakeResult RDIM_UnitBakeResult; -struct RDIM_UnitBakeResult -{ - RDI_Unit *units; - RDI_U64 units_count; -}; - -typedef struct RDIM_UnitVMapBakeResult RDIM_UnitVMapBakeResult; -struct RDIM_UnitVMapBakeResult -{ - RDIM_BakeVMap vmap; -}; - -typedef struct RDIM_SrcFileBakeResult RDIM_SrcFileBakeResult; -struct RDIM_SrcFileBakeResult -{ - RDI_SourceFile *source_files; - RDI_U64 source_files_count; - RDI_SourceLineMap *source_line_maps; - RDI_U64 source_line_maps_count; - RDI_U32 *source_line_map_nums; - RDI_U32 *source_line_map_rngs; - RDI_U64 *source_line_map_voffs; - RDI_U64 source_line_map_nums_count; - RDI_U64 source_line_map_rngs_count; - RDI_U64 source_line_map_voffs_count; -}; - -typedef struct RDIM_LineTableBakeResult RDIM_LineTableBakeResult; -struct RDIM_LineTableBakeResult -{ - RDI_LineTable *line_tables; - RDI_U64 line_tables_count; - RDI_U64 *line_table_voffs; - RDI_U64 line_table_voffs_count; - RDI_Line *line_table_lines; - RDI_U64 line_table_lines_count; - RDI_Column *line_table_columns; - RDI_U64 line_table_columns_count; -}; - -typedef struct RDIM_TypeNodeBakeResult RDIM_TypeNodeBakeResult; -struct RDIM_TypeNodeBakeResult -{ - RDI_TypeNode *type_nodes; - RDI_U64 type_nodes_count; -}; - -typedef struct RDIM_UDTBakeResult RDIM_UDTBakeResult; -struct RDIM_UDTBakeResult -{ - RDI_UDT *udts; - RDI_U64 udts_count; - RDI_Member *members; - RDI_U64 members_count; - RDI_EnumMember *enum_members; - RDI_U64 enum_members_count; -}; - -typedef struct RDIM_GlobalVariableBakeResult RDIM_GlobalVariableBakeResult; -struct RDIM_GlobalVariableBakeResult -{ - RDI_GlobalVariable *global_variables; - RDI_U64 global_variables_count; -}; - -typedef struct RDIM_GlobalVMapBakeResult RDIM_GlobalVMapBakeResult; -struct RDIM_GlobalVMapBakeResult -{ - RDIM_BakeVMap vmap; -}; - -typedef struct RDIM_ThreadVariableBakeResult RDIM_ThreadVariableBakeResult; -struct RDIM_ThreadVariableBakeResult -{ - RDI_ThreadVariable *thread_variables; - RDI_U64 thread_variables_count; -}; - -typedef struct RDIM_ProcedureBakeResult RDIM_ProcedureBakeResult; -struct RDIM_ProcedureBakeResult -{ - RDI_Procedure *procedures; - RDI_U64 procedures_count; -}; - -typedef struct RDIM_ScopeBakeResult RDIM_ScopeBakeResult; -struct RDIM_ScopeBakeResult -{ - RDI_Scope *scopes; - RDI_U64 scopes_count; - RDI_U64 *scope_voffs; - RDI_U64 scope_voffs_count; - RDI_Local *locals; - RDI_U64 locals_count; - RDI_LocationBlock *location_blocks; - RDI_U64 location_blocks_count; - RDI_U8 *location_data; - RDI_U64 location_data_size; -}; - -typedef struct RDIM_ScopeVMapBakeResult RDIM_ScopeVMapBakeResult; -struct RDIM_ScopeVMapBakeResult -{ - RDIM_BakeVMap vmap; -}; - -typedef struct RDIM_InlineSiteBakeResult RDIM_InlineSiteBakeResult; -struct RDIM_InlineSiteBakeResult -{ - RDI_InlineSite *inline_sites; - RDI_U64 inline_sites_count; -}; - -typedef struct RDIM_TopLevelNameMapBakeResult RDIM_TopLevelNameMapBakeResult; -struct RDIM_TopLevelNameMapBakeResult -{ - RDI_NameMap *name_maps; - RDI_U64 name_maps_count; -}; - -typedef struct RDIM_NameMapBakeResult RDIM_NameMapBakeResult; -struct RDIM_NameMapBakeResult -{ - RDI_NameMapBucket *buckets; - RDI_U64 buckets_count; - RDI_NameMapNode *nodes; - RDI_U64 nodes_count; -}; - -typedef struct RDIM_FilePathBakeResult RDIM_FilePathBakeResult; -struct RDIM_FilePathBakeResult -{ - RDI_FilePathNode *nodes; - RDI_U64 nodes_count; -}; - -typedef struct RDIM_StringBakeResult RDIM_StringBakeResult; -struct RDIM_StringBakeResult -{ - RDI_U32 *string_offs; - RDI_U64 string_offs_count; - RDI_U8 *string_data; - RDI_U64 string_data_size; -}; - -typedef struct RDIM_IndexRunBakeResult RDIM_IndexRunBakeResult; -struct RDIM_IndexRunBakeResult -{ - RDI_U32 *idx_runs; - RDI_U64 idx_count; -}; - -typedef struct RDIM_BakeResults RDIM_BakeResults; -struct RDIM_BakeResults -{ - RDIM_TopLevelInfoBakeResult top_level_info; - RDIM_BinarySectionBakeResult binary_sections; - RDIM_UnitBakeResult units; - RDIM_UnitVMapBakeResult unit_vmap; - RDIM_SrcFileBakeResult src_files; - RDIM_LineTableBakeResult line_tables; - RDIM_TypeNodeBakeResult type_nodes; - RDIM_UDTBakeResult udts; - RDIM_GlobalVariableBakeResult global_variables; - RDIM_GlobalVMapBakeResult global_vmap; - RDIM_ThreadVariableBakeResult thread_variables; - RDIM_ProcedureBakeResult procedures; - RDIM_ScopeBakeResult scopes; - RDIM_InlineSiteBakeResult inline_sites; - RDIM_ScopeVMapBakeResult scope_vmap; - RDIM_TopLevelNameMapBakeResult top_level_name_maps; - RDIM_NameMapBakeResult name_maps; - RDIM_FilePathBakeResult file_paths; - RDIM_StringBakeResult strings; - RDIM_IndexRunBakeResult idx_runs; -}; - -//////////////////////////////// -//~ rjf: Serialization Types - -typedef struct RDIM_SerializedSection RDIM_SerializedSection; -struct RDIM_SerializedSection -{ - void *data; - RDI_U64 encoded_size; - RDI_U64 unpacked_size; - RDI_SectionEncoding encoding; -}; - -typedef struct RDIM_SerializedSectionBundle RDIM_SerializedSectionBundle; -struct RDIM_SerializedSectionBundle -{ - RDIM_SerializedSection sections[RDI_SectionKind_COUNT]; -}; - -//////////////////////////////// -//~ rjf: Basic Helpers - -//- rjf: memory operations -#if !defined(RDIM_MEMSET_OVERRIDE) -RDI_PROC void *rdim_memset_fallback(void *dst, RDI_U8 c, RDI_U64 size); -#endif -#if !defined(RDIM_MEMCPY_OVERRIDE) -RDI_PROC void *rdim_memcpy_fallback(void *dst, void *src, RDI_U64 size); -#endif -#define rdim_memzero(ptr, size) rdim_memset((ptr), 0, (size)) -#define rdim_memzero_struct(ptr) rdim_memset((ptr), 0, sizeof(*(ptr))) -#define rdim_memcpy_struct(dst, src) rdim_memcpy((dst), (src), sizeof(*(dst))) - -//- rjf: arenas -#if !defined(RDIM_ARENA_OVERRIDE) -RDI_PROC RDIM_Arena *rdim_arena_alloc_fallback(void); -RDI_PROC void rdim_arena_release_fallback(RDIM_Arena *arena); -RDI_PROC RDI_U64 rdim_arena_pos_fallback(RDIM_Arena *arena); -RDI_PROC void *rdim_arena_push_fallback(RDIM_Arena *arena, RDI_U64 size); -RDI_PROC void rdim_arena_pop_to_fallback(RDIM_Arena *arena, RDI_U64 pos); -#endif -#define rdim_push_array_no_zero(a,T,c) (T*)rdim_arena_push((a), sizeof(T)*(c)) -#define rdim_push_array(a,T,c) (T*)rdim_memzero(rdim_push_array_no_zero(a,T,c), sizeof(T)*(c)) - -//- rjf: thread-local scratch arenas -#if !defined (RDIM_SCRATCH_OVERRIDE) -RDI_PROC RDIM_Temp rdim_scratch_begin_fallback(RDIM_Arena **conflicts, RDI_U64 conflicts_count); -RDI_PROC void rdim_scratch_end_fallback(RDIM_Temp temp); -#endif - -//- rjf: strings -RDI_PROC RDIM_String8 rdim_str8(RDI_U8 *str, RDI_U64 size); -RDI_PROC RDIM_String8 rdim_str8_copy(RDIM_Arena *arena, RDIM_String8 src); -RDI_PROC RDIM_String8 rdim_str8f(RDIM_Arena *arena, char *fmt, ...); -RDI_PROC RDIM_String8 rdim_str8fv(RDIM_Arena *arena, char *fmt, va_list args); -RDI_PROC RDI_S32 rdim_str8_match(RDIM_String8 a, RDIM_String8 b, RDIM_StringMatchFlags flags); -#define rdim_str8_lit(S) rdim_str8((RDI_U8*)(S), sizeof(S) - 1) -#define rdim_str8_struct(S) rdim_str8((RDI_U8*)(S), sizeof(*(S))) -#define rdim_str8_struct_array(S, C) rdim_str8((RDI_U8*)(S), sizeof(*(S)) * (C)) - -//- rjf: string lists -RDI_PROC void rdim_str8_list_push(RDIM_Arena *arena, RDIM_String8List *list, RDIM_String8 string); -RDI_PROC void rdim_str8_list_push_front(RDIM_Arena *arena, RDIM_String8List *list, RDIM_String8 string); -RDI_PROC void rdim_str8_list_push_align(RDIM_Arena *arena, RDIM_String8List *list, RDI_U64 align); -RDI_PROC RDIM_String8 rdim_str8_list_join(RDIM_Arena *arena, RDIM_String8List *list, RDIM_String8 sep); - -//- rjf: sortable range sorting -RDI_PROC RDIM_SortKey *rdim_sort_key_array(RDIM_Arena *arena, RDIM_SortKey *keys, RDI_U64 count); - -//- rjf: rng1u64 list -RDI_PROC void rdim_rng1u64_list_push(RDIM_Arena *arena, RDIM_Rng1U64List *list, RDIM_Rng1U64 r); - -//////////////////////////////// -//~ rjf: [Building] Binary Section Info Building - -RDI_PROC RDIM_BinarySection *rdim_binary_section_list_push(RDIM_Arena *arena, RDIM_BinarySectionList *list); - -//////////////////////////////// -//~ rjf: [Building] Source File Info Building - -RDI_PROC RDIM_SrcFile *rdim_src_file_chunk_list_push(RDIM_Arena *arena, RDIM_SrcFileChunkList *list, RDI_U64 cap); -RDI_PROC RDI_U64 rdim_idx_from_src_file(RDIM_SrcFile *src_file); -RDI_PROC void rdim_src_file_chunk_list_concat_in_place(RDIM_SrcFileChunkList *dst, RDIM_SrcFileChunkList *to_push); -RDI_PROC void rdim_src_file_push_line_sequence(RDIM_Arena *arena, RDIM_SrcFileChunkList *src_files, RDIM_SrcFile *src_file, RDIM_LineSequence *seq); - -//////////////////////////////// -//~ rjf: [Building] Line Info Building - -RDI_PROC RDIM_LineTable *rdim_line_table_chunk_list_push(RDIM_Arena *arena, RDIM_LineTableChunkList *list, RDI_U64 cap); -RDI_PROC RDI_U64 rdim_idx_from_line_table(RDIM_LineTable *line_table); -RDI_PROC void rdim_line_table_chunk_list_concat_in_place(RDIM_LineTableChunkList *dst, RDIM_LineTableChunkList *to_push); -RDI_PROC RDIM_LineSequence *rdim_line_table_push_sequence(RDIM_Arena *arena, RDIM_LineTableChunkList *line_tables, RDIM_LineTable *line_table, RDIM_SrcFile *src_file, RDI_U64 *voffs, RDI_U32 *line_nums, RDI_U16 *col_nums, RDI_U64 line_count); - -//////////////////////////////// -//~ rjf: [Building] Unit Info Building - -RDI_PROC RDIM_Unit *rdim_unit_chunk_list_push(RDIM_Arena *arena, RDIM_UnitChunkList *list, RDI_U64 cap); -RDI_PROC RDI_U64 rdim_idx_from_unit(RDIM_Unit *unit); -RDI_PROC void rdim_unit_chunk_list_concat_in_place(RDIM_UnitChunkList *dst, RDIM_UnitChunkList *to_push); - -//////////////////////////////// -//~ rjf: [Building] Type Info & UDT Building - -RDI_PROC RDIM_Type *rdim_type_chunk_list_push(RDIM_Arena *arena, RDIM_TypeChunkList *list, RDI_U64 cap); -RDI_PROC RDI_U64 rdim_idx_from_type(RDIM_Type *type); -RDI_PROC void rdim_type_chunk_list_concat_in_place(RDIM_TypeChunkList *dst, RDIM_TypeChunkList *to_push); -RDI_PROC RDIM_UDT *rdim_udt_chunk_list_push(RDIM_Arena *arena, RDIM_UDTChunkList *list, RDI_U64 cap); -RDI_PROC RDI_U64 rdim_idx_from_udt(RDIM_UDT *udt); -RDI_PROC void rdim_udt_chunk_list_concat_in_place(RDIM_UDTChunkList *dst, RDIM_UDTChunkList *to_push); -RDI_PROC RDIM_UDTMember *rdim_udt_push_member(RDIM_Arena *arena, RDIM_UDTChunkList *list, RDIM_UDT *udt); -RDI_PROC RDIM_UDTEnumVal *rdim_udt_push_enum_val(RDIM_Arena *arena, RDIM_UDTChunkList *list, RDIM_UDT *udt); - -//////////////////////////////// -//~ rjf: [Building] Symbol Info Building - -RDI_PROC RDIM_Symbol *rdim_symbol_chunk_list_push(RDIM_Arena *arena, RDIM_SymbolChunkList *list, RDI_U64 cap); -RDI_PROC RDI_U64 rdim_idx_from_symbol(RDIM_Symbol *symbol); -RDI_PROC void rdim_symbol_chunk_list_concat_in_place(RDIM_SymbolChunkList *dst, RDIM_SymbolChunkList *to_push); - -//////////////////////////////// -//~ rjf: [Building] Inline Site Info Building - -RDI_PROC RDIM_InlineSite *rdim_inline_site_chunk_list_push(RDIM_Arena *arena, RDIM_InlineSiteChunkList *list, RDI_U64 cap); -RDI_PROC RDI_U64 rdim_idx_from_inline_site(RDIM_InlineSite *inline_site); -RDI_PROC void rdim_inline_site_chunk_list_concat_in_place(RDIM_InlineSiteChunkList *dst, RDIM_InlineSiteChunkList *to_push); - -//////////////////////////////// -//~ rjf: [Building] Scope Info Building - -//- rjf: scopes -RDI_PROC RDIM_Scope *rdim_scope_chunk_list_push(RDIM_Arena *arena, RDIM_ScopeChunkList *list, RDI_U64 cap); -RDI_PROC RDI_U64 rdim_idx_from_scope(RDIM_Scope *scope); -RDI_PROC void rdim_scope_chunk_list_concat_in_place(RDIM_ScopeChunkList *dst, RDIM_ScopeChunkList *to_push); -RDI_PROC void rdim_scope_push_voff_range(RDIM_Arena *arena, RDIM_ScopeChunkList *list, RDIM_Scope *scope, RDIM_Rng1U64 range); -RDI_PROC RDIM_Local *rdim_scope_push_local(RDIM_Arena *arena, RDIM_ScopeChunkList *scopes, RDIM_Scope *scope); - -//- rjf: bytecode -RDI_PROC void rdim_bytecode_push_op(RDIM_Arena *arena, RDIM_EvalBytecode *bytecode, RDI_EvalOp op, RDI_U64 p); -RDI_PROC void rdim_bytecode_push_uconst(RDIM_Arena *arena, RDIM_EvalBytecode *bytecode, RDI_U64 x); -RDI_PROC void rdim_bytecode_push_sconst(RDIM_Arena *arena, RDIM_EvalBytecode *bytecode, RDI_S64 x); -RDI_PROC void rdim_bytecode_concat_in_place(RDIM_EvalBytecode *left_dst, RDIM_EvalBytecode *right_destroyed); - -//- rjf: individual locations -RDI_PROC RDIM_Location *rdim_push_location_addr_bytecode_stream(RDIM_Arena *arena, RDIM_EvalBytecode *bytecode); -RDI_PROC RDIM_Location *rdim_push_location_val_bytecode_stream(RDIM_Arena *arena, RDIM_EvalBytecode *bytecode); -RDI_PROC RDIM_Location *rdim_push_location_addr_reg_plus_u16(RDIM_Arena *arena, RDI_U8 reg_code, RDI_U16 offset); -RDI_PROC RDIM_Location *rdim_push_location_addr_addr_reg_plus_u16(RDIM_Arena *arena, RDI_U8 reg_code, RDI_U16 offset); -RDI_PROC RDIM_Location *rdim_push_location_val_reg(RDIM_Arena *arena, RDI_U8 reg_code); - -//- rjf: location sets -RDI_PROC void rdim_location_set_push_case(RDIM_Arena *arena, RDIM_ScopeChunkList *scopes, RDIM_LocationSet *locset, RDIM_Rng1U64 voff_range, RDIM_Location *location); - -//////////////////////////////// -//~ rjf: [Baking Helpers] Baked VMap Building - -RDI_PROC RDIM_BakeVMap rdim_bake_vmap_from_markers(RDIM_Arena *arena, RDIM_VMapMarker *markers, RDIM_SortKey *keys, RDI_U64 marker_count); - -//////////////////////////////// -//~ rjf: [Baking Helpers] Interned / Deduplicated Blob Data Structure Helpers - -//- rjf: bake string chunk lists -RDI_PROC RDIM_BakeString *rdim_bake_string_chunk_list_push(RDIM_Arena *arena, RDIM_BakeStringChunkList *list, RDI_U64 cap); -RDI_PROC void rdim_bake_string_chunk_list_concat_in_place(RDIM_BakeStringChunkList *dst, RDIM_BakeStringChunkList *to_push); -RDI_PROC RDIM_BakeStringChunkList rdim_bake_string_chunk_list_sorted_from_unsorted(RDIM_Arena *arena, RDIM_BakeStringChunkList *src); - -//- rjf: bake string chunk list maps -RDI_PROC RDIM_BakeStringMapLoose *rdim_bake_string_map_loose_make(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top); -RDI_PROC void rdim_bake_string_map_loose_insert(RDIM_Arena *arena, RDIM_BakeStringMapTopology *map_topology, RDIM_BakeStringMapLoose *map, RDI_U64 chunk_cap, RDIM_String8 string); -RDI_PROC void rdim_bake_string_map_loose_join_in_place(RDIM_BakeStringMapTopology *map_topology, RDIM_BakeStringMapLoose *dst, RDIM_BakeStringMapLoose *src); -RDI_PROC RDIM_BakeStringMapBaseIndices rdim_bake_string_map_base_indices_from_map_loose(RDIM_Arena *arena, RDIM_BakeStringMapTopology *map_topology, RDIM_BakeStringMapLoose *map); - -//- rjf: finalized bake string map -RDI_PROC RDIM_BakeStringMapTight rdim_bake_string_map_tight_from_loose(RDIM_Arena *arena, RDIM_BakeStringMapTopology *map_topology, RDIM_BakeStringMapBaseIndices *map_base_indices, RDIM_BakeStringMapLoose *map); -RDI_PROC RDI_U32 rdim_bake_idx_from_string(RDIM_BakeStringMapTight *map, RDIM_String8 string); - -//- rjf: bake idx run map reading/writing -RDI_PROC RDI_U64 rdim_hash_from_idx_run(RDI_U32 *idx_run, RDI_U32 count); -RDI_PROC RDI_U32 rdim_bake_idx_from_idx_run(RDIM_BakeIdxRunMap *map, RDI_U32 *idx_run, RDI_U32 count); -RDI_PROC RDI_U32 rdim_bake_idx_run_map_insert(RDIM_Arena *arena, RDIM_BakeIdxRunMap *map, RDI_U32 *idx_run, RDI_U32 count); - -//- rjf: bake path tree reading/writing -RDI_PROC RDIM_BakePathNode *rdim_bake_path_node_from_string(RDIM_BakePathTree *tree, RDIM_String8 string); -RDI_PROC RDI_U32 rdim_bake_path_node_idx_from_string(RDIM_BakePathTree *tree, RDIM_String8 string); -RDI_PROC RDIM_BakePathNode *rdim_bake_path_tree_insert(RDIM_Arena *arena, RDIM_BakePathTree *tree, RDIM_String8 string); - -//- rjf: bake name maps writing -RDI_PROC void rdim_bake_name_map_push(RDIM_Arena *arena, RDIM_BakeNameMap *map, RDIM_String8 string, RDI_U32 idx); - -//////////////////////////////// -//~ rjf: [Baking Helpers] Data Section List Building Helpers - -RDI_PROC RDIM_BakeSection *rdim_bake_section_list_push(RDIM_Arena *arena, RDIM_BakeSectionList *list); -RDI_PROC RDIM_BakeSection *rdim_bake_section_list_push_new_unpacked(RDIM_Arena *arena, RDIM_BakeSectionList *list, void *data, RDI_U64 size, RDI_SectionKind tag, RDI_U64 tag_idx); -RDI_PROC void rdim_bake_section_list_concat_in_place(RDIM_BakeSectionList *dst, RDIM_BakeSectionList *to_push); - -//////////////////////////////// -//~ rjf: [Baking] Build Artifacts -> Interned/Deduplicated Data Structures - -//- rjf: basic bake string gathering passes -RDI_PROC void rdim_bake_string_map_loose_push_top_level_info(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_TopLevelInfo *tli); -RDI_PROC void rdim_bake_string_map_loose_push_binary_sections(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_BinarySectionList *secs); -RDI_PROC void rdim_bake_string_map_loose_push_path_tree(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_BakePathTree *path_tree); - -//- rjf: slice-granularity bake string gathering passes -RDI_PROC void rdim_bake_string_map_loose_push_src_file_slice(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_SrcFile *v, RDI_U64 count); -RDI_PROC void rdim_bake_string_map_loose_push_unit_slice(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_Unit *v, RDI_U64 count); -RDI_PROC void rdim_bake_string_map_loose_push_type_slice(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_Type *v, RDI_U64 count); -RDI_PROC void rdim_bake_string_map_loose_push_udt_slice(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_UDT *v, RDI_U64 count); -RDI_PROC void rdim_bake_string_map_loose_push_symbol_slice(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_Symbol *v, RDI_U64 count); -RDI_PROC void rdim_bake_string_map_loose_push_scope_slice(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_Scope *v, RDI_U64 count); - -//- rjf: list-granularity bake string gathering passes -RDI_PROC void rdim_bake_string_map_loose_push_src_files(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_SrcFileChunkList *list); -RDI_PROC void rdim_bake_string_map_loose_push_units(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_UnitChunkList *list); -RDI_PROC void rdim_bake_string_map_loose_push_types(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_TypeChunkList *list); -RDI_PROC void rdim_bake_string_map_loose_push_udts(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_UDTChunkList *list); -RDI_PROC void rdim_bake_string_map_loose_push_symbols(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_SymbolChunkList *list); -RDI_PROC void rdim_bake_string_map_loose_push_scopes(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_ScopeChunkList *list); - -//- rjf: bake name map building -RDI_PROC RDIM_BakeNameMap *rdim_bake_name_map_from_kind_params(RDIM_Arena *arena, RDI_NameMapKind kind, RDIM_BakeParams *params); - -//- rjf: bake idx run map building -RDI_PROC RDIM_BakeIdxRunMap *rdim_bake_idx_run_map_from_params(RDIM_Arena *arena, RDIM_BakeNameMap *name_maps[RDI_NameMapKind_COUNT], RDIM_BakeParams *params); - -//- rjf: bake path tree building -RDI_PROC RDIM_BakePathTree *rdim_bake_path_tree_from_params(RDIM_Arena *arena, RDIM_BakeParams *params); - -//////////////////////////////// -//~ rjf: [Baking] Build Artifacts -> Baked Versions - -//- rjf: partial/joinable baking functions -RDI_PROC RDIM_NameMapBakeResult rdim_bake_name_map(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_BakeIdxRunMap *idx_runs, RDIM_BakeNameMap *src); - -//- rjf: partial bakes -> final bake functions -RDI_PROC RDIM_NameMapBakeResult rdim_name_map_bake_results_combine(RDIM_Arena *arena, RDIM_NameMapBakeResult *results, RDI_U64 results_count); - -//- rjf: independent (top-level, global) baking functions -RDI_PROC RDIM_TopLevelInfoBakeResult rdim_bake_top_level_info(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_TopLevelInfo *src); -RDI_PROC RDIM_BinarySectionBakeResult rdim_bake_binary_sections(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_BinarySectionList *src); -RDI_PROC RDIM_UnitBakeResult rdim_bake_units(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_BakePathTree *path_tree, RDIM_UnitChunkList *src); -RDI_PROC RDIM_UnitVMapBakeResult rdim_bake_unit_vmap(RDIM_Arena *arena, RDIM_UnitChunkList *units); -RDI_PROC RDIM_SrcFileBakeResult rdim_bake_src_files(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_BakePathTree *path_tree, RDIM_SrcFileChunkList *src); -RDI_PROC RDIM_LineTableBakeResult rdim_bake_line_tables(RDIM_Arena *arena, RDIM_LineTableChunkList *src); -RDI_PROC RDIM_TypeNodeBakeResult rdim_bake_types(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_BakeIdxRunMap *idx_runs, RDIM_TypeChunkList *src); -RDI_PROC RDIM_UDTBakeResult rdim_bake_udts(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_UDTChunkList *src); -RDI_PROC RDIM_GlobalVariableBakeResult rdim_bake_global_variables(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_SymbolChunkList *src); -RDI_PROC RDIM_GlobalVMapBakeResult rdim_bake_global_vmap(RDIM_Arena *arena, RDIM_SymbolChunkList *src); -RDI_PROC RDIM_ThreadVariableBakeResult rdim_bake_thread_variables(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_SymbolChunkList *src); -RDI_PROC RDIM_ProcedureBakeResult rdim_bake_procedures(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_SymbolChunkList *src); -RDI_PROC RDIM_ScopeBakeResult rdim_bake_scopes(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_ScopeChunkList *src); -RDI_PROC RDIM_ScopeVMapBakeResult rdim_bake_scope_vmap(RDIM_Arena *arena, RDIM_ScopeChunkList *src); -RDI_PROC RDIM_InlineSiteBakeResult rdim_bake_inline_sites(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_InlineSiteChunkList *src); -RDI_PROC RDIM_TopLevelNameMapBakeResult rdim_bake_name_maps_top_level(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_BakeIdxRunMap *idx_runs, RDIM_BakeNameMap *name_maps[RDI_NameMapKind_COUNT]); -RDI_PROC RDIM_FilePathBakeResult rdim_bake_file_paths(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_BakePathTree *path_tree); -RDI_PROC RDIM_StringBakeResult rdim_bake_strings(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings); -RDI_PROC RDIM_IndexRunBakeResult rdim_bake_index_runs(RDIM_Arena *arena, RDIM_BakeIdxRunMap *idx_runs); - -//////////////////////////////// -//~ rjf: [Serializing] Bake Results -> String Blobs - -RDI_PROC RDIM_SerializedSection rdim_serialized_section_make_unpacked(void *data, RDI_U64 size); -#define rdim_serialized_section_make_unpacked_struct(ptr) rdim_serialized_section_make_unpacked((ptr), sizeof(*(ptr))) -#define rdim_serialized_section_make_unpacked_array(ptr, count) rdim_serialized_section_make_unpacked((ptr), sizeof(*(ptr))*(count)) -RDI_PROC RDIM_SerializedSectionBundle rdim_serialized_section_bundle_from_bake_results(RDIM_BakeResults *results); -RDI_PROC RDIM_String8List rdim_file_blobs_from_section_bundle(RDIM_Arena *arena, RDIM_SerializedSectionBundle *bundle); - -#endif // RDI_MAKE_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////////////////////////////////////// +// RAD Debug Info Make, (R)AD(D)BG(I) (M)ake Library +// +// Library for building loose data structures which contain +// RDI debug information, and baking that down into the +// proper flattened RDI format. +// +// Requires prior inclusion of the RAD Debug Info, (R)AD(D)BG(I) +// Format Library, in rdi_format.h. + +#ifndef RDI_MAKE_H +#define RDI_MAKE_H + +//////////////////////////////// +//~ rjf: Overrideable Memory Operations + +// To override the slow/default memset implementation used by the library, +// do the following: +// +// #define RDIM_MEMSET_OVERRIDE +// #define rdim_memset + +#if !defined(rdim_memset) +# define rdim_memset rdim_memset_fallback +#endif + +// To override the slow/default memcpy implementation used by the library, +// do the following: +// +// #define RDIM_MEMCPY_OVERRIDE +// #define rdim_memcpy + +#if !defined(rdim_memset) +# define rdim_memset rdim_memset_fallback +#endif + +#if !defined(rdim_memcpy) +# define rdim_memcpy rdim_memcpy_fallback +#endif + +//////////////////////////////// +//~ rjf: Overrideable sprintf Functions + +#if !defined(rdim_vsnprintf) +# include +# define rdim_vsnprintf vsnprintf +#endif + +//////////////////////////////// +//~ rjf: Overrideable String View Types + +// To override the string view type used by the library, do the following: +// +// #define RDIM_STRING8_OVERRIDE +// #define RDIM_String8 +// #define RDIM_String8_BaseMember +// #define RDIM_String8_SizeMember + +// To override the string view list type used by the library, do the following: +// +// #define RDIM_STRING8LIST_OVERRIDE +// #define RDIM_String8Node +// #define RDIM_String8_NextPtrMember +// #define RDIM_String8_StringMember +// #define RDIM_String8List +// #define RDIM_String8_FirstMember +// #define RDIM_String8_LastMember +// #define RDIM_String8_NodeCount +// #define RDIM_String8_TotalSizeMember + +#if !defined(RDIM_String8) +#define RDIM_String8 RDIM_String8 +#define RDIM_String8_BaseMember str +#define RDIM_String8_SizeMember size +typedef struct RDIM_String8 RDIM_String8; +struct RDIM_String8 +{ + RDI_U8 *str; + RDI_U64 size; +}; +#endif + +#if !defined(RDIM_String8Node) +#define RDIM_String8Node RDIM_String8Node +#define RDIM_String8Node_NextPtrMember next +#define RDIM_String8Node_StringMember string +typedef struct RDIM_String8Node RDIM_String8Node; +struct RDIM_String8Node +{ + RDIM_String8Node *next; + RDIM_String8 string; +}; +#endif + +#if !defined(RDIM_String8List) +#define RDIM_String8List RDIM_String8List +#define RDIM_String8List_FirstMember first +#define RDIM_String8List_LastMember last +#define RDIM_String8List_NodeCountMember node_count +#define RDIM_String8List_TotalSizeMember total_size +typedef struct RDIM_String8List RDIM_String8List; +struct RDIM_String8List +{ + RDIM_String8Node *first; + RDIM_String8Node *last; + RDI_U64 node_count; + RDI_U64 total_size; +}; +#endif + +typedef RDI_U32 RDIM_StringMatchFlags; +enum +{ + RDIM_StringMatchFlag_CaseInsensitive = (1<<0), +}; + +//////////////////////////////// +//~ rjf: Overrideable Arena Allocator Types + +// To override the arena allocator type used by the library, do the following: +// +// #define RDIM_ARENA_OVERRIDE +// #define RDIM_Arena +// #define rdim_arena_alloc Arena*> +// #define rdim_arena_release void> +// #define rdim_arena_pos U64> +// #define rdim_arena_push void*> +// #define rdim_arena_pop_to void> + +#if !defined(RDIM_Arena) +# define RDIM_Arena RDIM_Arena +typedef struct RDIM_Arena RDIM_Arena; +struct RDIM_Arena +{ + RDIM_Arena *prev; + RDIM_Arena *current; + RDI_U64 base_pos; + RDI_U64 pos; + RDI_U64 cmt; + RDI_U64 res; + RDI_U64 align; + RDI_S8 grow; +}; +#endif + +#if !defined(rdim_arena_alloc) +# define rdim_arena_alloc rdim_arena_alloc_fallback +#endif +#if !defined(rdim_arena_release) +# define rdim_arena_release rdim_arena_release_fallback +#endif +#if !defined(rdim_arena_pos) +# define rdim_arena_pos rdim_arena_pos_fallback +#endif +#if !defined(rdim_arena_push) +# define rdim_arena_push rdim_arena_push_fallback +#endif + +//////////////////////////////// +//~ rjf: Overrideable Thread-Local Scratch Arenas + +// To override the default thread-local scratch arenas used by the library, +// do the following: +// +// #define RDIM_SCRATCH_OVERRIDE +// #define RDIM_Temp arena implementation - must be (Temp) -> (Arena*)> +// #define rdim_scratch_begin Temp> +// #define rdim_scratch_end void + +#if !defined(RDIM_Temp) +# define RDIM_Temp RDIM_Temp +typedef struct RDIM_Temp RDIM_Temp; +struct RDIM_Temp +{ + RDIM_Arena *arena; + RDI_U64 pos; +}; +#define rdim_temp_arena(t) ((t).arena) +#endif + +#if !defined(rdim_scratch_begin) +# define rdim_scratch_begin rdim_scratch_begin_fallback +#endif +#if !defined(rdim_scratch_end) +# define rdim_scratch_end rdim_scratch_end_fallback +#endif + +//////////////////////////////// +//~ rjf: Overrideable Profile Markup + +// To override the default profiling markup, do the following: +// +// #define RDIM_ProfBegin(...) +// #define RDIM_ProfEnd() + +#if !defined(RDIM_ProfBegin) +# define RDIM_ProfBegin(...) ((void)0) +#endif +#if !defined(RDIM_ProfEnd) +# define RDIM_ProfEnd() ((void)0) +#endif + +#define RDIM_ProfScope(...) for(int _i_ = ((RDIM_ProfBegin(__VA_ARGS__)), 0); !_i_; _i_ += 1, (RDIM_ProfEnd())) + +//////////////////////////////// +//~ rjf: Alignment Macros + +#if _MSC_VER +# define RDIM_AlignOf(T) __alignof(T) +#elif __clang__ +# define RDIM_AlignOf(T) __alignof(T) +#elif __GNUC__ +# define RDIM_AlignOf(T) __alignof__(T) +#else +# error [RDIM Build Error] RDIM_AlignOf(T) is not defined for this compiler. +#endif + +//////////////////////////////// +//~ rjf: Linked List Helper Macros + +#define RDIM_CheckNil(nil,p) ((p) == 0 || (p) == nil) +#define RDIM_SetNil(nil,p) ((p) = nil) + +//- rjf: Base Doubly-Linked-List Macros +#define RDIM_DLLInsert_NPZ(nil,f,l,p,n,next,prev) (RDIM_CheckNil(nil,f) ? \ +((f) = (l) = (n), RDIM_SetNil(nil,(n)->next), RDIM_SetNil(nil,(n)->prev)) :\ +RDIM_CheckNil(nil,p) ? \ +((n)->next = (f), (f)->prev = (n), (f) = (n), RDIM_SetNil(nil,(n)->prev)) :\ +((p)==(l)) ? \ +((l)->next = (n), (n)->prev = (l), (l) = (n), RDIM_SetNil(nil, (n)->next)) :\ +(((!RDIM_CheckNil(nil,p) && RDIM_CheckNil(nil,(p)->next)) ? (0) : ((p)->next->prev = (n))), ((n)->next = (p)->next), ((p)->next = (n)), ((n)->prev = (p)))) +#define RDIM_DLLPushBack_NPZ(nil,f,l,n,next,prev) RDIM_DLLInsert_NPZ(nil,f,l,l,n,next,prev) +#define RDIM_DLLPushFront_NPZ(nil,f,l,n,next,prev) RDIM_DLLInsert_NPZ(nil,l,f,f,n,prev,next) +#define RDIM_DLLRemove_NPZ(nil,f,l,n,next,prev) (((n) == (f) ? (f) = (n)->next : (0)),\ +((n) == (l) ? (l) = (l)->prev : (0)),\ +(RDIM_CheckNil(nil,(n)->prev) ? (0) :\ +((n)->prev->next = (n)->next)),\ +(RDIM_CheckNil(nil,(n)->next) ? (0) :\ +((n)->next->prev = (n)->prev))) + +//- rjf: Base Singly-Linked-List Queue Macros +#define RDIM_SLLQueuePush_NZ(nil,f,l,n,next) (RDIM_CheckNil(nil,f)?\ +((f)=(l)=(n),RDIM_SetNil(nil,(n)->next)):\ +((l)->next=(n),(l)=(n),RDIM_SetNil(nil,(n)->next))) +#define RDIM_SLLQueuePushFront_NZ(nil,f,l,n,next) (RDIM_CheckNil(nil,f)?\ +((f)=(l)=(n),RDIM_SetNil(nil,(n)->next)):\ +((n)->next=(f),(f)=(n))) +#define RDIM_SLLQueuePop_NZ(nil,f,l,next) ((f)==(l)?\ +(RDIM_SetNil(nil,f), RDIM_SetNil(nil,l)):\ +((f)=(f)->next)) + +//- rjf: Base Singly-Linked-List Stack Macros +#define RDIM_SLLStackPush_N(f,n,next) ((n)->next=(f), (f)=(n)) +#define RDIM_SLLStackPop_N(f,next) ((f)=(f)->next) + +//////////////////////////////// +//~ rjf: Convenience Wrappers + +//- rjf: Doubly-Linked-List Wrappers +#define RDIM_DLLInsert_NP(f,l,p,n,next,prev) RDIM_DLLInsert_NPZ(0,f,l,p,n,next,prev) +#define RDIM_DLLPushBack_NP(f,l,n,next,prev) RDIM_DLLPushBack_NPZ(0,f,l,n,next,prev) +#define RDIM_DLLPushFront_NP(f,l,n,next,prev) RDIM_DLLPushFront_NPZ(0,f,l,n,next,prev) +#define RDIM_DLLRemove_NP(f,l,n,next,prev) RDIM_DLLRemove_NPZ(0,f,l,n,next,prev) +#define RDIM_DLLInsert(f,l,p,n) RDIM_DLLInsert_NPZ(0,f,l,p,n,next,prev) +#define RDIM_DLLPushBack(f,l,n) RDIM_DLLPushBack_NPZ(0,f,l,n,next,prev) +#define RDIM_DLLPushFront(f,l,n) RDIM_DLLPushFront_NPZ(0,f,l,n,next,prev) +#define RDIM_DLLRemove(f,l,n) RDIM_DLLRemove_NPZ(0,f,l,n,next,prev) + +//- rjf: Singly-Linked-List Queue Wrappers +#define RDIM_SLLQueuePush_N(f,l,n,next) RDIM_SLLQueuePush_NZ(0,f,l,n,next) +#define RDIM_SLLQueuePushFront_N(f,l,n,next) RDIM_SLLQueuePushFront_NZ(0,f,l,n,next) +#define RDIM_SLLQueuePop_N(f,l,next) RDIM_SLLQueuePop_NZ(0,f,l,next) +#define RDIM_SLLQueuePush(f,l,n) RDIM_SLLQueuePush_NZ(0,f,l,n,next) +#define RDIM_SLLQueuePushFront(f,l,n) RDIM_SLLQueuePushFront_NZ(0,f,l,n,next) +#define RDIM_SLLQueuePop(f,l) RDIM_SLLQueuePop_NZ(0,f,l,next) + +//- rjf: Singly-Linked-List Stack Wrappers +#define RDIM_SLLStackPush(f,n) RDIM_SLLStackPush_N(f,n,next) +#define RDIM_SLLStackPop(f) RDIM_SLLStackPop_N(f,next) + +//////////////////////////////// +//~ rjf: Helper Macros + +#if defined(_MSC_VER) +# define RDIM_THREAD_LOCAL __declspec(thread) +#elif defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) +# define RDIM_THREAD_LOCAL __thread +#else +# error RDIM_THREAD_LOCAL not defined for this compiler. +#endif + +#if defined(_MSC_VER) +# define rdim_trap() __debugbreak() +#elif defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) +# define rdim_trap() __builtin_trap() +#else +# error "rdim_trap not defined for this compiler." +#endif + +#define rdim_assert_always(x) do{if(!(x)) {rdim_trap();}}while(0) +#if !defined(NDEBUG) +# define rdim_assert(x) rdim_assert_always(x) +#else +# define rdim_assert(x) (void)(x) +#endif +#define rdim_noop ((void)0) + +//////////////////////////////// +//~ rjf: Auxiliary Data Structure Types + +//- rjf: 1-dimensional U64 ranges + +typedef struct RDIM_Rng1U64 RDIM_Rng1U64; +struct RDIM_Rng1U64 +{ + RDI_U64 min; + RDI_U64 max; +}; + +typedef struct RDIM_Rng1U64Node RDIM_Rng1U64Node; +struct RDIM_Rng1U64Node +{ + RDIM_Rng1U64Node *next; + RDIM_Rng1U64 v; +}; + +typedef struct RDIM_Rng1U64List RDIM_Rng1U64List; +struct RDIM_Rng1U64List +{ + RDIM_Rng1U64Node *first; + RDIM_Rng1U64Node *last; + RDI_U64 count; + RDI_U64 min; +}; + +//- rjf: u64 -> pointer map + +typedef struct RDIM_U64ToPtrNode RDIM_U64ToPtrNode; +struct RDIM_U64ToPtrNode +{ + RDIM_U64ToPtrNode *next; + RDI_U64 _padding_; + RDI_U64 key[1]; + void *ptr[1]; +}; + +typedef struct RDIM_U64ToPtrMap RDIM_U64ToPtrMap; +struct RDIM_U64ToPtrMap +{ + RDIM_U64ToPtrNode **buckets; + RDI_U64 buckets_count; + RDI_U64 bucket_collision_count; + RDI_U64 pair_count; +}; + +typedef struct RDIM_U64ToPtrLookup RDIM_U64ToPtrLookup; +struct RDIM_U64ToPtrLookup +{ + void *match; + RDIM_U64ToPtrNode *fill_node; + RDI_U32 fill_k; +}; + +//- rjf: string8 -> pointer map + +typedef struct RDIM_Str8ToPtrNode RDIM_Str8ToPtrNode; +struct RDIM_Str8ToPtrNode +{ + struct RDIM_Str8ToPtrNode *next; + RDIM_String8 key; + RDI_U64 hash; + void *ptr; +}; + +typedef struct RDIM_Str8ToPtrMap RDIM_Str8ToPtrMap; +struct RDIM_Str8ToPtrMap +{ + RDIM_Str8ToPtrNode **buckets; + RDI_U64 buckets_count; + RDI_U64 bucket_collision_count; + RDI_U64 pair_count; +}; + +//- rjf: sortable range data structure + +typedef struct RDIM_SortKey RDIM_SortKey; +struct RDIM_SortKey +{ + RDI_U64 key; + void *val; +}; + +typedef struct RDIM_OrderedRange RDIM_OrderedRange; +struct RDIM_OrderedRange +{ + RDIM_OrderedRange *next; + RDI_U64 first; + RDI_U64 opl; +}; + +//////////////////////////////// +//~ rjf: Error/Warning/Note Message Types + +typedef struct RDIM_Msg RDIM_Msg; +struct RDIM_Msg +{ + RDIM_Msg *next; + RDIM_String8 string; +}; + +typedef struct RDIM_MsgList RDIM_MsgList; +struct RDIM_MsgList +{ + RDIM_Msg *first; + RDIM_Msg *last; + RDI_U64 count; +}; + +//////////////////////////////// +//~ rjf: Top-Level Debug Info Types + +typedef struct RDIM_TopLevelInfo RDIM_TopLevelInfo; +struct RDIM_TopLevelInfo +{ + RDI_Arch arch; + RDIM_String8 exe_name; + RDI_U64 exe_hash; + RDI_U64 voff_max; + RDIM_String8 producer_name; +}; + +//////////////////////////////// +//~ rjf: Binary Section Types + +typedef struct RDIM_BinarySection RDIM_BinarySection; +struct RDIM_BinarySection +{ + RDIM_String8 name; + RDI_BinarySectionFlags flags; + RDI_U64 voff_first; + RDI_U64 voff_opl; + RDI_U64 foff_first; + RDI_U64 foff_opl; +}; + +typedef struct RDIM_BinarySectionNode RDIM_BinarySectionNode; +struct RDIM_BinarySectionNode +{ + RDIM_BinarySectionNode *next; + RDIM_BinarySection v; +}; + +typedef struct RDIM_BinarySectionList RDIM_BinarySectionList; +struct RDIM_BinarySectionList +{ + RDIM_BinarySectionNode *first; + RDIM_BinarySectionNode *last; + RDI_U64 count; +}; + +//////////////////////////////// +//~ rjf: Source File Info Types + +typedef struct RDIM_SrcFileLineMapFragment RDIM_SrcFileLineMapFragment; +struct RDIM_SrcFileLineMapFragment +{ + RDIM_SrcFileLineMapFragment *next; + struct RDIM_LineSequence *seq; +}; + +typedef struct RDIM_SrcFile RDIM_SrcFile; +struct RDIM_SrcFile +{ + struct RDIM_SrcFileChunkNode *chunk; + RDIM_String8 normal_full_path; + RDIM_SrcFileLineMapFragment *first_line_map_fragment; + RDIM_SrcFileLineMapFragment *last_line_map_fragment; +}; + +typedef struct RDIM_SrcFileChunkNode RDIM_SrcFileChunkNode; +struct RDIM_SrcFileChunkNode +{ + RDIM_SrcFileChunkNode *next; + RDIM_SrcFile *v; + RDI_U64 count; + RDI_U64 cap; + RDI_U64 base_idx; +}; + +typedef struct RDIM_SrcFileChunkList RDIM_SrcFileChunkList; +struct RDIM_SrcFileChunkList +{ + RDIM_SrcFileChunkNode *first; + RDIM_SrcFileChunkNode *last; + RDI_U64 chunk_count; + RDI_U64 total_count; + RDI_U64 source_line_map_count; + RDI_U64 total_line_count; +}; + +//////////////////////////////// +//~ rjf: Line Info Types + +typedef struct RDIM_LineSequence RDIM_LineSequence; +struct RDIM_LineSequence +{ + RDIM_SrcFile *src_file; + RDI_U64 *voffs; // [line_count + 1] (sorted) + RDI_U32 *line_nums; // [line_count] + RDI_U16 *col_nums; // [2*line_count] + RDI_U64 line_count; +}; + +typedef struct RDIM_LineSequenceNode RDIM_LineSequenceNode; +struct RDIM_LineSequenceNode +{ + RDIM_LineSequenceNode *next; + RDIM_LineSequence v; +}; + +typedef struct RDIM_LineTable RDIM_LineTable; +struct RDIM_LineTable +{ + struct RDIM_LineTableChunkNode *chunk; + RDIM_LineSequenceNode *first_seq; + RDIM_LineSequenceNode *last_seq; + RDI_U64 seq_count; + RDI_U64 line_count; + RDI_U64 col_count; +}; + +typedef struct RDIM_LineTableChunkNode RDIM_LineTableChunkNode; +struct RDIM_LineTableChunkNode +{ + RDIM_LineTableChunkNode *next; + RDIM_LineTable *v; + RDI_U64 count; + RDI_U64 cap; + RDI_U64 base_idx; +}; + +typedef struct RDIM_LineTableChunkList RDIM_LineTableChunkList; +struct RDIM_LineTableChunkList +{ + RDIM_LineTableChunkNode *first; + RDIM_LineTableChunkNode *last; + RDI_U64 chunk_count; + RDI_U64 total_count; + RDI_U64 total_seq_count; + RDI_U64 total_line_count; + RDI_U64 total_col_count; +}; + +//////////////////////////////// +//~ rjf: Per-Compilation-Unit Info Types + +typedef struct RDIM_Unit RDIM_Unit; +struct RDIM_Unit +{ + struct RDIM_UnitChunkNode *chunk; + RDIM_String8 unit_name; + RDIM_String8 compiler_name; + RDIM_String8 source_file; + RDIM_String8 object_file; + RDIM_String8 archive_file; + RDIM_String8 build_path; + RDI_Language language; + RDIM_LineTable *line_table; + RDIM_Rng1U64List voff_ranges; +}; + +typedef struct RDIM_UnitChunkNode RDIM_UnitChunkNode; +struct RDIM_UnitChunkNode +{ + RDIM_UnitChunkNode *next; + RDIM_Unit *v; + RDI_U64 count; + RDI_U64 cap; + RDI_U64 base_idx; +}; + +typedef struct RDIM_UnitChunkList RDIM_UnitChunkList; +struct RDIM_UnitChunkList +{ + RDIM_UnitChunkNode *first; + RDIM_UnitChunkNode *last; + RDI_U64 chunk_count; + RDI_U64 total_count; +}; + +//////////////////////////////// +//~ rjf: Type System Node Types + +typedef struct RDIM_Type RDIM_Type; +struct RDIM_Type +{ + struct RDIM_TypeChunkNode *chunk; + RDI_TypeKind kind; + RDI_U32 byte_size; + RDI_U32 flags; + RDI_U32 off; + RDI_U32 count; + RDIM_String8 name; + RDIM_Type *direct_type; + RDIM_Type **param_types; + struct RDIM_UDT *udt; +}; + +typedef struct RDIM_TypeChunkNode RDIM_TypeChunkNode; +struct RDIM_TypeChunkNode +{ + RDIM_TypeChunkNode *next; + RDIM_Type *v; + RDI_U64 count; + RDI_U64 cap; + RDI_U64 base_idx; +}; + +typedef struct RDIM_TypeChunkList RDIM_TypeChunkList; +struct RDIM_TypeChunkList +{ + RDIM_TypeChunkNode *first; + RDIM_TypeChunkNode *last; + RDI_U64 chunk_count; + RDI_U64 total_count; +}; + +//////////////////////////////// +//~ rjf: User-Defined-Type Info Types + +typedef struct RDIM_UDTMember RDIM_UDTMember; +struct RDIM_UDTMember +{ + RDIM_UDTMember *next; + RDI_MemberKind kind; + RDIM_String8 name; + RDIM_Type *type; + RDI_U32 off; +}; + +typedef struct RDIM_UDTEnumVal RDIM_UDTEnumVal; +struct RDIM_UDTEnumVal +{ + RDIM_UDTEnumVal *next; + RDIM_String8 name; + RDI_U64 val; +}; + +typedef struct RDIM_UDT RDIM_UDT; +struct RDIM_UDT +{ + struct RDIM_UDTChunkNode *chunk; + RDIM_Type *self_type; + RDIM_UDTMember *first_member; + RDIM_UDTMember *last_member; + RDIM_UDTEnumVal *first_enum_val; + RDIM_UDTEnumVal *last_enum_val; + RDI_U32 member_count; + RDI_U32 enum_val_count; + RDIM_SrcFile *src_file; + RDI_U32 line; + RDI_U32 col; +}; + +typedef struct RDIM_UDTChunkNode RDIM_UDTChunkNode; +struct RDIM_UDTChunkNode +{ + RDIM_UDTChunkNode *next; + RDIM_UDT *v; + RDI_U64 count; + RDI_U64 cap; + RDI_U64 base_idx; +}; + +typedef struct RDIM_UDTChunkList RDIM_UDTChunkList; +struct RDIM_UDTChunkList +{ + RDIM_UDTChunkNode *first; + RDIM_UDTChunkNode *last; + RDI_U64 chunk_count; + RDI_U64 total_count; + RDI_U64 total_member_count; + RDI_U64 total_enum_val_count; +}; + +//////////////////////////////// +//~ rjf: Location Info Types + +typedef struct RDIM_EvalBytecodeOp RDIM_EvalBytecodeOp; +struct RDIM_EvalBytecodeOp +{ + RDIM_EvalBytecodeOp *next; + RDI_EvalOp op; + RDI_U32 p_size; + RDI_U64 p; +}; + +typedef struct RDIM_EvalBytecode RDIM_EvalBytecode; +struct RDIM_EvalBytecode +{ + RDIM_EvalBytecodeOp *first_op; + RDIM_EvalBytecodeOp *last_op; + RDI_U32 op_count; + RDI_U32 encoded_size; +}; + +typedef struct RDIM_Location RDIM_Location; +struct RDIM_Location +{ + RDI_LocationKind kind; + RDI_U8 reg_code; + RDI_U16 offset; + RDIM_EvalBytecode bytecode; +}; + +typedef struct RDIM_LocationCase RDIM_LocationCase; +struct RDIM_LocationCase +{ + RDIM_LocationCase *next; + RDIM_Rng1U64 voff_range; + RDIM_Location *location; +}; + +typedef struct RDIM_LocationSet RDIM_LocationSet; +struct RDIM_LocationSet +{ + RDIM_LocationCase *first_location_case; + RDIM_LocationCase *last_location_case; + RDI_U64 location_case_count; +}; + +//////////////////////////////// +//~ rjf: Symbol Info Types + +typedef enum RDIM_SymbolKind +{ + RDIM_SymbolKind_NULL, + RDIM_SymbolKind_GlobalVariable, + RDIM_SymbolKind_ThreadVariable, + RDIM_SymbolKind_Procedure, + RDIM_SymbolKind_COUNT +} +RDIM_SymbolKind; + +typedef struct RDIM_Symbol RDIM_Symbol; +struct RDIM_Symbol +{ + struct RDIM_SymbolChunkNode *chunk; + RDI_S32 is_extern; + RDIM_String8 name; + RDIM_String8 link_name; + RDIM_Type *type; + RDI_U64 offset; + RDIM_Symbol *container_symbol; + RDIM_Type *container_type; + struct RDIM_Scope *root_scope; +}; + +typedef struct RDIM_SymbolChunkNode RDIM_SymbolChunkNode; +struct RDIM_SymbolChunkNode +{ + RDIM_SymbolChunkNode *next; + RDIM_Symbol *v; + RDI_U64 count; + RDI_U64 cap; + RDI_U64 base_idx; +}; + +typedef struct RDIM_SymbolChunkList RDIM_SymbolChunkList; +struct RDIM_SymbolChunkList +{ + RDIM_SymbolChunkNode *first; + RDIM_SymbolChunkNode *last; + RDI_U64 chunk_count; + RDI_U64 total_count; +}; + +//////////////////////////////// +//~ rjf: Inline Site Info Types + +typedef struct RDIM_InlineSite RDIM_InlineSite; +struct RDIM_InlineSite +{ + struct RDIM_InlineSiteChunkNode *chunk; + RDIM_String8 name; + RDIM_Type *type; + RDIM_Type *owner; + RDIM_LineTable *line_table; +}; + +typedef struct RDIM_InlineSiteChunkNode RDIM_InlineSiteChunkNode; +struct RDIM_InlineSiteChunkNode +{ + RDIM_InlineSiteChunkNode *next; + RDIM_InlineSite *v; + RDI_U64 count; + RDI_U64 cap; + RDI_U64 base_idx; +}; + +typedef struct RDIM_InlineSiteChunkList RDIM_InlineSiteChunkList; +struct RDIM_InlineSiteChunkList +{ + RDIM_InlineSiteChunkNode *first; + RDIM_InlineSiteChunkNode *last; + RDI_U64 chunk_count; + RDI_U64 total_count; +}; + +//////////////////////////////// +//~ rjf: Scope Info Types + +typedef struct RDIM_Local RDIM_Local; +struct RDIM_Local +{ + RDIM_Local *next; + RDI_LocalKind kind; + RDIM_String8 name; + RDIM_Type *type; + RDIM_LocationSet locset; +}; + +typedef struct RDIM_Scope RDIM_Scope; +struct RDIM_Scope +{ + struct RDIM_ScopeChunkNode *chunk; + RDIM_Symbol *symbol; + RDIM_Scope *parent_scope; + RDIM_Scope *first_child; + RDIM_Scope *last_child; + RDIM_Scope *next_sibling; + RDIM_Rng1U64List voff_ranges; + RDIM_Local *first_local; + RDIM_Local *last_local; + RDI_U32 local_count; + RDIM_InlineSite *inline_site; +}; + +typedef struct RDIM_ScopeChunkNode RDIM_ScopeChunkNode; +struct RDIM_ScopeChunkNode +{ + RDIM_ScopeChunkNode *next; + RDIM_Scope *v; + RDI_U64 count; + RDI_U64 cap; + RDI_U64 base_idx; +}; + +typedef struct RDIM_ScopeChunkList RDIM_ScopeChunkList; +struct RDIM_ScopeChunkList +{ + RDIM_ScopeChunkNode *first; + RDIM_ScopeChunkNode *last; + RDI_U64 chunk_count; + RDI_U64 total_count; + RDI_U64 scope_voff_count; + RDI_U64 local_count; + RDI_U64 location_count; +}; + +//////////////////////////////// +//~ rjf: Baking Types + +//- rjf: baking parameters + +typedef struct RDIM_BakeParams RDIM_BakeParams; +struct RDIM_BakeParams +{ + RDIM_TopLevelInfo top_level_info; + RDIM_BinarySectionList binary_sections; + RDIM_UnitChunkList units; + RDIM_TypeChunkList types; + RDIM_UDTChunkList udts; + RDIM_SrcFileChunkList src_files; + RDIM_LineTableChunkList line_tables; + RDIM_SymbolChunkList global_variables; + RDIM_SymbolChunkList thread_variables; + RDIM_SymbolChunkList procedures; + RDIM_ScopeChunkList scopes; + RDIM_InlineSiteChunkList inline_sites; +}; + +//- rjf: data sections + +typedef struct RDIM_BakeSection RDIM_BakeSection; +struct RDIM_BakeSection +{ + void *data; + RDI_SectionEncoding encoding; + RDI_U64 encoded_size; + RDI_U64 unpacked_size; + RDI_SectionKind tag; + RDI_U64 tag_idx; +}; + +typedef struct RDIM_BakeSectionNode RDIM_BakeSectionNode; +struct RDIM_BakeSectionNode +{ + RDIM_BakeSectionNode *next; + RDIM_BakeSection v; +}; + +typedef struct RDIM_BakeSectionList RDIM_BakeSectionList; +struct RDIM_BakeSectionList +{ + RDIM_BakeSectionNode *first; + RDIM_BakeSectionNode *last; + RDI_U64 count; +}; + +//- rjf: interned string type + +typedef struct RDIM_BakeString RDIM_BakeString; +struct RDIM_BakeString +{ + RDI_U64 hash; + RDIM_String8 string; +}; + +typedef struct RDIM_BakeStringChunkNode RDIM_BakeStringChunkNode; +struct RDIM_BakeStringChunkNode +{ + RDIM_BakeStringChunkNode *next; + RDIM_BakeString *v; + RDI_U64 count; + RDI_U64 cap; + RDI_U64 base_idx; +}; + +typedef struct RDIM_BakeStringChunkList RDIM_BakeStringChunkList; +struct RDIM_BakeStringChunkList +{ + RDIM_BakeStringChunkNode *first; + RDIM_BakeStringChunkNode *last; + RDI_U64 chunk_count; + RDI_U64 total_count; +}; + +typedef struct RDIM_BakeStringMapTopology RDIM_BakeStringMapTopology; +struct RDIM_BakeStringMapTopology +{ + RDI_U64 slots_count; +}; + +typedef struct RDIM_BakeStringMapBaseIndices RDIM_BakeStringMapBaseIndices; +struct RDIM_BakeStringMapBaseIndices +{ + RDI_U64 *slots_base_idxs; +}; + +typedef struct RDIM_BakeStringMapLoose RDIM_BakeStringMapLoose; +struct RDIM_BakeStringMapLoose +{ + RDIM_BakeStringChunkList **slots; +}; + +typedef struct RDIM_BakeStringMapTight RDIM_BakeStringMapTight; +struct RDIM_BakeStringMapTight +{ + RDIM_BakeStringChunkList *slots; + RDI_U64 *slots_base_idxs; + RDI_U64 slots_count; + RDI_U64 total_count; +}; + +//- rjf: index runs + +typedef struct RDIM_BakeIdxRunNode RDIM_BakeIdxRunNode; +struct RDIM_BakeIdxRunNode +{ + RDIM_BakeIdxRunNode *hash_next; + RDIM_BakeIdxRunNode *order_next; + RDI_U32 *idx_run; + RDI_U64 hash; + RDI_U32 count; + RDI_U32 first_idx; +}; + +typedef struct RDIM_BakeIdxRunMap RDIM_BakeIdxRunMap; +struct RDIM_BakeIdxRunMap +{ + RDIM_BakeIdxRunNode *order_first; + RDIM_BakeIdxRunNode *order_last; + RDIM_BakeIdxRunNode **slots; + RDI_U64 slots_count; + RDI_U64 slot_collision_count; + RDI_U32 count; + RDI_U32 idx_count; +}; + +//- rjf: source info & path tree + +typedef struct RDIM_BakePathNode RDIM_BakePathNode; +struct RDIM_BakePathNode +{ + RDIM_BakePathNode *next_order; + RDIM_BakePathNode *parent; + RDIM_BakePathNode *first_child; + RDIM_BakePathNode *last_child; + RDIM_BakePathNode *next_sibling; + RDIM_String8 name; + RDIM_SrcFile *src_file; + RDI_U32 idx; +}; + +typedef struct RDIM_BakeLineMapFragment RDIM_BakeLineMapFragment; +struct RDIM_BakeLineMapFragment +{ + RDIM_BakeLineMapFragment *next; + RDIM_LineSequence *seq; +}; + +typedef struct RDIM_BakePathTree RDIM_BakePathTree; +struct RDIM_BakePathTree +{ + RDIM_BakePathNode root; + RDIM_BakePathNode *first; + RDIM_BakePathNode *last; + RDI_U32 count; +}; + +//- rjf: name maps + +typedef struct RDIM_BakeNameMapValNode RDIM_BakeNameMapValNode; +struct RDIM_BakeNameMapValNode +{ + RDIM_BakeNameMapValNode *next; + RDI_U32 val[6]; +}; + +typedef struct RDIM_BakeNameMapNode RDIM_BakeNameMapNode; +struct RDIM_BakeNameMapNode +{ + RDIM_BakeNameMapNode *slot_next; + RDIM_BakeNameMapNode *order_next; + RDIM_String8 string; + RDIM_BakeNameMapValNode *val_first; + RDIM_BakeNameMapValNode *val_last; + RDI_U64 val_count; +}; + +typedef struct RDIM_BakeNameMap RDIM_BakeNameMap; +struct RDIM_BakeNameMap +{ + RDIM_BakeNameMapNode **slots; + RDI_U64 slots_count; + RDI_U64 slot_collision_count; + RDIM_BakeNameMapNode *first; + RDIM_BakeNameMapNode *last; + RDI_U64 name_count; +}; + +//- rjf: vmaps + +typedef struct RDIM_BakeVMap RDIM_BakeVMap; +struct RDIM_BakeVMap +{ + RDI_VMapEntry *vmap; // [count + 1] + RDI_U32 count; +}; + +typedef struct RDIM_VMapMarker RDIM_VMapMarker; +struct RDIM_VMapMarker +{ + RDI_U32 idx; + RDI_U32 begin_range; +}; + +//- rjf: baking results + +typedef struct RDIM_TopLevelInfoBakeResult RDIM_TopLevelInfoBakeResult; +struct RDIM_TopLevelInfoBakeResult +{ + RDI_TopLevelInfo *top_level_info; +}; + +typedef struct RDIM_BinarySectionBakeResult RDIM_BinarySectionBakeResult; +struct RDIM_BinarySectionBakeResult +{ + RDI_BinarySection *binary_sections; + RDI_U64 binary_sections_count; +}; + +typedef struct RDIM_UnitBakeResult RDIM_UnitBakeResult; +struct RDIM_UnitBakeResult +{ + RDI_Unit *units; + RDI_U64 units_count; +}; + +typedef struct RDIM_UnitVMapBakeResult RDIM_UnitVMapBakeResult; +struct RDIM_UnitVMapBakeResult +{ + RDIM_BakeVMap vmap; +}; + +typedef struct RDIM_SrcFileBakeResult RDIM_SrcFileBakeResult; +struct RDIM_SrcFileBakeResult +{ + RDI_SourceFile *source_files; + RDI_U64 source_files_count; + RDI_SourceLineMap *source_line_maps; + RDI_U64 source_line_maps_count; + RDI_U32 *source_line_map_nums; + RDI_U32 *source_line_map_rngs; + RDI_U64 *source_line_map_voffs; + RDI_U64 source_line_map_nums_count; + RDI_U64 source_line_map_rngs_count; + RDI_U64 source_line_map_voffs_count; +}; + +typedef struct RDIM_LineTableBakeResult RDIM_LineTableBakeResult; +struct RDIM_LineTableBakeResult +{ + RDI_LineTable *line_tables; + RDI_U64 line_tables_count; + RDI_U64 *line_table_voffs; + RDI_U64 line_table_voffs_count; + RDI_Line *line_table_lines; + RDI_U64 line_table_lines_count; + RDI_Column *line_table_columns; + RDI_U64 line_table_columns_count; +}; + +typedef struct RDIM_TypeNodeBakeResult RDIM_TypeNodeBakeResult; +struct RDIM_TypeNodeBakeResult +{ + RDI_TypeNode *type_nodes; + RDI_U64 type_nodes_count; +}; + +typedef struct RDIM_UDTBakeResult RDIM_UDTBakeResult; +struct RDIM_UDTBakeResult +{ + RDI_UDT *udts; + RDI_U64 udts_count; + RDI_Member *members; + RDI_U64 members_count; + RDI_EnumMember *enum_members; + RDI_U64 enum_members_count; +}; + +typedef struct RDIM_GlobalVariableBakeResult RDIM_GlobalVariableBakeResult; +struct RDIM_GlobalVariableBakeResult +{ + RDI_GlobalVariable *global_variables; + RDI_U64 global_variables_count; +}; + +typedef struct RDIM_GlobalVMapBakeResult RDIM_GlobalVMapBakeResult; +struct RDIM_GlobalVMapBakeResult +{ + RDIM_BakeVMap vmap; +}; + +typedef struct RDIM_ThreadVariableBakeResult RDIM_ThreadVariableBakeResult; +struct RDIM_ThreadVariableBakeResult +{ + RDI_ThreadVariable *thread_variables; + RDI_U64 thread_variables_count; +}; + +typedef struct RDIM_ProcedureBakeResult RDIM_ProcedureBakeResult; +struct RDIM_ProcedureBakeResult +{ + RDI_Procedure *procedures; + RDI_U64 procedures_count; +}; + +typedef struct RDIM_ScopeBakeResult RDIM_ScopeBakeResult; +struct RDIM_ScopeBakeResult +{ + RDI_Scope *scopes; + RDI_U64 scopes_count; + RDI_U64 *scope_voffs; + RDI_U64 scope_voffs_count; + RDI_Local *locals; + RDI_U64 locals_count; + RDI_LocationBlock *location_blocks; + RDI_U64 location_blocks_count; + RDI_U8 *location_data; + RDI_U64 location_data_size; +}; + +typedef struct RDIM_ScopeVMapBakeResult RDIM_ScopeVMapBakeResult; +struct RDIM_ScopeVMapBakeResult +{ + RDIM_BakeVMap vmap; +}; + +typedef struct RDIM_InlineSiteBakeResult RDIM_InlineSiteBakeResult; +struct RDIM_InlineSiteBakeResult +{ + RDI_InlineSite *inline_sites; + RDI_U64 inline_sites_count; +}; + +typedef struct RDIM_TopLevelNameMapBakeResult RDIM_TopLevelNameMapBakeResult; +struct RDIM_TopLevelNameMapBakeResult +{ + RDI_NameMap *name_maps; + RDI_U64 name_maps_count; +}; + +typedef struct RDIM_NameMapBakeResult RDIM_NameMapBakeResult; +struct RDIM_NameMapBakeResult +{ + RDI_NameMapBucket *buckets; + RDI_U64 buckets_count; + RDI_NameMapNode *nodes; + RDI_U64 nodes_count; +}; + +typedef struct RDIM_FilePathBakeResult RDIM_FilePathBakeResult; +struct RDIM_FilePathBakeResult +{ + RDI_FilePathNode *nodes; + RDI_U64 nodes_count; +}; + +typedef struct RDIM_StringBakeResult RDIM_StringBakeResult; +struct RDIM_StringBakeResult +{ + RDI_U32 *string_offs; + RDI_U64 string_offs_count; + RDI_U8 *string_data; + RDI_U64 string_data_size; +}; + +typedef struct RDIM_IndexRunBakeResult RDIM_IndexRunBakeResult; +struct RDIM_IndexRunBakeResult +{ + RDI_U32 *idx_runs; + RDI_U64 idx_count; +}; + +typedef struct RDIM_BakeResults RDIM_BakeResults; +struct RDIM_BakeResults +{ + RDIM_TopLevelInfoBakeResult top_level_info; + RDIM_BinarySectionBakeResult binary_sections; + RDIM_UnitBakeResult units; + RDIM_UnitVMapBakeResult unit_vmap; + RDIM_SrcFileBakeResult src_files; + RDIM_LineTableBakeResult line_tables; + RDIM_TypeNodeBakeResult type_nodes; + RDIM_UDTBakeResult udts; + RDIM_GlobalVariableBakeResult global_variables; + RDIM_GlobalVMapBakeResult global_vmap; + RDIM_ThreadVariableBakeResult thread_variables; + RDIM_ProcedureBakeResult procedures; + RDIM_ScopeBakeResult scopes; + RDIM_InlineSiteBakeResult inline_sites; + RDIM_ScopeVMapBakeResult scope_vmap; + RDIM_TopLevelNameMapBakeResult top_level_name_maps; + RDIM_NameMapBakeResult name_maps; + RDIM_FilePathBakeResult file_paths; + RDIM_StringBakeResult strings; + RDIM_IndexRunBakeResult idx_runs; +}; + +//////////////////////////////// +//~ rjf: Serialization Types + +typedef struct RDIM_SerializedSection RDIM_SerializedSection; +struct RDIM_SerializedSection +{ + void *data; + RDI_U64 encoded_size; + RDI_U64 unpacked_size; + RDI_SectionEncoding encoding; +}; + +typedef struct RDIM_SerializedSectionBundle RDIM_SerializedSectionBundle; +struct RDIM_SerializedSectionBundle +{ + RDIM_SerializedSection sections[RDI_SectionKind_COUNT]; +}; + +//////////////////////////////// +//~ rjf: Basic Helpers + +//- rjf: memory operations +#if !defined(RDIM_MEMSET_OVERRIDE) +RDI_PROC void *rdim_memset_fallback(void *dst, RDI_U8 c, RDI_U64 size); +#endif +#if !defined(RDIM_MEMCPY_OVERRIDE) +RDI_PROC void *rdim_memcpy_fallback(void *dst, void *src, RDI_U64 size); +#endif +#define rdim_memzero(ptr, size) rdim_memset((ptr), 0, (size)) +#define rdim_memzero_struct(ptr) rdim_memset((ptr), 0, sizeof(*(ptr))) +#define rdim_memcpy_struct(dst, src) rdim_memcpy((dst), (src), sizeof(*(dst))) + +//- rjf: arenas +#if !defined(RDIM_ARENA_OVERRIDE) +RDI_PROC RDIM_Arena *rdim_arena_alloc_fallback(void); +RDI_PROC void rdim_arena_release_fallback(RDIM_Arena *arena); +RDI_PROC RDI_U64 rdim_arena_pos_fallback(RDIM_Arena *arena); +RDI_PROC void *rdim_arena_push_fallback(RDIM_Arena *arena, RDI_U64 align, RDI_U64 size); +RDI_PROC void rdim_arena_pop_to_fallback(RDIM_Arena *arena, RDI_U64 pos); +#endif +#define rdim_push_array_no_zero(a,T,c) (T*)rdim_arena_push((a), sizeof(T)*(c), RDIM_AlignOf(T)) +#define rdim_push_array(a,T,c) (T*)rdim_memzero(rdim_push_array_no_zero(a,T,c), sizeof(T)*(c)) + +//- rjf: thread-local scratch arenas +#if !defined (RDIM_SCRATCH_OVERRIDE) +RDI_PROC RDIM_Temp rdim_scratch_begin_fallback(RDIM_Arena **conflicts, RDI_U64 conflicts_count); +RDI_PROC void rdim_scratch_end_fallback(RDIM_Temp temp); +#endif + +//- rjf: strings +RDI_PROC RDIM_String8 rdim_str8(RDI_U8 *str, RDI_U64 size); +RDI_PROC RDIM_String8 rdim_str8_copy(RDIM_Arena *arena, RDIM_String8 src); +RDI_PROC RDIM_String8 rdim_str8f(RDIM_Arena *arena, char *fmt, ...); +RDI_PROC RDIM_String8 rdim_str8fv(RDIM_Arena *arena, char *fmt, va_list args); +RDI_PROC RDI_S32 rdim_str8_match(RDIM_String8 a, RDIM_String8 b, RDIM_StringMatchFlags flags); +#define rdim_str8_lit(S) rdim_str8((RDI_U8*)(S), sizeof(S) - 1) +#define rdim_str8_struct(S) rdim_str8((RDI_U8*)(S), sizeof(*(S))) +#define rdim_str8_struct_array(S, C) rdim_str8((RDI_U8*)(S), sizeof(*(S)) * (C)) + +//- rjf: string lists +RDI_PROC void rdim_str8_list_push(RDIM_Arena *arena, RDIM_String8List *list, RDIM_String8 string); +RDI_PROC void rdim_str8_list_push_front(RDIM_Arena *arena, RDIM_String8List *list, RDIM_String8 string); +RDI_PROC void rdim_str8_list_push_align(RDIM_Arena *arena, RDIM_String8List *list, RDI_U64 align); +RDI_PROC RDIM_String8 rdim_str8_list_join(RDIM_Arena *arena, RDIM_String8List *list, RDIM_String8 sep); + +//- rjf: sortable range sorting +RDI_PROC RDIM_SortKey *rdim_sort_key_array(RDIM_Arena *arena, RDIM_SortKey *keys, RDI_U64 count); + +//- rjf: rng1u64 list +RDI_PROC void rdim_rng1u64_list_push(RDIM_Arena *arena, RDIM_Rng1U64List *list, RDIM_Rng1U64 r); + +//////////////////////////////// +//~ rjf: [Building] Binary Section Info Building + +RDI_PROC RDIM_BinarySection *rdim_binary_section_list_push(RDIM_Arena *arena, RDIM_BinarySectionList *list); + +//////////////////////////////// +//~ rjf: [Building] Source File Info Building + +RDI_PROC RDIM_SrcFile *rdim_src_file_chunk_list_push(RDIM_Arena *arena, RDIM_SrcFileChunkList *list, RDI_U64 cap); +RDI_PROC RDI_U64 rdim_idx_from_src_file(RDIM_SrcFile *src_file); +RDI_PROC void rdim_src_file_chunk_list_concat_in_place(RDIM_SrcFileChunkList *dst, RDIM_SrcFileChunkList *to_push); +RDI_PROC void rdim_src_file_push_line_sequence(RDIM_Arena *arena, RDIM_SrcFileChunkList *src_files, RDIM_SrcFile *src_file, RDIM_LineSequence *seq); + +//////////////////////////////// +//~ rjf: [Building] Line Info Building + +RDI_PROC RDIM_LineTable *rdim_line_table_chunk_list_push(RDIM_Arena *arena, RDIM_LineTableChunkList *list, RDI_U64 cap); +RDI_PROC RDI_U64 rdim_idx_from_line_table(RDIM_LineTable *line_table); +RDI_PROC void rdim_line_table_chunk_list_concat_in_place(RDIM_LineTableChunkList *dst, RDIM_LineTableChunkList *to_push); +RDI_PROC RDIM_LineSequence *rdim_line_table_push_sequence(RDIM_Arena *arena, RDIM_LineTableChunkList *line_tables, RDIM_LineTable *line_table, RDIM_SrcFile *src_file, RDI_U64 *voffs, RDI_U32 *line_nums, RDI_U16 *col_nums, RDI_U64 line_count); + +//////////////////////////////// +//~ rjf: [Building] Unit Info Building + +RDI_PROC RDIM_Unit *rdim_unit_chunk_list_push(RDIM_Arena *arena, RDIM_UnitChunkList *list, RDI_U64 cap); +RDI_PROC RDI_U64 rdim_idx_from_unit(RDIM_Unit *unit); +RDI_PROC void rdim_unit_chunk_list_concat_in_place(RDIM_UnitChunkList *dst, RDIM_UnitChunkList *to_push); + +//////////////////////////////// +//~ rjf: [Building] Type Info & UDT Building + +RDI_PROC RDIM_Type *rdim_type_chunk_list_push(RDIM_Arena *arena, RDIM_TypeChunkList *list, RDI_U64 cap); +RDI_PROC RDI_U64 rdim_idx_from_type(RDIM_Type *type); +RDI_PROC void rdim_type_chunk_list_concat_in_place(RDIM_TypeChunkList *dst, RDIM_TypeChunkList *to_push); +RDI_PROC RDIM_UDT *rdim_udt_chunk_list_push(RDIM_Arena *arena, RDIM_UDTChunkList *list, RDI_U64 cap); +RDI_PROC RDI_U64 rdim_idx_from_udt(RDIM_UDT *udt); +RDI_PROC void rdim_udt_chunk_list_concat_in_place(RDIM_UDTChunkList *dst, RDIM_UDTChunkList *to_push); +RDI_PROC RDIM_UDTMember *rdim_udt_push_member(RDIM_Arena *arena, RDIM_UDTChunkList *list, RDIM_UDT *udt); +RDI_PROC RDIM_UDTEnumVal *rdim_udt_push_enum_val(RDIM_Arena *arena, RDIM_UDTChunkList *list, RDIM_UDT *udt); + +//////////////////////////////// +//~ rjf: [Building] Symbol Info Building + +RDI_PROC RDIM_Symbol *rdim_symbol_chunk_list_push(RDIM_Arena *arena, RDIM_SymbolChunkList *list, RDI_U64 cap); +RDI_PROC RDI_U64 rdim_idx_from_symbol(RDIM_Symbol *symbol); +RDI_PROC void rdim_symbol_chunk_list_concat_in_place(RDIM_SymbolChunkList *dst, RDIM_SymbolChunkList *to_push); + +//////////////////////////////// +//~ rjf: [Building] Inline Site Info Building + +RDI_PROC RDIM_InlineSite *rdim_inline_site_chunk_list_push(RDIM_Arena *arena, RDIM_InlineSiteChunkList *list, RDI_U64 cap); +RDI_PROC RDI_U64 rdim_idx_from_inline_site(RDIM_InlineSite *inline_site); +RDI_PROC void rdim_inline_site_chunk_list_concat_in_place(RDIM_InlineSiteChunkList *dst, RDIM_InlineSiteChunkList *to_push); + +//////////////////////////////// +//~ rjf: [Building] Scope Info Building + +//- rjf: scopes +RDI_PROC RDIM_Scope *rdim_scope_chunk_list_push(RDIM_Arena *arena, RDIM_ScopeChunkList *list, RDI_U64 cap); +RDI_PROC RDI_U64 rdim_idx_from_scope(RDIM_Scope *scope); +RDI_PROC void rdim_scope_chunk_list_concat_in_place(RDIM_ScopeChunkList *dst, RDIM_ScopeChunkList *to_push); +RDI_PROC void rdim_scope_push_voff_range(RDIM_Arena *arena, RDIM_ScopeChunkList *list, RDIM_Scope *scope, RDIM_Rng1U64 range); +RDI_PROC RDIM_Local *rdim_scope_push_local(RDIM_Arena *arena, RDIM_ScopeChunkList *scopes, RDIM_Scope *scope); + +//- rjf: bytecode +RDI_PROC void rdim_bytecode_push_op(RDIM_Arena *arena, RDIM_EvalBytecode *bytecode, RDI_EvalOp op, RDI_U64 p); +RDI_PROC void rdim_bytecode_push_uconst(RDIM_Arena *arena, RDIM_EvalBytecode *bytecode, RDI_U64 x); +RDI_PROC void rdim_bytecode_push_sconst(RDIM_Arena *arena, RDIM_EvalBytecode *bytecode, RDI_S64 x); +RDI_PROC void rdim_bytecode_concat_in_place(RDIM_EvalBytecode *left_dst, RDIM_EvalBytecode *right_destroyed); + +//- rjf: individual locations +RDI_PROC RDIM_Location *rdim_push_location_addr_bytecode_stream(RDIM_Arena *arena, RDIM_EvalBytecode *bytecode); +RDI_PROC RDIM_Location *rdim_push_location_val_bytecode_stream(RDIM_Arena *arena, RDIM_EvalBytecode *bytecode); +RDI_PROC RDIM_Location *rdim_push_location_addr_reg_plus_u16(RDIM_Arena *arena, RDI_U8 reg_code, RDI_U16 offset); +RDI_PROC RDIM_Location *rdim_push_location_addr_addr_reg_plus_u16(RDIM_Arena *arena, RDI_U8 reg_code, RDI_U16 offset); +RDI_PROC RDIM_Location *rdim_push_location_val_reg(RDIM_Arena *arena, RDI_U8 reg_code); + +//- rjf: location sets +RDI_PROC void rdim_location_set_push_case(RDIM_Arena *arena, RDIM_ScopeChunkList *scopes, RDIM_LocationSet *locset, RDIM_Rng1U64 voff_range, RDIM_Location *location); + +//////////////////////////////// +//~ rjf: [Baking Helpers] Baked VMap Building + +RDI_PROC RDIM_BakeVMap rdim_bake_vmap_from_markers(RDIM_Arena *arena, RDIM_VMapMarker *markers, RDIM_SortKey *keys, RDI_U64 marker_count); + +//////////////////////////////// +//~ rjf: [Baking Helpers] Interned / Deduplicated Blob Data Structure Helpers + +//- rjf: bake string chunk lists +RDI_PROC RDIM_BakeString *rdim_bake_string_chunk_list_push(RDIM_Arena *arena, RDIM_BakeStringChunkList *list, RDI_U64 cap); +RDI_PROC void rdim_bake_string_chunk_list_concat_in_place(RDIM_BakeStringChunkList *dst, RDIM_BakeStringChunkList *to_push); +RDI_PROC RDIM_BakeStringChunkList rdim_bake_string_chunk_list_sorted_from_unsorted(RDIM_Arena *arena, RDIM_BakeStringChunkList *src); + +//- rjf: bake string chunk list maps +RDI_PROC RDIM_BakeStringMapLoose *rdim_bake_string_map_loose_make(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top); +RDI_PROC void rdim_bake_string_map_loose_insert(RDIM_Arena *arena, RDIM_BakeStringMapTopology *map_topology, RDIM_BakeStringMapLoose *map, RDI_U64 chunk_cap, RDIM_String8 string); +RDI_PROC void rdim_bake_string_map_loose_join_in_place(RDIM_BakeStringMapTopology *map_topology, RDIM_BakeStringMapLoose *dst, RDIM_BakeStringMapLoose *src); +RDI_PROC RDIM_BakeStringMapBaseIndices rdim_bake_string_map_base_indices_from_map_loose(RDIM_Arena *arena, RDIM_BakeStringMapTopology *map_topology, RDIM_BakeStringMapLoose *map); + +//- rjf: finalized bake string map +RDI_PROC RDIM_BakeStringMapTight rdim_bake_string_map_tight_from_loose(RDIM_Arena *arena, RDIM_BakeStringMapTopology *map_topology, RDIM_BakeStringMapBaseIndices *map_base_indices, RDIM_BakeStringMapLoose *map); +RDI_PROC RDI_U32 rdim_bake_idx_from_string(RDIM_BakeStringMapTight *map, RDIM_String8 string); + +//- rjf: bake idx run map reading/writing +RDI_PROC RDI_U64 rdim_hash_from_idx_run(RDI_U32 *idx_run, RDI_U32 count); +RDI_PROC RDI_U32 rdim_bake_idx_from_idx_run(RDIM_BakeIdxRunMap *map, RDI_U32 *idx_run, RDI_U32 count); +RDI_PROC RDI_U32 rdim_bake_idx_run_map_insert(RDIM_Arena *arena, RDIM_BakeIdxRunMap *map, RDI_U32 *idx_run, RDI_U32 count); + +//- rjf: bake path tree reading/writing +RDI_PROC RDIM_BakePathNode *rdim_bake_path_node_from_string(RDIM_BakePathTree *tree, RDIM_String8 string); +RDI_PROC RDI_U32 rdim_bake_path_node_idx_from_string(RDIM_BakePathTree *tree, RDIM_String8 string); +RDI_PROC RDIM_BakePathNode *rdim_bake_path_tree_insert(RDIM_Arena *arena, RDIM_BakePathTree *tree, RDIM_String8 string); + +//- rjf: bake name maps writing +RDI_PROC void rdim_bake_name_map_push(RDIM_Arena *arena, RDIM_BakeNameMap *map, RDIM_String8 string, RDI_U32 idx); + +//////////////////////////////// +//~ rjf: [Baking Helpers] Data Section List Building Helpers + +RDI_PROC RDIM_BakeSection *rdim_bake_section_list_push(RDIM_Arena *arena, RDIM_BakeSectionList *list); +RDI_PROC RDIM_BakeSection *rdim_bake_section_list_push_new_unpacked(RDIM_Arena *arena, RDIM_BakeSectionList *list, void *data, RDI_U64 size, RDI_SectionKind tag, RDI_U64 tag_idx); +RDI_PROC void rdim_bake_section_list_concat_in_place(RDIM_BakeSectionList *dst, RDIM_BakeSectionList *to_push); + +//////////////////////////////// +//~ rjf: [Baking] Build Artifacts -> Interned/Deduplicated Data Structures + +//- rjf: basic bake string gathering passes +RDI_PROC void rdim_bake_string_map_loose_push_top_level_info(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_TopLevelInfo *tli); +RDI_PROC void rdim_bake_string_map_loose_push_binary_sections(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_BinarySectionList *secs); +RDI_PROC void rdim_bake_string_map_loose_push_path_tree(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_BakePathTree *path_tree); + +//- rjf: slice-granularity bake string gathering passes +RDI_PROC void rdim_bake_string_map_loose_push_src_file_slice(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_SrcFile *v, RDI_U64 count); +RDI_PROC void rdim_bake_string_map_loose_push_unit_slice(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_Unit *v, RDI_U64 count); +RDI_PROC void rdim_bake_string_map_loose_push_type_slice(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_Type *v, RDI_U64 count); +RDI_PROC void rdim_bake_string_map_loose_push_udt_slice(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_UDT *v, RDI_U64 count); +RDI_PROC void rdim_bake_string_map_loose_push_symbol_slice(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_Symbol *v, RDI_U64 count); +RDI_PROC void rdim_bake_string_map_loose_push_scope_slice(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_Scope *v, RDI_U64 count); + +//- rjf: list-granularity bake string gathering passes +RDI_PROC void rdim_bake_string_map_loose_push_src_files(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_SrcFileChunkList *list); +RDI_PROC void rdim_bake_string_map_loose_push_units(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_UnitChunkList *list); +RDI_PROC void rdim_bake_string_map_loose_push_types(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_TypeChunkList *list); +RDI_PROC void rdim_bake_string_map_loose_push_udts(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_UDTChunkList *list); +RDI_PROC void rdim_bake_string_map_loose_push_symbols(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_SymbolChunkList *list); +RDI_PROC void rdim_bake_string_map_loose_push_scopes(RDIM_Arena *arena, RDIM_BakeStringMapTopology *top, RDIM_BakeStringMapLoose *map, RDIM_ScopeChunkList *list); + +//- rjf: bake name map building +RDI_PROC RDIM_BakeNameMap *rdim_bake_name_map_from_kind_params(RDIM_Arena *arena, RDI_NameMapKind kind, RDIM_BakeParams *params); + +//- rjf: bake idx run map building +RDI_PROC RDIM_BakeIdxRunMap *rdim_bake_idx_run_map_from_params(RDIM_Arena *arena, RDIM_BakeNameMap *name_maps[RDI_NameMapKind_COUNT], RDIM_BakeParams *params); + +//- rjf: bake path tree building +RDI_PROC RDIM_BakePathTree *rdim_bake_path_tree_from_params(RDIM_Arena *arena, RDIM_BakeParams *params); + +//////////////////////////////// +//~ rjf: [Baking] Build Artifacts -> Baked Versions + +//- rjf: partial/joinable baking functions +RDI_PROC RDIM_NameMapBakeResult rdim_bake_name_map(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_BakeIdxRunMap *idx_runs, RDIM_BakeNameMap *src); + +//- rjf: partial bakes -> final bake functions +RDI_PROC RDIM_NameMapBakeResult rdim_name_map_bake_results_combine(RDIM_Arena *arena, RDIM_NameMapBakeResult *results, RDI_U64 results_count); + +//- rjf: independent (top-level, global) baking functions +RDI_PROC RDIM_TopLevelInfoBakeResult rdim_bake_top_level_info(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_TopLevelInfo *src); +RDI_PROC RDIM_BinarySectionBakeResult rdim_bake_binary_sections(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_BinarySectionList *src); +RDI_PROC RDIM_UnitBakeResult rdim_bake_units(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_BakePathTree *path_tree, RDIM_UnitChunkList *src); +RDI_PROC RDIM_UnitVMapBakeResult rdim_bake_unit_vmap(RDIM_Arena *arena, RDIM_UnitChunkList *units); +RDI_PROC RDIM_SrcFileBakeResult rdim_bake_src_files(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_BakePathTree *path_tree, RDIM_SrcFileChunkList *src); +RDI_PROC RDIM_LineTableBakeResult rdim_bake_line_tables(RDIM_Arena *arena, RDIM_LineTableChunkList *src); +RDI_PROC RDIM_TypeNodeBakeResult rdim_bake_types(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_BakeIdxRunMap *idx_runs, RDIM_TypeChunkList *src); +RDI_PROC RDIM_UDTBakeResult rdim_bake_udts(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_UDTChunkList *src); +RDI_PROC RDIM_GlobalVariableBakeResult rdim_bake_global_variables(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_SymbolChunkList *src); +RDI_PROC RDIM_GlobalVMapBakeResult rdim_bake_global_vmap(RDIM_Arena *arena, RDIM_SymbolChunkList *src); +RDI_PROC RDIM_ThreadVariableBakeResult rdim_bake_thread_variables(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_SymbolChunkList *src); +RDI_PROC RDIM_ProcedureBakeResult rdim_bake_procedures(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_SymbolChunkList *src); +RDI_PROC RDIM_ScopeBakeResult rdim_bake_scopes(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_ScopeChunkList *src); +RDI_PROC RDIM_ScopeVMapBakeResult rdim_bake_scope_vmap(RDIM_Arena *arena, RDIM_ScopeChunkList *src); +RDI_PROC RDIM_InlineSiteBakeResult rdim_bake_inline_sites(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_InlineSiteChunkList *src); +RDI_PROC RDIM_TopLevelNameMapBakeResult rdim_bake_name_maps_top_level(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_BakeIdxRunMap *idx_runs, RDIM_BakeNameMap *name_maps[RDI_NameMapKind_COUNT]); +RDI_PROC RDIM_FilePathBakeResult rdim_bake_file_paths(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings, RDIM_BakePathTree *path_tree); +RDI_PROC RDIM_StringBakeResult rdim_bake_strings(RDIM_Arena *arena, RDIM_BakeStringMapTight *strings); +RDI_PROC RDIM_IndexRunBakeResult rdim_bake_index_runs(RDIM_Arena *arena, RDIM_BakeIdxRunMap *idx_runs); + +//////////////////////////////// +//~ rjf: [Serializing] Bake Results -> String Blobs + +RDI_PROC RDIM_SerializedSection rdim_serialized_section_make_unpacked(void *data, RDI_U64 size); +#define rdim_serialized_section_make_unpacked_struct(ptr) rdim_serialized_section_make_unpacked((ptr), sizeof(*(ptr))) +#define rdim_serialized_section_make_unpacked_array(ptr, count) rdim_serialized_section_make_unpacked((ptr), sizeof(*(ptr))*(count)) +RDI_PROC RDIM_SerializedSectionBundle rdim_serialized_section_bundle_from_bake_results(RDIM_BakeResults *results); +RDI_PROC RDIM_String8List rdim_file_blobs_from_section_bundle(RDIM_Arena *arena, RDIM_SerializedSectionBundle *bundle); + +#endif // RDI_MAKE_H diff --git a/src/metagen/metagen_os/metagen_os_inc.h b/src/metagen/metagen_os/metagen_os_inc.h index 73b04029..e53f926c 100644 --- a/src/metagen/metagen_os/metagen_os_inc.h +++ b/src/metagen/metagen_os/metagen_os_inc.h @@ -1,39 +1,39 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef OS_INC_H -#define OS_INC_H - -#if !defined(OS_FEATURE_SOCKET) -# define OS_FEATURE_SOCKET 0 -#endif - -#if !defined(OS_FEATURE_GRAPHICAL) -# define OS_FEATURE_GRAPHICAL 0 -#endif - -#include "core/metagen_os_core.h" - -#if OS_FEATURE_SOCKET -#include "socket/metagen_os_socket.h" -#endif - -#if OS_FEATURE_GRAPHICAL -#include "gfx/metagen_os_gfx.h" -#endif - -#if OS_WINDOWS -# include "core/win32/metagen_os_core_win32.h" -# if OS_FEATURE_SOCKET -# include "socket/win32/metagen_os_socket_win32.h" -# endif -# if OS_FEATURE_GRAPHICAL -# include "gfx/win32/metagen_os_gfx_win32.h" -# endif -#elif OS_LINUX -# include "core/linux/metagen_os_core_linux.h" -#else -# error no OS layer setup -#endif - -#endif //OS_SWITCH_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_INC_H +#define OS_INC_H + +#if !defined(OS_FEATURE_SOCKET) +# define OS_FEATURE_SOCKET 0 +#endif + +#if !defined(OS_FEATURE_GRAPHICAL) +# define OS_FEATURE_GRAPHICAL 0 +#endif + +#include "core/metagen_os_core.h" + +#if OS_FEATURE_SOCKET +#include "socket/metagen_os_socket.h" +#endif + +#if OS_FEATURE_GRAPHICAL +#include "gfx/metagen_os_gfx.h" +#endif + +#if OS_WINDOWS +# include "core/win32/metagen_os_core_win32.h" +# if OS_FEATURE_SOCKET +# include "socket/win32/metagen_os_socket_win32.h" +# endif +# if OS_FEATURE_GRAPHICAL +# include "gfx/win32/metagen_os_gfx_win32.h" +# endif +#elif OS_LINUX +# include "core/linux/metagen_os_core_linux.h" +#else +# error no OS layer setup +#endif + +#endif // OS_INC_H diff --git a/src/mutable_text/mutable_text.c b/src/mutable_text/mutable_text.c index d1b8ff87..af7a46e3 100644 --- a/src/mutable_text/mutable_text.c +++ b/src/mutable_text/mutable_text.c @@ -1,142 +1,142 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Main Layer Initialization - -internal void -mtx_init(void) -{ - Arena *arena = arena_alloc(); - mtx_shared = push_array(arena, MTX_Shared, 1); - mtx_shared->arena = arena; - mtx_shared->slots_count = 256; - mtx_shared->stripes_count = Min(mtx_shared->slots_count, os_logical_core_count()); - mtx_shared->slots = push_array(arena, MTX_Slot, mtx_shared->slots_count); - mtx_shared->stripes = push_array(arena, MTX_Stripe, mtx_shared->stripes_count); - for(U64 idx = 0; idx < mtx_shared->stripes_count; idx += 1) - { - mtx_shared->stripes[idx].arena = arena_alloc(); - mtx_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); - } - mtx_shared->mut_threads_count = Min(os_logical_core_count(), 4); - mtx_shared->mut_threads = push_array(arena, MTX_MutThread, mtx_shared->mut_threads_count); - for(U64 idx = 0; idx < mtx_shared->mut_threads_count; idx += 1) - { - mtx_shared->mut_threads[idx].ring_size = KB(64); - mtx_shared->mut_threads[idx].ring_base = push_array_no_zero(arena, U8, mtx_shared->mut_threads[idx].ring_size); - mtx_shared->mut_threads[idx].cv = os_condition_variable_alloc(); - mtx_shared->mut_threads[idx].mutex = os_mutex_alloc(); - mtx_shared->mut_threads[idx].thread = os_launch_thread(mtx_mut_thread__entry_point, &mtx_shared->mut_threads[idx], 0); - } -} - -//////////////////////////////// -//~ rjf: Buffer Operations - -internal void -mtx_push_op(U128 buffer_key, MTX_Op op) -{ - MTX_MutThread *thread = &mtx_shared->mut_threads[buffer_key.u64[1]%mtx_shared->mut_threads_count]; - mtx_enqueue_op(thread, buffer_key, op); -} - -//////////////////////////////// -//~ rjf: Mutation Threads - -internal void -mtx_enqueue_op(MTX_MutThread *thread, U128 buffer_key, MTX_Op op) -{ - // TODO(rjf): if op.replace is too big, need to split into multiple edits - OS_MutexScope(thread->mutex) for(;;) - { - U64 unconsumed_size = thread->ring_write_pos - thread->ring_read_pos; - U64 available_size = thread->ring_size - unconsumed_size; - if(available_size >= sizeof(buffer_key) + sizeof(op.range) + sizeof(op.replace.size) + op.replace.size) - { - thread->ring_write_pos += ring_write_struct(thread->ring_base, thread->ring_size, thread->ring_write_pos, &buffer_key); - thread->ring_write_pos += ring_write_struct(thread->ring_base, thread->ring_size, thread->ring_write_pos, &op.range); - thread->ring_write_pos += ring_write_struct(thread->ring_base, thread->ring_size, thread->ring_write_pos, &op.replace.size); - thread->ring_write_pos += ring_write(thread->ring_base, thread->ring_size, thread->ring_write_pos, op.replace.str, op.replace.size); - thread->ring_write_pos += 7; - thread->ring_write_pos -= thread->ring_write_pos%8; - break; - } - os_condition_variable_wait(thread->cv, thread->mutex, max_U64); - } - os_condition_variable_broadcast(thread->cv); -} - -internal void -mtx_dequeue_op(Arena *arena, MTX_MutThread *thread, U128 *buffer_key_out, MTX_Op *op_out) -{ - OS_MutexScope(thread->mutex) for(;;) - { - U64 unconsumed_size = thread->ring_write_pos - thread->ring_read_pos; - if(unconsumed_size >= sizeof(*buffer_key_out) + sizeof(op_out->range) + sizeof(op_out->replace.size)) - { - thread->ring_read_pos += ring_read_struct(thread->ring_base, thread->ring_size, thread->ring_read_pos, buffer_key_out); - thread->ring_read_pos += ring_read_struct(thread->ring_base, thread->ring_size, thread->ring_read_pos, &op_out->range); - thread->ring_read_pos += ring_read_struct(thread->ring_base, thread->ring_size, thread->ring_read_pos, &op_out->replace.size); - op_out->replace.str = push_array_no_zero(arena, U8, op_out->replace.size); - thread->ring_read_pos += ring_read(thread->ring_base, thread->ring_size, thread->ring_read_pos, op_out->replace.str, op_out->replace.size); - thread->ring_read_pos += 7; - thread->ring_read_pos -= thread->ring_read_pos%8; - break; - } - os_condition_variable_wait(thread->cv, thread->mutex, max_U64); - } - os_condition_variable_broadcast(thread->cv); -} - -internal void -mtx_mut_thread__entry_point(void *p) -{ - MTX_MutThread *mut_thread = (MTX_MutThread *)p; - ThreadNameF("[mtx] mut thread #%I64u", (U64)(mut_thread - mtx_shared->mut_threads)); - for(;;) - { - Temp scratch = scratch_begin(0, 0); - HS_Scope *hs_scope = hs_scope_open(); - - //- rjf: get next op - U128 buffer_key = {0}; - MTX_Op op = {0}; - mtx_dequeue_op(scratch.arena, mut_thread, &buffer_key, &op); - - //- rjf: get buffer's current data - U128 hash = hs_hash_from_key(buffer_key, 0); - String8 data = hs_data_from_hash(hs_scope, hash); - - //- rjf: clamp op by data - op.range.min = Min(op.range.min, data.size); - op.range.max = Min(op.range.max, data.size); - - //- rjf: construct new buffer - if(op.range.max != op.range.min || op.replace.size != 0) - { - U64 new_data_size = data.size + op.replace.size - dim_1u64(op.range); - Arena *arena = arena_alloc__sized(new_data_size + ARENA_HEADER_SIZE, new_data_size + ARENA_HEADER_SIZE); - U8 *new_data_base = push_array_no_zero(arena, U8, new_data_size); - String8 pre_replace_data = str8_substr(data, r1u64(0, op.range.min)); - String8 post_replace_data = str8_substr(data, r1u64(op.range.max, data.size)); - if(pre_replace_data.size != 0) - { - MemoryCopy(new_data_base+0, pre_replace_data.str, pre_replace_data.size); - } - if(op.replace.size != 0) - { - MemoryCopy(new_data_base+pre_replace_data.size, op.replace.str, op.replace.size); - } - if(post_replace_data.size != 0) - { - MemoryCopy(new_data_base+pre_replace_data.size+op.replace.size, post_replace_data.str, post_replace_data.size); - } - String8 new_data = str8(new_data_base, new_data_size); - hs_submit_data(buffer_key, &arena, new_data); - } - - hs_scope_close(hs_scope); - scratch_end(scratch); - } -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Main Layer Initialization + +internal void +mtx_init(void) +{ + Arena *arena = arena_alloc(); + mtx_shared = push_array(arena, MTX_Shared, 1); + mtx_shared->arena = arena; + mtx_shared->slots_count = 256; + mtx_shared->stripes_count = Min(mtx_shared->slots_count, os_get_system_info()->logical_processor_count); + mtx_shared->slots = push_array(arena, MTX_Slot, mtx_shared->slots_count); + mtx_shared->stripes = push_array(arena, MTX_Stripe, mtx_shared->stripes_count); + for(U64 idx = 0; idx < mtx_shared->stripes_count; idx += 1) + { + mtx_shared->stripes[idx].arena = arena_alloc(); + mtx_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); + } + mtx_shared->mut_threads_count = Min(os_get_system_info()->logical_processor_count, 4); + mtx_shared->mut_threads = push_array(arena, MTX_MutThread, mtx_shared->mut_threads_count); + for(U64 idx = 0; idx < mtx_shared->mut_threads_count; idx += 1) + { + mtx_shared->mut_threads[idx].ring_size = KB(64); + mtx_shared->mut_threads[idx].ring_base = push_array_no_zero(arena, U8, mtx_shared->mut_threads[idx].ring_size); + mtx_shared->mut_threads[idx].cv = os_condition_variable_alloc(); + mtx_shared->mut_threads[idx].mutex = os_mutex_alloc(); + mtx_shared->mut_threads[idx].thread = os_thread_launch(mtx_mut_thread__entry_point, &mtx_shared->mut_threads[idx], 0); + } +} + +//////////////////////////////// +//~ rjf: Buffer Operations + +internal void +mtx_push_op(U128 buffer_key, MTX_Op op) +{ + MTX_MutThread *thread = &mtx_shared->mut_threads[buffer_key.u64[1]%mtx_shared->mut_threads_count]; + mtx_enqueue_op(thread, buffer_key, op); +} + +//////////////////////////////// +//~ rjf: Mutation Threads + +internal void +mtx_enqueue_op(MTX_MutThread *thread, U128 buffer_key, MTX_Op op) +{ + // TODO(rjf): if op.replace is too big, need to split into multiple edits + OS_MutexScope(thread->mutex) for(;;) + { + U64 unconsumed_size = thread->ring_write_pos - thread->ring_read_pos; + U64 available_size = thread->ring_size - unconsumed_size; + if(available_size >= sizeof(buffer_key) + sizeof(op.range) + sizeof(op.replace.size) + op.replace.size) + { + thread->ring_write_pos += ring_write_struct(thread->ring_base, thread->ring_size, thread->ring_write_pos, &buffer_key); + thread->ring_write_pos += ring_write_struct(thread->ring_base, thread->ring_size, thread->ring_write_pos, &op.range); + thread->ring_write_pos += ring_write_struct(thread->ring_base, thread->ring_size, thread->ring_write_pos, &op.replace.size); + thread->ring_write_pos += ring_write(thread->ring_base, thread->ring_size, thread->ring_write_pos, op.replace.str, op.replace.size); + thread->ring_write_pos += 7; + thread->ring_write_pos -= thread->ring_write_pos%8; + break; + } + os_condition_variable_wait(thread->cv, thread->mutex, max_U64); + } + os_condition_variable_broadcast(thread->cv); +} + +internal void +mtx_dequeue_op(Arena *arena, MTX_MutThread *thread, U128 *buffer_key_out, MTX_Op *op_out) +{ + OS_MutexScope(thread->mutex) for(;;) + { + U64 unconsumed_size = thread->ring_write_pos - thread->ring_read_pos; + if(unconsumed_size >= sizeof(*buffer_key_out) + sizeof(op_out->range) + sizeof(op_out->replace.size)) + { + thread->ring_read_pos += ring_read_struct(thread->ring_base, thread->ring_size, thread->ring_read_pos, buffer_key_out); + thread->ring_read_pos += ring_read_struct(thread->ring_base, thread->ring_size, thread->ring_read_pos, &op_out->range); + thread->ring_read_pos += ring_read_struct(thread->ring_base, thread->ring_size, thread->ring_read_pos, &op_out->replace.size); + op_out->replace.str = push_array_no_zero(arena, U8, op_out->replace.size); + thread->ring_read_pos += ring_read(thread->ring_base, thread->ring_size, thread->ring_read_pos, op_out->replace.str, op_out->replace.size); + thread->ring_read_pos += 7; + thread->ring_read_pos -= thread->ring_read_pos%8; + break; + } + os_condition_variable_wait(thread->cv, thread->mutex, max_U64); + } + os_condition_variable_broadcast(thread->cv); +} + +internal void +mtx_mut_thread__entry_point(void *p) +{ + MTX_MutThread *mut_thread = (MTX_MutThread *)p; + ThreadNameF("[mtx] mut thread #%I64u", (U64)(mut_thread - mtx_shared->mut_threads)); + for(;;) + { + Temp scratch = scratch_begin(0, 0); + HS_Scope *hs_scope = hs_scope_open(); + + //- rjf: get next op + U128 buffer_key = {0}; + MTX_Op op = {0}; + mtx_dequeue_op(scratch.arena, mut_thread, &buffer_key, &op); + + //- rjf: get buffer's current data + U128 hash = hs_hash_from_key(buffer_key, 0); + String8 data = hs_data_from_hash(hs_scope, hash); + + //- rjf: clamp op by data + op.range.min = Min(op.range.min, data.size); + op.range.max = Min(op.range.max, data.size); + + //- rjf: construct new buffer + if(op.range.max != op.range.min || op.replace.size != 0) + { + U64 new_data_size = data.size + op.replace.size - dim_1u64(op.range); + Arena *arena = arena_alloc(.commit_size = new_data_size + ARENA_HEADER_SIZE, .reserve_size = new_data_size + ARENA_HEADER_SIZE); + U8 *new_data_base = push_array_no_zero(arena, U8, new_data_size); + String8 pre_replace_data = str8_substr(data, r1u64(0, op.range.min)); + String8 post_replace_data = str8_substr(data, r1u64(op.range.max, data.size)); + if(pre_replace_data.size != 0) + { + MemoryCopy(new_data_base+0, pre_replace_data.str, pre_replace_data.size); + } + if(op.replace.size != 0) + { + MemoryCopy(new_data_base+pre_replace_data.size, op.replace.str, op.replace.size); + } + if(post_replace_data.size != 0) + { + MemoryCopy(new_data_base+pre_replace_data.size+op.replace.size, post_replace_data.str, post_replace_data.size); + } + String8 new_data = str8(new_data_base, new_data_size); + hs_submit_data(buffer_key, &arena, new_data); + } + + hs_scope_close(hs_scope); + scratch_end(scratch); + } +} diff --git a/src/os/core/linux/os_core_linux.c b/src/os/core/linux/os_core_linux.c index 8a6db843..8ac6bce5 100644 --- a/src/os/core/linux/os_core_linux.c +++ b/src/os/core/linux/os_core_linux.c @@ -1,1690 +1,2 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#include - -//////////////////////////////// -//~ rjf: Globals - -global pthread_mutex_t lnx_mutex = {0}; -global Arena *lnx_perm_arena = 0; -global String8List lnx_cmd_line_args = {0}; -global LNX_Entity lnx_entity_buffer[1024]; -global LNX_Entity *lnx_entity_free = 0; -global String8 lnx_initial_path = {0}; -thread_static LNX_SafeCallChain *lnx_safe_call_chain = 0; - -//////////////////////////////// -//~ rjf: Helpers - -internal B32 -lnx_write_list_to_file_descriptor(int fd, String8List list){ - B32 success = true; - - String8Node *node = list.first; - if (node != 0){ - U8 *ptr = node->string.str;; - U8 *opl = ptr + node->string.size; - - U64 p = 0; - for (;p < list.total_size;){ - U64 amt64 = (U64)(opl - ptr); - U32 amt = u32_from_u64_saturate(amt64); - S64 written_amt = write(fd, ptr, amt); - if (written_amt < 0){ - break; - } - p += written_amt; - ptr += written_amt; - - Assert(ptr <= opl); - if (ptr == opl){ - node = node->next; - if (node == 0){ - if (p < list.total_size){ - success = false; - } - break; - } - ptr = node->string.str; - opl = ptr + node->string.size; - } - } - } - - return(success); -} - -internal void -lnx_date_time_from_tm(DateTime *out, struct tm *in, U32 msec){ - out->msec = msec; - out->sec = in->tm_sec; - out->min = in->tm_min; - out->hour = in->tm_hour; - out->day = in->tm_mday - 1; - out->wday = in->tm_wday; - out->mon = in->tm_mon; - out->year = in->tm_year + 1900; -} - -internal void -lnx_tm_from_date_time(struct tm *out, DateTime *in){ - out->tm_sec = in->sec; - out->tm_min = in->min; - out->tm_hour = in->hour; - out->tm_mday = in->day + 1; - out->tm_mon = in->mon; - out->tm_year = in->year - 1900; -} - -internal void -lnx_dense_time_from_timespec(DenseTime *out, struct timespec *in){ - struct tm tm_time = {0}; - gmtime_r(&in->tv_sec, &tm_time); - DateTime date_time = {0}; - lnx_date_time_from_tm(&date_time, &tm_time, in->tv_nsec/Million(1)); - *out = dense_time_from_date_time(date_time); -} - -internal void -lnx_file_properties_from_stat(FileProperties *out, struct stat *in){ - MemoryZeroStruct(out); - out->size = in->st_size; - lnx_dense_time_from_timespec(&out->created, &in->st_ctim); - lnx_dense_time_from_timespec(&out->modified, &in->st_mtim); - if ((in->st_mode & S_IFDIR) != 0){ - out->flags |= FilePropertyFlag_IsFolder; - } -} - -internal String8 -lnx_string_from_signal(int signum){ - String8 result = str8_lit(""); - switch (signum){ - case SIGABRT: - { - result = str8_lit("SIGABRT"); - }break; - case SIGALRM: - { - result = str8_lit("SIGALRM"); - }break; - case SIGBUS: - { - result = str8_lit("SIGBUS"); - }break; - case SIGCHLD: - { - result = str8_lit("SIGCHLD"); - }break; - case SIGCONT: - { - result = str8_lit("SIGCONT"); - }break; - case SIGFPE: - { - result = str8_lit("SIGFPE"); - }break; - case SIGHUP: - { - result = str8_lit("SIGHUP"); - }break; - case SIGILL: - { - result = str8_lit("SIGILL"); - }break; - case SIGINT: - { - result = str8_lit("SIGINT"); - }break; - case SIGIO: - { - result = str8_lit("SIGIO"); - } - case SIGKILL: - { - result = str8_lit("SIGKILL"); - }break; - case SIGPIPE: - { - result = str8_lit("SIGPIPE"); - }break; - case SIGPROF: - { - result = str8_lit("SIGPROF"); - }break; - case SIGPWR: - { - result = str8_lit("SIGPWR"); - }break; - case SIGQUIT: - { - result = str8_lit("SIGQUIT"); - }break; - case SIGSEGV: - { - result = str8_lit("SIGSEGV"); - }break; - case SIGSTKFLT: - { - result = str8_lit("SIGSTKFLT"); - }break; - case SIGSTOP: - { - result = str8_lit("SIGSTOP"); - }break; - case SIGTSTP: - { - result = str8_lit("SIGTSTP"); - }break; - case SIGSYS: - { - result = str8_lit("SIGSYS"); - }break; - case SIGTERM: - { - result = str8_lit("SIGTERM"); - }break; - case SIGTRAP: - { - result = str8_lit("SIGTRAP"); - }break; - case SIGTTIN: - { - result = str8_lit("SIGTTIN"); - }break; - case SIGTTOU: - { - result = str8_lit("SIGTTOU"); - }break; - case SIGURG: - { - result = str8_lit("SIGURG"); - }break; - case SIGUSR1: - { - result = str8_lit("SIGUSR1"); - }break; - case SIGUSR2: - { - result = str8_lit("SIGUSR2"); - }break; - case SIGVTALRM: - { - result = str8_lit("SIGVTALRM"); - }break; - case SIGXCPU: - { - result = str8_lit("SIGXCPU"); - }break; - case SIGXFSZ: - { - result = str8_lit("SIGXFSZ"); - }break; - case SIGWINCH: - { - result = str8_lit("SIGWINCH"); - }break; - } - return(result); -} - -internal String8 -lnx_string_from_errno(int error_number){ - String8 result = str8_lit(""); - switch (error_number){ - case EPERM: - { - result = str8_lit("EPERM"); - }break; - case ENOENT: - { - result = str8_lit("ENOENT"); - }break; - case ESRCH: - { - result = str8_lit("ESRCH"); - }break; - case EINTR: - { - result = str8_lit("EINTR"); - }break; - case EIO: - { - result = str8_lit("EIO"); - }break; - case ENXIO: - { - result = str8_lit("ENXIO"); - }break; - case E2BIG: - { - result = str8_lit("E2BIG"); - }break; - case ENOEXEC: - { - result = str8_lit("ENOEXEC"); - }break; - case EBADF: - { - result = str8_lit("EBADF"); - }break; - case ECHILD: - { - result = str8_lit("ECHILD"); - }break; - case EAGAIN: - { - result = str8_lit("EAGAIN"); - }break; - case ENOMEM: - { - result = str8_lit("ENOMEM"); - }break; - case EACCES: - { - result = str8_lit("EACCES"); - }break; - case EFAULT: - { - result = str8_lit("EFAULT"); - }break; - case ENOTBLK: - { - result = str8_lit("ENOTBLK"); - }break; - case EBUSY: - { - result = str8_lit("EBUSY"); - }break; - case EEXIST: - { - result = str8_lit("EEXIST"); - }break; - case EXDEV: - { - result = str8_lit("EXDEV"); - }break; - case ENODEV: - { - result = str8_lit("ENODEV"); - }break; - case ENOTDIR: - { - result = str8_lit("ENOTDIR"); - }break; - case EISDIR: - { - result = str8_lit("EISDIR"); - }break; - case EINVAL: - { - result = str8_lit("EINVAL"); - }break; - case ENFILE: - { - result = str8_lit("ENFILE"); - }break; - case EMFILE: - { - result = str8_lit("EMFILE"); - }break; - case ENOTTY: - { - result = str8_lit("ENOTTY"); - }break; - case ETXTBSY: - { - result = str8_lit("ETXTBSY"); - }break; - case EFBIG: - { - result = str8_lit("EFBIG"); - }break; - case ENOSPC: - { - result = str8_lit("ENOSPC"); - }break; - case ESPIPE: - { - result = str8_lit("ESPIPE"); - }break; - case EROFS: - { - result = str8_lit("EROFS"); - }break; - case EMLINK: - { - result = str8_lit("EMLINK"); - }break; - case EPIPE: - { - result = str8_lit("EPIPE"); - }break; - case EDOM: - { - result = str8_lit("EDOM"); - }break; - case ERANGE: - { - result = str8_lit("ERANGE"); - }break; - case EDEADLK: - { - result = str8_lit("EDEADLK"); - }break; - case ENAMETOOLONG: - { - result = str8_lit("ENAMETOOLONG"); - }break; - case ENOLCK: - { - result = str8_lit("ENOLCK"); - }break; - case ENOSYS: - { - result = str8_lit("ENOSYS"); - }break; - case ENOTEMPTY: - { - result = str8_lit("ENOTEMPTY"); - }break; - case ELOOP: - { - result = str8_lit("ELOOP"); - }break; - case ENOMSG: - { - result = str8_lit("ENOMSG"); - }break; - case EIDRM: - { - result = str8_lit("EIDRM"); - }break; - case ECHRNG: - { - result = str8_lit("ECHRNG"); - }break; - case EL2NSYNC: - { - result = str8_lit("EL2NSYNC"); - }break; - case EL3HLT: - { - result = str8_lit("EL3HLT"); - }break; - case EL3RST: - { - result = str8_lit("EL3RST"); - }break; - case ELNRNG: - { - result = str8_lit("ELNRNG"); - }break; - case EUNATCH: - { - result = str8_lit("EUNATCH"); - }break; - case ENOCSI: - { - result = str8_lit("ENOCSI"); - }break; - case EL2HLT: - { - result = str8_lit("EL2HLT"); - }break; - case EBADE: - { - result = str8_lit("EBADE"); - }break; - case EBADR: - { - result = str8_lit("EBADR"); - }break; - case EXFULL: - { - result = str8_lit("EXFULL"); - }break; - case ENOANO: - { - result = str8_lit("ENOANO"); - }break; - case EBADRQC: - { - result = str8_lit("EBADRQC"); - }break; - case EBADSLT: - { - result = str8_lit("EBADSLT"); - }break; - case EBFONT: - { - result = str8_lit("EBFONT"); - }break; - case ENOSTR: - { - result = str8_lit("ENOSTR"); - }break; - case ENODATA: - { - result = str8_lit("ENODATA"); - }break; - case ETIME: - { - result = str8_lit("ETIME"); - }break; - case ENOSR: - { - result = str8_lit("ENOSR"); - }break; - case ENONET: - { - result = str8_lit("ENONET"); - }break; - case ENOPKG: - { - result = str8_lit("ENOPKG"); - }break; - case EREMOTE: - { - result = str8_lit("EREMOTE"); - }break; - case ENOLINK: - { - result = str8_lit("ENOLINK"); - }break; - case EADV: - { - result = str8_lit("EADV"); - }break; - case ESRMNT: - { - result = str8_lit("ESRMNT"); - }break; - case ECOMM: - { - result = str8_lit("ECOMM"); - }break; - case EPROTO: - { - result = str8_lit("EPROTO"); - }break; - case EMULTIHOP: - { - result = str8_lit("EMULTIHOP"); - }break; - case EDOTDOT: - { - result = str8_lit("EDOTDOT"); - }break; - case EBADMSG: - { - result = str8_lit("EBADMSG"); - }break; - case EOVERFLOW: - { - result = str8_lit("EOVERFLOW"); - }break; - case ENOTUNIQ: - { - result = str8_lit("ENOTUNIQ"); - }break; - case EBADFD: - { - result = str8_lit("EBADFD"); - }break; - case EREMCHG: - { - result = str8_lit("EREMCHG"); - }break; - case ELIBACC: - { - result = str8_lit("ELIBACC"); - }break; - case ELIBBAD: - { - result = str8_lit("ELIBBAD"); - }break; - case ELIBSCN: - { - result = str8_lit("ELIBSCN"); - }break; - case ELIBMAX: - { - result = str8_lit("ELIBMAX"); - }break; - case ELIBEXEC: - { - result = str8_lit("ELIBEXEC"); - }break; - case EILSEQ: - { - result = str8_lit("EILSEQ"); - }break; - case ERESTART: - { - result = str8_lit("ERESTART"); - }break; - case ESTRPIPE: - { - result = str8_lit("ESTRPIPE"); - }break; - case EUSERS: - { - result = str8_lit("EUSERS"); - }break; - case ENOTSOCK: - { - result = str8_lit("ENOTSOCK"); - }break; - case EDESTADDRREQ: - { - result = str8_lit("EDESTADDRREQ"); - }break; - case EMSGSIZE: - { - result = str8_lit("EMSGSIZE"); - }break; - case EPROTOTYPE: - { - result = str8_lit("EPROTOTYPE"); - }break; - case ENOPROTOOPT: - { - result = str8_lit("ENOPROTOOPT"); - }break; - case EPROTONOSUPPORT: - { - result = str8_lit("EPROTONOSUPPORT"); - }break; - case ESOCKTNOSUPPORT: - { - result = str8_lit("ESOCKTNOSUPPORT"); - }break; - case EOPNOTSUPP: - { - result = str8_lit("EOPNOTSUPP"); - }break; - case EPFNOSUPPORT: - { - result = str8_lit("EPFNOSUPPORT"); - }break; - case EAFNOSUPPORT: - { - result = str8_lit("EAFNOSUPPORT"); - }break; - case EADDRINUSE: - { - result = str8_lit("EADDRINUSE"); - }break; - case EADDRNOTAVAIL: - { - result = str8_lit("EADDRNOTAVAIL"); - }break; - case ENETDOWN: - { - result = str8_lit("ENETDOWN"); - }break; - case ENETUNREACH: - { - result = str8_lit("ENETUNREACH"); - }break; - case ENETRESET: - { - result = str8_lit("ENETRESET"); - }break; - case ECONNABORTED: - { - result = str8_lit("ECONNABORTED"); - }break; - case ECONNRESET: - { - result = str8_lit("ECONNRESET"); - }break; - case ENOBUFS: - { - result = str8_lit("ENOBUFS"); - }break; - case EISCONN: - { - result = str8_lit("EISCONN"); - }break; - case ENOTCONN: - { - result = str8_lit("ENOTCONN"); - }break; - case ESHUTDOWN: - { - result = str8_lit("ESHUTDOWN"); - }break; - case ETOOMANYREFS: - { - result = str8_lit("ETOOMANYREFS"); - }break; - case ETIMEDOUT: - { - result = str8_lit("ETIMEDOUT"); - }break; - case ECONNREFUSED: - { - result = str8_lit("ECONNREFUSED"); - }break; - case EHOSTDOWN: - { - result = str8_lit("EHOSTDOWN"); - }break; - case EHOSTUNREACH: - { - result = str8_lit("EHOSTUNREACH"); - }break; - case EALREADY: - { - result = str8_lit("EALREADY"); - }break; - case EINPROGRESS: - { - result = str8_lit("EINPROGRESS"); - }break; - case ESTALE: - { - result = str8_lit("ESTALE"); - }break; - case EUCLEAN: - { - result = str8_lit("EUCLEAN"); - }break; - case ENOTNAM: - { - result = str8_lit("ENOTNAM"); - }break; - case ENAVAIL: - { - result = str8_lit("ENAVAIL"); - }break; - case EISNAM: - { - result = str8_lit("EISNAM"); - }break; - case EREMOTEIO: - { - result = str8_lit("EREMOTEIO"); - }break; - case EDQUOT: - { - result = str8_lit("EDQUOT"); - }break; - case ENOMEDIUM: - { - result = str8_lit("ENOMEDIUM"); - }break; - case EMEDIUMTYPE: - { - result = str8_lit("EMEDIUMTYPE"); - }break; - case ECANCELED: - { - result = str8_lit("ECANCELED"); - }break; - case ENOKEY: - { - result = str8_lit("ENOKEY"); - }break; - case EKEYEXPIRED: - { - result = str8_lit("EKEYEXPIRED"); - }break; - case EKEYREVOKED: - { - result = str8_lit("EKEYREVOKED"); - }break; - case EKEYREJECTED: - { - result = str8_lit("EKEYREJECTED"); - }break; - case EOWNERDEAD: - { - result = str8_lit("EOWNERDEAD"); - }break; - case ENOTRECOVERABLE: - { - result = str8_lit("ENOTRECOVERABLE"); - }break; - case ERFKILL: - { - result = str8_lit("ERFKILL"); - }break; - case EHWPOISON: - { - result = str8_lit("EHWPOISON"); - }break; - } - return(result); -} - -internal LNX_Entity* -lnx_alloc_entity(LNX_EntityKind kind){ - pthread_mutex_lock(&lnx_mutex); - LNX_Entity *result = lnx_entity_free; - Assert(result != 0); - SLLStackPop(lnx_entity_free); - pthread_mutex_unlock(&lnx_mutex); - result->kind = kind; - return(result); -} - -internal void -lnx_free_entity(LNX_Entity *entity){ - entity->kind = LNX_EntityKind_Null; - pthread_mutex_lock(&lnx_mutex); - SLLStackPush(lnx_entity_free, entity); - pthread_mutex_unlock(&lnx_mutex); -} - -internal void* -lnx_thread_base(void *ptr){ - LNX_Entity *entity = (LNX_Entity*)ptr; - OS_ThreadFunctionType *func = entity->thread.func; - void *thread_ptr = entity->thread.ptr; - - TCTX tctx_; - tctx_init_and_equip(&tctx_); - func(thread_ptr); - tctx_release(); - - // remove my bit - U32 result = __sync_fetch_and_and(&entity->reference_mask, ~0x2); - // if the other bit is also gone, free entity - if ((result & 0x1) == 0){ - lnx_free_entity(entity); - } - return(0); -} - -internal void -lnx_safe_call_sig_handler(int){ - LNX_SafeCallChain *chain = lnx_safe_call_chain; - if (chain != 0 && chain->fail_handler != 0){ - chain->fail_handler(chain->ptr); - } - abort(); -} - -//////////////////////////////// -//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) - -internal void -os_init(void) -{ - // NOTE(allen): Initialize linux layer mutex - { - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - int pthread_result = pthread_mutex_init(&lnx_mutex, &attr); - pthread_mutexattr_destroy(&attr); - if (pthread_result == -1){ - abort(); - } - } - MemoryZeroArray(lnx_entity_buffer); - { - LNX_Entity *ptr = lnx_entity_free = lnx_entity_buffer; - for (U64 i = 1; i < ArrayCount(lnx_entity_buffer); i += 1, ptr += 1){ - ptr->next = ptr + 1; - } - ptr->next = 0; - } - - // NOTE(allen): Permanent memory allocator for this layer - Arena *perm_arena = arena_alloc(); - lnx_perm_arena = perm_arena; - - // NOTE(allen): Initialize Paths - lnx_initial_path = os_get_path(lnx_perm_arena, OS_SystemPath_Current); - - // NOTE(rjf): Setup command line args - lnx_cmd_line_args = os_string_list_from_argcv(lnx_perm_arena, argc, argv); -} - -//////////////////////////////// -//~ rjf: @os_hooks Memory Allocation (Implemented Per-OS) - -internal void* -os_reserve(U64 size){ - void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); - return(result); -} - -internal B32 -os_commit(void *ptr, U64 size){ - mprotect(ptr, size, PROT_READ|PROT_WRITE); - // TODO(allen): can we test this? - return(true); -} - -internal void* -os_reserve_large(U64 size){ - NotImplemented; - return 0; -} - -internal B32 -os_commit_large(void *ptr, U64 size){ - NotImplemented; - return 0; -} - -internal void -os_decommit(void *ptr, U64 size){ - madvise(ptr, size, MADV_DONTNEED); - mprotect(ptr, size, PROT_NONE); -} - -internal void -os_release(void *ptr, U64 size){ - munmap(ptr, size); -} - -internal void -os_set_large_pages(B32 flag) -{ - NotImplemented; -} - -internal B32 -os_large_pages_enabled(void) -{ - NotImplemented; - return 0; -} - -internal U64 -os_large_page_size(void) -{ - NotImplemented; - return 0; -} - -internal void* -os_alloc_ring_buffer(U64 size, U64 *actual_size_out) -{ - NotImplemented; - return 0; -} - -internal void -os_free_ring_buffer(void *ring_buffer, U64 actual_size) -{ - NotImplemented; -} - -//////////////////////////////// -//~ rjf: @os_hooks System Info (Implemented Per-OS) - -internal String8 -os_machine_name(void){ - local_persist B32 first = true; - local_persist String8 name = {0}; - - // TODO(allen): let's just pre-compute this at init and skip the complexity - pthread_mutex_lock(&lnx_mutex); - if (first){ - Temp scratch = scratch_begin(0, 0); - first = false; - - // get name - B32 got_final_result = false; - U8 *buffer = 0; - int size = 0; - for (S64 cap = 4096, r = 0; - r < 4; - cap *= 2, r += 1){ - scratch.restore(); - buffer = push_array_no_zero(scratch.arena, U8, cap); - size = gethostname((char*)buffer, cap); - if (size < cap){ - got_final_result = true; - break; - } - } - - // save string - if (got_final_result && size > 0){ - name.size = size; - name.str = push_array_no_zero(lnx_perm_arena, U8, name.size + 1); - MemoryCopy(name.str, buffer, name.size); - name.str[name.size] = 0; - } - - scratch_end(scratch); - } - pthread_mutex_unlock(&lnx_mutex); - - return(name); -} - -internal U64 -os_page_size(void){ - int size = getpagesize(); - return((U64)size); -} - -internal U64 -os_allocation_granularity(void) -{ - // On linux there is no equivalent of "dwAllocationGranularity" - os_page_size(); -} - -internal U64 -os_logical_core_count(void) -{ - // TODO(rjf): check this - return get_nprocs(); -} - -//////////////////////////////// -//~ rjf: @os_hooks Process & Thread Info (Implemented Per-OS) - -internal String8List -os_get_command_line_arguments(void) -{ - return lnx_cmd_line_args; -} - -internal S32 -os_get_pid(void){ - S32 result = getpid(); - return(result); -} - -internal S32 -os_get_tid(void){ - S32 result = 0; -#ifdef SYS_gettid - result = syscall(SYS_gettid); -#else - result = gettid(); -#endif - return(result); -} - -internal String8List -os_get_environment(void) -{ - NotImplemented; - String8List result = {0}; - return result; -} - -internal U64 -os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *out){ - U64 result = 0; - - switch (path){ - case OS_SystemPath_Binary: - { - local_persist B32 first = true; - local_persist String8 name = {0}; - - // TODO(allen): let's just pre-compute this at init and skip the complexity - pthread_mutex_lock(&lnx_mutex); - if (first){ - Temp scratch = scratch_begin(&arena, 1); - first = false; - - // get self string - B32 got_final_result = false; - U8 *buffer = 0; - int size = 0; - for (S64 cap = PATH_MAX, r = 0; - r < 4; - cap *= 2, r += 1){ - scratch.restore(); - buffer = push_array_no_zero(scratch.arena, U8, cap); - size = readlink("/proc/self/exe", (char*)buffer, cap); - if (size < cap){ - got_final_result = true; - break; - } - } - - // save string - if (got_final_result && size > 0){ - String8 full_name = str8(buffer, size); - String8 name_chopped = string_path_chop_last_slash(full_name); - name = push_str8_copy(lnx_perm_arena, name_chopped); - } - - scratch_end(scratch); - } - pthread_mutex_unlock(&lnx_mutex); - - result = 1; - str8_list_push(arena, out, name); - }break; - - case OS_SystemPath_Initial: - { - Assert(lnx_initial_path.str != 0); - result = 1; - str8_list_push(arena, out, lnx_initial_path); - }break; - - case OS_SystemPath_Current: - { - char *cwdir = getcwd(0, 0); - String8 string = push_str8_copy(arena, str8_cstring(cwdir)); - free(cwdir); - result = 1; - str8_list_push(arena, out, string); - }break; - - case OS_SystemPath_UserProgramData: - { - char *home = getenv("HOME"); - String8 string = str8_cstring(home); - result = 1; - str8_list_push(arena, out, string); - }break; - - case OS_SystemPath_ModuleLoad: - { - // TODO(allen): this one is big and complicated and only needed for making - // a debugger, skipping for now. - NotImplemented; - }break; - } - - return(result); -} - -//////////////////////////////// -//~ rjf: @os_hooks Process Control (Implemented Per-OS) - -internal void -os_exit_process(S32 exit_code){ - exit(exit_code); -} - -//////////////////////////////// -//~ rjf: @os_hooks File System (Implemented Per-OS) - -//- rjf: files - -internal OS_Handle -os_file_open(OS_AccessFlags flags, String8 path) -{ - OS_Handle file = {0}; - NotImplemented; - return file; -} - -internal void -os_file_close(OS_Handle file) -{ - NotImplemented; -} - -internal U64 -os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) -{ - NotImplemented; - return 0; -} - -internal void -os_file_write(OS_Handle file, Rng1U64 rng, void *data) -{ - NotImplemented; -} - -internal B32 -os_file_set_times(OS_Handle file, DateTime time) -{ - NotImplemented; -} - -internal FileProperties -os_properties_from_file(OS_Handle file) -{ - FileProperties props = {0}; - NotImplemented; - return props; -} - -internal OS_FileID -os_id_from_file(OS_Handle file) -{ - // TODO(nick): querry struct stat with fstat(2) and use st_dev and st_ino as ids - OS_FileID id = {0}; - NotImplemented; - return id; -} - -internal B32 -os_delete_file_at_path(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - B32 result = false; - String8 name_copy = push_str8_copy(scratch.arena, name); - if (remove((char*)name_copy.str) != -1){ - result = true; - } - scratch_end(scratch); - return(result); -} - -internal B32 -os_copy_file_path(String8 dst, String8 src) -{ - NotImplemented; - return 0; -} - -internal String8 -os_full_path_from_path(Arena *arena, String8 path) -{ - // TODO: realpath can be used to resolve full path - String8 result = {0}; - NotImplemented; - return result; -} - -internal B32 -os_file_path_exists(String8 path) -{ - NotImplemented; - return 0; -} - -internal FileProperties -os_properties_from_file_path(String8 path) -{ - FileProperties props = {0}; - NotImplemented; - return props; -} - -//- rjf: file maps - -internal OS_Handle -os_file_map_open(OS_AccessFlags flags, OS_Handle file) -{ - NotImplemented; - OS_Handle handle = {0}; - return handle; -} - -internal void -os_file_map_close(OS_Handle map) -{ - NotImplemented; -} - -internal void * -os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) -{ - NotImplemented; - return 0; -} - -internal void -os_file_map_view_close(OS_Handle map, void *ptr) -{ - NotImplemented; -} - -//- rjf: directory iteration - -internal OS_FileIter * -os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) -{ - NotImplemented; - return 0; -} - -internal B32 -os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) -{ - NotImplemented; - return 0; -} - -internal void -os_file_iter_end(OS_FileIter *iter) -{ - NotImplemented; -} - -//- rjf: directory creation - -internal B32 -os_make_directory(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - B32 result = false; - String8 name_copy = push_str8_copy(scratch.arena, name); - if (mkdir((char*)name_copy.str, 0777) != -1){ - result = true; - } - scratch_end(scratch); - return(result); -} - -//////////////////////////////// -//~ rjf: @os_hooks Shared Memory (Implemented Per-OS) - -internal OS_Handle -os_shared_memory_alloc(U64 size, String8 name) -{ - OS_Handle result = {0}; - NotImplemented; - return result; -} - -internal OS_Handle -os_shared_memory_open(String8 name) -{ - OS_Handle result = {0}; - NotImplemented; - return result; -} - -internal void -os_shared_memory_close(OS_Handle handle) -{ - NotImplemented; -} - -internal void * -os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) -{ - NotImplemented; - return 0; -} - -internal void -os_shared_memory_view_close(OS_Handle handle, void *ptr) -{ - NotImplemented; -} - -//////////////////////////////// -//~ rjf: @os_hooks Time (Implemented Per-OS) - -internal OS_UnixTime -os_now_unix(void) -{ - time_t t = time(0); - return (OS_UnixTime)t; -} - -internal DateTime -os_now_universal_time(void){ - time_t t = 0; - time(&t); - struct tm universal_tm = {0}; - gmtime_r(&t, &universal_tm); - DateTime result = {0}; - lnx_date_time_from_tm(&result, &universal_tm, 0); - return(result); -} - -internal DateTime -os_universal_time_from_local_time(DateTime *local_time){ - // local time -> universal time (using whatever types it takes) - struct tm local_tm = {0}; - lnx_tm_from_date_time(&local_tm, local_time); - local_tm.tm_isdst = -1; - time_t universal_t = mktime(&local_tm); - - // whatever type we ended up with -> DateTime (don't alter the space along the way) - struct tm universal_tm = {0}; - gmtime_r(&universal_t, &universal_tm); - DateTime result = {0}; - lnx_date_time_from_tm(&result, &universal_tm, 0); - return(result); -} - -internal DateTime -os_local_time_from_universal_time(DateTime *universal_time){ - // universal time -> local time (using whatever types it takes) - struct tm universal_tm = {0}; - lnx_tm_from_date_time(&universal_tm, universal_time); - universal_tm.tm_isdst = -1; - time_t universal_t = timegm(&universal_tm); - struct tm local_tm = {0}; - localtime_r(&universal_t, &local_tm); - - // whatever type we ended up with -> DateTime (don't alter the space along the way) - DateTime result = {0}; - lnx_date_time_from_tm(&result, &local_tm, 0); - return(result); -} - -internal U64 -os_now_microseconds(void){ - struct timespec t; - clock_gettime(CLOCK_MONOTONIC, &t); - U64 result = t.tv_sec*Million(1) + (t.tv_nsec/Thousand(1)); - return(result); -} - -internal void -os_sleep_milliseconds(U32 msec){ - usleep(msec*Thousand(1)); -} - -//////////////////////////////// -//~ rjf: @os_hooks Child Processes (Implemented Per-OS) - -internal B32 -os_launch_process(OS_LaunchOptions *options){ - // TODO(allen): I want to redo this API before I bother implementing it here - NotImplemented; - return(false); -} - -//////////////////////////////// -//~ rjf: @os_hooks Threads (Implemented Per-OS) - -internal OS_Handle -os_launch_thread(OS_ThreadFunctionType *func, void *ptr, void *params){ - // entity - LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_Thread); - entity->reference_mask = 0x3; - entity->thread.func = func; - entity->thread.ptr = ptr; - - // pthread - pthread_attr_t attr; - pthread_attr_init(&attr); - int pthread_result = pthread_create(&entity->thread.handle, &attr, lnx_thread_base, entity); - pthread_attr_destroy(&attr); - if (pthread_result == -1){ - lnx_free_entity(entity); - entity = 0; - } - - // cast to opaque handle - OS_Handle result = {IntFromPtr(entity)}; - return(result); -} - -internal void -os_release_thread_handle(OS_Handle thread){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(thread.id); - // remove my bit - U32 result = __sync_fetch_and_and(&entity->reference_mask, ~0x1); - // if the other bit is also gone, free entity - if ((result & 0x2) == 0){ - lnx_free_entity(entity); - } -} - -//////////////////////////////// -//~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) - -// NOTE(allen): Mutexes are recursive - support counted acquire/release nesting -// on a single thread - -//- rjf: recursive mutexes - -internal OS_Handle -os_mutex_alloc(void){ - // entity - LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_Mutex); - - // pthread - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - int pthread_result = pthread_mutex_init(&entity->mutex, &attr); - pthread_mutexattr_destroy(&attr); - if (pthread_result == -1){ - lnx_free_entity(entity); - entity = 0; - } - - // cast to opaque handle - OS_Handle result = {IntFromPtr(entity)}; - return(result); -} - -internal void -os_mutex_release(OS_Handle mutex){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.id); - pthread_mutex_destroy(&entity->mutex); - lnx_free_entity(entity); -} - -internal void -os_mutex_take_(OS_Handle mutex){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.id); - pthread_mutex_lock(&entity->mutex); -} - -internal void -os_mutex_drop_(OS_Handle mutex){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.id); - pthread_mutex_unlock(&entity->mutex); -} - -//- rjf: reader/writer mutexes - -internal OS_Handle -os_rw_mutex_alloc(void) -{ - OS_Handle result = {0}; - NotImplemented; - return result; -} - -internal void -os_rw_mutex_release(OS_Handle rw_mutex) -{ - NotImplemented; -} - -internal void -os_rw_mutex_take_r_(OS_Handle mutex) -{ - NotImplemented; -} - -internal void -os_rw_mutex_drop_r_(OS_Handle mutex) -{ - NotImplemented; -} - -internal void -os_rw_mutex_take_w_(OS_Handle mutex) -{ - NotImplemented; -} - -internal void -os_rw_mutex_drop_w_(OS_Handle mutex) -{ - NotImplemented; -} - -//- rjf: condition variables - -internal OS_Handle -os_condition_variable_alloc(void){ - // entity - LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_ConditionVariable); - - // pthread - pthread_condattr_t attr; - pthread_condattr_init(&attr); - int pthread_result = pthread_cond_init(&entity->cond, &attr); - pthread_condattr_destroy(&attr); - if (pthread_result == -1){ - lnx_free_entity(entity); - entity = 0; - } - - // cast to opaque handle - OS_Handle result = {IntFromPtr(entity)}; - return(result); -} - -internal void -os_condition_variable_release(OS_Handle cv){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.id); - pthread_cond_destroy(&entity->cond); - lnx_free_entity(entity); -} - -internal B32 -os_condition_variable_wait_(OS_Handle cv, OS_Handle mutex, U64 endt_us){ - B32 result = false; - LNX_Entity *entity_cond = (LNX_Entity*)PtrFromInt(cv.id); - LNX_Entity *entity_mutex = (LNX_Entity*)PtrFromInt(mutex.id); - // TODO(allen): implement the time control - pthread_cond_timedwait(&entity_cond->cond, &entity_mutex->mutex); - return(result); -} - -internal B32 -os_condition_variable_wait_rw_r_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) -{ - NotImplemented; - return 0; -} - -internal B32 -os_condition_variable_wait_rw_w_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) -{ - NotImplemented; - return 0; -} - -internal void -os_condition_variable_signal_(OS_Handle cv){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.id); - pthread_cond_signal(&entity->cond); -} - -internal void -os_condition_variable_broadcast_(OS_Handle cv){ - LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.id); - DontCompile; -} - -//- rjf: cross-process semaphores - -internal OS_Handle -os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) -{ - OS_Handle result = {0}; - NotImplemented; - return result; -} - -internal void -os_semaphore_release(OS_Handle semaphore) -{ - NotImplemented; -} - -internal OS_Handle -os_semaphore_open(String8 name) -{ - OS_Handle result = {0}; - NotImplemented; - return result; -} - -internal void -os_semaphore_close(OS_Handle semaphore) -{ - NotImplemented; -} - -internal B32 -os_semaphore_take(OS_Handle semaphore, U64 endt_us) -{ - NotImplemented; - return 0; -} - -internal void -os_semaphore_drop(OS_Handle semaphore) -{ - NotImplemented; -} - -//////////////////////////////// -//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) - -internal OS_Handle -os_library_open(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - char *path_cstr = (char *)push_str8_copy(scratch.arena, path).str; - void *so = dlopen(path_cstr, RTLD_LAZY); - OS_Handle lib = { (U64)so }; - scratch_end(scratch); - return lib; -} - -internal VoidProc * -os_library_load_proc(OS_Handle lib, String8 name) -{ - Temp scratch = scratch_begin(0, 0); - void *so = (void *)lib.id; - char *name_cstr = (char *)push_str8_copy(scratch.arena, name).str; - VoidProc *proc = (VoidProc *)dlsym(so, name_cstr); - scratch_end(scratch); - return proc; -} - -internal void -os_library_close(OS_Handle lib) -{ - void *so = (void *)lib.id; - dlclose(so); -} - -//////////////////////////////// -//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) - -internal void -os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, void *ptr){ - LNX_SafeCallChain chain = {0}; - SLLStackPush(lnx_safe_call_chain, &chain); - chain.fail_handler = fail_handler; - chain.ptr = ptr; - - struct sigaction new_act = {0}; - new_act.sa_handler = lnx_safe_call_sig_handler; - - int signals_to_handle[] = { - SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP, - }; - struct sigaction og_act[ArrayCount(signals_to_handle)] = {0}; - - for (U32 i = 0; i < ArrayCount(signals_to_handle); i += 1){ - sigaction(signals_to_handle[i], &new_act, &og_act[i]); - } - - func(ptr); - - for (U32 i = 0; i < ArrayCount(signals_to_handle); i += 1){ - sigaction(signals_to_handle[i], &og_act[i], 0); - } -} - -//////////////////////////////// - -internal OS_Guid -os_make_guid(void) -{ - NotImplemented; -} - +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) diff --git a/src/os/core/linux/os_core_linux.h b/src/os/core/linux/os_core_linux.h index 9899f94a..85f3dfd7 100644 --- a/src/os/core/linux/os_core_linux.h +++ b/src/os/core/linux/os_core_linux.h @@ -1,88 +1,7 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef LINUX_H -#define LINUX_H - -//////////////////////////////// -//~ NOTE(allen): Get all these linux includes - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//////////////////////////////// -//~ NOTE(allen): File Iterator - -struct LNX_FileIter{ - int fd; - DIR *dir; -}; -StaticAssert(sizeof(Member(OS_FileIter, memory)) >= sizeof(LNX_FileIter), file_iter_memory_size); - -//////////////////////////////// -//~ NOTE(allen): Threading Entities - -enum LNX_EntityKind{ - LNX_EntityKind_Null, - LNX_EntityKind_Thread, - LNX_EntityKind_Mutex, - LNX_EntityKind_ConditionVariable, -}; - -struct LNX_Entity{ - LNX_Entity *next; - LNX_EntityKind kind; - volatile U32 reference_mask; - union{ - struct{ - OS_ThreadFunctionType *func; - void *ptr; - pthread_t handle; - } thread; - pthread_mutex_t mutex; - pthread_cond_t cond; - }; -}; - -//////////////////////////////// -//~ NOTE(allen): Safe Call Chain - -struct LNX_SafeCallChain{ - LNX_SafeCallChain *next; - OS_ThreadFunctionType *fail_handler; - void *ptr; -}; - -//////////////////////////////// -//~ NOTE(allen): Helpers - -internal B32 lnx_write_list_to_file_descriptor(int fd, String8List list); - -internal void lnx_date_time_from_tm(DateTime *out, struct tm *in, U32 msec); -internal void lnx_tm_from_date_time(struct tm *out, DateTime *in); -internal void lnx_dense_time_from_timespec(DenseTime *out, struct timespec *in); -internal void lnx_file_properties_from_stat(FileProperties *out, struct stat *in); - -internal String8 lnx_string_from_signal(int signum); -internal String8 lnx_string_from_errno(int error_number); - -internal LNX_Entity* lnx_alloc_entity(LNX_EntityKind kind); -internal void lnx_free_entity(LNX_Entity *entity); -internal void* lnx_thread_base(void *ptr); - -internal void lnx_safe_call_sig_handler(int); - -#endif //LINUX_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_CORE_LINUX_H +#define OS_CORE_LINUX_H + +#endif // OS_CORE_LINUX_H diff --git a/src/os/core/linux/os_core_linux_old.c b/src/os/core/linux/os_core_linux_old.c new file mode 100644 index 00000000..f36cbb6d --- /dev/null +++ b/src/os/core/linux/os_core_linux_old.c @@ -0,0 +1,1677 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#include + +//////////////////////////////// +//~ rjf: Globals + +global pthread_mutex_t lnx_mutex = {0}; +global Arena *lnx_perm_arena = 0; +global String8List lnx_cmd_line_args = {0}; +global LNX_Entity lnx_entity_buffer[1024]; +global LNX_Entity *lnx_entity_free = 0; +global String8 lnx_initial_path = {0}; +thread_static LNX_SafeCallChain *lnx_safe_call_chain = 0; + +//////////////////////////////// +//~ rjf: Helpers + +internal B32 +lnx_write_list_to_file_descriptor(int fd, String8List list){ + B32 success = true; + + String8Node *node = list.first; + if (node != 0){ + U8 *ptr = node->string.str;; + U8 *opl = ptr + node->string.size; + + U64 p = 0; + for (;p < list.total_size;){ + U64 amt64 = (U64)(opl - ptr); + U32 amt = u32_from_u64_saturate(amt64); + S64 written_amt = write(fd, ptr, amt); + if (written_amt < 0){ + break; + } + p += written_amt; + ptr += written_amt; + + Assert(ptr <= opl); + if (ptr == opl){ + node = node->next; + if (node == 0){ + if (p < list.total_size){ + success = false; + } + break; + } + ptr = node->string.str; + opl = ptr + node->string.size; + } + } + } + + return(success); +} + +internal void +lnx_date_time_from_tm(DateTime *out, struct tm *in, U32 msec){ + out->msec = msec; + out->sec = in->tm_sec; + out->min = in->tm_min; + out->hour = in->tm_hour; + out->day = in->tm_mday - 1; + out->wday = in->tm_wday; + out->mon = in->tm_mon; + out->year = in->tm_year + 1900; +} + +internal void +lnx_tm_from_date_time(struct tm *out, DateTime *in){ + out->tm_sec = in->sec; + out->tm_min = in->min; + out->tm_hour = in->hour; + out->tm_mday = in->day + 1; + out->tm_mon = in->mon; + out->tm_year = in->year - 1900; +} + +internal void +lnx_dense_time_from_timespec(DenseTime *out, struct timespec *in){ + struct tm tm_time = {0}; + gmtime_r(&in->tv_sec, &tm_time); + DateTime date_time = {0}; + lnx_date_time_from_tm(&date_time, &tm_time, in->tv_nsec/Million(1)); + *out = dense_time_from_date_time(date_time); +} + +internal void +lnx_file_properties_from_stat(FileProperties *out, struct stat *in){ + MemoryZeroStruct(out); + out->size = in->st_size; + lnx_dense_time_from_timespec(&out->created, &in->st_ctim); + lnx_dense_time_from_timespec(&out->modified, &in->st_mtim); + if ((in->st_mode & S_IFDIR) != 0){ + out->flags |= FilePropertyFlag_IsFolder; + } +} + +internal String8 +lnx_string_from_signal(int signum){ + String8 result = str8_lit(""); + switch (signum){ + case SIGABRT: + { + result = str8_lit("SIGABRT"); + }break; + case SIGALRM: + { + result = str8_lit("SIGALRM"); + }break; + case SIGBUS: + { + result = str8_lit("SIGBUS"); + }break; + case SIGCHLD: + { + result = str8_lit("SIGCHLD"); + }break; + case SIGCONT: + { + result = str8_lit("SIGCONT"); + }break; + case SIGFPE: + { + result = str8_lit("SIGFPE"); + }break; + case SIGHUP: + { + result = str8_lit("SIGHUP"); + }break; + case SIGILL: + { + result = str8_lit("SIGILL"); + }break; + case SIGINT: + { + result = str8_lit("SIGINT"); + }break; + case SIGIO: + { + result = str8_lit("SIGIO"); + } + case SIGKILL: + { + result = str8_lit("SIGKILL"); + }break; + case SIGPIPE: + { + result = str8_lit("SIGPIPE"); + }break; + case SIGPROF: + { + result = str8_lit("SIGPROF"); + }break; + case SIGPWR: + { + result = str8_lit("SIGPWR"); + }break; + case SIGQUIT: + { + result = str8_lit("SIGQUIT"); + }break; + case SIGSEGV: + { + result = str8_lit("SIGSEGV"); + }break; + case SIGSTKFLT: + { + result = str8_lit("SIGSTKFLT"); + }break; + case SIGSTOP: + { + result = str8_lit("SIGSTOP"); + }break; + case SIGTSTP: + { + result = str8_lit("SIGTSTP"); + }break; + case SIGSYS: + { + result = str8_lit("SIGSYS"); + }break; + case SIGTERM: + { + result = str8_lit("SIGTERM"); + }break; + case SIGTRAP: + { + result = str8_lit("SIGTRAP"); + }break; + case SIGTTIN: + { + result = str8_lit("SIGTTIN"); + }break; + case SIGTTOU: + { + result = str8_lit("SIGTTOU"); + }break; + case SIGURG: + { + result = str8_lit("SIGURG"); + }break; + case SIGUSR1: + { + result = str8_lit("SIGUSR1"); + }break; + case SIGUSR2: + { + result = str8_lit("SIGUSR2"); + }break; + case SIGVTALRM: + { + result = str8_lit("SIGVTALRM"); + }break; + case SIGXCPU: + { + result = str8_lit("SIGXCPU"); + }break; + case SIGXFSZ: + { + result = str8_lit("SIGXFSZ"); + }break; + case SIGWINCH: + { + result = str8_lit("SIGWINCH"); + }break; + } + return(result); +} + +internal String8 +lnx_string_from_errno(int error_number){ + String8 result = str8_lit(""); + switch (error_number){ + case EPERM: + { + result = str8_lit("EPERM"); + }break; + case ENOENT: + { + result = str8_lit("ENOENT"); + }break; + case ESRCH: + { + result = str8_lit("ESRCH"); + }break; + case EINTR: + { + result = str8_lit("EINTR"); + }break; + case EIO: + { + result = str8_lit("EIO"); + }break; + case ENXIO: + { + result = str8_lit("ENXIO"); + }break; + case E2BIG: + { + result = str8_lit("E2BIG"); + }break; + case ENOEXEC: + { + result = str8_lit("ENOEXEC"); + }break; + case EBADF: + { + result = str8_lit("EBADF"); + }break; + case ECHILD: + { + result = str8_lit("ECHILD"); + }break; + case EAGAIN: + { + result = str8_lit("EAGAIN"); + }break; + case ENOMEM: + { + result = str8_lit("ENOMEM"); + }break; + case EACCES: + { + result = str8_lit("EACCES"); + }break; + case EFAULT: + { + result = str8_lit("EFAULT"); + }break; + case ENOTBLK: + { + result = str8_lit("ENOTBLK"); + }break; + case EBUSY: + { + result = str8_lit("EBUSY"); + }break; + case EEXIST: + { + result = str8_lit("EEXIST"); + }break; + case EXDEV: + { + result = str8_lit("EXDEV"); + }break; + case ENODEV: + { + result = str8_lit("ENODEV"); + }break; + case ENOTDIR: + { + result = str8_lit("ENOTDIR"); + }break; + case EISDIR: + { + result = str8_lit("EISDIR"); + }break; + case EINVAL: + { + result = str8_lit("EINVAL"); + }break; + case ENFILE: + { + result = str8_lit("ENFILE"); + }break; + case EMFILE: + { + result = str8_lit("EMFILE"); + }break; + case ENOTTY: + { + result = str8_lit("ENOTTY"); + }break; + case ETXTBSY: + { + result = str8_lit("ETXTBSY"); + }break; + case EFBIG: + { + result = str8_lit("EFBIG"); + }break; + case ENOSPC: + { + result = str8_lit("ENOSPC"); + }break; + case ESPIPE: + { + result = str8_lit("ESPIPE"); + }break; + case EROFS: + { + result = str8_lit("EROFS"); + }break; + case EMLINK: + { + result = str8_lit("EMLINK"); + }break; + case EPIPE: + { + result = str8_lit("EPIPE"); + }break; + case EDOM: + { + result = str8_lit("EDOM"); + }break; + case ERANGE: + { + result = str8_lit("ERANGE"); + }break; + case EDEADLK: + { + result = str8_lit("EDEADLK"); + }break; + case ENAMETOOLONG: + { + result = str8_lit("ENAMETOOLONG"); + }break; + case ENOLCK: + { + result = str8_lit("ENOLCK"); + }break; + case ENOSYS: + { + result = str8_lit("ENOSYS"); + }break; + case ENOTEMPTY: + { + result = str8_lit("ENOTEMPTY"); + }break; + case ELOOP: + { + result = str8_lit("ELOOP"); + }break; + case ENOMSG: + { + result = str8_lit("ENOMSG"); + }break; + case EIDRM: + { + result = str8_lit("EIDRM"); + }break; + case ECHRNG: + { + result = str8_lit("ECHRNG"); + }break; + case EL2NSYNC: + { + result = str8_lit("EL2NSYNC"); + }break; + case EL3HLT: + { + result = str8_lit("EL3HLT"); + }break; + case EL3RST: + { + result = str8_lit("EL3RST"); + }break; + case ELNRNG: + { + result = str8_lit("ELNRNG"); + }break; + case EUNATCH: + { + result = str8_lit("EUNATCH"); + }break; + case ENOCSI: + { + result = str8_lit("ENOCSI"); + }break; + case EL2HLT: + { + result = str8_lit("EL2HLT"); + }break; + case EBADE: + { + result = str8_lit("EBADE"); + }break; + case EBADR: + { + result = str8_lit("EBADR"); + }break; + case EXFULL: + { + result = str8_lit("EXFULL"); + }break; + case ENOANO: + { + result = str8_lit("ENOANO"); + }break; + case EBADRQC: + { + result = str8_lit("EBADRQC"); + }break; + case EBADSLT: + { + result = str8_lit("EBADSLT"); + }break; + case EBFONT: + { + result = str8_lit("EBFONT"); + }break; + case ENOSTR: + { + result = str8_lit("ENOSTR"); + }break; + case ENODATA: + { + result = str8_lit("ENODATA"); + }break; + case ETIME: + { + result = str8_lit("ETIME"); + }break; + case ENOSR: + { + result = str8_lit("ENOSR"); + }break; + case ENONET: + { + result = str8_lit("ENONET"); + }break; + case ENOPKG: + { + result = str8_lit("ENOPKG"); + }break; + case EREMOTE: + { + result = str8_lit("EREMOTE"); + }break; + case ENOLINK: + { + result = str8_lit("ENOLINK"); + }break; + case EADV: + { + result = str8_lit("EADV"); + }break; + case ESRMNT: + { + result = str8_lit("ESRMNT"); + }break; + case ECOMM: + { + result = str8_lit("ECOMM"); + }break; + case EPROTO: + { + result = str8_lit("EPROTO"); + }break; + case EMULTIHOP: + { + result = str8_lit("EMULTIHOP"); + }break; + case EDOTDOT: + { + result = str8_lit("EDOTDOT"); + }break; + case EBADMSG: + { + result = str8_lit("EBADMSG"); + }break; + case EOVERFLOW: + { + result = str8_lit("EOVERFLOW"); + }break; + case ENOTUNIQ: + { + result = str8_lit("ENOTUNIQ"); + }break; + case EBADFD: + { + result = str8_lit("EBADFD"); + }break; + case EREMCHG: + { + result = str8_lit("EREMCHG"); + }break; + case ELIBACC: + { + result = str8_lit("ELIBACC"); + }break; + case ELIBBAD: + { + result = str8_lit("ELIBBAD"); + }break; + case ELIBSCN: + { + result = str8_lit("ELIBSCN"); + }break; + case ELIBMAX: + { + result = str8_lit("ELIBMAX"); + }break; + case ELIBEXEC: + { + result = str8_lit("ELIBEXEC"); + }break; + case EILSEQ: + { + result = str8_lit("EILSEQ"); + }break; + case ERESTART: + { + result = str8_lit("ERESTART"); + }break; + case ESTRPIPE: + { + result = str8_lit("ESTRPIPE"); + }break; + case EUSERS: + { + result = str8_lit("EUSERS"); + }break; + case ENOTSOCK: + { + result = str8_lit("ENOTSOCK"); + }break; + case EDESTADDRREQ: + { + result = str8_lit("EDESTADDRREQ"); + }break; + case EMSGSIZE: + { + result = str8_lit("EMSGSIZE"); + }break; + case EPROTOTYPE: + { + result = str8_lit("EPROTOTYPE"); + }break; + case ENOPROTOOPT: + { + result = str8_lit("ENOPROTOOPT"); + }break; + case EPROTONOSUPPORT: + { + result = str8_lit("EPROTONOSUPPORT"); + }break; + case ESOCKTNOSUPPORT: + { + result = str8_lit("ESOCKTNOSUPPORT"); + }break; + case EOPNOTSUPP: + { + result = str8_lit("EOPNOTSUPP"); + }break; + case EPFNOSUPPORT: + { + result = str8_lit("EPFNOSUPPORT"); + }break; + case EAFNOSUPPORT: + { + result = str8_lit("EAFNOSUPPORT"); + }break; + case EADDRINUSE: + { + result = str8_lit("EADDRINUSE"); + }break; + case EADDRNOTAVAIL: + { + result = str8_lit("EADDRNOTAVAIL"); + }break; + case ENETDOWN: + { + result = str8_lit("ENETDOWN"); + }break; + case ENETUNREACH: + { + result = str8_lit("ENETUNREACH"); + }break; + case ENETRESET: + { + result = str8_lit("ENETRESET"); + }break; + case ECONNABORTED: + { + result = str8_lit("ECONNABORTED"); + }break; + case ECONNRESET: + { + result = str8_lit("ECONNRESET"); + }break; + case ENOBUFS: + { + result = str8_lit("ENOBUFS"); + }break; + case EISCONN: + { + result = str8_lit("EISCONN"); + }break; + case ENOTCONN: + { + result = str8_lit("ENOTCONN"); + }break; + case ESHUTDOWN: + { + result = str8_lit("ESHUTDOWN"); + }break; + case ETOOMANYREFS: + { + result = str8_lit("ETOOMANYREFS"); + }break; + case ETIMEDOUT: + { + result = str8_lit("ETIMEDOUT"); + }break; + case ECONNREFUSED: + { + result = str8_lit("ECONNREFUSED"); + }break; + case EHOSTDOWN: + { + result = str8_lit("EHOSTDOWN"); + }break; + case EHOSTUNREACH: + { + result = str8_lit("EHOSTUNREACH"); + }break; + case EALREADY: + { + result = str8_lit("EALREADY"); + }break; + case EINPROGRESS: + { + result = str8_lit("EINPROGRESS"); + }break; + case ESTALE: + { + result = str8_lit("ESTALE"); + }break; + case EUCLEAN: + { + result = str8_lit("EUCLEAN"); + }break; + case ENOTNAM: + { + result = str8_lit("ENOTNAM"); + }break; + case ENAVAIL: + { + result = str8_lit("ENAVAIL"); + }break; + case EISNAM: + { + result = str8_lit("EISNAM"); + }break; + case EREMOTEIO: + { + result = str8_lit("EREMOTEIO"); + }break; + case EDQUOT: + { + result = str8_lit("EDQUOT"); + }break; + case ENOMEDIUM: + { + result = str8_lit("ENOMEDIUM"); + }break; + case EMEDIUMTYPE: + { + result = str8_lit("EMEDIUMTYPE"); + }break; + case ECANCELED: + { + result = str8_lit("ECANCELED"); + }break; + case ENOKEY: + { + result = str8_lit("ENOKEY"); + }break; + case EKEYEXPIRED: + { + result = str8_lit("EKEYEXPIRED"); + }break; + case EKEYREVOKED: + { + result = str8_lit("EKEYREVOKED"); + }break; + case EKEYREJECTED: + { + result = str8_lit("EKEYREJECTED"); + }break; + case EOWNERDEAD: + { + result = str8_lit("EOWNERDEAD"); + }break; + case ENOTRECOVERABLE: + { + result = str8_lit("ENOTRECOVERABLE"); + }break; + case ERFKILL: + { + result = str8_lit("ERFKILL"); + }break; + case EHWPOISON: + { + result = str8_lit("EHWPOISON"); + }break; + } + return(result); +} + +internal LNX_Entity* +lnx_alloc_entity(LNX_EntityKind kind){ + pthread_mutex_lock(&lnx_mutex); + LNX_Entity *result = lnx_entity_free; + Assert(result != 0); + SLLStackPop(lnx_entity_free); + pthread_mutex_unlock(&lnx_mutex); + result->kind = kind; + return(result); +} + +internal void +lnx_free_entity(LNX_Entity *entity){ + entity->kind = LNX_EntityKind_Null; + pthread_mutex_lock(&lnx_mutex); + SLLStackPush(lnx_entity_free, entity); + pthread_mutex_unlock(&lnx_mutex); +} + +internal void* +lnx_thread_base(void *ptr){ + LNX_Entity *entity = (LNX_Entity*)ptr; + OS_ThreadFunctionType *func = entity->thread.func; + void *thread_ptr = entity->thread.ptr; + + TCTX tctx_; + tctx_init_and_equip(&tctx_); + func(thread_ptr); + tctx_release(); + + // remove my bit + U32 result = __sync_fetch_and_and(&entity->reference_mask, ~0x2); + // if the other bit is also gone, free entity + if ((result & 0x1) == 0){ + lnx_free_entity(entity); + } + return(0); +} + +internal void +lnx_safe_call_sig_handler(int){ + LNX_SafeCallChain *chain = lnx_safe_call_chain; + if (chain != 0 && chain->fail_handler != 0){ + chain->fail_handler(chain->ptr); + } + abort(); +} + +//////////////////////////////// +//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) + +internal void +os_init(void) +{ + // NOTE(allen): Initialize linux layer mutex + { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + int pthread_result = pthread_mutex_init(&lnx_mutex, &attr); + pthread_mutexattr_destroy(&attr); + if (pthread_result == -1){ + abort(); + } + } + MemoryZeroArray(lnx_entity_buffer); + { + LNX_Entity *ptr = lnx_entity_free = lnx_entity_buffer; + for (U64 i = 1; i < ArrayCount(lnx_entity_buffer); i += 1, ptr += 1){ + ptr->next = ptr + 1; + } + ptr->next = 0; + } + + // NOTE(allen): Permanent memory allocator for this layer + Arena *perm_arena = arena_alloc(); + lnx_perm_arena = perm_arena; + + // NOTE(allen): Initialize Paths + lnx_initial_path = os_get_path(lnx_perm_arena, OS_SystemPath_Current); + + // NOTE(rjf): Setup command line args + lnx_cmd_line_args = os_string_list_from_argcv(lnx_perm_arena, argc, argv); +} + +//////////////////////////////// +//~ rjf: @os_hooks Memory Allocation (Implemented Per-OS) + +internal void* +os_reserve(U64 size){ + void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + return(result); +} + +internal B32 +os_commit(void *ptr, U64 size){ + mprotect(ptr, size, PROT_READ|PROT_WRITE); + // TODO(allen): can we test this? + return(true); +} + +internal void* +os_reserve_large(U64 size){ + NotImplemented; + return 0; +} + +internal B32 +os_commit_large(void *ptr, U64 size){ + NotImplemented; + return 0; +} + +internal void +os_decommit(void *ptr, U64 size){ + madvise(ptr, size, MADV_DONTNEED); + mprotect(ptr, size, PROT_NONE); +} + +internal void +os_release(void *ptr, U64 size){ + munmap(ptr, size); +} + +internal void +os_set_large_pages_enabled(B32 flag) +{ + NotImplemented; +} + +internal B32 +os_large_pages_enabled(void) +{ + NotImplemented; + return 0; +} + +internal U64 +os_large_page_size(void) +{ + NotImplemented; + return 0; +} + +//////////////////////////////// +//~ rjf: @os_hooks System Info (Implemented Per-OS) + +internal String8 +os_machine_name(void){ + local_persist B32 first = true; + local_persist String8 name = {0}; + + // TODO(allen): let's just pre-compute this at init and skip the complexity + pthread_mutex_lock(&lnx_mutex); + if (first){ + Temp scratch = scratch_begin(0, 0); + first = false; + + // get name + B32 got_final_result = false; + U8 *buffer = 0; + int size = 0; + for (S64 cap = 4096, r = 0; + r < 4; + cap *= 2, r += 1){ + scratch.restore(); + buffer = push_array_no_zero(scratch.arena, U8, cap); + size = gethostname((char*)buffer, cap); + if (size < cap){ + got_final_result = true; + break; + } + } + + // save string + if (got_final_result && size > 0){ + name.size = size; + name.str = push_array_no_zero(lnx_perm_arena, U8, name.size + 1); + MemoryCopy(name.str, buffer, name.size); + name.str[name.size] = 0; + } + + scratch_end(scratch); + } + pthread_mutex_unlock(&lnx_mutex); + + return(name); +} + +internal U64 +os_page_size(void){ + int size = getpagesize(); + return((U64)size); +} + +internal U64 +os_allocation_granularity(void) +{ + // On linux there is no equivalent of "dwAllocationGranularity" + os_page_size(); +} + +internal U64 +os_logical_core_count(void) +{ + // TODO(rjf): check this + return get_nprocs(); +} + +//////////////////////////////// +//~ rjf: @os_hooks Process & Thread Info (Implemented Per-OS) + +internal String8List +os_get_command_line_arguments(void) +{ + return lnx_cmd_line_args; +} + +internal S32 +os_pid(void){ + S32 result = getpid(); + return(result); +} + +internal S32 +os_tid(void){ + S32 result = 0; +#ifdef SYS_gettid + result = syscall(SYS_gettid); +#else + result = gettid(); +#endif + return(result); +} + +internal String8List +os_environment(void) +{ + NotImplemented; + String8List result = {0}; + return result; +} + +internal U64 +os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *out){ + U64 result = 0; + + switch (path){ + case OS_SystemPath_Binary: + { + local_persist B32 first = true; + local_persist String8 name = {0}; + + // TODO(allen): let's just pre-compute this at init and skip the complexity + pthread_mutex_lock(&lnx_mutex); + if (first){ + Temp scratch = scratch_begin(&arena, 1); + first = false; + + // get self string + B32 got_final_result = false; + U8 *buffer = 0; + int size = 0; + for (S64 cap = PATH_MAX, r = 0; + r < 4; + cap *= 2, r += 1){ + scratch.restore(); + buffer = push_array_no_zero(scratch.arena, U8, cap); + size = readlink("/proc/self/exe", (char*)buffer, cap); + if (size < cap){ + got_final_result = true; + break; + } + } + + // save string + if (got_final_result && size > 0){ + String8 full_name = str8(buffer, size); + String8 name_chopped = string_path_chop_last_slash(full_name); + name = push_str8_copy(lnx_perm_arena, name_chopped); + } + + scratch_end(scratch); + } + pthread_mutex_unlock(&lnx_mutex); + + result = 1; + str8_list_push(arena, out, name); + }break; + + case OS_SystemPath_Initial: + { + Assert(lnx_initial_path.str != 0); + result = 1; + str8_list_push(arena, out, lnx_initial_path); + }break; + + case OS_SystemPath_Current: + { + char *cwdir = getcwd(0, 0); + String8 string = push_str8_copy(arena, str8_cstring(cwdir)); + free(cwdir); + result = 1; + str8_list_push(arena, out, string); + }break; + + case OS_SystemPath_UserProgramData: + { + char *home = getenv("HOME"); + String8 string = str8_cstring(home); + result = 1; + str8_list_push(arena, out, string); + }break; + + case OS_SystemPath_ModuleLoad: + { + // TODO(allen): this one is big and complicated and only needed for making + // a debugger, skipping for now. + NotImplemented; + }break; + } + + return(result); +} + +//////////////////////////////// +//~ rjf: @os_hooks Process Control (Implemented Per-OS) + +internal void +os_abort(S32 exit_code){ + exit(exit_code); +} + +//////////////////////////////// +//~ rjf: @os_hooks File System (Implemented Per-OS) + +//- rjf: files + +internal OS_Handle +os_file_open(OS_AccessFlags flags, String8 path) +{ + OS_Handle file = {0}; + NotImplemented; + return file; +} + +internal void +os_file_close(OS_Handle file) +{ + NotImplemented; +} + +internal U64 +os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) +{ + NotImplemented; + return 0; +} + +internal void +os_file_write(OS_Handle file, Rng1U64 rng, void *data) +{ + NotImplemented; +} + +internal B32 +os_file_set_times(OS_Handle file, DateTime time) +{ + NotImplemented; +} + +internal FileProperties +os_properties_from_file(OS_Handle file) +{ + FileProperties props = {0}; + NotImplemented; + return props; +} + +internal OS_FileID +os_id_from_file(OS_Handle file) +{ + // TODO(nick): querry struct stat with fstat(2) and use st_dev and st_ino as ids + OS_FileID id = {0}; + NotImplemented; + return id; +} + +internal B32 +os_delete_file_at_path(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + B32 result = false; + String8 name_copy = push_str8_copy(scratch.arena, name); + if (remove((char*)name_copy.str) != -1){ + result = true; + } + scratch_end(scratch); + return(result); +} + +internal B32 +os_copy_file_path(String8 dst, String8 src) +{ + NotImplemented; + return 0; +} + +internal String8 +os_full_path_from_path(Arena *arena, String8 path) +{ + // TODO: realpath can be used to resolve full path + String8 result = {0}; + NotImplemented; + return result; +} + +internal B32 +os_file_path_exists(String8 path) +{ + NotImplemented; + return 0; +} + +internal FileProperties +os_properties_from_file_path(String8 path) +{ + FileProperties props = {0}; + NotImplemented; + return props; +} + +//- rjf: file maps + +internal OS_Handle +os_file_map_open(OS_AccessFlags flags, OS_Handle file) +{ + NotImplemented; + OS_Handle handle = {0}; + return handle; +} + +internal void +os_file_map_close(OS_Handle map) +{ + NotImplemented; +} + +internal void * +os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) +{ + NotImplemented; + return 0; +} + +internal void +os_file_map_view_close(OS_Handle map, void *ptr) +{ + NotImplemented; +} + +//- rjf: directory iteration + +internal OS_FileIter * +os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) +{ + NotImplemented; + return 0; +} + +internal B32 +os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) +{ + NotImplemented; + return 0; +} + +internal void +os_file_iter_end(OS_FileIter *iter) +{ + NotImplemented; +} + +//- rjf: directory creation + +internal B32 +os_make_directory(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + B32 result = false; + String8 name_copy = push_str8_copy(scratch.arena, name); + if (mkdir((char*)name_copy.str, 0777) != -1){ + result = true; + } + scratch_end(scratch); + return(result); +} + +//////////////////////////////// +//~ rjf: @os_hooks Shared Memory (Implemented Per-OS) + +internal OS_Handle +os_shared_memory_alloc(U64 size, String8 name) +{ + OS_Handle result = {0}; + NotImplemented; + return result; +} + +internal OS_Handle +os_shared_memory_open(String8 name) +{ + OS_Handle result = {0}; + NotImplemented; + return result; +} + +internal void +os_shared_memory_close(OS_Handle handle) +{ + NotImplemented; +} + +internal void * +os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) +{ + NotImplemented; + return 0; +} + +internal void +os_shared_memory_view_close(OS_Handle handle, void *ptr) +{ + NotImplemented; +} + +//////////////////////////////// +//~ rjf: @os_hooks Time (Implemented Per-OS) + +internal U32 +os_now_unix(void) +{ + time_t t = time(0); + return (U32)t; +} + +internal DateTime +os_now_universal_time(void){ + time_t t = 0; + time(&t); + struct tm universal_tm = {0}; + gmtime_r(&t, &universal_tm); + DateTime result = {0}; + lnx_date_time_from_tm(&result, &universal_tm, 0); + return(result); +} + +internal DateTime +os_universal_time_from_local(DateTime *local_time){ + // local time -> universal time (using whatever types it takes) + struct tm local_tm = {0}; + lnx_tm_from_date_time(&local_tm, local_time); + local_tm.tm_isdst = -1; + time_t universal_t = mktime(&local_tm); + + // whatever type we ended up with -> DateTime (don't alter the space along the way) + struct tm universal_tm = {0}; + gmtime_r(&universal_t, &universal_tm); + DateTime result = {0}; + lnx_date_time_from_tm(&result, &universal_tm, 0); + return(result); +} + +internal DateTime +os_local_time_from_universal(DateTime *universal_time){ + // universal time -> local time (using whatever types it takes) + struct tm universal_tm = {0}; + lnx_tm_from_date_time(&universal_tm, universal_time); + universal_tm.tm_isdst = -1; + time_t universal_t = timegm(&universal_tm); + struct tm local_tm = {0}; + localtime_r(&universal_t, &local_tm); + + // whatever type we ended up with -> DateTime (don't alter the space along the way) + DateTime result = {0}; + lnx_date_time_from_tm(&result, &local_tm, 0); + return(result); +} + +internal U64 +os_now_microseconds(void){ + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + U64 result = t.tv_sec*Million(1) + (t.tv_nsec/Thousand(1)); + return(result); +} + +internal void +os_sleep_milliseconds(U32 msec){ + usleep(msec*Thousand(1)); +} + +//////////////////////////////// +//~ rjf: @os_hooks Child Processes (Implemented Per-OS) + +internal B32 +os_launch_process(OS_LaunchOptions *options){ + // TODO(allen): I want to redo this API before I bother implementing it here + NotImplemented; + return(false); +} + +//////////////////////////////// +//~ rjf: @os_hooks Threads (Implemented Per-OS) + +internal OS_Handle +os_thread_launch(OS_ThreadFunctionType *func, void *ptr, void *params){ + // entity + LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_Thread); + entity->reference_mask = 0x3; + entity->thread.func = func; + entity->thread.ptr = ptr; + + // pthread + pthread_attr_t attr; + pthread_attr_init(&attr); + int pthread_result = pthread_create(&entity->thread.handle, &attr, lnx_thread_base, entity); + pthread_attr_destroy(&attr); + if (pthread_result == -1){ + lnx_free_entity(entity); + entity = 0; + } + + // cast to opaque handle + OS_Handle result = {IntFromPtr(entity)}; + return(result); +} + +internal void +os_release_thread_handle(OS_Handle thread){ + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(thread.id); + // remove my bit + U32 result = __sync_fetch_and_and(&entity->reference_mask, ~0x1); + // if the other bit is also gone, free entity + if ((result & 0x2) == 0){ + lnx_free_entity(entity); + } +} + +//////////////////////////////// +//~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) + +// NOTE(allen): Mutexes are recursive - support counted acquire/release nesting +// on a single thread + +//- rjf: recursive mutexes + +internal OS_Handle +os_mutex_alloc(void){ + // entity + LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_Mutex); + + // pthread + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + int pthread_result = pthread_mutex_init(&entity->mutex, &attr); + pthread_mutexattr_destroy(&attr); + if (pthread_result == -1){ + lnx_free_entity(entity); + entity = 0; + } + + // cast to opaque handle + OS_Handle result = {IntFromPtr(entity)}; + return(result); +} + +internal void +os_mutex_release(OS_Handle mutex){ + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.id); + pthread_mutex_destroy(&entity->mutex); + lnx_free_entity(entity); +} + +internal void +os_mutex_take_(OS_Handle mutex){ + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.id); + pthread_mutex_lock(&entity->mutex); +} + +internal void +os_mutex_drop_(OS_Handle mutex){ + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(mutex.id); + pthread_mutex_unlock(&entity->mutex); +} + +//- rjf: reader/writer mutexes + +internal OS_Handle +os_rw_mutex_alloc(void) +{ + OS_Handle result = {0}; + NotImplemented; + return result; +} + +internal void +os_rw_mutex_release(OS_Handle rw_mutex) +{ + NotImplemented; +} + +internal void +os_rw_mutex_take_r_(OS_Handle mutex) +{ + NotImplemented; +} + +internal void +os_rw_mutex_drop_r_(OS_Handle mutex) +{ + NotImplemented; +} + +internal void +os_rw_mutex_take_w_(OS_Handle mutex) +{ + NotImplemented; +} + +internal void +os_rw_mutex_drop_w_(OS_Handle mutex) +{ + NotImplemented; +} + +//- rjf: condition variables + +internal OS_Handle +os_condition_variable_alloc(void){ + // entity + LNX_Entity *entity = lnx_alloc_entity(LNX_EntityKind_ConditionVariable); + + // pthread + pthread_condattr_t attr; + pthread_condattr_init(&attr); + int pthread_result = pthread_cond_init(&entity->cond, &attr); + pthread_condattr_destroy(&attr); + if (pthread_result == -1){ + lnx_free_entity(entity); + entity = 0; + } + + // cast to opaque handle + OS_Handle result = {IntFromPtr(entity)}; + return(result); +} + +internal void +os_condition_variable_release(OS_Handle cv){ + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.id); + pthread_cond_destroy(&entity->cond); + lnx_free_entity(entity); +} + +internal B32 +os_condition_variable_wait_(OS_Handle cv, OS_Handle mutex, U64 endt_us){ + B32 result = false; + LNX_Entity *entity_cond = (LNX_Entity*)PtrFromInt(cv.id); + LNX_Entity *entity_mutex = (LNX_Entity*)PtrFromInt(mutex.id); + // TODO(allen): implement the time control + pthread_cond_timedwait(&entity_cond->cond, &entity_mutex->mutex); + return(result); +} + +internal B32 +os_condition_variable_wait_rw_r_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) +{ + NotImplemented; + return 0; +} + +internal B32 +os_condition_variable_wait_rw_w_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) +{ + NotImplemented; + return 0; +} + +internal void +os_condition_variable_signal_(OS_Handle cv){ + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.id); + pthread_cond_signal(&entity->cond); +} + +internal void +os_condition_variable_broadcast_(OS_Handle cv){ + LNX_Entity *entity = (LNX_Entity*)PtrFromInt(cv.id); + DontCompile; +} + +//- rjf: cross-process semaphores + +internal OS_Handle +os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) +{ + OS_Handle result = {0}; + NotImplemented; + return result; +} + +internal void +os_semaphore_release(OS_Handle semaphore) +{ + NotImplemented; +} + +internal OS_Handle +os_semaphore_open(String8 name) +{ + OS_Handle result = {0}; + NotImplemented; + return result; +} + +internal void +os_semaphore_close(OS_Handle semaphore) +{ + NotImplemented; +} + +internal B32 +os_semaphore_take(OS_Handle semaphore, U64 endt_us) +{ + NotImplemented; + return 0; +} + +internal void +os_semaphore_drop(OS_Handle semaphore) +{ + NotImplemented; +} + +//////////////////////////////// +//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) + +internal OS_Handle +os_library_open(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + char *path_cstr = (char *)push_str8_copy(scratch.arena, path).str; + void *so = dlopen(path_cstr, RTLD_LAZY); + OS_Handle lib = { (U64)so }; + scratch_end(scratch); + return lib; +} + +internal VoidProc * +os_library_load_proc(OS_Handle lib, String8 name) +{ + Temp scratch = scratch_begin(0, 0); + void *so = (void *)lib.id; + char *name_cstr = (char *)push_str8_copy(scratch.arena, name).str; + VoidProc *proc = (VoidProc *)dlsym(so, name_cstr); + scratch_end(scratch); + return proc; +} + +internal void +os_library_close(OS_Handle lib) +{ + void *so = (void *)lib.id; + dlclose(so); +} + +//////////////////////////////// +//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) + +internal void +os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, void *ptr){ + LNX_SafeCallChain chain = {0}; + SLLStackPush(lnx_safe_call_chain, &chain); + chain.fail_handler = fail_handler; + chain.ptr = ptr; + + struct sigaction new_act = {0}; + new_act.sa_handler = lnx_safe_call_sig_handler; + + int signals_to_handle[] = { + SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP, + }; + struct sigaction og_act[ArrayCount(signals_to_handle)] = {0}; + + for (U32 i = 0; i < ArrayCount(signals_to_handle); i += 1){ + sigaction(signals_to_handle[i], &new_act, &og_act[i]); + } + + func(ptr); + + for (U32 i = 0; i < ArrayCount(signals_to_handle); i += 1){ + sigaction(signals_to_handle[i], &og_act[i], 0); + } +} + +//////////////////////////////// + +internal OS_Guid +os_make_guid(void) +{ + NotImplemented; +} + diff --git a/src/os/core/linux/os_core_linux_old.h b/src/os/core/linux/os_core_linux_old.h new file mode 100644 index 00000000..4b6263d3 --- /dev/null +++ b/src/os/core/linux/os_core_linux_old.h @@ -0,0 +1,88 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef LINUX_H +#define LINUX_H + +//////////////////////////////// +//~ NOTE(allen): Get all these linux includes + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//////////////////////////////// +//~ NOTE(allen): File Iterator + +struct LNX_FileIter{ + int fd; + DIR *dir; +}; +StaticAssert(sizeof(Member(OS_FileIter, memory)) >= sizeof(LNX_FileIter), file_iter_memory_size); + +//////////////////////////////// +//~ NOTE(allen): Threading Entities + +enum LNX_EntityKind{ + LNX_EntityKind_Null, + LNX_EntityKind_Thread, + LNX_EntityKind_Mutex, + LNX_EntityKind_ConditionVariable, +}; + +struct LNX_Entity{ + LNX_Entity *next; + LNX_EntityKind kind; + volatile U32 reference_mask; + union{ + struct{ + OS_ThreadFunctionType *func; + void *ptr; + pthread_t handle; + } thread; + pthread_mutex_t mutex; + pthread_cond_t cond; + }; +}; + +//////////////////////////////// +//~ NOTE(allen): Safe Call Chain + +struct LNX_SafeCallChain{ + LNX_SafeCallChain *next; + OS_ThreadFunctionType *fail_handler; + void *ptr; +}; + +//////////////////////////////// +//~ NOTE(allen): Helpers + +internal B32 lnx_write_list_to_file_descriptor(int fd, String8List list); + +internal void lnx_date_time_from_tm(DateTime *out, struct tm *in, U32 msec); +internal void lnx_tm_from_date_time(struct tm *out, DateTime *in); +internal void lnx_dense_time_from_timespec(DenseTime *out, struct timespec *in); +internal void lnx_file_properties_from_stat(FileProperties *out, struct stat *in); + +internal String8 lnx_string_from_signal(int signum); +internal String8 lnx_string_from_errno(int error_number); + +internal LNX_Entity* lnx_alloc_entity(LNX_EntityKind kind); +internal void lnx_free_entity(LNX_Entity *entity); +internal void* lnx_thread_base(void *ptr); + +internal void lnx_safe_call_sig_handler(int); + +#endif //LINUX_H diff --git a/src/os/core/os_core.c b/src/os/core/os_core.c index cf96fe1f..a6718e32 100644 --- a/src/os/core/os_core.c +++ b/src/os/core/os_core.c @@ -1,243 +1,173 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Handle Type Functions (Helpers, Implemented Once) - -internal OS_Handle -os_handle_zero(void) -{ - OS_Handle handle = {0}; - return handle; -} - -internal B32 -os_handle_match(OS_Handle a, OS_Handle b) -{ - return a.u64[0] == b.u64[0]; -} - -internal void -os_handle_list_push(Arena *arena, OS_HandleList *handles, OS_Handle handle) -{ - OS_HandleNode *n = push_array(arena, OS_HandleNode, 1); - n->v = handle; - SLLQueuePush(handles->first, handles->last, n); - handles->count += 1; -} - -internal OS_HandleArray -os_handle_array_from_list(Arena *arena, OS_HandleList *list) -{ - OS_HandleArray result = {0}; - result.count = list->count; - result.v = push_array_no_zero(arena, OS_Handle, result.count); - U64 idx = 0; - for(OS_HandleNode *n = list->first; n != 0; n = n->next, idx += 1) - { - result.v[idx] = n->v; - } - return result; -} - -//////////////////////////////// -//~ rjf: System Path Helper (Helper, Implemented Once) - -internal String8 -os_string_from_system_path(Arena *arena, OS_SystemPath path) -{ - String8List strs = {0}; - os_string_list_from_system_path(arena, path, &strs); - String8 result = str8_list_first(&strs); - return result; -} - -//////////////////////////////// -//~ rjf: Command Line Argc/Argv Helper (Helper, Implemented Once) - -internal String8List -os_string_list_from_argcv(Arena *arena, int argc, char **argv) -{ - String8List result = {0}; - for(int i = 0; i < argc; i += 1) - { - String8 str = str8_cstring(argv[i]); - str8_list_push(arena, &result, str); - } - return result; -} - -//////////////////////////////// -//~ rjf: Filesystem Helpers (Helpers, Implemented Once) - -internal String8 -os_data_from_file_path(Arena *arena, String8 path) -{ - OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead, path); - FileProperties props = os_properties_from_file(file); - String8 data = os_string_from_file_range(arena, file, r1u64(0, props.size)); - os_file_close(file); - return data; -} - -internal B32 -os_write_data_to_file_path(String8 path, String8 data) -{ - B32 good = 0; - OS_Handle file = os_file_open(OS_AccessFlag_Write, path); - if(!os_handle_match(file, os_handle_zero())) - { - good = 1; - os_file_write(file, r1u64(0, data.size), data.str); - os_file_close(file); - } - return good; -} - -internal B32 -os_write_data_list_to_file_path(String8 path, String8List list) -{ - B32 good = 0; - OS_Handle file = os_file_open(OS_AccessFlag_Write, path); - if(!os_handle_match(file, os_handle_zero())) - { - good = 1; - U64 off = 0; - for(String8Node *n = list.first; n != 0; n = n->next) - { - os_file_write(file, r1u64(off, off+n->string.size), n->string.str); - off += n->string.size; - } - os_file_close(file); - } - return good; -} - -internal B32 -os_append_data_to_file_path(String8 path, String8 data) -{ - B32 good = 0; - if(data.size != 0) - { - OS_Handle file = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Append, path); - if(!os_handle_match(file, os_handle_zero())) - { - good = 1; - U64 pos = os_properties_from_file(file).size; - os_file_write(file, r1u64(pos, pos+data.size), data.str); - os_file_close(file); - } - } - return good; -} - -internal OS_FileID -os_id_from_file_path(String8 path) -{ - OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead, path); - OS_FileID id = os_id_from_file(file); - os_file_close(file); - return id; -} - -internal S64 -os_file_id_compare(OS_FileID a, OS_FileID b) -{ - S64 cmp = MemoryCompare((void*)&a.v[0], (void*)&b.v[0], sizeof(a.v)); - return cmp; -} - -internal String8 -os_string_from_file_range(Arena *arena, OS_Handle file, Rng1U64 range) -{ - U64 pre_pos = arena_pos(arena); - String8 result; - result.size = dim_1u64(range); - result.str = push_array_no_zero(arena, U8, result.size); - U64 actual_read_size = os_file_read(file, range, result.str); - if(actual_read_size < result.size) - { - arena_pop_to(arena, pre_pos + actual_read_size); - result.size = actual_read_size; - } - return result; -} - -//////////////////////////////// -//~ rjf: Synchronization Primitive Helpers (Helpers, Implemented Once) - -internal void -os_mutex_take(OS_Handle mutex){ - os_mutex_take_(mutex); -} - -internal void -os_mutex_drop(OS_Handle mutex){ - os_mutex_drop_(mutex); -} - -internal void -os_rw_mutex_take_r(OS_Handle rw_mutex){ - os_rw_mutex_take_r_(rw_mutex); -} - -internal void -os_rw_mutex_drop_r(OS_Handle rw_mutex){ - os_rw_mutex_drop_r_(rw_mutex); -} - -internal void -os_rw_mutex_take_w(OS_Handle rw_mutex){ - os_rw_mutex_take_w_(rw_mutex); -} - -internal void -os_rw_mutex_drop_w(OS_Handle rw_mutex){ - os_rw_mutex_drop_w_(rw_mutex); -} - -internal B32 -os_condition_variable_wait(OS_Handle cv, OS_Handle mutex, U64 endt_us){ - B32 result = os_condition_variable_wait_(cv, mutex, endt_us); - return(result); -} - -internal B32 -os_condition_variable_wait_rw_r(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us){ - B32 result = os_condition_variable_wait_rw_r_(cv, mutex_rw, endt_us); - return(result); -} - -internal B32 -os_condition_variable_wait_rw_w(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us){ - B32 result = os_condition_variable_wait_rw_w_(cv, mutex_rw, endt_us); - return(result); -} - -internal void -os_condition_variable_signal(OS_Handle cv){ - os_condition_variable_signal_(cv); -} - -internal void -os_condition_variable_broadcast(OS_Handle cv){ - os_condition_variable_broadcast_(cv); -} - -internal String8 -os_string_from_guid(Arena *arena, OS_Guid guid) -{ - String8 result = push_str8f(arena, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", - guid.data1, - guid.data2, - guid.data3, - guid.data4[0], - guid.data4[1], - guid.data4[2], - guid.data4[3], - guid.data4[4], - guid.data4[5], - guid.data4[6], - guid.data4[7]); - return result; -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Handle Type Functions (Helpers, Implemented Once) + +internal OS_Handle +os_handle_zero(void) +{ + OS_Handle handle = {0}; + return handle; +} + +internal B32 +os_handle_match(OS_Handle a, OS_Handle b) +{ + return a.u64[0] == b.u64[0]; +} + +internal void +os_handle_list_push(Arena *arena, OS_HandleList *handles, OS_Handle handle) +{ + OS_HandleNode *n = push_array(arena, OS_HandleNode, 1); + n->v = handle; + SLLQueuePush(handles->first, handles->last, n); + handles->count += 1; +} + +internal OS_HandleArray +os_handle_array_from_list(Arena *arena, OS_HandleList *list) +{ + OS_HandleArray result = {0}; + result.count = list->count; + result.v = push_array_no_zero(arena, OS_Handle, result.count); + U64 idx = 0; + for(OS_HandleNode *n = list->first; n != 0; n = n->next, idx += 1) + { + result.v[idx] = n->v; + } + return result; +} + +//////////////////////////////// +//~ rjf: Command Line Argc/Argv Helper (Helper, Implemented Once) + +internal String8List +os_string_list_from_argcv(Arena *arena, int argc, char **argv) +{ + String8List result = {0}; + for(int i = 0; i < argc; i += 1) + { + String8 str = str8_cstring(argv[i]); + str8_list_push(arena, &result, str); + } + return result; +} + +//////////////////////////////// +//~ rjf: Filesystem Helpers (Helpers, Implemented Once) + +internal String8 +os_data_from_file_path(Arena *arena, String8 path) +{ + OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead, path); + FileProperties props = os_properties_from_file(file); + String8 data = os_string_from_file_range(arena, file, r1u64(0, props.size)); + os_file_close(file); + return data; +} + +internal B32 +os_write_data_to_file_path(String8 path, String8 data) +{ + B32 good = 0; + OS_Handle file = os_file_open(OS_AccessFlag_Write, path); + if(!os_handle_match(file, os_handle_zero())) + { + good = 1; + os_file_write(file, r1u64(0, data.size), data.str); + os_file_close(file); + } + return good; +} + +internal B32 +os_write_data_list_to_file_path(String8 path, String8List list) +{ + B32 good = 0; + OS_Handle file = os_file_open(OS_AccessFlag_Write, path); + if(!os_handle_match(file, os_handle_zero())) + { + good = 1; + U64 off = 0; + for(String8Node *n = list.first; n != 0; n = n->next) + { + os_file_write(file, r1u64(off, off+n->string.size), n->string.str); + off += n->string.size; + } + os_file_close(file); + } + return good; +} + +internal B32 +os_append_data_to_file_path(String8 path, String8 data) +{ + B32 good = 0; + if(data.size != 0) + { + OS_Handle file = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Append, path); + if(!os_handle_match(file, os_handle_zero())) + { + good = 1; + U64 pos = os_properties_from_file(file).size; + os_file_write(file, r1u64(pos, pos+data.size), data.str); + os_file_close(file); + } + } + return good; +} + +internal OS_FileID +os_id_from_file_path(String8 path) +{ + OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead, path); + OS_FileID id = os_id_from_file(file); + os_file_close(file); + return id; +} + +internal S64 +os_file_id_compare(OS_FileID a, OS_FileID b) +{ + S64 cmp = MemoryCompare((void*)&a.v[0], (void*)&b.v[0], sizeof(a.v)); + return cmp; +} + +internal String8 +os_string_from_file_range(Arena *arena, OS_Handle file, Rng1U64 range) +{ + U64 pre_pos = arena_pos(arena); + String8 result; + result.size = dim_1u64(range); + result.str = push_array_no_zero(arena, U8, result.size); + U64 actual_read_size = os_file_read(file, range, result.str); + if(actual_read_size < result.size) + { + arena_pop_to(arena, pre_pos + actual_read_size); + result.size = actual_read_size; + } + return result; +} + +//////////////////////////////// +//~ rjf: GUID Helpers (Helpers, Implemented Once) + +internal String8 +os_string_from_guid(Arena *arena, OS_Guid guid) +{ + String8 result = push_str8f(arena, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", + guid.data1, + guid.data2, + guid.data3, + guid.data4[0], + guid.data4[1], + guid.data4[2], + guid.data4[3], + guid.data4[4], + guid.data4[5], + guid.data4[6], + guid.data4[7]); + return result; +} diff --git a/src/os/core/os_core.h b/src/os/core/os_core.h index d618d405..f0b56cd4 100644 --- a/src/os/core/os_core.h +++ b/src/os/core/os_core.h @@ -1,380 +1,339 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef OS_CORE_H -#define OS_CORE_H - -//////////////////////////////// -//~ rjf: Access Flags - -typedef U32 OS_AccessFlags; -enum -{ - OS_AccessFlag_Read = (1<<0), - OS_AccessFlag_Write = (1<<1), - OS_AccessFlag_Execute = (1<<2), - OS_AccessFlag_Append = (1<<3), - OS_AccessFlag_ShareRead = (1<<4), - OS_AccessFlag_ShareWrite = (1<<5), -}; - -//////////////////////////////// -//~ allen: Files - -typedef U32 OS_FileIterFlags; -enum -{ - OS_FileIterFlag_SkipFolders = (1 << 0), - OS_FileIterFlag_SkipFiles = (1 << 1), - OS_FileIterFlag_SkipHiddenFiles = (1 << 2), - OS_FileIterFlag_Done = (1 << 31), -}; - -typedef struct OS_FileIter OS_FileIter; -struct OS_FileIter -{ - OS_FileIterFlags flags; - U8 memory[800]; -}; - -typedef struct OS_FileInfo OS_FileInfo; -struct OS_FileInfo -{ - String8 name; - FileProperties props; -}; - -// nick: on-disk file identifier -typedef struct OS_FileID OS_FileID; -struct OS_FileID -{ - U64 v[3]; -}; - -//////////////////////////////// -//~ rjf: System Paths - -typedef enum OS_SystemPath -{ - OS_SystemPath_Binary, - OS_SystemPath_Initial, - OS_SystemPath_Current, - OS_SystemPath_UserProgramData, - OS_SystemPath_ModuleLoad, -} -OS_SystemPath; - -typedef enum OS_PathFromUserKind -{ - OS_PathFromUserKind_Save, - OS_PathFromUserKind_Load, -} -OS_PathFromUserKind; - -typedef struct OS_PathFromUser OS_PathFromUser; -struct OS_PathFromUser -{ - OS_PathFromUserKind kind; - String8 path; - U64 filter_count; - String8 *filter_extensions; - String8 *filter_names; -}; - -//////////////////////////////// -//~ allen: Launch Input - -typedef struct OS_LaunchOptions OS_LaunchOptions; -struct OS_LaunchOptions -{ - String8List cmd_line; - String8 path; - String8List env; - B32 inherit_env; - B32 consoleless; -}; - -//////////////////////////////// -//~ rjf: Handle Type - -typedef struct OS_Handle OS_Handle; -struct OS_Handle -{ - U64 u64[1]; -}; - -typedef struct OS_HandleNode OS_HandleNode; -struct OS_HandleNode -{ - OS_HandleNode *next; - OS_Handle v; -}; - -typedef struct OS_HandleList OS_HandleList; -struct OS_HandleList -{ - OS_HandleNode *first; - OS_HandleNode *last; - U64 count; -}; - -typedef struct OS_HandleArray OS_HandleArray; -struct OS_HandleArray -{ - OS_Handle *v; - U64 count; -}; - -//////////////////////////////// -// Time - -#define OS_UNIX_TIME_MAX max_U32 -typedef U32 OS_UnixTime; - -//////////////////////////////// -// Global Unique ID - -typedef struct OS_Guid -{ - U32 data1; - U16 data2; - U16 data3; - U8 data4[8]; -} OS_Guid; -StaticAssert(sizeof(OS_Guid) == 16, os_guid_check); - -//////////////////////////////// -//~ rjf: Thread Types - -typedef void OS_ThreadFunctionType(void *ptr); - -//////////////////////////////// -//~ rjf: Handle Type Functions (Helpers, Implemented Once) - -internal OS_Handle os_handle_zero(void); -internal B32 os_handle_match(OS_Handle a, OS_Handle b); -internal void os_handle_list_push(Arena *arena, OS_HandleList *handles, OS_Handle handle); -internal OS_HandleArray os_handle_array_from_list(Arena *arena, OS_HandleList *list); - -//////////////////////////////// -//~ rjf: System Path Helper (Helper, Implemented Once) - -internal String8 os_string_from_system_path(Arena *arena, OS_SystemPath path); - -//////////////////////////////// -//~ rjf: Command Line Argc/Argv Helper (Helper, Implemented Once) - -internal String8List os_string_list_from_argcv(Arena *arena, int argc, char **argv); - -//////////////////////////////// -//~ rjf: Filesystem Helpers (Helpers, Implemented Once) - -internal String8 os_data_from_file_path(Arena *arena, String8 path); -internal B32 os_write_data_to_file_path(String8 path, String8 data); -internal B32 os_write_data_list_to_file_path(String8 path, String8List list); -internal B32 os_append_data_to_file_path(String8 path, String8 data); -internal OS_FileID os_id_from_file_path(String8 path); -internal S64 os_file_id_compare(OS_FileID a, OS_FileID b); -internal String8 os_string_from_file_range(Arena *arena, OS_Handle file, Rng1U64 range); - -//////////////////////////////// -//~ rjf: Synchronization Primitive Helpers (Helpers, Implemented Once) - -internal void os_mutex_take(OS_Handle mutex); -internal void os_mutex_drop(OS_Handle mutex); -internal void os_rw_mutex_take_r(OS_Handle rw_mutex); -internal void os_rw_mutex_drop_r(OS_Handle rw_mutex); -internal void os_rw_mutex_take_w(OS_Handle rw_mutex); -internal void os_rw_mutex_drop_w(OS_Handle rw_mutex); -// returns false on timeout, true on signal, (max_wait_ms = max_U64) -> no timeout -internal B32 os_condition_variable_wait(OS_Handle cv, OS_Handle mutex, U64 endt_us); -internal B32 os_condition_variable_wait_rw_r(OS_Handle cv, OS_Handle rw_mutex, U64 endt_us); -internal B32 os_condition_variable_wait_rw_w(OS_Handle cv, OS_Handle rw_mutex, U64 endt_us); -internal void os_condition_variable_signal(OS_Handle cv); -internal void os_condition_variable_broadcast(OS_Handle cv); - -#define OS_MutexScope(mutex) DeferLoop(os_mutex_take(mutex), os_mutex_drop(mutex)) -#define OS_MutexScopeR(mutex) DeferLoop(os_rw_mutex_take_r(mutex), os_rw_mutex_drop_r(mutex)) -#define OS_MutexScopeW(mutex) DeferLoop(os_rw_mutex_take_w(mutex), os_rw_mutex_drop_w(mutex)) -#define OS_MutexScopeRWPromote(mutex) DeferLoop((os_rw_mutex_drop_r(mutex), os_rw_mutex_take_w(mutex)), (os_rw_mutex_drop_w(mutex), os_rw_mutex_take_r(mutex))) - -//////////////////////////////// -//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) - -internal void os_init(void); - -//////////////////////////////// -//~ rjf: @os_hooks Memory Allocation (Implemented Per-OS) - -internal void* os_reserve(U64 size); -internal B32 os_commit(void *ptr, U64 size); -internal void* os_reserve_large(U64 size); -internal B32 os_commit_large(void *ptr, U64 size); -internal void os_decommit(void *ptr, U64 size); -internal void os_release(void *ptr, U64 size); - -internal B32 os_set_large_pages(B32 flag); -internal B32 os_large_pages_enabled(void); -internal U64 os_large_page_size(void); - -internal void* os_alloc_ring_buffer(U64 size, U64 *actual_size_out); -internal void os_free_ring_buffer(void *ring_buffer, U64 actual_size); - -//////////////////////////////// -//~ rjf: @os_hooks System Info (Implemented Per-OS) - -internal String8 os_machine_name(void); -internal U64 os_page_size(void); -internal U64 os_allocation_granularity(void); -internal U64 os_logical_core_count(void); - -//////////////////////////////// -//~ rjf: @os_hooks Process & Thread Info (Implemented Per-OS) - -internal S32 os_get_pid(void); -internal S32 os_get_tid(void); -internal String8List os_get_environment(void); -internal U64 os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *out); - -//////////////////////////////// -//~ rjf: @os_hooks Thread Names - -internal void os_set_thread_name(String8 string); - -//////////////////////////////// -//~ rjf: @os_hooks Process Control (Implemented Per-OS) - -internal void os_exit_process(S32 exit_code); - -//////////////////////////////// -//~ rjf: @os_hooks File System (Implemented Per-OS) - -//- rjf: files -internal OS_Handle os_file_open(OS_AccessFlags flags, String8 path); -internal void os_file_close(OS_Handle file); -internal U64 os_file_read(OS_Handle file, Rng1U64 rng, void *out_data); -internal void os_file_write(OS_Handle file, Rng1U64 rng, void *data); -internal B32 os_file_set_times(OS_Handle file, DateTime time); -internal FileProperties os_properties_from_file(OS_Handle file); -internal OS_FileID os_id_from_file(OS_Handle file); -internal B32 os_delete_file_at_path(String8 path); -internal B32 os_copy_file_path(String8 dst, String8 src); -internal String8 os_full_path_from_path(Arena *arena, String8 path); -internal B32 os_file_path_exists(String8 path); -internal FileProperties os_properties_from_file_path(String8 path); - -//- rjf: file maps -internal OS_Handle os_file_map_open(OS_AccessFlags flags, OS_Handle file); -internal void os_file_map_close(OS_Handle map); -internal void * os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range); -internal void os_file_map_view_close(OS_Handle map, void *ptr); - -//- rjf: directory iteration -internal OS_FileIter *os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags); -internal B32 os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out); -internal void os_file_iter_end(OS_FileIter *iter); - -//- rjf: directory creation -internal B32 os_make_directory(String8 path); - -//////////////////////////////// -//~ rjf: @os_hooks Shared Memory (Implemented Per-OS) - -internal OS_Handle os_shared_memory_alloc(U64 size, String8 name); -internal OS_Handle os_shared_memory_open(String8 name); -internal void os_shared_memory_close(OS_Handle handle); -internal void * os_shared_memory_view_open(OS_Handle handle, Rng1U64 range); -internal void os_shared_memory_view_close(OS_Handle handle, void *ptr); - -//////////////////////////////// -//~ rjf: @os_hooks Time (Implemented Per-OS) - -internal OS_UnixTime os_now_unix(void); -internal DateTime os_now_universal_time(void); -internal DateTime os_universal_time_from_local_time(DateTime *local_time); -internal DateTime os_local_time_from_universal_time(DateTime *universal_time); -internal U64 os_now_microseconds(void); -internal void os_sleep_milliseconds(U32 msec); - -//////////////////////////////// -//~ rjf: @os_hooks Child Processes (Implemented Per-OS) - -internal B32 os_launch_process(OS_LaunchOptions *options, OS_Handle *handle_out); -internal B32 os_process_wait(OS_Handle handle, U64 endt_us); -internal void os_process_release_handle(OS_Handle handle); - -//////////////////////////////// -//~ rjf: @os_hooks Threads (Implemented Per-OS) - -internal OS_Handle os_launch_thread(OS_ThreadFunctionType *func, void *ptr, void *params); -internal B32 os_thread_wait(OS_Handle handle, U64 endt_us); -internal void os_release_thread_handle(OS_Handle thread); - -//////////////////////////////// -//~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) - -// NOTE(allen): Mutexes are recursive - support counted acquire/release nesting -// on a single thread - -//- rjf: recursive mutexes -internal OS_Handle os_mutex_alloc(void); -internal void os_mutex_release(OS_Handle mutex); -internal void os_mutex_take_(OS_Handle mutex); -internal void os_mutex_drop_(OS_Handle mutex); - -//- rjf: reader/writer mutexes -internal OS_Handle os_rw_mutex_alloc(void); -internal void os_rw_mutex_release(OS_Handle rw_mutex); -internal void os_rw_mutex_take_r_(OS_Handle mutex); -internal void os_rw_mutex_drop_r_(OS_Handle mutex); -internal void os_rw_mutex_take_w_(OS_Handle mutex); -internal void os_rw_mutex_drop_w_(OS_Handle mutex); - -//- rjf: condition variables -internal OS_Handle os_condition_variable_alloc(void); -internal void os_condition_variable_release(OS_Handle cv); -// returns false on timeout, true on signal, (max_wait_ms = max_U64) -> no timeout -internal B32 os_condition_variable_wait_(OS_Handle cv, OS_Handle mutex, U64 endt_us); -internal B32 os_condition_variable_wait_rw_r_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us); -internal B32 os_condition_variable_wait_rw_w_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us); -internal void os_condition_variable_signal_(OS_Handle cv); -internal void os_condition_variable_broadcast_(OS_Handle cv); - -//- rjf: cross-process semaphores -internal OS_Handle os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name); -internal void os_semaphore_release(OS_Handle semaphore); -internal OS_Handle os_semaphore_open(String8 name); -internal void os_semaphore_close(OS_Handle semaphore); -internal B32 os_semaphore_take(OS_Handle semaphore, U64 endt_us); -internal void os_semaphore_drop(OS_Handle semaphore); - -//////////////////////////////// -//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) - -internal OS_Handle os_library_open(String8 path); -internal VoidProc *os_library_load_proc(OS_Handle lib, String8 name); -internal void os_library_close(OS_Handle lib); - -//////////////////////////////// -//~ rjf: @os_hooks Safe Calls (Implemented Per-OS) - -internal void os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, void *ptr); - -//////////////////////////////// -//~ rjf: @os_hooks GUIDs (Implemented Per-OS) - -internal OS_Guid os_make_guid(void); -internal String8 os_string_from_guid(Arena *arena, OS_Guid guid); - -//////////////////////////////// -//~ rjf: @os_hooks Entry Points (Implemented Per-OS) - -// NOTE(rjf): The implementation of `os_core` will define low-level entry -// points if BUILD_ENTRY_DEFINING_UNIT is defined to 1. These will call -// into the standard codebase program entry points, named "entry_point". - -#if BUILD_ENTRY_DEFINING_UNIT -internal void entry_point(CmdLine *cmdline); -#endif - -#endif // OS_CORE_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_CORE_H +#define OS_CORE_H + +//////////////////////////////// +//~ rjf: System Info + +typedef struct OS_SystemInfo OS_SystemInfo; +struct OS_SystemInfo +{ + U32 logical_processor_count; + U64 page_size; + U64 large_page_size; + U64 allocation_granularity; + U64 microsecond_resolution; + String8 machine_name; +}; + +//////////////////////////////// +//~ rjf: Process Info + +typedef struct OS_ProcessInfo OS_ProcessInfo; +struct OS_ProcessInfo +{ + U32 pid; + String8 binary_path; + String8 initial_path; + String8 user_program_data_path; + String8List module_load_paths; + String8List environment; +}; + +//////////////////////////////// +//~ rjf: Access Flags + +typedef U32 OS_AccessFlags; +enum +{ + OS_AccessFlag_Read = (1<<0), + OS_AccessFlag_Write = (1<<1), + OS_AccessFlag_Execute = (1<<2), + OS_AccessFlag_Append = (1<<3), + OS_AccessFlag_ShareRead = (1<<4), + OS_AccessFlag_ShareWrite = (1<<5), +}; + +//////////////////////////////// +//~ rjf: Files + +typedef U32 OS_FileIterFlags; +enum +{ + OS_FileIterFlag_SkipFolders = (1 << 0), + OS_FileIterFlag_SkipFiles = (1 << 1), + OS_FileIterFlag_SkipHiddenFiles = (1 << 2), + OS_FileIterFlag_Done = (1 << 31), +}; + +typedef struct OS_FileIter OS_FileIter; +struct OS_FileIter +{ + OS_FileIterFlags flags; + U8 memory[800]; +}; + +typedef struct OS_FileInfo OS_FileInfo; +struct OS_FileInfo +{ + String8 name; + FileProperties props; +}; + +// nick: on-disk file identifier +typedef struct OS_FileID OS_FileID; +struct OS_FileID +{ + U64 v[3]; +}; + +//////////////////////////////// +//~ rjf: Process Launch Parameters + +typedef struct OS_ProcessLaunchParams OS_ProcessLaunchParams; +struct OS_ProcessLaunchParams +{ + String8List cmd_line; + String8 path; + String8List env; + B32 inherit_env; + B32 consoleless; +}; + +//////////////////////////////// +//~ rjf: Handle Type + +typedef struct OS_Handle OS_Handle; +struct OS_Handle +{ + U64 u64[1]; +}; + +typedef struct OS_HandleNode OS_HandleNode; +struct OS_HandleNode +{ + OS_HandleNode *next; + OS_Handle v; +}; + +typedef struct OS_HandleList OS_HandleList; +struct OS_HandleList +{ + OS_HandleNode *first; + OS_HandleNode *last; + U64 count; +}; + +typedef struct OS_HandleArray OS_HandleArray; +struct OS_HandleArray +{ + OS_Handle *v; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Globally Unique IDs + +typedef struct OS_Guid OS_Guid; +struct OS_Guid +{ + U32 data1; + U16 data2; + U16 data3; + U8 data4[8]; +}; +StaticAssert(sizeof(OS_Guid) == 16, os_guid_check); + +//////////////////////////////// +//~ rjf: Thread Types + +typedef void OS_ThreadFunctionType(void *ptr); + +//////////////////////////////// +//~ rjf: Handle Type Functions (Helpers, Implemented Once) + +internal OS_Handle os_handle_zero(void); +internal B32 os_handle_match(OS_Handle a, OS_Handle b); +internal void os_handle_list_push(Arena *arena, OS_HandleList *handles, OS_Handle handle); +internal OS_HandleArray os_handle_array_from_list(Arena *arena, OS_HandleList *list); + +//////////////////////////////// +//~ rjf: Command Line Argc/Argv Helper (Helper, Implemented Once) + +internal String8List os_string_list_from_argcv(Arena *arena, int argc, char **argv); + +//////////////////////////////// +//~ rjf: Filesystem Helpers (Helpers, Implemented Once) + +internal String8 os_data_from_file_path(Arena *arena, String8 path); +internal B32 os_write_data_to_file_path(String8 path, String8 data); +internal B32 os_write_data_list_to_file_path(String8 path, String8List list); +internal B32 os_append_data_to_file_path(String8 path, String8 data); +internal OS_FileID os_id_from_file_path(String8 path); +internal S64 os_file_id_compare(OS_FileID a, OS_FileID b); +internal String8 os_string_from_file_range(Arena *arena, OS_Handle file, Rng1U64 range); + +//////////////////////////////// +//~ rjf: GUID Helpers (Helpers, Implemented Once) + +internal String8 os_string_from_guid(Arena *arena, OS_Guid guid); + +//////////////////////////////// +//~ rjf: @os_hooks System/Process Info (Implemented Per-OS) + +internal OS_SystemInfo *os_get_system_info(void); +internal OS_ProcessInfo *os_get_process_info(void); +internal String8 os_get_current_path(Arena *arena); + +//////////////////////////////// +//~ rjf: @os_hooks Memory Allocation (Implemented Per-OS) + +//- rjf: basic +internal void *os_reserve(U64 size); +internal B32 os_commit(void *ptr, U64 size); +internal void os_decommit(void *ptr, U64 size); +internal void os_release(void *ptr, U64 size); + +//- rjf: large pages +internal B32 os_set_large_pages_enabled(B32 flag); +internal B32 os_large_pages_enabled(void); +internal void *os_reserve_large(U64 size); +internal B32 os_commit_large(void *ptr, U64 size); + +//////////////////////////////// +//~ rjf: @os_hooks Thread Info (Implemented Per-OS) + +internal U32 os_tid(void); +internal void os_set_thread_name(String8 string); + +//////////////////////////////// +//~ rjf: @os_hooks Aborting (Implemented Per-OS) + +internal void os_abort(S32 exit_code); + +//////////////////////////////// +//~ rjf: @os_hooks File System (Implemented Per-OS) + +//- rjf: files +internal OS_Handle os_file_open(OS_AccessFlags flags, String8 path); +internal void os_file_close(OS_Handle file); +internal U64 os_file_read(OS_Handle file, Rng1U64 rng, void *out_data); +internal void os_file_write(OS_Handle file, Rng1U64 rng, void *data); +internal B32 os_file_set_times(OS_Handle file, DateTime time); +internal FileProperties os_properties_from_file(OS_Handle file); +internal OS_FileID os_id_from_file(OS_Handle file); +internal B32 os_delete_file_at_path(String8 path); +internal B32 os_copy_file_path(String8 dst, String8 src); +internal String8 os_full_path_from_path(Arena *arena, String8 path); +internal B32 os_file_path_exists(String8 path); +internal FileProperties os_properties_from_file_path(String8 path); + +//- rjf: file maps +internal OS_Handle os_file_map_open(OS_AccessFlags flags, OS_Handle file); +internal void os_file_map_close(OS_Handle map); +internal void * os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range); +internal void os_file_map_view_close(OS_Handle map, void *ptr); + +//- rjf: directory iteration +internal OS_FileIter *os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags); +internal B32 os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out); +internal void os_file_iter_end(OS_FileIter *iter); + +//- rjf: directory creation +internal B32 os_make_directory(String8 path); + +//////////////////////////////// +//~ rjf: @os_hooks Shared Memory (Implemented Per-OS) + +internal OS_Handle os_shared_memory_alloc(U64 size, String8 name); +internal OS_Handle os_shared_memory_open(String8 name); +internal void os_shared_memory_close(OS_Handle handle); +internal void * os_shared_memory_view_open(OS_Handle handle, Rng1U64 range); +internal void os_shared_memory_view_close(OS_Handle handle, void *ptr); + +//////////////////////////////// +//~ rjf: @os_hooks Time (Implemented Per-OS) + +internal U64 os_now_microseconds(void); +internal U32 os_now_unix(void); +internal DateTime os_now_universal_time(void); +internal DateTime os_universal_time_from_local(DateTime *local_time); +internal DateTime os_local_time_from_universal(DateTime *universal_time); +internal void os_sleep_milliseconds(U32 msec); + +//////////////////////////////// +//~ rjf: @os_hooks Child Processes (Implemented Per-OS) + +internal OS_Handle os_process_launch(OS_ProcessLaunchParams *params); +internal B32 os_process_join(OS_Handle handle, U64 endt_us); +internal void os_process_detach(OS_Handle handle); + +//////////////////////////////// +//~ rjf: @os_hooks Threads (Implemented Per-OS) + +internal OS_Handle os_thread_launch(OS_ThreadFunctionType *func, void *ptr, void *params); +internal B32 os_thread_join(OS_Handle handle, U64 endt_us); +internal void os_thread_detach(OS_Handle handle); + +//////////////////////////////// +//~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) + +//- rjf: recursive mutexes +internal OS_Handle os_mutex_alloc(void); +internal void os_mutex_release(OS_Handle mutex); +internal void os_mutex_take(OS_Handle mutex); +internal void os_mutex_drop(OS_Handle mutex); + +//- rjf: reader/writer mutexes +internal OS_Handle os_rw_mutex_alloc(void); +internal void os_rw_mutex_release(OS_Handle rw_mutex); +internal void os_rw_mutex_take_r(OS_Handle mutex); +internal void os_rw_mutex_drop_r(OS_Handle mutex); +internal void os_rw_mutex_take_w(OS_Handle mutex); +internal void os_rw_mutex_drop_w(OS_Handle mutex); + +//- rjf: condition variables +internal OS_Handle os_condition_variable_alloc(void); +internal void os_condition_variable_release(OS_Handle cv); +// returns false on timeout, true on signal, (max_wait_ms = max_U64) -> no timeout +internal B32 os_condition_variable_wait(OS_Handle cv, OS_Handle mutex, U64 endt_us); +internal B32 os_condition_variable_wait_rw_r(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us); +internal B32 os_condition_variable_wait_rw_w(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us); +internal void os_condition_variable_signal(OS_Handle cv); +internal void os_condition_variable_broadcast(OS_Handle cv); + +//- rjf: cross-process semaphores +internal OS_Handle os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name); +internal void os_semaphore_release(OS_Handle semaphore); +internal OS_Handle os_semaphore_open(String8 name); +internal void os_semaphore_close(OS_Handle semaphore); +internal B32 os_semaphore_take(OS_Handle semaphore, U64 endt_us); +internal void os_semaphore_drop(OS_Handle semaphore); + +//- rjf: scope macros +#define OS_MutexScope(mutex) DeferLoop(os_mutex_take(mutex), os_mutex_drop(mutex)) +#define OS_MutexScopeR(mutex) DeferLoop(os_rw_mutex_take_r(mutex), os_rw_mutex_drop_r(mutex)) +#define OS_MutexScopeW(mutex) DeferLoop(os_rw_mutex_take_w(mutex), os_rw_mutex_drop_w(mutex)) +#define OS_MutexScopeRWPromote(mutex) DeferLoop((os_rw_mutex_drop_r(mutex), os_rw_mutex_take_w(mutex)), (os_rw_mutex_drop_w(mutex), os_rw_mutex_take_r(mutex))) + +//////////////////////////////// +//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) + +internal OS_Handle os_library_open(String8 path); +internal void os_library_close(OS_Handle lib); +internal VoidProc *os_library_load_proc(OS_Handle lib, String8 name); + +//////////////////////////////// +//~ rjf: @os_hooks Safe Calls (Implemented Per-OS) + +internal void os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, void *ptr); + +//////////////////////////////// +//~ rjf: @os_hooks GUIDs (Implemented Per-OS) + +internal OS_Guid os_make_guid(void); + +//////////////////////////////// +//~ rjf: @os_hooks Entry Points (Implemented Per-OS) + +// NOTE(rjf): The implementation of `os_core` will define low-level entry +// points if BUILD_ENTRY_DEFINING_UNIT is defined to 1. These will call +// into the standard codebase program entry points, named "entry_point". + +#if BUILD_ENTRY_DEFINING_UNIT +internal void entry_point(CmdLine *cmdline); +#endif + +#endif // OS_CORE_H diff --git a/src/os/core/win32/os_core_win32.c b/src/os/core/win32/os_core_win32.c index 47b4a7b3..686fd957 100644 --- a/src/os/core/win32/os_core_win32.c +++ b/src/os/core/win32/os_core_win32.c @@ -1,1856 +1,1669 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#pragma comment(lib, "user32") -#pragma comment(lib, "winmm") -#pragma comment(lib, "shell32") -#pragma comment(lib, "advapi32") -#pragma comment(lib, "rpcrt4") -#pragma comment(lib, "shlwapi") -#pragma comment(lib, "comctl32") - -// this is required for loading correct comctl32 dll file -#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") - -//////////////////////////////// -//~ allen: Definitions For Symbols That Are Sometimes Missing in Older Windows SDKs - -#if !defined(MEM_PRESERVE_PLACEHOLDER) -#define MEM_PRESERVE_PLACEHOLDER 0x2 -#endif -#if !defined(MEM_RESERVE_PLACEHOLDER) -# define MEM_REPLACE_PLACEHOLDER 0x00004000 -#endif -#if !defined(MEM_RESERVE_PLACEHOLDER) -# define MEM_RESERVE_PLACEHOLDER 0x00040000 -#endif - -typedef PVOID W32_VirtualAlloc2_Type(HANDLE Process, - PVOID BaseAddress, - SIZE_T Size, - ULONG AllocationType, - ULONG PageProtection, - void* ExtendedParameters, - ULONG ParameterCount); -typedef PVOID W32_MapViewOfFile3_Type(HANDLE FileMapping, - HANDLE Process, - PVOID BaseAddress, - ULONG64 Offset, - SIZE_T ViewSize, - ULONG AllocationType, - ULONG PageProtection, - void* ExtendedParameters, - ULONG ParameterCount); -typedef HRESULT W32_SetThreadDescription_Type(HANDLE hThread, - PCWSTR lpThreadDescription); - -global W32_VirtualAlloc2_Type *w32_VirtualAlloc2_func = 0; -global W32_MapViewOfFile3_Type *w32_MapViewOfFile3_func = 0; - -global W32_SetThreadDescription_Type *w32_SetThreadDescription_func = 0; - -//////////////////////////////// -//~ rjf: Globals - -global Arena *w32_perm_arena = 0; -global String8List w32_cmd_line_args = {0}; -global String8List w32_environment = {0}; -global CRITICAL_SECTION w32_mutex = {0}; -global String8 w32_initial_path = {0}; -global U64 w32_microsecond_resolution = 0; -global W32_Entity *w32_entity_free = 0; -global B32 w32_large_pages_enabled = 0; - -//////////////////////////////// -//~ rjf: Helpers - -//- rjf: files - -internal FilePropertyFlags -w32_file_property_flags_from_dwFileAttributes(DWORD dwFileAttributes){ - FilePropertyFlags flags = 0; - if (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){ - flags |= FilePropertyFlag_IsFolder; - } - return(flags); -} - -internal void -w32_file_properties_from_attributes(FileProperties *properties, WIN32_FILE_ATTRIBUTE_DATA *attributes){ - properties->size = Compose64Bit(attributes->nFileSizeHigh, attributes->nFileSizeLow); - w32_dense_time_from_file_time(&properties->created, &attributes->ftCreationTime); - w32_dense_time_from_file_time(&properties->modified, &attributes->ftLastWriteTime); - properties->flags = w32_file_property_flags_from_dwFileAttributes(attributes->dwFileAttributes); -} - -//- rjf: time - -internal void -w32_date_time_from_system_time(DateTime *out, SYSTEMTIME *in){ - out->year = in->wYear; - out->mon = in->wMonth - 1; - out->wday = in->wDayOfWeek; - out->day = in->wDay; - out->hour = in->wHour; - out->min = in->wMinute; - out->sec = in->wSecond; - out->msec = in->wMilliseconds; -} - -internal void -w32_system_time_from_date_time(SYSTEMTIME *out, DateTime *in){ - out->wYear = (WORD)(in->year); - out->wMonth = in->mon + 1; - out->wDay = in->day; - out->wHour = in->hour; - out->wMinute = in->min; - out->wSecond = in->sec; - out->wMilliseconds = in->msec; -} - -internal void -w32_dense_time_from_file_time(DenseTime *out, FILETIME *in){ - SYSTEMTIME systime = {0}; - FileTimeToSystemTime(in, &systime); - DateTime date_time = {0}; - w32_date_time_from_system_time(&date_time, &systime); - *out = dense_time_from_date_time(date_time); -} - -internal U32 -w32_sleep_ms_from_endt_us(U64 endt_us){ - U32 sleep_ms = 0; - if (endt_us == max_U64){ - sleep_ms = INFINITE; - } - else{ - U64 begint = os_now_microseconds(); - if (begint < endt_us){ - U64 sleep_us = endt_us - begint; - sleep_ms = (U32)((sleep_us + 999)/1000); - } - } - return(sleep_ms); -} - -//- rjf: entities - -internal W32_Entity* -w32_alloc_entity(W32_EntityKind kind){ - EnterCriticalSection(&w32_mutex); - W32_Entity *result = w32_entity_free; - if(result != 0) - { - SLLStackPop(w32_entity_free); - } - else - { - result = push_array_no_zero(w32_perm_arena, W32_Entity, 1); - } - MemoryZeroStruct(result); - Assert(result != 0); - LeaveCriticalSection(&w32_mutex); - MemoryZeroStruct(result); - result->kind = kind; - return(result); -} - -internal void -w32_free_entity(W32_Entity *entity){ - entity->kind = W32_EntityKind_Null; - EnterCriticalSection(&w32_mutex); - SLLStackPush(w32_entity_free, entity); - LeaveCriticalSection(&w32_mutex); -} - -//- rjf: threads - -internal DWORD -w32_thread_base(void *ptr){ - W32_Entity *entity = (W32_Entity*)ptr; - OS_ThreadFunctionType *func = entity->thread.func; - void *thread_ptr = entity->thread.ptr; - - TCTX tctx_; - tctx_init_and_equip(&tctx_); - func(thread_ptr); - tctx_release(); - - // remove my bit - LONG result = InterlockedAnd((LONG*)&entity->reference_mask, ~0x2); - // if the other bit is also gone, free entity - if ((result & 0x1) == 0){ - w32_free_entity(entity); - } - return(0); -} - -//////////////////////////////// -//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) - -internal void -os_init(void) -{ - // Load Fancy Memory Functions - { - HMODULE module = LoadLibraryA("kernel32.dll"); - if (module != 0){ - w32_VirtualAlloc2_func = (W32_VirtualAlloc2_Type*)GetProcAddress(module, "VirtualAlloc2"); - w32_MapViewOfFile3_func = (W32_MapViewOfFile3_Type*)GetProcAddress(module, "MapViewOfFile3"); - w32_SetThreadDescription_func = (W32_SetThreadDescription_Type*)GetProcAddress(module, "SetThreadDescription"); - - FreeLibrary(module); - } - } - - // Thread handshake - InitializeCriticalSection(&w32_mutex); - - // Permanent memory allocator for this layer - w32_perm_arena = arena_alloc(); - - // Init microsecond counter resolution - LARGE_INTEGER large_int_resolution; - if (QueryPerformanceFrequency(&large_int_resolution)){ - w32_microsecond_resolution = large_int_resolution.QuadPart; - } - else{ - w32_microsecond_resolution = 1; - } - - // Setup initial path - w32_initial_path = os_string_from_system_path(w32_perm_arena, OS_SystemPath_Current); - - // rjf: setup environment variables - { - WCHAR *this_proc_env = GetEnvironmentStringsW(); - U64 start_idx = 0; - for(U64 idx = 0;; idx += 1) - { - if(this_proc_env[idx] == 0) - { - if(start_idx == idx) - { - break; - } - else - { - String16 string16 = str16((U16 *)this_proc_env + start_idx, idx - start_idx); - String8 string = str8_from_16(w32_perm_arena, string16); - str8_list_push(w32_perm_arena, &w32_environment, string); - start_idx = idx+1; - } - } - } - } -} - -//////////////////////////////// -//~ rjf: @os_hooks Memory Allocation (Implemented Per-OS) - -internal void* -os_reserve(U64 size){ - void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE); - return(result); -} - -internal B32 -os_commit(void *ptr, U64 size){ - B32 result = (VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != 0); - return(result); -} - -internal void* -os_reserve_large(U64 size){ - // we commit on reserve because windows - void *result = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_LARGE_PAGES, PAGE_READWRITE); - return(result); -} - -internal B32 -os_commit_large(void *ptr, U64 size){ - return 1; -} - -internal void -os_decommit(void *ptr, U64 size){ - VirtualFree(ptr, size, MEM_DECOMMIT); -} - -internal void -os_release(void *ptr, U64 size){ - // NOTE(rjf): size not used - not necessary on Windows, but necessary for other OSes. - VirtualFree(ptr, 0, MEM_RELEASE); -} - -internal B32 -os_set_large_pages(B32 flag) -{ - B32 is_ok = 0; - HANDLE token; - if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) { - LUID luid; - if (LookupPrivilegeValue(0, SE_LOCK_MEMORY_NAME, &luid)) { - TOKEN_PRIVILEGES priv; - priv.PrivilegeCount = 1; - priv.Privileges[0].Luid = luid; - priv.Privileges[0].Attributes = flag ? SE_PRIVILEGE_ENABLED: 0; - if (AdjustTokenPrivileges(token, 0, &priv, sizeof(priv), 0, 0)) { - w32_large_pages_enabled = flag; - is_ok = 1; - } - } - CloseHandle(token); - } - return is_ok; -} - -internal B32 -os_large_pages_enabled(void) -{ - return w32_large_pages_enabled; -} - -internal U64 -os_large_page_size(void) -{ - U64 page_size = GetLargePageMinimum(); - return page_size; -} - -internal void* -os_alloc_ring_buffer(U64 size, U64 *actual_size_out){ - void *result = 0; - -#define W32_MAX_RING_SIZE GB(1) - - Assert(IsPow2(size)); - Assert(size <= W32_MAX_RING_SIZE); - - // get allocation granularity - SYSTEM_INFO info = {0}; - GetSystemInfo(&info); - Assert(IsPow2(info.dwAllocationGranularity)); - - // align size - U64 aligned_size = AlignPow2(size, (U64)(info.dwAllocationGranularity)); - - // split size - U32 lo_size = (U32)(aligned_size & 0xFFFFFFFF); - U32 hi_size = (U32)(aligned_size >> 32); - - // create pagefile-backed section - HANDLE section = CreateFileMappingA(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, hi_size, lo_size, 0); - - if (section != 0){ - if (w32_VirtualAlloc2_func != 0 && w32_MapViewOfFile3_func != 0){ - void *ptr1 = 0; - void *ptr2 = 0; - void *view1 = 0; - void *view2 = 0; - - // reserve virtual space placeholder - ptr1 = w32_VirtualAlloc2_func(0, 0, aligned_size*2, - MEM_RESERVE|MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, 0, 0); - if (ptr1 != 0){ - - // split off the first part of placeholder - VirtualFree(ptr1, aligned_size, MEM_RELEASE|MEM_PRESERVE_PLACEHOLDER); - ptr2 = ((U8*)ptr1 + aligned_size); - - // create views - view1 = w32_MapViewOfFile3_func(section, 0, ptr1, 0, aligned_size, - MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, 0, 0); - view2 = w32_MapViewOfFile3_func(section, 0, ptr2, 0, aligned_size, - MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, 0, 0); - if (view1 != 0 && view2 != 0){ - result = ptr1; - *actual_size_out = aligned_size; - } - } - - // cleanup - if (result == 0){ - if (ptr1 != 0){ - VirtualFree(ptr1, 0, MEM_RELEASE); - } - if (ptr2 != 0){ - VirtualFree(ptr2, 0, MEM_RELEASE); - } - if (view1 != 0){ - UnmapViewOfFileEx(view1, 0); - } - if (view2 != 0){ - UnmapViewOfFileEx(view2, 0); - } - } - } - - // no fancy memory functions available - else{ - for (U64 addr = GB(16); - addr < GB(272); - addr += W32_MAX_RING_SIZE){ - - // create the first view - void *view1 = MapViewOfFileEx(section, FILE_MAP_ALL_ACCESS, 0, 0, aligned_size, (void*)addr); - if (view1 != 0){ - - // create the second view - void *view2 = MapViewOfFileEx(section, FILE_MAP_ALL_ACCESS, 0, 0, aligned_size, (U8*)view1 + aligned_size); - - // on success make this the result - if (view2 != 0){ - result = view1; - *actual_size_out = aligned_size; - break; - } - - // cleanup view1 on failure - UnmapViewOfFile(view1); - } - } - } - } - - // cleanup - if (section != 0){ - CloseHandle(section); - } - - return(result); -} - -internal void -os_free_ring_buffer(void *ring_buffer, U64 actual_size){ - void *ptr1 = ring_buffer; - void *ptr2 = ((U8*)ptr1 + actual_size); - VirtualFree(ptr1, 0, MEM_RELEASE); - VirtualFree(ptr2, 0, MEM_RELEASE); - UnmapViewOfFileEx(ptr1, 0); - UnmapViewOfFileEx(ptr2, 0); -} - -//////////////////////////////// -//~ rjf: @os_hooks System Info (Implemented Per-OS) - -internal String8 -os_machine_name(void){ - local_persist U8 buffer[MAX_COMPUTERNAME_LENGTH + 1]; - local_persist String8 string = {0}; - local_persist B32 first = 1; - if (first){ - first = 0; - DWORD size = MAX_COMPUTERNAME_LENGTH + 1; - if (GetComputerNameA((char*)buffer, &size)){ - string = str8(buffer, size); - } - } - return(string); -} - -internal U64 -os_page_size(void){ - SYSTEM_INFO sysinfo = {0}; - GetSystemInfo(&sysinfo); - return(sysinfo.dwPageSize); -} - -internal U64 -os_allocation_granularity(void) -{ - SYSTEM_INFO sysinfo = {0}; - GetSystemInfo(&sysinfo); - return sysinfo.dwAllocationGranularity; -} - -internal U64 -os_logical_core_count(void) -{ - SYSTEM_INFO sysinfo = {0}; - GetSystemInfo(&sysinfo); - return sysinfo.dwNumberOfProcessors; -} - -//////////////////////////////// -//~ rjf: @os_hooks Process Info (Implemented Per-OS) - -internal S32 -os_get_pid(void){ - DWORD id = GetCurrentProcessId(); - return((S32)id); -} - -internal S32 -os_get_tid(void){ - DWORD id = GetCurrentThreadId(); - return((S32)id); -} - -internal String8List -os_get_environment(void) -{ - return w32_environment; -} - -internal U64 -os_string_list_from_system_path(Arena *arena, OS_SystemPath path, String8List *out){ - Temp scratch = scratch_begin(&arena, 1); - - U64 result = 0; - - switch (path){ - case OS_SystemPath_Binary: - { - local_persist B32 first = 1; - local_persist String8 name = {0}; - - // TODO(allen): let's just pre-compute this at init and skip the complexity - EnterCriticalSection(&w32_mutex); - if (first){ - first = 0; - DWORD size = KB(32); - U16 *buffer = push_array_no_zero(scratch.arena, U16, size); - DWORD length = GetModuleFileNameW(0, (WCHAR*)buffer, size); - String8 name8 = str8_from_16(scratch.arena, str16(buffer, length)); - String8 name_chopped = str8_chop_last_slash(name8); - name = push_str8_copy(w32_perm_arena, name_chopped); - } - LeaveCriticalSection(&w32_mutex); - - result = 1; - str8_list_push(arena, out, name); - }break; - - case OS_SystemPath_Initial: - { - Assert(w32_initial_path.str != 0); - result = 1; - str8_list_push(arena, out, w32_initial_path); - }break; - - case OS_SystemPath_Current: - { - DWORD length = GetCurrentDirectoryW(0, 0); - U16 *memory = push_array_no_zero(scratch.arena, U16, length + 1); - length = GetCurrentDirectoryW(length + 1, (WCHAR*)memory); - String8 name = str8_from_16(arena, str16(memory, length)); - result = 1; - str8_list_push(arena, out, name); - }break; - - case OS_SystemPath_UserProgramData: - { - local_persist B32 first = 1; - local_persist String8 name = {0}; - if (first){ - first = 0; - U64 size = KB(32); - U16 *buffer = push_array_no_zero(scratch.arena, U16, size); - if (SUCCEEDED(SHGetFolderPathW(0, CSIDL_APPDATA, 0, 0, (WCHAR*)buffer))){ - name = str8_from_16(scratch.arena, str16_cstring(buffer)); - EnterCriticalSection(&w32_mutex); - U8 *buffer8 = push_array_no_zero(w32_perm_arena, U8, name.size); - LeaveCriticalSection(&w32_mutex); - MemoryCopy(buffer8, name.str, name.size); - name.str = buffer8; - } - } - result = 1; - str8_list_push(arena, out, name); - }break; - - case OS_SystemPath_ModuleLoad: - { - U64 og_count = out->node_count; - - { - UINT cap = GetSystemDirectoryW(0, 0); - if (cap > 0){ - U16 *buffer = push_array_no_zero(scratch.arena, U16, cap); - UINT size = GetSystemDirectoryW((WCHAR*)buffer, cap); - if (size > 0){ - str8_list_push(arena, out, str8_from_16(arena, str16(buffer, size))); - } - } - } - - { - UINT cap = GetWindowsDirectoryW(0, 0); - if (cap > 0){ - U16 *buffer = push_array_no_zero(scratch.arena, U16, cap); - UINT size = GetWindowsDirectoryW((WCHAR*)buffer, cap); - if (size > 0){ - str8_list_push(arena, out, str8_from_16(arena, str16(buffer, size))); - } - } - } - - result = out->node_count - og_count; - }break; - } - - scratch_end(scratch); - return(result); -} - -//////////////////////////////// -//~ rjf: @os_hooks Thread Names - -internal void -os_set_thread_name(String8 name) -{ - Temp scratch = scratch_begin(0, 0); - - // rjf: windows 10 style - if(w32_SetThreadDescription_func) - { - String16 name16 = str16_from_8(scratch.arena, name); - HRESULT hr = w32_SetThreadDescription_func(GetCurrentThread(), (WCHAR*)name16.str); - } - - // rjf: raise-exception style - { - String8 name_copy = push_str8_copy(scratch.arena, name); -#pragma pack(push,8) - typedef struct THREADNAME_INFO THREADNAME_INFO; - struct THREADNAME_INFO - { - U32 dwType; // Must be 0x1000. - char *szName; // Pointer to name (in user addr space). - U32 dwThreadID; // Thread ID (-1=caller thread). - U32 dwFlags; // Reserved for future use, must be zero. - }; -#pragma pack(pop) - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = (char *)name_copy.str; - info.dwThreadID = os_get_tid(); - info.dwFlags = 0; -#pragma warning(push) -#pragma warning(disable: 6320 6322) - __try - { - RaiseException(0x406D1388, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info); - } - __except (EXCEPTION_EXECUTE_HANDLER) - { - } -#pragma warning(pop) - } - - scratch_end(scratch); -} - - -//////////////////////////////// -//~ rjf: @os_hooks Process Control (Implemented Per-OS) - -internal void -os_exit_process(S32 exit_code){ - ExitProcess(exit_code); -} - -//////////////////////////////// -//~ rjf: @os_hooks File System (Implemented Per-OS) - -//- rjf: files - -internal OS_Handle -os_file_open(OS_AccessFlags flags, String8 path) -{ - OS_Handle result = {0}; - Temp scratch = scratch_begin(0, 0); - String16 path16 = str16_from_8(scratch.arena, path); - DWORD access_flags = 0; - DWORD share_mode = 0; - DWORD creation_disposition = OPEN_EXISTING; - if(flags & OS_AccessFlag_Read) {access_flags |= GENERIC_READ;} - if(flags & OS_AccessFlag_Write) {access_flags |= GENERIC_WRITE;} - if(flags & OS_AccessFlag_Execute) {access_flags |= GENERIC_EXECUTE;} - if(flags & OS_AccessFlag_ShareRead) {share_mode |= FILE_SHARE_READ;} - if(flags & OS_AccessFlag_ShareWrite) {share_mode |= FILE_SHARE_WRITE|FILE_SHARE_DELETE;} - if(flags & OS_AccessFlag_Write) {creation_disposition = CREATE_ALWAYS;} - if(flags & OS_AccessFlag_Append) {creation_disposition = OPEN_ALWAYS;} - HANDLE file = CreateFileW((WCHAR *)path16.str, access_flags, share_mode, 0, creation_disposition, FILE_ATTRIBUTE_NORMAL, 0); - if(file != INVALID_HANDLE_VALUE) - { - result.u64[0] = (U64)file; - } - scratch_end(scratch); - return result; -} - -internal void -os_file_close(OS_Handle file) -{ - if(os_handle_match(file, os_handle_zero())) { return; } - HANDLE handle = (HANDLE)file.u64[0]; - BOOL result = CloseHandle(handle); - (void)result; -} - -internal U64 -os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) -{ - if(os_handle_match(file, os_handle_zero())) { return 0; } - HANDLE handle = (HANDLE)file.u64[0]; - - // rjf: clamp range by file size - U64 size = 0; - GetFileSizeEx(handle, (LARGE_INTEGER *)&size); - Rng1U64 rng_clamped = r1u64(ClampTop(rng.min, size), ClampTop(rng.max, size)); - U64 total_read_size = 0; - - // rjf: read loop - { - U64 to_read = dim_1u64(rng_clamped); - for(U64 off = rng.min; total_read_size < to_read;) - { - U64 amt64 = to_read - total_read_size; - U32 amt32 = u32_from_u64_saturate(amt64); - DWORD read_size = 0; - OVERLAPPED overlapped = {0}; - overlapped.Offset = (off&0x00000000ffffffffull); - overlapped.OffsetHigh = (off&0xffffffff00000000ull) >> 32; - ReadFile(handle, (U8 *)out_data + total_read_size, amt32, &read_size, &overlapped); - off += read_size; - total_read_size += read_size; - if(read_size != amt32) - { - break; - } - } - } - - return total_read_size; -} - -internal void -os_file_write(OS_Handle file, Rng1U64 rng, void *data) -{ - if(os_handle_match(file, os_handle_zero())) { return; } - HANDLE win_handle = (HANDLE)file.u64[0]; - U64 src_off = 0; - U64 dst_off = rng.min; - U64 bytes_to_write_total = rng.max-rng.min; - for(;src_off < bytes_to_write_total;) - { - void *bytes_src = (void *)((U8 *)data + src_off); - U64 bytes_to_write_64 = (bytes_to_write_total-src_off); - U32 bytes_to_write_32 = u32_from_u64_saturate(bytes_to_write_64); - U32 bytes_written = 0; - OVERLAPPED overlapped = {0}; - overlapped.Offset = (dst_off&0x00000000ffffffffull); - overlapped.OffsetHigh = (dst_off&0xffffffff00000000ull) >> 32; - BOOL success = WriteFile(win_handle, bytes_src, bytes_to_write_32, (DWORD *)&bytes_written, &overlapped); - if(success == 0) - { - break; - } - src_off += bytes_written; - dst_off += bytes_written; - } -} - -internal B32 -os_file_set_times(OS_Handle file, DateTime time) -{ - if(os_handle_match(file, os_handle_zero())) { return 0; } - B32 result = 0; - HANDLE handle = (HANDLE)file.u64[0]; - SYSTEMTIME system_time = {0}; - w32_system_time_from_date_time(&system_time, &time); - FILETIME file_time = {0}; - result = (SystemTimeToFileTime(&system_time, &file_time) && - SetFileTime(handle, &file_time, &file_time, &file_time)); - return result; -} - -internal FileProperties -os_properties_from_file(OS_Handle file) -{ - if(os_handle_match(file, os_handle_zero())) { FileProperties r = {0}; return r; } - FileProperties props = {0}; - HANDLE handle = (HANDLE)file.u64[0]; - BY_HANDLE_FILE_INFORMATION info; - BOOL info_good = GetFileInformationByHandle(handle, &info); - if(info_good) - { - U32 size_lo = info.nFileSizeLow; - U32 size_hi = info.nFileSizeHigh; - props.size = (U64)size_lo | (((U64)size_hi)<<32); - w32_dense_time_from_file_time(&props.modified, &info.ftLastWriteTime); - w32_dense_time_from_file_time(&props.created, &info.ftCreationTime); - props.flags = w32_file_property_flags_from_dwFileAttributes(info.dwFileAttributes); - } - return props; -} - -internal OS_FileID -os_id_from_file(OS_Handle file) -{ - if(os_handle_match(file, os_handle_zero())) { OS_FileID r = {0}; return r; } - OS_FileID result = {0}; - HANDLE handle = (HANDLE)file.u64[0]; - BY_HANDLE_FILE_INFORMATION info; - BOOL is_ok = GetFileInformationByHandle(handle, &info); - if(is_ok) - { - result.v[0] = info.dwVolumeSerialNumber; - result.v[1] = info.nFileIndexLow; - result.v[2] = info.nFileIndexHigh; - } - return result; -} - -internal B32 -os_delete_file_at_path(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - String16 path16 = str16_from_8(scratch.arena, path); - B32 result = DeleteFileW((WCHAR*)path16.str); - scratch_end(scratch); - return result; -} - -internal B32 -os_copy_file_path(String8 dst, String8 src) -{ - Temp scratch = scratch_begin(0, 0); - String16 dst16 = str16_from_8(scratch.arena, dst); - String16 src16 = str16_from_8(scratch.arena, src); - B32 result = CopyFileW((WCHAR*)src16.str, (WCHAR*)dst16.str, 0); - scratch_end(scratch); - return result; -} - -internal String8 -os_full_path_from_path(Arena *arena, String8 path) -{ - Temp scratch = scratch_begin(&arena, 1); - DWORD buffer_size = MAX_PATH + 1; - U16 *buffer = push_array_no_zero(scratch.arena, U16, buffer_size); - String16 path16 = str16_from_8(scratch.arena, path); - DWORD path16_size = GetFullPathNameW((WCHAR*)path16.str, buffer_size, (WCHAR*)buffer, NULL); - String8 full_path = str8_from_16(arena, str16(buffer, path16_size)); - scratch_end(scratch); - return full_path; -} - -internal B32 -os_file_path_exists(String8 path) -{ - Temp scratch = scratch_begin(0,0); - String16 path16 = str16_from_8(scratch.arena, path); - DWORD attributes = GetFileAttributesW((WCHAR *)path16.str); - B32 exists = (attributes != INVALID_FILE_ATTRIBUTES) && !!(~attributes & FILE_ATTRIBUTE_DIRECTORY); - scratch_end(scratch); - return exists; -} - -internal FileProperties -os_properties_from_file_path(String8 path) -{ - WIN32_FIND_DATAW find_data = {0}; - Temp scratch = scratch_begin(0, 0); - String16 path16 = str16_from_8(scratch.arena, path); - HANDLE handle = FindFirstFileW((WCHAR *)path16.str, &find_data); - FileProperties props = {0}; - if(handle != INVALID_HANDLE_VALUE) - { - props.size = Compose64Bit(find_data.nFileSizeHigh, find_data.nFileSizeLow); - w32_dense_time_from_file_time(&props.created, &find_data.ftCreationTime); - w32_dense_time_from_file_time(&props.modified, &find_data.ftLastWriteTime); - props.flags = w32_file_property_flags_from_dwFileAttributes(find_data.dwFileAttributes); - } - FindClose(handle); - scratch_end(scratch); - return props; -} - -//- rjf: file maps - -internal OS_Handle -os_file_map_open(OS_AccessFlags flags, OS_Handle file) -{ - OS_Handle map = {0}; - { - HANDLE file_handle = (HANDLE)file.u64[0]; - DWORD protect_flags = 0; - { - switch(flags) - { - default:{}break; - case OS_AccessFlag_Read: - {protect_flags = PAGE_READONLY;}break; - case OS_AccessFlag_Write: - case OS_AccessFlag_Read|OS_AccessFlag_Write: - {protect_flags = PAGE_READWRITE;}break; - case OS_AccessFlag_Execute: - case OS_AccessFlag_Read|OS_AccessFlag_Execute: - {protect_flags = PAGE_EXECUTE_READ;}break; - case OS_AccessFlag_Execute|OS_AccessFlag_Write|OS_AccessFlag_Read: - case OS_AccessFlag_Execute|OS_AccessFlag_Write: - {protect_flags = PAGE_EXECUTE_READWRITE;}break; - } - } - HANDLE map_handle = CreateFileMappingA(file_handle, 0, protect_flags, 0, 0, 0); - map.u64[0] = (U64)map_handle; - } - return map; -} - -internal void -os_file_map_close(OS_Handle map) -{ - HANDLE handle = (HANDLE)map.u64[0]; - BOOL result = CloseHandle(handle); - (void)result; -} - -internal void * -os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) -{ - HANDLE handle = (HANDLE)map.u64[0]; - U32 off_lo = (U32)((range.min&0x00000000ffffffffull)>>0); - U32 off_hi = (U32)((range.min&0xffffffff00000000ull)>>32); - U64 size = dim_1u64(range); - DWORD access_flags = 0; - { - switch(flags) - { - default:{}break; - case OS_AccessFlag_Read: - { - access_flags = FILE_MAP_READ; - }break; - case OS_AccessFlag_Write: - { - access_flags = FILE_MAP_WRITE; - }break; - case OS_AccessFlag_Read|OS_AccessFlag_Write: - { - access_flags = FILE_MAP_ALL_ACCESS; - }break; - case OS_AccessFlag_Execute: - case OS_AccessFlag_Read|OS_AccessFlag_Execute: - case OS_AccessFlag_Write|OS_AccessFlag_Execute: - case OS_AccessFlag_Read|OS_AccessFlag_Write|OS_AccessFlag_Execute: - { - access_flags = FILE_MAP_ALL_ACCESS|FILE_MAP_EXECUTE; - }break; - } - } - void *result = MapViewOfFile(handle, access_flags, off_hi, off_lo, size); - return result; -} - -internal void -os_file_map_view_close(OS_Handle map, void *ptr) -{ - BOOL result = UnmapViewOfFile(ptr); - (void)result; -} - -//- rjf: directory iteration - -internal OS_FileIter * -os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) -{ - Temp scratch = scratch_begin(&arena, 1); - String8 path_with_wildcard = push_str8_cat(scratch.arena, path, str8_lit("\\*")); - String16 path16 = str16_from_8(scratch.arena, path_with_wildcard); - OS_FileIter *iter = push_array(arena, OS_FileIter, 1); - iter->flags = flags; - W32_FileIter *w32_iter = (W32_FileIter*)iter->memory; - if(path.size == 0) - { - w32_iter->is_volume_iter = 1; - WCHAR buffer[512] = {0}; - DWORD length = GetLogicalDriveStringsW(sizeof(buffer), buffer); - String8List drive_strings = {0}; - for(U64 off = 0; off < (U64)length;) - { - String16 next_drive_string_16 = str16_cstring((U16 *)buffer+off); - off += next_drive_string_16.size+1; - String8 next_drive_string = str8_from_16(arena, next_drive_string_16); - next_drive_string = str8_chop_last_slash(next_drive_string); - str8_list_push(scratch.arena, &drive_strings, next_drive_string); - } - w32_iter->drive_strings = str8_array_from_list(arena, &drive_strings); - w32_iter->drive_strings_iter_idx = 0; - } - else - { - w32_iter->handle = FindFirstFileW((WCHAR*)path16.str, &w32_iter->find_data); - } - scratch_end(scratch); - return iter; -} - -internal B32 -os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) -{ - B32 result = 0; - OS_FileIterFlags flags = iter->flags; - W32_FileIter *w32_iter = (W32_FileIter*)iter->memory; - switch(w32_iter->is_volume_iter) - { - //- rjf: file iteration - default: - case 0: - { - if (!(flags & OS_FileIterFlag_Done) && w32_iter->handle != INVALID_HANDLE_VALUE) - { - do - { - // check is usable - B32 usable_file = 1; - - WCHAR *file_name = w32_iter->find_data.cFileName; - DWORD attributes = w32_iter->find_data.dwFileAttributes; - if (file_name[0] == '.'){ - if (flags & OS_FileIterFlag_SkipHiddenFiles){ - usable_file = 0; - } - else if (file_name[1] == 0){ - usable_file = 0; - } - else if (file_name[1] == '.' && file_name[2] == 0){ - usable_file = 0; - } - } - if (attributes & FILE_ATTRIBUTE_DIRECTORY){ - if (flags & OS_FileIterFlag_SkipFolders){ - usable_file = 0; - } - } - else{ - if (flags & OS_FileIterFlag_SkipFiles){ - usable_file = 0; - } - } - - // emit if usable - if (usable_file){ - info_out->name = str8_from_16(arena, str16_cstring((U16*)file_name)); - info_out->props.size = (U64)w32_iter->find_data.nFileSizeLow | (((U64)w32_iter->find_data.nFileSizeHigh)<<32); - w32_dense_time_from_file_time(&info_out->props.created, &w32_iter->find_data.ftCreationTime); - w32_dense_time_from_file_time(&info_out->props.modified, &w32_iter->find_data.ftLastWriteTime); - info_out->props.flags = w32_file_property_flags_from_dwFileAttributes(attributes); - result = 1; - if (!FindNextFileW(w32_iter->handle, &w32_iter->find_data)){ - iter->flags |= OS_FileIterFlag_Done; - } - break; - } - }while(FindNextFileW(w32_iter->handle, &w32_iter->find_data)); - } - }break; - - //- rjf: volume iteration - case 1: - { - result = w32_iter->drive_strings_iter_idx < w32_iter->drive_strings.count; - if(result != 0) - { - MemoryZeroStruct(info_out); - info_out->name = w32_iter->drive_strings.v[w32_iter->drive_strings_iter_idx]; - info_out->props.flags |= FilePropertyFlag_IsFolder; - w32_iter->drive_strings_iter_idx += 1; - } - }break; - } - if(!result) - { - iter->flags |= OS_FileIterFlag_Done; - } - return result; -} - -internal void -os_file_iter_end(OS_FileIter *iter) -{ - W32_FileIter *w32_iter = (W32_FileIter*)iter->memory; - FindClose(w32_iter->handle); -} - -//- rjf: directory creation - -internal B32 -os_make_directory(String8 path) -{ - B32 result = 0; - Temp scratch = scratch_begin(0, 0); - String16 name16 = str16_from_8(scratch.arena, path); - WIN32_FILE_ATTRIBUTE_DATA attributes = {0}; - GetFileAttributesExW((WCHAR*)name16.str, GetFileExInfoStandard, &attributes); - if(attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - result = 1; - } - else if(CreateDirectoryW((WCHAR*)name16.str, 0)) - { - result = 1; - } - scratch_end(scratch); - return(result); -} - -//////////////////////////////// -//~ rjf: @os_hooks Shared Memory (Implemented Per-OS) - -internal OS_Handle -os_shared_memory_alloc(U64 size, String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String16 name16 = str16_from_8(scratch.arena, name); - HANDLE file = CreateFileMappingW(INVALID_HANDLE_VALUE, - 0, - PAGE_READWRITE, - (U32)((size & 0xffffffff00000000) >> 32), - (U32)((size & 0x00000000ffffffff)), - (WCHAR *)name16.str); - OS_Handle result = {(U64)file}; - scratch_end(scratch); - return result; -} - -internal OS_Handle -os_shared_memory_open(String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String16 name16 = str16_from_8(scratch.arena, name); - HANDLE file = OpenFileMappingW(FILE_MAP_ALL_ACCESS, 0, (WCHAR *)name16.str); - OS_Handle result = {(U64)file}; - scratch_end(scratch); - return result; -} - -internal void -os_shared_memory_close(OS_Handle handle) -{ - HANDLE file = (HANDLE)(handle.u64[0]); - CloseHandle(file); -} - -internal void * -os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) -{ - HANDLE file = (HANDLE)(handle.u64[0]); - U64 offset = range.min; - U64 size = range.max-range.min; - void *ptr = MapViewOfFile(file, FILE_MAP_ALL_ACCESS, - (U32)((offset & 0xffffffff00000000) >> 32), - (U32)((offset & 0x00000000ffffffff)), - size); - return ptr; -} - -internal void -os_shared_memory_view_close(OS_Handle handle, void *ptr) -{ - UnmapViewOfFile(ptr); -} - -//////////////////////////////// -//~ rjf: @os_hooks Time (Implemented Per-OS) - -internal OS_UnixTime -os_now_unix(void) -{ - FILETIME file_time; - GetSystemTimeAsFileTime(&file_time); - - U64 win32_time = ((U64)file_time.dwHighDateTime << 32) | file_time.dwLowDateTime; - U64 unix_time64 = ((win32_time - 0x19DB1DED53E8000ULL) / 10000000); - - Assert(unix_time64 <= OS_UNIX_TIME_MAX); - OS_UnixTime unix_time32 = (OS_UnixTime)unix_time64; - - return unix_time32; -} - -internal DateTime -os_now_universal_time(void){ - SYSTEMTIME systime = {0}; - GetSystemTime(&systime); - DateTime result = {0}; - w32_date_time_from_system_time(&result, &systime); - return(result); -} - -internal DateTime -os_universal_time_from_local_time(DateTime *date_time){ - SYSTEMTIME systime = {0}; - w32_system_time_from_date_time(&systime, date_time); - FILETIME ftime = {0}; - SystemTimeToFileTime(&systime, &ftime); - FILETIME ftime_local = {0}; - LocalFileTimeToFileTime(&ftime, &ftime_local); - FileTimeToSystemTime(&ftime_local, &systime); - DateTime result = {0}; - w32_date_time_from_system_time(&result, &systime); - return(result); -} - -internal DateTime -os_local_time_from_universal_time(DateTime *date_time){ - SYSTEMTIME systime = {0}; - w32_system_time_from_date_time(&systime, date_time); - FILETIME ftime = {0}; - SystemTimeToFileTime(&systime, &ftime); - FILETIME ftime_local = {0}; - FileTimeToLocalFileTime(&ftime, &ftime_local); - FileTimeToSystemTime(&ftime_local, &systime); - DateTime result = {0}; - w32_date_time_from_system_time(&result, &systime); - return(result); -} - -internal U64 -os_now_microseconds(void){ - U64 result = 0; - LARGE_INTEGER large_int_counter; - if (QueryPerformanceCounter(&large_int_counter)){ - result = (large_int_counter.QuadPart*Million(1))/w32_microsecond_resolution; - } - return(result); -} - -internal void -os_sleep_milliseconds(U32 msec){ - Sleep(msec); -} - -//////////////////////////////// -//~ rjf: @os_hooks Child Processes (Implemented Per-OS) - -internal B32 -os_launch_process(OS_LaunchOptions *options, OS_Handle *handle_out){ - B32 result = 0; - Temp scratch = scratch_begin(0, 0); - - StringJoin join_params = {0}; - join_params.pre = str8_lit("\""); - join_params.sep = str8_lit("\" \""); - join_params.post = str8_lit("\""); - String8 cmd = str8_list_join(scratch.arena, &options->cmd_line, &join_params); - - StringJoin join_params2 = {0}; - join_params2.sep = str8_lit("\0"); - join_params2.post = str8_lit("\0"); - B32 use_null_env_arg = 0; - String8List all_opts = options->env; - if(options->inherit_env != 0) - { - if(all_opts.node_count != 0) - { - MemoryZeroStruct(&all_opts); - for(String8Node *n = options->env.first; n != 0; n = n->next) - { - str8_list_push(scratch.arena, &all_opts, n->string); - } - for(String8Node *n = w32_environment.first; n != 0; n = n->next) - { - str8_list_push(scratch.arena, &all_opts, n->string); - } - } - else - { - use_null_env_arg = 1; - } - } - String8 env = {0}; - if(use_null_env_arg == 0) - { - env = str8_list_join(scratch.arena, &all_opts, &join_params2); - } - - String16 cmd16 = str16_from_8(scratch.arena, cmd); - String16 dir16 = str16_from_8(scratch.arena, options->path); - String16 env16 = {0}; - if(use_null_env_arg == 0) - { - env16 = str16_from_8(scratch.arena, env); - } - - DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT; - if(options->consoleless) - { - creation_flags |= CREATE_NO_WINDOW; - } - STARTUPINFOW startup_info = {sizeof(startup_info)}; - PROCESS_INFORMATION process_info = {0}; - if (CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, 0, creation_flags, use_null_env_arg ? 0 : (WCHAR*)env16.str, (WCHAR*)dir16.str, - &startup_info, &process_info)){ - if (handle_out == 0){ - CloseHandle(process_info.hProcess); - } - CloseHandle(process_info.hThread); - - if (handle_out != 0){ - OS_Handle handle_result = {(U64)process_info.hProcess}; - *handle_out = handle_result; - } - result = 1; - } - - scratch_end(scratch); - return(result); -} - -internal B32 -os_process_wait(OS_Handle handle, U64 endt_us){ - HANDLE process = (HANDLE)(handle.u64[0]); - DWORD sleep_ms = w32_sleep_ms_from_endt_us(endt_us); - DWORD result = WaitForSingleObject(process, sleep_ms); - return (result == WAIT_OBJECT_0); -} - -internal void -os_process_release_handle(OS_Handle handle){ - HANDLE process = (HANDLE)(handle.u64[0]); - CloseHandle(process); -} - -//////////////////////////////// -//~ rjf: @os_hooks Threads (Implemented Per-OS) - -internal OS_Handle -os_launch_thread(OS_ThreadFunctionType *func, void *ptr, void *params){ - W32_Entity *entity = w32_alloc_entity(W32_EntityKind_Thread); - entity->reference_mask = 0x3; - entity->thread.func = func; - entity->thread.ptr = ptr; - entity->thread.handle = CreateThread(0, 0, w32_thread_base, entity, 0, &entity->thread.tid); - OS_Handle result = {IntFromPtr(entity)}; - return(result); -} - -internal B32 -os_thread_wait(OS_Handle handle, U64 endt_us) -{ - DWORD sleep_ms = w32_sleep_ms_from_endt_us(endt_us); - W32_Entity *entity = (W32_Entity *)PtrFromInt(handle.u64[0]); - DWORD wait_result = WAIT_OBJECT_0; - if(entity != 0) - { - wait_result = WaitForSingleObject(entity->thread.handle, sleep_ms); - } - return (wait_result == WAIT_OBJECT_0); -} - -internal void -os_release_thread_handle(OS_Handle thread){ - W32_Entity *entity = (W32_Entity*)PtrFromInt(thread.u64[0]); - // remove my bit - LONG result = InterlockedAnd((LONG*)&entity->reference_mask, ~0x1); - // if the other bit is also gone, free entity - if ((result & 0x2) == 0){ - w32_free_entity(entity); - } -} - -//////////////////////////////// -//~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) - -//- rjf: mutexes - -internal OS_Handle -os_mutex_alloc(void){ - W32_Entity *entity = w32_alloc_entity(W32_EntityKind_Mutex); - InitializeCriticalSection(&entity->mutex); - - OS_Handle result = {IntFromPtr(entity)}; - return(result); -} - -internal void -os_mutex_release(OS_Handle mutex){ - W32_Entity *entity = (W32_Entity*)PtrFromInt(mutex.u64[0]); - w32_free_entity(entity); -} - -internal void -os_mutex_take_(OS_Handle mutex){ - W32_Entity *entity = (W32_Entity*)PtrFromInt(mutex.u64[0]); - EnterCriticalSection(&entity->mutex); -} - -internal void -os_mutex_drop_(OS_Handle mutex){ - W32_Entity *entity = (W32_Entity*)PtrFromInt(mutex.u64[0]); - LeaveCriticalSection(&entity->mutex); -} - -//- rjf: reader/writer mutexes - -internal OS_Handle -os_rw_mutex_alloc(void){ - W32_Entity *entity = w32_alloc_entity(W32_EntityKind_RWMutex); - InitializeSRWLock(&entity->rw_mutex); - - OS_Handle result = {IntFromPtr(entity)}; - return(result); -} - -internal void -os_rw_mutex_release(OS_Handle rw_mutex){ - W32_Entity *entity = (W32_Entity*)PtrFromInt(rw_mutex.u64[0]); - w32_free_entity(entity); -} - -internal void -os_rw_mutex_take_r_(OS_Handle rw_mutex){ - W32_Entity *entity = (W32_Entity*)PtrFromInt(rw_mutex.u64[0]); - AcquireSRWLockShared(&entity->rw_mutex); -} - -internal void -os_rw_mutex_drop_r_(OS_Handle rw_mutex){ - W32_Entity *entity = (W32_Entity*)PtrFromInt(rw_mutex.u64[0]); - ReleaseSRWLockShared(&entity->rw_mutex); -} - -internal void -os_rw_mutex_take_w_(OS_Handle rw_mutex){ - W32_Entity *entity = (W32_Entity*)PtrFromInt(rw_mutex.u64[0]); - AcquireSRWLockExclusive(&entity->rw_mutex); -} - -internal void -os_rw_mutex_drop_w_(OS_Handle rw_mutex){ - W32_Entity *entity = (W32_Entity*)PtrFromInt(rw_mutex.u64[0]); - ReleaseSRWLockExclusive(&entity->rw_mutex); -} - -//- rjf: condition variables - -internal OS_Handle -os_condition_variable_alloc(void){ - W32_Entity *entity = w32_alloc_entity(W32_EntityKind_ConditionVariable); - InitializeConditionVariable(&entity->cv); - OS_Handle result = {IntFromPtr(entity)}; - return(result); -} - -internal void -os_condition_variable_release(OS_Handle cv){ - W32_Entity *entity = (W32_Entity*)PtrFromInt(cv.u64[0]); - w32_free_entity(entity); -} - -internal B32 -os_condition_variable_wait_(OS_Handle cv, OS_Handle mutex, U64 endt_us){ - U32 sleep_ms = w32_sleep_ms_from_endt_us(endt_us); - BOOL result = 0; - if (sleep_ms > 0){ - W32_Entity *entity = (W32_Entity*)PtrFromInt(cv.u64[0]); - W32_Entity *mutex_entity = (W32_Entity*)PtrFromInt(mutex.u64[0]); - result = SleepConditionVariableCS(&entity->cv, &mutex_entity->mutex, sleep_ms); - } - return(result); -} - -internal B32 -os_condition_variable_wait_rw_r_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us){ - U32 sleep_ms = w32_sleep_ms_from_endt_us(endt_us); - BOOL result = 0; - if (sleep_ms > 0){ - W32_Entity *entity = (W32_Entity*)PtrFromInt(cv.u64[0]); - W32_Entity *mutex_entity = (W32_Entity*)PtrFromInt(mutex_rw.u64[0]); - result = SleepConditionVariableSRW(&entity->cv, &mutex_entity->rw_mutex, sleep_ms, - CONDITION_VARIABLE_LOCKMODE_SHARED); - } - return(result); -} - -internal B32 -os_condition_variable_wait_rw_w_(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us){ - U32 sleep_ms = w32_sleep_ms_from_endt_us(endt_us); - BOOL result = 0; - if (sleep_ms > 0){ - W32_Entity *entity = (W32_Entity*)PtrFromInt(cv.u64[0]); - W32_Entity *mutex_entity = (W32_Entity*)PtrFromInt(mutex_rw.u64[0]); - result = SleepConditionVariableSRW(&entity->cv, &mutex_entity->rw_mutex, sleep_ms, 0); - } - return(result); -} - -internal void -os_condition_variable_signal_(OS_Handle cv){ - W32_Entity *entity = (W32_Entity*)PtrFromInt(cv.u64[0]); - WakeConditionVariable(&entity->cv); -} - -internal void -os_condition_variable_broadcast_(OS_Handle cv){ - W32_Entity *entity = (W32_Entity*)PtrFromInt(cv.u64[0]); - WakeAllConditionVariable(&entity->cv); -} - -//- rjf: cross-process semaphores - -internal OS_Handle -os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String16 name16 = str16_from_8(scratch.arena, name); - HANDLE handle = CreateSemaphoreW(0, initial_count, max_count, (WCHAR *)name16.str); - OS_Handle result = {(U64)handle}; - scratch_end(scratch); - return result; -} - -internal void -os_semaphore_release(OS_Handle semaphore) -{ - HANDLE handle = (HANDLE)semaphore.u64[0]; - CloseHandle(handle); -} - -internal OS_Handle -os_semaphore_open(String8 name) -{ - Temp scratch = scratch_begin(0, 0); - String16 name16 = str16_from_8(scratch.arena, name); - HANDLE handle = OpenSemaphoreW(SEMAPHORE_ALL_ACCESS , 0, (WCHAR *)name16.str); - OS_Handle result = {(U64)handle}; - scratch_end(scratch); - return result; -} - -internal void -os_semaphore_close(OS_Handle semaphore) -{ - HANDLE handle = (HANDLE)semaphore.u64[0]; - CloseHandle(handle); -} - -internal B32 -os_semaphore_take(OS_Handle semaphore, U64 endt_us) -{ - U32 sleep_ms = w32_sleep_ms_from_endt_us(endt_us); - HANDLE handle = (HANDLE)semaphore.u64[0]; - DWORD wait_result = WaitForSingleObject(handle, sleep_ms); - B32 result = (wait_result == WAIT_OBJECT_0); - return result; -} - -internal void -os_semaphore_drop(OS_Handle semaphore) -{ - HANDLE handle = (HANDLE)semaphore.u64[0]; - ReleaseSemaphore(handle, 1, 0); -} - -//////////////////////////////// -//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) - -internal OS_Handle -os_library_open(String8 path){ - Temp scratch = scratch_begin(0, 0); - String16 path16 = str16_from_8(scratch.arena, path); - HMODULE mod = LoadLibraryW((LPCWSTR)path16.str); - OS_Handle result = { (U64)mod }; - scratch_end(scratch); - return(result); -} - -internal VoidProc* -os_library_load_proc(OS_Handle lib, String8 name){ - Temp scratch = scratch_begin(0, 0); - HMODULE mod = (HMODULE)lib.u64[0]; - name = push_str8_copy(scratch.arena, name); - VoidProc *result = (VoidProc*)GetProcAddress(mod, (LPCSTR)name.str); - scratch_end(scratch); - return(result); -} - -internal void -os_library_close(OS_Handle lib){ - HMODULE mod = (HMODULE)lib.u64[0]; - FreeLibrary(mod); -} - -//////////////////////////////// -//~ rjf: @os_hooks Safe Calls (Implemented Per-OS) - -internal void -os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, void *ptr){ - __try{ - func(ptr); - } - __except (EXCEPTION_EXECUTE_HANDLER){ - if (fail_handler != 0){ - fail_handler(ptr); - } - ExitProcess(1); - } -} - -//////////////////////////////// -//~ rjf: @os_hooks GUIDs (Implemented Per-OS) - -internal OS_Guid -os_make_guid(void) -{ - OS_Guid result; MemoryZeroStruct(&result); - UUID uuid; - RPC_STATUS rpc_status = UuidCreate(&uuid); - if(rpc_status == RPC_S_OK) - { - result.data1 = uuid.Data1; - result.data2 = uuid.Data2; - result.data3 = uuid.Data3; - MemoryCopyArray(result.data4, uuid.Data4); - } - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Entry Points (Implemented Per-OS) - -#include -#undef OS_WINDOWS // shlwapi uses its own OS_WINDOWS include inside -#include - -internal B32 win32_g_is_quiet = 0; - -internal HRESULT WINAPI -win32_dialog_callback(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, LONG_PTR data) -{ - if(msg == TDN_HYPERLINK_CLICKED) - { - ShellExecuteW(NULL, L"open", (LPWSTR)lparam, NULL, NULL, SW_SHOWNORMAL); - } - return S_OK; -} - -internal LONG WINAPI -win32_exception_filter(EXCEPTION_POINTERS* exception_ptrs) -{ - if(win32_g_is_quiet) - { - ExitProcess(1); - } - - static volatile LONG first = 0; - if(InterlockedCompareExchange(&first, 1, 0) != 0) - { - // prevent failures in other threads to popup same message box - // this handler just shows first thread that crashes - // we are terminating afterwards anyway - for (;;) Sleep(1000); - } - - WCHAR buffer[4096] = {0}; - int buflen = 0; - - DWORD exception_code = exception_ptrs->ExceptionRecord->ExceptionCode; - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"A fatal exception (code 0x%x) occurred. The process is terminating.\n", exception_code); - - // load dbghelp dynamically just in case if it is missing - HMODULE dbghelp = LoadLibraryA("dbghelp.dll"); - if(dbghelp) - { - DWORD (WINAPI *dbg_SymSetOptions)(DWORD SymOptions); - BOOL (WINAPI *dbg_SymInitializeW)(HANDLE hProcess, PCWSTR UserSearchPath, BOOL fInvadeProcess); - BOOL (WINAPI *dbg_StackWalk64)(DWORD MachineType, HANDLE hProcess, HANDLE hThread, - LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, - PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, - PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress); - PVOID (WINAPI *dbg_SymFunctionTableAccess64)(HANDLE hProcess, DWORD64 AddrBase); - DWORD64 (WINAPI *dbg_SymGetModuleBase64)(HANDLE hProcess, DWORD64 qwAddr); - BOOL (WINAPI *dbg_SymFromAddrW)(HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, PSYMBOL_INFOW Symbol); - BOOL (WINAPI *dbg_SymGetLineFromAddrW64)(HANDLE hProcess, DWORD64 dwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINEW64 Line); - BOOL (WINAPI *dbg_SymGetModuleInfoW64)(HANDLE hProcess, DWORD64 qwAddr, PIMAGEHLP_MODULEW64 ModuleInfo); - - *(FARPROC*)&dbg_SymSetOptions = GetProcAddress(dbghelp, "SymSetOptions"); - *(FARPROC*)&dbg_SymInitializeW = GetProcAddress(dbghelp, "SymInitializeW"); - *(FARPROC*)&dbg_StackWalk64 = GetProcAddress(dbghelp, "StackWalk64"); - *(FARPROC*)&dbg_SymFunctionTableAccess64 = GetProcAddress(dbghelp, "SymFunctionTableAccess64"); - *(FARPROC*)&dbg_SymGetModuleBase64 = GetProcAddress(dbghelp, "SymGetModuleBase64"); - *(FARPROC*)&dbg_SymFromAddrW = GetProcAddress(dbghelp, "SymFromAddrW"); - *(FARPROC*)&dbg_SymGetLineFromAddrW64 = GetProcAddress(dbghelp, "SymGetLineFromAddrW64"); - *(FARPROC*)&dbg_SymGetModuleInfoW64 = GetProcAddress(dbghelp, "SymGetModuleInfoW64"); - - if(dbg_SymSetOptions && dbg_SymInitializeW && dbg_StackWalk64 && dbg_SymFunctionTableAccess64 && dbg_SymGetModuleBase64 && dbg_SymFromAddrW && dbg_SymGetLineFromAddrW64 && dbg_SymGetModuleInfoW64) - { - HANDLE process = GetCurrentProcess(); - HANDLE thread = GetCurrentThread(); - CONTEXT* context = exception_ptrs->ContextRecord; - - dbg_SymSetOptions(SYMOPT_EXACT_SYMBOLS | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); - if(dbg_SymInitializeW(process, L"", TRUE)) - { - // check that raddbg.pdb file is good - B32 raddbg_pdb_valid = 0; - { - IMAGEHLP_MODULEW64 module = {0}; - module.SizeOfStruct = sizeof(module); - if(dbg_SymGetModuleInfoW64(process, (DWORD64)&win32_exception_filter, &module)) - { - raddbg_pdb_valid = (module.SymType == SymPdb); - } - } - - if(!raddbg_pdb_valid) - { - buflen += wnsprintfW(buffer + buflen, sizeof(buffer) - buflen, - L"\nThe PDB debug information file for this executable is not valid or was not found. Please rebuild binary to get the call stack.\n"); - } - else - { - STACKFRAME64 frame = {0}; - DWORD image_type; -#if defined(_M_AMD64) - image_type = IMAGE_FILE_MACHINE_AMD64; - frame.AddrPC.Offset = context->Rip; - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrFrame.Offset = context->Rbp; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrStack.Offset = context->Rsp; - frame.AddrStack.Mode = AddrModeFlat; -#elif defined(_M_ARM64) - image_type = IMAGE_FILE_MACHINE_ARM64; - frame.AddrPC.Offset = context->Pc; - frame.AddrPC.Mode = AddrModeFlat; - frame.AddrFrame.Offset = context->Fp; - frame.AddrFrame.Mode = AddrModeFlat; - frame.AddrStack.Offset = context->Sp; - frame.AddrStack.Mode = AddrModeFlat; -#else -# error Architecture not supported! -#endif - - for(U32 idx=0; ;idx++) - { - const U32 max_frames = 32; - if(idx == max_frames) - { - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"..."); - break; - } - - if(!dbg_StackWalk64(image_type, process, thread, &frame, context, 0, dbg_SymFunctionTableAccess64, dbg_SymGetModuleBase64, 0)) - { - break; - } - - U64 address = frame.AddrPC.Offset; - if(address == 0) - { - break; - } - - if(idx==0) - { -#if BUILD_CONSOLE_INTERFACE - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\nCreate a new issue with this report at %S.\n\n", BUILD_ISSUES_LINK_STRING_LITERAL); -#else - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, - L"\nPress Ctrl+C to copy this text to clipboard, then create a new issue at\n" - L"%S\n\n", BUILD_ISSUES_LINK_STRING_LITERAL, BUILD_ISSUES_LINK_STRING_LITERAL); -#endif - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"Call stack:\n"); - } - - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"%u. [0x%I64x]", idx + 1, address); - - struct { - SYMBOL_INFOW info; - WCHAR name[MAX_SYM_NAME]; - } symbol = {0}; - - symbol.info.SizeOfStruct = sizeof(symbol.info); - symbol.info.MaxNameLen = MAX_SYM_NAME; - - DWORD64 displacement = 0; - if(dbg_SymFromAddrW(process, address, &displacement, &symbol.info)) - { - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L" %s +%u", symbol.info.Name, (DWORD)displacement); - - IMAGEHLP_LINEW64 line = {0}; - line.SizeOfStruct = sizeof(line); - - DWORD line_displacement = 0; - if(dbg_SymGetLineFromAddrW64(process, address, &line_displacement, &line)) - { - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L", %s line %u", PathFindFileNameW(line.FileName), line.LineNumber); - } - } - else - { - IMAGEHLP_MODULEW64 module = {0}; - module.SizeOfStruct = sizeof(module); - if(dbg_SymGetModuleInfoW64(process, address, &module)) - { - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L" %s", module.ModuleName); - } - } - - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\n"); - } - } - } - } - } - - buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\nVersion: %S%S", BUILD_VERSION_STRING_LITERAL, BUILD_GIT_HASH_STRING_LITERAL_APPEND); - -#if BUILD_CONSOLE_INTERFACE - fwprintf(stderr, L"\n--- Fatal Exception ---\n"); - fwprintf(stderr, L"%s\n\n", buffer); -#else - TASKDIALOGCONFIG dialog = {0}; - dialog.cbSize = sizeof(dialog); - dialog.dwFlags = TDF_SIZE_TO_CONTENT | TDF_ENABLE_HYPERLINKS | TDF_ALLOW_DIALOG_CANCELLATION; - dialog.pszMainIcon = TD_ERROR_ICON; - dialog.dwCommonButtons = TDCBF_CLOSE_BUTTON; - dialog.pszWindowTitle = L"Fatal Exception"; - dialog.pszContent = buffer; - dialog.pfCallback = &win32_dialog_callback; - TaskDialogIndirect(&dialog, 0, 0, 0); -#endif - - ExitProcess(1); -} - -#undef OS_WINDOWS // shlwapi uses its own OS_WINDOWS include inside -#define OS_WINDOWS 1 - -internal void -w32_entry_point_caller(int argc, WCHAR **wargv) -{ - SetUnhandledExceptionFilter(&win32_exception_filter); - Arena *args_arena = arena_alloc__sized(MB(1), KB(32)); - char **argv = push_array(args_arena, char *, argc); - for(int i = 0; i < argc; i += 1) - { - String16 arg16 = str16_cstring((U16 *)wargv[i]); - String8 arg8 = str8_from_16(args_arena, arg16); - if(str8_match(arg8, str8_lit("--quiet"), StringMatchFlag_CaseInsensitive)) - { - win32_g_is_quiet = 1; - } - argv[i] = (char *)arg8.str; - } - main_thread_base_entry_point(entry_point, argv, (U64)argc); -} - -#if BUILD_CONSOLE_INTERFACE -int wmain(int argc, WCHAR **argv) -{ - w32_entry_point_caller(argc, argv); - return 0; -} -#else -int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) -{ - w32_entry_point_caller(__argc, __wargv); - return 0; -} -#endif +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Modern Windows SDK Functions +// +// (We must dynamically link to them, since they can be missing in older SDKs) + +typedef HRESULT W32_SetThreadDescription_Type(HANDLE hThread, PCWSTR lpThreadDescription); +global W32_SetThreadDescription_Type *w32_SetThreadDescription_func = 0; + +//////////////////////////////// +//~ rjf: File Info Conversion Helpers + +internal FilePropertyFlags +os_w32_file_property_flags_from_dwFileAttributes(DWORD dwFileAttributes) +{ + FilePropertyFlags flags = 0; + if(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + flags |= FilePropertyFlag_IsFolder; + } + return flags; +} + +internal void +os_w32_file_properties_from_attribute_data(FileProperties *properties, WIN32_FILE_ATTRIBUTE_DATA *attributes) +{ + properties->size = Compose64Bit(attributes->nFileSizeHigh, attributes->nFileSizeLow); + os_w32_dense_time_from_file_time(&properties->created, &attributes->ftCreationTime); + os_w32_dense_time_from_file_time(&properties->modified, &attributes->ftLastWriteTime); + properties->flags = os_w32_file_property_flags_from_dwFileAttributes(attributes->dwFileAttributes); +} + +//////////////////////////////// +//~ rjf: Time Conversion Helpers + +internal void +os_w32_date_time_from_system_time(DateTime *out, SYSTEMTIME *in) +{ + out->year = in->wYear; + out->mon = in->wMonth - 1; + out->wday = in->wDayOfWeek; + out->day = in->wDay; + out->hour = in->wHour; + out->min = in->wMinute; + out->sec = in->wSecond; + out->msec = in->wMilliseconds; +} + +internal void +os_w32_system_time_from_date_time(SYSTEMTIME *out, DateTime *in) +{ + out->wYear = (WORD)(in->year); + out->wMonth = in->mon + 1; + out->wDay = in->day; + out->wHour = in->hour; + out->wMinute = in->min; + out->wSecond = in->sec; + out->wMilliseconds = in->msec; +} + +internal void +os_w32_dense_time_from_file_time(DenseTime *out, FILETIME *in) +{ + SYSTEMTIME systime = {0}; + FileTimeToSystemTime(in, &systime); + DateTime date_time = {0}; + os_w32_date_time_from_system_time(&date_time, &systime); + *out = dense_time_from_date_time(date_time); +} + +internal U32 +os_w32_sleep_ms_from_endt_us(U64 endt_us) +{ + U32 sleep_ms = 0; + if(endt_us == max_U64) + { + sleep_ms = INFINITE; + } + else + { + U64 begint = os_now_microseconds(); + if(begint < endt_us) + { + U64 sleep_us = endt_us - begint; + sleep_ms = (U32)((sleep_us + 999)/1000); + } + } + return sleep_ms; +} + +//////////////////////////////// +//~ rjf: Entity Functions + +internal OS_W32_Entity * +os_w32_entity_alloc(OS_W32_EntityKind kind) +{ + OS_W32_Entity *result = 0; + EnterCriticalSection(&os_w32_state.entity_mutex); + { + result = os_w32_state.entity_free; + if(result) + { + SLLStackPop(os_w32_state.entity_free); + } + else + { + result = push_array_no_zero(os_w32_state.entity_arena, OS_W32_Entity, 1); + } + MemoryZeroStruct(result); + } + LeaveCriticalSection(&os_w32_state.entity_mutex); + result->kind = kind; + return result; +} + +internal void +os_w32_entity_release(OS_W32_Entity *entity) +{ + entity->kind = OS_W32_EntityKind_Null; + EnterCriticalSection(&os_w32_state.entity_mutex); + SLLStackPush(os_w32_state.entity_free, entity); + LeaveCriticalSection(&os_w32_state.entity_mutex); +} + +//////////////////////////////// +//~ rjf: Thread Entry Point + +internal DWORD +os_w32_thread_entry_point(void *ptr) +{ + OS_W32_Entity *entity = (OS_W32_Entity *)ptr; + OS_ThreadFunctionType *func = entity->thread.func; + void *thread_ptr = entity->thread.ptr; + TCTX tctx_; + tctx_init_and_equip(&tctx_); + func(thread_ptr); + tctx_release(); + return 0; +} + +//////////////////////////////// +//~ rjf: @os_hooks System/Process Info (Implemented Per-OS) + +internal OS_SystemInfo * +os_get_system_info(void) +{ + return &os_w32_state.system_info; +} + +internal OS_ProcessInfo * +os_get_process_info(void) +{ + return &os_w32_state.process_info; +} + +internal String8 +os_get_current_path(Arena *arena) +{ + Temp scratch = scratch_begin(&arena, 1); + DWORD length = GetCurrentDirectoryW(0, 0); + U16 *memory = push_array_no_zero(scratch.arena, U16, length + 1); + length = GetCurrentDirectoryW(length + 1, (WCHAR*)memory); + String8 name = str8_from_16(arena, str16(memory, length)); + scratch_end(scratch); + return name; +} + +//////////////////////////////// +//~ rjf: @os_hooks Memory Allocation (Implemented Per-OS) + +//- rjf: basic + +internal void * +os_reserve(U64 size) +{ + void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE); + return result; +} + +internal B32 +os_commit(void *ptr, U64 size) +{ + B32 result = (VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != 0); + return result; +} + +internal void +os_decommit(void *ptr, U64 size) +{ + VirtualFree(ptr, size, MEM_DECOMMIT); +} + +internal void +os_release(void *ptr, U64 size) +{ + // NOTE(rjf): size not used - not necessary on Windows, but necessary for other OSes. + VirtualFree(ptr, 0, MEM_RELEASE); +} + +//- rjf: large pages + +internal B32 +os_set_large_pages_enabled(B32 flag) +{ + B32 is_ok = 0; + HANDLE token; + if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) + { + LUID luid; + if(LookupPrivilegeValue(0, SE_LOCK_MEMORY_NAME, &luid)) + { + TOKEN_PRIVILEGES priv; + priv.PrivilegeCount = 1; + priv.Privileges[0].Luid = luid; + priv.Privileges[0].Attributes = flag ? SE_PRIVILEGE_ENABLED: 0; + if(AdjustTokenPrivileges(token, 0, &priv, sizeof(priv), 0, 0)) + { + os_w32_state.large_pages_enabled = flag; + is_ok = 1; + } + } + CloseHandle(token); + } + return is_ok; +} + +internal B32 +os_large_pages_enabled(void) +{ + return os_w32_state.large_pages_enabled; +} + +internal void * +os_reserve_large(U64 size) +{ + // we commit on reserve because windows + void *result = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_LARGE_PAGES, PAGE_READWRITE); + return result; +} + +internal B32 +os_commit_large(void *ptr, U64 size) +{ + return 1; +} + +//////////////////////////////// +//~ rjf: @os_hooks Thread Info (Implemented Per-OS) + +internal U32 +os_tid(void) +{ + DWORD id = GetCurrentThreadId(); + return (U32)id; +} + +internal void +os_set_thread_name(String8 name) +{ + Temp scratch = scratch_begin(0, 0); + + // rjf: windows 10 style + if(w32_SetThreadDescription_func) + { + String16 name16 = str16_from_8(scratch.arena, name); + HRESULT hr = w32_SetThreadDescription_func(GetCurrentThread(), (WCHAR*)name16.str); + } + + // rjf: raise-exception style + { + String8 name_copy = push_str8_copy(scratch.arena, name); +#pragma pack(push,8) + typedef struct THREADNAME_INFO THREADNAME_INFO; + struct THREADNAME_INFO + { + U32 dwType; // Must be 0x1000. + char *szName; // Pointer to name (in user addr space). + U32 dwThreadID; // Thread ID (-1=caller thread). + U32 dwFlags; // Reserved for future use, must be zero. + }; +#pragma pack(pop) + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = (char *)name_copy.str; + info.dwThreadID = os_tid(); + info.dwFlags = 0; +#pragma warning(push) +#pragma warning(disable: 6320 6322) + __try + { + RaiseException(0x406D1388, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + } +#pragma warning(pop) + } + + scratch_end(scratch); +} + +//////////////////////////////// +//~ rjf: @os_hooks Aborting (Implemented Per-OS) + +internal void +os_abort(S32 exit_code) +{ + ExitProcess(exit_code); +} + +//////////////////////////////// +//~ rjf: @os_hooks File System (Implemented Per-OS) + +//- rjf: files + +internal OS_Handle +os_file_open(OS_AccessFlags flags, String8 path) +{ + OS_Handle result = {0}; + Temp scratch = scratch_begin(0, 0); + String16 path16 = str16_from_8(scratch.arena, path); + DWORD access_flags = 0; + DWORD share_mode = 0; + DWORD creation_disposition = OPEN_EXISTING; + if(flags & OS_AccessFlag_Read) {access_flags |= GENERIC_READ;} + if(flags & OS_AccessFlag_Write) {access_flags |= GENERIC_WRITE;} + if(flags & OS_AccessFlag_Execute) {access_flags |= GENERIC_EXECUTE;} + if(flags & OS_AccessFlag_ShareRead) {share_mode |= FILE_SHARE_READ;} + if(flags & OS_AccessFlag_ShareWrite) {share_mode |= FILE_SHARE_WRITE|FILE_SHARE_DELETE;} + if(flags & OS_AccessFlag_Write) {creation_disposition = CREATE_ALWAYS;} + if(flags & OS_AccessFlag_Append) {creation_disposition = OPEN_ALWAYS;} + HANDLE file = CreateFileW((WCHAR *)path16.str, access_flags, share_mode, 0, creation_disposition, FILE_ATTRIBUTE_NORMAL, 0); + if(file != INVALID_HANDLE_VALUE) + { + result.u64[0] = (U64)file; + } + scratch_end(scratch); + return result; +} + +internal void +os_file_close(OS_Handle file) +{ + if(os_handle_match(file, os_handle_zero())) { return; } + HANDLE handle = (HANDLE)file.u64[0]; + BOOL result = CloseHandle(handle); + (void)result; +} + +internal U64 +os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) +{ + if(os_handle_match(file, os_handle_zero())) { return 0; } + HANDLE handle = (HANDLE)file.u64[0]; + + // rjf: clamp range by file size + U64 size = 0; + GetFileSizeEx(handle, (LARGE_INTEGER *)&size); + Rng1U64 rng_clamped = r1u64(ClampTop(rng.min, size), ClampTop(rng.max, size)); + U64 total_read_size = 0; + + // rjf: read loop + { + U64 to_read = dim_1u64(rng_clamped); + for(U64 off = rng.min; total_read_size < to_read;) + { + U64 amt64 = to_read - total_read_size; + U32 amt32 = u32_from_u64_saturate(amt64); + DWORD read_size = 0; + OVERLAPPED overlapped = {0}; + overlapped.Offset = (off&0x00000000ffffffffull); + overlapped.OffsetHigh = (off&0xffffffff00000000ull) >> 32; + ReadFile(handle, (U8 *)out_data + total_read_size, amt32, &read_size, &overlapped); + off += read_size; + total_read_size += read_size; + if(read_size != amt32) + { + break; + } + } + } + + return total_read_size; +} + +internal void +os_file_write(OS_Handle file, Rng1U64 rng, void *data) +{ + if(os_handle_match(file, os_handle_zero())) { return; } + HANDLE win_handle = (HANDLE)file.u64[0]; + U64 src_off = 0; + U64 dst_off = rng.min; + U64 bytes_to_write_total = rng.max-rng.min; + for(;src_off < bytes_to_write_total;) + { + void *bytes_src = (void *)((U8 *)data + src_off); + U64 bytes_to_write_64 = (bytes_to_write_total-src_off); + U32 bytes_to_write_32 = u32_from_u64_saturate(bytes_to_write_64); + U32 bytes_written = 0; + OVERLAPPED overlapped = {0}; + overlapped.Offset = (dst_off&0x00000000ffffffffull); + overlapped.OffsetHigh = (dst_off&0xffffffff00000000ull) >> 32; + BOOL success = WriteFile(win_handle, bytes_src, bytes_to_write_32, (DWORD *)&bytes_written, &overlapped); + if(success == 0) + { + break; + } + src_off += bytes_written; + dst_off += bytes_written; + } +} + +internal B32 +os_file_set_times(OS_Handle file, DateTime time) +{ + if(os_handle_match(file, os_handle_zero())) { return 0; } + B32 result = 0; + HANDLE handle = (HANDLE)file.u64[0]; + SYSTEMTIME system_time = {0}; + w32_system_time_from_date_time(&system_time, &time); + FILETIME file_time = {0}; + result = (SystemTimeToFileTime(&system_time, &file_time) && + SetFileTime(handle, &file_time, &file_time, &file_time)); + return result; +} + +internal FileProperties +os_properties_from_file(OS_Handle file) +{ + if(os_handle_match(file, os_handle_zero())) { FileProperties r = {0}; return r; } + FileProperties props = {0}; + HANDLE handle = (HANDLE)file.u64[0]; + BY_HANDLE_FILE_INFORMATION info; + BOOL info_good = GetFileInformationByHandle(handle, &info); + if(info_good) + { + U32 size_lo = info.nFileSizeLow; + U32 size_hi = info.nFileSizeHigh; + props.size = (U64)size_lo | (((U64)size_hi)<<32); + os_w32_dense_time_from_file_time(&props.modified, &info.ftLastWriteTime); + os_w32_dense_time_from_file_time(&props.created, &info.ftCreationTime); + props.flags = os_w32_file_property_flags_from_dwFileAttributes(info.dwFileAttributes); + } + return props; +} + +internal OS_FileID +os_id_from_file(OS_Handle file) +{ + if(os_handle_match(file, os_handle_zero())) { OS_FileID r = {0}; return r; } + OS_FileID result = {0}; + HANDLE handle = (HANDLE)file.u64[0]; + BY_HANDLE_FILE_INFORMATION info; + BOOL is_ok = GetFileInformationByHandle(handle, &info); + if(is_ok) + { + result.v[0] = info.dwVolumeSerialNumber; + result.v[1] = info.nFileIndexLow; + result.v[2] = info.nFileIndexHigh; + } + return result; +} + +internal B32 +os_delete_file_at_path(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String16 path16 = str16_from_8(scratch.arena, path); + B32 result = DeleteFileW((WCHAR*)path16.str); + scratch_end(scratch); + return result; +} + +internal B32 +os_copy_file_path(String8 dst, String8 src) +{ + Temp scratch = scratch_begin(0, 0); + String16 dst16 = str16_from_8(scratch.arena, dst); + String16 src16 = str16_from_8(scratch.arena, src); + B32 result = CopyFileW((WCHAR*)src16.str, (WCHAR*)dst16.str, 0); + scratch_end(scratch); + return result; +} + +internal String8 +os_full_path_from_path(Arena *arena, String8 path) +{ + Temp scratch = scratch_begin(&arena, 1); + DWORD buffer_size = MAX_PATH + 1; + U16 *buffer = push_array_no_zero(scratch.arena, U16, buffer_size); + String16 path16 = str16_from_8(scratch.arena, path); + DWORD path16_size = GetFullPathNameW((WCHAR*)path16.str, buffer_size, (WCHAR*)buffer, NULL); + String8 full_path = str8_from_16(arena, str16(buffer, path16_size)); + scratch_end(scratch); + return full_path; +} + +internal B32 +os_file_path_exists(String8 path) +{ + Temp scratch = scratch_begin(0,0); + String16 path16 = str16_from_8(scratch.arena, path); + DWORD attributes = GetFileAttributesW((WCHAR *)path16.str); + B32 exists = (attributes != INVALID_FILE_ATTRIBUTES) && !!(~attributes & FILE_ATTRIBUTE_DIRECTORY); + scratch_end(scratch); + return exists; +} + +internal FileProperties +os_properties_from_file_path(String8 path) +{ + WIN32_FIND_DATAW find_data = {0}; + Temp scratch = scratch_begin(0, 0); + String16 path16 = str16_from_8(scratch.arena, path); + HANDLE handle = FindFirstFileW((WCHAR *)path16.str, &find_data); + FileProperties props = {0}; + if(handle != INVALID_HANDLE_VALUE) + { + props.size = Compose64Bit(find_data.nFileSizeHigh, find_data.nFileSizeLow); + os_w32_dense_time_from_file_time(&props.created, &find_data.ftCreationTime); + os_w32_dense_time_from_file_time(&props.modified, &find_data.ftLastWriteTime); + props.flags = os_w32_file_property_flags_from_dwFileAttributes(find_data.dwFileAttributes); + } + FindClose(handle); + scratch_end(scratch); + return props; +} + +//- rjf: file maps + +internal OS_Handle +os_file_map_open(OS_AccessFlags flags, OS_Handle file) +{ + OS_Handle map = {0}; + { + HANDLE file_handle = (HANDLE)file.u64[0]; + DWORD protect_flags = 0; + { + switch(flags) + { + default:{}break; + case OS_AccessFlag_Read: + {protect_flags = PAGE_READONLY;}break; + case OS_AccessFlag_Write: + case OS_AccessFlag_Read|OS_AccessFlag_Write: + {protect_flags = PAGE_READWRITE;}break; + case OS_AccessFlag_Execute: + case OS_AccessFlag_Read|OS_AccessFlag_Execute: + {protect_flags = PAGE_EXECUTE_READ;}break; + case OS_AccessFlag_Execute|OS_AccessFlag_Write|OS_AccessFlag_Read: + case OS_AccessFlag_Execute|OS_AccessFlag_Write: + {protect_flags = PAGE_EXECUTE_READWRITE;}break; + } + } + HANDLE map_handle = CreateFileMappingA(file_handle, 0, protect_flags, 0, 0, 0); + map.u64[0] = (U64)map_handle; + } + return map; +} + +internal void +os_file_map_close(OS_Handle map) +{ + HANDLE handle = (HANDLE)map.u64[0]; + BOOL result = CloseHandle(handle); + (void)result; +} + +internal void * +os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) +{ + HANDLE handle = (HANDLE)map.u64[0]; + U32 off_lo = (U32)((range.min&0x00000000ffffffffull)>>0); + U32 off_hi = (U32)((range.min&0xffffffff00000000ull)>>32); + U64 size = dim_1u64(range); + DWORD access_flags = 0; + { + switch(flags) + { + default:{}break; + case OS_AccessFlag_Read: + { + access_flags = FILE_MAP_READ; + }break; + case OS_AccessFlag_Write: + { + access_flags = FILE_MAP_WRITE; + }break; + case OS_AccessFlag_Read|OS_AccessFlag_Write: + { + access_flags = FILE_MAP_ALL_ACCESS; + }break; + case OS_AccessFlag_Execute: + case OS_AccessFlag_Read|OS_AccessFlag_Execute: + case OS_AccessFlag_Write|OS_AccessFlag_Execute: + case OS_AccessFlag_Read|OS_AccessFlag_Write|OS_AccessFlag_Execute: + { + access_flags = FILE_MAP_ALL_ACCESS|FILE_MAP_EXECUTE; + }break; + } + } + void *result = MapViewOfFile(handle, access_flags, off_hi, off_lo, size); + return result; +} + +internal void +os_file_map_view_close(OS_Handle map, void *ptr) +{ + BOOL result = UnmapViewOfFile(ptr); + (void)result; +} + +//- rjf: directory iteration + +internal OS_FileIter * +os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) +{ + Temp scratch = scratch_begin(&arena, 1); + String8 path_with_wildcard = push_str8_cat(scratch.arena, path, str8_lit("\\*")); + String16 path16 = str16_from_8(scratch.arena, path_with_wildcard); + OS_FileIter *iter = push_array(arena, OS_FileIter, 1); + iter->flags = flags; + OS_W32_FileIter *w32_iter = (OS_W32_FileIter*)iter->memory; + if(path.size == 0) + { + w32_iter->is_volume_iter = 1; + WCHAR buffer[512] = {0}; + DWORD length = GetLogicalDriveStringsW(sizeof(buffer), buffer); + String8List drive_strings = {0}; + for(U64 off = 0; off < (U64)length;) + { + String16 next_drive_string_16 = str16_cstring((U16 *)buffer+off); + off += next_drive_string_16.size+1; + String8 next_drive_string = str8_from_16(arena, next_drive_string_16); + next_drive_string = str8_chop_last_slash(next_drive_string); + str8_list_push(scratch.arena, &drive_strings, next_drive_string); + } + w32_iter->drive_strings = str8_array_from_list(arena, &drive_strings); + w32_iter->drive_strings_iter_idx = 0; + } + else + { + w32_iter->handle = FindFirstFileW((WCHAR*)path16.str, &w32_iter->find_data); + } + scratch_end(scratch); + return iter; +} + +internal B32 +os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) +{ + B32 result = 0; + OS_FileIterFlags flags = iter->flags; + OS_W32_FileIter *w32_iter = (OS_W32_FileIter*)iter->memory; + switch(w32_iter->is_volume_iter) + { + //- rjf: file iteration + default: + case 0: + { + if (!(flags & OS_FileIterFlag_Done) && w32_iter->handle != INVALID_HANDLE_VALUE) + { + do + { + // check is usable + B32 usable_file = 1; + + WCHAR *file_name = w32_iter->find_data.cFileName; + DWORD attributes = w32_iter->find_data.dwFileAttributes; + if (file_name[0] == '.'){ + if (flags & OS_FileIterFlag_SkipHiddenFiles){ + usable_file = 0; + } + else if (file_name[1] == 0){ + usable_file = 0; + } + else if (file_name[1] == '.' && file_name[2] == 0){ + usable_file = 0; + } + } + if (attributes & FILE_ATTRIBUTE_DIRECTORY){ + if (flags & OS_FileIterFlag_SkipFolders){ + usable_file = 0; + } + } + else{ + if (flags & OS_FileIterFlag_SkipFiles){ + usable_file = 0; + } + } + + // emit if usable + if (usable_file){ + info_out->name = str8_from_16(arena, str16_cstring((U16*)file_name)); + info_out->props.size = (U64)w32_iter->find_data.nFileSizeLow | (((U64)w32_iter->find_data.nFileSizeHigh)<<32); + os_w32_dense_time_from_file_time(&info_out->props.created, &w32_iter->find_data.ftCreationTime); + os_w32_dense_time_from_file_time(&info_out->props.modified, &w32_iter->find_data.ftLastWriteTime); + info_out->props.flags = os_w32_file_property_flags_from_dwFileAttributes(attributes); + result = 1; + if (!FindNextFileW(w32_iter->handle, &w32_iter->find_data)){ + iter->flags |= OS_FileIterFlag_Done; + } + break; + } + }while(FindNextFileW(w32_iter->handle, &w32_iter->find_data)); + } + }break; + + //- rjf: volume iteration + case 1: + { + result = w32_iter->drive_strings_iter_idx < w32_iter->drive_strings.count; + if(result != 0) + { + MemoryZeroStruct(info_out); + info_out->name = w32_iter->drive_strings.v[w32_iter->drive_strings_iter_idx]; + info_out->props.flags |= FilePropertyFlag_IsFolder; + w32_iter->drive_strings_iter_idx += 1; + } + }break; + } + if(!result) + { + iter->flags |= OS_FileIterFlag_Done; + } + return result; +} + +internal void +os_file_iter_end(OS_FileIter *iter) +{ + OS_W32_FileIter *w32_iter = (OS_W32_FileIter*)iter->memory; + FindClose(w32_iter->handle); +} + +//- rjf: directory creation + +internal B32 +os_make_directory(String8 path) +{ + B32 result = 0; + Temp scratch = scratch_begin(0, 0); + String16 name16 = str16_from_8(scratch.arena, path); + WIN32_FILE_ATTRIBUTE_DATA attributes = {0}; + GetFileAttributesExW((WCHAR*)name16.str, GetFileExInfoStandard, &attributes); + if(attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + result = 1; + } + else if(CreateDirectoryW((WCHAR*)name16.str, 0)) + { + result = 1; + } + scratch_end(scratch); + return(result); +} + +//////////////////////////////// +//~ rjf: @os_hooks Shared Memory (Implemented Per-OS) + +internal OS_Handle +os_shared_memory_alloc(U64 size, String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String16 name16 = str16_from_8(scratch.arena, name); + HANDLE file = CreateFileMappingW(INVALID_HANDLE_VALUE, + 0, + PAGE_READWRITE, + (U32)((size & 0xffffffff00000000) >> 32), + (U32)((size & 0x00000000ffffffff)), + (WCHAR *)name16.str); + OS_Handle result = {(U64)file}; + scratch_end(scratch); + return result; +} + +internal OS_Handle +os_shared_memory_open(String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String16 name16 = str16_from_8(scratch.arena, name); + HANDLE file = OpenFileMappingW(FILE_MAP_ALL_ACCESS, 0, (WCHAR *)name16.str); + OS_Handle result = {(U64)file}; + scratch_end(scratch); + return result; +} + +internal void +os_shared_memory_close(OS_Handle handle) +{ + HANDLE file = (HANDLE)(handle.u64[0]); + CloseHandle(file); +} + +internal void * +os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) +{ + HANDLE file = (HANDLE)(handle.u64[0]); + U64 offset = range.min; + U64 size = range.max-range.min; + void *ptr = MapViewOfFile(file, FILE_MAP_ALL_ACCESS, + (U32)((offset & 0xffffffff00000000) >> 32), + (U32)((offset & 0x00000000ffffffff)), + size); + return ptr; +} + +internal void +os_shared_memory_view_close(OS_Handle handle, void *ptr) +{ + UnmapViewOfFile(ptr); +} + +//////////////////////////////// +//~ rjf: @os_hooks Time (Implemented Per-OS) + +internal U64 +os_now_microseconds(void) +{ + U64 result = 0; + LARGE_INTEGER large_int_counter; + if(QueryPerformanceCounter(&large_int_counter)) + { + result = (large_int_counter.QuadPart*Million(1))/os_w32_state.system_info.microsecond_resolution; + } + return result; +} + +internal U32 +os_now_unix(void) +{ + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + U64 win32_time = ((U64)file_time.dwHighDateTime << 32) | file_time.dwLowDateTime; + U64 unix_time64 = ((win32_time - 0x19DB1DED53E8000ULL) / 10000000); + U32 unix_time32 = (U32)unix_time64; + return unix_time32; +} + +internal DateTime +os_now_universal_time(void) +{ + SYSTEMTIME systime = {0}; + GetSystemTime(&systime); + DateTime result = {0}; + os_w32_date_time_from_system_time(&result, &systime); + return result; +} + +internal DateTime +os_universal_time_from_local(DateTime *date_time) +{ + SYSTEMTIME systime = {0}; + os_w32_system_time_from_date_time(&systime, date_time); + FILETIME ftime = {0}; + SystemTimeToFileTime(&systime, &ftime); + FILETIME ftime_local = {0}; + LocalFileTimeToFileTime(&ftime, &ftime_local); + FileTimeToSystemTime(&ftime_local, &systime); + DateTime result = {0}; + os_w32_date_time_from_system_time(&result, &systime); + return result; +} + +internal DateTime +os_local_time_from_universal(DateTime *date_time) +{ + SYSTEMTIME systime = {0}; + os_w32_system_time_from_date_time(&systime, date_time); + FILETIME ftime = {0}; + SystemTimeToFileTime(&systime, &ftime); + FILETIME ftime_local = {0}; + FileTimeToLocalFileTime(&ftime, &ftime_local); + FileTimeToSystemTime(&ftime_local, &systime); + DateTime result = {0}; + os_w32_date_time_from_system_time(&result, &systime); + return result; +} + +internal void +os_sleep_milliseconds(U32 msec) +{ + Sleep(msec); +} + +//////////////////////////////// +//~ rjf: @os_hooks Child Processes (Implemented Per-OS) + +internal OS_Handle +os_process_launch(OS_ProcessLaunchParams *params) +{ + OS_Handle result = {0}; + Temp scratch = scratch_begin(0, 0); + + //- rjf: form full command string + String8 cmd = {0}; + { + StringJoin join_params = {0}; + join_params.pre = str8_lit("\""); + join_params.sep = str8_lit("\" \""); + join_params.post = str8_lit("\""); + cmd = str8_list_join(scratch.arena, ¶ms->cmd_line, &join_params); + } + + //- rjf: form environment + B32 use_null_env_arg = 0; + String8 env = {0}; + { + StringJoin join_params2 = {0}; + join_params2.sep = str8_lit("\0"); + join_params2.post = str8_lit("\0"); + String8List all_opts = params->env; + if(params->inherit_env != 0) + { + if(all_opts.node_count != 0) + { + MemoryZeroStruct(&all_opts); + for(String8Node *n = params->env.first; n != 0; n = n->next) + { + str8_list_push(scratch.arena, &all_opts, n->string); + } + for(String8Node *n = os_w32_state.process_info.environment.first; n != 0; n = n->next) + { + str8_list_push(scratch.arena, &all_opts, n->string); + } + } + else + { + use_null_env_arg = 1; + } + } + if(use_null_env_arg == 0) + { + env = str8_list_join(scratch.arena, &all_opts, &join_params2); + } + } + + //- rjf: utf-8 -> utf-16 + String16 cmd16 = str16_from_8(scratch.arena, cmd); + String16 dir16 = str16_from_8(scratch.arena, params->path); + String16 env16 = {0}; + if(use_null_env_arg == 0) + { + env16 = str16_from_8(scratch.arena, env); + } + + //- rjf: determine creation flags + DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT; + if(params->consoleless) + { + creation_flags |= CREATE_NO_WINDOW; + } + + //- rjf: launch + STARTUPINFOW startup_info = {sizeof(startup_info)}; + PROCESS_INFORMATION process_info = {0}; + if(CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, 0, creation_flags, use_null_env_arg ? 0 : (WCHAR*)env16.str, (WCHAR*)dir16.str, &startup_info, &process_info)) + { + result.u64[0] = (U64)process_info.hProcess; + CloseHandle(process_info.hThread); + } + + scratch_end(scratch); + return result; +} + +internal B32 +os_process_join(OS_Handle handle, U64 endt_us) +{ + HANDLE process = (HANDLE)(handle.u64[0]); + DWORD sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + DWORD result = WaitForSingleObject(process, sleep_ms); + return (result == WAIT_OBJECT_0); +} + +internal void +os_process_detach(OS_Handle handle) +{ + HANDLE process = (HANDLE)(handle.u64[0]); + CloseHandle(process); +} + +//////////////////////////////// +//~ rjf: @os_hooks Threads (Implemented Per-OS) + +internal OS_Handle +os_thread_launch(OS_ThreadFunctionType *func, void *ptr, void *params) +{ + OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_Thread); + entity->thread.func = func; + entity->thread.ptr = ptr; + entity->thread.handle = CreateThread(0, 0, os_w32_thread_entry_point, entity, 0, &entity->thread.tid); + OS_Handle result = {IntFromPtr(entity)}; + return result; +} + +internal B32 +os_thread_join(OS_Handle handle, U64 endt_us) +{ + DWORD sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + OS_W32_Entity *entity = (OS_W32_Entity *)PtrFromInt(handle.u64[0]); + DWORD wait_result = WAIT_OBJECT_0; + if(entity != 0) + { + wait_result = WaitForSingleObject(entity->thread.handle, sleep_ms); + } + os_w32_entity_release(entity); + return (wait_result == WAIT_OBJECT_0); +} + +internal void +os_thread_detach(OS_Handle thread) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(thread.u64[0]); + os_w32_entity_release(entity); +} + +//////////////////////////////// +//~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) + +//- rjf: mutexes + +internal OS_Handle +os_mutex_alloc(void) +{ + OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_Mutex); + InitializeCriticalSection(&entity->mutex); + OS_Handle result = {IntFromPtr(entity)}; + return result; +} + +internal void +os_mutex_release(OS_Handle mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); + os_w32_entity_release(entity); +} + +internal void +os_mutex_take(OS_Handle mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); + EnterCriticalSection(&entity->mutex); +} + +internal void +os_mutex_drop(OS_Handle mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); + LeaveCriticalSection(&entity->mutex); +} + +//- rjf: reader/writer mutexes + +internal OS_Handle +os_rw_mutex_alloc(void) +{ + OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_RWMutex); + InitializeSRWLock(&entity->rw_mutex); + OS_Handle result = {IntFromPtr(entity)}; + return result; +} + +internal void +os_rw_mutex_release(OS_Handle rw_mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); + os_w32_entity_release(entity); +} + +internal void +os_rw_mutex_take_r(OS_Handle rw_mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); + AcquireSRWLockShared(&entity->rw_mutex); +} + +internal void +os_rw_mutex_drop_r(OS_Handle rw_mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); + ReleaseSRWLockShared(&entity->rw_mutex); +} + +internal void +os_rw_mutex_take_w(OS_Handle rw_mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); + AcquireSRWLockExclusive(&entity->rw_mutex); +} + +internal void +os_rw_mutex_drop_w(OS_Handle rw_mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); + ReleaseSRWLockExclusive(&entity->rw_mutex); +} + +//- rjf: condition variables + +internal OS_Handle +os_condition_variable_alloc(void) +{ + OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_ConditionVariable); + InitializeConditionVariable(&entity->cv); + OS_Handle result = {IntFromPtr(entity)}; + return result; +} + +internal void +os_condition_variable_release(OS_Handle cv) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + os_w32_entity_release(entity); +} + +internal B32 +os_condition_variable_wait(OS_Handle cv, OS_Handle mutex, U64 endt_us) +{ + U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + BOOL result = 0; + if(sleep_ms > 0) + { + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + OS_W32_Entity *mutex_entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); + result = SleepConditionVariableCS(&entity->cv, &mutex_entity->mutex, sleep_ms); + } + return result; +} + +internal B32 +os_condition_variable_wait_rw_r(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) +{ + U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + BOOL result = 0; + if(sleep_ms > 0) + { + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + OS_W32_Entity *mutex_entity = (OS_W32_Entity*)PtrFromInt(mutex_rw.u64[0]); + result = SleepConditionVariableSRW(&entity->cv, &mutex_entity->rw_mutex, sleep_ms, + CONDITION_VARIABLE_LOCKMODE_SHARED); + } + return result; +} + +internal B32 +os_condition_variable_wait_rw_w(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) +{ + U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + BOOL result = 0; + if(sleep_ms > 0) + { + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + OS_W32_Entity *mutex_entity = (OS_W32_Entity*)PtrFromInt(mutex_rw.u64[0]); + result = SleepConditionVariableSRW(&entity->cv, &mutex_entity->rw_mutex, sleep_ms, 0); + } + return result; +} + +internal void +os_condition_variable_signal(OS_Handle cv) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + WakeConditionVariable(&entity->cv); +} + +internal void +os_condition_variable_broadcast(OS_Handle cv) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + WakeAllConditionVariable(&entity->cv); +} + +//- rjf: cross-process semaphores + +internal OS_Handle +os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String16 name16 = str16_from_8(scratch.arena, name); + HANDLE handle = CreateSemaphoreW(0, initial_count, max_count, (WCHAR *)name16.str); + OS_Handle result = {(U64)handle}; + scratch_end(scratch); + return result; +} + +internal void +os_semaphore_release(OS_Handle semaphore) +{ + HANDLE handle = (HANDLE)semaphore.u64[0]; + CloseHandle(handle); +} + +internal OS_Handle +os_semaphore_open(String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String16 name16 = str16_from_8(scratch.arena, name); + HANDLE handle = OpenSemaphoreW(SEMAPHORE_ALL_ACCESS , 0, (WCHAR *)name16.str); + OS_Handle result = {(U64)handle}; + scratch_end(scratch); + return result; +} + +internal void +os_semaphore_close(OS_Handle semaphore) +{ + HANDLE handle = (HANDLE)semaphore.u64[0]; + CloseHandle(handle); +} + +internal B32 +os_semaphore_take(OS_Handle semaphore, U64 endt_us) +{ + U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + HANDLE handle = (HANDLE)semaphore.u64[0]; + DWORD wait_result = WaitForSingleObject(handle, sleep_ms); + B32 result = (wait_result == WAIT_OBJECT_0); + return result; +} + +internal void +os_semaphore_drop(OS_Handle semaphore) +{ + HANDLE handle = (HANDLE)semaphore.u64[0]; + ReleaseSemaphore(handle, 1, 0); +} + +//////////////////////////////// +//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) + +internal OS_Handle +os_library_open(String8 path){ + Temp scratch = scratch_begin(0, 0); + String16 path16 = str16_from_8(scratch.arena, path); + HMODULE mod = LoadLibraryW((LPCWSTR)path16.str); + OS_Handle result = { (U64)mod }; + scratch_end(scratch); + return(result); +} + +internal VoidProc* +os_library_load_proc(OS_Handle lib, String8 name){ + Temp scratch = scratch_begin(0, 0); + HMODULE mod = (HMODULE)lib.u64[0]; + name = push_str8_copy(scratch.arena, name); + VoidProc *result = (VoidProc*)GetProcAddress(mod, (LPCSTR)name.str); + scratch_end(scratch); + return(result); +} + +internal void +os_library_close(OS_Handle lib){ + HMODULE mod = (HMODULE)lib.u64[0]; + FreeLibrary(mod); +} + +//////////////////////////////// +//~ rjf: @os_hooks Safe Calls (Implemented Per-OS) + +internal void +os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, void *ptr) +{ + __try + { + func(ptr); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + if(fail_handler != 0) + { + fail_handler(ptr); + } + ExitProcess(1); + } +} + +//////////////////////////////// +//~ rjf: @os_hooks GUIDs (Implemented Per-OS) + +internal OS_Guid +os_make_guid(void) +{ + OS_Guid result; MemoryZeroStruct(&result); + UUID uuid; + RPC_STATUS rpc_status = UuidCreate(&uuid); + if(rpc_status == RPC_S_OK) + { + result.data1 = uuid.Data1; + result.data2 = uuid.Data2; + result.data3 = uuid.Data3; + MemoryCopyArray(result.data4, uuid.Data4); + } + return result; +} + +//////////////////////////////// +//~ rjf: @os_hooks Entry Points (Implemented Per-OS) + +#include +#undef OS_WINDOWS // shlwapi uses its own OS_WINDOWS include inside +#include + +internal B32 win32_g_is_quiet = 0; + +internal HRESULT WINAPI +win32_dialog_callback(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, LONG_PTR data) +{ + if(msg == TDN_HYPERLINK_CLICKED) + { + ShellExecuteW(NULL, L"open", (LPWSTR)lparam, NULL, NULL, SW_SHOWNORMAL); + } + return S_OK; +} + +internal LONG WINAPI +win32_exception_filter(EXCEPTION_POINTERS* exception_ptrs) +{ + if(win32_g_is_quiet) + { + ExitProcess(1); + } + + static volatile LONG first = 0; + if(InterlockedCompareExchange(&first, 1, 0) != 0) + { + // prevent failures in other threads to popup same message box + // this handler just shows first thread that crashes + // we are terminating afterwards anyway + for (;;) Sleep(1000); + } + + WCHAR buffer[4096] = {0}; + int buflen = 0; + + DWORD exception_code = exception_ptrs->ExceptionRecord->ExceptionCode; + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"A fatal exception (code 0x%x) occurred. The process is terminating.\n", exception_code); + + // load dbghelp dynamically just in case if it is missing + HMODULE dbghelp = LoadLibraryA("dbghelp.dll"); + if(dbghelp) + { + DWORD (WINAPI *dbg_SymSetOptions)(DWORD SymOptions); + BOOL (WINAPI *dbg_SymInitializeW)(HANDLE hProcess, PCWSTR UserSearchPath, BOOL fInvadeProcess); + BOOL (WINAPI *dbg_StackWalk64)(DWORD MachineType, HANDLE hProcess, HANDLE hThread, + LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress); + PVOID (WINAPI *dbg_SymFunctionTableAccess64)(HANDLE hProcess, DWORD64 AddrBase); + DWORD64 (WINAPI *dbg_SymGetModuleBase64)(HANDLE hProcess, DWORD64 qwAddr); + BOOL (WINAPI *dbg_SymFromAddrW)(HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, PSYMBOL_INFOW Symbol); + BOOL (WINAPI *dbg_SymGetLineFromAddrW64)(HANDLE hProcess, DWORD64 dwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINEW64 Line); + BOOL (WINAPI *dbg_SymGetModuleInfoW64)(HANDLE hProcess, DWORD64 qwAddr, PIMAGEHLP_MODULEW64 ModuleInfo); + + *(FARPROC*)&dbg_SymSetOptions = GetProcAddress(dbghelp, "SymSetOptions"); + *(FARPROC*)&dbg_SymInitializeW = GetProcAddress(dbghelp, "SymInitializeW"); + *(FARPROC*)&dbg_StackWalk64 = GetProcAddress(dbghelp, "StackWalk64"); + *(FARPROC*)&dbg_SymFunctionTableAccess64 = GetProcAddress(dbghelp, "SymFunctionTableAccess64"); + *(FARPROC*)&dbg_SymGetModuleBase64 = GetProcAddress(dbghelp, "SymGetModuleBase64"); + *(FARPROC*)&dbg_SymFromAddrW = GetProcAddress(dbghelp, "SymFromAddrW"); + *(FARPROC*)&dbg_SymGetLineFromAddrW64 = GetProcAddress(dbghelp, "SymGetLineFromAddrW64"); + *(FARPROC*)&dbg_SymGetModuleInfoW64 = GetProcAddress(dbghelp, "SymGetModuleInfoW64"); + + if(dbg_SymSetOptions && dbg_SymInitializeW && dbg_StackWalk64 && dbg_SymFunctionTableAccess64 && dbg_SymGetModuleBase64 && dbg_SymFromAddrW && dbg_SymGetLineFromAddrW64 && dbg_SymGetModuleInfoW64) + { + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + CONTEXT* context = exception_ptrs->ContextRecord; + + dbg_SymSetOptions(SYMOPT_EXACT_SYMBOLS | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); + if(dbg_SymInitializeW(process, L"", TRUE)) + { + // check that raddbg.pdb file is good + B32 raddbg_pdb_valid = 0; + { + IMAGEHLP_MODULEW64 module = {0}; + module.SizeOfStruct = sizeof(module); + if(dbg_SymGetModuleInfoW64(process, (DWORD64)&win32_exception_filter, &module)) + { + raddbg_pdb_valid = (module.SymType == SymPdb); + } + } + + if(!raddbg_pdb_valid) + { + buflen += wnsprintfW(buffer + buflen, sizeof(buffer) - buflen, + L"\nThe PDB debug information file for this executable is not valid or was not found. Please rebuild binary to get the call stack.\n"); + } + else + { + STACKFRAME64 frame = {0}; + DWORD image_type; +#if defined(_M_AMD64) + image_type = IMAGE_FILE_MACHINE_AMD64; + frame.AddrPC.Offset = context->Rip; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context->Rbp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = context->Rsp; + frame.AddrStack.Mode = AddrModeFlat; +#elif defined(_M_ARM64) + image_type = IMAGE_FILE_MACHINE_ARM64; + frame.AddrPC.Offset = context->Pc; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context->Fp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = context->Sp; + frame.AddrStack.Mode = AddrModeFlat; +#else +# error Architecture not supported! +#endif + + for(U32 idx=0; ;idx++) + { + const U32 max_frames = 32; + if(idx == max_frames) + { + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"..."); + break; + } + + if(!dbg_StackWalk64(image_type, process, thread, &frame, context, 0, dbg_SymFunctionTableAccess64, dbg_SymGetModuleBase64, 0)) + { + break; + } + + U64 address = frame.AddrPC.Offset; + if(address == 0) + { + break; + } + + if(idx==0) + { +#if BUILD_CONSOLE_INTERFACE + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\nCreate a new issue with this report at %S.\n\n", BUILD_ISSUES_LINK_STRING_LITERAL); +#else + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, + L"\nPress Ctrl+C to copy this text to clipboard, then create a new issue at\n" + L"%S\n\n", BUILD_ISSUES_LINK_STRING_LITERAL, BUILD_ISSUES_LINK_STRING_LITERAL); +#endif + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"Call stack:\n"); + } + + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"%u. [0x%I64x]", idx + 1, address); + + struct { + SYMBOL_INFOW info; + WCHAR name[MAX_SYM_NAME]; + } symbol = {0}; + + symbol.info.SizeOfStruct = sizeof(symbol.info); + symbol.info.MaxNameLen = MAX_SYM_NAME; + + DWORD64 displacement = 0; + if(dbg_SymFromAddrW(process, address, &displacement, &symbol.info)) + { + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L" %s +%u", symbol.info.Name, (DWORD)displacement); + + IMAGEHLP_LINEW64 line = {0}; + line.SizeOfStruct = sizeof(line); + + DWORD line_displacement = 0; + if(dbg_SymGetLineFromAddrW64(process, address, &line_displacement, &line)) + { + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L", %s line %u", PathFindFileNameW(line.FileName), line.LineNumber); + } + } + else + { + IMAGEHLP_MODULEW64 module = {0}; + module.SizeOfStruct = sizeof(module); + if(dbg_SymGetModuleInfoW64(process, address, &module)) + { + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L" %s", module.ModuleName); + } + } + + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\n"); + } + } + } + } + } + + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\nVersion: %S%S", BUILD_VERSION_STRING_LITERAL, BUILD_GIT_HASH_STRING_LITERAL_APPEND); + +#if BUILD_CONSOLE_INTERFACE + fwprintf(stderr, L"\n--- Fatal Exception ---\n"); + fwprintf(stderr, L"%s\n\n", buffer); +#else + TASKDIALOGCONFIG dialog = {0}; + dialog.cbSize = sizeof(dialog); + dialog.dwFlags = TDF_SIZE_TO_CONTENT | TDF_ENABLE_HYPERLINKS | TDF_ALLOW_DIALOG_CANCELLATION; + dialog.pszMainIcon = TD_ERROR_ICON; + dialog.dwCommonButtons = TDCBF_CLOSE_BUTTON; + dialog.pszWindowTitle = L"Fatal Exception"; + dialog.pszContent = buffer; + dialog.pfCallback = &win32_dialog_callback; + TaskDialogIndirect(&dialog, 0, 0, 0); +#endif + + ExitProcess(1); +} + +#undef OS_WINDOWS // shlwapi uses its own OS_WINDOWS include inside +#define OS_WINDOWS 1 + +internal void +w32_entry_point_caller(int argc, WCHAR **wargv) +{ + SetUnhandledExceptionFilter(&win32_exception_filter); + + //- rjf: do OS layer initialization + { + // rjf: dynamically load windows functions which are not guaranteed + // in all SDKs + { + HMODULE module = LoadLibraryA("kernel32.dll"); + w32_SetThreadDescription_func = (W32_SetThreadDescription_Type *)GetProcAddress(module, "SetThreadDescription"); + FreeLibrary(module); + } + + // rjf: try to enable large pages if we can + { + HANDLE token; + if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) + { + LUID luid; + if(LookupPrivilegeValue(0, SE_LOCK_MEMORY_NAME, &luid)) + { + TOKEN_PRIVILEGES priv; + priv.PrivilegeCount = 1; + priv.Privileges[0].Luid = luid; + priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + AdjustTokenPrivileges(token, 0, &priv, sizeof(priv), 0, 0); + } + CloseHandle(token); + } + } + + // rjf: get system info + SYSTEM_INFO sysinfo = {0}; + GetSystemInfo(&sysinfo); + + // rjf: set up non-dynamically-alloc'd state + // + // (we need to set up some basics before this layer can supply + // memory allocation primitives) + { + OS_SystemInfo *info = &os_w32_state.system_info; + info->logical_processor_count = (U64)sysinfo.dwNumberOfProcessors; + info->page_size = sysinfo.dwPageSize; + info->large_page_size = GetLargePageMinimum(); + info->allocation_granularity = sysinfo.dwAllocationGranularity; + info->microsecond_resolution = 1; + LARGE_INTEGER large_int_resolution; + if(QueryPerformanceFrequency(&large_int_resolution)) + { + info->microsecond_resolution = large_int_resolution.QuadPart; + } + } + { + OS_ProcessInfo *info = &os_w32_state.process_info; + info->pid = GetCurrentProcessId(); + } + + // rjf: set up thread context + TCTX tctx; + tctx_init_and_equip(&tctx); + + // rjf: set up dynamically-alloc'd state + Arena *arena = arena_alloc(); + { + os_w32_state.arena = arena; + { + OS_SystemInfo *info = &os_w32_state.system_info; + U8 buffer[MAX_COMPUTERNAME_LENGTH + 1] = {0}; + DWORD size = MAX_COMPUTERNAME_LENGTH + 1; + if(GetComputerNameA((char*)buffer, &size)) + { + info->machine_name = push_str8_copy(arena, str8(buffer, size)); + } + } + } + { + OS_ProcessInfo *info = &os_w32_state.process_info; + { + Temp scratch = scratch_begin(0, 0); + DWORD size = KB(32); + U16 *buffer = push_array_no_zero(scratch.arena, U16, size); + DWORD length = GetModuleFileNameW(0, (WCHAR*)buffer, size); + String8 name8 = str8_from_16(scratch.arena, str16(buffer, length)); + String8 name_chopped = str8_chop_last_slash(name8); + info->binary_path = push_str8_copy(arena, name_chopped); + scratch_end(scratch); + } + info->initial_path = os_get_current_path(arena); + { + Temp scratch = scratch_begin(0, 0); + U64 size = KB(32); + U16 *buffer = push_array_no_zero(scratch.arena, U16, size); + if(SUCCEEDED(SHGetFolderPathW(0, CSIDL_APPDATA, 0, 0, (WCHAR*)buffer))) + { + info->user_program_data_path = str8_from_16(arena, str16_cstring(buffer)); + } + scratch_end(scratch); + } + { + WCHAR *this_proc_env = GetEnvironmentStringsW(); + U64 start_idx = 0; + for(U64 idx = 0;; idx += 1) + { + if(this_proc_env[idx] == 0) + { + if(start_idx == idx) + { + break; + } + else + { + String16 string16 = str16((U16 *)this_proc_env + start_idx, idx - start_idx); + String8 string = str8_from_16(arena, string16); + str8_list_push(arena, &info->environment, string); + start_idx = idx+1; + } + } + } + } + } + + // rjf: set up entity storage + InitializeCriticalSection(&os_w32_state.entity_mutex); + os_w32_state.entity_arena = arena_alloc(); + } + + //- rjf: extract arguments + Arena *args_arena = arena_alloc(.reserve_size = MB(1), .commit_size = KB(32)); + char **argv = push_array(args_arena, char *, argc); + for(int i = 0; i < argc; i += 1) + { + String16 arg16 = str16_cstring((U16 *)wargv[i]); + String8 arg8 = str8_from_16(args_arena, arg16); + if(str8_match(arg8, str8_lit("--quiet"), StringMatchFlag_CaseInsensitive)) + { + win32_g_is_quiet = 1; + } + argv[i] = (char *)arg8.str; + } + + //- rjf: call into "real" entry point + main_thread_base_entry_point(entry_point, argv, (U64)argc); +} + +#if BUILD_CONSOLE_INTERFACE +int wmain(int argc, WCHAR **argv) +{ + w32_entry_point_caller(argc, argv); + return 0; +} +#else +int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) +{ + w32_entry_point_caller(__argc, __wargv); + return 0; +} +#endif diff --git a/src/os/core/win32/os_core_win32.h b/src/os/core/win32/os_core_win32.h index 0b104f58..d6e1ef4c 100644 --- a/src/os/core/win32/os_core_win32.h +++ b/src/os/core/win32/os_core_win32.h @@ -1,84 +1,124 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef WIN32_H -#define WIN32_H - -//////////////////////////////// -//~ NOTE(allen): Negotiate the windows header include order - -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include -#include -#include - -//////////////////////////////// -//~ NOTE(allen): File Iterator - -typedef struct W32_FileIter W32_FileIter; -struct W32_FileIter -{ - HANDLE handle; - WIN32_FIND_DATAW find_data; - B32 is_volume_iter; - String8Array drive_strings; - U64 drive_strings_iter_idx; -}; -StaticAssert(sizeof(Member(OS_FileIter, memory)) >= sizeof(W32_FileIter), file_iter_memory_size); - -//////////////////////////////// -//~ NOTE(allen): Threading Entities - -typedef enum W32_EntityKind -{ - W32_EntityKind_Null, - W32_EntityKind_Thread, - W32_EntityKind_Mutex, - W32_EntityKind_RWMutex, - W32_EntityKind_ConditionVariable, -} -W32_EntityKind; - -typedef struct W32_Entity W32_Entity; -struct W32_Entity -{ - W32_Entity *next; - W32_EntityKind kind; - volatile U32 reference_mask; - union{ - struct{ - OS_ThreadFunctionType *func; - void *ptr; - HANDLE handle; - DWORD tid; - } thread; - CRITICAL_SECTION mutex; - SRWLOCK rw_mutex; - CONDITION_VARIABLE cv; - }; -}; - -//////////////////////////////// -//~ rjf: Helpers - -//- rjf: files -internal FilePropertyFlags w32_file_property_flags_from_dwFileAttributes(DWORD dwFileAttributes); -internal void w32_file_properties_from_attributes(FileProperties *properties, WIN32_FILE_ATTRIBUTE_DATA *attributes); - -//- rjf: time -internal void w32_date_time_from_system_time(DateTime *out, SYSTEMTIME *in); -internal void w32_system_time_from_date_time(SYSTEMTIME *out, DateTime *in); -internal void w32_dense_time_from_file_time(DenseTime *out, FILETIME *in); -internal U32 w32_sleep_ms_from_endt_us(U64 endt_us); - -//- rjf: entities -internal W32_Entity* w32_alloc_entity(W32_EntityKind kind); -internal void w32_free_entity(W32_Entity *entity); - -//- rjf: threads -internal DWORD w32_thread_base(void *ptr); - -#endif //WIN32_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_CORE_WIN32_H +#define OS_CORE_WIN32_H + +//////////////////////////////// +//~ rjf: Includes / Libraries + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#pragma comment(lib, "user32") +#pragma comment(lib, "winmm") +#pragma comment(lib, "shell32") +#pragma comment(lib, "advapi32") +#pragma comment(lib, "rpcrt4") +#pragma comment(lib, "shlwapi") +#pragma comment(lib, "comctl32") +#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") // this is required for loading correct comctl32 dll file + +//////////////////////////////// +//~ rjf: File Iterator Types + +typedef struct OS_W32_FileIter OS_W32_FileIter; +struct OS_W32_FileIter +{ + HANDLE handle; + WIN32_FIND_DATAW find_data; + B32 is_volume_iter; + String8Array drive_strings; + U64 drive_strings_iter_idx; +}; +StaticAssert(sizeof(Member(OS_FileIter, memory)) >= sizeof(OS_W32_FileIter), file_iter_memory_size); + +//////////////////////////////// +//~ rjf: Entity Types + +typedef enum OS_W32_EntityKind +{ + OS_W32_EntityKind_Null, + OS_W32_EntityKind_Thread, + OS_W32_EntityKind_Mutex, + OS_W32_EntityKind_RWMutex, + OS_W32_EntityKind_ConditionVariable, +} +OS_W32_EntityKind; + +typedef struct OS_W32_Entity OS_W32_Entity; +struct OS_W32_Entity +{ + OS_W32_Entity *next; + OS_W32_EntityKind kind; + union + { + struct + { + OS_ThreadFunctionType *func; + void *ptr; + HANDLE handle; + DWORD tid; + } thread; + CRITICAL_SECTION mutex; + SRWLOCK rw_mutex; + CONDITION_VARIABLE cv; + }; +}; + +//////////////////////////////// +//~ rjf: State + +typedef struct OS_W32_State OS_W32_State; +struct OS_W32_State +{ + Arena *arena; + + // rjf: info + OS_SystemInfo system_info; + OS_ProcessInfo process_info; + + // rjf: large pages + B32 large_pages_enabled; + + // rjf: entity storage + CRITICAL_SECTION entity_mutex; + Arena *entity_arena; + OS_W32_Entity *entity_free; +}; + +//////////////////////////////// +//~ rjf: Globals + +global OS_W32_State os_w32_state = {0}; + +//////////////////////////////// +//~ rjf: File Info Conversion Helpers + +internal FilePropertyFlags os_w32_file_property_flags_from_dwFileAttributes(DWORD dwFileAttributes); +internal void os_w32_file_properties_from_attribute_data(FileProperties *properties, WIN32_FILE_ATTRIBUTE_DATA *attributes); + +//////////////////////////////// +//~ rjf: Time Conversion Helpers + +internal void os_w32_date_time_from_system_time(DateTime *out, SYSTEMTIME *in); +internal void os_w32_system_time_from_date_time(SYSTEMTIME *out, DateTime *in); +internal void os_w32_dense_time_from_file_time(DenseTime *out, FILETIME *in); +internal U32 os_w32_sleep_ms_from_endt_us(U64 endt_us); + +//////////////////////////////// +//~ rjf: Entity Functions + +internal OS_W32_Entity *os_w32_entity_alloc(OS_W32_EntityKind kind); +internal void os_w32_entity_release(OS_W32_Entity *entity); + +//////////////////////////////// +//~ rjf: Thread Entry Point + +internal DWORD os_w32_thread_entry_point(void *ptr); + +#endif // OS_CORE_WIN32_H diff --git a/src/os/gfx/os_gfx.h b/src/os/gfx/os_gfx.h index 4bd72b99..92cc4816 100644 --- a/src/os/gfx/os_gfx.h +++ b/src/os/gfx/os_gfx.h @@ -1,182 +1,191 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef OS_GRAPHICAL_H -#define OS_GRAPHICAL_H - -//////////////////////////////// -//~ rjf: Window Types - -typedef U32 OS_WindowFlags; -enum -{ - OS_WindowFlag_CustomBorder = (1<<0), -}; - -typedef void OS_WindowRepaintFunctionType(OS_Handle window, void *user_data); - -//////////////////////////////// -//~ rjf: Cursor Types - -typedef enum OS_Cursor -{ - OS_Cursor_Pointer, - OS_Cursor_IBar, - OS_Cursor_LeftRight, - OS_Cursor_UpDown, - OS_Cursor_DownRight, - OS_Cursor_UpRight, - OS_Cursor_UpDownLeftRight, - OS_Cursor_HandPoint, - OS_Cursor_Disabled, - OS_Cursor_COUNT, -} -OS_Cursor; - -//////////////////////////////// -//~ rjf: Generated Code - -#include "os/gfx/generated/os_gfx.meta.h" - -//////////////////////////////// -//~ rjf: Event Types - -typedef enum OS_EventKind -{ - OS_EventKind_Null, - OS_EventKind_Press, - OS_EventKind_Release, - OS_EventKind_MouseMove, - OS_EventKind_Text, - OS_EventKind_Scroll, - OS_EventKind_WindowLoseFocus, - OS_EventKind_WindowClose, - OS_EventKind_FileDrop, - OS_EventKind_Wakeup, - OS_EventKind_COUNT -} -OS_EventKind; - -typedef U32 OS_EventFlags; -enum -{ - OS_EventFlag_Ctrl = (1<<0), - OS_EventFlag_Shift = (1<<1), - OS_EventFlag_Alt = (1<<2), -}; - -typedef struct OS_Event OS_Event; -struct OS_Event -{ - OS_Event *next; - OS_Event *prev; - U64 timestamp_us; - OS_Handle window; - OS_EventKind kind; - OS_EventFlags flags; - OS_Key key; - B32 is_repeat; - B32 right_sided; - U32 character; - U32 repeat_count; - Vec2F32 pos; - Vec2F32 delta; - String8List strings; -}; - -typedef struct OS_EventList OS_EventList; -struct OS_EventList -{ - U64 count; - OS_Event *first; - OS_Event *last; -}; - -//////////////////////////////// -//~ rjf: Event Functions (Helpers, Implemented Once) - -internal String8List os_string_list_from_event_flags(Arena *arena, OS_EventFlags flags); -internal U32 os_codepoint_from_event_flags_and_key(OS_EventFlags flags, OS_Key key); -internal void os_eat_event(OS_EventList *events, OS_Event *event); -internal B32 os_key_press(OS_EventList *events, OS_Handle window, OS_EventFlags flags, OS_Key key); -internal B32 os_key_release(OS_EventList *events, OS_Handle window, OS_EventFlags flags, OS_Key key); -internal B32 os_text(OS_EventList *events, OS_Handle window, U32 character); -internal OS_EventList os_event_list_copy(Arena *arena, OS_EventList *src); -internal void os_event_list_concat_in_place(OS_EventList *dst, OS_EventList *to_push); - -//////////////////////////////// -//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) - -internal void os_graphical_init(void); - -//////////////////////////////// -//~ rjf: @os_hooks Clipboards (Implemented Per-OS) - -internal void os_set_clipboard_text(String8 string); -internal String8 os_get_clipboard_text(Arena *arena); - -//////////////////////////////// -//~ rjf: @os_hooks Windows (Implemented Per-OS) - -internal OS_Handle os_window_open(Vec2F32 resolution, OS_WindowFlags flags, String8 title); -internal void os_window_close(OS_Handle window); -internal void os_window_first_paint(OS_Handle window); -internal void os_window_equip_repaint(OS_Handle window, OS_WindowRepaintFunctionType *repaint, void *user_data); -internal void os_window_focus(OS_Handle window); -internal B32 os_window_is_focused(OS_Handle window); -internal B32 os_window_is_fullscreen(OS_Handle window); -internal void os_window_set_fullscreen(OS_Handle window, B32 fullscreen); -internal B32 os_window_is_maximized(OS_Handle window); -internal void os_window_set_maximized(OS_Handle window, B32 maximized); -internal void os_window_minimize(OS_Handle window); -internal void os_window_bring_to_front(OS_Handle window); -internal void os_window_set_monitor(OS_Handle window, OS_Handle monitor); -internal void os_window_clear_custom_border_data(OS_Handle handle); -internal void os_window_push_custom_title_bar(OS_Handle handle, F32 thickness); -internal void os_window_push_custom_edges(OS_Handle handle, F32 thickness); -internal void os_window_push_custom_title_bar_client_area(OS_Handle handle, Rng2F32 rect); -internal Rng2F32 os_rect_from_window(OS_Handle window); -internal Rng2F32 os_client_rect_from_window(OS_Handle window); -internal F32 os_dpi_from_window(OS_Handle window); - -//////////////////////////////// -//~ rjf: @os_hooks Monitors (Implemented Per-OS) - -internal OS_HandleArray os_push_monitors_array(Arena *arena); -internal OS_Handle os_primary_monitor(void); -internal OS_Handle os_monitor_from_window(OS_Handle window); -internal String8 os_name_from_monitor(Arena *arena, OS_Handle monitor); -internal Vec2F32 os_dim_from_monitor(OS_Handle monitor); - -//////////////////////////////// -//~ rjf: @os_hooks Events (Implemented Per-OS) - -internal void os_send_wakeup_event(void); -internal OS_EventList os_get_events(Arena *arena, B32 wait); -internal OS_EventFlags os_get_event_flags(void); -internal B32 os_key_is_down(OS_Key key); -internal Vec2F32 os_mouse_from_window(OS_Handle window); - -//////////////////////////////// -//~ rjf: @os_hooks Cursors (Implemented Per-OS) - -internal void os_set_cursor(OS_Cursor cursor); - -//////////////////////////////// -//~ rjf: @os_hooks System Properties (Implemented Per-OS) - -internal F32 os_double_click_time(void); -internal F32 os_caret_blink_time(void); -internal F32 os_default_refresh_rate(void); - -//////////////////////////////// -//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) - -internal void os_graphical_message(B32 error, String8 title, String8 message); - -//////////////////////////////// -//~ rjf: @os_hooks Shell Operations - -internal void os_show_in_filesystem_ui(String8 path); - -#endif // OS_GRAPHICAL_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_GRAPHICAL_H +#define OS_GRAPHICAL_H + +//////////////////////////////// +//~ rjf: Graphics System Info + +typedef struct OS_GfxInfo OS_GfxInfo; +struct OS_GfxInfo +{ + F32 double_click_time; + F32 caret_blink_time; + F32 default_refresh_rate; +}; + +//////////////////////////////// +//~ rjf: Window Types + +typedef U32 OS_WindowFlags; +enum +{ + OS_WindowFlag_CustomBorder = (1<<0), +}; + +typedef void OS_WindowRepaintFunctionType(OS_Handle window, void *user_data); + +//////////////////////////////// +//~ rjf: Cursor Types + +typedef enum OS_Cursor +{ + OS_Cursor_Pointer, + OS_Cursor_IBar, + OS_Cursor_LeftRight, + OS_Cursor_UpDown, + OS_Cursor_DownRight, + OS_Cursor_UpRight, + OS_Cursor_UpDownLeftRight, + OS_Cursor_HandPoint, + OS_Cursor_Disabled, + OS_Cursor_COUNT, +} +OS_Cursor; + +//////////////////////////////// +//~ rjf: Generated Code + +#include "os/gfx/generated/os_gfx.meta.h" + +//////////////////////////////// +//~ rjf: Event Types + +typedef enum OS_EventKind +{ + OS_EventKind_Null, + OS_EventKind_Press, + OS_EventKind_Release, + OS_EventKind_MouseMove, + OS_EventKind_Text, + OS_EventKind_Scroll, + OS_EventKind_WindowLoseFocus, + OS_EventKind_WindowClose, + OS_EventKind_FileDrop, + OS_EventKind_Wakeup, + OS_EventKind_COUNT +} +OS_EventKind; + +typedef U32 OS_EventFlags; +enum +{ + OS_EventFlag_Ctrl = (1<<0), + OS_EventFlag_Shift = (1<<1), + OS_EventFlag_Alt = (1<<2), +}; + +typedef struct OS_Event OS_Event; +struct OS_Event +{ + OS_Event *next; + OS_Event *prev; + U64 timestamp_us; + OS_Handle window; + OS_EventKind kind; + OS_EventFlags flags; + OS_Key key; + B32 is_repeat; + B32 right_sided; + U32 character; + U32 repeat_count; + Vec2F32 pos; + Vec2F32 delta; + String8List strings; +}; + +typedef struct OS_EventList OS_EventList; +struct OS_EventList +{ + U64 count; + OS_Event *first; + OS_Event *last; +}; + +//////////////////////////////// +//~ rjf: Event Functions (Helpers, Implemented Once) + +internal String8List os_string_list_from_event_flags(Arena *arena, OS_EventFlags flags); +internal U32 os_codepoint_from_event_flags_and_key(OS_EventFlags flags, OS_Key key); +internal void os_eat_event(OS_EventList *events, OS_Event *event); +internal B32 os_key_press(OS_EventList *events, OS_Handle window, OS_EventFlags flags, OS_Key key); +internal B32 os_key_release(OS_EventList *events, OS_Handle window, OS_EventFlags flags, OS_Key key); +internal B32 os_text(OS_EventList *events, OS_Handle window, U32 character); +internal OS_EventList os_event_list_copy(Arena *arena, OS_EventList *src); +internal void os_event_list_concat_in_place(OS_EventList *dst, OS_EventList *to_push); + +//////////////////////////////// +//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) + +internal void os_gfx_init(void); + +//////////////////////////////// +//~ rjf: @os_hooks Graphics System Info (Implemented Per-OS) + +internal OS_GfxInfo *os_get_gfx_info(void); + +//////////////////////////////// +//~ rjf: @os_hooks Clipboards (Implemented Per-OS) + +internal void os_set_clipboard_text(String8 string); +internal String8 os_get_clipboard_text(Arena *arena); + +//////////////////////////////// +//~ rjf: @os_hooks Windows (Implemented Per-OS) + +internal OS_Handle os_window_open(Vec2F32 resolution, OS_WindowFlags flags, String8 title); +internal void os_window_close(OS_Handle window); +internal void os_window_first_paint(OS_Handle window); +internal void os_window_equip_repaint(OS_Handle window, OS_WindowRepaintFunctionType *repaint, void *user_data); +internal void os_window_focus(OS_Handle window); +internal B32 os_window_is_focused(OS_Handle window); +internal B32 os_window_is_fullscreen(OS_Handle window); +internal void os_window_set_fullscreen(OS_Handle window, B32 fullscreen); +internal B32 os_window_is_maximized(OS_Handle window); +internal void os_window_set_maximized(OS_Handle window, B32 maximized); +internal void os_window_minimize(OS_Handle window); +internal void os_window_bring_to_front(OS_Handle window); +internal void os_window_set_monitor(OS_Handle window, OS_Handle monitor); +internal void os_window_clear_custom_border_data(OS_Handle handle); +internal void os_window_push_custom_title_bar(OS_Handle handle, F32 thickness); +internal void os_window_push_custom_edges(OS_Handle handle, F32 thickness); +internal void os_window_push_custom_title_bar_client_area(OS_Handle handle, Rng2F32 rect); +internal Rng2F32 os_rect_from_window(OS_Handle window); +internal Rng2F32 os_client_rect_from_window(OS_Handle window); +internal F32 os_dpi_from_window(OS_Handle window); + +//////////////////////////////// +//~ rjf: @os_hooks Monitors (Implemented Per-OS) + +internal OS_HandleArray os_push_monitors_array(Arena *arena); +internal OS_Handle os_primary_monitor(void); +internal OS_Handle os_monitor_from_window(OS_Handle window); +internal String8 os_name_from_monitor(Arena *arena, OS_Handle monitor); +internal Vec2F32 os_dim_from_monitor(OS_Handle monitor); + +//////////////////////////////// +//~ rjf: @os_hooks Events (Implemented Per-OS) + +internal void os_send_wakeup_event(void); +internal OS_EventList os_get_events(Arena *arena, B32 wait); +internal OS_EventFlags os_get_event_flags(void); +internal B32 os_key_is_down(OS_Key key); +internal Vec2F32 os_mouse_from_window(OS_Handle window); + +//////////////////////////////// +//~ rjf: @os_hooks Cursors (Implemented Per-OS) + +internal void os_set_cursor(OS_Cursor cursor); + +//////////////////////////////// +//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) + +internal void os_graphical_message(B32 error, String8 title, String8 message); + +//////////////////////////////// +//~ rjf: @os_hooks Shell Operations + +internal void os_show_in_filesystem_ui(String8 path); + +#endif // OS_GRAPHICAL_H diff --git a/src/os/gfx/stub/os_gfx_stub.c b/src/os/gfx/stub/os_gfx_stub.c index 06e3a4fc..8adb429e 100644 --- a/src/os/gfx/stub/os_gfx_stub.c +++ b/src/os/gfx/stub/os_gfx_stub.c @@ -1,256 +1,229 @@ -//////////////////////////////// -//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) - -internal void -os_graphical_init(void) -{ -} - -//////////////////////////////// -//~ rjf: @os_hooks Clipboards (Implemented Per-OS) - -internal void -os_set_clipboard_text(String8 string) -{ -} - -internal String8 -os_get_clipboard_text(Arena *arena) -{ - return str8_zero(); -} - -//////////////////////////////// -//~ rjf: @os_hooks Windows (Implemented Per-OS) - -internal OS_Handle -os_window_open(Vec2F32 resolution, OS_WindowFlags flags, String8 title) -{ - OS_Handle handle = {1}; - return handle; -} - -internal void -os_window_close(OS_Handle window) -{ -} - -internal void -os_window_first_paint(OS_Handle window) -{ -} - -internal void -os_window_equip_repaint(OS_Handle window, OS_WindowRepaintFunctionType *repaint, void *user_data) -{ -} - -internal void -os_window_focus(OS_Handle window) -{ -} - -internal B32 -os_window_is_focused(OS_Handle window) -{ - return 0; -} - -internal B32 -os_window_is_fullscreen(OS_Handle window) -{ - return 0; -} - -internal void -os_window_set_fullscreen(OS_Handle window, B32 fullscreen) -{ -} - -internal B32 -os_window_is_maximized(OS_Handle window) -{ - return 0; -} - -internal void -os_window_set_maximized(OS_Handle window, B32 maximized) -{ -} - -internal void -os_window_minimize(OS_Handle window) -{ -} - -internal void -os_window_bring_to_front(OS_Handle window) -{ -} - -internal void -os_window_set_monitor(OS_Handle window, OS_Handle monitor) -{ -} - -internal void -os_window_clear_custom_border_data(OS_Handle handle) -{ -} - -internal void -os_window_push_custom_title_bar(OS_Handle handle, F32 thickness) -{ -} - -internal void -os_window_push_custom_edges(OS_Handle handle, F32 thickness) -{ -} - -internal void -os_window_push_custom_title_bar_client_area(OS_Handle handle, Rng2F32 rect) -{ -} - -internal Rng2F32 -os_rect_from_window(OS_Handle window) -{ - Rng2F32 rect = r2f32(v2f32(0, 0), v2f32(500, 500)); - return rect; -} - -internal Rng2F32 -os_client_rect_from_window(OS_Handle window) -{ - Rng2F32 rect = r2f32(v2f32(0, 0), v2f32(500, 500)); - return rect; -} - -internal F32 -os_dpi_from_window(OS_Handle window) -{ - return 96.f; -} - -//////////////////////////////// -//~ rjf: @os_hooks Monitors (Implemented Per-OS) - -internal OS_HandleArray -os_push_monitors_array(Arena *arena) -{ - OS_HandleArray arr = {0}; - return arr; -} - -internal OS_Handle -os_primary_monitor(void) -{ - OS_Handle handle = {1}; - return handle; -} - -internal OS_Handle -os_monitor_from_window(OS_Handle window) -{ - OS_Handle handle = {1}; - return handle; -} - -internal String8 -os_name_from_monitor(Arena *arena, OS_Handle monitor) -{ - return str8_zero(); -} - -internal Vec2F32 -os_dim_from_monitor(OS_Handle monitor) -{ - Vec2F32 v = v2f32(1000, 1000); - return v; -} - -//////////////////////////////// -//~ rjf: @os_hooks Events (Implemented Per-OS) - -internal void -os_send_wakeup_event(void) -{ -} - -internal OS_EventList -os_get_events(Arena *arena, B32 wait) -{ - OS_EventList evts = {0}; - return evts; -} - -internal OS_EventFlags -os_get_event_flags(void) -{ - OS_EventFlags f = 0; - return f; -} - -internal B32 -os_key_is_down(OS_Key key) -{ - return 0; -} - -internal Vec2F32 -os_mouse_from_window(OS_Handle window) -{ - return v2f32(0, 0); -} - -//////////////////////////////// -//~ rjf: @os_hooks Cursors (Implemented Per-OS) - -internal void -os_set_cursor(OS_Cursor cursor) -{ -} - -//////////////////////////////// -//~ rjf: @os_hooks System Properties (Implemented Per-OS) - -internal F32 -os_double_click_time(void) -{ - return 1.f; -} - -internal F32 -os_caret_blink_time(void) -{ - return 1.f; -} - -internal F32 -os_default_refresh_rate(void) -{ - return 60.f; -} - -internal B32 -os_granular_sleep_enabled(void) -{ - return 1; -} - -//////////////////////////////// -//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) - -internal void -os_graphical_message(B32 error, String8 title, String8 message) -{ -} - -//////////////////////////////// -//~ rjf: @os_hooks Shell Operations - -internal void -os_show_in_filesystem_ui(String8 path) -{ -} +//////////////////////////////// +//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) + +internal void +os_gfx_init(void) +{ +} + +//////////////////////////////// +//~ rjf: @os_hooks Clipboards (Implemented Per-OS) + +internal void +os_set_clipboard_text(String8 string) +{ +} + +internal String8 +os_get_clipboard_text(Arena *arena) +{ + return str8_zero(); +} + +//////////////////////////////// +//~ rjf: @os_hooks Windows (Implemented Per-OS) + +internal OS_Handle +os_window_open(Vec2F32 resolution, OS_WindowFlags flags, String8 title) +{ + OS_Handle handle = {1}; + return handle; +} + +internal void +os_window_close(OS_Handle window) +{ +} + +internal void +os_window_first_paint(OS_Handle window) +{ +} + +internal void +os_window_equip_repaint(OS_Handle window, OS_WindowRepaintFunctionType *repaint, void *user_data) +{ +} + +internal void +os_window_focus(OS_Handle window) +{ +} + +internal B32 +os_window_is_focused(OS_Handle window) +{ + return 0; +} + +internal B32 +os_window_is_fullscreen(OS_Handle window) +{ + return 0; +} + +internal void +os_window_set_fullscreen(OS_Handle window, B32 fullscreen) +{ +} + +internal B32 +os_window_is_maximized(OS_Handle window) +{ + return 0; +} + +internal void +os_window_set_maximized(OS_Handle window, B32 maximized) +{ +} + +internal void +os_window_minimize(OS_Handle window) +{ +} + +internal void +os_window_bring_to_front(OS_Handle window) +{ +} + +internal void +os_window_set_monitor(OS_Handle window, OS_Handle monitor) +{ +} + +internal void +os_window_clear_custom_border_data(OS_Handle handle) +{ +} + +internal void +os_window_push_custom_title_bar(OS_Handle handle, F32 thickness) +{ +} + +internal void +os_window_push_custom_edges(OS_Handle handle, F32 thickness) +{ +} + +internal void +os_window_push_custom_title_bar_client_area(OS_Handle handle, Rng2F32 rect) +{ +} + +internal Rng2F32 +os_rect_from_window(OS_Handle window) +{ + Rng2F32 rect = r2f32(v2f32(0, 0), v2f32(500, 500)); + return rect; +} + +internal Rng2F32 +os_client_rect_from_window(OS_Handle window) +{ + Rng2F32 rect = r2f32(v2f32(0, 0), v2f32(500, 500)); + return rect; +} + +internal F32 +os_dpi_from_window(OS_Handle window) +{ + return 96.f; +} + +//////////////////////////////// +//~ rjf: @os_hooks Monitors (Implemented Per-OS) + +internal OS_HandleArray +os_push_monitors_array(Arena *arena) +{ + OS_HandleArray arr = {0}; + return arr; +} + +internal OS_Handle +os_primary_monitor(void) +{ + OS_Handle handle = {1}; + return handle; +} + +internal OS_Handle +os_monitor_from_window(OS_Handle window) +{ + OS_Handle handle = {1}; + return handle; +} + +internal String8 +os_name_from_monitor(Arena *arena, OS_Handle monitor) +{ + return str8_zero(); +} + +internal Vec2F32 +os_dim_from_monitor(OS_Handle monitor) +{ + Vec2F32 v = v2f32(1000, 1000); + return v; +} + +//////////////////////////////// +//~ rjf: @os_hooks Events (Implemented Per-OS) + +internal void +os_send_wakeup_event(void) +{ +} + +internal OS_EventList +os_get_events(Arena *arena, B32 wait) +{ + OS_EventList evts = {0}; + return evts; +} + +internal OS_EventFlags +os_get_event_flags(void) +{ + OS_EventFlags f = 0; + return f; +} + +internal B32 +os_key_is_down(OS_Key key) +{ + return 0; +} + +internal Vec2F32 +os_mouse_from_window(OS_Handle window) +{ + return v2f32(0, 0); +} + +//////////////////////////////// +//~ rjf: @os_hooks Cursors (Implemented Per-OS) + +internal void +os_set_cursor(OS_Cursor cursor) +{ +} + +//////////////////////////////// +//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) + +internal void +os_graphical_message(B32 error, String8 title, String8 message) +{ +} + +//////////////////////////////// +//~ rjf: @os_hooks Shell Operations + +internal void +os_show_in_filesystem_ui(String8 path) +{ +} diff --git a/src/os/gfx/win32/os_gfx_win32.c b/src/os/gfx/win32/os_gfx_win32.c index 97442948..e3a665ee 100644 --- a/src/os/gfx/win32/os_gfx_win32.c +++ b/src/os/gfx/win32/os_gfx_win32.c @@ -1,1461 +1,1525 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Includes - -#include -#include -#include -#pragma comment(lib, "gdi32") -#pragma comment(lib, "dwmapi") -#pragma comment(lib, "UxTheme") -#pragma comment(lib, "ole32") - -//////////////////////////////// -//~ rjf: Globals - -global U32 w32_gfx_thread_tid = 0; -global HINSTANCE w32_h_instance = 0; -global W32_Window * w32_first_window = 0; -global W32_Window * w32_last_window = 0; -global W32_Window * w32_first_free_window = 0; -global OS_EventList w32_event_list = {0}; -global Arena * w32_event_arena = 0; -global HCURSOR w32_hcursor = 0; -global B32 w32_resizing = 0; -global F32 w32_default_refresh_rate = 60.f; - -//////////////////////////////// -//~ allen: Windows SDK Inconsistency Fixer - -typedef BOOL w32_SetProcessDpiAwarenessContext_Type(void* value); -typedef UINT w32_GetDpiForWindow_Type(HWND hwnd); -#define w32_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((void*)-4) - -global w32_GetDpiForWindow_Type *w32_GetDpiForWindow_func = 0; - -//////////////////////////////// -//~ rjf: Basic Helpers - -internal Rng2F32 -w32_base_rect_from_win32_rect(RECT rect) -{ - Rng2F32 r = {0}; - r.x0 = (F32)rect.left; - r.x1 = (F32)rect.right; - r.y0 = (F32)rect.top; - r.y1 = (F32)rect.bottom; - return r; -} - -//////////////////////////////// -//~ rjf: Windows - -internal OS_Handle -os_window_from_w32_window(W32_Window *window) -{ - OS_Handle handle = {(U64)window}; - return handle; -} - -internal W32_Window * -w32_window_from_os_window(OS_Handle handle) -{ - W32_Window *window = (W32_Window *)handle.u64[0]; - return window; -} - -internal W32_Window * -w32_window_from_hwnd(HWND hwnd) -{ - W32_Window *result = 0; - for(W32_Window *w = w32_first_window; w; w = w->next) - { - if(w->hwnd == hwnd) - { - result = w; - break; - } - } - return result; -} - -internal HWND -w32_hwnd_from_window(W32_Window *window) -{ - return window->hwnd; -} - -internal W32_Window * -w32_allocate_window(void) -{ - W32_Window *result = w32_first_free_window; - if(result == 0) - { - result = push_array(w32_perm_arena, W32_Window, 1); - } - else - { - w32_first_free_window = w32_first_free_window->next; - MemoryZeroStruct(result); - } - if(result) - { - DLLPushBack(w32_first_window, w32_last_window, result); - } - result->last_window_placement.length = sizeof(WINDOWPLACEMENT); - return result; -} - -internal void -w32_free_window(W32_Window *window) -{ - if(window->paint_arena != 0) - { - arena_release(window->paint_arena); - } - DestroyWindow(window->hwnd); - DLLRemove(w32_first_window, w32_last_window, window); - window->next = w32_first_free_window; - w32_first_free_window = window; -} - -internal OS_Event * -w32_push_event(OS_EventKind kind, W32_Window *window) -{ - OS_Event *result = push_array(w32_event_arena, OS_Event, 1); - DLLPushBack(w32_event_list.first, w32_event_list.last, result); - result->timestamp_us = os_now_microseconds(); - result->kind = kind; - result->window = os_window_from_w32_window(window); - result->flags = os_get_event_flags(); - w32_event_list.count += 1; - return(result); -} - -internal OS_Key -w32_os_key_from_vkey(WPARAM vkey) -{ - local_persist B32 first = 1; - local_persist OS_Key key_table[256]; - if (first){ - first = 0; - MemoryZeroArray(key_table); - - key_table[(unsigned int)'A'] = OS_Key_A; - key_table[(unsigned int)'B'] = OS_Key_B; - key_table[(unsigned int)'C'] = OS_Key_C; - key_table[(unsigned int)'D'] = OS_Key_D; - key_table[(unsigned int)'E'] = OS_Key_E; - key_table[(unsigned int)'F'] = OS_Key_F; - key_table[(unsigned int)'G'] = OS_Key_G; - key_table[(unsigned int)'H'] = OS_Key_H; - key_table[(unsigned int)'I'] = OS_Key_I; - key_table[(unsigned int)'J'] = OS_Key_J; - key_table[(unsigned int)'K'] = OS_Key_K; - key_table[(unsigned int)'L'] = OS_Key_L; - key_table[(unsigned int)'M'] = OS_Key_M; - key_table[(unsigned int)'N'] = OS_Key_N; - key_table[(unsigned int)'O'] = OS_Key_O; - key_table[(unsigned int)'P'] = OS_Key_P; - key_table[(unsigned int)'Q'] = OS_Key_Q; - key_table[(unsigned int)'R'] = OS_Key_R; - key_table[(unsigned int)'S'] = OS_Key_S; - key_table[(unsigned int)'T'] = OS_Key_T; - key_table[(unsigned int)'U'] = OS_Key_U; - key_table[(unsigned int)'V'] = OS_Key_V; - key_table[(unsigned int)'W'] = OS_Key_W; - key_table[(unsigned int)'X'] = OS_Key_X; - key_table[(unsigned int)'Y'] = OS_Key_Y; - key_table[(unsigned int)'Z'] = OS_Key_Z; - - for (U64 i = '0', j = OS_Key_0; i <= '9'; i += 1, j += 1){ - key_table[i] = (OS_Key)j; - } - for (U64 i = VK_NUMPAD0, j = OS_Key_0; i <= VK_NUMPAD9; i += 1, j += 1){ - key_table[i] = (OS_Key)j; - } - for (U64 i = VK_F1, j = OS_Key_F1; i <= VK_F24; i += 1, j += 1){ - key_table[i] = (OS_Key)j; - } - - key_table[VK_SPACE] = OS_Key_Space; - key_table[VK_OEM_3] = OS_Key_Tick; - key_table[VK_OEM_MINUS] = OS_Key_Minus; - key_table[VK_OEM_PLUS] = OS_Key_Equal; - key_table[VK_OEM_4] = OS_Key_LeftBracket; - key_table[VK_OEM_6] = OS_Key_RightBracket; - key_table[VK_OEM_1] = OS_Key_Semicolon; - key_table[VK_OEM_7] = OS_Key_Quote; - key_table[VK_OEM_COMMA] = OS_Key_Comma; - key_table[VK_OEM_PERIOD]= OS_Key_Period; - key_table[VK_OEM_2] = OS_Key_Slash; - key_table[VK_OEM_5] = OS_Key_BackSlash; - - key_table[VK_TAB] = OS_Key_Tab; - key_table[VK_PAUSE] = OS_Key_Pause; - key_table[VK_ESCAPE] = OS_Key_Esc; - - key_table[VK_UP] = OS_Key_Up; - key_table[VK_LEFT] = OS_Key_Left; - key_table[VK_DOWN] = OS_Key_Down; - key_table[VK_RIGHT] = OS_Key_Right; - - key_table[VK_BACK] = OS_Key_Backspace; - key_table[VK_RETURN] = OS_Key_Return; - - key_table[VK_DELETE] = OS_Key_Delete; - key_table[VK_INSERT] = OS_Key_Insert; - key_table[VK_PRIOR] = OS_Key_PageUp; - key_table[VK_NEXT] = OS_Key_PageDown; - key_table[VK_HOME] = OS_Key_Home; - key_table[VK_END] = OS_Key_End; - - key_table[VK_CAPITAL] = OS_Key_CapsLock; - key_table[VK_NUMLOCK] = OS_Key_NumLock; - key_table[VK_SCROLL] = OS_Key_ScrollLock; - key_table[VK_APPS] = OS_Key_Menu; - - key_table[VK_CONTROL] = OS_Key_Ctrl; - key_table[VK_LCONTROL] = OS_Key_Ctrl; - key_table[VK_RCONTROL] = OS_Key_Ctrl; - key_table[VK_SHIFT] = OS_Key_Shift; - key_table[VK_LSHIFT] = OS_Key_Shift; - key_table[VK_RSHIFT] = OS_Key_Shift; - key_table[VK_MENU] = OS_Key_Alt; - key_table[VK_LMENU] = OS_Key_Alt; - key_table[VK_RMENU] = OS_Key_Alt; - - key_table[VK_DIVIDE] = OS_Key_NumSlash; - key_table[VK_MULTIPLY] = OS_Key_NumStar; - key_table[VK_SUBTRACT] = OS_Key_NumMinus; - key_table[VK_ADD] = OS_Key_NumPlus; - key_table[VK_DECIMAL] = OS_Key_NumPeriod; - - for (U32 i = 0; i < 10; i += 1){ - key_table[VK_NUMPAD0 + i] = (OS_Key)((U64)OS_Key_Num0 + i); - } - - for (U64 i = 0xDF, j = 0; i < 0xFF; i += 1, j += 1){ - key_table[i] = (OS_Key)((U64)OS_Key_Ex0 + j); - } - } - - OS_Key key = key_table[vkey&bitmask8]; - return(key); -} - -internal WPARAM -w32_vkey_from_os_key(OS_Key key) -{ - WPARAM result = 0; - { - local_persist B32 initialized = 0; - local_persist WPARAM vkey_table[OS_Key_COUNT] = {0}; - if(initialized == 0) - { - initialized = 1; - vkey_table[OS_Key_Esc] = VK_ESCAPE; - for(OS_Key key = OS_Key_F1; key <= OS_Key_F24; key = (OS_Key)(key+1)) - { - vkey_table[key] = VK_F1+(key-OS_Key_F1); - } - vkey_table[OS_Key_Tick] = VK_OEM_3; - for(OS_Key key = OS_Key_0; key <= OS_Key_9; key = (OS_Key)(key+1)) - { - vkey_table[key] = '0'+(key-OS_Key_0); - } - vkey_table[OS_Key_Minus] = VK_OEM_MINUS; - vkey_table[OS_Key_Equal] = VK_OEM_PLUS; - vkey_table[OS_Key_Backspace] = VK_BACK; - vkey_table[OS_Key_Tab] = VK_TAB; - vkey_table[OS_Key_Q] = 'Q'; - vkey_table[OS_Key_W] = 'W'; - vkey_table[OS_Key_E] = 'E'; - vkey_table[OS_Key_R] = 'R'; - vkey_table[OS_Key_T] = 'T'; - vkey_table[OS_Key_Y] = 'Y'; - vkey_table[OS_Key_U] = 'U'; - vkey_table[OS_Key_I] = 'I'; - vkey_table[OS_Key_O] = 'O'; - vkey_table[OS_Key_P] = 'P'; - vkey_table[OS_Key_LeftBracket] = VK_OEM_4; - vkey_table[OS_Key_RightBracket] = VK_OEM_6; - vkey_table[OS_Key_BackSlash] = VK_OEM_5; - vkey_table[OS_Key_CapsLock] = VK_CAPITAL; - vkey_table[OS_Key_A] = 'A'; - vkey_table[OS_Key_S] = 'S'; - vkey_table[OS_Key_D] = 'D'; - vkey_table[OS_Key_F] = 'F'; - vkey_table[OS_Key_G] = 'G'; - vkey_table[OS_Key_H] = 'H'; - vkey_table[OS_Key_J] = 'J'; - vkey_table[OS_Key_K] = 'K'; - vkey_table[OS_Key_L] = 'L'; - vkey_table[OS_Key_Semicolon] = VK_OEM_1; - vkey_table[OS_Key_Quote] = VK_OEM_7; - vkey_table[OS_Key_Return] = VK_RETURN; - vkey_table[OS_Key_Shift] = VK_SHIFT; - vkey_table[OS_Key_Z] = 'Z'; - vkey_table[OS_Key_X] = 'X'; - vkey_table[OS_Key_C] = 'C'; - vkey_table[OS_Key_V] = 'V'; - vkey_table[OS_Key_B] = 'B'; - vkey_table[OS_Key_N] = 'N'; - vkey_table[OS_Key_M] = 'M'; - vkey_table[OS_Key_Comma] = VK_OEM_COMMA; - vkey_table[OS_Key_Period] = VK_OEM_PERIOD; - vkey_table[OS_Key_Slash] = VK_OEM_2; - vkey_table[OS_Key_Ctrl] = VK_CONTROL; - vkey_table[OS_Key_Alt] = VK_MENU; - vkey_table[OS_Key_Space] = VK_SPACE; - vkey_table[OS_Key_Menu] = VK_APPS; - vkey_table[OS_Key_ScrollLock] = VK_SCROLL; - vkey_table[OS_Key_Pause] = VK_PAUSE; - vkey_table[OS_Key_Insert] = VK_INSERT; - vkey_table[OS_Key_Home] = VK_HOME; - vkey_table[OS_Key_PageUp] = VK_PRIOR; - vkey_table[OS_Key_Delete] = VK_DELETE; - vkey_table[OS_Key_End] = VK_END; - vkey_table[OS_Key_PageDown] = VK_NEXT; - vkey_table[OS_Key_Up] = VK_UP; - vkey_table[OS_Key_Left] = VK_LEFT; - vkey_table[OS_Key_Down] = VK_DOWN; - vkey_table[OS_Key_Right] = VK_RIGHT; - for(OS_Key key = OS_Key_Ex0; key <= OS_Key_Ex29; key = (OS_Key)(key+1)) - { - vkey_table[key] = 0xDF + (key-OS_Key_Ex0); - } - vkey_table[OS_Key_NumLock] = VK_NUMLOCK; - vkey_table[OS_Key_NumSlash] = VK_DIVIDE; - vkey_table[OS_Key_NumStar] = VK_MULTIPLY; - vkey_table[OS_Key_NumMinus] = VK_SUBTRACT; - vkey_table[OS_Key_NumPlus] = VK_ADD; - vkey_table[OS_Key_NumPeriod] = VK_DECIMAL; - vkey_table[OS_Key_Num0] = VK_NUMPAD0; - vkey_table[OS_Key_Num1] = VK_NUMPAD1; - vkey_table[OS_Key_Num2] = VK_NUMPAD2; - vkey_table[OS_Key_Num3] = VK_NUMPAD3; - vkey_table[OS_Key_Num4] = VK_NUMPAD4; - vkey_table[OS_Key_Num5] = VK_NUMPAD5; - vkey_table[OS_Key_Num6] = VK_NUMPAD6; - vkey_table[OS_Key_Num7] = VK_NUMPAD7; - vkey_table[OS_Key_Num8] = VK_NUMPAD8; - vkey_table[OS_Key_Num9] = VK_NUMPAD9; - } - result = vkey_table[key]; - } - return result; -} - -internal LRESULT -w32_wnd_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - ProfBeginFunction(); - LRESULT result = 0; - - B32 good = 1; - if(w32_event_arena == 0) - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - good = 0; - } - - if(good) - { - W32_Window *window = w32_window_from_hwnd(hwnd); - OS_Handle window_handle = os_window_from_w32_window(window); - B32 release = 0; - - switch(uMsg) - { - default: - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - }break; - - case WM_ENTERSIZEMOVE: - { - w32_resizing = 1; - }break; - - case WM_EXITSIZEMOVE: - { - w32_resizing = 0; - }break; - - case WM_SIZE: - case WM_PAINT: - { - if(window->repaint != 0) - { - PAINTSTRUCT ps = {0}; - BeginPaint(hwnd, &ps); - window->repaint(os_window_from_w32_window(window), window->repaint_user_data); - EndPaint(hwnd, &ps); - } - else - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - }break; - - case WM_CLOSE: - { - w32_push_event(OS_EventKind_WindowClose, window); - }break; - - case WM_LBUTTONUP: - case WM_MBUTTONUP: - case WM_RBUTTONUP: - { - release = 1; - } // fallthrough; - case WM_LBUTTONDOWN: - case WM_MBUTTONDOWN: - case WM_RBUTTONDOWN: - { - OS_Event *event = w32_push_event(release ? OS_EventKind_Release : OS_EventKind_Press, window); - switch (uMsg) - { - case WM_LBUTTONUP: case WM_LBUTTONDOWN: - { - event->key = OS_Key_LeftMouseButton; - }break; - case WM_MBUTTONUP: case WM_MBUTTONDOWN: - { - event->key = OS_Key_MiddleMouseButton; - }break; - case WM_RBUTTONUP: case WM_RBUTTONDOWN: - { - event->key = OS_Key_RightMouseButton; - }break; - } - event->pos.x = (F32)(S16)LOWORD(lParam); - event->pos.y = (F32)(S16)HIWORD(lParam); - if(release) - { - ReleaseCapture(); - } - else - { - SetCapture(hwnd); - } - }break; - - case WM_MOUSEMOVE: - { - OS_Event *event = w32_push_event(OS_EventKind_MouseMove, window); - event->pos.x = (F32)(S16)LOWORD(lParam); - event->pos.y = (F32)(S16)HIWORD(lParam); - }break; - - case WM_MOUSEWHEEL: - { - S16 wheel_delta = HIWORD(wParam); - OS_Event *event = w32_push_event(OS_EventKind_Scroll, window); - POINT p; - p.x = (S32)(S16)LOWORD(lParam); - p.y = (S32)(S16)HIWORD(lParam); - ScreenToClient(window->hwnd, &p); - event->pos.x = (F32)p.x; - event->pos.y = (F32)p.y; - event->delta = v2f32(0.f, -(F32)wheel_delta); - }break; - - case WM_MOUSEHWHEEL: - { - S16 wheel_delta = HIWORD(wParam); - OS_Event *event = w32_push_event(OS_EventKind_Scroll, window); - POINT p; - p.x = (S32)(S16)LOWORD(lParam); - p.y = (S32)(S16)HIWORD(lParam); - ScreenToClient(window->hwnd, &p); - event->pos.x = (F32)p.x; - event->pos.y = (F32)p.y; - event->delta = v2f32((F32)wheel_delta, 0.f); - }break; - - case WM_SYSKEYDOWN: case WM_SYSKEYUP: - { - if(wParam != VK_MENU && (wParam < VK_F1 || VK_F24 < wParam || wParam == VK_F4)) - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - } // fallthrough; - case WM_KEYDOWN: case WM_KEYUP: - { - B32 was_down = (lParam & bit31); - B32 is_down = !(lParam & bit32); - - B32 is_repeat = 0; - if(!is_down) - { - release = 1; - } - else if(was_down) - { - is_repeat = 1; - } - - B32 right_sided = 0; - if ((lParam & bit25) && - (wParam == VK_CONTROL || wParam == VK_RCONTROL || - wParam == VK_MENU || wParam == VK_RMENU || - wParam == VK_SHIFT || wParam == VK_RSHIFT)) - { - right_sided = 1; - } - - OS_Event *event = w32_push_event(release ? OS_EventKind_Release : OS_EventKind_Press, window); - event->key = w32_os_key_from_vkey(wParam); - event->repeat_count = lParam & bitmask16; - event->is_repeat = is_repeat; - event->right_sided = right_sided; - if(event->key == OS_Key_Alt && event->flags & OS_EventFlag_Alt) { event->flags &= ~OS_EventFlag_Alt; } - if(event->key == OS_Key_Ctrl && event->flags & OS_EventFlag_Ctrl) { event->flags &= ~OS_EventFlag_Ctrl; } - if(event->key == OS_Key_Shift && event->flags & OS_EventFlag_Shift) { event->flags &= ~OS_EventFlag_Shift; } - }break; - - case WM_SYSCHAR: - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - }break; - - case WM_CHAR: - { - U32 character = wParam; - if(character >= 32 && character != 127) - { - OS_Event *event = w32_push_event(OS_EventKind_Text, window); - if(lParam & bit29) - { - event->flags |= OS_EventFlag_Alt; - } - event->character = character; - } - }break; - - case WM_KILLFOCUS: - { - w32_push_event(OS_EventKind_WindowLoseFocus, window); - ReleaseCapture(); - }break; - - case WM_SETCURSOR: - { - Rng2F32 window_rect = os_client_rect_from_window(window_handle); - Vec2F32 mouse = os_mouse_from_window(window_handle); - B32 on_border = 0; - DWORD window_style = window ? GetWindowLong(window->hwnd, GWL_STYLE) : 0; - B32 is_fullscreen = !(window_style & WS_OVERLAPPEDWINDOW); - if(window != 0 && window->custom_border && !is_fullscreen) - { - B32 on_border_x = (mouse.x <= window->custom_border_edge_thickness || window_rect.x1-window->custom_border_edge_thickness <= mouse.x); - B32 on_border_y = (mouse.y <= window->custom_border_edge_thickness || window_rect.y1-window->custom_border_edge_thickness <= mouse.y); - on_border = on_border_x || on_border_y; - } - if(!w32_resizing && !on_border && - contains_2f32(window_rect, mouse)) - { - SetCursor(w32_hcursor); - } - else - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - }break; - - case WM_DPICHANGED: - { - F32 new_dpi = (F32)(wParam & 0xffff); - window->dpi = new_dpi; - }break; - - //- rjf: [custom border] - case WM_NCPAINT: - { - if(window != 0 && window->custom_border && !window->custom_border_composition_enabled) - { - result = 0; - } - else - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - }break; - case WM_DWMCOMPOSITIONCHANGED: - { - if(window != 0 && window->custom_border) - { - BOOL enabled = 0; - DwmIsCompositionEnabled(&enabled); - window->custom_border_composition_enabled = enabled; - if(enabled) - { - MARGINS m = { 0, 0, 1, 0 }; - DwmExtendFrameIntoClientArea(hwnd, &m); - DWORD dwmncrp_enabled = DWMNCRP_ENABLED; - DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &enabled, sizeof(dwmncrp_enabled)); - } - } - else - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - }break; - case WM_WINDOWPOSCHANGED: - { - result = 0; - }break; - case WM_NCUAHDRAWCAPTION: - case WM_NCUAHDRAWFRAME: - { - // NOTE(rjf): undocumented messages for drawing themed window borders. - if(window != 0 && window->custom_border) - { - result = 0; - } - else - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - }break; - case WM_SETICON: - case WM_SETTEXT: - { - if(window && window->custom_border && !window->custom_border_composition_enabled) - { - // NOTE(rjf): - // https://blogs.msdn.microsoft.com/wpfsdk/2008/09/08/custom-window-chrome-in-wpf/ - LONG_PTR old_style = GetWindowLongPtrW(hwnd, GWL_STYLE); - SetWindowLongPtrW(hwnd, GWL_STYLE, old_style & ~WS_VISIBLE); - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - SetWindowLongPtrW(hwnd, GWL_STYLE, old_style); - } - else - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - }break; - - //- rjf: [custom border] activation - without this `result`, stuff flickers. - case WM_NCACTIVATE: - { - if(window == 0 || window->custom_border == 0) - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - else - { - result = DefWindowProcW(hwnd, uMsg, wParam, -1); - } - }break; - - //- rjf: [custom border] client/window size calculation - case WM_NCCALCSIZE: - if(window != 0) - { - if(window->custom_border == 0) - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - else - { - MARGINS m = {0, 0, 0, 0}; - RECT *r = (RECT *)lParam; - DWORD window_style = window ? GetWindowLong(window->hwnd, GWL_STYLE) : 0; - B32 is_fullscreen = !(window_style & WS_OVERLAPPEDWINDOW); - if(IsZoomed(hwnd) && !is_fullscreen) - { - int x_push_in = GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); - int y_push_in = GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); - r->left += x_push_in; - r->top += y_push_in; - r->bottom -= x_push_in; - r->right -= y_push_in; - m.cxLeftWidth = m.cxRightWidth = x_push_in; - m.cyTopHeight = m.cyBottomHeight = y_push_in; - } - DwmExtendFrameIntoClientArea(hwnd, &m); - } - }break; - - //- rjf: [custom border] client/window hit testing (mapping mouse -> action) - case WM_NCHITTEST: - { - DWORD window_style = window ? GetWindowLong(window->hwnd, GWL_STYLE) : 0; - B32 is_fullscreen = !(window_style & WS_OVERLAPPEDWINDOW); - if(window == 0 || window->custom_border == 0 || is_fullscreen) - { - result = DefWindowProcW(hwnd, uMsg, wParam, lParam); - } - else - { - POINT pos_monitor; - pos_monitor.x = GET_X_LPARAM(lParam); - pos_monitor.y = GET_Y_LPARAM(lParam); - POINT pos_client = pos_monitor; - ScreenToClient(hwnd, &pos_client); - - //- rjf: check against window boundaries - RECT frame_rect; - GetWindowRect(hwnd, &frame_rect); - B32 is_over_window = (frame_rect.left <= pos_monitor.x && pos_monitor.x < frame_rect.right && - frame_rect.top <= pos_monitor.y && pos_monitor.y < frame_rect.bottom); - - //- rjf: check against borders - B32 is_over_left = 0; - B32 is_over_right = 0; - B32 is_over_top = 0; - B32 is_over_bottom = 0; - { - RECT rect; - GetClientRect(hwnd, &rect); - if(!IsZoomed(hwnd)) - { - if(rect.left <= pos_client.x && pos_client.x < rect.left + window->custom_border_edge_thickness) - { - is_over_left = 1; - } - if(rect.right - window->custom_border_edge_thickness <= pos_client.x && pos_client.x < rect.right) - { - is_over_right = 1; - } - if(rect.bottom - window->custom_border_edge_thickness <= pos_client.y && pos_client.y < rect.bottom) - { - is_over_bottom = 1; - } - if(rect.top <= pos_client.y && pos_client.y < rect.top + window->custom_border_edge_thickness) - { - is_over_top = 1; - } - } - } - - //- rjf: check against title bar - B32 is_over_title_bar = 0; - { - RECT rect; - GetClientRect(hwnd, &rect); - is_over_title_bar = (rect.left <= pos_client.x && pos_client.x < rect.right && - rect.top <= pos_client.y && pos_client.y < rect.top + window->custom_border_title_thickness); - } - - //- rjf: check against title bar client areas - B32 is_over_title_bar_client_area = 0; - for(W32_TitleBarClientArea *area = window->first_title_bar_client_area; - area != 0; - area = area->next) - { - Rng2F32 rect = area->rect; - if(rect.x0 <= pos_client.x && pos_client.x < rect.x1 && - rect.y0 <= pos_client.y && pos_client.y < rect.y1) - { - is_over_title_bar_client_area = 1; - break; - } - } - - //- rjf: resolve hovering to result - result = HTNOWHERE; - if(is_over_window) - { - // rjf: default to client area - result = HTCLIENT; - - // rjf: title bar - if(is_over_title_bar) - { - result = HTCAPTION; - } - - // rjf: normal edges - if(is_over_left) { result = HTLEFT; } - if(is_over_right) { result = HTRIGHT; } - if(is_over_top) { result = HTTOP; } - if(is_over_bottom) { result = HTBOTTOM; } - - // rjf: corners - if(is_over_left && is_over_top) { result = HTTOPLEFT; } - if(is_over_left && is_over_bottom) { result = HTBOTTOMLEFT; } - if(is_over_right && is_over_top) { result = HTTOPRIGHT; } - if(is_over_right && is_over_bottom) { result = HTBOTTOMRIGHT; } - - // rjf: title bar client area - if(is_over_title_bar_client_area) - { - result = HTCLIENT; - } - } - } - }break; - } - } - - ProfEnd(); - return result; -} - -//////////////////////////////// -//~ rjf: Monitors - -internal BOOL -w32_monitor_gather_enum_proc(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM bundle_ptr) -{ - W32_MonitorGatherBundle *bundle = (W32_MonitorGatherBundle *)bundle_ptr; - OS_Handle handle = {(U64)monitor}; - os_handle_list_push(bundle->arena, bundle->list, handle); - return 1; -} - -//////////////////////////////// -//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) - -internal void -os_graphical_init(void) -{ - //- rjf: grab TID of thread which is doing graphics - w32_gfx_thread_tid = (U32)GetCurrentThreadId(); - - //- rjf: grab hinstance - w32_h_instance = GetModuleHandle(0); - - //- rjf: set dpi awareness - w32_SetProcessDpiAwarenessContext_Type *SetProcessDpiAwarenessContext_func = 0; - HMODULE module = LoadLibraryA("user32.dll"); - if(module != 0) - { - SetProcessDpiAwarenessContext_func = - (w32_SetProcessDpiAwarenessContext_Type*)GetProcAddress(module, "SetProcessDpiAwarenessContext"); - w32_GetDpiForWindow_func = - (w32_GetDpiForWindow_Type*)GetProcAddress(module, "GetDpiForWindow"); - FreeLibrary(module); - } - if(SetProcessDpiAwarenessContext_func != 0) - { - SetProcessDpiAwarenessContext_func(w32_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); - } - - //- rjf: register graphical-window class - { - WNDCLASSEXW wndclass = {sizeof(wndclass)}; - wndclass.lpfnWndProc = w32_wnd_proc; - wndclass.hInstance = w32_h_instance; - wndclass.lpszClassName = L"graphical-window"; - wndclass.hCursor = LoadCursorA(0, IDC_ARROW); - wndclass.hIcon = LoadIcon(w32_h_instance, MAKEINTRESOURCE(1)); - wndclass.style = CS_VREDRAW|CS_HREDRAW; - ATOM wndatom = RegisterClassExW(&wndclass); - (void)wndatom; - } - - //- rjf: grab refresh rate - { - DEVMODEW devmodew = {0}; - if(EnumDisplaySettingsW(0, ENUM_CURRENT_SETTINGS, &devmodew)) - { - w32_default_refresh_rate = (F32)devmodew.dmDisplayFrequency; - } - } - - //- rjf: set initial cursor - os_set_cursor(OS_Cursor_Pointer); -} - -//////////////////////////////// -//~ rjf: @os_hooks Clipboards (Implemented Per-OS) - -internal void -os_set_clipboard_text(String8 string) -{ - if(OpenClipboard(0)) - { - EmptyClipboard(); - HANDLE string_copy_handle = GlobalAlloc(GMEM_MOVEABLE, string.size+1); - if(string_copy_handle) - { - U8 *copy_buffer = (U8 *)GlobalLock(string_copy_handle); - MemoryCopy(copy_buffer, string.str, string.size); - copy_buffer[string.size] = 0; - GlobalUnlock(string_copy_handle); - SetClipboardData(CF_TEXT, string_copy_handle); - } - CloseClipboard(); - } -} - -internal String8 -os_get_clipboard_text(Arena *arena) -{ - String8 result = {0}; - if(IsClipboardFormatAvailable(CF_TEXT) && - OpenClipboard(0)) - { - HANDLE data_handle = GetClipboardData(CF_TEXT); - if(data_handle) - { - U8 *buffer = (U8 *)GlobalLock(data_handle); - if(buffer) - { - U64 size = cstring8_length(buffer); - result = push_str8_copy(arena, str8(buffer, size)); - GlobalUnlock(data_handle); - } - } - CloseClipboard(); - } - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Windows (Implemented Per-OS) - -internal OS_Handle -os_window_open(Vec2F32 resolution, OS_WindowFlags flags, String8 title) -{ - //- rjf: make hwnd - HWND hwnd = 0; - { - Temp scratch = scratch_begin(0, 0); - String16 title16 = str16_from_8(scratch.arena, title); - hwnd = CreateWindowExW(WS_EX_APPWINDOW, - L"graphical-window", - (WCHAR*)title16.str, - WS_OVERLAPPEDWINDOW | WS_SIZEBOX, - CW_USEDEFAULT, CW_USEDEFAULT, - (int)resolution.x, - (int)resolution.y, - 0, 0, - w32_h_instance, - 0); - scratch_end(scratch); - } - - //- rjf- make/fill window - W32_Window *window = w32_allocate_window(); - { - window->hwnd = hwnd; - if (w32_GetDpiForWindow_func != 0){ - window->dpi = (F32)w32_GetDpiForWindow_func(hwnd); - } - else{ - window->dpi = 96.f; - } - } - - //- rjf: early detection of composition - { - BOOL enabled = 0; - DwmIsCompositionEnabled(&enabled); - window->custom_border_composition_enabled = enabled; - } - - //- rjf: custom border - if(flags & OS_WindowFlag_CustomBorder) - { - window->custom_border = 1; - window->paint_arena = arena_alloc(); - } - - //- rjf: convert to handle + return - OS_Handle result = os_window_from_w32_window(window); - return result; -} - -internal void -os_window_close(OS_Handle handle) -{ - W32_Window *window = w32_window_from_os_window(handle); - w32_free_window(window); -} - -internal void -os_window_first_paint(OS_Handle window_handle) -{ - W32_Window *window = w32_window_from_os_window(window_handle); - window->first_paint_done = 1; - ShowWindow(window->hwnd, SW_SHOW); - if(window->maximized) - { - ShowWindow(window->hwnd, SW_MAXIMIZE); - } -} - -internal void -os_window_equip_repaint(OS_Handle handle, OS_WindowRepaintFunctionType *repaint, void *user_data) -{ - W32_Window *window = w32_window_from_os_window(handle); - window->repaint = repaint; - window->repaint_user_data = user_data; -} - -internal void -os_window_focus(OS_Handle handle) -{ - W32_Window *window = w32_window_from_os_window(handle); - SetForegroundWindow(window->hwnd); - SetFocus(window->hwnd); -} - -internal B32 -os_window_is_focused(OS_Handle handle) -{ - W32_Window *window = w32_window_from_os_window(handle); - HWND active_hwnd = GetActiveWindow(); - return active_hwnd == window->hwnd; -} - -internal B32 -os_window_is_fullscreen(OS_Handle handle) -{ - W32_Window *window = w32_window_from_os_window(handle); - DWORD window_style = GetWindowLong(window->hwnd, GWL_STYLE); - return !(window_style & WS_OVERLAPPEDWINDOW); -} - -internal void -os_window_set_fullscreen(OS_Handle handle, B32 fullscreen) -{ - W32_Window *window = w32_window_from_os_window(handle); - OS_WindowRepaintFunctionType *repaint = window->repaint; - window->repaint = 0; - DWORD window_style = GetWindowLong(window->hwnd, GWL_STYLE); - B32 is_fullscreen_already = os_window_is_fullscreen(handle); - if(fullscreen) - { - if(!is_fullscreen_already) - { - GetWindowPlacement(window->hwnd, &window->last_window_placement); - } - MONITORINFO monitor_info = {sizeof(monitor_info)}; - if(GetMonitorInfo(MonitorFromWindow(window->hwnd, MONITOR_DEFAULTTOPRIMARY), &monitor_info)) - { - SetWindowLong(window->hwnd, GWL_STYLE, window_style & ~WS_OVERLAPPEDWINDOW); - SetWindowPos(window->hwnd, HWND_TOP, - monitor_info.rcMonitor.left, - monitor_info.rcMonitor.top, - monitor_info.rcMonitor.right - monitor_info.rcMonitor.left, - monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top, - SWP_NOOWNERZORDER | SWP_FRAMECHANGED); - } - } - else - { - SetWindowLong(window->hwnd, GWL_STYLE, window_style | WS_OVERLAPPEDWINDOW); - SetWindowPlacement(window->hwnd, &window->last_window_placement); - SetWindowPos(window->hwnd, 0, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | - SWP_NOOWNERZORDER | SWP_FRAMECHANGED); - } - window->repaint = repaint; -} - -internal B32 -os_window_is_maximized(OS_Handle handle) -{ - B32 result = 0; - W32_Window *window = w32_window_from_os_window(handle); - if(window) - { - result = !!(IsZoomed(window->hwnd)); - } - return result; -} - -internal void -os_window_set_maximized(OS_Handle handle, B32 maximized) -{ - W32_Window *window = w32_window_from_os_window(handle); - if(window != 0) - { - if(window->first_paint_done) - { - switch(maximized) - { - default: - case 0:{ShowWindow(window->hwnd, SW_RESTORE);}break; - case 1:{ShowWindow(window->hwnd, SW_MAXIMIZE);}break; - } - } - else - { - window->maximized = maximized; - } - } -} - -internal void -os_window_minimize(OS_Handle handle) -{ - W32_Window *window = w32_window_from_os_window(handle); - if(window != 0) - { - ShowWindow(window->hwnd, SW_MINIMIZE); - } -} - -internal void -os_window_bring_to_front(OS_Handle handle) -{ - W32_Window *window = w32_window_from_os_window(handle); - if(window != 0) - { - BringWindowToTop(window->hwnd); - } -} - -internal void -os_window_set_monitor(OS_Handle window_handle, OS_Handle monitor) -{ - W32_Window *window = w32_window_from_os_window(window_handle); - HMONITOR hmonitor = (HMONITOR)monitor.u64[0]; - { - MONITORINFOEXW info; - info.cbSize = sizeof(MONITORINFOEXW); - if(GetMonitorInfoW(hmonitor, (MONITORINFO *)&info)) - { - Rng2F32 existing_rect = os_rect_from_window(window_handle); - Vec2F32 window_size = dim_2f32(existing_rect); - SetWindowPos(window->hwnd, HWND_TOP, - (info.rcWork.left + info.rcWork.right)/2 - window_size.x/2, - (info.rcWork.top + info.rcWork.bottom)/2 - window_size.y/2, - window_size.x, - window_size.y, 0); - } - } -} - -internal void -os_window_clear_custom_border_data(OS_Handle handle) -{ - W32_Window *window = w32_window_from_os_window(handle); - if(window->custom_border) - { - arena_clear(window->paint_arena); - window->first_title_bar_client_area = window->last_title_bar_client_area = 0; - window->custom_border_title_thickness = 0; - window->custom_border_edge_thickness = 0; - } -} - -internal void -os_window_push_custom_title_bar(OS_Handle handle, F32 thickness) -{ - W32_Window *window = w32_window_from_os_window(handle); - window->custom_border_title_thickness = thickness; -} - -internal void -os_window_push_custom_edges(OS_Handle handle, F32 thickness) -{ - W32_Window *window = w32_window_from_os_window(handle); - window->custom_border_edge_thickness = thickness; -} - -internal void -os_window_push_custom_title_bar_client_area(OS_Handle handle, Rng2F32 rect) -{ - W32_Window *window = w32_window_from_os_window(handle); - if(window->custom_border) - { - W32_TitleBarClientArea *area = push_array(window->paint_arena, W32_TitleBarClientArea, 1); - if(area != 0) - { - area->rect = rect; - SLLQueuePush(window->first_title_bar_client_area, window->last_title_bar_client_area, area); - } - } -} - -internal Rng2F32 -os_rect_from_window(OS_Handle handle) -{ - Rng2F32 r = {0}; - W32_Window *window = w32_window_from_os_window(handle); - if(window) - { - RECT rect = {0}; - GetWindowRect(w32_hwnd_from_window(window), &rect); - r = w32_base_rect_from_win32_rect(rect); - } - return r; -} - -internal Rng2F32 -os_client_rect_from_window(OS_Handle handle) -{ - Rng2F32 r = {0}; - W32_Window *window = w32_window_from_os_window(handle); - if(window) - { - RECT rect = {0}; - GetClientRect(w32_hwnd_from_window(window), &rect); - r = w32_base_rect_from_win32_rect(rect); - } - return r; -} - -internal F32 -os_dpi_from_window(OS_Handle handle) -{ - F32 result = 96.f; - W32_Window *window = w32_window_from_os_window(handle); - if(window != 0) - { - result = window->dpi; - } - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Monitors (Implemented Per-OS) - -internal OS_HandleArray -os_push_monitors_array(Arena *arena) -{ - Temp scratch = scratch_begin(&arena, 1); - OS_HandleList list = {0}; - { - W32_MonitorGatherBundle bundle = {arena, &list}; - EnumDisplayMonitors(0, 0, w32_monitor_gather_enum_proc, (LPARAM)&bundle); - } - OS_HandleArray array = os_handle_array_from_list(arena, &list); - scratch_end(scratch); - return array; -} - -internal OS_Handle -os_primary_monitor(void) -{ - POINT zero_pt = {0, 0}; - HMONITOR monitor = MonitorFromPoint(zero_pt, MONITOR_DEFAULTTOPRIMARY); - OS_Handle result = {(U64)monitor}; - return result; -} - -internal OS_Handle -os_monitor_from_window(OS_Handle window) -{ - W32_Window *w = w32_window_from_os_window(window); - HMONITOR handle = MonitorFromWindow(w->hwnd, MONITOR_DEFAULTTOPRIMARY); - OS_Handle result = {(U64)handle}; - return result; -} - -internal String8 -os_name_from_monitor(Arena *arena, OS_Handle monitor) -{ - String8 result = {0}; - HMONITOR monitor_handle = (HMONITOR)monitor.u64[0]; - MONITORINFOEXW info; - info.cbSize = sizeof(MONITORINFOEXW); - if(GetMonitorInfoW(monitor_handle, (MONITORINFO *)&info)) - { - String16 result16 = str16_cstring((U16 *)info.szDevice); - result = str8_from_16(arena, result16); - } - return result; -} - -internal Vec2F32 -os_dim_from_monitor(OS_Handle monitor) -{ - Vec2F32 result = {0}; - HMONITOR monitor_handle = (HMONITOR)monitor.u64[0]; - MONITORINFO info = {0}; - info.cbSize = sizeof(MONITORINFO); - if(GetMonitorInfoW(monitor_handle, &info)) - { - result.x = info.rcWork.right - info.rcWork.left; - result.y = info.rcWork.bottom - info.rcWork.top; - } - return result; -} - -//////////////////////////////// -//~ rjf: @os_hooks Events (Implemented Per-OS) - -internal void -os_send_wakeup_event(void) -{ - PostThreadMessageA(w32_gfx_thread_tid, 0x401, 0, 0); -} - -internal OS_EventList -os_get_events(Arena *arena, B32 wait) -{ - w32_event_arena = arena; - MemoryZeroStruct(&w32_event_list); - MSG msg = {0}; - if(!wait || GetMessage(&msg, 0, 0, 0)) - { - B32 first_wait = wait; - for(;first_wait || PeekMessage(&msg, 0, 0, 0, PM_REMOVE); first_wait = 0) - { - DispatchMessage(&msg); - TranslateMessage(&msg); - if(msg.message == WM_QUIT) - { - w32_push_event(OS_EventKind_WindowClose, 0); - } - } - } - return w32_event_list; -} - -internal OS_EventFlags -os_get_event_flags(void) -{ - OS_EventFlags flags = 0; - if(GetKeyState(VK_CONTROL) & 0x8000) - { - flags |= OS_EventFlag_Ctrl; - } - if(GetKeyState(VK_SHIFT) & 0x8000) - { - flags |= OS_EventFlag_Shift; - } - if(GetKeyState(VK_MENU) & 0x8000) - { - flags |= OS_EventFlag_Alt; - } - return(flags); -} - -internal B32 -os_key_is_down(OS_Key key) -{ - B32 result = 0; - { - WPARAM vkey_code = w32_vkey_from_os_key(key); - SHORT state = GetAsyncKeyState(vkey_code); - result = !!(state & (0x8000)); - } - return result; -} - -internal Vec2F32 -os_mouse_from_window(OS_Handle handle) -{ - ProfBeginFunction(); - Vec2F32 v = {0}; - POINT p; - if(GetCursorPos(&p)) - { - W32_Window *window = w32_window_from_os_window(handle); - ScreenToClient(window->hwnd, &p); - v.x = (F32)p.x; - v.y = (F32)p.y; - } - ProfEnd(); - return v; -} - -//////////////////////////////// -//~ rjf: @os_hooks Cursors (Implemented Per-OS) - -internal void -os_set_cursor(OS_Cursor cursor) -{ - B32 valid_cursor = 1; - - HCURSOR hcursor = 0; - switch(cursor) - { - default: {valid_cursor = 0;}break; -#define Win32CursorXList(X) \ -X(Pointer, IDC_ARROW) \ -X(IBar, IDC_IBEAM) \ -X(LeftRight, IDC_SIZEWE) \ -X(UpDown, IDC_SIZENS) \ -X(DownRight, IDC_SIZENWSE) \ -X(UpRight, IDC_SIZENESW) \ -X(UpDownLeftRight, IDC_SIZEALL) \ -X(HandPoint, IDC_HAND)\ -X(Disabled, IDC_NO) -#define CursorCase(E,R) case OS_Cursor_##E:{ \ -local_persist HCURSOR curs = 0; \ -if (curs == 0){ curs = LoadCursor(NULL, R); } \ -hcursor = curs; }break; - Win32CursorXList(CursorCase) -#undef CursorCase -#undef Win32CursorXList - } - - if(valid_cursor && !w32_resizing) - { - if(hcursor != w32_hcursor) - { - PostMessage(0, WM_SETCURSOR, 0, 0); - POINT p = {0}; - GetCursorPos(&p); - SetCursorPos(p.x, p.y); - } - w32_hcursor = hcursor; - } -} - -//////////////////////////////// -//~ rjf: @os_hooks System Properties (Implemented Per-OS) - -internal F32 -os_double_click_time(void) -{ - UINT time_milliseconds = GetDoubleClickTime(); - return time_milliseconds / 1000.f; -} - -internal F32 -os_caret_blink_time(void) -{ - UINT time_milliseconds = GetCaretBlinkTime(); - return time_milliseconds / 1000.f; -} - -internal F32 -os_default_refresh_rate(void) -{ - return w32_default_refresh_rate; -} - -//////////////////////////////// -//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) - -internal void -os_graphical_message(B32 error, String8 title, String8 message) -{ - Temp scratch = scratch_begin(0, 0); - String16 title16 = str16_from_8(scratch.arena, title); - String16 message16 = str16_from_8(scratch.arena, message); - MessageBoxW(0, (WCHAR *)message16.str, (WCHAR *)title16.str, MB_OK|(!!error*MB_ICONERROR)); - scratch_end(scratch); -} - -//////////////////////////////// -//~ rjf: @os_hooks Shell Operations - -internal void -os_show_in_filesystem_ui(String8 path) -{ - Temp scratch = scratch_begin(0, 0); - String8 path_copy = push_str8_copy(scratch.arena, path); - for(U64 idx = 0; idx < path_copy.size; idx += 1) - { - if(path_copy.str[idx] == '/') - { - path_copy.str[idx] = '\\'; - } - } - String16 path16 = str16_from_8(scratch.arena, path_copy); - SFGAOF flags = 0; - PIDLIST_ABSOLUTE list = 0; - if(path16.size != 0 && SUCCEEDED(SHParseDisplayName(path16.str, 0, &list, 0, &flags))) - { - HRESULT hr = SHOpenFolderAndSelectItems(list, 0, 0, 0); - CoTaskMemFree(list); - (void)hr; - } - scratch_end(scratch); -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Modern Windows SDK Functions +// +// (We must dynamically link to them, since they can be missing in older SDKs) + +typedef BOOL w32_SetProcessDpiAwarenessContext_Type(void* value); +typedef UINT w32_GetDpiForWindow_Type(HWND hwnd); +#define w32_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((void*)-4) +global w32_GetDpiForWindow_Type *w32_GetDpiForWindow_func = 0; + +//////////////////////////////// +//~ rjf: Basic Helpers + +internal Rng2F32 +os_w32_rng2f32_from_rect(RECT rect) +{ + Rng2F32 r = {0}; + r.x0 = (F32)rect.left; + r.x1 = (F32)rect.right; + r.y0 = (F32)rect.top; + r.y1 = (F32)rect.bottom; + return r; +} + +//////////////////////////////// +//~ rjf: Windows + +internal OS_Handle +os_w32_handle_from_window(OS_W32_Window *window) +{ + OS_Handle handle = {(U64)window}; + return handle; +} + +internal OS_W32_Window * +os_w32_window_from_handle(OS_Handle handle) +{ + OS_W32_Window *window = (OS_W32_Window *)handle.u64[0]; + return window; +} + +internal OS_W32_Window * +os_w32_window_from_hwnd(HWND hwnd) +{ + OS_W32_Window *result = 0; + for(OS_W32_Window *w = os_w32_gfx_state->first_window; w; w = w->next) + { + if(w->hwnd == hwnd) + { + result = w; + break; + } + } + return result; +} + +internal HWND +os_w32_hwnd_from_window(OS_W32_Window *window) +{ + return window->hwnd; +} + +internal OS_W32_Window * +os_w32_window_alloc(void) +{ + OS_W32_Window *result = os_w32_gfx_state->free_window; + if(result) + { + SLLStackPop(os_w32_gfx_state->free_window); + } + else + { + result = push_array_no_zero(os_w32_gfx_state->arena, OS_W32_Window, 1); + } + MemoryZeroStruct(result); + if(result) + { + DLLPushBack(os_w32_gfx_state->first_window, os_w32_gfx_state->last_window, result); + } + result->last_window_placement.length = sizeof(WINDOWPLACEMENT); + return result; +} + +internal void +os_w32_window_release(OS_W32_Window *window) +{ + if(window->paint_arena != 0) + { + arena_release(window->paint_arena); + } + DestroyWindow(window->hwnd); + DLLRemove(os_w32_gfx_state->first_window, os_w32_gfx_state->last_window, window); + SLLStackPush(os_w32_gfx_state->free_window, window); +} + +internal OS_Event * +os_w32_push_event(OS_EventKind kind, OS_W32_Window *window) +{ + OS_Event *result = push_array(os_w32_event_arena, OS_Event, 1); + DLLPushBack(os_w32_event_list.first, os_w32_event_list.last, result); + result->timestamp_us = os_now_microseconds(); + result->kind = kind; + result->window = os_w32_handle_from_window(window); + result->flags = os_get_event_flags(); + os_w32_event_list.count += 1; + return result; +} + +internal OS_Key +os_w32_os_key_from_vkey(WPARAM vkey) +{ + local_persist B32 first = 1; + local_persist OS_Key key_table[256]; + if (first){ + first = 0; + MemoryZeroArray(key_table); + + key_table[(unsigned int)'A'] = OS_Key_A; + key_table[(unsigned int)'B'] = OS_Key_B; + key_table[(unsigned int)'C'] = OS_Key_C; + key_table[(unsigned int)'D'] = OS_Key_D; + key_table[(unsigned int)'E'] = OS_Key_E; + key_table[(unsigned int)'F'] = OS_Key_F; + key_table[(unsigned int)'G'] = OS_Key_G; + key_table[(unsigned int)'H'] = OS_Key_H; + key_table[(unsigned int)'I'] = OS_Key_I; + key_table[(unsigned int)'J'] = OS_Key_J; + key_table[(unsigned int)'K'] = OS_Key_K; + key_table[(unsigned int)'L'] = OS_Key_L; + key_table[(unsigned int)'M'] = OS_Key_M; + key_table[(unsigned int)'N'] = OS_Key_N; + key_table[(unsigned int)'O'] = OS_Key_O; + key_table[(unsigned int)'P'] = OS_Key_P; + key_table[(unsigned int)'Q'] = OS_Key_Q; + key_table[(unsigned int)'R'] = OS_Key_R; + key_table[(unsigned int)'S'] = OS_Key_S; + key_table[(unsigned int)'T'] = OS_Key_T; + key_table[(unsigned int)'U'] = OS_Key_U; + key_table[(unsigned int)'V'] = OS_Key_V; + key_table[(unsigned int)'W'] = OS_Key_W; + key_table[(unsigned int)'X'] = OS_Key_X; + key_table[(unsigned int)'Y'] = OS_Key_Y; + key_table[(unsigned int)'Z'] = OS_Key_Z; + + for (U64 i = '0', j = OS_Key_0; i <= '9'; i += 1, j += 1){ + key_table[i] = (OS_Key)j; + } + for (U64 i = VK_NUMPAD0, j = OS_Key_0; i <= VK_NUMPAD9; i += 1, j += 1){ + key_table[i] = (OS_Key)j; + } + for (U64 i = VK_F1, j = OS_Key_F1; i <= VK_F24; i += 1, j += 1){ + key_table[i] = (OS_Key)j; + } + + key_table[VK_SPACE] = OS_Key_Space; + key_table[VK_OEM_3] = OS_Key_Tick; + key_table[VK_OEM_MINUS] = OS_Key_Minus; + key_table[VK_OEM_PLUS] = OS_Key_Equal; + key_table[VK_OEM_4] = OS_Key_LeftBracket; + key_table[VK_OEM_6] = OS_Key_RightBracket; + key_table[VK_OEM_1] = OS_Key_Semicolon; + key_table[VK_OEM_7] = OS_Key_Quote; + key_table[VK_OEM_COMMA] = OS_Key_Comma; + key_table[VK_OEM_PERIOD]= OS_Key_Period; + key_table[VK_OEM_2] = OS_Key_Slash; + key_table[VK_OEM_5] = OS_Key_BackSlash; + + key_table[VK_TAB] = OS_Key_Tab; + key_table[VK_PAUSE] = OS_Key_Pause; + key_table[VK_ESCAPE] = OS_Key_Esc; + + key_table[VK_UP] = OS_Key_Up; + key_table[VK_LEFT] = OS_Key_Left; + key_table[VK_DOWN] = OS_Key_Down; + key_table[VK_RIGHT] = OS_Key_Right; + + key_table[VK_BACK] = OS_Key_Backspace; + key_table[VK_RETURN] = OS_Key_Return; + + key_table[VK_DELETE] = OS_Key_Delete; + key_table[VK_INSERT] = OS_Key_Insert; + key_table[VK_PRIOR] = OS_Key_PageUp; + key_table[VK_NEXT] = OS_Key_PageDown; + key_table[VK_HOME] = OS_Key_Home; + key_table[VK_END] = OS_Key_End; + + key_table[VK_CAPITAL] = OS_Key_CapsLock; + key_table[VK_NUMLOCK] = OS_Key_NumLock; + key_table[VK_SCROLL] = OS_Key_ScrollLock; + key_table[VK_APPS] = OS_Key_Menu; + + key_table[VK_CONTROL] = OS_Key_Ctrl; + key_table[VK_LCONTROL] = OS_Key_Ctrl; + key_table[VK_RCONTROL] = OS_Key_Ctrl; + key_table[VK_SHIFT] = OS_Key_Shift; + key_table[VK_LSHIFT] = OS_Key_Shift; + key_table[VK_RSHIFT] = OS_Key_Shift; + key_table[VK_MENU] = OS_Key_Alt; + key_table[VK_LMENU] = OS_Key_Alt; + key_table[VK_RMENU] = OS_Key_Alt; + + key_table[VK_DIVIDE] = OS_Key_NumSlash; + key_table[VK_MULTIPLY] = OS_Key_NumStar; + key_table[VK_SUBTRACT] = OS_Key_NumMinus; + key_table[VK_ADD] = OS_Key_NumPlus; + key_table[VK_DECIMAL] = OS_Key_NumPeriod; + + for (U32 i = 0; i < 10; i += 1){ + key_table[VK_NUMPAD0 + i] = (OS_Key)((U64)OS_Key_Num0 + i); + } + + for (U64 i = 0xDF, j = 0; i < 0xFF; i += 1, j += 1){ + key_table[i] = (OS_Key)((U64)OS_Key_Ex0 + j); + } + } + + OS_Key key = key_table[vkey&bitmask8]; + return key; +} + +internal WPARAM +os_w32_vkey_from_os_key(OS_Key key) +{ + WPARAM result = 0; + { + local_persist B32 initialized = 0; + local_persist WPARAM vkey_table[OS_Key_COUNT] = {0}; + if(initialized == 0) + { + initialized = 1; + vkey_table[OS_Key_Esc] = VK_ESCAPE; + for(OS_Key key = OS_Key_F1; key <= OS_Key_F24; key = (OS_Key)(key+1)) + { + vkey_table[key] = VK_F1+(key-OS_Key_F1); + } + vkey_table[OS_Key_Tick] = VK_OEM_3; + for(OS_Key key = OS_Key_0; key <= OS_Key_9; key = (OS_Key)(key+1)) + { + vkey_table[key] = '0'+(key-OS_Key_0); + } + vkey_table[OS_Key_Minus] = VK_OEM_MINUS; + vkey_table[OS_Key_Equal] = VK_OEM_PLUS; + vkey_table[OS_Key_Backspace] = VK_BACK; + vkey_table[OS_Key_Tab] = VK_TAB; + vkey_table[OS_Key_Q] = 'Q'; + vkey_table[OS_Key_W] = 'W'; + vkey_table[OS_Key_E] = 'E'; + vkey_table[OS_Key_R] = 'R'; + vkey_table[OS_Key_T] = 'T'; + vkey_table[OS_Key_Y] = 'Y'; + vkey_table[OS_Key_U] = 'U'; + vkey_table[OS_Key_I] = 'I'; + vkey_table[OS_Key_O] = 'O'; + vkey_table[OS_Key_P] = 'P'; + vkey_table[OS_Key_LeftBracket] = VK_OEM_4; + vkey_table[OS_Key_RightBracket] = VK_OEM_6; + vkey_table[OS_Key_BackSlash] = VK_OEM_5; + vkey_table[OS_Key_CapsLock] = VK_CAPITAL; + vkey_table[OS_Key_A] = 'A'; + vkey_table[OS_Key_S] = 'S'; + vkey_table[OS_Key_D] = 'D'; + vkey_table[OS_Key_F] = 'F'; + vkey_table[OS_Key_G] = 'G'; + vkey_table[OS_Key_H] = 'H'; + vkey_table[OS_Key_J] = 'J'; + vkey_table[OS_Key_K] = 'K'; + vkey_table[OS_Key_L] = 'L'; + vkey_table[OS_Key_Semicolon] = VK_OEM_1; + vkey_table[OS_Key_Quote] = VK_OEM_7; + vkey_table[OS_Key_Return] = VK_RETURN; + vkey_table[OS_Key_Shift] = VK_SHIFT; + vkey_table[OS_Key_Z] = 'Z'; + vkey_table[OS_Key_X] = 'X'; + vkey_table[OS_Key_C] = 'C'; + vkey_table[OS_Key_V] = 'V'; + vkey_table[OS_Key_B] = 'B'; + vkey_table[OS_Key_N] = 'N'; + vkey_table[OS_Key_M] = 'M'; + vkey_table[OS_Key_Comma] = VK_OEM_COMMA; + vkey_table[OS_Key_Period] = VK_OEM_PERIOD; + vkey_table[OS_Key_Slash] = VK_OEM_2; + vkey_table[OS_Key_Ctrl] = VK_CONTROL; + vkey_table[OS_Key_Alt] = VK_MENU; + vkey_table[OS_Key_Space] = VK_SPACE; + vkey_table[OS_Key_Menu] = VK_APPS; + vkey_table[OS_Key_ScrollLock] = VK_SCROLL; + vkey_table[OS_Key_Pause] = VK_PAUSE; + vkey_table[OS_Key_Insert] = VK_INSERT; + vkey_table[OS_Key_Home] = VK_HOME; + vkey_table[OS_Key_PageUp] = VK_PRIOR; + vkey_table[OS_Key_Delete] = VK_DELETE; + vkey_table[OS_Key_End] = VK_END; + vkey_table[OS_Key_PageDown] = VK_NEXT; + vkey_table[OS_Key_Up] = VK_UP; + vkey_table[OS_Key_Left] = VK_LEFT; + vkey_table[OS_Key_Down] = VK_DOWN; + vkey_table[OS_Key_Right] = VK_RIGHT; + for(OS_Key key = OS_Key_Ex0; key <= OS_Key_Ex29; key = (OS_Key)(key+1)) + { + vkey_table[key] = 0xDF + (key-OS_Key_Ex0); + } + vkey_table[OS_Key_NumLock] = VK_NUMLOCK; + vkey_table[OS_Key_NumSlash] = VK_DIVIDE; + vkey_table[OS_Key_NumStar] = VK_MULTIPLY; + vkey_table[OS_Key_NumMinus] = VK_SUBTRACT; + vkey_table[OS_Key_NumPlus] = VK_ADD; + vkey_table[OS_Key_NumPeriod] = VK_DECIMAL; + vkey_table[OS_Key_Num0] = VK_NUMPAD0; + vkey_table[OS_Key_Num1] = VK_NUMPAD1; + vkey_table[OS_Key_Num2] = VK_NUMPAD2; + vkey_table[OS_Key_Num3] = VK_NUMPAD3; + vkey_table[OS_Key_Num4] = VK_NUMPAD4; + vkey_table[OS_Key_Num5] = VK_NUMPAD5; + vkey_table[OS_Key_Num6] = VK_NUMPAD6; + vkey_table[OS_Key_Num7] = VK_NUMPAD7; + vkey_table[OS_Key_Num8] = VK_NUMPAD8; + vkey_table[OS_Key_Num9] = VK_NUMPAD9; + } + result = vkey_table[key]; + } + return result; +} + +internal LRESULT +os_w32_wnd_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + ProfBeginFunction(); + LRESULT result = 0; + B32 good = 1; + if(os_w32_event_arena == 0) + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + good = 0; + } + if(good) + { + OS_W32_Window *window = os_w32_window_from_hwnd(hwnd); + OS_Handle window_handle = os_w32_handle_from_window(window); + B32 release = 0; + + switch(uMsg) + { + default: + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + }break; + + case WM_ENTERSIZEMOVE: + { + os_w32_resizing = 1; + }break; + + case WM_EXITSIZEMOVE: + { + os_w32_resizing = 0; + }break; + + case WM_SIZE: + case WM_PAINT: + { + if(window->repaint != 0) + { + PAINTSTRUCT ps = {0}; + BeginPaint(hwnd, &ps); + window->repaint(os_w32_handle_from_window(window), window->repaint_user_data); + EndPaint(hwnd, &ps); + } + else + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + }break; + + case WM_CLOSE: + { + os_w32_push_event(OS_EventKind_WindowClose, window); + }break; + + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + { + release = 1; + } // fallthrough; + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + { + OS_Event *event = os_w32_push_event(release ? OS_EventKind_Release : OS_EventKind_Press, window); + switch (uMsg) + { + case WM_LBUTTONUP: case WM_LBUTTONDOWN: + { + event->key = OS_Key_LeftMouseButton; + }break; + case WM_MBUTTONUP: case WM_MBUTTONDOWN: + { + event->key = OS_Key_MiddleMouseButton; + }break; + case WM_RBUTTONUP: case WM_RBUTTONDOWN: + { + event->key = OS_Key_RightMouseButton; + }break; + } + event->pos.x = (F32)(S16)LOWORD(lParam); + event->pos.y = (F32)(S16)HIWORD(lParam); + if(release) + { + ReleaseCapture(); + } + else + { + SetCapture(hwnd); + } + }break; + + case WM_MOUSEMOVE: + { + OS_Event *event = os_w32_push_event(OS_EventKind_MouseMove, window); + event->pos.x = (F32)(S16)LOWORD(lParam); + event->pos.y = (F32)(S16)HIWORD(lParam); + }break; + + case WM_MOUSEWHEEL: + { + S16 wheel_delta = HIWORD(wParam); + OS_Event *event = os_w32_push_event(OS_EventKind_Scroll, window); + POINT p; + p.x = (S32)(S16)LOWORD(lParam); + p.y = (S32)(S16)HIWORD(lParam); + ScreenToClient(window->hwnd, &p); + event->pos.x = (F32)p.x; + event->pos.y = (F32)p.y; + event->delta = v2f32(0.f, -(F32)wheel_delta); + }break; + + case WM_MOUSEHWHEEL: + { + S16 wheel_delta = HIWORD(wParam); + OS_Event *event = os_w32_push_event(OS_EventKind_Scroll, window); + POINT p; + p.x = (S32)(S16)LOWORD(lParam); + p.y = (S32)(S16)HIWORD(lParam); + ScreenToClient(window->hwnd, &p); + event->pos.x = (F32)p.x; + event->pos.y = (F32)p.y; + event->delta = v2f32((F32)wheel_delta, 0.f); + }break; + + case WM_SYSKEYDOWN: case WM_SYSKEYUP: + { + if(wParam != VK_MENU && (wParam < VK_F1 || VK_F24 < wParam || wParam == VK_F4)) + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + } // fallthrough; + case WM_KEYDOWN: case WM_KEYUP: + { + B32 was_down = (lParam & bit31); + B32 is_down = !(lParam & bit32); + + B32 is_repeat = 0; + if(!is_down) + { + release = 1; + } + else if(was_down) + { + is_repeat = 1; + } + + B32 right_sided = 0; + if ((lParam & bit25) && + (wParam == VK_CONTROL || wParam == VK_RCONTROL || + wParam == VK_MENU || wParam == VK_RMENU || + wParam == VK_SHIFT || wParam == VK_RSHIFT)) + { + right_sided = 1; + } + + OS_Event *event = os_w32_push_event(release ? OS_EventKind_Release : OS_EventKind_Press, window); + event->key = os_w32_os_key_from_vkey(wParam); + event->repeat_count = lParam & bitmask16; + event->is_repeat = is_repeat; + event->right_sided = right_sided; + if(event->key == OS_Key_Alt && event->flags & OS_EventFlag_Alt) { event->flags &= ~OS_EventFlag_Alt; } + if(event->key == OS_Key_Ctrl && event->flags & OS_EventFlag_Ctrl) { event->flags &= ~OS_EventFlag_Ctrl; } + if(event->key == OS_Key_Shift && event->flags & OS_EventFlag_Shift) { event->flags &= ~OS_EventFlag_Shift; } + }break; + + case WM_SYSCHAR: + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + }break; + + case WM_CHAR: + { + U32 character = wParam; + if(character >= 32 && character != 127) + { + OS_Event *event = os_w32_push_event(OS_EventKind_Text, window); + if(lParam & bit29) + { + event->flags |= OS_EventFlag_Alt; + } + event->character = character; + } + }break; + + case WM_KILLFOCUS: + { + os_w32_push_event(OS_EventKind_WindowLoseFocus, window); + ReleaseCapture(); + }break; + + case WM_SETCURSOR: + { + Rng2F32 window_rect = os_client_rect_from_window(window_handle); + Vec2F32 mouse = os_mouse_from_window(window_handle); + B32 on_border = 0; + DWORD window_style = window ? GetWindowLong(window->hwnd, GWL_STYLE) : 0; + B32 is_fullscreen = !(window_style & WS_OVERLAPPEDWINDOW); + if(window != 0 && window->custom_border && !is_fullscreen) + { + B32 on_border_x = (mouse.x <= window->custom_border_edge_thickness || window_rect.x1-window->custom_border_edge_thickness <= mouse.x); + B32 on_border_y = (mouse.y <= window->custom_border_edge_thickness || window_rect.y1-window->custom_border_edge_thickness <= mouse.y); + on_border = on_border_x || on_border_y; + } + if(!os_w32_resizing && !on_border && contains_2f32(window_rect, mouse)) + { + SetCursor(os_w32_gfx_state->hCursor); + } + else + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + }break; + + case WM_DPICHANGED: + { + F32 new_dpi = (F32)(wParam & 0xffff); + window->dpi = new_dpi; + }break; + + //- rjf: [custom border] + case WM_NCPAINT: + { + if(window != 0 && window->custom_border && !window->custom_border_composition_enabled) + { + result = 0; + } + else + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + }break; + case WM_DWMCOMPOSITIONCHANGED: + { + if(window != 0 && window->custom_border) + { + BOOL enabled = 0; + DwmIsCompositionEnabled(&enabled); + window->custom_border_composition_enabled = enabled; + if(enabled) + { + MARGINS m = { 0, 0, 1, 0 }; + DwmExtendFrameIntoClientArea(hwnd, &m); + DWORD dwmncrp_enabled = DWMNCRP_ENABLED; + DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &enabled, sizeof(dwmncrp_enabled)); + } + } + else + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + }break; + case WM_WINDOWPOSCHANGED: + { + result = 0; + }break; + case WM_NCUAHDRAWCAPTION: + case WM_NCUAHDRAWFRAME: + { + // NOTE(rjf): undocumented messages for drawing themed window borders. + if(window != 0 && window->custom_border) + { + result = 0; + } + else + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + }break; + case WM_SETICON: + case WM_SETTEXT: + { + if(window && window->custom_border && !window->custom_border_composition_enabled) + { + // NOTE(rjf): + // https://blogs.msdn.microsoft.com/wpfsdk/2008/09/08/custom-window-chrome-in-wpf/ + LONG_PTR old_style = GetWindowLongPtrW(hwnd, GWL_STYLE); + SetWindowLongPtrW(hwnd, GWL_STYLE, old_style & ~WS_VISIBLE); + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + SetWindowLongPtrW(hwnd, GWL_STYLE, old_style); + } + else + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + }break; + + //- rjf: [custom border] activation - without this `result`, stuff flickers. + case WM_NCACTIVATE: + { + if(window == 0 || window->custom_border == 0) + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + else + { + result = DefWindowProcW(hwnd, uMsg, wParam, -1); + } + }break; + + //- rjf: [custom border] client/window size calculation + case WM_NCCALCSIZE: + if(window != 0) + { + if(window->custom_border == 0) + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + else + { + MARGINS m = {0, 0, 0, 0}; + RECT *r = (RECT *)lParam; + DWORD window_style = window ? GetWindowLong(window->hwnd, GWL_STYLE) : 0; + B32 is_fullscreen = !(window_style & WS_OVERLAPPEDWINDOW); + if(IsZoomed(hwnd) && !is_fullscreen) + { + int x_push_in = GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); + int y_push_in = GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER); + r->left += x_push_in; + r->top += y_push_in; + r->bottom -= x_push_in; + r->right -= y_push_in; + m.cxLeftWidth = m.cxRightWidth = x_push_in; + m.cyTopHeight = m.cyBottomHeight = y_push_in; + } + DwmExtendFrameIntoClientArea(hwnd, &m); + } + }break; + + //- rjf: [custom border] client/window hit testing (mapping mouse -> action) + case WM_NCHITTEST: + { + DWORD window_style = window ? GetWindowLong(window->hwnd, GWL_STYLE) : 0; + B32 is_fullscreen = !(window_style & WS_OVERLAPPEDWINDOW); + if(window == 0 || window->custom_border == 0 || is_fullscreen) + { + result = DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + else + { + POINT pos_monitor; + pos_monitor.x = GET_X_LPARAM(lParam); + pos_monitor.y = GET_Y_LPARAM(lParam); + POINT pos_client = pos_monitor; + ScreenToClient(hwnd, &pos_client); + + //- rjf: check against window boundaries + RECT frame_rect; + GetWindowRect(hwnd, &frame_rect); + B32 is_over_window = (frame_rect.left <= pos_monitor.x && pos_monitor.x < frame_rect.right && + frame_rect.top <= pos_monitor.y && pos_monitor.y < frame_rect.bottom); + + //- rjf: check against borders + B32 is_over_left = 0; + B32 is_over_right = 0; + B32 is_over_top = 0; + B32 is_over_bottom = 0; + { + RECT rect; + GetClientRect(hwnd, &rect); + if(!IsZoomed(hwnd)) + { + if(rect.left <= pos_client.x && pos_client.x < rect.left + window->custom_border_edge_thickness) + { + is_over_left = 1; + } + if(rect.right - window->custom_border_edge_thickness <= pos_client.x && pos_client.x < rect.right) + { + is_over_right = 1; + } + if(rect.bottom - window->custom_border_edge_thickness <= pos_client.y && pos_client.y < rect.bottom) + { + is_over_bottom = 1; + } + if(rect.top <= pos_client.y && pos_client.y < rect.top + window->custom_border_edge_thickness) + { + is_over_top = 1; + } + } + } + + //- rjf: check against title bar + B32 is_over_title_bar = 0; + { + RECT rect; + GetClientRect(hwnd, &rect); + is_over_title_bar = (rect.left <= pos_client.x && pos_client.x < rect.right && + rect.top <= pos_client.y && pos_client.y < rect.top + window->custom_border_title_thickness); + } + + //- rjf: check against title bar client areas + B32 is_over_title_bar_client_area = 0; + for(OS_W32_TitleBarClientArea *area = window->first_title_bar_client_area; + area != 0; + area = area->next) + { + Rng2F32 rect = area->rect; + if(rect.x0 <= pos_client.x && pos_client.x < rect.x1 && + rect.y0 <= pos_client.y && pos_client.y < rect.y1) + { + is_over_title_bar_client_area = 1; + break; + } + } + + //- rjf: resolve hovering to result + result = HTNOWHERE; + if(is_over_window) + { + // rjf: default to client area + result = HTCLIENT; + + // rjf: title bar + if(is_over_title_bar) + { + result = HTCAPTION; + } + + // rjf: normal edges + if(is_over_left) { result = HTLEFT; } + if(is_over_right) { result = HTRIGHT; } + if(is_over_top) { result = HTTOP; } + if(is_over_bottom) { result = HTBOTTOM; } + + // rjf: corners + if(is_over_left && is_over_top) { result = HTTOPLEFT; } + if(is_over_left && is_over_bottom) { result = HTBOTTOMLEFT; } + if(is_over_right && is_over_top) { result = HTTOPRIGHT; } + if(is_over_right && is_over_bottom) { result = HTBOTTOMRIGHT; } + + // rjf: title bar client area + if(is_over_title_bar_client_area) + { + result = HTCLIENT; + } + } + } + }break; + } + } + ProfEnd(); + return result; +} + +//////////////////////////////// +//~ rjf: Monitors + +internal BOOL +os_w32_monitor_gather_enum_proc(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM bundle_ptr) +{ + OS_W32_MonitorGatherBundle *bundle = (OS_W32_MonitorGatherBundle *)bundle_ptr; + OS_Handle handle = {(U64)monitor}; + os_handle_list_push(bundle->arena, bundle->list, handle); + return 1; +} + +//////////////////////////////// +//~ rjf: @os_hooks Main Initialization API (Implemented Per-OS) + +internal void +os_gfx_init(void) +{ + //- rjf: set up base shared state + Arena *arena = arena_alloc(); + os_w32_gfx_state = push_array(arena, OS_W32_GfxState, 1); + os_w32_gfx_state->arena = arena; + os_w32_gfx_state->gfx_thread_tid = (U32)GetCurrentThreadId(); + os_w32_gfx_state->hInstance = GetModuleHandle(0); + + //- rjf: set dpi awareness + w32_SetProcessDpiAwarenessContext_Type *SetProcessDpiAwarenessContext_func = 0; + HMODULE module = LoadLibraryA("user32.dll"); + if(module != 0) + { + SetProcessDpiAwarenessContext_func = + (w32_SetProcessDpiAwarenessContext_Type*)GetProcAddress(module, "SetProcessDpiAwarenessContext"); + w32_GetDpiForWindow_func = + (w32_GetDpiForWindow_Type*)GetProcAddress(module, "GetDpiForWindow"); + FreeLibrary(module); + } + if(SetProcessDpiAwarenessContext_func != 0) + { + SetProcessDpiAwarenessContext_func(w32_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + } + + //- rjf: register graphical-window class + { + WNDCLASSEXW wndclass = {sizeof(wndclass)}; + wndclass.lpfnWndProc = os_w32_wnd_proc; + wndclass.hInstance = os_w32_gfx_state->hInstance; + wndclass.lpszClassName = L"graphical-window"; + wndclass.hCursor = LoadCursorA(0, IDC_ARROW); + wndclass.hIcon = LoadIcon(os_w32_gfx_state->hInstance, MAKEINTRESOURCE(1)); + wndclass.style = CS_VREDRAW|CS_HREDRAW; + ATOM wndatom = RegisterClassExW(&wndclass); + (void)wndatom; + } + + //- rjf: grab graphics system info + { + os_w32_gfx_state->gfx_info.double_click_time = GetDoubleClickTime()/1000.f; + os_w32_gfx_state->gfx_info.caret_blink_time = GetCaretBlinkTime()/1000.f; + DEVMODEW devmodew = {0}; + if(EnumDisplaySettingsW(0, ENUM_CURRENT_SETTINGS, &devmodew)) + { + os_w32_gfx_state->gfx_info.default_refresh_rate = (F32)devmodew.dmDisplayFrequency; + } + } + + //- rjf: set initial cursor + os_set_cursor(OS_Cursor_Pointer); + + //- rjf: fill vkey -> OS_Key table + { + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'A'] = OS_Key_A; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'B'] = OS_Key_B; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'C'] = OS_Key_C; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'D'] = OS_Key_D; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'E'] = OS_Key_E; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'F'] = OS_Key_F; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'G'] = OS_Key_G; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'H'] = OS_Key_H; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'I'] = OS_Key_I; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'J'] = OS_Key_J; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'K'] = OS_Key_K; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'L'] = OS_Key_L; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'M'] = OS_Key_M; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'N'] = OS_Key_N; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'O'] = OS_Key_O; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'P'] = OS_Key_P; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'Q'] = OS_Key_Q; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'R'] = OS_Key_R; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'S'] = OS_Key_S; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'T'] = OS_Key_T; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'U'] = OS_Key_U; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'V'] = OS_Key_V; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'W'] = OS_Key_W; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'X'] = OS_Key_X; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'Y'] = OS_Key_Y; + os_w32_gfx_state->key_from_vkey_table[(unsigned int)'Z'] = OS_Key_Z; + + for(U64 i = '0', j = OS_Key_0; i <= '9'; i += 1, j += 1) + { + os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)j; + } + for(U64 i = VK_NUMPAD0, j = OS_Key_0; i <= VK_NUMPAD9; i += 1, j += 1) + { + os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)j; + } + for(U64 i = VK_F1, j = OS_Key_F1; i <= VK_F24; i += 1, j += 1) + { + os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)j; + } + + os_w32_gfx_state->key_from_vkey_table[VK_SPACE] = OS_Key_Space; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_3] = OS_Key_Tick; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_MINUS] = OS_Key_Minus; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_PLUS] = OS_Key_Equal; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_4] = OS_Key_LeftBracket; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_6] = OS_Key_RightBracket; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_1] = OS_Key_Semicolon; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_7] = OS_Key_Quote; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_COMMA] = OS_Key_Comma; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_PERIOD]= OS_Key_Period; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_2] = OS_Key_Slash; + os_w32_gfx_state->key_from_vkey_table[VK_OEM_5] = OS_Key_BackSlash; + + os_w32_gfx_state->key_from_vkey_table[VK_TAB] = OS_Key_Tab; + os_w32_gfx_state->key_from_vkey_table[VK_PAUSE] = OS_Key_Pause; + os_w32_gfx_state->key_from_vkey_table[VK_ESCAPE] = OS_Key_Esc; + + os_w32_gfx_state->key_from_vkey_table[VK_UP] = OS_Key_Up; + os_w32_gfx_state->key_from_vkey_table[VK_LEFT] = OS_Key_Left; + os_w32_gfx_state->key_from_vkey_table[VK_DOWN] = OS_Key_Down; + os_w32_gfx_state->key_from_vkey_table[VK_RIGHT] = OS_Key_Right; + + os_w32_gfx_state->key_from_vkey_table[VK_BACK] = OS_Key_Backspace; + os_w32_gfx_state->key_from_vkey_table[VK_RETURN] = OS_Key_Return; + + os_w32_gfx_state->key_from_vkey_table[VK_DELETE] = OS_Key_Delete; + os_w32_gfx_state->key_from_vkey_table[VK_INSERT] = OS_Key_Insert; + os_w32_gfx_state->key_from_vkey_table[VK_PRIOR] = OS_Key_PageUp; + os_w32_gfx_state->key_from_vkey_table[VK_NEXT] = OS_Key_PageDown; + os_w32_gfx_state->key_from_vkey_table[VK_HOME] = OS_Key_Home; + os_w32_gfx_state->key_from_vkey_table[VK_END] = OS_Key_End; + + os_w32_gfx_state->key_from_vkey_table[VK_CAPITAL] = OS_Key_CapsLock; + os_w32_gfx_state->key_from_vkey_table[VK_NUMLOCK] = OS_Key_NumLock; + os_w32_gfx_state->key_from_vkey_table[VK_SCROLL] = OS_Key_ScrollLock; + os_w32_gfx_state->key_from_vkey_table[VK_APPS] = OS_Key_Menu; + + os_w32_gfx_state->key_from_vkey_table[VK_CONTROL] = OS_Key_Ctrl; + os_w32_gfx_state->key_from_vkey_table[VK_LCONTROL] = OS_Key_Ctrl; + os_w32_gfx_state->key_from_vkey_table[VK_RCONTROL] = OS_Key_Ctrl; + os_w32_gfx_state->key_from_vkey_table[VK_SHIFT] = OS_Key_Shift; + os_w32_gfx_state->key_from_vkey_table[VK_LSHIFT] = OS_Key_Shift; + os_w32_gfx_state->key_from_vkey_table[VK_RSHIFT] = OS_Key_Shift; + os_w32_gfx_state->key_from_vkey_table[VK_MENU] = OS_Key_Alt; + os_w32_gfx_state->key_from_vkey_table[VK_LMENU] = OS_Key_Alt; + os_w32_gfx_state->key_from_vkey_table[VK_RMENU] = OS_Key_Alt; + + os_w32_gfx_state->key_from_vkey_table[VK_DIVIDE] = OS_Key_NumSlash; + os_w32_gfx_state->key_from_vkey_table[VK_MULTIPLY] = OS_Key_NumStar; + os_w32_gfx_state->key_from_vkey_table[VK_SUBTRACT] = OS_Key_NumMinus; + os_w32_gfx_state->key_from_vkey_table[VK_ADD] = OS_Key_NumPlus; + os_w32_gfx_state->key_from_vkey_table[VK_DECIMAL] = OS_Key_NumPeriod; + + for(U32 i = 0; i < 10; i += 1) + { + os_w32_gfx_state->key_from_vkey_table[VK_NUMPAD0 + i] = (OS_Key)((U64)OS_Key_Num0 + i); + } + + for(U64 i = 0xDF, j = 0; i < 0xFF; i += 1, j += 1) + { + os_w32_gfx_state->key_from_vkey_table[i] = (OS_Key)((U64)OS_Key_Ex0 + j); + } + } +} + +//////////////////////////////// +//~ rjf: @os_hooks Graphics System Info (Implemented Per-OS) + +internal OS_GfxInfo * +os_get_gfx_info(void) +{ + return &os_w32_gfx_state->gfx_info; +} + +//////////////////////////////// +//~ rjf: @os_hooks Clipboards (Implemented Per-OS) + +internal void +os_set_clipboard_text(String8 string) +{ + if(OpenClipboard(0)) + { + EmptyClipboard(); + HANDLE string_copy_handle = GlobalAlloc(GMEM_MOVEABLE, string.size+1); + if(string_copy_handle) + { + U8 *copy_buffer = (U8 *)GlobalLock(string_copy_handle); + MemoryCopy(copy_buffer, string.str, string.size); + copy_buffer[string.size] = 0; + GlobalUnlock(string_copy_handle); + SetClipboardData(CF_TEXT, string_copy_handle); + } + CloseClipboard(); + } +} + +internal String8 +os_get_clipboard_text(Arena *arena) +{ + String8 result = {0}; + if(IsClipboardFormatAvailable(CF_TEXT) && + OpenClipboard(0)) + { + HANDLE data_handle = GetClipboardData(CF_TEXT); + if(data_handle) + { + U8 *buffer = (U8 *)GlobalLock(data_handle); + if(buffer) + { + U64 size = cstring8_length(buffer); + result = push_str8_copy(arena, str8(buffer, size)); + GlobalUnlock(data_handle); + } + } + CloseClipboard(); + } + return result; +} + +//////////////////////////////// +//~ rjf: @os_hooks Windows (Implemented Per-OS) + +internal OS_Handle +os_window_open(Vec2F32 resolution, OS_WindowFlags flags, String8 title) +{ + //- rjf: make hwnd + HWND hwnd = 0; + { + Temp scratch = scratch_begin(0, 0); + String16 title16 = str16_from_8(scratch.arena, title); + hwnd = CreateWindowExW(WS_EX_APPWINDOW, + L"graphical-window", + (WCHAR*)title16.str, + WS_OVERLAPPEDWINDOW | WS_SIZEBOX, + CW_USEDEFAULT, CW_USEDEFAULT, + (int)resolution.x, + (int)resolution.y, + 0, 0, + os_w32_gfx_state->hInstance, + 0); + scratch_end(scratch); + } + + //- rjf- make/fill window + OS_W32_Window *window = os_w32_window_alloc(); + { + window->hwnd = hwnd; + if (w32_GetDpiForWindow_func != 0){ + window->dpi = (F32)w32_GetDpiForWindow_func(hwnd); + } + else{ + window->dpi = 96.f; + } + } + + //- rjf: early detection of composition + { + BOOL enabled = 0; + DwmIsCompositionEnabled(&enabled); + window->custom_border_composition_enabled = enabled; + } + + //- rjf: custom border + if(flags & OS_WindowFlag_CustomBorder) + { + window->custom_border = 1; + window->paint_arena = arena_alloc(); + } + + //- rjf: convert to handle + return + OS_Handle result = os_w32_handle_from_window(window); + return result; +} + +internal void +os_window_close(OS_Handle handle) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + os_w32_window_release(window); +} + +internal void +os_window_first_paint(OS_Handle window_handle) +{ + OS_W32_Window *window = os_w32_window_from_handle(window_handle); + window->first_paint_done = 1; + ShowWindow(window->hwnd, SW_SHOW); + if(window->maximized) + { + ShowWindow(window->hwnd, SW_MAXIMIZE); + } +} + +internal void +os_window_equip_repaint(OS_Handle handle, OS_WindowRepaintFunctionType *repaint, void *user_data) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + window->repaint = repaint; + window->repaint_user_data = user_data; +} + +internal void +os_window_focus(OS_Handle handle) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + SetForegroundWindow(window->hwnd); + SetFocus(window->hwnd); +} + +internal B32 +os_window_is_focused(OS_Handle handle) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + HWND active_hwnd = GetActiveWindow(); + return active_hwnd == window->hwnd; +} + +internal B32 +os_window_is_fullscreen(OS_Handle handle) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + DWORD window_style = GetWindowLong(window->hwnd, GWL_STYLE); + return !(window_style & WS_OVERLAPPEDWINDOW); +} + +internal void +os_window_set_fullscreen(OS_Handle handle, B32 fullscreen) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + OS_WindowRepaintFunctionType *repaint = window->repaint; + window->repaint = 0; + DWORD window_style = GetWindowLong(window->hwnd, GWL_STYLE); + B32 is_fullscreen_already = os_window_is_fullscreen(handle); + if(fullscreen) + { + if(!is_fullscreen_already) + { + GetWindowPlacement(window->hwnd, &window->last_window_placement); + } + MONITORINFO monitor_info = {sizeof(monitor_info)}; + if(GetMonitorInfo(MonitorFromWindow(window->hwnd, MONITOR_DEFAULTTOPRIMARY), &monitor_info)) + { + SetWindowLong(window->hwnd, GWL_STYLE, window_style & ~WS_OVERLAPPEDWINDOW); + SetWindowPos(window->hwnd, HWND_TOP, + monitor_info.rcMonitor.left, + monitor_info.rcMonitor.top, + monitor_info.rcMonitor.right - monitor_info.rcMonitor.left, + monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } + } + else + { + SetWindowLong(window->hwnd, GWL_STYLE, window_style | WS_OVERLAPPEDWINDOW); + SetWindowPlacement(window->hwnd, &window->last_window_placement); + SetWindowPos(window->hwnd, 0, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } + window->repaint = repaint; +} + +internal B32 +os_window_is_maximized(OS_Handle handle) +{ + B32 result = 0; + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window) + { + result = !!(IsZoomed(window->hwnd)); + } + return result; +} + +internal void +os_window_set_maximized(OS_Handle handle, B32 maximized) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window != 0) + { + if(window->first_paint_done) + { + switch(maximized) + { + default: + case 0:{ShowWindow(window->hwnd, SW_RESTORE);}break; + case 1:{ShowWindow(window->hwnd, SW_MAXIMIZE);}break; + } + } + else + { + window->maximized = maximized; + } + } +} + +internal void +os_window_minimize(OS_Handle handle) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window != 0) + { + ShowWindow(window->hwnd, SW_MINIMIZE); + } +} + +internal void +os_window_bring_to_front(OS_Handle handle) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window != 0) + { + BringWindowToTop(window->hwnd); + } +} + +internal void +os_window_set_monitor(OS_Handle window_handle, OS_Handle monitor) +{ + OS_W32_Window *window = os_w32_window_from_handle(window_handle); + HMONITOR hmonitor = (HMONITOR)monitor.u64[0]; + { + MONITORINFOEXW info; + info.cbSize = sizeof(MONITORINFOEXW); + if(GetMonitorInfoW(hmonitor, (MONITORINFO *)&info)) + { + Rng2F32 existing_rect = os_rect_from_window(window_handle); + Vec2F32 window_size = dim_2f32(existing_rect); + SetWindowPos(window->hwnd, HWND_TOP, + (info.rcWork.left + info.rcWork.right)/2 - window_size.x/2, + (info.rcWork.top + info.rcWork.bottom)/2 - window_size.y/2, + window_size.x, + window_size.y, 0); + } + } +} + +internal void +os_window_clear_custom_border_data(OS_Handle handle) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window->custom_border) + { + arena_clear(window->paint_arena); + window->first_title_bar_client_area = window->last_title_bar_client_area = 0; + window->custom_border_title_thickness = 0; + window->custom_border_edge_thickness = 0; + } +} + +internal void +os_window_push_custom_title_bar(OS_Handle handle, F32 thickness) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + window->custom_border_title_thickness = thickness; +} + +internal void +os_window_push_custom_edges(OS_Handle handle, F32 thickness) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + window->custom_border_edge_thickness = thickness; +} + +internal void +os_window_push_custom_title_bar_client_area(OS_Handle handle, Rng2F32 rect) +{ + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window->custom_border) + { + OS_W32_TitleBarClientArea *area = push_array(window->paint_arena, OS_W32_TitleBarClientArea, 1); + if(area != 0) + { + area->rect = rect; + SLLQueuePush(window->first_title_bar_client_area, window->last_title_bar_client_area, area); + } + } +} + +internal Rng2F32 +os_rect_from_window(OS_Handle handle) +{ + Rng2F32 r = {0}; + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window) + { + RECT rect = {0}; + GetWindowRect(os_w32_hwnd_from_window(window), &rect); + r = os_w32_rng2f32_from_rect(rect); + } + return r; +} + +internal Rng2F32 +os_client_rect_from_window(OS_Handle handle) +{ + Rng2F32 r = {0}; + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window) + { + RECT rect = {0}; + GetClientRect(os_w32_hwnd_from_window(window), &rect); + r = os_w32_rng2f32_from_rect(rect); + } + return r; +} + +internal F32 +os_dpi_from_window(OS_Handle handle) +{ + F32 result = 96.f; + OS_W32_Window *window = os_w32_window_from_handle(handle); + if(window != 0) + { + result = window->dpi; + } + return result; +} + +//////////////////////////////// +//~ rjf: @os_hooks Monitors (Implemented Per-OS) + +internal OS_HandleArray +os_push_monitors_array(Arena *arena) +{ + Temp scratch = scratch_begin(&arena, 1); + OS_HandleList list = {0}; + { + OS_W32_MonitorGatherBundle bundle = {arena, &list}; + EnumDisplayMonitors(0, 0, os_w32_monitor_gather_enum_proc, (LPARAM)&bundle); + } + OS_HandleArray array = os_handle_array_from_list(arena, &list); + scratch_end(scratch); + return array; +} + +internal OS_Handle +os_primary_monitor(void) +{ + POINT zero_pt = {0, 0}; + HMONITOR monitor = MonitorFromPoint(zero_pt, MONITOR_DEFAULTTOPRIMARY); + OS_Handle result = {(U64)monitor}; + return result; +} + +internal OS_Handle +os_monitor_from_window(OS_Handle window) +{ + OS_W32_Window *w = os_w32_window_from_handle(window); + HMONITOR handle = MonitorFromWindow(w->hwnd, MONITOR_DEFAULTTOPRIMARY); + OS_Handle result = {(U64)handle}; + return result; +} + +internal String8 +os_name_from_monitor(Arena *arena, OS_Handle monitor) +{ + String8 result = {0}; + HMONITOR monitor_handle = (HMONITOR)monitor.u64[0]; + MONITORINFOEXW info; + info.cbSize = sizeof(MONITORINFOEXW); + if(GetMonitorInfoW(monitor_handle, (MONITORINFO *)&info)) + { + String16 result16 = str16_cstring((U16 *)info.szDevice); + result = str8_from_16(arena, result16); + } + return result; +} + +internal Vec2F32 +os_dim_from_monitor(OS_Handle monitor) +{ + Vec2F32 result = {0}; + HMONITOR monitor_handle = (HMONITOR)monitor.u64[0]; + MONITORINFO info = {0}; + info.cbSize = sizeof(MONITORINFO); + if(GetMonitorInfoW(monitor_handle, &info)) + { + result.x = info.rcWork.right - info.rcWork.left; + result.y = info.rcWork.bottom - info.rcWork.top; + } + return result; +} + +//////////////////////////////// +//~ rjf: @os_hooks Events (Implemented Per-OS) + +internal void +os_send_wakeup_event(void) +{ + PostThreadMessageA(os_w32_gfx_state->gfx_thread_tid, 0x401, 0, 0); +} + +internal OS_EventList +os_get_events(Arena *arena, B32 wait) +{ + os_w32_event_arena = arena; + MemoryZeroStruct(&os_w32_event_list); + MSG msg = {0}; + if(!wait || GetMessage(&msg, 0, 0, 0)) + { + B32 first_wait = wait; + for(;first_wait || PeekMessage(&msg, 0, 0, 0, PM_REMOVE); first_wait = 0) + { + DispatchMessage(&msg); + TranslateMessage(&msg); + if(msg.message == WM_QUIT) + { + os_w32_push_event(OS_EventKind_WindowClose, 0); + } + } + } + return os_w32_event_list; +} + +internal OS_EventFlags +os_get_event_flags(void) +{ + OS_EventFlags flags = 0; + if(GetKeyState(VK_CONTROL) & 0x8000) + { + flags |= OS_EventFlag_Ctrl; + } + if(GetKeyState(VK_SHIFT) & 0x8000) + { + flags |= OS_EventFlag_Shift; + } + if(GetKeyState(VK_MENU) & 0x8000) + { + flags |= OS_EventFlag_Alt; + } + return(flags); +} + +internal B32 +os_key_is_down(OS_Key key) +{ + B32 result = 0; + { + WPARAM vkey_code = os_w32_vkey_from_os_key(key); + SHORT state = GetAsyncKeyState(vkey_code); + result = !!(state & (0x8000)); + } + return result; +} + +internal Vec2F32 +os_mouse_from_window(OS_Handle handle) +{ + ProfBeginFunction(); + Vec2F32 v = {0}; + POINT p; + if(GetCursorPos(&p)) + { + OS_W32_Window *window = os_w32_window_from_handle(handle); + ScreenToClient(window->hwnd, &p); + v.x = (F32)p.x; + v.y = (F32)p.y; + } + ProfEnd(); + return v; +} + +//////////////////////////////// +//~ rjf: @os_hooks Cursors (Implemented Per-OS) + +internal void +os_set_cursor(OS_Cursor cursor) +{ + B32 valid_cursor = 1; + HCURSOR hcursor = 0; + switch(cursor) + { + default: {valid_cursor = 0;}break; +#define Win32CursorXList(X) \ +X(Pointer, IDC_ARROW) \ +X(IBar, IDC_IBEAM) \ +X(LeftRight, IDC_SIZEWE) \ +X(UpDown, IDC_SIZENS) \ +X(DownRight, IDC_SIZENWSE) \ +X(UpRight, IDC_SIZENESW) \ +X(UpDownLeftRight, IDC_SIZEALL) \ +X(HandPoint, IDC_HAND)\ +X(Disabled, IDC_NO) +#define CursorCase(E,R) case OS_Cursor_##E:{ \ +local_persist HCURSOR curs = 0; \ +if (curs == 0){ curs = LoadCursor(NULL, R); } \ +hcursor = curs; }break; + Win32CursorXList(CursorCase) +#undef CursorCase +#undef Win32CursorXList + } + if(valid_cursor && !os_w32_resizing) + { + if(hcursor != os_w32_gfx_state->hCursor) + { + PostMessage(0, WM_SETCURSOR, 0, 0); + POINT p = {0}; + GetCursorPos(&p); + SetCursorPos(p.x, p.y); + } + os_w32_gfx_state->hCursor = hcursor; + } +} + +//////////////////////////////// +//~ rjf: @os_hooks Native User-Facing Graphical Messages (Implemented Per-OS) + +internal void +os_graphical_message(B32 error, String8 title, String8 message) +{ + Temp scratch = scratch_begin(0, 0); + String16 title16 = str16_from_8(scratch.arena, title); + String16 message16 = str16_from_8(scratch.arena, message); + MessageBoxW(0, (WCHAR *)message16.str, (WCHAR *)title16.str, MB_OK|(!!error*MB_ICONERROR)); + scratch_end(scratch); +} + +//////////////////////////////// +//~ rjf: @os_hooks Shell Operations + +internal void +os_show_in_filesystem_ui(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String8 path_copy = push_str8_copy(scratch.arena, path); + for(U64 idx = 0; idx < path_copy.size; idx += 1) + { + if(path_copy.str[idx] == '/') + { + path_copy.str[idx] = '\\'; + } + } + String16 path16 = str16_from_8(scratch.arena, path_copy); + SFGAOF flags = 0; + PIDLIST_ABSOLUTE list = 0; + if(path16.size != 0 && SUCCEEDED(SHParseDisplayName(path16.str, 0, &list, 0, &flags))) + { + HRESULT hr = SHOpenFolderAndSelectItems(list, 0, 0, 0); + CoTaskMemFree(list); + (void)hr; + } + scratch_end(scratch); +} diff --git a/src/os/gfx/win32/os_gfx_win32.h b/src/os/gfx/win32/os_gfx_win32.h index cfb54d25..dadf4e9d 100644 --- a/src/os/gfx/win32/os_gfx_win32.h +++ b/src/os/gfx/win32/os_gfx_win32.h @@ -1,82 +1,115 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef WIN32_GRAPHICAL_H -#define WIN32_GRAPHICAL_H - -#pragma comment(lib, "user32") -#pragma comment(lib, "gdi32") - -#ifndef WM_NCUAHDRAWCAPTION -#define WM_NCUAHDRAWCAPTION (0x00AE) -#endif -#ifndef WM_NCUAHDRAWFRAME -#define WM_NCUAHDRAWFRAME (0x00AF) -#endif - -//////////////////////////////// -//~ rjf: Windows - -typedef struct W32_TitleBarClientArea W32_TitleBarClientArea; -struct W32_TitleBarClientArea -{ - W32_TitleBarClientArea *next; - Rng2F32 rect; -}; - -typedef struct W32_Window W32_Window; -struct W32_Window -{ - W32_Window *next; - W32_Window *prev; - HWND hwnd; - WINDOWPLACEMENT last_window_placement; - OS_WindowRepaintFunctionType *repaint; - void *repaint_user_data; - F32 dpi; - B32 first_paint_done; - B32 maximized; - B32 custom_border; - F32 custom_border_title_thickness; - F32 custom_border_edge_thickness; - B32 custom_border_composition_enabled; - Arena *paint_arena; - W32_TitleBarClientArea *first_title_bar_client_area; - W32_TitleBarClientArea *last_title_bar_client_area; -}; - -//////////////////////////////// -//~ rjf: Monitor Gathering Bundle - -typedef struct W32_MonitorGatherBundle W32_MonitorGatherBundle; -struct W32_MonitorGatherBundle -{ - Arena *arena; - OS_HandleList *list; -}; - -//////////////////////////////// -//~ rjf: Basic Helpers - -internal Rng2F32 w32_base_rect_from_win32_rect(RECT rect); - -//////////////////////////////// -//~ rjf: Windows - -internal OS_Handle os_window_from_w32_window(W32_Window *window); -internal W32_Window * w32_window_from_os_window(OS_Handle window); -internal W32_Window * w32_window_from_hwnd(HWND hwnd); -internal HWND w32_hwnd_from_window(W32_Window *window); -internal W32_Window * w32_allocate_window(void); -internal void w32_free_window(W32_Window *window); -internal OS_Event * w32_push_event(OS_EventKind kind, W32_Window *window); -internal OS_Key w32_os_key_from_vkey(WPARAM vkey); -internal WPARAM w32_vkey_from_os_key(OS_Key key); -internal LRESULT w32_wnd_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - -//////////////////////////////// -//~ rjf: Monitors - -internal BOOL w32_monitor_gather_enum_proc(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM bundle_ptr); - -#endif // WIN32_GRAPHICAL_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_GFX_WIN32_H +#define OS_GFX_WIN32_H + +//////////////////////////////// +//~ rjf: Includes / Libraries + +#include +#include +#include +#pragma comment(lib, "gdi32") +#pragma comment(lib, "dwmapi") +#pragma comment(lib, "UxTheme") +#pragma comment(lib, "ole32") +#pragma comment(lib, "user32") +#ifndef WM_NCUAHDRAWCAPTION +#define WM_NCUAHDRAWCAPTION (0x00AE) +#endif +#ifndef WM_NCUAHDRAWFRAME +#define WM_NCUAHDRAWFRAME (0x00AF) +#endif + +//////////////////////////////// +//~ rjf: Windows + +typedef struct OS_W32_TitleBarClientArea OS_W32_TitleBarClientArea; +struct OS_W32_TitleBarClientArea +{ + OS_W32_TitleBarClientArea *next; + Rng2F32 rect; +}; + +typedef struct OS_W32_Window OS_W32_Window; +struct OS_W32_Window +{ + OS_W32_Window *next; + OS_W32_Window *prev; + HWND hwnd; + WINDOWPLACEMENT last_window_placement; + OS_WindowRepaintFunctionType *repaint; + void *repaint_user_data; + F32 dpi; + B32 first_paint_done; + B32 maximized; + B32 custom_border; + F32 custom_border_title_thickness; + F32 custom_border_edge_thickness; + B32 custom_border_composition_enabled; + Arena *paint_arena; + OS_W32_TitleBarClientArea *first_title_bar_client_area; + OS_W32_TitleBarClientArea *last_title_bar_client_area; +}; + +//////////////////////////////// +//~ rjf: Monitor Gathering Bundle + +typedef struct OS_W32_MonitorGatherBundle OS_W32_MonitorGatherBundle; +struct OS_W32_MonitorGatherBundle +{ + Arena *arena; + OS_HandleList *list; +}; + +//////////////////////////////// +//~ rjf: Global State + +typedef struct OS_W32_GfxState OS_W32_GfxState; +struct OS_W32_GfxState +{ + Arena *arena; + U32 gfx_thread_tid; + HINSTANCE hInstance; + HCURSOR hCursor; + OS_GfxInfo gfx_info; + OS_W32_Window *first_window; + OS_W32_Window *last_window; + OS_W32_Window *free_window; + OS_Key key_from_vkey_table[256]; +}; + +//////////////////////////////// +//~ rjf: Globals + +global OS_W32_GfxState *os_w32_gfx_state = 0; +global OS_EventList os_w32_event_list = {0}; +global Arena *os_w32_event_arena = 0; +B32 os_w32_resizing = 0; + +//////////////////////////////// +//~ rjf: Basic Helpers + +internal Rng2F32 os_w32_rng2f32_from_rect(RECT rect); + +//////////////////////////////// +//~ rjf: Windows + +internal OS_Handle os_w32_handle_from_window(OS_W32_Window *window); +internal OS_W32_Window * os_w32_window_from_handle(OS_Handle window); +internal OS_W32_Window * os_w32_window_from_hwnd(HWND hwnd); +internal HWND os_w32_hwnd_from_window(OS_W32_Window *window); +internal OS_W32_Window * os_w32_window_alloc(void); +internal void os_w32_window_release(OS_W32_Window *window); +internal OS_Event * os_w32_push_event(OS_EventKind kind, OS_W32_Window *window); +internal OS_Key os_w32_os_key_from_vkey(WPARAM vkey); +internal WPARAM os_w32_vkey_from_os_key(OS_Key key); +internal LRESULT os_w32_wnd_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +//////////////////////////////// +//~ rjf: Monitors + +internal BOOL os_w32_monitor_gather_enum_proc(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM bundle_ptr); + +#endif // OS_GFX_WIN32_H diff --git a/src/os/os_inc.c b/src/os/os_inc.c index 7b2125ae..d92f9283 100644 --- a/src/os/os_inc.c +++ b/src/os/os_inc.c @@ -1,33 +1,27 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -// NOTE(allen): Include OS features for extra features and target OS - -#include "core/os_core.c" - -#if OS_FEATURE_SOCKET -#include "socket/os_socket.c" -#endif - -#if OS_FEATURE_GRAPHICAL -#include "gfx/os_gfx.c" -#endif - -#if OS_WINDOWS -# include "core/win32/os_core_win32.c" -# if OS_FEATURE_SOCKET -# include "socket/win32/os_socket_win32.c" -# endif -# if OS_FEATURE_GRAPHICAL && !OS_GFX_STUB -# include "gfx/win32/os_gfx_win32.c" -# endif -#elif OS_LINUX -# include "core/linux/os_core_linux.c" -#else -# error no OS layer setup -#endif - -#if OS_GFX_STUB -#include "gfx/stub/os_gfx_stub.c" -#endif +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#include "os/core/os_core.c" +#if OS_FEATURE_GRAPHICAL +# include "os/gfx/os_gfx.c" +#endif + +#if OS_WINDOWS +# include "os/core/win32/os_core_win32.c" +#elif OS_LINUX +# include "os/core/linux/os_core_linux.c" +#else +# error OS core layer not implemented for this operating system. +#endif + +#if OS_FEATURE_GRAPHICAL +# if OS_GFX_STUB +# include "os/gfx/stub/os_gfx_stub.c" +# elif OS_WINDOWS +# include "os/gfx/win32/os_gfx_win32.c" +# elif OS_LINUX +# include "os/gfx/linux/os_gfx_linux.c" +# else +# error OS graphical layer not implemented for this operating system. +# endif +#endif diff --git a/src/os/os_inc.h b/src/os/os_inc.h index 8d7c1db1..ce5246a0 100644 --- a/src/os/os_inc.h +++ b/src/os/os_inc.h @@ -1,47 +1,40 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef OS_INC_H -#define OS_INC_H - -#if !defined(OS_FEATURE_SOCKET) -# define OS_FEATURE_SOCKET 0 -#endif - -#if !defined(OS_FEATURE_GRAPHICAL) -# define OS_FEATURE_GRAPHICAL 0 -#endif - -#if !defined(OS_GFX_STUB) -# define OS_GFX_STUB 0 -#endif - -#include "core/os_core.h" - -#if OS_FEATURE_SOCKET -#include "socket/os_socket.h" -#endif - -#if OS_FEATURE_GRAPHICAL -#include "gfx/os_gfx.h" -#endif - -#if OS_WINDOWS -# include "core/win32/os_core_win32.h" -# if OS_FEATURE_SOCKET -# include "socket/win32/os_socket_win32.h" -# endif -# if OS_FEATURE_GRAPHICAL && !OS_GFX_STUB -# include "gfx/win32/os_gfx_win32.h" -# endif -#elif OS_LINUX -# include "core/linux/os_core_linux.h" -#else -# error no OS layer setup -#endif - -#if OS_GFX_STUB -#include "gfx/stub/os_gfx_stub.h" -#endif - -#endif //OS_SWITCH_H +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_INC_H +#define OS_INC_H + +#if !defined(OS_FEATURE_GRAPHICAL) +# define OS_FEATURE_GRAPHICAL 0 +#endif + +#if !defined(OS_GFX_STUB) +# define OS_GFX_STUB 0 +#endif + +#include "os/core/os_core.h" +#if OS_FEATURE_GRAPHICAL +# include "os/gfx/os_gfx.h" +#endif + +#if OS_WINDOWS +# include "os/core/win32/os_core_win32.h" +#elif OS_LINUX +# include "os/core/linux/os_core_linux.h" +#else +# error OS core layer not implemented for this operating system. +#endif + +#if OS_FEATURE_GRAPHICAL +# if OS_GFX_STUB +# include "os/gfx/stub/os_gfx_stub.h" +# elif OS_WINDOWS +# include "os/gfx/win32/os_gfx_win32.h" +# elif OS_LINUX +# include "os/gfx/linux/os_gfx_linux.h" +# else +# error OS graphical layer not implemented for this operating system. +# endif +#endif + +#endif // OS_INC_H diff --git a/src/os/socket/os_socket.c b/src/os/socket/os_socket.c deleted file mode 100644 index dc2ef23a..00000000 --- a/src/os/socket/os_socket.c +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -// NOTE(allen): Helper - -internal B32 -os_socket_write(OS_Socket *socket, String8 data){ - String8Node node = {0}; - String8List list = {0}; - str8_list_push(&list, &node, data); - B32 result = os_socket_write(socket, list); - return(result); -} - -#endif diff --git a/src/os/socket/os_socket.h b/src/os/socket/os_socket.h deleted file mode 100644 index b9a9a94b..00000000 --- a/src/os/socket/os_socket.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef OS_SOCKET_H -#define OS_SOCKET_H - -enum OS_SocketStatus{ - OS_SocketStatus_Uninitialized, - OS_SocketStatus_Connected, - OS_SocketStatus_GracefullyClosed, - OS_SocketStatus_Error, -}; - -typedef U16 OS_SocketError; -enum{ - OS_SocketError_None, - OS_SocketError_SocketSystemNotInitialized, - OS_SocketError_BadPortArgument, - OS_SocketError_BadIPArgument, - OS_SocketError_WSAError, -}; - -struct OS_Socket{ - U8 memory[32]; -}; - - -//////////////////////////////// -//~ NOTE(allen): Implemented Per Operating System - -internal void os_socket_init(void); - -internal void os_socket_listen(OS_Socket *socket, String8 port); -internal void os_socket_connect(OS_Socket *socket, String8 ip, String8 port); -internal void os_socket_close(OS_Socket *socket); - -internal String8 os_socket_read(Arena *arena, OS_Socket *socket); -internal B32 os_socket_write(OS_Socket *socket, String8List list); - -internal B32 os_socket_status(OS_Socket *socket, OS_SocketStatus status); -internal String8 os_socket_error_string(Arena *arena, OS_Socket *socket); -internal void os_socket_assert_on_error(OS_Socket *socket, B32 assert_on_error); - -//////////////////////////////// -//~ NOTE(allen): Helpers - Portable Implementation - -internal B32 os_socket_write(OS_Socket *socket, String8 data); - -#endif //OS_SOCKET_H diff --git a/src/os/socket/win32/os_socket_win32.c b/src/os/socket/win32/os_socket_win32.c deleted file mode 100644 index 03218840..00000000 --- a/src/os/socket/win32/os_socket_win32.c +++ /dev/null @@ -1,353 +0,0 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Helpers - -internal void -w32_socket_set_error(W32_Socket *socket, OS_SocketError error){ - socket->error = error; - // NOTE(allen): This flag was set earlier so that the socket would assert - // when an error occurs. The "bug" or issue is whatever caused this error - // not the fact the flag is set. Unless the flag wasn't supposed to be set! - Assert(!(socket->flags & W32_SocketFlag_AssertOnError)); -} - -internal void -w32_socket_set_error_wsa(W32_Socket *socket, int wsa_error){ - switch (wsa_error){ - default: - { - socket->wsa_error = wsa_error; - w32_socket_set_error(socket, OS_SocketError_WSAError); - }break; - case WSANOTINITIALISED: - { - w32_socket_set_error(socket, OS_SocketError_SocketSystemNotInitialized); - }break; - } -} - -internal B32 -w32_socket_read_looped(W32_Socket *w32_socket, void *buffer, U32 size){ - U32 p = 0; - CHAR *ptr = (CHAR*)buffer; - for (;p < size;){ - DWORD amt = (DWORD)(size - p); - WSABUF wsabuf = {amt, ptr}; - // NOTE(allen): The flags pointer is _NOT_ optional but we can ignore it. - // We have to zero it because it's an in/out pointer. - DWORD ignore = 0; - if (WSARecv(w32_socket->socket, &wsabuf, 1, &amt, &ignore, 0, 0) != 0){ - w32_socket_set_error_wsa(w32_socket, WSAGetLastError()); - break; - } - if (amt == 0){ - w32_socket->flags |= W32_SocketFlag_Closed; - break; - } - p += amt; - ptr += amt; - } - B32 result = (p == size); - return(result); -} - -//////////////////////////////// -//~ rjf: Per-OS Hook Implementations - -internal void -os_socket_init(void){ - WSADATA wsaData; - WORD vreq = MAKEWORD(2,2); - WSAStartup(vreq, &wsaData); -} - -internal void -os_socket_listen(OS_Socket *s, String8 port){ - W32_Socket *w32_socket = (W32_Socket*)s->memory; - - // NOTE(allen): check port string - char port_buffer[6]; - if (port.size == 0 || port.size >= sizeof(port_buffer)){ - w32_socket_set_error(w32_socket, OS_SocketError_BadPortArgument); - return; - } - MemoryCopy(port_buffer, port.str, port.size); - port_buffer[port.size] = 0; - - // NOTE(allen): listen socket addrinfo - addrinfo listen_hint = {0}; - listen_hint.ai_flags = AI_PASSIVE|AI_NUMERICSERV; - listen_hint.ai_family = AF_UNSPEC; - listen_hint.ai_socktype = SOCK_STREAM; - listen_hint.ai_protocol = AF_UNSPEC; - - addrinfo *addr = {0}; - INT error = getaddrinfo(0, port_buffer, &listen_hint, &addr); - if (error != 0){ - w32_socket_set_error_wsa(w32_socket, WSAGetLastError()); - return; - } - - // NOTE(allen): init listen socket - SOCKET socket_listener = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); - W32_SocketCloser listener_closer(&socket_listener); - - // NOTE(allen): reuseraddr - { - union { B32 b; char c[1]; } enable; - enable.b = true; - if (setsockopt(socket_listener, SOL_SOCKET, SO_REUSEADDR, enable.c, sizeof(enable)) < 0){ - w32_socket_set_error_wsa(w32_socket, WSAGetLastError()); - return; - } - } - - // NOTE(allen): bind - if (bind(socket_listener, addr->ai_addr, (int)addr->ai_addrlen) < 0){ - w32_socket_set_error_wsa(w32_socket, WSAGetLastError()); - return; - } - - // NOTE(allen): listen - if (listen(socket_listener, 1) < 0){ - w32_socket_set_error_wsa(w32_socket, WSAGetLastError()); - return; - } - - // NOTE(allen): accept - SOCKET client_socket = accept(socket_listener, 0, 0); - W32_SocketCloser client_closer(&client_socket); - - // NOTE(allen): TCP_NODELAY - { - union { B32 b; char c[1]; } enable; - enable.b = true; - if (setsockopt(client_socket, IPPROTO_TCP, TCP_NODELAY, enable.c, sizeof(enable)) < 0){ - w32_socket_set_error_wsa(w32_socket, WSAGetLastError()); - return; - } - } - - // NOTE(allen): success - w32_socket->flags |= W32_SocketFlag_Connected; - w32_socket->socket = client_socket; - w32_socket->error = OS_SocketError_None; - client_closer.do_not_close(); -} - -internal void -os_socket_connect(OS_Socket *s, String8 ip, String8 port){ - W32_Socket *w32_socket = (W32_Socket*)s->memory; - - // NOTE(allen): check port string - char port_buffer[6]; - if (port.size == 0 || port.size >= sizeof(port_buffer)){ - w32_socket_set_error(w32_socket, OS_SocketError_BadPortArgument); - return; - } - MemoryCopy(port_buffer, port.str, port.size); - port_buffer[port.size] = 0; - - // NOTE(allen): check ip string - if (ip.size == 0){ - ip = str8_lit("localhost"); - } - char ip_buffer[KB(1)]; - if (ip.size >= sizeof(ip_buffer)){ - w32_socket_set_error(w32_socket, OS_SocketError_BadIPArgument); - return; - } - MemoryCopy(ip_buffer, ip.str, ip.size); - ip_buffer[ip.size] = 0; - - // NOTE(allen): socket addrinfo - addrinfo hint = {0}; - hint.ai_flags = AI_PASSIVE|AI_NUMERICSERV; - hint.ai_family = AF_UNSPEC; - hint.ai_socktype = SOCK_STREAM; - hint.ai_protocol = AF_UNSPEC; - - addrinfo *addr = {0}; - INT error = getaddrinfo(ip_buffer, port_buffer, &hint, &addr); - if (error != 0){ - w32_socket_set_error_wsa(w32_socket, WSAGetLastError()); - return; - } - - // NOTE(allen): init socket - SOCKET socket_server = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); - W32_SocketCloser closer(&socket_server); - - // NOTE(allen): TCP_NODELAY - { - union { B32 b; char c[1]; } enable; - enable.b = true; - if (setsockopt(socket_server, IPPROTO_TCP, TCP_NODELAY, enable.c, sizeof(enable)) < 0){ - w32_socket_set_error_wsa(w32_socket, WSAGetLastError()); - return; - } - } - - // NOTE(allen): connect - if (connect(socket_server, addr->ai_addr, (int)addr->ai_addrlen) < 0){ - w32_socket_set_error_wsa(w32_socket, WSAGetLastError()); - return; - } - - // NOTE(allen): success - w32_socket->flags |= W32_SocketFlag_Connected; - w32_socket->socket = socket_server; - w32_socket->error = OS_SocketError_None; - closer.do_not_close(); -} - -internal void -os_socket_close(OS_Socket *socket){ - W32_Socket *w32_socket = (W32_Socket*)socket->memory; - closesocket(w32_socket->socket); - MemoryZeroStruct(w32_socket); -} - -internal String8 -os_socket_read(Arena *arena, OS_Socket *socket){ - W32_Socket *w32_socket = (W32_Socket*)socket->memory; - String8 result = {0}; - U32 size = 0; - if (w32_socket_read_looped(w32_socket, &size, sizeof(size))){ - Temp restore = temp_begin(arena); - result.str = push_array_no_zero(arena, U8, size); - if (w32_socket_read_looped(w32_socket, result.str, size)){ - result.size = size; - } - else{ - temp_end(restore); - result.str = 0; - } - } - return(result); -} - -internal B32 -os_socket_write(OS_Socket *socket, String8List list){ - U32 size = (U32)list.total_size; - String8Node node = {0}; - str8_list_push_front(&list, &node, str8_struct(&size)); - - W32_Socket *w32_socket = (W32_Socket*)socket->memory; - - WSABUF wsabuf[64]; - Assert(list.node_count <= ArrayCount(wsabuf)); - - U64 wsabuf_count = 0; - for (String8Node *node = list.first; - node != 0; - node = node->next){ - wsabuf[wsabuf_count].len = (U32)node->string.size; - wsabuf[wsabuf_count].buf = (CHAR*)node->string.str; - wsabuf_count += 1; - } - - B32 result = false; - DWORD amt = 0; - if (WSASend(w32_socket->socket, wsabuf, wsabuf_count, &amt, 0, 0, 0) != 0){ - w32_socket_set_error_wsa(w32_socket, WSAGetLastError()); - } - else if (amt == 0){ - w32_socket->flags |= W32_SocketFlag_Connected; - } - else{ - result = true; - } - - return(result); -} - -internal B32 -os_socket_status(OS_Socket *socket, OS_SocketStatus status){ - W32_Socket *w32_socket = (W32_Socket*)socket; - B32 result = false; - switch (status){ - case OS_SocketStatus_Uninitialized: - { - result = (((w32_socket->flags & (W32_SocketFlag_Connected|W32_SocketFlag_Closed)) == 0) && - (w32_socket->error == 0)); - }break; - - case OS_SocketStatus_Connected: - { - result = (((w32_socket->flags & (W32_SocketFlag_Connected|W32_SocketFlag_Closed)) == W32_SocketFlag_Connected) && - (w32_socket->error == 0)); - }break; - - case OS_SocketStatus_GracefullyClosed: - { - result = (((w32_socket->flags & W32_SocketFlag_Closed) == W32_SocketFlag_Closed) && (w32_socket->error == 0)); - }break; - - case OS_SocketStatus_Error: - { - result = (w32_socket->error != 0); - }break; - } - return(result); -} - -internal String8 -os_socket_error_string(Arena *arena, OS_Socket *socket){ - String8 result = str8_lit("no error"); - - W32_Socket *w32_socket = (W32_Socket*)socket; - switch (w32_socket->error){ - default: - { - result = str8_lit("Bad error code"); - }break; - - case OS_SocketError_None:break; - - case OS_SocketError_SocketSystemNotInitialized: - { - result = str8_lit("Missing call to os_socket_init"); - }break; - - case OS_SocketError_BadPortArgument: - { - result = str8_lit("Invalid port argument to socket API"); - }break; - - case OS_SocketError_BadIPArgument: - { - result = str8_lit("Invalid ip argument to socket API"); - }break; - - case OS_SocketError_WSAError: - { - DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_IGNORE_INSERTS|FORMAT_MESSAGE_FROM_SYSTEM; - CHAR *message = 0; - DWORD size = FormatMessageA(flags, 0, w32_socket->wsa_error, 0, (CHAR*)&message, 0, 0); - if (size == 0){ - result = str8_lit("Unknown WSA error"); - } - else{ - String8 string = str8_skip_chop_whitespace(str8((U8*)message, size)); - result = push_str8_copy(arena, string); - LocalFree(message); - } - }break; - } - - return(result); -} - -internal void -os_socket_assert_on_error(OS_Socket *socket, B32 assert_on_error){ - W32_Socket *w32_socket = (W32_Socket*)socket; - if (assert_on_error){ - w32_socket->flags |= W32_SocketFlag_AssertOnError; - } - else{ - w32_socket->flags &= ~W32_SocketFlag_AssertOnError; - } -} diff --git a/src/os/socket/win32/os_socket_win32.h b/src/os/socket/win32/os_socket_win32.h deleted file mode 100644 index 6f6ad766..00000000 --- a/src/os/socket/win32/os_socket_win32.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#ifndef WIN32_SOCKET_H -#define WIN32_SOCKET_H - -//////////////////////////////// -//~ rjf: Types - -typedef U16 W32_SocketFlags; -enum{ - W32_SocketFlag_Connected = (1 << 0), - W32_SocketFlag_Closed = (1 << 1), - W32_SocketFlag_AssertOnError = (1 << 2), -}; - -struct W32_Socket{ - W32_SocketFlags flags; - OS_SocketError error; - int wsa_error; - SOCKET socket; -}; - -struct W32_SocketCloser{ - B32 need_to_close; - SOCKET *socket; - W32_SocketCloser(SOCKET *s){ - this->need_to_close = true; - this->socket = s; - } - ~W32_SocketCloser(){ - this->close_now(); - } - void close_now(){ - if (this->need_to_close){ - closesocket(*this->socket); - this->need_to_close = false; - } - } - void do_not_close(){ - this->need_to_close = false; - } -}; - -StaticAssert(sizeof(Member(OS_Socket, memory)) >= sizeof(W32_Socket), socket_memory_size); - -//////////////////////////////// -//~ rjf: Helpers - -internal void w32_socket_set_error(W32_Socket *socket, OS_SocketError error); -internal void w32_socket_set_error_wsa(W32_Socket *socket, int wsa_error); -internal B32 w32_socket_read_looped(W32_Socket *w32_socket, void *buffer, U32 size); - -#endif // WIN32_SOCKET_H diff --git a/src/path/path.c b/src/path/path.c index d80a5d69..60df6685 100644 --- a/src/path/path.c +++ b/src/path/path.c @@ -1,167 +1,165 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ allen: Path Helper Functions - -internal StringMatchFlags -path_match_flags_from_os(OperatingSystem os) -{ - StringMatchFlags flags = StringMatchFlag_SlashInsensitive; - switch(os) - { - default:{}break; - case OperatingSystem_Windows: - { - flags |= StringMatchFlag_CaseInsensitive; - }break; - case OperatingSystem_Linux: - case OperatingSystem_Mac: - { - // NOTE(rjf): no-op - }break; - } - return flags; -} - -internal String8 -path_relative_dst_from_absolute_dst_src(Arena *arena, String8 dst, String8 src) -{ - Temp scratch = scratch_begin(&arena, 1); - - // rjf: gather path parts - String8 dst_name = str8_skip_last_slash(dst); - String8 src_folder = str8_chop_last_slash(src); - String8 dst_folder = str8_chop_last_slash(dst); - String8List src_folders = str8_split_path(scratch.arena, src_folder); - String8List dst_folders = str8_split_path(scratch.arena, dst_folder); - - // rjf: count # of backtracks to get from src -> dest - U64 num_backtracks = src_folders.node_count; - for(String8Node *src_n = src_folders.first, *bp_n = dst_folders.first; - src_n != 0 && bp_n != 0; - src_n = src_n->next, bp_n = bp_n->next) - { - if(str8_match(src_n->string, bp_n->string, path_match_flags_from_os(operating_system_from_context()))) - { - num_backtracks -= 1; - } - else - { - break; - } - } - - // rjf: only build relative string if # of backtracks is not the entire `src`. - // if getting to `dst` from `src` requires erasing the entire `src`, then the - // only possible way to get to `dst` from `src` is via absolute path. - String8 dst_path = {0}; - if(num_backtracks >= src_folders.node_count) - { - dst_path = path_normalized_from_string(arena, dst); - } - else - { - // rjf: build backtrack parts - String8List dst_path_strs = {0}; - for(U64 idx = 0; idx < num_backtracks; idx += 1) - { - str8_list_push(scratch.arena, &dst_path_strs, str8_lit("..")); - } - - // rjf: build parts of dst which are unique from src - { - B32 unique_from_src = 0; - for(String8Node *src_n = src_folders.first, *bp_n = dst_folders.first; - bp_n != 0; - bp_n = bp_n->next) - { - if(!unique_from_src && (src_n == 0 || !str8_match(src_n->string, bp_n->string, path_match_flags_from_os(operating_system_from_context())))) - { - unique_from_src = 1; - } - if(unique_from_src) - { - str8_list_push(scratch.arena, &dst_path_strs, bp_n->string); - } - if(src_n != 0) - { - src_n = src_n->next; - } - } - } - - // rjf: build file name - str8_list_push(scratch.arena, &dst_path_strs, dst_name); - - // rjf: join - StringJoin join = {0}; - { - join.sep = str8_lit("/"); - } - dst_path = str8_list_join(arena, &dst_path_strs, &join); - } - scratch_end(scratch); - return dst_path; -} - -internal String8 -path_absolute_dst_from_relative_dst_src(Arena *arena, String8 dst, String8 src) -{ - String8 result = dst; - PathStyle dst_style = path_style_from_str8(dst); - if(dst_style == PathStyle_Relative) - { - Temp scratch = scratch_begin(&arena, 1); - String8 dst_from_src_absolute = push_str8f(scratch.arena, "%S/%S", src, dst); - String8 dst_from_src_absolute_normalized = path_normalized_from_string(arena, dst_from_src_absolute); - result = dst_from_src_absolute_normalized; - scratch_end(scratch); - } - return result; -} - -internal String8List -path_normalized_list_from_string(Arena *arena, String8 path_string, PathStyle *style_out){ - // analyze path - PathStyle path_style = path_style_from_str8(path_string); - String8List path = str8_split_path(arena, path_string); - - // prepend current path to convert relative -> absolute - PathStyle path_style_full = path_style; - if (path.node_count != 0 && path_style == PathStyle_Relative){ - String8List current_path_strs = {0}; - os_string_list_from_system_path(arena, OS_SystemPath_Current, ¤t_path_strs); - String8 current_path_string = str8_list_first(¤t_path_strs); - - PathStyle current_path_style = path_style_from_str8(current_path_string); - Assert(current_path_style != PathStyle_Relative); - - String8List current_path = str8_split_path(arena, current_path_string); - str8_list_concat_in_place(¤t_path, &path); - path = current_path; - path_style_full = current_path_style; - } - - // resolve dots - str8_path_list_resolve_dots_in_place(&path, path_style_full); - - // return - if (style_out != 0){ - *style_out = path_style_full; - } - return(path); -} - -internal String8 -path_normalized_from_string(Arena *arena, String8 path_string){ - Temp scratch = scratch_begin(&arena, 1); - - PathStyle style = PathStyle_Relative; - String8List path = path_normalized_list_from_string(scratch.arena, path_string, &style); - - String8 result = str8_path_list_join_by_style(arena, &path, style); - scratch_end(scratch); - return(result); -} - +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ allen: Path Helper Functions + +internal StringMatchFlags +path_match_flags_from_os(OperatingSystem os) +{ + StringMatchFlags flags = StringMatchFlag_SlashInsensitive; + switch(os) + { + default:{}break; + case OperatingSystem_Windows: + { + flags |= StringMatchFlag_CaseInsensitive; + }break; + case OperatingSystem_Linux: + case OperatingSystem_Mac: + { + // NOTE(rjf): no-op + }break; + } + return flags; +} + +internal String8 +path_relative_dst_from_absolute_dst_src(Arena *arena, String8 dst, String8 src) +{ + Temp scratch = scratch_begin(&arena, 1); + + // rjf: gather path parts + String8 dst_name = str8_skip_last_slash(dst); + String8 src_folder = str8_chop_last_slash(src); + String8 dst_folder = str8_chop_last_slash(dst); + String8List src_folders = str8_split_path(scratch.arena, src_folder); + String8List dst_folders = str8_split_path(scratch.arena, dst_folder); + + // rjf: count # of backtracks to get from src -> dest + U64 num_backtracks = src_folders.node_count; + for(String8Node *src_n = src_folders.first, *bp_n = dst_folders.first; + src_n != 0 && bp_n != 0; + src_n = src_n->next, bp_n = bp_n->next) + { + if(str8_match(src_n->string, bp_n->string, path_match_flags_from_os(operating_system_from_context()))) + { + num_backtracks -= 1; + } + else + { + break; + } + } + + // rjf: only build relative string if # of backtracks is not the entire `src`. + // if getting to `dst` from `src` requires erasing the entire `src`, then the + // only possible way to get to `dst` from `src` is via absolute path. + String8 dst_path = {0}; + if(num_backtracks >= src_folders.node_count) + { + dst_path = path_normalized_from_string(arena, dst); + } + else + { + // rjf: build backtrack parts + String8List dst_path_strs = {0}; + for(U64 idx = 0; idx < num_backtracks; idx += 1) + { + str8_list_push(scratch.arena, &dst_path_strs, str8_lit("..")); + } + + // rjf: build parts of dst which are unique from src + { + B32 unique_from_src = 0; + for(String8Node *src_n = src_folders.first, *bp_n = dst_folders.first; + bp_n != 0; + bp_n = bp_n->next) + { + if(!unique_from_src && (src_n == 0 || !str8_match(src_n->string, bp_n->string, path_match_flags_from_os(operating_system_from_context())))) + { + unique_from_src = 1; + } + if(unique_from_src) + { + str8_list_push(scratch.arena, &dst_path_strs, bp_n->string); + } + if(src_n != 0) + { + src_n = src_n->next; + } + } + } + + // rjf: build file name + str8_list_push(scratch.arena, &dst_path_strs, dst_name); + + // rjf: join + StringJoin join = {0}; + { + join.sep = str8_lit("/"); + } + dst_path = str8_list_join(arena, &dst_path_strs, &join); + } + scratch_end(scratch); + return dst_path; +} + +internal String8 +path_absolute_dst_from_relative_dst_src(Arena *arena, String8 dst, String8 src) +{ + String8 result = dst; + PathStyle dst_style = path_style_from_str8(dst); + if(dst_style == PathStyle_Relative) + { + Temp scratch = scratch_begin(&arena, 1); + String8 dst_from_src_absolute = push_str8f(scratch.arena, "%S/%S", src, dst); + String8 dst_from_src_absolute_normalized = path_normalized_from_string(arena, dst_from_src_absolute); + result = dst_from_src_absolute_normalized; + scratch_end(scratch); + } + return result; +} + +internal String8List +path_normalized_list_from_string(Arena *arena, String8 path_string, PathStyle *style_out){ + // analyze path + PathStyle path_style = path_style_from_str8(path_string); + String8List path = str8_split_path(arena, path_string); + + // prepend current path to convert relative -> absolute + PathStyle path_style_full = path_style; + if (path.node_count != 0 && path_style == PathStyle_Relative){ + String8 current_path_string = os_get_current_path(arena); + + PathStyle current_path_style = path_style_from_str8(current_path_string); + Assert(current_path_style != PathStyle_Relative); + + String8List current_path = str8_split_path(arena, current_path_string); + str8_list_concat_in_place(¤t_path, &path); + path = current_path; + path_style_full = current_path_style; + } + + // resolve dots + str8_path_list_resolve_dots_in_place(&path, path_style_full); + + // return + if (style_out != 0){ + *style_out = path_style_full; + } + return(path); +} + +internal String8 +path_normalized_from_string(Arena *arena, String8 path_string){ + Temp scratch = scratch_begin(&arena, 1); + + PathStyle style = PathStyle_Relative; + String8List path = path_normalized_list_from_string(scratch.arena, path_string, &style); + + String8 result = str8_path_list_join_by_style(arena, &path, style); + scratch_end(scratch); + return(result); +} + diff --git a/src/pdb/pdb.c b/src/pdb/pdb.c index c6eed0b4..01f8ca3a 100644 --- a/src/pdb/pdb.c +++ b/src/pdb/pdb.c @@ -1,1004 +1,1003 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ PDB Parser Functions - -internal PDB_Info* -pdb_info_from_data(Arena *arena, String8 data){ - ProfBegin("pdb_info_from_data"); - - // get header - PDB_InfoHeader *header = 0; - if (data.size >= sizeof(*header)){ - header = (PDB_InfoHeader*)data.str; - } - - PDB_Info *result = 0; - if (header != 0){ - // read guid - COFF_Guid *auth_guid = 0; - U32 after_auth_guid_off = sizeof(*header); - switch (header->version){ - case PDB_Version_VC70_DEP: - case PDB_Version_VC70: - case PDB_Version_VC80: - case PDB_Version_VC110: - case PDB_Version_VC140: - { - auth_guid = (COFF_Guid*)(data.str + after_auth_guid_off); - after_auth_guid_off = sizeof(*header) + sizeof(*auth_guid); - }break; - - default: - {}break; - } - - if (header->version != 0){ - // table layout: names - U32 names_len_off = after_auth_guid_off; - U32 names_len = 0; - if (names_len_off + 4 <= data.size){ - names_len = *(U32*)(data.str + names_len_off); - } - - U32 names_base_off = names_len_off + 4; - U32 names_base_opl = names_base_off + names_len; - - // table layout: hash table - U32 hash_table_count_off = names_base_opl; - U32 hash_table_max_off = hash_table_count_off + 4; - - U32 hash_table_count = 0; - U32 hash_table_max = 0; - if (hash_table_max_off + 4 <= data.size){ - hash_table_count = *(U32*)(data.str + hash_table_count_off); - hash_table_max = *(U32*)(data.str + hash_table_max_off); - } - - // table layout: words - U32 num_present_words_off = hash_table_max_off + 4; - U32 num_present_words = 0; - if (hash_table_max_off + 4 <= data.size){ - num_present_words = *(U32*)(data.str + num_present_words_off); - } - U32 present_words_array_off = num_present_words_off + 4; - - U32 num_deleted_words_off = present_words_array_off + num_present_words*sizeof(U32); - U32 num_deleted_words = 0; - if (num_deleted_words_off + 4 <= data.size){ - num_deleted_words = *(U32*)(data.str + num_deleted_words_off); - } - U32 deleted_words_array_off = num_deleted_words_off + 4; - - // table layout: epilogue - U32 epilogue_base_off = deleted_words_array_off + num_deleted_words*sizeof(U32); - - // read table - if (hash_table_count > 0 && epilogue_base_off <= data.size){ - PDB_InfoNode *first = 0; - PDB_InfoNode *last = 0; - - U32 record_off = epilogue_base_off; - for (U32 i = 0; i < hash_table_count; i += 1, record_off += 8){ - U32 *record = (U32*)(data.str + record_off); - U32 relative_name_off = record[0]; - MSF_StreamNumber sn = (MSF_StreamNumber)record[1]; - - U32 name_off = names_base_off + relative_name_off; - String8 name = str8_cstring_capped((char*)(data.str + name_off), - (char*)(data.str + names_base_opl)); - - // push info node - PDB_InfoNode *node = push_array(arena, PDB_InfoNode, 1); - SLLQueuePush(first, last, node); - node->string = name; - node->sn = sn; - } - - result = push_array(arena, PDB_Info, 1); - result->first = first; - result->last = last; - result->auth_guid = *auth_guid; - } - - } - } - - ProfEnd(); - - return(result); -} - -internal PDB_NamedStreamTable* -pdb_named_stream_table_from_info(Arena *arena, PDB_Info *info){ - ProfBegin("pdb_named_stream_table_from_info"); - - // mapping "NamedStream" indexes to strings - struct StreamNameIndexPair{ - PDB_NamedStream index; - String8 name; - }; - struct StreamNameIndexPair pairs[] = { - {PDB_NamedStream_HEADER_BLOCK, str8_lit("/src/headerblock")}, - {PDB_NamedStream_STRTABLE , str8_lit("/names")}, - {PDB_NamedStream_LINK_INFO , str8_lit("/LinkInfo")}, - }; - - // build baked table - PDB_NamedStreamTable *result = push_array(arena, PDB_NamedStreamTable, 1); - struct StreamNameIndexPair *p = pairs; - for (U64 i = 0; i < ArrayCount(pairs); i += 1, p += 1){ - String8 name = p->name; - - // get info node with this name - PDB_InfoNode *match = 0; - for (PDB_InfoNode *node = info->first; - node != 0; - node = node->next){ - if (str8_match(name, node->string, 0)){ - match = node; - break; - } - } - - // if match found save stream number - if (match != 0){ - result->sn[p->index] = match->sn; - } - else{ - result->sn[p->index] = 0xFFFF; - } - } - - ProfEnd(); - - return(result); -} - -internal PDB_Strtbl* -pdb_strtbl_from_data(Arena *arena, String8 data){ - ProfBegin("pdb_strtbl_from_data"); - - // get header - PDB_StrtblHeader *header = 0; - if (sizeof(*header) <= data.size){ - header = (PDB_StrtblHeader*)data.str; - } - - PDB_Strtbl *result = 0; - if (header != 0 && header->magic == PDB_StrtblHeader_MAGIC && header->version == 1){ - U32 strblock_size_off = sizeof(*header); - U32 strblock_size = 0; - if (strblock_size_off + 4 <= data.size){ - strblock_size = *(U32*)(data.str + strblock_size_off); - } - U32 strblock_off = strblock_size_off + 4; - - U32 bucket_count_off = strblock_off + strblock_size; - U32 bucket_count = 0; - if (bucket_count_off + 4 <= data.size){ - bucket_count = *(U32*)(data.str + bucket_count_off); - } - - U32 bucket_array_off = bucket_count_off + 4; - U32 bucket_array_size = bucket_count*sizeof(PDB_StringIndex); - - if (bucket_array_off + bucket_array_size <= data.size){ - result = push_array(arena, PDB_Strtbl, 1); - result->data = data; - result->bucket_count = bucket_count; - result->strblock_min = strblock_off; - result->strblock_max = strblock_off + strblock_size; - result->buckets_min = bucket_array_off; - result->buckets_max = bucket_array_off + bucket_array_size; - } - } - - ProfEnd(); - - return(result); -} - -internal PDB_DbiParsed* -pdb_dbi_from_data(Arena *arena, String8 data){ - ProfBegin("pdb_dbi_from_data"); - - // get header - PDB_DbiHeader *header = 0; - if (sizeof(*header) <= data.size){ - header = (PDB_DbiHeader*)data.str; - } - - PDB_DbiParsed *result = 0; - if (header != 0 && header->sig == PDB_DbiHeaderSignature_V1){ - // extract range sizes - U64 range_size[PDB_DbiRange_COUNT]; - range_size[PDB_DbiRange_ModuleInfo] = header->module_info_size; - range_size[PDB_DbiRange_SecCon] = header->sec_con_size; - range_size[PDB_DbiRange_SecMap] = header->sec_map_size; - range_size[PDB_DbiRange_FileInfo] = header->file_info_size; - range_size[PDB_DbiRange_TSM] = header->tsm_size; - range_size[PDB_DbiRange_EcInfo] = header->ec_info_size; - range_size[PDB_DbiRange_DbgHeader] = header->dbg_header_size; - - // fill result - result = push_array(arena, PDB_DbiParsed, 1); - result->data = data; - result->machine_type = header->machine; - result->gsi_sn = header->gsi_sn; - result->psi_sn = header->psi_sn; - result->sym_sn = header->sym_sn; - - - // fill result's range offsets - { - U64 cursor = sizeof(*header); - for (U64 i = 0; i < (U64)(PDB_DbiRange_COUNT); i += 1){ - result->range_off[i] = cursor; - cursor += range_size[i]; - cursor = ClampTop(cursor, data.size); - } - result->range_off[PDB_DbiRange_COUNT] = cursor; - } - - // fill result's debug streams - U64 dbg_streams_min = result->range_off[PDB_DbiRange_DbgHeader]; - U64 dbg_streams_max = result->range_off[PDB_DbiRange_DbgHeader + 1]; - U64 dbg_streams_size_raw = dbg_streams_max - dbg_streams_min; - U64 dbg_streams_size = ClampTop(dbg_streams_size_raw, sizeof(result->dbg_streams)); - MemoryCopy(result->dbg_streams, data.str + dbg_streams_min, dbg_streams_size); - if (dbg_streams_size < sizeof(result->dbg_streams)){ - U64 filled_count = dbg_streams_size/sizeof(MSF_StreamNumber); - MemorySet(result->dbg_streams + filled_count, 0xff, - (ArrayCount(result->dbg_streams) - filled_count)*sizeof(MSF_StreamNumber)); - } - } - - ProfEnd(); - - return(result); -} - -internal PDB_TpiParsed* -pdb_tpi_from_data(Arena *arena, String8 data){ - ProfBegin("pdb_tpi_from_data"); - - // get header - PDB_TpiHeader *header = 0; - if (sizeof(*header) <= data.size){ - header = (PDB_TpiHeader*)data.str; - } - - PDB_TpiParsed *result = 0; - if (header != 0 && header->version == PDB_TpiVersion_IMPV80){ - U64 leaf_first_raw = header->header_size; - U64 leaf_first = ClampTop(leaf_first_raw, data.size); - U64 leaf_opl_raw = leaf_first + header->leaf_data_size; - U64 leaf_opl = ClampTop(leaf_opl_raw, data.size); - - result = push_array(arena, PDB_TpiParsed, 1); - result->data = data; - - result->leaf_first = leaf_first; - result->leaf_opl = leaf_opl; - result->itype_first = header->ti_lo; - result->itype_opl = header->ti_hi; - - result->hash_sn = header->hash_sn; - result->hash_sn_aux = header->hash_sn_aux; - result->hash_key_size = header->hash_key_size; - result->hash_bucket_count = header->hash_bucket_count; - result->hash_vals_off = header->hash_vals_off; - result->hash_vals_size = header->hash_vals_size; - result->itype_off = header->itype_off; - result->itype_size = header->itype_size; - result->hash_adj_off = header->hash_adj_off; - result->hash_adj_size = header->hash_adj_size; - } - - ProfEnd(); - - return(result); -} - -internal PDB_TpiHashParsed* -pdb_tpi_hash_from_data(Arena *arena, PDB_Strtbl *strtbl, PDB_TpiParsed *tpi, String8 data, String8 aux_data){ - ProfBegin("pdb_tpi_hash_from_data"); - - PDB_TpiHashParsed *result = 0; - - U32 stride = tpi->hash_key_size; - U32 bucket_count = tpi->hash_bucket_count; - if (1 <= stride && stride <= 8 && bucket_count > 0 && data.str != 0){ - - // allocate buckets - PDB_TpiHashBlock **buckets = push_array(arena, PDB_TpiHashBlock*, bucket_count); - - // extract "hash" array - U8 *hashes = data.str + tpi->hash_vals_off; - U8 *hash_opl = hashes + tpi->hash_vals_size; - - // for each index in the array... - CV_TypeId itype = tpi->itype_first; - U8 *hash_cursor = hashes; - for (;hash_cursor + stride <= hash_opl;){ - - // read index - U64 bucket_idx = 0; - MemoryCopy(&bucket_idx, hash_cursor, stride); - - // save to map - if (bucket_idx < bucket_count){ - PDB_TpiHashBlock *block = buckets[bucket_idx]; - if (block == 0 || block->local_count == ArrayCount(block->itypes)){ - block = push_array(arena, PDB_TpiHashBlock, 1); - SLLStackPush(buckets[bucket_idx], block); - } - if(block->local_count != 0) - { - MemoryCopy(block->itypes+1, block->itypes, sizeof(CV_TypeId)*block->local_count); - } - block->itypes[0] = itype; - block->local_count += 1; - } - - // advance cursor - hash_cursor += stride; - itype += 1; - } - - //- rjf: compute bucket mask - U32 bucket_mask = 0; - if(IsPow2OrZero(bucket_count)) - { - bucket_mask = bucket_count-1; - } - - //- rjf: apply hash adjustments, to pull correct type IDs to the front of - // the chains - if(tpi->hash_adj_size != 0) - { - // NOTE(rjf): this table is laid out in the following format: - // - // pair_count: U32 -> # of name_index/type_index pairs - // slot_count: U32 -> # of slots in this hash table - // present_bit_array_count: U32 -> count for next array - // present_bit_array: U32[present_bit_array_count] -> 1 bit per slot, "is present" - // deleted_bit_array_count: U32 -> count for next array - // deleted_bit_array: U32[deleted_bit_array_count] -> 1 bit per slot, "is deleted" - // (U32, U32)[pair_count] -> array of name_index/type_index pairs - // - U8 *adjs = data.str + tpi->hash_adj_off; - U8 *adjs_opl = adjs + tpi->hash_adj_size; - U8 *adjs_cursor = adjs; - U32 pair_count = *(U32 *)adjs_cursor; - adjs_cursor += sizeof(U32); - U32 slot_count = *(U32 *)adjs_cursor; - adjs_cursor += sizeof(U32); - U32 present_bit_array_count = *(U32 *)adjs_cursor; // skip present_bit_array - adjs_cursor += sizeof(U32); - adjs_cursor += present_bit_array_count*sizeof(U32); - U32 deleted_bit_array_count = *(U32 *)adjs_cursor; // skip deleted_bit_array - adjs_cursor += sizeof(U32); - adjs_cursor += deleted_bit_array_count*sizeof(U32); - U32 adjs_stride = sizeof(U32)*2; - U32 pair_idx = 0; - for(;adjs_cursor < adjs_opl && pair_idx < pair_count; - adjs_cursor += adjs_stride, pair_idx += 1) - { - U32 name_off = ((U32 *)adjs_cursor)[0]; - CV_TypeId type_id = ((CV_TypeId *)adjs_cursor)[1]; - String8 string = pdb_strtbl_string_from_off(strtbl, name_off); - U32 hash = pdb_string_hash1(string); - U32 bucket_idx = ((bucket_mask != 0) ? hash&bucket_mask : hash%bucket_count); - PDB_TpiHashBlock *prev_block = 0; - for(PDB_TpiHashBlock *block = buckets[bucket_idx]; - block != 0; - prev_block = block, block = block->next) - { - for(U32 local_idx = 0; - local_idx < block->local_count && local_idx < ArrayCount(block->itypes); - local_idx += 1) - { - if(block->itypes[local_idx] == type_id) - { - if(prev_block != 0) - { - prev_block->next = block->next; - block->next = buckets[bucket_idx]; - buckets[bucket_idx] = block; - } - if(local_idx != 0) - { - Swap(CV_TypeId, block->itypes[0], block->itypes[local_idx]); - } - break; - } - } - } - } - } - - // fill result - result = push_array(arena, PDB_TpiHashParsed, 1); - result->data = data; - result->aux_data = aux_data; - result->buckets = buckets; - result->bucket_count = bucket_count; - result->bucket_mask = bucket_mask; - } - - ProfEnd(); - - return(result); -} - -internal PDB_GsiParsed* -pdb_gsi_from_data(Arena *arena, String8 data){ - ProfBegin("pdb_gsi_from_data"); - - // get header - PDB_GsiHeader *header = 0; - if (sizeof(*header) <= data.size){ - header = (PDB_GsiHeader*)data.str; - } - - PDB_GsiParsed *result = 0; - if (header != 0 && header->signature == PDB_GsiSignature_Basic && - header->version == PDB_GsiVersion_V70 && header->num_buckets != 0){ - Temp scratch = scratch_begin(&arena, 1); - - // hash offset - U32 hash_record_array_off = sizeof(*header); - - // bucket count - U32 slot_count = 4097; - - // array offsets - U32 bitmask_u32_count = CeilIntegerDiv(slot_count, 32); - U32 bitmask_byte_size = bitmask_u32_count*4; - U32 bitmask_off = hash_record_array_off + header->hr_len; - U32 offsets_off = bitmask_off + bitmask_byte_size; - - // get bitmask & packed offset arrays - U8 *bitmasks = 0; - U8 *packed_offsets = 0; - if (bitmask_off + bitmask_byte_size <= data.size){ - bitmasks = (data.str + bitmask_off); - packed_offsets = (data.str + offsets_off); - } - U32 packed_offset_count = (data.size - offsets_off)/4; - - // unpack - U32 *unpacked_offsets = 0; - if (packed_offsets != 0){ - unpacked_offsets = push_array(scratch.arena, U32, slot_count); - - U32 *bitmask_ptr = (U32*)bitmasks; - U32 *bitmask_opl = bitmask_ptr + bitmask_u32_count; - U32 *src_ptr = (U32*)packed_offsets; - U32 *src_opl = src_ptr + packed_offset_count; - U32 *dst_ptr = unpacked_offsets; - U32 *dst_opl = dst_ptr + slot_count; - for (; bitmask_ptr < bitmask_opl && src_ptr < src_opl; bitmask_ptr += 1){ - U32 bits = *bitmask_ptr; - U32 src_max = (U32)(src_opl - src_ptr); - U32 dst_max = (U32)(dst_opl - dst_ptr); - U32 k_max0 = ClampTop(32, dst_max); - U32 k_max = ClampTop(k_max0, src_max); - for (U32 k = 0; k < k_max; k += 1){ - if ((bits & 1) == 1){ - *dst_ptr = *src_ptr; - src_ptr += 1; - } - else{ - *dst_ptr = 0xFFFFFFFF; - } - dst_ptr += 1; - bits >>= 1; - } - } - for (; dst_ptr < dst_opl; dst_ptr += 1){ - *dst_ptr = 0xFFFFFFFF; - } - } - - // construct table - B32 bad_table = 0; - if (unpacked_offsets != 0){ - result = push_array(arena, PDB_GsiParsed, 1); - - // hash records - PDB_GsiHashRecord *hash_records = (PDB_GsiHashRecord*)(data.str + hash_record_array_off); - U32 hash_record_count = header->hr_len/sizeof(PDB_GsiHashRecord); - - // * We unpack hash records into the the table by scanning backwards through the - // * hash records. Neighboring values in unpacked_offsets *sort of* form counts, but we - // * have to skip the max-U32s (sloppy PDB nonsense). - - // * PDBs put one extra slot at the beginning of the encoded buckets that is mean - // * to be padding for modifying the buffer in place. After decoding there are 4096 buckets, - // * in the encoded buckets there are 4097. We are meant to drop the first one. - - // build table - PDB_GsiHashRecord *hash_record_ptr = hash_records + hash_record_count - 1; - U32 prev_n = hash_record_count; - for (U32 i = slot_count; i > 1;){ - i -= 1; - if (unpacked_offsets[i] != 0xFFFFFFFF){ - // determine hash record range to use - // * The "12" here is the result of some really sloppy PDB magic. - U32 n = unpacked_offsets[i]/12; - if (n > prev_n){ - bad_table = 1; - break; - } - U32 num_steps = prev_n - n; - - // fill this bucket - arena_push_align(arena, 4); - U32 *bucket_offs = push_array_no_zero(arena, U32, num_steps); - for (U32 j = num_steps; j > 0;){ - j -= 1; - // * The "- 1" is more sloppy PDB magic. - bucket_offs[j] = hash_record_ptr->symbol_off - 1; - hash_record_ptr -= 1; - } - PDB_GsiBucket *bucket = &result->buckets[i - 1]; - bucket->count = num_steps; - bucket->offs = bucket_offs; - - // update prev_n - prev_n = n; - } - } - } - - scratch_end(scratch); - } - - ProfEnd(); - - return(result); -} - -internal PDB_CoffSectionArray* -pdb_coff_section_array_from_data(Arena *arena, String8 data){ - U64 count = data.size/sizeof(COFF_SectionHeader); - - PDB_CoffSectionArray *result = push_array(arena, PDB_CoffSectionArray, 1); - result->sections = (COFF_SectionHeader*)data.str; - result->count = count; - return(result); -} - -internal PDB_CompUnitArray* -pdb_comp_unit_array_from_data(Arena *arena, String8 data){ - PDB_CompUnitNode *first = 0; - PDB_CompUnitNode *last = 0; - U64 count = 0; - - U64 cursor = 0; - for (;cursor + sizeof(PDB_DbiCompUnitHeader) <= data.size;){ - // get header - PDB_DbiCompUnitHeader *header = (PDB_DbiCompUnitHeader*)(data.str + cursor); - - // get names - U64 name_off = cursor + sizeof(*header); - String8 name = str8_cstring_capped((char *)(data.str + name_off), (char *)(data.str + data.size)); - - U64 name2_off = name_off + name.size + 1; - String8 name2 = str8_cstring_capped((char *)(data.str + name2_off), (char *)(data.str + data.size)); - - U64 after_name2_off = name2_off + name2.size + 1; - - // save mod info - PDB_CompUnitNode *node = push_array_no_zero(arena, PDB_CompUnitNode, 1); - SLLQueuePush(first, last, node); - count += 1; - node->unit.sn = header->sn; - node->unit.obj_name = name; - node->unit.group_name = name2; - - // fill range offsets - U32 *range_buf = node->unit.range_off; - { - // fill the buffer with size of each range - range_buf[PDB_DbiCompUnitRange_Symbols] = header->symbols_size; - range_buf[PDB_DbiCompUnitRange_C11] = header->c11_lines_size; - range_buf[PDB_DbiCompUnitRange_C13] = header->c13_lines_size; - Assert(PDB_DbiCompUnitRange_C13 + 1 == PDB_DbiCompUnitRange_COUNT); - - // in-place sizes -> offs conversion - U64 i = 0; - U32 range_cursor = 0; - for (; i < (U64)(PDB_DbiCompUnitRange_COUNT); i += 1){ - U64 adv = range_buf[i]; - range_buf[i] = range_cursor; - range_cursor += adv; - } - range_buf[i] = range_cursor; - - // skip 4 byte signature in symbols range - if (range_buf[1] >= 4){ - range_buf[0] += 4; - } - } - - // update cursor - cursor = AlignPow2(after_name2_off, 4); - } - - - // fill result - PDB_CompUnit **units = push_array_no_zero(arena, PDB_CompUnit*, count); - { - U64 idx = 0; - for (PDB_CompUnitNode *node = first; - node != 0; - node = node->next, idx += 1){ - units[idx] = &node->unit; - } - } - - PDB_CompUnitArray *result = push_array(arena, PDB_CompUnitArray, 1); - result->units = units; - result->count = count; - - return(result); -} - -internal PDB_CompUnitContributionArray* -pdb_comp_unit_contribution_array_from_data(Arena *arena, String8 data, - PDB_CoffSectionArray *sections){ - PDB_CompUnitContribution *contributions = 0; - U64 count = 0; - if (data.size >= sizeof(PDB_DbiSectionContribVersion)){ - PDB_DbiSectionContribVersion *version = (PDB_DbiSectionContribVersion*)data.str; - - // determine array layout from version - U32 item_size = 0; - U32 array_off = 0; - switch (*version){ - default: - { - // TODO(allen): do we have a test case for this? - item_size = sizeof(PDB_DbiSectionContrib40); - }break; - case PDB_DbiSectionContribVersion_1: - { - item_size = sizeof(PDB_DbiSectionContrib); - array_off = sizeof(*version); - }break; - case PDB_DbiSectionContribVersion_2: - { - item_size = sizeof(PDB_DbiSectionContrib2); - array_off = sizeof(*version); - }break; - } - - // allocate ranges - U64 max_count = (data.size - array_off)/item_size; - contributions = push_array_no_zero(arena, PDB_CompUnitContribution, max_count); - - // binary section info - U64 section_count = sections->count; - COFF_SectionHeader* section_headers = sections->sections; - - // fill array - PDB_CompUnitContribution *contribution_ptr = contributions; - U64 cursor = array_off; - for (; cursor + item_size <= data.size; cursor += item_size){ - PDB_DbiSectionContrib40 *sc = (PDB_DbiSectionContrib40*)(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; - - contribution_ptr->mod = sc->mod; - contribution_ptr->voff_first = voff; - contribution_ptr->voff_opl = voff + sc->size; - contribution_ptr += 1; - } - } - count = (U64)(contribution_ptr - contributions); - } - - // fill result - PDB_CompUnitContributionArray *result = push_array(arena, PDB_CompUnitContributionArray, 1); - result->contributions = contributions; - result->count = count; - - return(result); -} - - -//////////////////////////////// -//~ PDB Definition Functions - -internal U32 -pdb_string_hash1(String8 string){ - U32 result = 0; - U8 *ptr = string.str; - U8 *opl = ptr + (string.size&(~3)); - for (; ptr < opl; ptr += 4){ result ^= *(U32*)ptr; } - if ((string.size&2) != 0){ result ^= *(U16*)ptr; ptr += 2; } - if ((string.size&1) != 0){ result ^= *ptr; } - result |= 0x20202020; - result ^= (result >> 11); - result ^= (result >> 16); - return(result); -} - - -//////////////////////////////// -//~ PDB Dbi Functions - -internal String8 -pdb_data_from_dbi_range(PDB_DbiParsed *dbi, PDB_DbiRange range){ - String8 result = {0}; - if (range < PDB_DbiRange_COUNT){ - U64 first = dbi->range_off[range]; - U64 opl = dbi->range_off[range + 1]; - result.str = dbi->data.str + first; - result.size = opl - first; - } - return(result); -} - -internal String8 -pdb_data_from_unit_range(MSF_Parsed *msf, PDB_CompUnit *unit, PDB_DbiCompUnitRange range){ - String8 result = {0}; - if (range < PDB_DbiCompUnitRange_COUNT){ - String8 full_stream_data = msf_data_from_stream(msf, unit->sn); - - U64 first_raw = unit->range_off[range]; - U64 opl_raw = unit->range_off[range + 1]; - U64 opl = ClampTop(opl_raw, full_stream_data.size); - U64 first = ClampTop(first_raw, opl); - - result.str = full_stream_data.str + first; - result.size = opl - first; - } - return(result); -} - -//////////////////////////////// -//~ PDB Tpi Functions - -internal String8 -pdb_leaf_data_from_tpi(PDB_TpiParsed *tpi){ - String8 data = tpi->data; - U8 *first = data.str + tpi->leaf_first; - U8 *opl = data.str + tpi->leaf_opl; - String8 result = str8_range(first, opl); - return(result); -} - -internal CV_TypeIdArray -pdb_tpi_itypes_from_name(Arena *arena, PDB_TpiHashParsed *tpi_hash, CV_LeafParsed *leaf, - String8 name, B32 compare_unique_name, U32 output_cap){ - U32 hash = pdb_string_hash1(name); - U32 bucket_idx = ((tpi_hash->bucket_mask != 0) ? - hash&tpi_hash->bucket_mask : - hash%tpi_hash->bucket_count); - - CV_TypeId itype_first = leaf->itype_first; - CV_TypeId itype_opl = leaf->itype_opl; - String8 data = leaf->data; - - Temp scratch = scratch_begin(&arena, 1); - struct Chain{ - struct Chain *next; - CV_TypeId itype; - }; - struct Chain *first = 0; - struct Chain *last = 0; - U32 count = 0; - - for (PDB_TpiHashBlock *block = tpi_hash->buckets[bucket_idx]; - block != 0; - block = block->next){ - U32 local_count = block->local_count; - CV_TypeId *itype_ptr = block->itypes; - for (U32 i = 0; i < local_count; i += 1, itype_ptr += 1){ - - String8 extracted_name = {0}; - - CV_TypeId itype = *itype_ptr; - if (itype_first <= itype && itype < itype_opl){ - CV_RecRange *range = &leaf->leaf_ranges.ranges[itype - leaf->itype_first]; - if (range->off + range->hdr.size <= data.size){ - U8 *first = data.str + range->off + 2; - U64 cap = range->hdr.size - 2; - - switch (range->hdr.kind){ - default:break; - - case CV_LeafKind_CLASS: - case CV_LeafKind_STRUCTURE: - { - if (sizeof(CV_LeafStruct) <= cap){ - CV_LeafStruct *lf_struct = (CV_LeafStruct*)first; - - if (!(lf_struct->props & CV_TypeProp_FwdRef)){ - // size - U8 *numeric_ptr = (U8*)(lf_struct + 1); - CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, first + cap); - - // name - U8 *name_ptr = numeric_ptr + size.encoded_size; - String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); - - // unique name - if (compare_unique_name){ - if (lf_struct->props & CV_TypeProp_HasUniqueName) { - U8 *unique_name_ptr = name_ptr + name.size + 1; - String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); - extracted_name = unique_name; - } - } - else{ - extracted_name = name; - } - } - } - }break; - - case CV_LeafKind_CLASS2: - case CV_LeafKind_STRUCT2: - { - if (sizeof(CV_LeafStruct2) <= cap){ - CV_LeafStruct2 *lf_struct = (CV_LeafStruct2*)first; - - if (!(lf_struct->props & CV_TypeProp_FwdRef)){ - // size - U8 *numeric_ptr = (U8*)(lf_struct + 1); - CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, first + cap); - - // name - U8 *name_ptr = numeric_ptr + size.encoded_size; - String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); - - // unique name - if (compare_unique_name){ - if (lf_struct->props & CV_TypeProp_HasUniqueName) { - U8 *unique_name_ptr = name_ptr + name.size + 1; - String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); - extracted_name = unique_name; - } - } - else{ - extracted_name = name; - } - } - } - }break; - - case CV_LeafKind_UNION: - { - if (sizeof(CV_LeafUnion) <= cap){ - CV_LeafUnion *lf_union = (CV_LeafUnion*)first; - - if (!(lf_union->props & CV_TypeProp_FwdRef)){ - // size - U8 *numeric_ptr = (U8*)(lf_union + 1); - CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, first + cap); - - // name - U8 *name_ptr = numeric_ptr + size.encoded_size; - String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); - - // unique name - if (compare_unique_name){ - if (lf_union->props & CV_TypeProp_HasUniqueName) { - U8 *unique_name_ptr = name_ptr + name.size + 1; - String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); - extracted_name = unique_name; - } - } - else{ - extracted_name = name; - } - } - } - }break; - - case CV_LeafKind_ENUM: - { - if (sizeof(CV_LeafEnum) <= cap){ - CV_LeafEnum *lf_enum = (CV_LeafEnum*)first; - - if (!(lf_enum->props & CV_TypeProp_FwdRef)){ - // name - U8 *name_ptr = (U8*)(lf_enum + 1); - String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); - - // unique name - if (compare_unique_name){ - if (lf_enum->props & CV_TypeProp_HasUniqueName) { - U8 *unique_name_ptr = name_ptr + name.size + 1; - String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); - extracted_name = unique_name; - } - } - else{ - extracted_name = name; - } - } - } - }break; - } - } - } - - if (str8_match(extracted_name, name, 0)){ - struct Chain *chain = push_array(scratch.arena, struct Chain, 1); - SLLQueuePush(first, last, chain); - count += 1; - chain->itype = itype; - if (count == output_cap){ - goto dblbreak; - } - } - } - } - - dblbreak:; - - - // assemble result - CV_TypeId *itypes = push_array(arena, CV_TypeId, count); - { - CV_TypeId *itype_ptr = itypes; - for (struct Chain *node = first; - node != 0; - node = node->next, itype_ptr += 1){ - *itype_ptr = node->itype; - } - } - CV_TypeIdArray result = {0}; - result.itypes = itypes; - result.count = count; - - scratch_end(scratch); - - return(result); -} - -internal CV_TypeId -pdb_tpi_first_itype_from_name(PDB_TpiHashParsed *tpi_hash, CV_LeafParsed *tpi_leaf, - String8 name, B32 compare_unique_name){ - Temp scratch = scratch_begin(0, 0); - CV_TypeIdArray array = pdb_tpi_itypes_from_name(scratch.arena, tpi_hash, tpi_leaf, - name, compare_unique_name, 1); - CV_TypeId result = 0; - if (array.count > 0){ - result = array.itypes[0]; - } - - scratch_end(scratch); - return(result); -} - -//////////////////////////////// -//~ PDB Strtbl Functions - -internal String8 -pdb_strtbl_string_from_off(PDB_Strtbl *strtbl, U32 off){ - U32 strblock_max = strtbl->strblock_max; - U32 full_off_raw = strtbl->strblock_min + off; - U32 full_off = ClampTop(full_off_raw, strblock_max); - String8 result = str8_cstring_capped((char*)(strtbl->data.str + full_off), - (char*)(strtbl->data.str + strblock_max)); - return(result); -} - -internal String8 -pdb_strtbl_string_from_index(PDB_Strtbl *strtbl, PDB_StringIndex idx){ - String8 result = {0}; - if (idx < strtbl->bucket_count){ - U32 off = *(U32*)(strtbl->data.str + strtbl->buckets_min + idx*4); - result = pdb_strtbl_string_from_off(strtbl, off); - } - return(result); -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ PDB Parser Functions + +internal PDB_Info* +pdb_info_from_data(Arena *arena, String8 data){ + ProfBegin("pdb_info_from_data"); + + // get header + PDB_InfoHeader *header = 0; + if (data.size >= sizeof(*header)){ + header = (PDB_InfoHeader*)data.str; + } + + PDB_Info *result = 0; + if (header != 0){ + // read guid + COFF_Guid *auth_guid = 0; + U32 after_auth_guid_off = sizeof(*header); + switch (header->version){ + case PDB_Version_VC70_DEP: + case PDB_Version_VC70: + case PDB_Version_VC80: + case PDB_Version_VC110: + case PDB_Version_VC140: + { + auth_guid = (COFF_Guid*)(data.str + after_auth_guid_off); + after_auth_guid_off = sizeof(*header) + sizeof(*auth_guid); + }break; + + default: + {}break; + } + + if (header->version != 0){ + // table layout: names + U32 names_len_off = after_auth_guid_off; + U32 names_len = 0; + if (names_len_off + 4 <= data.size){ + names_len = *(U32*)(data.str + names_len_off); + } + + U32 names_base_off = names_len_off + 4; + U32 names_base_opl = names_base_off + names_len; + + // table layout: hash table + U32 hash_table_count_off = names_base_opl; + U32 hash_table_max_off = hash_table_count_off + 4; + + U32 hash_table_count = 0; + U32 hash_table_max = 0; + if (hash_table_max_off + 4 <= data.size){ + hash_table_count = *(U32*)(data.str + hash_table_count_off); + hash_table_max = *(U32*)(data.str + hash_table_max_off); + } + + // table layout: words + U32 num_present_words_off = hash_table_max_off + 4; + U32 num_present_words = 0; + if (hash_table_max_off + 4 <= data.size){ + num_present_words = *(U32*)(data.str + num_present_words_off); + } + U32 present_words_array_off = num_present_words_off + 4; + + U32 num_deleted_words_off = present_words_array_off + num_present_words*sizeof(U32); + U32 num_deleted_words = 0; + if (num_deleted_words_off + 4 <= data.size){ + num_deleted_words = *(U32*)(data.str + num_deleted_words_off); + } + U32 deleted_words_array_off = num_deleted_words_off + 4; + + // table layout: epilogue + U32 epilogue_base_off = deleted_words_array_off + num_deleted_words*sizeof(U32); + + // read table + if (hash_table_count > 0 && epilogue_base_off <= data.size){ + PDB_InfoNode *first = 0; + PDB_InfoNode *last = 0; + + U32 record_off = epilogue_base_off; + for (U32 i = 0; i < hash_table_count; i += 1, record_off += 8){ + U32 *record = (U32*)(data.str + record_off); + U32 relative_name_off = record[0]; + MSF_StreamNumber sn = (MSF_StreamNumber)record[1]; + + U32 name_off = names_base_off + relative_name_off; + String8 name = str8_cstring_capped((char*)(data.str + name_off), + (char*)(data.str + names_base_opl)); + + // push info node + PDB_InfoNode *node = push_array(arena, PDB_InfoNode, 1); + SLLQueuePush(first, last, node); + node->string = name; + node->sn = sn; + } + + result = push_array(arena, PDB_Info, 1); + result->first = first; + result->last = last; + result->auth_guid = *auth_guid; + } + + } + } + + ProfEnd(); + + return(result); +} + +internal PDB_NamedStreamTable* +pdb_named_stream_table_from_info(Arena *arena, PDB_Info *info){ + ProfBegin("pdb_named_stream_table_from_info"); + + // mapping "NamedStream" indexes to strings + struct StreamNameIndexPair{ + PDB_NamedStream index; + String8 name; + }; + struct StreamNameIndexPair pairs[] = { + {PDB_NamedStream_HEADER_BLOCK, str8_lit("/src/headerblock")}, + {PDB_NamedStream_STRTABLE , str8_lit("/names")}, + {PDB_NamedStream_LINK_INFO , str8_lit("/LinkInfo")}, + }; + + // build baked table + PDB_NamedStreamTable *result = push_array(arena, PDB_NamedStreamTable, 1); + struct StreamNameIndexPair *p = pairs; + for (U64 i = 0; i < ArrayCount(pairs); i += 1, p += 1){ + String8 name = p->name; + + // get info node with this name + PDB_InfoNode *match = 0; + for (PDB_InfoNode *node = info->first; + node != 0; + node = node->next){ + if (str8_match(name, node->string, 0)){ + match = node; + break; + } + } + + // if match found save stream number + if (match != 0){ + result->sn[p->index] = match->sn; + } + else{ + result->sn[p->index] = 0xFFFF; + } + } + + ProfEnd(); + + return(result); +} + +internal PDB_Strtbl* +pdb_strtbl_from_data(Arena *arena, String8 data){ + ProfBegin("pdb_strtbl_from_data"); + + // get header + PDB_StrtblHeader *header = 0; + if (sizeof(*header) <= data.size){ + header = (PDB_StrtblHeader*)data.str; + } + + PDB_Strtbl *result = 0; + if (header != 0 && header->magic == PDB_StrtblHeader_MAGIC && header->version == 1){ + U32 strblock_size_off = sizeof(*header); + U32 strblock_size = 0; + if (strblock_size_off + 4 <= data.size){ + strblock_size = *(U32*)(data.str + strblock_size_off); + } + U32 strblock_off = strblock_size_off + 4; + + U32 bucket_count_off = strblock_off + strblock_size; + U32 bucket_count = 0; + if (bucket_count_off + 4 <= data.size){ + bucket_count = *(U32*)(data.str + bucket_count_off); + } + + U32 bucket_array_off = bucket_count_off + 4; + U32 bucket_array_size = bucket_count*sizeof(PDB_StringIndex); + + if (bucket_array_off + bucket_array_size <= data.size){ + result = push_array(arena, PDB_Strtbl, 1); + result->data = data; + result->bucket_count = bucket_count; + result->strblock_min = strblock_off; + result->strblock_max = strblock_off + strblock_size; + result->buckets_min = bucket_array_off; + result->buckets_max = bucket_array_off + bucket_array_size; + } + } + + ProfEnd(); + + return(result); +} + +internal PDB_DbiParsed* +pdb_dbi_from_data(Arena *arena, String8 data){ + ProfBegin("pdb_dbi_from_data"); + + // get header + PDB_DbiHeader *header = 0; + if (sizeof(*header) <= data.size){ + header = (PDB_DbiHeader*)data.str; + } + + PDB_DbiParsed *result = 0; + if (header != 0 && header->sig == PDB_DbiHeaderSignature_V1){ + // extract range sizes + U64 range_size[PDB_DbiRange_COUNT]; + range_size[PDB_DbiRange_ModuleInfo] = header->module_info_size; + range_size[PDB_DbiRange_SecCon] = header->sec_con_size; + range_size[PDB_DbiRange_SecMap] = header->sec_map_size; + range_size[PDB_DbiRange_FileInfo] = header->file_info_size; + range_size[PDB_DbiRange_TSM] = header->tsm_size; + range_size[PDB_DbiRange_EcInfo] = header->ec_info_size; + range_size[PDB_DbiRange_DbgHeader] = header->dbg_header_size; + + // fill result + result = push_array(arena, PDB_DbiParsed, 1); + result->data = data; + result->machine_type = header->machine; + result->gsi_sn = header->gsi_sn; + result->psi_sn = header->psi_sn; + result->sym_sn = header->sym_sn; + + + // fill result's range offsets + { + U64 cursor = sizeof(*header); + for (U64 i = 0; i < (U64)(PDB_DbiRange_COUNT); i += 1){ + result->range_off[i] = cursor; + cursor += range_size[i]; + cursor = ClampTop(cursor, data.size); + } + result->range_off[PDB_DbiRange_COUNT] = cursor; + } + + // fill result's debug streams + U64 dbg_streams_min = result->range_off[PDB_DbiRange_DbgHeader]; + U64 dbg_streams_max = result->range_off[PDB_DbiRange_DbgHeader + 1]; + U64 dbg_streams_size_raw = dbg_streams_max - dbg_streams_min; + U64 dbg_streams_size = ClampTop(dbg_streams_size_raw, sizeof(result->dbg_streams)); + MemoryCopy(result->dbg_streams, data.str + dbg_streams_min, dbg_streams_size); + if (dbg_streams_size < sizeof(result->dbg_streams)){ + U64 filled_count = dbg_streams_size/sizeof(MSF_StreamNumber); + MemorySet(result->dbg_streams + filled_count, 0xff, + (ArrayCount(result->dbg_streams) - filled_count)*sizeof(MSF_StreamNumber)); + } + } + + ProfEnd(); + + return(result); +} + +internal PDB_TpiParsed* +pdb_tpi_from_data(Arena *arena, String8 data){ + ProfBegin("pdb_tpi_from_data"); + + // get header + PDB_TpiHeader *header = 0; + if (sizeof(*header) <= data.size){ + header = (PDB_TpiHeader*)data.str; + } + + PDB_TpiParsed *result = 0; + if (header != 0 && header->version == PDB_TpiVersion_IMPV80){ + U64 leaf_first_raw = header->header_size; + U64 leaf_first = ClampTop(leaf_first_raw, data.size); + U64 leaf_opl_raw = leaf_first + header->leaf_data_size; + U64 leaf_opl = ClampTop(leaf_opl_raw, data.size); + + result = push_array(arena, PDB_TpiParsed, 1); + result->data = data; + + result->leaf_first = leaf_first; + result->leaf_opl = leaf_opl; + result->itype_first = header->ti_lo; + result->itype_opl = header->ti_hi; + + result->hash_sn = header->hash_sn; + result->hash_sn_aux = header->hash_sn_aux; + result->hash_key_size = header->hash_key_size; + result->hash_bucket_count = header->hash_bucket_count; + result->hash_vals_off = header->hash_vals_off; + result->hash_vals_size = header->hash_vals_size; + result->itype_off = header->itype_off; + result->itype_size = header->itype_size; + result->hash_adj_off = header->hash_adj_off; + result->hash_adj_size = header->hash_adj_size; + } + + ProfEnd(); + + return(result); +} + +internal PDB_TpiHashParsed* +pdb_tpi_hash_from_data(Arena *arena, PDB_Strtbl *strtbl, PDB_TpiParsed *tpi, String8 data, String8 aux_data){ + ProfBegin("pdb_tpi_hash_from_data"); + + PDB_TpiHashParsed *result = 0; + + U32 stride = tpi->hash_key_size; + U32 bucket_count = tpi->hash_bucket_count; + if (1 <= stride && stride <= 8 && bucket_count > 0 && data.str != 0){ + + // allocate buckets + PDB_TpiHashBlock **buckets = push_array(arena, PDB_TpiHashBlock*, bucket_count); + + // extract "hash" array + U8 *hashes = data.str + tpi->hash_vals_off; + U8 *hash_opl = hashes + tpi->hash_vals_size; + + // for each index in the array... + CV_TypeId itype = tpi->itype_first; + U8 *hash_cursor = hashes; + for (;hash_cursor + stride <= hash_opl;){ + + // read index + U64 bucket_idx = 0; + MemoryCopy(&bucket_idx, hash_cursor, stride); + + // save to map + if (bucket_idx < bucket_count){ + PDB_TpiHashBlock *block = buckets[bucket_idx]; + if (block == 0 || block->local_count == ArrayCount(block->itypes)){ + block = push_array(arena, PDB_TpiHashBlock, 1); + SLLStackPush(buckets[bucket_idx], block); + } + if(block->local_count != 0) + { + MemoryCopy(block->itypes+1, block->itypes, sizeof(CV_TypeId)*block->local_count); + } + block->itypes[0] = itype; + block->local_count += 1; + } + + // advance cursor + hash_cursor += stride; + itype += 1; + } + + //- rjf: compute bucket mask + U32 bucket_mask = 0; + if(IsPow2OrZero(bucket_count)) + { + bucket_mask = bucket_count-1; + } + + //- rjf: apply hash adjustments, to pull correct type IDs to the front of + // the chains + if(tpi->hash_adj_size != 0) + { + // NOTE(rjf): this table is laid out in the following format: + // + // pair_count: U32 -> # of name_index/type_index pairs + // slot_count: U32 -> # of slots in this hash table + // present_bit_array_count: U32 -> count for next array + // present_bit_array: U32[present_bit_array_count] -> 1 bit per slot, "is present" + // deleted_bit_array_count: U32 -> count for next array + // deleted_bit_array: U32[deleted_bit_array_count] -> 1 bit per slot, "is deleted" + // (U32, U32)[pair_count] -> array of name_index/type_index pairs + // + U8 *adjs = data.str + tpi->hash_adj_off; + U8 *adjs_opl = adjs + tpi->hash_adj_size; + U8 *adjs_cursor = adjs; + U32 pair_count = *(U32 *)adjs_cursor; + adjs_cursor += sizeof(U32); + U32 slot_count = *(U32 *)adjs_cursor; + adjs_cursor += sizeof(U32); + U32 present_bit_array_count = *(U32 *)adjs_cursor; // skip present_bit_array + adjs_cursor += sizeof(U32); + adjs_cursor += present_bit_array_count*sizeof(U32); + U32 deleted_bit_array_count = *(U32 *)adjs_cursor; // skip deleted_bit_array + adjs_cursor += sizeof(U32); + adjs_cursor += deleted_bit_array_count*sizeof(U32); + U32 adjs_stride = sizeof(U32)*2; + U32 pair_idx = 0; + for(;adjs_cursor < adjs_opl && pair_idx < pair_count; + adjs_cursor += adjs_stride, pair_idx += 1) + { + U32 name_off = ((U32 *)adjs_cursor)[0]; + CV_TypeId type_id = ((CV_TypeId *)adjs_cursor)[1]; + String8 string = pdb_strtbl_string_from_off(strtbl, name_off); + U32 hash = pdb_string_hash1(string); + U32 bucket_idx = ((bucket_mask != 0) ? hash&bucket_mask : hash%bucket_count); + PDB_TpiHashBlock *prev_block = 0; + for(PDB_TpiHashBlock *block = buckets[bucket_idx]; + block != 0; + prev_block = block, block = block->next) + { + for(U32 local_idx = 0; + local_idx < block->local_count && local_idx < ArrayCount(block->itypes); + local_idx += 1) + { + if(block->itypes[local_idx] == type_id) + { + if(prev_block != 0) + { + prev_block->next = block->next; + block->next = buckets[bucket_idx]; + buckets[bucket_idx] = block; + } + if(local_idx != 0) + { + Swap(CV_TypeId, block->itypes[0], block->itypes[local_idx]); + } + break; + } + } + } + } + } + + // fill result + result = push_array(arena, PDB_TpiHashParsed, 1); + result->data = data; + result->aux_data = aux_data; + result->buckets = buckets; + result->bucket_count = bucket_count; + result->bucket_mask = bucket_mask; + } + + ProfEnd(); + + return(result); +} + +internal PDB_GsiParsed* +pdb_gsi_from_data(Arena *arena, String8 data){ + ProfBegin("pdb_gsi_from_data"); + + // get header + PDB_GsiHeader *header = 0; + if (sizeof(*header) <= data.size){ + header = (PDB_GsiHeader*)data.str; + } + + PDB_GsiParsed *result = 0; + if (header != 0 && header->signature == PDB_GsiSignature_Basic && + header->version == PDB_GsiVersion_V70 && header->num_buckets != 0){ + Temp scratch = scratch_begin(&arena, 1); + + // hash offset + U32 hash_record_array_off = sizeof(*header); + + // bucket count + U32 slot_count = 4097; + + // array offsets + U32 bitmask_u32_count = CeilIntegerDiv(slot_count, 32); + U32 bitmask_byte_size = bitmask_u32_count*4; + U32 bitmask_off = hash_record_array_off + header->hr_len; + U32 offsets_off = bitmask_off + bitmask_byte_size; + + // get bitmask & packed offset arrays + U8 *bitmasks = 0; + U8 *packed_offsets = 0; + if (bitmask_off + bitmask_byte_size <= data.size){ + bitmasks = (data.str + bitmask_off); + packed_offsets = (data.str + offsets_off); + } + U32 packed_offset_count = (data.size - offsets_off)/4; + + // unpack + U32 *unpacked_offsets = 0; + if (packed_offsets != 0){ + unpacked_offsets = push_array(scratch.arena, U32, slot_count); + + U32 *bitmask_ptr = (U32*)bitmasks; + U32 *bitmask_opl = bitmask_ptr + bitmask_u32_count; + U32 *src_ptr = (U32*)packed_offsets; + U32 *src_opl = src_ptr + packed_offset_count; + U32 *dst_ptr = unpacked_offsets; + U32 *dst_opl = dst_ptr + slot_count; + for (; bitmask_ptr < bitmask_opl && src_ptr < src_opl; bitmask_ptr += 1){ + U32 bits = *bitmask_ptr; + U32 src_max = (U32)(src_opl - src_ptr); + U32 dst_max = (U32)(dst_opl - dst_ptr); + U32 k_max0 = ClampTop(32, dst_max); + U32 k_max = ClampTop(k_max0, src_max); + for (U32 k = 0; k < k_max; k += 1){ + if ((bits & 1) == 1){ + *dst_ptr = *src_ptr; + src_ptr += 1; + } + else{ + *dst_ptr = 0xFFFFFFFF; + } + dst_ptr += 1; + bits >>= 1; + } + } + for (; dst_ptr < dst_opl; dst_ptr += 1){ + *dst_ptr = 0xFFFFFFFF; + } + } + + // construct table + B32 bad_table = 0; + if (unpacked_offsets != 0){ + result = push_array(arena, PDB_GsiParsed, 1); + + // hash records + PDB_GsiHashRecord *hash_records = (PDB_GsiHashRecord*)(data.str + hash_record_array_off); + U32 hash_record_count = header->hr_len/sizeof(PDB_GsiHashRecord); + + // * We unpack hash records into the the table by scanning backwards through the + // * hash records. Neighboring values in unpacked_offsets *sort of* form counts, but we + // * have to skip the max-U32s (sloppy PDB nonsense). + + // * PDBs put one extra slot at the beginning of the encoded buckets that is mean + // * to be padding for modifying the buffer in place. After decoding there are 4096 buckets, + // * in the encoded buckets there are 4097. We are meant to drop the first one. + + // build table + PDB_GsiHashRecord *hash_record_ptr = hash_records + hash_record_count - 1; + U32 prev_n = hash_record_count; + for (U32 i = slot_count; i > 1;){ + i -= 1; + if (unpacked_offsets[i] != 0xFFFFFFFF){ + // determine hash record range to use + // * The "12" here is the result of some really sloppy PDB magic. + U32 n = unpacked_offsets[i]/12; + if (n > prev_n){ + bad_table = 1; + break; + } + U32 num_steps = prev_n - n; + + // fill this bucket + U32 *bucket_offs = push_array_aligned(arena, U32, num_steps, 4); + for (U32 j = num_steps; j > 0;){ + j -= 1; + // * The "- 1" is more sloppy PDB magic. + bucket_offs[j] = hash_record_ptr->symbol_off - 1; + hash_record_ptr -= 1; + } + PDB_GsiBucket *bucket = &result->buckets[i - 1]; + bucket->count = num_steps; + bucket->offs = bucket_offs; + + // update prev_n + prev_n = n; + } + } + } + + scratch_end(scratch); + } + + ProfEnd(); + + return(result); +} + +internal PDB_CoffSectionArray* +pdb_coff_section_array_from_data(Arena *arena, String8 data){ + U64 count = data.size/sizeof(COFF_SectionHeader); + + PDB_CoffSectionArray *result = push_array(arena, PDB_CoffSectionArray, 1); + result->sections = (COFF_SectionHeader*)data.str; + result->count = count; + return(result); +} + +internal PDB_CompUnitArray* +pdb_comp_unit_array_from_data(Arena *arena, String8 data){ + PDB_CompUnitNode *first = 0; + PDB_CompUnitNode *last = 0; + U64 count = 0; + + U64 cursor = 0; + for (;cursor + sizeof(PDB_DbiCompUnitHeader) <= data.size;){ + // get header + PDB_DbiCompUnitHeader *header = (PDB_DbiCompUnitHeader*)(data.str + cursor); + + // get names + U64 name_off = cursor + sizeof(*header); + String8 name = str8_cstring_capped((char *)(data.str + name_off), (char *)(data.str + data.size)); + + U64 name2_off = name_off + name.size + 1; + String8 name2 = str8_cstring_capped((char *)(data.str + name2_off), (char *)(data.str + data.size)); + + U64 after_name2_off = name2_off + name2.size + 1; + + // save mod info + PDB_CompUnitNode *node = push_array_no_zero(arena, PDB_CompUnitNode, 1); + SLLQueuePush(first, last, node); + count += 1; + node->unit.sn = header->sn; + node->unit.obj_name = name; + node->unit.group_name = name2; + + // fill range offsets + U32 *range_buf = node->unit.range_off; + { + // fill the buffer with size of each range + range_buf[PDB_DbiCompUnitRange_Symbols] = header->symbols_size; + range_buf[PDB_DbiCompUnitRange_C11] = header->c11_lines_size; + range_buf[PDB_DbiCompUnitRange_C13] = header->c13_lines_size; + Assert(PDB_DbiCompUnitRange_C13 + 1 == PDB_DbiCompUnitRange_COUNT); + + // in-place sizes -> offs conversion + U64 i = 0; + U32 range_cursor = 0; + for (; i < (U64)(PDB_DbiCompUnitRange_COUNT); i += 1){ + U64 adv = range_buf[i]; + range_buf[i] = range_cursor; + range_cursor += adv; + } + range_buf[i] = range_cursor; + + // skip 4 byte signature in symbols range + if (range_buf[1] >= 4){ + range_buf[0] += 4; + } + } + + // update cursor + cursor = AlignPow2(after_name2_off, 4); + } + + + // fill result + PDB_CompUnit **units = push_array_no_zero(arena, PDB_CompUnit*, count); + { + U64 idx = 0; + for (PDB_CompUnitNode *node = first; + node != 0; + node = node->next, idx += 1){ + units[idx] = &node->unit; + } + } + + PDB_CompUnitArray *result = push_array(arena, PDB_CompUnitArray, 1); + result->units = units; + result->count = count; + + return(result); +} + +internal PDB_CompUnitContributionArray* +pdb_comp_unit_contribution_array_from_data(Arena *arena, String8 data, + PDB_CoffSectionArray *sections){ + PDB_CompUnitContribution *contributions = 0; + U64 count = 0; + if (data.size >= sizeof(PDB_DbiSectionContribVersion)){ + PDB_DbiSectionContribVersion *version = (PDB_DbiSectionContribVersion*)data.str; + + // determine array layout from version + U32 item_size = 0; + U32 array_off = 0; + switch (*version){ + default: + { + // TODO(allen): do we have a test case for this? + item_size = sizeof(PDB_DbiSectionContrib40); + }break; + case PDB_DbiSectionContribVersion_1: + { + item_size = sizeof(PDB_DbiSectionContrib); + array_off = sizeof(*version); + }break; + case PDB_DbiSectionContribVersion_2: + { + item_size = sizeof(PDB_DbiSectionContrib2); + array_off = sizeof(*version); + }break; + } + + // allocate ranges + U64 max_count = (data.size - array_off)/item_size; + contributions = push_array_no_zero(arena, PDB_CompUnitContribution, max_count); + + // binary section info + U64 section_count = sections->count; + COFF_SectionHeader* section_headers = sections->sections; + + // fill array + PDB_CompUnitContribution *contribution_ptr = contributions; + U64 cursor = array_off; + for (; cursor + item_size <= data.size; cursor += item_size){ + PDB_DbiSectionContrib40 *sc = (PDB_DbiSectionContrib40*)(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; + + contribution_ptr->mod = sc->mod; + contribution_ptr->voff_first = voff; + contribution_ptr->voff_opl = voff + sc->size; + contribution_ptr += 1; + } + } + count = (U64)(contribution_ptr - contributions); + } + + // fill result + PDB_CompUnitContributionArray *result = push_array(arena, PDB_CompUnitContributionArray, 1); + result->contributions = contributions; + result->count = count; + + return(result); +} + + +//////////////////////////////// +//~ PDB Definition Functions + +internal U32 +pdb_string_hash1(String8 string){ + U32 result = 0; + U8 *ptr = string.str; + U8 *opl = ptr + (string.size&(~3)); + for (; ptr < opl; ptr += 4){ result ^= *(U32*)ptr; } + if ((string.size&2) != 0){ result ^= *(U16*)ptr; ptr += 2; } + if ((string.size&1) != 0){ result ^= *ptr; } + result |= 0x20202020; + result ^= (result >> 11); + result ^= (result >> 16); + return(result); +} + + +//////////////////////////////// +//~ PDB Dbi Functions + +internal String8 +pdb_data_from_dbi_range(PDB_DbiParsed *dbi, PDB_DbiRange range){ + String8 result = {0}; + if (range < PDB_DbiRange_COUNT){ + U64 first = dbi->range_off[range]; + U64 opl = dbi->range_off[range + 1]; + result.str = dbi->data.str + first; + result.size = opl - first; + } + return(result); +} + +internal String8 +pdb_data_from_unit_range(MSF_Parsed *msf, PDB_CompUnit *unit, PDB_DbiCompUnitRange range){ + String8 result = {0}; + if (range < PDB_DbiCompUnitRange_COUNT){ + String8 full_stream_data = msf_data_from_stream(msf, unit->sn); + + U64 first_raw = unit->range_off[range]; + U64 opl_raw = unit->range_off[range + 1]; + U64 opl = ClampTop(opl_raw, full_stream_data.size); + U64 first = ClampTop(first_raw, opl); + + result.str = full_stream_data.str + first; + result.size = opl - first; + } + return(result); +} + +//////////////////////////////// +//~ PDB Tpi Functions + +internal String8 +pdb_leaf_data_from_tpi(PDB_TpiParsed *tpi){ + String8 data = tpi->data; + U8 *first = data.str + tpi->leaf_first; + U8 *opl = data.str + tpi->leaf_opl; + String8 result = str8_range(first, opl); + return(result); +} + +internal CV_TypeIdArray +pdb_tpi_itypes_from_name(Arena *arena, PDB_TpiHashParsed *tpi_hash, CV_LeafParsed *leaf, + String8 name, B32 compare_unique_name, U32 output_cap){ + U32 hash = pdb_string_hash1(name); + U32 bucket_idx = ((tpi_hash->bucket_mask != 0) ? + hash&tpi_hash->bucket_mask : + hash%tpi_hash->bucket_count); + + CV_TypeId itype_first = leaf->itype_first; + CV_TypeId itype_opl = leaf->itype_opl; + String8 data = leaf->data; + + Temp scratch = scratch_begin(&arena, 1); + struct Chain{ + struct Chain *next; + CV_TypeId itype; + }; + struct Chain *first = 0; + struct Chain *last = 0; + U32 count = 0; + + for (PDB_TpiHashBlock *block = tpi_hash->buckets[bucket_idx]; + block != 0; + block = block->next){ + U32 local_count = block->local_count; + CV_TypeId *itype_ptr = block->itypes; + for (U32 i = 0; i < local_count; i += 1, itype_ptr += 1){ + + String8 extracted_name = {0}; + + CV_TypeId itype = *itype_ptr; + if (itype_first <= itype && itype < itype_opl){ + CV_RecRange *range = &leaf->leaf_ranges.ranges[itype - leaf->itype_first]; + if (range->off + range->hdr.size <= data.size){ + U8 *first = data.str + range->off + 2; + U64 cap = range->hdr.size - 2; + + switch (range->hdr.kind){ + default:break; + + case CV_LeafKind_CLASS: + case CV_LeafKind_STRUCTURE: + { + if (sizeof(CV_LeafStruct) <= cap){ + CV_LeafStruct *lf_struct = (CV_LeafStruct*)first; + + if (!(lf_struct->props & CV_TypeProp_FwdRef)){ + // size + U8 *numeric_ptr = (U8*)(lf_struct + 1); + CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, first + cap); + + // name + U8 *name_ptr = numeric_ptr + size.encoded_size; + String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); + + // unique name + if (compare_unique_name){ + if (lf_struct->props & CV_TypeProp_HasUniqueName) { + U8 *unique_name_ptr = name_ptr + name.size + 1; + String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); + extracted_name = unique_name; + } + } + else{ + extracted_name = name; + } + } + } + }break; + + case CV_LeafKind_CLASS2: + case CV_LeafKind_STRUCT2: + { + if (sizeof(CV_LeafStruct2) <= cap){ + CV_LeafStruct2 *lf_struct = (CV_LeafStruct2*)first; + + if (!(lf_struct->props & CV_TypeProp_FwdRef)){ + // size + U8 *numeric_ptr = (U8*)(lf_struct + 1); + CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, first + cap); + + // name + U8 *name_ptr = numeric_ptr + size.encoded_size; + String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); + + // unique name + if (compare_unique_name){ + if (lf_struct->props & CV_TypeProp_HasUniqueName) { + U8 *unique_name_ptr = name_ptr + name.size + 1; + String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); + extracted_name = unique_name; + } + } + else{ + extracted_name = name; + } + } + } + }break; + + case CV_LeafKind_UNION: + { + if (sizeof(CV_LeafUnion) <= cap){ + CV_LeafUnion *lf_union = (CV_LeafUnion*)first; + + if (!(lf_union->props & CV_TypeProp_FwdRef)){ + // size + U8 *numeric_ptr = (U8*)(lf_union + 1); + CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, first + cap); + + // name + U8 *name_ptr = numeric_ptr + size.encoded_size; + String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); + + // unique name + if (compare_unique_name){ + if (lf_union->props & CV_TypeProp_HasUniqueName) { + U8 *unique_name_ptr = name_ptr + name.size + 1; + String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); + extracted_name = unique_name; + } + } + else{ + extracted_name = name; + } + } + } + }break; + + case CV_LeafKind_ENUM: + { + if (sizeof(CV_LeafEnum) <= cap){ + CV_LeafEnum *lf_enum = (CV_LeafEnum*)first; + + if (!(lf_enum->props & CV_TypeProp_FwdRef)){ + // name + U8 *name_ptr = (U8*)(lf_enum + 1); + String8 name = str8_cstring_capped((char*)name_ptr, (char *)(first + cap)); + + // unique name + if (compare_unique_name){ + if (lf_enum->props & CV_TypeProp_HasUniqueName) { + U8 *unique_name_ptr = name_ptr + name.size + 1; + String8 unique_name = str8_cstring_capped((char*)unique_name_ptr, (char *)(first + cap)); + extracted_name = unique_name; + } + } + else{ + extracted_name = name; + } + } + } + }break; + } + } + } + + if (str8_match(extracted_name, name, 0)){ + struct Chain *chain = push_array(scratch.arena, struct Chain, 1); + SLLQueuePush(first, last, chain); + count += 1; + chain->itype = itype; + if (count == output_cap){ + goto dblbreak; + } + } + } + } + + dblbreak:; + + + // assemble result + CV_TypeId *itypes = push_array_aligned(arena, CV_TypeId, count, 8); + { + CV_TypeId *itype_ptr = itypes; + for (struct Chain *node = first; + node != 0; + node = node->next, itype_ptr += 1){ + *itype_ptr = node->itype; + } + } + CV_TypeIdArray result = {0}; + result.itypes = itypes; + result.count = count; + + scratch_end(scratch); + + return(result); +} + +internal CV_TypeId +pdb_tpi_first_itype_from_name(PDB_TpiHashParsed *tpi_hash, CV_LeafParsed *tpi_leaf, + String8 name, B32 compare_unique_name){ + Temp scratch = scratch_begin(0, 0); + CV_TypeIdArray array = pdb_tpi_itypes_from_name(scratch.arena, tpi_hash, tpi_leaf, + name, compare_unique_name, 1); + CV_TypeId result = 0; + if (array.count > 0){ + result = array.itypes[0]; + } + + scratch_end(scratch); + return(result); +} + +//////////////////////////////// +//~ PDB Strtbl Functions + +internal String8 +pdb_strtbl_string_from_off(PDB_Strtbl *strtbl, U32 off){ + U32 strblock_max = strtbl->strblock_max; + U32 full_off_raw = strtbl->strblock_min + off; + U32 full_off = ClampTop(full_off_raw, strblock_max); + String8 result = str8_cstring_capped((char*)(strtbl->data.str + full_off), + (char*)(strtbl->data.str + strblock_max)); + return(result); +} + +internal String8 +pdb_strtbl_string_from_index(PDB_Strtbl *strtbl, PDB_StringIndex idx){ + String8 result = {0}; + if (idx < strtbl->bucket_count){ + U32 off = *(U32*)(strtbl->data.str + strtbl->buckets_min + idx*4); + result = pdb_strtbl_string_from_off(strtbl, off); + } + return(result); +} diff --git a/src/raddbg/raddbg.c b/src/raddbg/raddbg.c index abfdb4af..a3ffec8f 100644 --- a/src/raddbg/raddbg.c +++ b/src/raddbg/raddbg.c @@ -1,397 +1,397 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Frontend Entry Points - -internal void -update_and_render(OS_Handle repaint_window_handle, void *user_data) -{ - ProfTick(0); - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - - ////////////////////////////// - //- rjf: begin logging - // - if(main_thread_log == 0) - { - main_thread_log = log_alloc(); - String8 user_program_data_path = os_string_from_system_path(scratch.arena, OS_SystemPath_UserProgramData); - String8 user_data_folder = push_str8f(scratch.arena, "%S/raddbg/logs", user_program_data_path); - main_thread_log_path = push_str8f(df_state->arena, "%S/ui_thread.raddbg_log", user_data_folder); - os_make_directory(user_data_folder); - os_write_data_to_file_path(main_thread_log_path, str8_zero()); - } - log_select(main_thread_log); - log_scope_begin(); - - ////////////////////////////// - //- rjf: tick cache layers - // - txt_user_clock_tick(); - dasm_user_clock_tick(); - geo_user_clock_tick(); - tex_user_clock_tick(); - - ////////////////////////////// - //- rjf: pick target hz - // - // TODO(rjf): maximize target, given all windows and their monitors - F32 target_hz = os_default_refresh_rate(); - if(frame_time_us_history_idx > 32) - { - // rjf: calculate average frame time out of the last N - U64 num_frames_in_history = Min(ArrayCount(frame_time_us_history), frame_time_us_history_idx); - U64 frame_time_history_sum_us = 0; - for(U64 idx = 0; idx < num_frames_in_history; idx += 1) - { - frame_time_history_sum_us += frame_time_us_history[idx]; - } - U64 frame_time_history_avg_us = frame_time_history_sum_us/num_frames_in_history; - - // rjf: pick among a number of sensible targets to snap to, given how well - // we've been performing - F32 possible_alternate_hz_targets[] = {target_hz, 60.f, 120.f, 144.f, 240.f}; - F32 best_target_hz = target_hz; - S64 best_target_hz_frame_time_us_diff = max_S64; - for(U64 idx = 0; idx < ArrayCount(possible_alternate_hz_targets); idx += 1) - { - F32 candidate = possible_alternate_hz_targets[idx]; - if(candidate <= target_hz) - { - U64 candidate_frame_time_us = 1000000/(U64)candidate; - S64 frame_time_us_diff = (S64)frame_time_history_avg_us - (S64)candidate_frame_time_us; - if(abs_s64(frame_time_us_diff) < best_target_hz_frame_time_us_diff) - { - best_target_hz = candidate; - best_target_hz_frame_time_us_diff = frame_time_us_diff; - } - } - } - target_hz = best_target_hz; - } - - ////////////////////////////// - //- rjf: target Hz -> delta time - // - F32 dt = 1.f/target_hz; - - ////////////////////////////// - //- rjf: get events from the OS - // - OS_EventList events = {0}; - if(os_handle_match(repaint_window_handle, os_handle_zero())) - { - events = os_get_events(scratch.arena, df_gfx_state->num_frames_requested == 0); - } - - ////////////////////////////// - //- rjf: begin measuring actual per-frame work - // - U64 begin_time_us = os_now_microseconds(); - - ////////////////////////////// - //- rjf: bind change - // - if(!df_gfx_state->confirm_active && df_gfx_state->bind_change_active) - { - if(os_key_press(&events, os_handle_zero(), 0, OS_Key_Esc)) - { - df_gfx_request_frame(); - df_gfx_state->bind_change_active = 0; - } - if(os_key_press(&events, os_handle_zero(), 0, OS_Key_Delete)) - { - df_gfx_request_frame(); - df_unbind_spec(df_gfx_state->bind_change_cmd_spec, df_gfx_state->bind_change_binding); - df_gfx_state->bind_change_active = 0; - DF_CmdParams p = df_cmd_params_from_gfx(); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(df_g_cfg_src_write_cmd_kind_table[DF_CfgSrc_User])); - } - for(OS_Event *event = events.first, *next = 0; event != 0; event = next) - { - if(event->kind == OS_EventKind_Press && - event->key != OS_Key_Esc && - event->key != OS_Key_Return && - event->key != OS_Key_Backspace && - event->key != OS_Key_Delete && - event->key != OS_Key_LeftMouseButton && - event->key != OS_Key_RightMouseButton && - event->key != OS_Key_MiddleMouseButton && - event->key != OS_Key_Ctrl && - event->key != OS_Key_Alt && - event->key != OS_Key_Shift) - { - df_gfx_state->bind_change_active = 0; - DF_Binding binding = zero_struct; - { - binding.key = event->key; - binding.flags = event->flags; - } - df_unbind_spec(df_gfx_state->bind_change_cmd_spec, df_gfx_state->bind_change_binding); - df_bind_spec(df_gfx_state->bind_change_cmd_spec, binding); - U32 codepoint = os_codepoint_from_event_flags_and_key(event->flags, event->key); - os_text(&events, os_handle_zero(), codepoint); - os_eat_event(&events, event); - DF_CmdParams p = df_cmd_params_from_gfx(); - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(df_g_cfg_src_write_cmd_kind_table[DF_CfgSrc_User])); - df_gfx_request_frame(); - break; - } - } - } - - ////////////////////////////// - //- rjf: consume events - // - B32 queue_drag_drop = 0; - { - for(OS_Event *event = events.first, *next = 0; - event != 0; - event = next) - { - next = event->next; - DF_Window *window = df_window_from_os_handle(event->window); - DF_CmdParams params = window ? df_cmd_params_from_window(window) : df_cmd_params_from_gfx(); - B32 take = 0; - B32 skip = 0; - - //- rjf: try drag-drop - if(df_drag_is_active() && event->kind == OS_EventKind_Release && event->key == OS_Key_LeftMouseButton) - { - skip = 1; - queue_drag_drop = 1; - } - - //- rjf: try window close - if(!take && event->kind == OS_EventKind_WindowClose && window != 0) - { - take = 1; - DF_CmdParams params = df_cmd_params_from_window(window); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CloseWindow)); - } - - //- rjf: try menu bar operations - { - if(!take && event->kind == OS_EventKind_Press && event->key == OS_Key_Alt && event->flags == 0 && event->is_repeat == 0) - { - take = 1; - df_gfx_request_frame(); - window->menu_bar_focused_on_press = window->menu_bar_focused; - window->menu_bar_key_held = 1; - window->menu_bar_focus_press_started = 1; - } - if(!take && event->kind == OS_EventKind_Release && event->key == OS_Key_Alt && event->flags == 0 && event->is_repeat == 0) - { - take = 1; - df_gfx_request_frame(); - window->menu_bar_key_held = 0; - } - if(window->menu_bar_focused && event->kind == OS_EventKind_Press && event->key == OS_Key_Alt && event->flags == 0 && event->is_repeat == 0) - { - take = 1; - df_gfx_request_frame(); - window->menu_bar_focused = 0; - } - else if(window->menu_bar_focus_press_started && !window->menu_bar_focused && event->kind == OS_EventKind_Release && event->flags == 0 && event->key == OS_Key_Alt && event->is_repeat == 0) - { - take = 1; - df_gfx_request_frame(); - window->menu_bar_focused = !window->menu_bar_focused_on_press; - window->menu_bar_focus_press_started = 0; - } - else if(event->kind == OS_EventKind_Press && event->key == OS_Key_Esc && window->menu_bar_focused && !ui_any_ctx_menu_is_open()) - { - take = 1; - df_gfx_request_frame(); - window->menu_bar_focused = 0; - } - } - - //- rjf: try hotkey presses - if(!take && event->kind == OS_EventKind_Press) - { - DF_Binding binding = {event->key, event->flags}; - DF_CmdSpecList spec_candidates = df_cmd_spec_list_from_binding(scratch.arena, binding); - if(spec_candidates.first != 0 && !df_cmd_spec_is_nil(spec_candidates.first->spec)) - { - DF_CmdSpec *run_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand); - DF_CmdSpec *spec = spec_candidates.first->spec; - if(run_spec != spec) - { - params.cmd_spec = spec; - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); - } - U32 hit_char = os_codepoint_from_event_flags_and_key(event->flags, event->key); - take = 1; - df_push_cmd__root(¶ms, run_spec); - if(event->flags & OS_EventFlag_Alt) - { - window->menu_bar_focus_press_started = 0; - } - } - else if(OS_Key_F1 <= event->key && event->key <= OS_Key_F19) - { - window->menu_bar_focus_press_started = 0; - } - df_gfx_request_frame(); - } - - //- rjf: try text events - if(!take && event->kind == OS_EventKind_Text) - { - String32 insertion32 = str32(&event->character, 1); - String8 insertion8 = str8_from_32(scratch.arena, insertion32); - DF_CmdSpec *spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_InsertText); - params.string = insertion8; - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); - df_push_cmd__root(¶ms, spec); - df_gfx_request_frame(); - take = 1; - if(event->flags & OS_EventFlag_Alt) - { - window->menu_bar_focus_press_started = 0; - } - } - - //- rjf: do fall-through - if(!take) - { - take = 1; - params.os_event = event; - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_OSEvent)); - } - - //- rjf: take - if(take && !skip) - { - os_eat_event(&events, event); - } - } - } - - ////////////////////////////// - //- rjf: gather root-level commands - // - DF_CmdList cmds = df_core_gather_root_cmds(scratch.arena); - - ////////////////////////////// - //- rjf: begin frame - // - df_core_begin_frame(scratch.arena, &cmds, dt); - df_gfx_begin_frame(scratch.arena, &cmds); - - ////////////////////////////// - //- rjf: queue drop for drag/drop - // - if(queue_drag_drop) - { - df_queue_drag_drop(); - } - - ////////////////////////////// - //- rjf: auto-focus moused-over windows while dragging - // - if(df_drag_is_active()) - { - B32 over_focused_window = 0; - { - for(DF_Window *window = df_gfx_state->first_window; window != 0; window = window->next) - { - Vec2F32 mouse = os_mouse_from_window(window->os); - Rng2F32 rect = os_client_rect_from_window(window->os); - if(os_window_is_focused(window->os) && contains_2f32(rect, mouse)) - { - over_focused_window = 1; - break; - } - } - } - if(!over_focused_window) - { - for(DF_Window *window = df_gfx_state->first_window; window != 0; window = window->next) - { - Vec2F32 mouse = os_mouse_from_window(window->os); - Rng2F32 rect = os_client_rect_from_window(window->os); - if(!os_window_is_focused(window->os) && contains_2f32(rect, mouse)) - { - os_window_focus(window->os); - break; - } - } - } - } - - ////////////////////////////// - //- rjf: update & render - // - { - d_begin_frame(); - for(DF_Window *w = df_gfx_state->first_window; w != 0; w = w->next) - { - B32 window_is_focused = os_window_is_focused(w->os); - if(window_is_focused) - { - last_focused_window = df_handle_from_window(w); - } - df_push_interact_regs(); - df_window_update_and_render(scratch.arena, w, &cmds); - DF_InteractRegs *window_regs = df_pop_interact_regs(); - if(df_window_from_handle(last_focused_window) == w) - { - MemoryCopyStruct(df_interact_regs(), window_regs); - } - } - } - - ////////////////////////////// - //- rjf: end frontend frame, send signals, etc. - // - df_gfx_end_frame(); - df_core_end_frame(); - - ////////////////////////////// - //- rjf: submit rendering to all windows - // - { - r_begin_frame(); - for(DF_Window *w = df_gfx_state->first_window; w != 0; w = w->next) - { - r_window_begin_frame(w->os, w->r); - d_submit_bucket(w->os, w->r, w->draw_bucket); - r_window_end_frame(w->os, w->r); - } - r_end_frame(); - } - - ////////////////////////////// - //- rjf: determine frame time, record into history - // - U64 end_time_us = os_now_microseconds(); - U64 frame_time_us = end_time_us-begin_time_us; - frame_time_us_history[frame_time_us_history_idx%ArrayCount(frame_time_us_history)] = frame_time_us; - frame_time_us_history_idx += 1; - - ////////////////////////////// - //- rjf: end logging - // - { - LogScopeResult log = log_scope_end(scratch.arena); - os_append_data_to_file_path(main_thread_log_path, log.strings[LogMsgKind_Info]); - if(log.strings[LogMsgKind_UserError].size != 0) - { - DF_CmdParams p = df_cmd_params_from_gfx(); - p.string = log.strings[LogMsgKind_UserError]; - df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - } - } - - scratch_end(scratch); - ProfEnd(); -} - -internal CTRL_WAKEUP_FUNCTION_DEF(wakeup_hook_ctrl) -{ - os_send_wakeup_event(); -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Frontend Entry Points + +internal void +update_and_render(OS_Handle repaint_window_handle, void *user_data) +{ + ProfTick(0); + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + + ////////////////////////////// + //- rjf: begin logging + // + if(main_thread_log == 0) + { + main_thread_log = log_alloc(); + String8 user_program_data_path = os_get_process_info()->user_program_data_path; + String8 user_data_folder = push_str8f(scratch.arena, "%S/raddbg/logs", user_program_data_path); + main_thread_log_path = push_str8f(df_state->arena, "%S/ui_thread.raddbg_log", user_data_folder); + os_make_directory(user_data_folder); + os_write_data_to_file_path(main_thread_log_path, str8_zero()); + } + log_select(main_thread_log); + log_scope_begin(); + + ////////////////////////////// + //- rjf: tick cache layers + // + txt_user_clock_tick(); + dasm_user_clock_tick(); + geo_user_clock_tick(); + tex_user_clock_tick(); + + ////////////////////////////// + //- rjf: pick target hz + // + // TODO(rjf): maximize target, given all windows and their monitors + F32 target_hz = os_get_gfx_info()->default_refresh_rate; + if(frame_time_us_history_idx > 32) + { + // rjf: calculate average frame time out of the last N + U64 num_frames_in_history = Min(ArrayCount(frame_time_us_history), frame_time_us_history_idx); + U64 frame_time_history_sum_us = 0; + for(U64 idx = 0; idx < num_frames_in_history; idx += 1) + { + frame_time_history_sum_us += frame_time_us_history[idx]; + } + U64 frame_time_history_avg_us = frame_time_history_sum_us/num_frames_in_history; + + // rjf: pick among a number of sensible targets to snap to, given how well + // we've been performing + F32 possible_alternate_hz_targets[] = {target_hz, 60.f, 120.f, 144.f, 240.f}; + F32 best_target_hz = target_hz; + S64 best_target_hz_frame_time_us_diff = max_S64; + for(U64 idx = 0; idx < ArrayCount(possible_alternate_hz_targets); idx += 1) + { + F32 candidate = possible_alternate_hz_targets[idx]; + if(candidate <= target_hz) + { + U64 candidate_frame_time_us = 1000000/(U64)candidate; + S64 frame_time_us_diff = (S64)frame_time_history_avg_us - (S64)candidate_frame_time_us; + if(abs_s64(frame_time_us_diff) < best_target_hz_frame_time_us_diff) + { + best_target_hz = candidate; + best_target_hz_frame_time_us_diff = frame_time_us_diff; + } + } + } + target_hz = best_target_hz; + } + + ////////////////////////////// + //- rjf: target Hz -> delta time + // + F32 dt = 1.f/target_hz; + + ////////////////////////////// + //- rjf: get events from the OS + // + OS_EventList events = {0}; + if(os_handle_match(repaint_window_handle, os_handle_zero())) + { + events = os_get_events(scratch.arena, df_gfx_state->num_frames_requested == 0); + } + + ////////////////////////////// + //- rjf: begin measuring actual per-frame work + // + U64 begin_time_us = os_now_microseconds(); + + ////////////////////////////// + //- rjf: bind change + // + if(!df_gfx_state->confirm_active && df_gfx_state->bind_change_active) + { + if(os_key_press(&events, os_handle_zero(), 0, OS_Key_Esc)) + { + df_gfx_request_frame(); + df_gfx_state->bind_change_active = 0; + } + if(os_key_press(&events, os_handle_zero(), 0, OS_Key_Delete)) + { + df_gfx_request_frame(); + df_unbind_spec(df_gfx_state->bind_change_cmd_spec, df_gfx_state->bind_change_binding); + df_gfx_state->bind_change_active = 0; + DF_CmdParams p = df_cmd_params_from_gfx(); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(df_g_cfg_src_write_cmd_kind_table[DF_CfgSrc_User])); + } + for(OS_Event *event = events.first, *next = 0; event != 0; event = next) + { + if(event->kind == OS_EventKind_Press && + event->key != OS_Key_Esc && + event->key != OS_Key_Return && + event->key != OS_Key_Backspace && + event->key != OS_Key_Delete && + event->key != OS_Key_LeftMouseButton && + event->key != OS_Key_RightMouseButton && + event->key != OS_Key_MiddleMouseButton && + event->key != OS_Key_Ctrl && + event->key != OS_Key_Alt && + event->key != OS_Key_Shift) + { + df_gfx_state->bind_change_active = 0; + DF_Binding binding = zero_struct; + { + binding.key = event->key; + binding.flags = event->flags; + } + df_unbind_spec(df_gfx_state->bind_change_cmd_spec, df_gfx_state->bind_change_binding); + df_bind_spec(df_gfx_state->bind_change_cmd_spec, binding); + U32 codepoint = os_codepoint_from_event_flags_and_key(event->flags, event->key); + os_text(&events, os_handle_zero(), codepoint); + os_eat_event(&events, event); + DF_CmdParams p = df_cmd_params_from_gfx(); + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(df_g_cfg_src_write_cmd_kind_table[DF_CfgSrc_User])); + df_gfx_request_frame(); + break; + } + } + } + + ////////////////////////////// + //- rjf: consume events + // + B32 queue_drag_drop = 0; + { + for(OS_Event *event = events.first, *next = 0; + event != 0; + event = next) + { + next = event->next; + DF_Window *window = df_window_from_os_handle(event->window); + DF_CmdParams params = window ? df_cmd_params_from_window(window) : df_cmd_params_from_gfx(); + B32 take = 0; + B32 skip = 0; + + //- rjf: try drag-drop + if(df_drag_is_active() && event->kind == OS_EventKind_Release && event->key == OS_Key_LeftMouseButton) + { + skip = 1; + queue_drag_drop = 1; + } + + //- rjf: try window close + if(!take && event->kind == OS_EventKind_WindowClose && window != 0) + { + take = 1; + DF_CmdParams params = df_cmd_params_from_window(window); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_CloseWindow)); + } + + //- rjf: try menu bar operations + { + if(!take && event->kind == OS_EventKind_Press && event->key == OS_Key_Alt && event->flags == 0 && event->is_repeat == 0) + { + take = 1; + df_gfx_request_frame(); + window->menu_bar_focused_on_press = window->menu_bar_focused; + window->menu_bar_key_held = 1; + window->menu_bar_focus_press_started = 1; + } + if(!take && event->kind == OS_EventKind_Release && event->key == OS_Key_Alt && event->flags == 0 && event->is_repeat == 0) + { + take = 1; + df_gfx_request_frame(); + window->menu_bar_key_held = 0; + } + if(window->menu_bar_focused && event->kind == OS_EventKind_Press && event->key == OS_Key_Alt && event->flags == 0 && event->is_repeat == 0) + { + take = 1; + df_gfx_request_frame(); + window->menu_bar_focused = 0; + } + else if(window->menu_bar_focus_press_started && !window->menu_bar_focused && event->kind == OS_EventKind_Release && event->flags == 0 && event->key == OS_Key_Alt && event->is_repeat == 0) + { + take = 1; + df_gfx_request_frame(); + window->menu_bar_focused = !window->menu_bar_focused_on_press; + window->menu_bar_focus_press_started = 0; + } + else if(event->kind == OS_EventKind_Press && event->key == OS_Key_Esc && window->menu_bar_focused && !ui_any_ctx_menu_is_open()) + { + take = 1; + df_gfx_request_frame(); + window->menu_bar_focused = 0; + } + } + + //- rjf: try hotkey presses + if(!take && event->kind == OS_EventKind_Press) + { + DF_Binding binding = {event->key, event->flags}; + DF_CmdSpecList spec_candidates = df_cmd_spec_list_from_binding(scratch.arena, binding); + if(spec_candidates.first != 0 && !df_cmd_spec_is_nil(spec_candidates.first->spec)) + { + DF_CmdSpec *run_spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_RunCommand); + DF_CmdSpec *spec = spec_candidates.first->spec; + if(run_spec != spec) + { + params.cmd_spec = spec; + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_CmdSpec); + } + U32 hit_char = os_codepoint_from_event_flags_and_key(event->flags, event->key); + take = 1; + df_push_cmd__root(¶ms, run_spec); + if(event->flags & OS_EventFlag_Alt) + { + window->menu_bar_focus_press_started = 0; + } + } + else if(OS_Key_F1 <= event->key && event->key <= OS_Key_F19) + { + window->menu_bar_focus_press_started = 0; + } + df_gfx_request_frame(); + } + + //- rjf: try text events + if(!take && event->kind == OS_EventKind_Text) + { + String32 insertion32 = str32(&event->character, 1); + String8 insertion8 = str8_from_32(scratch.arena, insertion32); + DF_CmdSpec *spec = df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_InsertText); + params.string = insertion8; + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); + df_push_cmd__root(¶ms, spec); + df_gfx_request_frame(); + take = 1; + if(event->flags & OS_EventFlag_Alt) + { + window->menu_bar_focus_press_started = 0; + } + } + + //- rjf: do fall-through + if(!take) + { + take = 1; + params.os_event = event; + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_OSEvent)); + } + + //- rjf: take + if(take && !skip) + { + os_eat_event(&events, event); + } + } + } + + ////////////////////////////// + //- rjf: gather root-level commands + // + DF_CmdList cmds = df_core_gather_root_cmds(scratch.arena); + + ////////////////////////////// + //- rjf: begin frame + // + df_core_begin_frame(scratch.arena, &cmds, dt); + df_gfx_begin_frame(scratch.arena, &cmds); + + ////////////////////////////// + //- rjf: queue drop for drag/drop + // + if(queue_drag_drop) + { + df_queue_drag_drop(); + } + + ////////////////////////////// + //- rjf: auto-focus moused-over windows while dragging + // + if(df_drag_is_active()) + { + B32 over_focused_window = 0; + { + for(DF_Window *window = df_gfx_state->first_window; window != 0; window = window->next) + { + Vec2F32 mouse = os_mouse_from_window(window->os); + Rng2F32 rect = os_client_rect_from_window(window->os); + if(os_window_is_focused(window->os) && contains_2f32(rect, mouse)) + { + over_focused_window = 1; + break; + } + } + } + if(!over_focused_window) + { + for(DF_Window *window = df_gfx_state->first_window; window != 0; window = window->next) + { + Vec2F32 mouse = os_mouse_from_window(window->os); + Rng2F32 rect = os_client_rect_from_window(window->os); + if(!os_window_is_focused(window->os) && contains_2f32(rect, mouse)) + { + os_window_focus(window->os); + break; + } + } + } + } + + ////////////////////////////// + //- rjf: update & render + // + { + d_begin_frame(); + for(DF_Window *w = df_gfx_state->first_window; w != 0; w = w->next) + { + B32 window_is_focused = os_window_is_focused(w->os); + if(window_is_focused) + { + last_focused_window = df_handle_from_window(w); + } + df_push_interact_regs(); + df_window_update_and_render(scratch.arena, w, &cmds); + DF_InteractRegs *window_regs = df_pop_interact_regs(); + if(df_window_from_handle(last_focused_window) == w) + { + MemoryCopyStruct(df_interact_regs(), window_regs); + } + } + } + + ////////////////////////////// + //- rjf: end frontend frame, send signals, etc. + // + df_gfx_end_frame(); + df_core_end_frame(); + + ////////////////////////////// + //- rjf: submit rendering to all windows + // + { + r_begin_frame(); + for(DF_Window *w = df_gfx_state->first_window; w != 0; w = w->next) + { + r_window_begin_frame(w->os, w->r); + d_submit_bucket(w->os, w->r, w->draw_bucket); + r_window_end_frame(w->os, w->r); + } + r_end_frame(); + } + + ////////////////////////////// + //- rjf: determine frame time, record into history + // + U64 end_time_us = os_now_microseconds(); + U64 frame_time_us = end_time_us-begin_time_us; + frame_time_us_history[frame_time_us_history_idx%ArrayCount(frame_time_us_history)] = frame_time_us; + frame_time_us_history_idx += 1; + + ////////////////////////////// + //- rjf: end logging + // + { + LogScopeResult log = log_scope_end(scratch.arena); + os_append_data_to_file_path(main_thread_log_path, log.strings[LogMsgKind_Info]); + if(log.strings[LogMsgKind_UserError].size != 0) + { + DF_CmdParams p = df_cmd_params_from_gfx(); + p.string = log.strings[LogMsgKind_UserError]; + df_push_cmd__root(&p, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + } + } + + scratch_end(scratch); + ProfEnd(); +} + +internal CTRL_WAKEUP_FUNCTION_DEF(wakeup_hook_ctrl) +{ + os_send_wakeup_event(); +} diff --git a/src/raddbg/raddbg_main.c b/src/raddbg/raddbg_main.c index 98b59d30..6283d927 100644 --- a/src/raddbg/raddbg_main.c +++ b/src/raddbg/raddbg_main.c @@ -1,574 +1,574 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Build Options - -#define BUILD_VERSION_MAJOR 0 -#define BUILD_VERSION_MINOR 9 -#define BUILD_VERSION_PATCH 11 -#define BUILD_RELEASE_PHASE_STRING_LITERAL "ALPHA" -#define BUILD_TITLE "The RAD Debugger" -#define OS_FEATURE_GRAPHICAL 1 - -#define R_INIT_MANUAL 1 -#define TEX_INIT_MANUAL 1 -#define GEO_INIT_MANUAL 1 -#define F_INIT_MANUAL 1 -#define DF_INIT_MANUAL 1 -#define DF_GFX_INIT_MANUAL 1 - -//////////////////////////////// -//~ rjf: Includes - -//- rjf: [lib] -#include "third_party/rad_lzb_simple/rad_lzb_simple.h" -#include "third_party/rad_lzb_simple/rad_lzb_simple.c" - -//- rjf: [h] -#include "base/base_inc.h" -#include "os/os_inc.h" -#include "task_system/task_system.h" -#include "ico/ico.h" -#include "rdi_format/rdi_format_local.h" -#include "rdi_make/rdi_make_local.h" -#include "mdesk/mdesk.h" -#include "hash_store/hash_store.h" -#include "file_stream/file_stream.h" -#include "text_cache/text_cache.h" -#include "mutable_text/mutable_text.h" -#include "path/path.h" -#include "coff/coff.h" -#include "pe/pe.h" -#include "codeview/codeview.h" -#include "codeview/codeview_stringize.h" -#include "msf/msf.h" -#include "pdb/pdb.h" -#include "pdb/pdb_stringize.h" -#include "rdi_from_pdb/rdi_from_pdb.h" -#include "regs/regs.h" -#include "regs/rdi/regs_rdi.h" -#include "type_graph/type_graph.h" -#include "dbgi/dbgi.h" -#include "dasm_cache/dasm_cache.h" -#include "fuzzy_search/fuzzy_search.h" -#include "demon/demon_inc.h" -#include "eval/eval_inc.h" -#include "ctrl/ctrl_inc.h" -#include "font_provider/font_provider_inc.h" -#include "render/render_inc.h" -#include "texture_cache/texture_cache.h" -#include "geo_cache/geo_cache.h" -#include "font_cache/font_cache.h" -#include "draw/draw.h" -#include "ui/ui_inc.h" -#include "df/df_inc.h" -#include "raddbg.h" - -//- rjf: [c] -#include "base/base_inc.c" -#include "os/os_inc.c" -#include "task_system/task_system.c" -#include "ico/ico.c" -#include "rdi_format/rdi_format_local.c" -#include "rdi_make/rdi_make_local.c" -#include "mdesk/mdesk.c" -#include "hash_store/hash_store.c" -#include "file_stream/file_stream.c" -#include "text_cache/text_cache.c" -#include "mutable_text/mutable_text.c" -#include "path/path.c" -#include "coff/coff.c" -#include "pe/pe.c" -#include "codeview/codeview.c" -#include "codeview/codeview_stringize.c" -#include "msf/msf.c" -#include "pdb/pdb.c" -#include "pdb/pdb_stringize.c" -#include "rdi_from_pdb/rdi_from_pdb.c" -#include "regs/regs.c" -#include "regs/rdi/regs_rdi.c" -#include "type_graph/type_graph.c" -#include "dbgi/dbgi.c" -#include "dasm_cache/dasm_cache.c" -#include "fuzzy_search/fuzzy_search.c" -#include "demon/demon_inc.c" -#include "eval/eval_inc.c" -#include "ctrl/ctrl_inc.c" -#include "font_provider/font_provider_inc.c" -#include "render/render_inc.c" -#include "texture_cache/texture_cache.c" -#include "geo_cache/geo_cache.c" -#include "font_cache/font_cache.c" -#include "draw/draw.c" -#include "ui/ui_inc.c" -#include "df/df_inc.c" -#include "raddbg.c" - -//////////////////////////////// -//~ rjf: IPC Signaler Thread - -internal void -ipc_signaler_thread__entry_point(void *p) -{ - for(;;) - { - if(os_semaphore_take(ipc_signal_semaphore, max_U64)) - { - if(os_semaphore_take(ipc_lock_semaphore, max_U64)) - { - IPCInfo *ipc_info = (IPCInfo *)ipc_shared_memory_base; - String8 msg = str8((U8 *)(ipc_info+1), ipc_info->msg_size); - msg.size = Min(msg.size, IPC_SHARED_MEMORY_BUFFER_SIZE - sizeof(IPCInfo)); - OS_MutexScope(ipc_s2m_ring_mutex) for(;;) - { - U64 unconsumed_size = ipc_s2m_ring_write_pos - ipc_s2m_ring_read_pos; - U64 available_size = (sizeof(ipc_s2m_ring_buffer) - unconsumed_size); - if(available_size >= sizeof(U64)+sizeof(msg.size)) - { - ipc_s2m_ring_write_pos += ring_write_struct(ipc_s2m_ring_buffer, sizeof(ipc_s2m_ring_buffer), ipc_s2m_ring_write_pos, &msg.size); - ipc_s2m_ring_write_pos += ring_write(ipc_s2m_ring_buffer, sizeof(ipc_s2m_ring_buffer), ipc_s2m_ring_write_pos, msg.str, msg.size); - break; - } - os_condition_variable_wait(ipc_s2m_ring_cv, ipc_s2m_ring_mutex, max_U64); - } - os_condition_variable_broadcast(ipc_s2m_ring_cv); - os_send_wakeup_event(); - ipc_info->msg_size = 0; - os_semaphore_drop(ipc_lock_semaphore); - } - } - } -} - -//////////////////////////////// -//~ rjf: Entry Point - -internal void -entry_point(CmdLine *cmd_line) -{ - Temp scratch = scratch_begin(0, 0); - - //- rjf: windows -> turn off output handles, as we need to control those for target processes -#if OS_WINDOWS - HANDLE output_handles[3] = - { - GetStdHandle(STD_INPUT_HANDLE), - GetStdHandle(STD_OUTPUT_HANDLE), - GetStdHandle(STD_ERROR_HANDLE), - }; - for(U64 idx = 0; idx < ArrayCount(output_handles); idx += 1) - { - B32 duplicate = 0; - for(U64 idx2 = 0; idx2 < idx; idx2 += 1) - { - if(output_handles[idx2] == output_handles[idx]) - { - duplicate = 1; - break; - } - } - if(duplicate) - { - output_handles[idx] = 0; - } - } - for(U64 idx = 0; idx < ArrayCount(output_handles); idx += 1) - { - if(output_handles[idx] != 0) - { - CloseHandle(output_handles[idx]); - } - } - SetStdHandle(STD_INPUT_HANDLE, 0); - SetStdHandle(STD_OUTPUT_HANDLE, 0); - SetStdHandle(STD_ERROR_HANDLE, 0); -#endif - - //- rjf: unpack command line arguments - ExecMode exec_mode = ExecMode_Normal; - B32 auto_run = 0; - B32 auto_step = 0; - B32 jit_attach = 0; - U64 jit_pid = 0; - U64 jit_code = 0; - U64 jit_addr = 0; - { - if(cmd_line_has_flag(cmd_line, str8_lit("ipc"))) - { - exec_mode = ExecMode_IPCSender; - } - else if(cmd_line_has_flag(cmd_line, str8_lit("convert"))) - { - exec_mode = ExecMode_Converter; - } - else if(cmd_line_has_flag(cmd_line, str8_lit("?")) || - cmd_line_has_flag(cmd_line, str8_lit("help"))) - { - exec_mode = ExecMode_Help; - } - auto_run = cmd_line_has_flag(cmd_line, str8_lit("auto_run")); - auto_step = cmd_line_has_flag(cmd_line, str8_lit("auto_step")); - String8 jit_pid_string = cmd_line_string(cmd_line, str8_lit("jit_pid")); - String8 jit_code_string = cmd_line_string(cmd_line, str8_lit("jit_code")); - String8 jit_addr_string = cmd_line_string(cmd_line, str8_lit("jit_addr")); - try_u64_from_str8_c_rules(jit_pid_string, &jit_pid); - try_u64_from_str8_c_rules(jit_code_string, &jit_code); - try_u64_from_str8_c_rules(jit_addr_string, &jit_addr); - jit_attach = (jit_addr != 0); - } - - //- rjf: set up layers - ctrl_set_wakeup_hook(wakeup_hook_ctrl); - - //- rjf: dispatch to top-level codepath based on execution mode - switch(exec_mode) - { - //- rjf: normal execution - default: - case ExecMode_Normal: - { - //- rjf: manual layer initialization - { - r_init(cmd_line); - tex_init(); - geo_init(); - f_init(); - DF_StateDeltaHistory *hist = df_state_delta_history_alloc(); - df_core_init(cmd_line, hist); - df_gfx_init(update_and_render, df_state_delta_history()); - } - - //- rjf: setup initial target from command line args - { - String8List args = cmd_line->inputs; - if(args.node_count > 0 && args.first->string.size != 0) - { - Temp scratch = scratch_begin(0, 0); - DF_Entity *target = df_entity_alloc(0, df_entity_root(), DF_EntityKind_Target); - df_entity_equip_b32(target, 1); - df_entity_equip_cfg_src(target, DF_CfgSrc_CommandLine); - String8List passthrough_args_list = {0}; - for(String8Node *n = args.first->next; n != 0; n = n->next) - { - str8_list_push(scratch.arena, &passthrough_args_list, n->string); - } - - // rjf: get current path - String8 current_path = os_string_from_system_path(scratch.arena, OS_SystemPath_Current); - - // rjf: equip exe - if(args.first->string.size != 0) - { - String8 exe_name = args.first->string; - DF_Entity *exe = df_entity_alloc(0, target, DF_EntityKind_Executable); - PathStyle style = path_style_from_str8(exe_name); - if(style == PathStyle_Relative) - { - exe_name = push_str8f(scratch.arena, "%S/%S", current_path, exe_name); - exe_name = path_normalized_from_string(scratch.arena, exe_name); - } - df_entity_equip_name(0, exe, exe_name); - } - - // rjf: equip path - String8 path_part_of_arg = str8_chop_last_slash(args.first->string); - if(path_part_of_arg.size != 0) - { - String8 path = push_str8f(scratch.arena, "%S/", path_part_of_arg); - DF_Entity *execution_path = df_entity_alloc(0, target, DF_EntityKind_ExecutionPath); - df_entity_equip_name(0, execution_path, path); - } - - // rjf: equip args - StringJoin join = {str8_lit(""), str8_lit(" "), str8_lit("")}; - String8 args_str = str8_list_join(scratch.arena, &passthrough_args_list, &join); - if(args_str.size != 0) - { - DF_Entity *args_entity = df_entity_alloc(0, target, DF_EntityKind_Arguments); - df_entity_equip_name(0, args_entity, args_str); - } - scratch_end(scratch); - } - } - - //- rjf: set up shared resources for ipc to this instance; launch IPC signaler thread - { - Temp scratch = scratch_begin(0, 0); - U32 instance_pid = os_get_pid(); - String8 ipc_shared_memory_name = push_str8f(scratch.arena, "_raddbg_ipc_shared_memory_%i_", instance_pid); - String8 ipc_signal_semaphore_name = push_str8f(scratch.arena, "_raddbg_ipc_signal_semaphore_%i_", instance_pid); - String8 ipc_lock_semaphore_name = push_str8f(scratch.arena, "_raddbg_ipc_lock_semaphore_%i_", instance_pid); - OS_Handle ipc_shared_memory = os_shared_memory_alloc(IPC_SHARED_MEMORY_BUFFER_SIZE, ipc_shared_memory_name); - ipc_shared_memory_base = (U8 *)os_shared_memory_view_open(ipc_shared_memory, r1u64(0, IPC_SHARED_MEMORY_BUFFER_SIZE)); - ipc_signal_semaphore = os_semaphore_alloc(0, 1, ipc_signal_semaphore_name); - ipc_lock_semaphore = os_semaphore_alloc(1, 1, ipc_lock_semaphore_name); - ipc_s2m_ring_mutex = os_mutex_alloc(); - ipc_s2m_ring_cv = os_condition_variable_alloc(); - IPCInfo *ipc_info = (IPCInfo *)ipc_shared_memory_base; - MemoryZeroStruct(ipc_info); - os_launch_thread(ipc_signaler_thread__entry_point, 0, 0); - scratch_end(scratch); - } - - //- rjf: main application loop - { - for(;;) - { - //- rjf: consume IPC messages, dispatch UI commands - { - Temp scratch = scratch_begin(0, 0); - B32 consumed = 0; - String8 msg = {0}; - OS_MutexScope(ipc_s2m_ring_mutex) - { - U64 unconsumed_size = ipc_s2m_ring_write_pos - ipc_s2m_ring_read_pos; - if(unconsumed_size >= sizeof(U64)) - { - consumed = 1; - ipc_s2m_ring_read_pos += ring_read_struct(ipc_s2m_ring_buffer, sizeof(ipc_s2m_ring_buffer), ipc_s2m_ring_read_pos, &msg.size); - msg.size = Min(msg.size, unconsumed_size); - msg.str = push_array(scratch.arena, U8, msg.size); - ipc_s2m_ring_read_pos += ring_read(ipc_s2m_ring_buffer, sizeof(ipc_s2m_ring_buffer), ipc_s2m_ring_read_pos, msg.str, msg.size); - } - } - if(consumed) - { - os_condition_variable_broadcast(ipc_s2m_ring_cv); - } - if(msg.size != 0) - { - log_infof("ipc_msg: \"%S\"", msg); - DF_Window *dst_window = df_gfx_state->first_window; - for(DF_Window *window = dst_window; window != 0; window = window->next) - { - if(os_window_is_focused(window->os)) - { - dst_window = window; - break; - } - } - if(dst_window != 0) - { - dst_window->window_temporarily_focused_ipc = 1; - String8 cmd_spec_string = df_cmd_name_part_from_string(msg); - DF_CmdSpec *cmd_spec = df_cmd_spec_from_string(cmd_spec_string); - if(!df_cmd_spec_is_nil(cmd_spec)) - { - DF_CmdParams params = df_cmd_params_from_window(dst_window); - DF_CtrlCtx ctrl_ctx = df_ctrl_ctx_from_window(dst_window); - String8 error = df_cmd_params_apply_spec_query(scratch.arena, &ctrl_ctx, ¶ms, cmd_spec, df_cmd_arg_part_from_string(msg)); - if(error.size == 0) - { - df_push_cmd__root(¶ms, cmd_spec); - df_gfx_request_frame(); - } - else - { - DF_CmdParams params = df_cmd_params_from_window(dst_window); - params.string = error; - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - df_gfx_request_frame(); - } - } - else - { - DF_CmdParams params = df_cmd_params_from_window(dst_window); - params.string = push_str8f(scratch.arena, "\"%S\" is not a command.", cmd_spec_string); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); - df_gfx_request_frame(); - } - } - } - scratch_end(scratch); - } - - //- rjf: update & render frame - OS_Handle repaint_window = {0}; - update_and_render(repaint_window, 0); - - //- rjf: auto run - if(auto_run) - { - auto_run = 0; - DF_CmdParams params = df_cmd_params_from_gfx(); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_LaunchAndRun)); - } - - //- rjf: auto step - if(auto_step) - { - auto_step = 0; - DF_CmdParams params = df_cmd_params_from_gfx(); - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_StepInto)); - } - - //- rjf: jit attach - if(jit_attach) - { - jit_attach = 0; - DF_CmdParams params = df_cmd_params_from_gfx(); - df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_ID); - params.id = jit_pid; - df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Attach)); - } - - //- rjf: quit if no windows are left - if(df_gfx_state->first_window == 0) - { - break; - } - } - } - - }break; - - //- rjf: inter-process communication message sender - case ExecMode_IPCSender: - { - Temp scratch = scratch_begin(0, 0); - - //- rjf: grab explicit PID argument - U32 dst_pid = 0; - if(cmd_line_has_argument(cmd_line, str8_lit("pid"))) - { - String8 dst_pid_string = cmd_line_string(cmd_line, str8_lit("pid")); - U64 dst_pid_u64 = 0; - if(dst_pid_string.size != 0 && - try_u64_from_str8_c_rules(dst_pid_string, &dst_pid_u64)) - { - dst_pid = (U32)dst_pid_u64; - } - } - - //- rjf: no explicit PID? -> find PID to send message to, by looking for other raddbg instances - if(dst_pid == 0) - { - U32 this_pid = os_get_pid(); - DMN_ProcessIter it = {0}; - dmn_process_iter_begin(&it); - for(DMN_ProcessInfo info = {0}; dmn_process_iter_next(scratch.arena, &it, &info);) - { - if(str8_match(str8_skip_last_slash(str8_chop_last_dot(cmd_line->exe_name)), str8_skip_last_slash(str8_chop_last_dot(info.name)), StringMatchFlag_CaseInsensitive) && - this_pid != info.pid) - { - dst_pid = info.pid; - break; - } - } - dmn_process_iter_end(&it); - } - - //- rjf: grab destination instance's shared memory resources - String8 ipc_shared_memory_name = push_str8f(scratch.arena, "_raddbg_ipc_shared_memory_%i_", dst_pid); - String8 ipc_signal_semaphore_name = push_str8f(scratch.arena, "_raddbg_ipc_signal_semaphore_%i_", dst_pid); - String8 ipc_lock_semaphore_name = push_str8f(scratch.arena, "_raddbg_ipc_lock_semaphore_%i_", dst_pid); - OS_Handle ipc_shared_memory = os_shared_memory_alloc(IPC_SHARED_MEMORY_BUFFER_SIZE, ipc_shared_memory_name); - ipc_shared_memory_base = (U8 *)os_shared_memory_view_open(ipc_shared_memory, r1u64(0, IPC_SHARED_MEMORY_BUFFER_SIZE)); - ipc_signal_semaphore = os_semaphore_alloc(0, 1, ipc_signal_semaphore_name); - ipc_lock_semaphore = os_semaphore_alloc(1, 1, ipc_lock_semaphore_name); - - //- rjf: got resources -> write message - if(ipc_shared_memory_base != 0 && - os_semaphore_take(ipc_lock_semaphore, max_U64)) - { - IPCInfo *ipc_info = (IPCInfo *)ipc_shared_memory_base; - U8 *buffer = (U8 *)(ipc_info+1); - U64 buffer_max = IPC_SHARED_MEMORY_BUFFER_SIZE - sizeof(IPCInfo); - StringJoin join = {str8_lit(""), str8_lit(" "), str8_lit("")}; - String8 msg = str8_list_join(scratch.arena, &cmd_line->inputs, &join); - ipc_info->msg_size = Min(buffer_max, msg.size); - MemoryCopy(buffer, msg.str, ipc_info->msg_size); - os_semaphore_drop(ipc_signal_semaphore); - os_semaphore_drop(ipc_lock_semaphore); - } - - scratch_end(scratch); - }break; - - //- rjf: built-in pdb/dwarf -> raddbg converter mode - case ExecMode_Converter: - { - Temp scratch = scratch_begin(0, 0); - - //- rjf: parse arguments - P2R_User2Convert *user2convert = p2r_user2convert_from_cmdln(scratch.arena, cmd_line); - - //- rjf: open output file - String8 output_name = push_str8_copy(scratch.arena, user2convert->output_name); - OS_Handle out_file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_Write, output_name); - B32 out_file_is_good = !os_handle_match(out_file, os_handle_zero()); - - //- rjf: convert - P2R_Convert2Bake *convert2bake = 0; - if(out_file_is_good) - { - convert2bake = p2r_convert(scratch.arena, user2convert); - } - - //- rjf: bake - P2R_Bake2Serialize *bake2srlz = 0; - ProfScope("bake") - { - bake2srlz = p2r_bake(scratch.arena, convert2bake); - } - - //- rjf: serialize - P2R_Serialize2File *srlz2file = 0; - ProfScope("serialize") - { - srlz2file = push_array(scratch.arena, P2R_Serialize2File, 1); - srlz2file->bundle = rdim_serialized_section_bundle_from_bake_results(&bake2srlz->bake_results); - } - - //- rjf: compress - P2R_Serialize2File *srlz2file_compressed = srlz2file; - if(cmd_line_has_flag(cmd_line, str8_lit("compress"))) ProfScope("compress") - { - srlz2file_compressed = push_array(scratch.arena, P2R_Serialize2File, 1); - srlz2file_compressed = p2r_compress(scratch.arena, srlz2file); - } - - //- rjf: serialize - String8List blobs = rdim_file_blobs_from_section_bundle(scratch.arena, &srlz2file_compressed->bundle); - - //- rjf: write - if(out_file_is_good) - { - U64 off = 0; - for(String8Node *n = blobs.first; n != 0; n = n->next) - { - os_file_write(out_file, r1u64(off, off+n->string.size), n->string.str); - off += n->string.size; - } - } - - //- rjf: close output file - os_file_close(out_file); - - scratch_end(scratch); - }break; - - //- rjf: help message box - case ExecMode_Help: - { - os_graphical_message(0, - str8_lit("The RAD Debugger - Help"), - str8_lit("The following options may be used when starting the RAD Debugger from the command line:\n\n" - "--user:\n" - "Use to specify the location of a user file which should be used. User files are used to store settings for users, including window and panel setups, path mapping, and visual settings. If this file does not exist, it will be created as necessary. This file will be autosaved as user-related changes are made.\n\n" - "--project:\n" - "Use to specify the location of a project file which should be used. Project files are used to store settings for users and projects. If this file does not exist, it will be created as necessary. This file will be autosaved as project-related changes are made.\n\n" - "--auto_step\n" - "This will step into all targets after the debugger initially starts.\n\n" - "--auto_run\n" - "This will run all targets after the debugger initially starts.\n\n" - "--ipc \n" - "This will launch the debugger in the non-graphical IPC mode, which is used to communicate with another running instance of the debugger. The debugger instance will launch, send the specified command, then immediately terminate. This may be used by editors or other programs to control the debugger.\n\n")); - }break; - } - - scratch_end(scratch); -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Build Options + +#define BUILD_VERSION_MAJOR 0 +#define BUILD_VERSION_MINOR 9 +#define BUILD_VERSION_PATCH 11 +#define BUILD_RELEASE_PHASE_STRING_LITERAL "ALPHA" +#define BUILD_TITLE "The RAD Debugger" +#define OS_FEATURE_GRAPHICAL 1 + +#define R_INIT_MANUAL 1 +#define TEX_INIT_MANUAL 1 +#define GEO_INIT_MANUAL 1 +#define F_INIT_MANUAL 1 +#define DF_INIT_MANUAL 1 +#define DF_GFX_INIT_MANUAL 1 + +//////////////////////////////// +//~ rjf: Includes + +//- rjf: [lib] +#include "third_party/rad_lzb_simple/rad_lzb_simple.h" +#include "third_party/rad_lzb_simple/rad_lzb_simple.c" + +//- rjf: [h] +#include "base/base_inc.h" +#include "os/os_inc.h" +#include "task_system/task_system.h" +#include "ico/ico.h" +#include "rdi_format/rdi_format_local.h" +#include "rdi_make/rdi_make_local.h" +#include "mdesk/mdesk.h" +#include "hash_store/hash_store.h" +#include "file_stream/file_stream.h" +#include "text_cache/text_cache.h" +#include "mutable_text/mutable_text.h" +#include "path/path.h" +#include "coff/coff.h" +#include "pe/pe.h" +#include "codeview/codeview.h" +#include "codeview/codeview_stringize.h" +#include "msf/msf.h" +#include "pdb/pdb.h" +#include "pdb/pdb_stringize.h" +#include "rdi_from_pdb/rdi_from_pdb.h" +#include "regs/regs.h" +#include "regs/rdi/regs_rdi.h" +#include "type_graph/type_graph.h" +#include "dbgi/dbgi.h" +#include "dasm_cache/dasm_cache.h" +#include "fuzzy_search/fuzzy_search.h" +#include "demon/demon_inc.h" +#include "eval/eval_inc.h" +#include "ctrl/ctrl_inc.h" +#include "font_provider/font_provider_inc.h" +#include "render/render_inc.h" +#include "texture_cache/texture_cache.h" +#include "geo_cache/geo_cache.h" +#include "font_cache/font_cache.h" +#include "draw/draw.h" +#include "ui/ui_inc.h" +#include "df/df_inc.h" +#include "raddbg.h" + +//- rjf: [c] +#include "base/base_inc.c" +#include "os/os_inc.c" +#include "task_system/task_system.c" +#include "ico/ico.c" +#include "rdi_format/rdi_format_local.c" +#include "rdi_make/rdi_make_local.c" +#include "mdesk/mdesk.c" +#include "hash_store/hash_store.c" +#include "file_stream/file_stream.c" +#include "text_cache/text_cache.c" +#include "mutable_text/mutable_text.c" +#include "path/path.c" +#include "coff/coff.c" +#include "pe/pe.c" +#include "codeview/codeview.c" +#include "codeview/codeview_stringize.c" +#include "msf/msf.c" +#include "pdb/pdb.c" +#include "pdb/pdb_stringize.c" +#include "rdi_from_pdb/rdi_from_pdb.c" +#include "regs/regs.c" +#include "regs/rdi/regs_rdi.c" +#include "type_graph/type_graph.c" +#include "dbgi/dbgi.c" +#include "dasm_cache/dasm_cache.c" +#include "fuzzy_search/fuzzy_search.c" +#include "demon/demon_inc.c" +#include "eval/eval_inc.c" +#include "ctrl/ctrl_inc.c" +#include "font_provider/font_provider_inc.c" +#include "render/render_inc.c" +#include "texture_cache/texture_cache.c" +#include "geo_cache/geo_cache.c" +#include "font_cache/font_cache.c" +#include "draw/draw.c" +#include "ui/ui_inc.c" +#include "df/df_inc.c" +#include "raddbg.c" + +//////////////////////////////// +//~ rjf: IPC Signaler Thread + +internal void +ipc_signaler_thread__entry_point(void *p) +{ + for(;;) + { + if(os_semaphore_take(ipc_signal_semaphore, max_U64)) + { + if(os_semaphore_take(ipc_lock_semaphore, max_U64)) + { + IPCInfo *ipc_info = (IPCInfo *)ipc_shared_memory_base; + String8 msg = str8((U8 *)(ipc_info+1), ipc_info->msg_size); + msg.size = Min(msg.size, IPC_SHARED_MEMORY_BUFFER_SIZE - sizeof(IPCInfo)); + OS_MutexScope(ipc_s2m_ring_mutex) for(;;) + { + U64 unconsumed_size = ipc_s2m_ring_write_pos - ipc_s2m_ring_read_pos; + U64 available_size = (sizeof(ipc_s2m_ring_buffer) - unconsumed_size); + if(available_size >= sizeof(U64)+sizeof(msg.size)) + { + ipc_s2m_ring_write_pos += ring_write_struct(ipc_s2m_ring_buffer, sizeof(ipc_s2m_ring_buffer), ipc_s2m_ring_write_pos, &msg.size); + ipc_s2m_ring_write_pos += ring_write(ipc_s2m_ring_buffer, sizeof(ipc_s2m_ring_buffer), ipc_s2m_ring_write_pos, msg.str, msg.size); + break; + } + os_condition_variable_wait(ipc_s2m_ring_cv, ipc_s2m_ring_mutex, max_U64); + } + os_condition_variable_broadcast(ipc_s2m_ring_cv); + os_send_wakeup_event(); + ipc_info->msg_size = 0; + os_semaphore_drop(ipc_lock_semaphore); + } + } + } +} + +//////////////////////////////// +//~ rjf: Entry Point + +internal void +entry_point(CmdLine *cmd_line) +{ + Temp scratch = scratch_begin(0, 0); + + //- rjf: windows -> turn off output handles, as we need to control those for target processes +#if OS_WINDOWS + HANDLE output_handles[3] = + { + GetStdHandle(STD_INPUT_HANDLE), + GetStdHandle(STD_OUTPUT_HANDLE), + GetStdHandle(STD_ERROR_HANDLE), + }; + for(U64 idx = 0; idx < ArrayCount(output_handles); idx += 1) + { + B32 duplicate = 0; + for(U64 idx2 = 0; idx2 < idx; idx2 += 1) + { + if(output_handles[idx2] == output_handles[idx]) + { + duplicate = 1; + break; + } + } + if(duplicate) + { + output_handles[idx] = 0; + } + } + for(U64 idx = 0; idx < ArrayCount(output_handles); idx += 1) + { + if(output_handles[idx] != 0) + { + CloseHandle(output_handles[idx]); + } + } + SetStdHandle(STD_INPUT_HANDLE, 0); + SetStdHandle(STD_OUTPUT_HANDLE, 0); + SetStdHandle(STD_ERROR_HANDLE, 0); +#endif + + //- rjf: unpack command line arguments + ExecMode exec_mode = ExecMode_Normal; + B32 auto_run = 0; + B32 auto_step = 0; + B32 jit_attach = 0; + U64 jit_pid = 0; + U64 jit_code = 0; + U64 jit_addr = 0; + { + if(cmd_line_has_flag(cmd_line, str8_lit("ipc"))) + { + exec_mode = ExecMode_IPCSender; + } + else if(cmd_line_has_flag(cmd_line, str8_lit("convert"))) + { + exec_mode = ExecMode_Converter; + } + else if(cmd_line_has_flag(cmd_line, str8_lit("?")) || + cmd_line_has_flag(cmd_line, str8_lit("help"))) + { + exec_mode = ExecMode_Help; + } + auto_run = cmd_line_has_flag(cmd_line, str8_lit("auto_run")); + auto_step = cmd_line_has_flag(cmd_line, str8_lit("auto_step")); + String8 jit_pid_string = cmd_line_string(cmd_line, str8_lit("jit_pid")); + String8 jit_code_string = cmd_line_string(cmd_line, str8_lit("jit_code")); + String8 jit_addr_string = cmd_line_string(cmd_line, str8_lit("jit_addr")); + try_u64_from_str8_c_rules(jit_pid_string, &jit_pid); + try_u64_from_str8_c_rules(jit_code_string, &jit_code); + try_u64_from_str8_c_rules(jit_addr_string, &jit_addr); + jit_attach = (jit_addr != 0); + } + + //- rjf: set up layers + ctrl_set_wakeup_hook(wakeup_hook_ctrl); + + //- rjf: dispatch to top-level codepath based on execution mode + switch(exec_mode) + { + //- rjf: normal execution + default: + case ExecMode_Normal: + { + //- rjf: manual layer initialization + { + r_init(cmd_line); + tex_init(); + geo_init(); + f_init(); + DF_StateDeltaHistory *hist = df_state_delta_history_alloc(); + df_core_init(cmd_line, hist); + df_gfx_init(update_and_render, df_state_delta_history()); + } + + //- rjf: setup initial target from command line args + { + String8List args = cmd_line->inputs; + if(args.node_count > 0 && args.first->string.size != 0) + { + Temp scratch = scratch_begin(0, 0); + DF_Entity *target = df_entity_alloc(0, df_entity_root(), DF_EntityKind_Target); + df_entity_equip_b32(target, 1); + df_entity_equip_cfg_src(target, DF_CfgSrc_CommandLine); + String8List passthrough_args_list = {0}; + for(String8Node *n = args.first->next; n != 0; n = n->next) + { + str8_list_push(scratch.arena, &passthrough_args_list, n->string); + } + + // rjf: get current path + String8 current_path = os_get_current_path(scratch.arena); + + // rjf: equip exe + if(args.first->string.size != 0) + { + String8 exe_name = args.first->string; + DF_Entity *exe = df_entity_alloc(0, target, DF_EntityKind_Executable); + PathStyle style = path_style_from_str8(exe_name); + if(style == PathStyle_Relative) + { + exe_name = push_str8f(scratch.arena, "%S/%S", current_path, exe_name); + exe_name = path_normalized_from_string(scratch.arena, exe_name); + } + df_entity_equip_name(0, exe, exe_name); + } + + // rjf: equip path + String8 path_part_of_arg = str8_chop_last_slash(args.first->string); + if(path_part_of_arg.size != 0) + { + String8 path = push_str8f(scratch.arena, "%S/", path_part_of_arg); + DF_Entity *execution_path = df_entity_alloc(0, target, DF_EntityKind_ExecutionPath); + df_entity_equip_name(0, execution_path, path); + } + + // rjf: equip args + StringJoin join = {str8_lit(""), str8_lit(" "), str8_lit("")}; + String8 args_str = str8_list_join(scratch.arena, &passthrough_args_list, &join); + if(args_str.size != 0) + { + DF_Entity *args_entity = df_entity_alloc(0, target, DF_EntityKind_Arguments); + df_entity_equip_name(0, args_entity, args_str); + } + scratch_end(scratch); + } + } + + //- rjf: set up shared resources for ipc to this instance; launch IPC signaler thread + { + Temp scratch = scratch_begin(0, 0); + U32 instance_pid = os_get_process_info()->pid; + String8 ipc_shared_memory_name = push_str8f(scratch.arena, "_raddbg_ipc_shared_memory_%i_", instance_pid); + String8 ipc_signal_semaphore_name = push_str8f(scratch.arena, "_raddbg_ipc_signal_semaphore_%i_", instance_pid); + String8 ipc_lock_semaphore_name = push_str8f(scratch.arena, "_raddbg_ipc_lock_semaphore_%i_", instance_pid); + OS_Handle ipc_shared_memory = os_shared_memory_alloc(IPC_SHARED_MEMORY_BUFFER_SIZE, ipc_shared_memory_name); + ipc_shared_memory_base = (U8 *)os_shared_memory_view_open(ipc_shared_memory, r1u64(0, IPC_SHARED_MEMORY_BUFFER_SIZE)); + ipc_signal_semaphore = os_semaphore_alloc(0, 1, ipc_signal_semaphore_name); + ipc_lock_semaphore = os_semaphore_alloc(1, 1, ipc_lock_semaphore_name); + ipc_s2m_ring_mutex = os_mutex_alloc(); + ipc_s2m_ring_cv = os_condition_variable_alloc(); + IPCInfo *ipc_info = (IPCInfo *)ipc_shared_memory_base; + MemoryZeroStruct(ipc_info); + os_thread_launch(ipc_signaler_thread__entry_point, 0, 0); + scratch_end(scratch); + } + + //- rjf: main application loop + { + for(;;) + { + //- rjf: consume IPC messages, dispatch UI commands + { + Temp scratch = scratch_begin(0, 0); + B32 consumed = 0; + String8 msg = {0}; + OS_MutexScope(ipc_s2m_ring_mutex) + { + U64 unconsumed_size = ipc_s2m_ring_write_pos - ipc_s2m_ring_read_pos; + if(unconsumed_size >= sizeof(U64)) + { + consumed = 1; + ipc_s2m_ring_read_pos += ring_read_struct(ipc_s2m_ring_buffer, sizeof(ipc_s2m_ring_buffer), ipc_s2m_ring_read_pos, &msg.size); + msg.size = Min(msg.size, unconsumed_size); + msg.str = push_array(scratch.arena, U8, msg.size); + ipc_s2m_ring_read_pos += ring_read(ipc_s2m_ring_buffer, sizeof(ipc_s2m_ring_buffer), ipc_s2m_ring_read_pos, msg.str, msg.size); + } + } + if(consumed) + { + os_condition_variable_broadcast(ipc_s2m_ring_cv); + } + if(msg.size != 0) + { + log_infof("ipc_msg: \"%S\"", msg); + DF_Window *dst_window = df_gfx_state->first_window; + for(DF_Window *window = dst_window; window != 0; window = window->next) + { + if(os_window_is_focused(window->os)) + { + dst_window = window; + break; + } + } + if(dst_window != 0) + { + dst_window->window_temporarily_focused_ipc = 1; + String8 cmd_spec_string = df_cmd_name_part_from_string(msg); + DF_CmdSpec *cmd_spec = df_cmd_spec_from_string(cmd_spec_string); + if(!df_cmd_spec_is_nil(cmd_spec)) + { + DF_CmdParams params = df_cmd_params_from_window(dst_window); + DF_CtrlCtx ctrl_ctx = df_ctrl_ctx_from_window(dst_window); + String8 error = df_cmd_params_apply_spec_query(scratch.arena, &ctrl_ctx, ¶ms, cmd_spec, df_cmd_arg_part_from_string(msg)); + if(error.size == 0) + { + df_push_cmd__root(¶ms, cmd_spec); + df_gfx_request_frame(); + } + else + { + DF_CmdParams params = df_cmd_params_from_window(dst_window); + params.string = error; + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + df_gfx_request_frame(); + } + } + else + { + DF_CmdParams params = df_cmd_params_from_window(dst_window); + params.string = push_str8f(scratch.arena, "\"%S\" is not a command.", cmd_spec_string); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_String); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Error)); + df_gfx_request_frame(); + } + } + } + scratch_end(scratch); + } + + //- rjf: update & render frame + OS_Handle repaint_window = {0}; + update_and_render(repaint_window, 0); + + //- rjf: auto run + if(auto_run) + { + auto_run = 0; + DF_CmdParams params = df_cmd_params_from_gfx(); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_LaunchAndRun)); + } + + //- rjf: auto step + if(auto_step) + { + auto_step = 0; + DF_CmdParams params = df_cmd_params_from_gfx(); + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_StepInto)); + } + + //- rjf: jit attach + if(jit_attach) + { + jit_attach = 0; + DF_CmdParams params = df_cmd_params_from_gfx(); + df_cmd_params_mark_slot(¶ms, DF_CmdParamSlot_ID); + params.id = jit_pid; + df_push_cmd__root(¶ms, df_cmd_spec_from_core_cmd_kind(DF_CoreCmdKind_Attach)); + } + + //- rjf: quit if no windows are left + if(df_gfx_state->first_window == 0) + { + break; + } + } + } + + }break; + + //- rjf: inter-process communication message sender + case ExecMode_IPCSender: + { + Temp scratch = scratch_begin(0, 0); + + //- rjf: grab explicit PID argument + U32 dst_pid = 0; + if(cmd_line_has_argument(cmd_line, str8_lit("pid"))) + { + String8 dst_pid_string = cmd_line_string(cmd_line, str8_lit("pid")); + U64 dst_pid_u64 = 0; + if(dst_pid_string.size != 0 && + try_u64_from_str8_c_rules(dst_pid_string, &dst_pid_u64)) + { + dst_pid = (U32)dst_pid_u64; + } + } + + //- rjf: no explicit PID? -> find PID to send message to, by looking for other raddbg instances + if(dst_pid == 0) + { + U32 this_pid = os_get_process_info()->pid; + DMN_ProcessIter it = {0}; + dmn_process_iter_begin(&it); + for(DMN_ProcessInfo info = {0}; dmn_process_iter_next(scratch.arena, &it, &info);) + { + if(str8_match(str8_skip_last_slash(str8_chop_last_dot(cmd_line->exe_name)), str8_skip_last_slash(str8_chop_last_dot(info.name)), StringMatchFlag_CaseInsensitive) && + this_pid != info.pid) + { + dst_pid = info.pid; + break; + } + } + dmn_process_iter_end(&it); + } + + //- rjf: grab destination instance's shared memory resources + String8 ipc_shared_memory_name = push_str8f(scratch.arena, "_raddbg_ipc_shared_memory_%i_", dst_pid); + String8 ipc_signal_semaphore_name = push_str8f(scratch.arena, "_raddbg_ipc_signal_semaphore_%i_", dst_pid); + String8 ipc_lock_semaphore_name = push_str8f(scratch.arena, "_raddbg_ipc_lock_semaphore_%i_", dst_pid); + OS_Handle ipc_shared_memory = os_shared_memory_alloc(IPC_SHARED_MEMORY_BUFFER_SIZE, ipc_shared_memory_name); + ipc_shared_memory_base = (U8 *)os_shared_memory_view_open(ipc_shared_memory, r1u64(0, IPC_SHARED_MEMORY_BUFFER_SIZE)); + ipc_signal_semaphore = os_semaphore_alloc(0, 1, ipc_signal_semaphore_name); + ipc_lock_semaphore = os_semaphore_alloc(1, 1, ipc_lock_semaphore_name); + + //- rjf: got resources -> write message + if(ipc_shared_memory_base != 0 && + os_semaphore_take(ipc_lock_semaphore, max_U64)) + { + IPCInfo *ipc_info = (IPCInfo *)ipc_shared_memory_base; + U8 *buffer = (U8 *)(ipc_info+1); + U64 buffer_max = IPC_SHARED_MEMORY_BUFFER_SIZE - sizeof(IPCInfo); + StringJoin join = {str8_lit(""), str8_lit(" "), str8_lit("")}; + String8 msg = str8_list_join(scratch.arena, &cmd_line->inputs, &join); + ipc_info->msg_size = Min(buffer_max, msg.size); + MemoryCopy(buffer, msg.str, ipc_info->msg_size); + os_semaphore_drop(ipc_signal_semaphore); + os_semaphore_drop(ipc_lock_semaphore); + } + + scratch_end(scratch); + }break; + + //- rjf: built-in pdb/dwarf -> raddbg converter mode + case ExecMode_Converter: + { + Temp scratch = scratch_begin(0, 0); + + //- rjf: parse arguments + P2R_User2Convert *user2convert = p2r_user2convert_from_cmdln(scratch.arena, cmd_line); + + //- rjf: open output file + String8 output_name = push_str8_copy(scratch.arena, user2convert->output_name); + OS_Handle out_file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_Write, output_name); + B32 out_file_is_good = !os_handle_match(out_file, os_handle_zero()); + + //- rjf: convert + P2R_Convert2Bake *convert2bake = 0; + if(out_file_is_good) + { + convert2bake = p2r_convert(scratch.arena, user2convert); + } + + //- rjf: bake + P2R_Bake2Serialize *bake2srlz = 0; + ProfScope("bake") + { + bake2srlz = p2r_bake(scratch.arena, convert2bake); + } + + //- rjf: serialize + P2R_Serialize2File *srlz2file = 0; + ProfScope("serialize") + { + srlz2file = push_array(scratch.arena, P2R_Serialize2File, 1); + srlz2file->bundle = rdim_serialized_section_bundle_from_bake_results(&bake2srlz->bake_results); + } + + //- rjf: compress + P2R_Serialize2File *srlz2file_compressed = srlz2file; + if(cmd_line_has_flag(cmd_line, str8_lit("compress"))) ProfScope("compress") + { + srlz2file_compressed = push_array(scratch.arena, P2R_Serialize2File, 1); + srlz2file_compressed = p2r_compress(scratch.arena, srlz2file); + } + + //- rjf: serialize + String8List blobs = rdim_file_blobs_from_section_bundle(scratch.arena, &srlz2file_compressed->bundle); + + //- rjf: write + if(out_file_is_good) + { + U64 off = 0; + for(String8Node *n = blobs.first; n != 0; n = n->next) + { + os_file_write(out_file, r1u64(off, off+n->string.size), n->string.str); + off += n->string.size; + } + } + + //- rjf: close output file + os_file_close(out_file); + + scratch_end(scratch); + }break; + + //- rjf: help message box + case ExecMode_Help: + { + os_graphical_message(0, + str8_lit("The RAD Debugger - Help"), + str8_lit("The following options may be used when starting the RAD Debugger from the command line:\n\n" + "--user:\n" + "Use to specify the location of a user file which should be used. User files are used to store settings for users, including window and panel setups, path mapping, and visual settings. If this file does not exist, it will be created as necessary. This file will be autosaved as user-related changes are made.\n\n" + "--project:\n" + "Use to specify the location of a project file which should be used. Project files are used to store settings for users and projects. If this file does not exist, it will be created as necessary. This file will be autosaved as project-related changes are made.\n\n" + "--auto_step\n" + "This will step into all targets after the debugger initially starts.\n\n" + "--auto_run\n" + "This will run all targets after the debugger initially starts.\n\n" + "--ipc \n" + "This will launch the debugger in the non-graphical IPC mode, which is used to communicate with another running instance of the debugger. The debugger instance will launch, send the specified command, then immediately terminate. This may be used by editors or other programs to control the debugger.\n\n")); + }break; + } + + scratch_end(scratch); +} diff --git a/src/rdi_breakpad_from_pdb/rdi_breakpad_from_pdb_main.c b/src/rdi_breakpad_from_pdb/rdi_breakpad_from_pdb_main.c index d776c7c5..ceb90206 100644 --- a/src/rdi_breakpad_from_pdb/rdi_breakpad_from_pdb_main.c +++ b/src/rdi_breakpad_from_pdb/rdi_breakpad_from_pdb_main.c @@ -1,316 +1,316 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Build Options - -#define BUILD_VERSION_MAJOR 0 -#define BUILD_VERSION_MINOR 9 -#define BUILD_VERSION_PATCH 11 -#define BUILD_RELEASE_PHASE_STRING_LITERAL "ALPHA" -#define BUILD_TITLE "rdi_breakpad_from_pdb" -#define BUILD_CONSOLE_INTERFACE 1 - -//////////////////////////////// -//~ rjf: Includes - -//- rjf: [lib] -#include "lib_rdi_format/rdi_format.h" -#include "lib_rdi_format/rdi_format.c" -#include "lib_rdi_format/rdi_format_parse.h" -#include "lib_rdi_format/rdi_format_parse.c" -#include "third_party/rad_lzb_simple/rad_lzb_simple.h" -#include "third_party/rad_lzb_simple/rad_lzb_simple.c" - -//- rjf: [h] -#include "base/base_inc.h" -#include "os/os_inc.h" -#include "task_system/task_system.h" -#include "rdi_make/rdi_make_local.h" -#include "coff/coff.h" -#include "codeview/codeview.h" -#include "codeview/codeview_stringize.h" -#include "msf/msf.h" -#include "pdb/pdb.h" -#include "pdb/pdb_stringize.h" -#include "rdi_from_pdb/rdi_from_pdb.h" - -//- rjf: [c] -#include "base/base_inc.c" -#include "os/os_inc.c" -#include "task_system/task_system.c" -#include "rdi_make/rdi_make_local.c" -#include "coff/coff.c" -#include "codeview/codeview.c" -#include "codeview/codeview_stringize.c" -#include "msf/msf.c" -#include "pdb/pdb.c" -#include "pdb/pdb_stringize.c" -#include "rdi_from_pdb/rdi_from_pdb.c" - -//////////////////////////////// -//~ rjf: Baking Tasks - -//- rjf: unit vmap baking - -typedef struct P2B_BakeUnitVMapIn P2B_BakeUnitVMapIn; -struct P2B_BakeUnitVMapIn -{ - RDIM_UnitChunkList *units; -}; - -internal TS_TASK_FUNCTION_DEF(p2b_bake_unit_vmap_task__entry_point) -{ - P2B_BakeUnitVMapIn *in = (P2B_BakeUnitVMapIn *)p; - RDIM_UnitVMapBakeResult *out = push_array(arena, RDIM_UnitVMapBakeResult, 1); - *out = rdim_bake_unit_vmap(arena, in->units); - return out; -} - -//- rjf: line table baking - -typedef struct P2B_BakeLineTablesIn P2B_BakeLineTablesIn; -struct P2B_BakeLineTablesIn -{ - RDIM_LineTableChunkList *line_tables; -}; - -internal TS_TASK_FUNCTION_DEF(p2b_bake_line_table_task__entry_point) -{ - P2B_BakeLineTablesIn *in = (P2B_BakeLineTablesIn *)p; - RDIM_LineTableBakeResult *out = push_array(arena, RDIM_LineTableBakeResult, 1); - *out = rdim_bake_line_tables(arena, in->line_tables); - return out; -} - -//- rjf: per-procedure chunk dumping - -typedef struct P2B_DumpProcChunkIn P2B_DumpProcChunkIn; -struct P2B_DumpProcChunkIn -{ - RDI_VMapEntry *unit_vmap; - U32 unit_vmap_count; - U32 *unit_line_table_idxs; - U64 unit_count; - RDIM_LineTableBakeResult *line_tables_bake; - RDIM_SymbolChunkNode *chunk; -}; - -internal TS_TASK_FUNCTION_DEF(p2b_dump_proc_chunk_task__entry_point) -{ - P2B_DumpProcChunkIn *in = (P2B_DumpProcChunkIn *)p; - String8List *out = push_array(arena, String8List, 1); - RDI_LineTable *line_tables = in->line_tables_bake->line_tables; - RDI_U64 line_tables_count = in->line_tables_bake->line_tables_count; - RDI_U64 *line_table_voffs = in->line_tables_bake->line_table_voffs; - RDI_U64 line_table_voffs_count = in->line_tables_bake->line_table_voffs_count; - RDI_Line *line_table_lines = in->line_tables_bake->line_table_lines; - RDI_U64 line_table_lines_count = in->line_tables_bake->line_table_lines_count; - for(U64 idx = 0; idx < in->chunk->count; idx += 1) - { - // NOTE(rjf): breakpad does not support multiple voff ranges per procedure. - RDIM_Symbol *proc = &in->chunk->v[idx]; - RDIM_Scope *root_scope = proc->root_scope; - if(root_scope != 0 && root_scope->voff_ranges.first != 0) - { - // rjf: dump function record - RDIM_Rng1U64 voff_range = root_scope->voff_ranges.first->v; - str8_list_pushf(arena, out, "FUNC %I64x %I64x %I64x %S\n", voff_range.min, voff_range.max-voff_range.min, 0ull, proc->name); - - // rjf: dump function lines - U64 unit_idx = rdi_vmap_idx_from_voff(in->unit_vmap, in->unit_vmap_count, voff_range.min); - if(0 < unit_idx && unit_idx <= in->unit_count) - { - U32 line_table_idx = in->unit_line_table_idxs[unit_idx]; - if(0 < line_table_idx && line_table_idx <= line_tables_count) - { - // rjf: unpack unit line info - RDI_LineTable *line_table = &line_tables[line_table_idx]; - RDI_ParsedLineTable line_info = - { - line_table_voffs + line_table->voffs_base_idx, - line_table_lines + line_table->lines_base_idx, - 0, - line_table->lines_count, - 0 - }; - for(U64 voff = voff_range.min, last_voff = 0; - voff < voff_range.max && voff > last_voff;) - { - RDI_U64 line_info_idx = rdi_line_info_idx_from_voff(&line_info, voff); - if(line_info_idx < line_info.count) - { - RDI_Line *line = &line_info.lines[line_info_idx]; - U64 line_voff_min = line_info.voffs[line_info_idx]; - U64 line_voff_opl = line_info.voffs[line_info_idx+1]; - if(line->file_idx != 0) - { - str8_list_pushf(arena, out, "%I64x %I64x %I64u %I64u\n", - line_voff_min, - line_voff_opl-line_voff_min, - (U64)line->line_num, - (U64)line->file_idx); - } - last_voff = voff; - voff = line_voff_opl; - } - else - { - break; - } - } - } - } - } - } - return out; -} - -//////////////////////////////// -//~ rjf: Entry Point - -internal void -entry_point(CmdLine *cmdline) -{ - //- rjf: initialize state, unpack command line - Arena *arena = arena_alloc(); - B32 do_help = (cmd_line_has_flag(cmdline, str8_lit("help")) || - cmd_line_has_flag(cmdline, str8_lit("h")) || - cmd_line_has_flag(cmdline, str8_lit("?"))); - P2R_User2Convert *user2convert = p2r_user2convert_from_cmdln(arena, cmdline); - user2convert->flags &= ~(P2R_ConvertFlag_Types|P2R_ConvertFlag_UDTs); - - //- rjf: display help - if(do_help || user2convert->errors.node_count != 0) - { - fprintf(stderr, "--- rdi_breakpad_from_pdb -----------------------------------------------------\n\n"); - - fprintf(stderr, "This utility converts debug information from PDBs into the textual Breakpad\n"); - fprintf(stderr, "symbol information format, used for various external utilities, using the RAD\n"); - fprintf(stderr, "Debug Info conversion systems. The following arguments are accepted:\n\n"); - - fprintf(stderr, "--exe: [optional] Specifies the path of the executable file for which the\n"); - fprintf(stderr, " debug info was generated.\n"); - fprintf(stderr, "--pdb: Specifies the path of the PDB debug info file to\n"); - fprintf(stderr, " convert.\n"); - fprintf(stderr, "--out: Specifies the path at which the output Breakpad debug\n"); - fprintf(stderr, " info will be written.\n\n"); - - if(!do_help) - { - for(String8Node *n = user2convert->errors.first; n != 0; n = n->next) - { - fprintf(stderr, "error(input): %.*s\n", str8_varg(n->string)); - } - } - os_exit_process(0); - } - - //- rjf: convert - P2R_Convert2Bake *convert2bake = 0; - ProfScope("convert") - { - convert2bake = p2r_convert(arena, user2convert); - } - - //- rjf: dump breakpad text - String8List dump = {0}; - ProfScope("dump breakpad text") - { - RDIM_BakeParams *params = &convert2bake->bake_params; - - //- rjf: kick off unit vmap baking - P2B_BakeUnitVMapIn bake_unit_vmap_in = {¶ms->units}; - TS_Ticket bake_unit_vmap_ticket = ts_kickoff(p2b_bake_unit_vmap_task__entry_point, 0, &bake_unit_vmap_in); - - //- rjf: kick off line-table baking - P2B_BakeLineTablesIn bake_line_tables_in = {¶ms->line_tables}; - TS_Ticket bake_line_tables_ticket = ts_kickoff(p2b_bake_line_table_task__entry_point, 0, &bake_line_tables_in); - - //- rjf: build unit -> line table idx array - U64 unit_count = params->units.total_count; - U32 *unit_line_table_idxs = push_array(arena, U32, unit_count+1); - { - U64 dst_idx = 1; - for(RDIM_UnitChunkNode *n = params->units.first; n != 0; n = n->next) - { - for(U64 n_idx = 0; n_idx < n->count; n_idx += 1, dst_idx += 1) - { - unit_line_table_idxs[dst_idx] = rdim_idx_from_line_table(n->v[n_idx].line_table); - } - } - } - - //- rjf: dump MODULE record - str8_list_pushf(arena, &dump, "MODULE windows x86_64 %I64x %S\n", params->top_level_info.exe_hash, params->top_level_info.exe_name); - - //- rjf: dump FILE records - ProfScope("dump FILE records") - { - for(RDIM_SrcFileChunkNode *n = params->src_files.first; n != 0; n = n->next) - { - for(U64 idx = 0; idx < n->count; idx += 1) - { - U64 file_idx = rdim_idx_from_src_file(&n->v[idx]); - String8 src_path = n->v[idx].normal_full_path; - str8_list_pushf(arena, &dump, "FILE %I64u %S\n", file_idx, src_path); - } - } - } - - //- rjf: join unit vmap - ProfBegin("join unit vmap"); - RDIM_UnitVMapBakeResult *bake_unit_vmap_out = ts_join_struct(bake_unit_vmap_ticket, max_U64, RDIM_UnitVMapBakeResult); - RDI_VMapEntry *unit_vmap = bake_unit_vmap_out->vmap.vmap; - U32 unit_vmap_count = bake_unit_vmap_out->vmap.count; - ProfEnd(); - - //- rjf: join line tables - ProfBegin("join line table"); - RDIM_LineTableBakeResult *bake_line_tables_out = ts_join_struct(bake_line_tables_ticket, max_U64, RDIM_LineTableBakeResult); - ProfEnd(); - - //- rjf: kick off FUNC & line record dump tasks - P2B_DumpProcChunkIn *dump_proc_chunk_in = push_array(arena, P2B_DumpProcChunkIn, params->procedures.chunk_count); - TS_Ticket *dump_proc_chunk_tickets = push_array(arena, TS_Ticket, params->procedures.chunk_count); - ProfScope("kick off FUNC & line record dump tasks") - { - U64 task_idx = 0; - for(RDIM_SymbolChunkNode *n = params->procedures.first; n != 0; n = n->next, task_idx += 1) - { - dump_proc_chunk_in[task_idx].unit_vmap = unit_vmap; - dump_proc_chunk_in[task_idx].unit_vmap_count = unit_vmap_count; - dump_proc_chunk_in[task_idx].unit_line_table_idxs = unit_line_table_idxs; - dump_proc_chunk_in[task_idx].unit_count = unit_count; - dump_proc_chunk_in[task_idx].line_tables_bake = bake_line_tables_out; - dump_proc_chunk_in[task_idx].chunk = n; - dump_proc_chunk_tickets[task_idx] = ts_kickoff(p2b_dump_proc_chunk_task__entry_point, 0, &dump_proc_chunk_in[task_idx]); - } - } - - //- rjf: join FUNC & line record dump tasks - ProfScope("join FUNC & line record dump tasks") - { - for(U64 idx = 0; idx < params->procedures.chunk_count; idx += 1) - { - String8List *out = ts_join_struct(dump_proc_chunk_tickets[idx], max_U64, String8List); - str8_list_concat_in_place(&dump, out); - } - } - } - - //- rjf: bake - String8 baked = {0}; - ProfScope("bake") - { - baked = str8_list_join(arena, &dump, 0); - } - - //- rjf: write - ProfScope("write") - { - OS_Handle output_file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_Write, user2convert->output_name); - os_file_write(output_file, r1u64(0, baked.size), baked.str); - os_file_close(output_file); - } -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Build Options + +#define BUILD_VERSION_MAJOR 0 +#define BUILD_VERSION_MINOR 9 +#define BUILD_VERSION_PATCH 11 +#define BUILD_RELEASE_PHASE_STRING_LITERAL "ALPHA" +#define BUILD_TITLE "rdi_breakpad_from_pdb" +#define BUILD_CONSOLE_INTERFACE 1 + +//////////////////////////////// +//~ rjf: Includes + +//- rjf: [lib] +#include "lib_rdi_format/rdi_format.h" +#include "lib_rdi_format/rdi_format.c" +#include "lib_rdi_format/rdi_format_parse.h" +#include "lib_rdi_format/rdi_format_parse.c" +#include "third_party/rad_lzb_simple/rad_lzb_simple.h" +#include "third_party/rad_lzb_simple/rad_lzb_simple.c" + +//- rjf: [h] +#include "base/base_inc.h" +#include "os/os_inc.h" +#include "task_system/task_system.h" +#include "rdi_make/rdi_make_local.h" +#include "coff/coff.h" +#include "codeview/codeview.h" +#include "codeview/codeview_stringize.h" +#include "msf/msf.h" +#include "pdb/pdb.h" +#include "pdb/pdb_stringize.h" +#include "rdi_from_pdb/rdi_from_pdb.h" + +//- rjf: [c] +#include "base/base_inc.c" +#include "os/os_inc.c" +#include "task_system/task_system.c" +#include "rdi_make/rdi_make_local.c" +#include "coff/coff.c" +#include "codeview/codeview.c" +#include "codeview/codeview_stringize.c" +#include "msf/msf.c" +#include "pdb/pdb.c" +#include "pdb/pdb_stringize.c" +#include "rdi_from_pdb/rdi_from_pdb.c" + +//////////////////////////////// +//~ rjf: Baking Tasks + +//- rjf: unit vmap baking + +typedef struct P2B_BakeUnitVMapIn P2B_BakeUnitVMapIn; +struct P2B_BakeUnitVMapIn +{ + RDIM_UnitChunkList *units; +}; + +internal TS_TASK_FUNCTION_DEF(p2b_bake_unit_vmap_task__entry_point) +{ + P2B_BakeUnitVMapIn *in = (P2B_BakeUnitVMapIn *)p; + RDIM_UnitVMapBakeResult *out = push_array(arena, RDIM_UnitVMapBakeResult, 1); + *out = rdim_bake_unit_vmap(arena, in->units); + return out; +} + +//- rjf: line table baking + +typedef struct P2B_BakeLineTablesIn P2B_BakeLineTablesIn; +struct P2B_BakeLineTablesIn +{ + RDIM_LineTableChunkList *line_tables; +}; + +internal TS_TASK_FUNCTION_DEF(p2b_bake_line_table_task__entry_point) +{ + P2B_BakeLineTablesIn *in = (P2B_BakeLineTablesIn *)p; + RDIM_LineTableBakeResult *out = push_array(arena, RDIM_LineTableBakeResult, 1); + *out = rdim_bake_line_tables(arena, in->line_tables); + return out; +} + +//- rjf: per-procedure chunk dumping + +typedef struct P2B_DumpProcChunkIn P2B_DumpProcChunkIn; +struct P2B_DumpProcChunkIn +{ + RDI_VMapEntry *unit_vmap; + U32 unit_vmap_count; + U32 *unit_line_table_idxs; + U64 unit_count; + RDIM_LineTableBakeResult *line_tables_bake; + RDIM_SymbolChunkNode *chunk; +}; + +internal TS_TASK_FUNCTION_DEF(p2b_dump_proc_chunk_task__entry_point) +{ + P2B_DumpProcChunkIn *in = (P2B_DumpProcChunkIn *)p; + String8List *out = push_array(arena, String8List, 1); + RDI_LineTable *line_tables = in->line_tables_bake->line_tables; + RDI_U64 line_tables_count = in->line_tables_bake->line_tables_count; + RDI_U64 *line_table_voffs = in->line_tables_bake->line_table_voffs; + RDI_U64 line_table_voffs_count = in->line_tables_bake->line_table_voffs_count; + RDI_Line *line_table_lines = in->line_tables_bake->line_table_lines; + RDI_U64 line_table_lines_count = in->line_tables_bake->line_table_lines_count; + for(U64 idx = 0; idx < in->chunk->count; idx += 1) + { + // NOTE(rjf): breakpad does not support multiple voff ranges per procedure. + RDIM_Symbol *proc = &in->chunk->v[idx]; + RDIM_Scope *root_scope = proc->root_scope; + if(root_scope != 0 && root_scope->voff_ranges.first != 0) + { + // rjf: dump function record + RDIM_Rng1U64 voff_range = root_scope->voff_ranges.first->v; + str8_list_pushf(arena, out, "FUNC %I64x %I64x %I64x %S\n", voff_range.min, voff_range.max-voff_range.min, 0ull, proc->name); + + // rjf: dump function lines + U64 unit_idx = rdi_vmap_idx_from_voff(in->unit_vmap, in->unit_vmap_count, voff_range.min); + if(0 < unit_idx && unit_idx <= in->unit_count) + { + U32 line_table_idx = in->unit_line_table_idxs[unit_idx]; + if(0 < line_table_idx && line_table_idx <= line_tables_count) + { + // rjf: unpack unit line info + RDI_LineTable *line_table = &line_tables[line_table_idx]; + RDI_ParsedLineTable line_info = + { + line_table_voffs + line_table->voffs_base_idx, + line_table_lines + line_table->lines_base_idx, + 0, + line_table->lines_count, + 0 + }; + for(U64 voff = voff_range.min, last_voff = 0; + voff < voff_range.max && voff > last_voff;) + { + RDI_U64 line_info_idx = rdi_line_info_idx_from_voff(&line_info, voff); + if(line_info_idx < line_info.count) + { + RDI_Line *line = &line_info.lines[line_info_idx]; + U64 line_voff_min = line_info.voffs[line_info_idx]; + U64 line_voff_opl = line_info.voffs[line_info_idx+1]; + if(line->file_idx != 0) + { + str8_list_pushf(arena, out, "%I64x %I64x %I64u %I64u\n", + line_voff_min, + line_voff_opl-line_voff_min, + (U64)line->line_num, + (U64)line->file_idx); + } + last_voff = voff; + voff = line_voff_opl; + } + else + { + break; + } + } + } + } + } + } + return out; +} + +//////////////////////////////// +//~ rjf: Entry Point + +internal void +entry_point(CmdLine *cmdline) +{ + //- rjf: initialize state, unpack command line + Arena *arena = arena_alloc(); + B32 do_help = (cmd_line_has_flag(cmdline, str8_lit("help")) || + cmd_line_has_flag(cmdline, str8_lit("h")) || + cmd_line_has_flag(cmdline, str8_lit("?"))); + P2R_User2Convert *user2convert = p2r_user2convert_from_cmdln(arena, cmdline); + user2convert->flags &= ~(P2R_ConvertFlag_Types|P2R_ConvertFlag_UDTs); + + //- rjf: display help + if(do_help || user2convert->errors.node_count != 0) + { + fprintf(stderr, "--- rdi_breakpad_from_pdb -----------------------------------------------------\n\n"); + + fprintf(stderr, "This utility converts debug information from PDBs into the textual Breakpad\n"); + fprintf(stderr, "symbol information format, used for various external utilities, using the RAD\n"); + fprintf(stderr, "Debug Info conversion systems. The following arguments are accepted:\n\n"); + + fprintf(stderr, "--exe: [optional] Specifies the path of the executable file for which the\n"); + fprintf(stderr, " debug info was generated.\n"); + fprintf(stderr, "--pdb: Specifies the path of the PDB debug info file to\n"); + fprintf(stderr, " convert.\n"); + fprintf(stderr, "--out: Specifies the path at which the output Breakpad debug\n"); + fprintf(stderr, " info will be written.\n\n"); + + if(!do_help) + { + for(String8Node *n = user2convert->errors.first; n != 0; n = n->next) + { + fprintf(stderr, "error(input): %.*s\n", str8_varg(n->string)); + } + } + os_abort(0); + } + + //- rjf: convert + P2R_Convert2Bake *convert2bake = 0; + ProfScope("convert") + { + convert2bake = p2r_convert(arena, user2convert); + } + + //- rjf: dump breakpad text + String8List dump = {0}; + ProfScope("dump breakpad text") + { + RDIM_BakeParams *params = &convert2bake->bake_params; + + //- rjf: kick off unit vmap baking + P2B_BakeUnitVMapIn bake_unit_vmap_in = {¶ms->units}; + TS_Ticket bake_unit_vmap_ticket = ts_kickoff(p2b_bake_unit_vmap_task__entry_point, 0, &bake_unit_vmap_in); + + //- rjf: kick off line-table baking + P2B_BakeLineTablesIn bake_line_tables_in = {¶ms->line_tables}; + TS_Ticket bake_line_tables_ticket = ts_kickoff(p2b_bake_line_table_task__entry_point, 0, &bake_line_tables_in); + + //- rjf: build unit -> line table idx array + U64 unit_count = params->units.total_count; + U32 *unit_line_table_idxs = push_array(arena, U32, unit_count+1); + { + U64 dst_idx = 1; + for(RDIM_UnitChunkNode *n = params->units.first; n != 0; n = n->next) + { + for(U64 n_idx = 0; n_idx < n->count; n_idx += 1, dst_idx += 1) + { + unit_line_table_idxs[dst_idx] = rdim_idx_from_line_table(n->v[n_idx].line_table); + } + } + } + + //- rjf: dump MODULE record + str8_list_pushf(arena, &dump, "MODULE windows x86_64 %I64x %S\n", params->top_level_info.exe_hash, params->top_level_info.exe_name); + + //- rjf: dump FILE records + ProfScope("dump FILE records") + { + for(RDIM_SrcFileChunkNode *n = params->src_files.first; n != 0; n = n->next) + { + for(U64 idx = 0; idx < n->count; idx += 1) + { + U64 file_idx = rdim_idx_from_src_file(&n->v[idx]); + String8 src_path = n->v[idx].normal_full_path; + str8_list_pushf(arena, &dump, "FILE %I64u %S\n", file_idx, src_path); + } + } + } + + //- rjf: join unit vmap + ProfBegin("join unit vmap"); + RDIM_UnitVMapBakeResult *bake_unit_vmap_out = ts_join_struct(bake_unit_vmap_ticket, max_U64, RDIM_UnitVMapBakeResult); + RDI_VMapEntry *unit_vmap = bake_unit_vmap_out->vmap.vmap; + U32 unit_vmap_count = bake_unit_vmap_out->vmap.count; + ProfEnd(); + + //- rjf: join line tables + ProfBegin("join line table"); + RDIM_LineTableBakeResult *bake_line_tables_out = ts_join_struct(bake_line_tables_ticket, max_U64, RDIM_LineTableBakeResult); + ProfEnd(); + + //- rjf: kick off FUNC & line record dump tasks + P2B_DumpProcChunkIn *dump_proc_chunk_in = push_array(arena, P2B_DumpProcChunkIn, params->procedures.chunk_count); + TS_Ticket *dump_proc_chunk_tickets = push_array(arena, TS_Ticket, params->procedures.chunk_count); + ProfScope("kick off FUNC & line record dump tasks") + { + U64 task_idx = 0; + for(RDIM_SymbolChunkNode *n = params->procedures.first; n != 0; n = n->next, task_idx += 1) + { + dump_proc_chunk_in[task_idx].unit_vmap = unit_vmap; + dump_proc_chunk_in[task_idx].unit_vmap_count = unit_vmap_count; + dump_proc_chunk_in[task_idx].unit_line_table_idxs = unit_line_table_idxs; + dump_proc_chunk_in[task_idx].unit_count = unit_count; + dump_proc_chunk_in[task_idx].line_tables_bake = bake_line_tables_out; + dump_proc_chunk_in[task_idx].chunk = n; + dump_proc_chunk_tickets[task_idx] = ts_kickoff(p2b_dump_proc_chunk_task__entry_point, 0, &dump_proc_chunk_in[task_idx]); + } + } + + //- rjf: join FUNC & line record dump tasks + ProfScope("join FUNC & line record dump tasks") + { + for(U64 idx = 0; idx < params->procedures.chunk_count; idx += 1) + { + String8List *out = ts_join_struct(dump_proc_chunk_tickets[idx], max_U64, String8List); + str8_list_concat_in_place(&dump, out); + } + } + } + + //- rjf: bake + String8 baked = {0}; + ProfScope("bake") + { + baked = str8_list_join(arena, &dump, 0); + } + + //- rjf: write + ProfScope("write") + { + OS_Handle output_file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_Write, user2convert->output_name); + os_file_write(output_file, r1u64(0, baked.size), baked.str); + os_file_close(output_file); + } +} diff --git a/src/rdi_from_pdb/rdi_from_pdb.c b/src/rdi_from_pdb/rdi_from_pdb.c index de944eea..7057595e 100644 --- a/src/rdi_from_pdb/rdi_from_pdb.c +++ b/src/rdi_from_pdb/rdi_from_pdb.c @@ -1,4862 +1,4862 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -// TODO(rjf): eliminate redundant null checks, just always allocate -// empty results, and have nulls gracefully fall through -// -// (search for != 0 instances, inserted to prevent prior crashes) - -//////////////////////////////// -//~ rjf: Basic Helpers - -internal U64 -p2r_end_of_cplusplus_container_name(String8 str) -{ - // NOTE: This finds the index one past the last "::" contained in str. - // if no "::" is contained in str, then the returned index is 0. - // The intent is that [0,clamp_bot(0,result - 2)) gives the - // "container name" and [result,str.size) gives the leaf name. - U64 result = 0; - if(str.size >= 2) - { - for(U64 i = str.size; i >= 2; i -= 1) - { - if(str.str[i - 2] == ':' && str.str[i - 1] == ':') - { - result = i; - break; - } - } - } - return(result); -} - -internal U64 -p2r_hash_from_voff(U64 voff) -{ - U64 hash = (voff >> 3) ^ ((7 & voff) << 6); - return hash; -} - -//////////////////////////////// -//~ rjf: Command Line -> Conversion Inputs - -internal P2R_User2Convert * -p2r_user2convert_from_cmdln(Arena *arena, CmdLine *cmdline) -{ - P2R_User2Convert *result = push_array(arena, P2R_User2Convert, 1); - - //- rjf: get input pdb - { - String8 input_name = cmd_line_string(cmdline, str8_lit("pdb")); - if(input_name.size == 0) - { - str8_list_push(arena, &result->errors, str8_lit("Missing required parameter: '--pdb:'")); - } - if(input_name.size > 0) - { - String8 input_data = os_data_from_file_path(arena, input_name); - if(input_data.size == 0) - { - str8_list_pushf(arena, &result->errors, "Could not load input PDB file from '%S'", input_name); - } - if(input_data.size != 0) - { - result->input_pdb_name = input_name; - result->input_pdb_data = input_data; - } - } - } - - //- rjf: get input exe - { - String8 input_name = cmd_line_string(cmdline, str8_lit("exe")); - if(input_name.size > 0) - { - String8 input_data = os_data_from_file_path(arena, input_name); - if(input_data.size == 0) - { - str8_list_pushf(arena, &result->errors, "Could not load input EXE file from '%S'", input_name); - } - if(input_data.size != 0) - { - result->input_exe_name = input_name; - result->input_exe_data = input_data; - } - } - } - - //- rjf: get output name - { - result->output_name = cmd_line_string(cmdline, str8_lit("out")); - if(result->output_name.size == 0) - { - str8_list_pushf(arena, &result->errors, "Missing required parameter: '--out:'"); - } - } - - //- rjf: define string -> flag bits -#define FlagNameMapXList \ -Case("sections", BinarySections)\ -Case("units", Units)\ -Case("procedures", Procedures)\ -Case("globals", GlobalVariables)\ -Case("threadvars", ThreadVariables)\ -Case("scopes", Scopes)\ -Case("locals", Locals)\ -Case("types", Types)\ -Case("udts", UDTs)\ -Case("lines", LineInfo)\ -Case("globals_name_map", GlobalVariableNameMap)\ -Case("threadvars_name_map", ThreadVariableNameMap)\ -Case("procedure_name_map", ProcedureNameMap)\ -Case("type_name_map", TypeNameMap)\ -Case("link_name_map", LinkNameProcedureNameMap)\ -Case("source_path_name_map",NormalSourcePathNameMap)\ - - //- rjf: get flags - { - result->flags = P2R_ConvertFlag_All; - String8List only_names = cmd_line_strings(cmdline, str8_lit("only")); - String8List omit_names = cmd_line_strings(cmdline, str8_lit("only")); - if(only_names.node_count != 0) - { - result->flags = 0; - for(String8Node *n = only_names.first; n != 0; n = n->next) - { - String8 string = n->string; -#define Case(str, flag) if(str8_match(string, str8_lit(str), StringMatchFlag_CaseInsensitive)) {result->flags |= P2R_ConvertFlag_##flag;} - FlagNameMapXList; -#undef Case - } - } - if(omit_names.node_count != 0) - { - for(String8Node *n = omit_names.first; n != 0; n = n->next) - { - String8 string = n->string; -#define Case(str, flag) if(str8_match(string, str8_lit(str), StringMatchFlag_CaseInsensitive)) {result->flags &= ~P2R_ConvertFlag_##flag;} - FlagNameMapXList; -#undef Case - } - } - } - -#undef FlagNameMapXList - return result; -} - -//////////////////////////////// -//~ rjf: COFF <-> RDI Canonical Conversions - -internal RDI_BinarySectionFlags -p2r_rdi_binary_section_flags_from_coff_section_flags(COFF_SectionFlags flags) -{ - RDI_BinarySectionFlags result = 0; - if(flags & COFF_SectionFlag_MEM_READ) - { - result |= RDI_BinarySectionFlag_Read; - } - if(flags & COFF_SectionFlag_MEM_WRITE) - { - result |= RDI_BinarySectionFlag_Write; - } - if(flags & COFF_SectionFlag_MEM_EXECUTE) - { - result |= RDI_BinarySectionFlag_Execute; - } - return(result); -} - -//////////////////////////////// -//~ rjf: CodeView <-> RDI Canonical Conversions - -internal RDI_Arch -p2r_rdi_arch_from_cv_arch(CV_Arch cv_arch) -{ - RDI_Arch result = 0; - switch(cv_arch) - { - case CV_Arch_8086: result = RDI_Arch_X86; break; - case CV_Arch_X64: result = RDI_Arch_X64; break; - //case CV_Arch_8080: break; - //case CV_Arch_80286: break; - //case CV_Arch_80386: break; - //case CV_Arch_80486: break; - //case CV_Arch_PENTIUM: break; - //case CV_Arch_PENTIUMII: break; - //case CV_Arch_PENTIUMIII: break; - //case CV_Arch_MIPS: break; - //case CV_Arch_MIPS16: break; - //case CV_Arch_MIPS32: break; - //case CV_Arch_MIPS64: break; - //case CV_Arch_MIPSI: break; - //case CV_Arch_MIPSII: break; - //case CV_Arch_MIPSIII: break; - //case CV_Arch_MIPSIV: break; - //case CV_Arch_MIPSV: break; - //case CV_Arch_M68000: break; - //case CV_Arch_M68010: break; - //case CV_Arch_M68020: break; - //case CV_Arch_M68030: break; - //case CV_Arch_M68040: break; - //case CV_Arch_ALPHA: break; - //case CV_Arch_ALPHA_21164: break; - //case CV_Arch_ALPHA_21164A: break; - //case CV_Arch_ALPHA_21264: break; - //case CV_Arch_ALPHA_21364: break; - //case CV_Arch_PPC601: break; - //case CV_Arch_PPC603: break; - //case CV_Arch_PPC604: break; - //case CV_Arch_PPC620: break; - //case CV_Arch_PPCFP: break; - //case CV_Arch_PPCBE: break; - //case CV_Arch_SH3: break; - //case CV_Arch_SH3E: break; - //case CV_Arch_SH3DSP: break; - //case CV_Arch_SH4: break; - //case CV_Arch_SHMEDIA: break; - //case CV_Arch_ARM3: break; - //case CV_Arch_ARM4: break; - //case CV_Arch_ARM4T: break; - //case CV_Arch_ARM5: break; - //case CV_Arch_ARM5T: break; - //case CV_Arch_ARM6: break; - //case CV_Arch_ARM_XMAC: break; - //case CV_Arch_ARM_WMMX: break; - //case CV_Arch_ARM7: break; - //case CV_Arch_OMNI: break; - //case CV_Arch_IA64_1: break; - //case CV_Arch_IA64_2: break; - //case CV_Arch_CEE: break; - //case CV_Arch_AM33: break; - //case CV_Arch_M32R: break; - //case CV_Arch_TRICORE: break; - //case CV_Arch_EBC: break; - //case CV_Arch_THUMB: break; - //case CV_Arch_ARMNT: break; - //case CV_Arch_ARM64: break; - //case CV_Arch_D3D11_SHADER: break; - } - return(result); -} - -internal RDI_RegCode -p2r_rdi_reg_code_from_cv_reg_code(RDI_Arch arch, CV_Reg reg_code) -{ - RDI_RegCode result = 0; - switch(arch) - { - case RDI_Arch_X86: - { - switch(reg_code) - { -#define X(CVN,C,RDN,BP,BZ) case C: result = RDI_RegCodeX86_##RDN; break; - CV_Reg_X86_XList(X) -#undef X - } - }break; - case RDI_Arch_X64: - { - switch(reg_code) - { -#define X(CVN,C,RDN,BP,BZ) case C: result = RDI_RegCodeX64_##RDN; break; - CV_Reg_X64_XList(X) -#undef X - } - }break; - } - return(result); -} - -internal RDI_Language -p2r_rdi_language_from_cv_language(CV_Language cv_language) -{ - RDI_Language result = 0; - switch(cv_language) - { - case CV_Language_C: result = RDI_Language_C; break; - case CV_Language_CXX: result = RDI_Language_CPlusPlus; break; - //case CV_Language_FORTRAN: result = ; break; - //case CV_Language_MASM: result = ; break; - //case CV_Language_PASCAL: result = ; break; - //case CV_Language_BASIC: result = ; break; - //case CV_Language_COBOL: result = ; break; - //case CV_Language_LINK: result = ; break; - //case CV_Language_CVTRES: result = ; break; - //case CV_Language_CVTPGD: result = ; break; - //case CV_Language_CSHARP: result = ; break; - //case CV_Language_VB: result = ; break; - //case CV_Language_ILASM: result = ; break; - //case CV_Language_JAVA: result = ; break; - //case CV_Language_JSCRIPT: result = ; break; - //case CV_Language_MSIL: result = ; break; - //case CV_Language_HLSL: result = ; break; - } - return(result); -} - -internal RDI_TypeKind -p2r_rdi_type_kind_from_cv_basic_type(CV_BasicType basic_type) -{ - RDI_TypeKind result = RDI_TypeKind_NULL; - switch(basic_type) - { - case CV_BasicType_VOID: {result = RDI_TypeKind_Void;}break; - case CV_BasicType_HRESULT: {result = RDI_TypeKind_Handle;}break; - - case CV_BasicType_RCHAR: - case CV_BasicType_CHAR: - case CV_BasicType_CHAR8: - {result = RDI_TypeKind_Char8;}break; - - case CV_BasicType_UCHAR: {result = RDI_TypeKind_UChar8;}break; - case CV_BasicType_WCHAR: {result = RDI_TypeKind_UChar16;}break; - case CV_BasicType_CHAR16: {result = RDI_TypeKind_Char16;}break; - case CV_BasicType_CHAR32: {result = RDI_TypeKind_Char32;}break; - - case CV_BasicType_BOOL8: - case CV_BasicType_INT8: - {result = RDI_TypeKind_S8;}break; - - case CV_BasicType_BOOL16: - case CV_BasicType_INT16: - case CV_BasicType_SHORT: - {result = RDI_TypeKind_S16;}break; - - case CV_BasicType_BOOL32: - case CV_BasicType_INT32: - case CV_BasicType_LONG: - {result = RDI_TypeKind_S32;}break; - - case CV_BasicType_BOOL64: - case CV_BasicType_INT64: - case CV_BasicType_QUAD: - {result = RDI_TypeKind_S64;}break; - - case CV_BasicType_INT128: - case CV_BasicType_OCT: - {result = RDI_TypeKind_S128;}break; - - case CV_BasicType_UINT8: {result = RDI_TypeKind_U8;}break; - - case CV_BasicType_UINT16: - case CV_BasicType_USHORT: - {result = RDI_TypeKind_U16;}break; - - case CV_BasicType_UINT32: - case CV_BasicType_ULONG: - {result = RDI_TypeKind_U32;}break; - - case CV_BasicType_UINT64: - case CV_BasicType_UQUAD: - {result = RDI_TypeKind_U64;}break; - - case CV_BasicType_UINT128: - case CV_BasicType_UOCT: - {result = RDI_TypeKind_U128;}break; - - case CV_BasicType_FLOAT16:{result = RDI_TypeKind_F16;}break; - case CV_BasicType_FLOAT32:{result = RDI_TypeKind_F32;}break; - case CV_BasicType_FLOAT32PP:{result = RDI_TypeKind_F32PP;}break; - case CV_BasicType_FLOAT48:{result = RDI_TypeKind_F48;}break; - case CV_BasicType_FLOAT64:{result = RDI_TypeKind_F64;}break; - case CV_BasicType_FLOAT80:{result = RDI_TypeKind_F80;}break; - case CV_BasicType_FLOAT128:{result = RDI_TypeKind_F128;}break; - case CV_BasicType_COMPLEX32:{result = RDI_TypeKind_ComplexF32;}break; - case CV_BasicType_COMPLEX64:{result = RDI_TypeKind_ComplexF64;}break; - case CV_BasicType_COMPLEX80:{result = RDI_TypeKind_ComplexF80;}break; - case CV_BasicType_COMPLEX128:{result = RDI_TypeKind_ComplexF128;}break; - case CV_BasicType_PTR:{result = RDI_TypeKind_Handle;}break; - } - return result; -} - -//////////////////////////////// -//~ rjf: Location Info Building Helpers - -internal RDIM_Location * -p2r_location_from_addr_reg_off(Arena *arena, RDI_Arch arch, RDI_RegCode reg_code, U32 reg_byte_size, U32 reg_byte_pos, S64 offset, B32 extra_indirection) -{ - RDIM_Location *result = 0; - if(0 <= offset && offset <= (S64)max_U16) - { - if(extra_indirection) - { - result = rdim_push_location_addr_addr_reg_plus_u16(arena, reg_code, (U16)offset); - } - else - { - result = rdim_push_location_addr_reg_plus_u16(arena, reg_code, (U16)offset); - } - } - else - { - RDIM_EvalBytecode bytecode = {0}; - U32 regread_param = RDI_EncodeRegReadParam(reg_code, reg_byte_size, reg_byte_pos); - rdim_bytecode_push_op(arena, &bytecode, RDI_EvalOp_RegRead, regread_param); - rdim_bytecode_push_sconst(arena, &bytecode, offset); - rdim_bytecode_push_op(arena, &bytecode, RDI_EvalOp_Add, 0); - if(extra_indirection) - { - U64 addr_size = rdi_addr_size_from_arch(arch); - rdim_bytecode_push_op(arena, &bytecode, RDI_EvalOp_MemRead, addr_size); - } - result = rdim_push_location_addr_bytecode_stream(arena, &bytecode); - } - return result; -} - -internal CV_EncodedFramePtrReg -p2r_cv_encoded_fp_reg_from_frameproc(CV_SymFrameproc *frameproc, B32 param_base) -{ - CV_EncodedFramePtrReg result = 0; - CV_FrameprocFlags flags = frameproc->flags; - if(param_base) - { - result = CV_FrameprocFlags_ExtractParamBasePointer(flags); - } - else - { - result = CV_FrameprocFlags_ExtractLocalBasePointer(flags); - } - return result; -} - -internal RDI_RegCode -p2r_reg_code_from_arch_encoded_fp_reg(RDI_Arch arch, CV_EncodedFramePtrReg encoded_reg) -{ - RDI_RegCode result = 0; - switch(arch) - { - case RDI_Arch_X86: - { - switch(encoded_reg) - { - case CV_EncodedFramePtrReg_StackPtr: - { - // TODO(allen): support CV_AllReg_VFRAME - // TODO(allen): error - }break; - case CV_EncodedFramePtrReg_FramePtr: - { - result = RDI_RegCodeX86_ebp; - }break; - case CV_EncodedFramePtrReg_BasePtr: - { - result = RDI_RegCodeX86_ebx; - }break; - } - }break; - case RDI_Arch_X64: - { - switch(encoded_reg) - { - case CV_EncodedFramePtrReg_StackPtr: - { - result = RDI_RegCodeX64_rsp; - }break; - case CV_EncodedFramePtrReg_FramePtr: - { - result = RDI_RegCodeX64_rbp; - }break; - case CV_EncodedFramePtrReg_BasePtr: - { - result = RDI_RegCodeX64_r13; - }break; - } - }break; - } - return(result); -} - -internal void -p2r_location_over_lvar_addr_range(Arena *arena, RDIM_ScopeChunkList *scopes, RDIM_LocationSet *locset, RDIM_Location *location, CV_LvarAddrRange *range, COFF_SectionHeader *section, CV_LvarAddrGap *gaps, U64 gap_count) -{ - //- rjf: extract range info - U64 voff_first = 0; - U64 voff_opl = 0; - if(section != 0) - { - voff_first = section->voff + range->off; - voff_opl = voff_first + range->len; - } - - //- rjf: emit ranges - CV_LvarAddrGap *gap_ptr = gaps; - U64 voff_cursor = voff_first; - for(U64 i = 0; i < gap_count; i += 1, gap_ptr += 1) - { - U64 voff_gap_first = voff_first + gap_ptr->off; - U64 voff_gap_opl = voff_gap_first + gap_ptr->len; - if(voff_cursor < voff_gap_first) - { - RDIM_Rng1U64 voff_range = {voff_cursor, voff_gap_first}; - rdim_location_set_push_case(arena, scopes, locset, voff_range, location); - } - voff_cursor = voff_gap_opl; - } - - //- rjf: emit remaining range - if(voff_cursor < voff_opl) - { - RDIM_Rng1U64 voff_range = {voff_cursor, voff_opl}; - rdim_location_set_push_case(arena, scopes, locset, voff_range, location); - } -} - -//////////////////////////////// -//~ rjf: Initial Parsing & Preparation Pass Tasks - -internal TS_TASK_FUNCTION_DEF(p2r_exe_hash_task__entry_point) -{ - P2R_EXEHashIn *in = (P2R_EXEHashIn *)p; - U64 *out = push_array(arena, U64, 1); - ProfScope("hash exe") *out = rdi_hash(in->exe_data.str, in->exe_data.size); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_tpi_hash_parse_task__entry_point) -{ - P2R_TPIHashParseIn *in = (P2R_TPIHashParseIn *)p; - void *out = 0; - ProfScope("parse tpi hash") out = pdb_tpi_hash_from_data(arena, in->strtbl, in->tpi, in->hash_data, in->aux_data); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_tpi_leaf_parse_task__entry_point) -{ - P2R_TPILeafParseIn *in = (P2R_TPILeafParseIn *)p; - void *out = 0; - ProfScope("parse tpi leaf") out = cv_leaf_from_data(arena, in->leaf_data, in->itype_first); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_symbol_stream_parse_task__entry_point) -{ - P2R_SymbolStreamParseIn *in = (P2R_SymbolStreamParseIn *)p; - void *out = 0; - ProfScope("parse symbol stream") out = cv_sym_from_data(arena, in->data, 4); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_c13_stream_parse_task__entry_point) -{ - P2R_C13StreamParseIn *in = (P2R_C13StreamParseIn *)p; - void *out = 0; - ProfScope("parse c13 stream") out = cv_c13_parsed_from_data(arena, in->data, in->strtbl, in->coff_sections); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_comp_unit_parse_task__entry_point) -{ - P2R_CompUnitParseIn *in = (P2R_CompUnitParseIn *)p; - void *out = 0; - ProfScope("parse comp units") out = pdb_comp_unit_array_from_data(arena, in->data); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_comp_unit_contributions_parse_task__entry_point) -{ - P2R_CompUnitContributionsParseIn *in = (P2R_CompUnitContributionsParseIn *)p; - void *out = 0; - ProfScope("parse comp unit contributions") out = pdb_comp_unit_contribution_array_from_data(arena, in->data, in->coff_sections); - return out; -} - -//////////////////////////////// -//~ rjf: Unit Conversion Tasks - -internal TS_TASK_FUNCTION_DEF(p2r_units_convert_task__entry_point) -{ - Temp scratch = scratch_begin(&arena, 1); - P2R_UnitConvertIn *in = (P2R_UnitConvertIn *)p; - P2R_UnitConvertOut *out = push_array(arena, P2R_UnitConvertOut, 1); - ProfScope("build units, initial src file map, & collect unit source files") - if(in->comp_units != 0) - { - U64 units_chunk_cap = in->comp_units->count; - P2R_SrcFileMap src_file_map = {0}; - src_file_map.slots_count = 65536; - src_file_map.slots = push_array(scratch.arena, P2R_SrcFileNode *, src_file_map.slots_count); - - //////////////////////////// - //- rjf: pass 1: build per-unit info & per-unit line tables - // - ProfScope("pass 1: build per-unit info & per-unit line tables") - for(U64 comp_unit_idx = 0; comp_unit_idx < in->comp_units->count; comp_unit_idx += 1) - { - PDB_CompUnit *pdb_unit = in->comp_units->units[comp_unit_idx]; - CV_SymParsed *pdb_unit_sym = in->comp_unit_syms[comp_unit_idx]; - CV_C13Parsed *pdb_unit_c13 = in->comp_unit_c13s[comp_unit_idx]; - - //- rjf: produce unit name - String8 unit_name = pdb_unit->obj_name; - if(unit_name.size != 0) - { - String8 unit_name_past_last_slash = str8_skip_last_slash(unit_name); - if(unit_name_past_last_slash.size != 0) - { - unit_name = unit_name_past_last_slash; - } - } - - //- rjf: produce obj name - String8 obj_name = pdb_unit->obj_name; - if(str8_match(obj_name, str8_lit("* Linker *"), 0) || - str8_match(obj_name, str8_lit("Import:"), StringMatchFlag_RightSideSloppy)) - { - MemoryZeroStruct(&obj_name); - } - - //- rjf: build this unit's line table, fill out primary line info (inline info added after) - RDIM_LineTable *line_table = 0; - for(CV_C13SubSectionNode *node = pdb_unit_c13->first_sub_section; - node != 0; - node = node->next) - { - if(node->kind == CV_C13SubSectionKind_Lines) - { - for(CV_C13LinesParsedNode *lines_n = node->lines_first; - lines_n != 0; - lines_n = lines_n->next) - { - CV_C13LinesParsed *lines = &lines_n->v; - - // rjf: file name -> normalized file path - String8 file_path = lines->file_name; - String8 file_path_normalized = lower_from_str8(scratch.arena, str8_skip_chop_whitespace(file_path)); - for(U64 idx = 0; idx < file_path_normalized.size; idx += 1) - { - if(file_path_normalized.str[idx] == '\\') - { - file_path_normalized.str[idx] = '/'; - } - } - - // rjf: normalized file path -> source file node - U64 file_path_normalized_hash = rdi_hash(file_path_normalized.str, file_path_normalized.size); - U64 src_file_slot = file_path_normalized_hash%src_file_map.slots_count; - P2R_SrcFileNode *src_file_node = 0; - for(P2R_SrcFileNode *n = src_file_map.slots[src_file_slot]; n != 0; n = n->next) - { - if(str8_match(n->src_file->normal_full_path, file_path_normalized, 0)) - { - src_file_node = n; - break; - } - } - if(src_file_node == 0) - { - src_file_node = push_array(scratch.arena, P2R_SrcFileNode, 1); - SLLStackPush(src_file_map.slots[src_file_slot], src_file_node); - src_file_node->src_file = rdim_src_file_chunk_list_push(arena, &out->src_files, 4096); - src_file_node->src_file->normal_full_path = push_str8_copy(arena, file_path_normalized); - } - - // rjf: push sequence into both line table & source file's line map - if(lines->line_count != 0) - { - if(line_table == 0) - { - line_table = rdim_line_table_chunk_list_push(arena, &out->line_tables, 256); - } - RDIM_LineSequence *seq = rdim_line_table_push_sequence(arena, &out->line_tables, line_table, src_file_node->src_file, lines->voffs, lines->line_nums, lines->col_nums, lines->line_count); - rdim_src_file_push_line_sequence(arena, &out->src_files, src_file_node->src_file, seq); - } - } - } - } - - //- rjf: build unit - RDIM_Unit *dst_unit = rdim_unit_chunk_list_push(arena, &out->units, units_chunk_cap); - dst_unit->unit_name = unit_name; - dst_unit->compiler_name = pdb_unit_sym->info.compiler_name; - dst_unit->object_file = obj_name; - dst_unit->archive_file = pdb_unit->group_name; - dst_unit->language = p2r_rdi_language_from_cv_language(pdb_unit_sym->info.language); - dst_unit->line_table = line_table; - } - - //////////////////////////// - //- rjf: pass 2: build per-unit voff ranges from comp unit contributions table - // - PDB_CompUnitContribution *contrib_ptr = in->comp_unit_contributions->contributions; - PDB_CompUnitContribution *contrib_opl = contrib_ptr + in->comp_unit_contributions->count; - ProfScope("pass 2: build per-unit voff ranges from comp unit contributions table") - for(;contrib_ptr < contrib_opl; contrib_ptr += 1) - { - if(contrib_ptr->mod < in->comp_units->count) - { - RDIM_Unit *unit = &out->units.first->v[contrib_ptr->mod]; - RDIM_Rng1U64 range = {contrib_ptr->voff_first, contrib_ptr->voff_opl}; - rdim_rng1u64_list_push(arena, &unit->voff_ranges, range); - } - } - - //////////////////////////// - //- rjf: pass 3: parse all inlinee line tables - // - out->units_first_inline_site_line_tables = push_array(arena, RDIM_LineTable *, in->comp_units->count); - ProfScope("pass 3: parse all inlinee line tables") - for(U64 comp_unit_idx = 0; comp_unit_idx < in->comp_units->count; comp_unit_idx += 1) - { - CV_SymParsed *unit_sym = in->comp_unit_syms[comp_unit_idx]; - CV_C13Parsed *unit_c13 = in->comp_unit_c13s[comp_unit_idx]; - CV_RecRange *rec_ranges_first = unit_sym->sym_ranges.ranges; - CV_RecRange *rec_ranges_opl = rec_ranges_first+unit_sym->sym_ranges.count; - U64 base_voff = 0; - for(CV_RecRange *rec_range = rec_ranges_first; - rec_range < rec_ranges_opl; - rec_range += 1) - { - //- rjf: rec range -> symbol info range - U64 sym_off_first = rec_range->off + 2; - U64 sym_off_opl = rec_range->off + rec_range->hdr.size; - - //- rjf: skip invalid ranges - if(sym_off_opl > unit_sym->data.size || sym_off_first > unit_sym->data.size || sym_off_first > sym_off_opl) - { - continue; - } - - //- rjf: unpack symbol info - CV_SymKind kind = rec_range->hdr.kind; - U64 sym_header_struct_size = cv_header_struct_size_from_sym_kind(kind); - void *sym_header_struct_base = unit_sym->data.str + sym_off_first; - void *sym_data_opl = unit_sym->data.str + sym_off_opl; - - //- rjf: skip bad sizes - if(sym_off_first + sym_header_struct_size > sym_off_opl) - { - continue; - } - - //- rjf: process symbol - switch(kind) - { - default:{}break; - - //- rjf: LPROC32/GPROC32 (gather base address) - case CV_SymKind_LPROC32: - case CV_SymKind_GPROC32: - { - CV_SymProc32 *proc32 = (CV_SymProc32 *)sym_header_struct_base; - COFF_SectionHeader *section = (0 < proc32->sec && proc32->sec <= in->coff_sections->count) ? &in->coff_sections->sections[proc32->sec-1] : 0; - if(section != 0) - { - base_voff = section->voff + proc32->off; - } - }break; - - //- rjf: INLINESITE - case CV_SymKind_INLINESITE: - { - // rjf: unpack sym - CV_SymInlineSite *sym = (CV_SymInlineSite *)sym_header_struct_base; - String8 binary_annots = str8((U8 *)(sym+1), rec_range->hdr.size - sizeof(rec_range->hdr.kind) - sizeof(*sym)); - - // rjf: map inlinee -> parsed cv c13 inlinee line info - CV_C13InlineeLinesParsed *inlinee_lines_parsed = 0; - { - U64 hash = cv_hash_from_item_id(sym->inlinee); - U64 slot_idx = hash%unit_c13->inlinee_lines_parsed_slots_count; - for(CV_C13InlineeLinesParsedNode *n = unit_c13->inlinee_lines_parsed_slots[slot_idx]; n != 0; n = n->hash_next) - { - if(n->v.inlinee == sym->inlinee) - { - inlinee_lines_parsed = &n->v; - break; - } - } - } - - // rjf: build line table, fill with parsed binary annotations - RDIM_LineTable *line_table = 0; - if(inlinee_lines_parsed != 0) - { - // rjf: state machine registers - CV_InlineRangeKind range_kind = 0; - U32 code_length = 0; - U32 code_offset = 0; - U32 last_code_offset = code_offset; - String8 file_name = inlinee_lines_parsed->file_name; - String8 last_file_name = file_name; - S32 line = (S32)inlinee_lines_parsed->first_source_ln; - S32 last_line = line; - S32 column = 1; - S32 last_column = column; - - // rjf: gathered lines - typedef struct LineChunk LineChunk; - struct LineChunk - { - LineChunk *next; - U64 cap; - U64 count; - U64 *voffs; // [line_count + 1] (sorted) - U32 *line_nums; // [line_count] - U16 *col_nums; // [2*line_count] - }; - LineChunk *first_line_chunk = 0; - LineChunk *last_line_chunk = 0; - U64 total_line_chunk_line_count = 0; - - // rjf: grab checksums sub-section - CV_C13SubSectionNode *file_chksms = unit_c13->file_chksms_sub_section; - - // rjf: decode loop - U64 read_off = 0; - U64 read_off_opl = binary_annots.size; - for(B32 good = 1; read_off < read_off_opl && good;) - { - // rjf: decode next annotation op - U32 op = CV_InlineBinaryAnnotation_Null; - read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &op); - - // rjf: apply op - switch(op) - { - default:{good = 0;}break; - case CV_InlineBinaryAnnotation_Null: - { - good = 0; - }break; - case CV_InlineBinaryAnnotation_CodeOffset: - { - read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_offset); - }break; - case CV_InlineBinaryAnnotation_ChangeCodeOffsetBase: - { - good = 0; - // TODO(rjf): currently untested/unknown - first guess below: - // - // U32 delta = 0; - // read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &delta); - // code_offset_base = code_offset; - // code_offset_end = code_offset + delta; - // code_offset += delta; - }break; - case CV_InlineBinaryAnnotation_ChangeCodeOffset: - { - U32 delta = 0; - read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &delta); - code_offset += delta; - }break; - case CV_InlineBinaryAnnotation_ChangeCodeLength: - { - code_length = 0; - read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_length); - }break; - case CV_InlineBinaryAnnotation_ChangeFile: - { - U32 new_file_off = 0; - read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &new_file_off); - String8 new_file_name = {0}; - if(new_file_off + sizeof(CV_C13Checksum) <= file_chksms->size) - { - CV_C13Checksum *checksum = (CV_C13Checksum*)(unit_c13->data.str + file_chksms->off + new_file_off); - U32 name_off = checksum->name_off; - new_file_name = pdb_strtbl_string_from_off(in->pdb_strtbl, name_off); - } - file_name = new_file_name; - }break; - case CV_InlineBinaryAnnotation_ChangeLineOffset: - { - S32 delta = 0; - read_off += cv_decode_inline_annot_s32(binary_annots, read_off, &delta); - line += delta; - }break; - case CV_InlineBinaryAnnotation_ChangeLineEndDelta: - { - good = 0; - // TODO(rjf): currently untested/unknown - first guess below: - // - // S32 end_delta = 1; - // read_off += cv_decode_inline_annot_s32(binary_annots, read_off, &end_delta); - // line += end_delta; - }break; - case CV_InlineBinaryAnnotation_ChangeRangeKind: - { - good = 0; - // TODO(rjf): currently untested/unknown - first guess below: - // - // read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &range_kind); - }break; - case CV_InlineBinaryAnnotation_ChangeColumnStart: - { - good = 0; - // TODO(rjf): currently untested/unknown - first guess below: - // - // S32 delta = 0; - // read_off += cv_decode_inline_annot_s32(binary_annots, read_off, &delta); - // column += delta; - }break; - case CV_InlineBinaryAnnotation_ChangeColumnEndDelta: - { - // TODO(rjf): currently untested/unknown - first guess below: - // - // S32 end_delta = 0; - // read_off += cv_decode_inline_annot_s32(binary_annots, read_off, &end_delta); - // column += end_delta; - }break; - case CV_InlineBinaryAnnotation_ChangeCodeOffsetAndLineOffset: - { - U32 code_offset_and_line_offset = 0; - read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_offset_and_line_offset); - U32 code_delta = (code_offset_and_line_offset & 0xf); - S32 line_delta = cv_inline_annot_signed_from_unsigned_operand(code_offset_and_line_offset >> 4); - code_offset += code_delta; - line += line_delta; - }break; - case CV_InlineBinaryAnnotation_ChangeCodeLengthAndCodeOffset: - { - U32 offset_delta = 0; - read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_length); - read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &offset_delta); - code_offset += offset_delta; - }break; - case CV_InlineBinaryAnnotation_ChangeColumnEnd: - { - // TODO(rjf): currently untested/unknown - first guess below: - // - // U32 column_end = 0; - // read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &column_end); - }break; - } - - // rjf: gather new lines - if(!good || line != last_line || code_offset != last_code_offset) - { - LineChunk *chunk = last_line_chunk; - if(chunk == 0 || chunk->count+1 >= chunk->cap) - { - chunk = push_array(scratch.arena, LineChunk, 1); - SLLQueuePush(first_line_chunk, last_line_chunk, chunk); - chunk->cap = 256; - chunk->voffs = push_array_no_zero(scratch.arena, U64, chunk->cap); - chunk->line_nums = push_array_no_zero(scratch.arena, U32, chunk->cap); - } - chunk->voffs[chunk->count] = base_voff + code_offset; - chunk->voffs[chunk->count+1] = base_voff + code_offset + code_length; - chunk->line_nums[chunk->count] = (U32)line; - chunk->count += 1; - total_line_chunk_line_count += 1; - } - - // rjf: push line sequence to line table & source file - if(!good || (op == CV_InlineBinaryAnnotation_ChangeFile && !str8_match(last_file_name, file_name, 0))) - { - String8 seq_file_name = last_file_name; // NOTE(rjf): `file_name` is possibly changed to the next sequence, so use previous - - // rjf: file name -> normalized file path - String8 file_path = seq_file_name; - String8 file_path_normalized = lower_from_str8(scratch.arena, str8_skip_chop_whitespace(file_path)); - for(U64 idx = 0; idx < file_path_normalized.size; idx += 1) - { - if(file_path_normalized.str[idx] == '\\') - { - file_path_normalized.str[idx] = '/'; - } - } - - // rjf: normalized file path -> source file node - U64 file_path_normalized_hash = rdi_hash(file_path_normalized.str, file_path_normalized.size); - U64 src_file_slot = file_path_normalized_hash%src_file_map.slots_count; - P2R_SrcFileNode *src_file_node = 0; - for(P2R_SrcFileNode *n = src_file_map.slots[src_file_slot]; n != 0; n = n->next) - { - if(str8_match(n->src_file->normal_full_path, file_path_normalized, 0)) - { - src_file_node = n; - break; - } - } - if(src_file_node == 0) - { - src_file_node = push_array(scratch.arena, P2R_SrcFileNode, 1); - SLLStackPush(src_file_map.slots[src_file_slot], src_file_node); - src_file_node->src_file = rdim_src_file_chunk_list_push(arena, &out->src_files, 4096); - src_file_node->src_file->normal_full_path = push_str8_copy(arena, file_path_normalized); - } - - // rjf: gather all lines - RDI_U64 *voffs = push_array_no_zero(arena, RDI_U64, total_line_chunk_line_count+1); - RDI_U32 *line_nums = push_array_no_zero(arena, RDI_U32, total_line_chunk_line_count); - RDI_U64 line_count = total_line_chunk_line_count; - { - U64 dst_idx = 0; - for(LineChunk *chunk = first_line_chunk; chunk != 0; chunk = chunk->next) - { - MemoryCopy(voffs+dst_idx, chunk->voffs, sizeof(U64)*chunk->count); - MemoryCopy(line_nums+dst_idx, chunk->line_nums, sizeof(U32)*chunk->count); - dst_idx += chunk->count; - } - voffs[dst_idx] = 0xffffffffffffffffull; - } - - // rjf: push - if(line_count != 0) - { - if(line_table == 0) - { - line_table = rdim_line_table_chunk_list_push(arena, &out->line_tables, 256); - if(out->units_first_inline_site_line_tables[comp_unit_idx] == 0) - { - out->units_first_inline_site_line_tables[comp_unit_idx] = line_table; - } - } - RDIM_LineSequence *seq = rdim_line_table_push_sequence(arena, &out->line_tables, line_table, src_file_node->src_file, voffs, line_nums, 0, line_count); - rdim_src_file_push_line_sequence(arena, &out->src_files, src_file_node->src_file, seq); - } - - // rjf: clear line chunks for subsequent sequences - first_line_chunk = last_line_chunk = 0; - total_line_chunk_line_count = 0; - } - - // rjf: update prev/current states - last_file_name = file_name; - last_line = line; - last_column = column; - last_code_offset = code_offset; - } - } - }break; - } - } - } - } - scratch_end(scratch); - return out; -} - -//////////////////////////////// -//~ rjf: Link Name Map Building Tasks - -internal TS_TASK_FUNCTION_DEF(p2r_link_name_map_build_task__entry_point) -{ - P2R_LinkNameMapBuildIn *in = (P2R_LinkNameMapBuildIn *)p; - CV_RecRange *rec_ranges_first = in->sym->sym_ranges.ranges; - CV_RecRange *rec_ranges_opl = rec_ranges_first + in->sym->sym_ranges.count; - for(CV_RecRange *rec_range = rec_ranges_first; - rec_range < rec_ranges_opl; - rec_range += 1) - { - //- rjf: unpack symbol range info - CV_SymKind kind = rec_range->hdr.kind; - U64 header_struct_size = cv_header_struct_size_from_sym_kind(kind); - U8 *sym_first = in->sym->data.str + rec_range->off + 2; - U8 *sym_opl = sym_first + rec_range->hdr.size; - - //- rjf: skip bad ranges - if(sym_opl > in->sym->data.str + in->sym->data.size || sym_first + header_struct_size > in->sym->data.str + in->sym->data.size) - { - continue; - } - - //- rjf: consume symbol - switch(kind) - { - default:{}break; - case CV_SymKind_PUB32: - { - // rjf: unpack sym - CV_SymPub32 *pub32 = (CV_SymPub32 *)sym_first; - String8 name = str8_cstring_capped(pub32+1, sym_opl); - COFF_SectionHeader *section = (0 < pub32->sec && pub32->sec <= in->coff_sections->count) ? &in->coff_sections->sections[pub32->sec-1] : 0; - U64 voff = 0; - if(section != 0) - { - voff = section->voff + pub32->off; - } - - // rjf: commit to link name map - U64 hash = p2r_hash_from_voff(voff); - U64 bucket_idx = hash%in->link_name_map->buckets_count; - P2R_LinkNameNode *node = push_array(arena, P2R_LinkNameNode, 1); - SLLStackPush(in->link_name_map->buckets[bucket_idx], node); - node->voff = voff; - node->name = name; - in->link_name_map->link_name_count += 1; - in->link_name_map->bucket_collision_count += (node->next != 0); - }break; - } - } - return 0; -} - -//////////////////////////////// -//~ rjf: Type Parsing/Conversion Tasks - -internal TS_TASK_FUNCTION_DEF(p2r_itype_fwd_map_fill_task__entry_point) -{ - P2R_ITypeFwdMapFillIn *in = (P2R_ITypeFwdMapFillIn *)p; - ProfScope("fill itype fwd map") for(CV_TypeId itype = in->itype_first; itype < in->itype_opl; itype += 1) - { - //- rjf: skip if not in the actually stored itype range - if(itype < in->tpi_leaf->itype_first) - { - continue; - } - - //- rjf: determine if this itype resolves to another - CV_TypeId itype_fwd = 0; - CV_RecRange *range = &in->tpi_leaf->leaf_ranges.ranges[itype-in->tpi_leaf->itype_first]; - CV_LeafKind kind = range->hdr.kind; - U64 header_struct_size = cv_header_struct_size_from_leaf_kind(kind); - if(range->off+range->hdr.size <= in->tpi_leaf->data.size && - range->off+2+header_struct_size <= in->tpi_leaf->data.size && - range->hdr.size >= 2) - { - U8 *itype_leaf_first = in->tpi_leaf->data.str + range->off+2; - U8 *itype_leaf_opl = itype_leaf_first + range->hdr.size-2; - switch(kind) - { - default:{}break; - - //- rjf: CLASS/STRUCTURE - case CV_LeafKind_CLASS: - case CV_LeafKind_STRUCTURE: - { - // rjf: unpack leaf header - CV_LeafStruct *lf_struct = (CV_LeafStruct *)itype_leaf_first; - - // rjf: has fwd ref flag -> lookup itype that this itype resolves to - if(lf_struct->props & CV_TypeProp_FwdRef) - { - // rjf: unpack rest of leaf - U8 *numeric_ptr = (U8 *)(lf_struct + 1); - CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, itype_leaf_opl); - U8 *name_ptr = numeric_ptr + size.encoded_size; - String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); - U8 *unique_name_ptr = name_ptr + name.size + 1; - String8 unique_name = str8_cstring_capped(unique_name_ptr, itype_leaf_opl); - - // rjf: lookup - B32 do_unique_name_lookup = (((lf_struct->props & CV_TypeProp_Scoped) != 0) && - ((lf_struct->props & CV_TypeProp_HasUniqueName) != 0)); - itype_fwd = pdb_tpi_first_itype_from_name(in->tpi_hash, in->tpi_leaf, do_unique_name_lookup?unique_name:name, do_unique_name_lookup); - } - }break; - - //- rjf: CLASS2/STRUCT2 - case CV_LeafKind_CLASS2: - case CV_LeafKind_STRUCT2: - { - // rjf: unpack leaf header - CV_LeafStruct2 *lf_struct = (CV_LeafStruct2 *)itype_leaf_first; - - // rjf: has fwd ref flag -> lookup itype that this itype resolves to - if(lf_struct->props & CV_TypeProp_FwdRef) - { - // rjf: unpack rest of leaf - U8 *numeric_ptr = (U8 *)(lf_struct + 1); - CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, itype_leaf_opl); - U8 *name_ptr = (U8 *)numeric_ptr + size.encoded_size; - String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); - U8 *unique_name_ptr = name_ptr + name.size + 1; - String8 unique_name = str8_cstring_capped(unique_name_ptr, itype_leaf_opl); - - // rjf: lookup - B32 do_unique_name_lookup = (((lf_struct->props & CV_TypeProp_Scoped) != 0) && - ((lf_struct->props & CV_TypeProp_HasUniqueName) != 0)); - itype_fwd = pdb_tpi_first_itype_from_name(in->tpi_hash, in->tpi_leaf, do_unique_name_lookup?unique_name:name, do_unique_name_lookup); - } - }break; - - //- rjf: UNION - case CV_LeafKind_UNION: - { - // rjf: unpack leaf - CV_LeafUnion *lf_union = (CV_LeafUnion *)itype_leaf_first; - U8 *numeric_ptr = (U8 *)(lf_union + 1); - CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, itype_leaf_opl); - U8 *name_ptr = numeric_ptr + size.encoded_size; - String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); - U8 *unique_name_ptr = name_ptr + name.size + 1; - String8 unique_name = str8_cstring_capped(unique_name_ptr, itype_leaf_opl); - - // rjf: has fwd ref flag -> lookup itype that this itype resolves tos - if(lf_union->props & CV_TypeProp_FwdRef) - { - B32 do_unique_name_lookup = (((lf_union->props & CV_TypeProp_Scoped) != 0) && - ((lf_union->props & CV_TypeProp_HasUniqueName) != 0)); - itype_fwd = pdb_tpi_first_itype_from_name(in->tpi_hash, in->tpi_leaf, do_unique_name_lookup?unique_name:name, do_unique_name_lookup); - } - }break; - - //- rjf: ENUM - case CV_LeafKind_ENUM: - { - // rjf: unpack leaf - CV_LeafEnum *lf_enum = (CV_LeafEnum*)itype_leaf_first; - U8 *name_ptr = (U8 *)(lf_enum + 1); - String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); - U8 *unique_name_ptr = name_ptr + name.size + 1; - String8 unique_name = str8_cstring_capped(unique_name_ptr, itype_leaf_opl); - - // rjf: has fwd ref flag -> lookup itype that this itype resolves to - if(lf_enum->props & CV_TypeProp_FwdRef) - { - B32 do_unique_name_lookup = (((lf_enum->props & CV_TypeProp_Scoped) != 0) && - ((lf_enum->props & CV_TypeProp_HasUniqueName) != 0)); - itype_fwd = pdb_tpi_first_itype_from_name(in->tpi_hash, in->tpi_leaf, do_unique_name_lookup?unique_name:name, do_unique_name_lookup); - } - }break; - } - } - - //- rjf: if the forwarded itype is nonzero & in TPI range -> save to map - if(itype_fwd != 0 && itype_fwd < in->tpi_leaf->itype_opl) - { - in->itype_fwd_map[itype] = itype_fwd; - } - } - return 0; -} - -internal TS_TASK_FUNCTION_DEF(p2r_itype_chain_build_task__entry_point) -{ - Temp scratch = scratch_begin(&arena, 1); - P2R_ITypeChainBuildIn *in = (P2R_ITypeChainBuildIn *)p; - ProfScope("dependency itype chain build") - { - for(CV_TypeId itype = in->itype_first; itype < in->itype_opl; itype += 1) - { - //- rjf: push initial itype - should be final-visited-itype for this itype - { - P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); - c->itype = itype; - SLLStackPush(in->itype_chains[itype], c); - } - - //- rjf: skip basic types for dependency walk - if(itype < in->tpi_leaf->itype_first) - { - continue; - } - - //- rjf: walk dependent types, push to chain - P2R_TypeIdChain start_walk_task = {0, itype}; - P2R_TypeIdChain *first_walk_task = &start_walk_task; - P2R_TypeIdChain *last_walk_task = &start_walk_task; - for(P2R_TypeIdChain *walk_task = first_walk_task; - walk_task != 0; - walk_task = walk_task->next) - { - CV_TypeId walk_itype = in->itype_fwd_map[walk_task->itype] ? in->itype_fwd_map[walk_task->itype] : walk_task->itype; - if(walk_itype < in->tpi_leaf->itype_first) - { - continue; - } - CV_RecRange *range = &in->tpi_leaf->leaf_ranges.ranges[walk_itype-in->tpi_leaf->itype_first]; - CV_LeafKind kind = range->hdr.kind; - U64 header_struct_size = cv_header_struct_size_from_leaf_kind(kind); - if(range->off+range->hdr.size <= in->tpi_leaf->data.size && - range->off+2+header_struct_size <= in->tpi_leaf->data.size && - range->hdr.size >= 2) - { - U8 *itype_leaf_first = in->tpi_leaf->data.str + range->off+2; - U8 *itype_leaf_opl = itype_leaf_first + range->hdr.size-2; - switch(kind) - { - default:{}break; - - //- rjf: MODIFIER - case CV_LeafKind_MODIFIER: - { - CV_LeafModifier *lf = (CV_LeafModifier *)itype_leaf_first; - - // rjf: push dependent itype to chain - { - P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); - c->itype = lf->itype; - SLLStackPush(in->itype_chains[itype], c); - } - - // rjf: push task to walk dependency itype - { - P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); - c->itype = lf->itype; - SLLQueuePush(first_walk_task, last_walk_task, c); - } - }break; - - //- rjf: POINTER - case CV_LeafKind_POINTER: - { - CV_LeafModifier *lf = (CV_LeafModifier *)itype_leaf_first; - - // rjf: push dependent itype to chain - { - P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); - c->itype = lf->itype; - SLLStackPush(in->itype_chains[itype], c); - } - - // rjf: push task to walk dependency itype - { - P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); - c->itype = lf->itype; - SLLQueuePush(first_walk_task, last_walk_task, c); - } - }break; - - //- rjf: PROCEDURE - case CV_LeafKind_PROCEDURE: - { - CV_LeafProcedure *lf = (CV_LeafProcedure *)itype_leaf_first; - - // rjf: push return itypes to chain - { - P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); - c->itype = lf->ret_itype; - SLLStackPush(in->itype_chains[itype], c); - } - - // rjf: push task to walk return itype - { - P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); - c->itype = lf->ret_itype; - SLLQueuePush(first_walk_task, last_walk_task, c); - } - - // rjf: unpack arglist range - CV_RecRange *arglist_range = &in->tpi_leaf->leaf_ranges.ranges[lf->arg_itype-in->tpi_leaf->itype_first]; - if(arglist_range->hdr.kind != CV_LeafKind_ARGLIST || - arglist_range->hdr.size<2 || - arglist_range->off + arglist_range->hdr.size > in->tpi_leaf->data.size) - { - break; - } - U8 *arglist_first = in->tpi_leaf->data.str + arglist_range->off + 2; - U8 *arglist_opl = arglist_first+arglist_range->hdr.size-2; - if(arglist_first + sizeof(CV_LeafArgList) > arglist_opl) - { - break; - } - - // rjf: unpack arglist info - CV_LeafArgList *arglist = (CV_LeafArgList*)arglist_first; - CV_TypeId *arglist_itypes_base = (CV_TypeId *)(arglist+1); - U32 arglist_itypes_count = arglist->count; - - // rjf: push arg types to chain - for(U32 idx = 0; idx < arglist_itypes_count; idx += 1) - { - P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); - c->itype = arglist_itypes_base[idx]; - SLLStackPush(in->itype_chains[itype], c); - } - - // rjf: push task to walk arg types - for(U32 idx = 0; idx < arglist_itypes_count; idx += 1) - { - P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); - c->itype = arglist_itypes_base[idx]; - SLLQueuePush(first_walk_task, last_walk_task, c); - } - }break; - - //- rjf: MFUNCTION - case CV_LeafKind_MFUNCTION: - { - CV_LeafMFunction *lf = (CV_LeafMFunction *)itype_leaf_first; - - // rjf: push dependent itypes to chain - { - P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); - c->itype = lf->ret_itype; - SLLStackPush(in->itype_chains[itype], c); - } - { - P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); - c->itype = lf->arg_itype; - SLLStackPush(in->itype_chains[itype], c); - } - { - P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); - c->itype = lf->this_itype; - SLLStackPush(in->itype_chains[itype], c); - } - - // rjf: push task to walk dependency itypes - { - P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); - c->itype = lf->ret_itype; - SLLQueuePush(first_walk_task, last_walk_task, c); - } - { - P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); - c->itype = lf->arg_itype; - SLLQueuePush(first_walk_task, last_walk_task, c); - } - { - P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); - c->itype = lf->this_itype; - SLLQueuePush(first_walk_task, last_walk_task, c); - } - - // rjf: unpack arglist range - CV_RecRange *arglist_range = &in->tpi_leaf->leaf_ranges.ranges[lf->arg_itype-in->tpi_leaf->itype_first]; - if(arglist_range->hdr.kind != CV_LeafKind_ARGLIST || - arglist_range->hdr.size<2 || - arglist_range->off + arglist_range->hdr.size > in->tpi_leaf->data.size) - { - break; - } - U8 *arglist_first = in->tpi_leaf->data.str + arglist_range->off + 2; - U8 *arglist_opl = arglist_first+arglist_range->hdr.size-2; - if(arglist_first + sizeof(CV_LeafArgList) > arglist_opl) - { - break; - } - - // rjf: unpack arglist info - CV_LeafArgList *arglist = (CV_LeafArgList*)arglist_first; - CV_TypeId *arglist_itypes_base = (CV_TypeId *)(arglist+1); - U32 arglist_itypes_count = arglist->count; - - // rjf: push arg types to chain - for(U32 idx = 0; idx < arglist_itypes_count; idx += 1) - { - P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); - c->itype = arglist_itypes_base[idx]; - SLLStackPush(in->itype_chains[itype], c); - } - - // rjf: push task to walk arg types - for(U32 idx = 0; idx < arglist_itypes_count; idx += 1) - { - P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); - c->itype = arglist_itypes_base[idx]; - SLLQueuePush(first_walk_task, last_walk_task, c); - } - }break; - - //- rjf: BITFIELD - case CV_LeafKind_BITFIELD: - { - CV_LeafBitField *lf = (CV_LeafBitField *)itype_leaf_first; - - // rjf: push dependent itype to chain - { - P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); - c->itype = lf->itype; - SLLStackPush(in->itype_chains[itype], c); - } - - // rjf: push task to walk dependency itype - { - P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); - c->itype = lf->itype; - SLLQueuePush(first_walk_task, last_walk_task, c); - } - }break; - - //- rjf: ARRAY - case CV_LeafKind_ARRAY: - { - CV_LeafArray *lf = (CV_LeafArray *)itype_leaf_first; - - // rjf: push dependent itypes to chain - { - P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); - c->itype = lf->entry_itype; - SLLStackPush(in->itype_chains[itype], c); - } - { - P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); - c->itype = lf->index_itype; - SLLStackPush(in->itype_chains[itype], c); - } - - // rjf: push task to walk dependency itypes - { - P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); - c->itype = lf->entry_itype; - SLLQueuePush(first_walk_task, last_walk_task, c); - } - { - P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); - c->itype = lf->index_itype; - SLLQueuePush(first_walk_task, last_walk_task, c); - } - }break; - - //- rjf: ENUM - case CV_LeafKind_ENUM: - { - CV_LeafEnum *lf = (CV_LeafEnum *)itype_leaf_first; - - // rjf: push dependent itypes to chain - { - P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); - c->itype = lf->base_itype; - SLLStackPush(in->itype_chains[itype], c); - } - - // rjf: push task to walk dependency itypes - { - P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); - c->itype = lf->base_itype; - SLLQueuePush(first_walk_task, last_walk_task, c); - } - }break; - } - } - } - } - } - scratch_end(scratch); - return 0; -} - -//////////////////////////////// -//~ rjf: UDT Conversion Tasks - -internal TS_TASK_FUNCTION_DEF(p2r_udt_convert_task__entry_point) -{ - P2R_UDTConvertIn *in = (P2R_UDTConvertIn *)p; -#define p2r_type_ptr_from_itype(itype) ((in->itype_type_ptrs && (itype) < in->tpi_leaf->itype_opl) ? (in->itype_type_ptrs[(in->itype_fwd_map[(itype)] ? in->itype_fwd_map[(itype)] : (itype))]) : 0) - RDIM_UDTChunkList *udts = push_array(arena, RDIM_UDTChunkList, 1); - RDI_U64 udts_chunk_cap = 1024; - ProfScope("convert UDT info") - { - for(CV_TypeId itype = in->itype_first; itype < in->itype_opl; itype += 1) - { - //- rjf: skip basics - if(itype < in->tpi_leaf->itype_first) { continue; } - - //- rjf: grab type for this itype - skip if empty - RDIM_Type *dst_type = in->itype_type_ptrs[itype]; - if(dst_type == 0) { continue; } - - //- rjf: unpack itype leaf range - skip if out-of-range - CV_RecRange *range = &in->tpi_leaf->leaf_ranges.ranges[itype-in->tpi_leaf->itype_first]; - CV_LeafKind kind = range->hdr.kind; - U64 header_struct_size = cv_header_struct_size_from_leaf_kind(kind); - U8 *itype_leaf_first = in->tpi_leaf->data.str + range->off+2; - U8 *itype_leaf_opl = itype_leaf_first + range->hdr.size-2; - if(range->off+range->hdr.size > in->tpi_leaf->data.size || - range->off+2+header_struct_size > in->tpi_leaf->data.size || - range->hdr.size < 2) - { - continue; - } - - //- rjf: build UDT - CV_TypeId field_itype = 0; - switch(kind) - { - default:{}break; - - //////////////////////// - //- rjf: structs/unions/classes -> equip members - // - case CV_LeafKind_CLASS: - case CV_LeafKind_STRUCTURE: - { - CV_LeafStruct *lf = (CV_LeafStruct *)itype_leaf_first; - if(lf->props & CV_TypeProp_FwdRef) - { - break; - } - field_itype = lf->field_itype; - }goto equip_members; - case CV_LeafKind_UNION: - { - CV_LeafUnion *lf = (CV_LeafUnion *)itype_leaf_first; - if(lf->props & CV_TypeProp_FwdRef) - { - break; - } - field_itype = lf->field_itype; - }goto equip_members; - case CV_LeafKind_CLASS2: - case CV_LeafKind_STRUCT2: - { - CV_LeafStruct2 *lf = (CV_LeafStruct2 *)itype_leaf_first; - if(lf->props & CV_TypeProp_FwdRef) - { - break; - } - field_itype = lf->field_itype; - }goto equip_members; - equip_members: - { - Temp scratch = scratch_begin(&arena, 1); - - //- rjf: grab UDT info - RDIM_UDT *dst_udt = dst_type->udt; - if(dst_udt == 0) - { - dst_udt = dst_type->udt = rdim_udt_chunk_list_push(arena, udts, udts_chunk_cap); - dst_udt->self_type = dst_type; - } - - //- rjf: gather all fields - typedef struct FieldListTask FieldListTask; - struct FieldListTask - { - FieldListTask *next; - CV_TypeId itype; - }; - FieldListTask start_fl_task = {0, field_itype}; - FieldListTask *fl_todo_stack = &start_fl_task; - FieldListTask *fl_done_stack = 0; - for(;fl_todo_stack != 0;) - { - //- rjf: take & unpack task - FieldListTask *fl_task = fl_todo_stack; - SLLStackPop(fl_todo_stack); - SLLStackPush(fl_done_stack, fl_task); - CV_TypeId field_list_itype = fl_task->itype; - - //- rjf: skip bad itypes - if(field_list_itype < in->tpi_leaf->itype_first || in->tpi_leaf->itype_opl <= field_list_itype) - { - continue; - } - - //- rjf: field list itype -> range - CV_RecRange *range = &in->tpi_leaf->leaf_ranges.ranges[field_list_itype-in->tpi_leaf->itype_first]; - - //- rjf: skip bad headers - if(range->off+range->hdr.size > in->tpi_leaf->data.size || - range->hdr.size < 2 || - range->hdr.kind != CV_LeafKind_FIELDLIST) - { - continue; - } - - //- rjf: loop over all fields - { - U8 *field_list_first = in->tpi_leaf->data.str+range->off+2; - U8 *field_list_opl = field_list_first+range->hdr.size-2; - for(U8 *read_ptr = field_list_first, *next_read_ptr = field_list_opl; - read_ptr < field_list_opl; - read_ptr = next_read_ptr) - { - // rjf: unpack field - CV_LeafKind field_kind = *(CV_LeafKind *)read_ptr; - U64 field_leaf_header_size = cv_header_struct_size_from_leaf_kind(field_kind); - U8 *field_leaf_first = read_ptr+2; - U8 *field_leaf_opl = field_list_opl; - next_read_ptr = field_leaf_opl; - - // rjf: skip out-of-bounds fields - if(field_leaf_first+field_leaf_header_size > field_list_opl) - { - continue; - } - - // rjf: process field - switch(field_kind) - { - //- rjf: unhandled/invalid cases - default: - { - // TODO(rjf): log - }break; - - //- rjf: INDEX - case CV_LeafKind_INDEX: - { - // rjf: unpack leaf - CV_LeafIndex *lf = (CV_LeafIndex *)field_leaf_first; - CV_TypeId new_itype = lf->itype; - - // rjf: bump next read pointer past header - next_read_ptr = (U8 *)(lf+1); - - // rjf: determine if index itype is new - B32 is_new = 1; - for(FieldListTask *t = fl_done_stack; t != 0; t = t->next) - { - if(t->itype == new_itype) - { - is_new = 0; - break; - } - } - - // rjf: if new -> push task to follow new itype - if(is_new) - { - FieldListTask *new_task = push_array(scratch.arena, FieldListTask, 1); - SLLStackPush(fl_todo_stack, new_task); - new_task->itype = new_itype; - } - }break; - - //- rjf: MEMBER - case CV_LeafKind_MEMBER: - { - // TODO(rjf): log on bad offset - - // rjf: unpack leaf - CV_LeafMember *lf = (CV_LeafMember *)field_leaf_first; - U8 *offset_ptr = (U8 *)(lf+1); - CV_NumericParsed offset = cv_numeric_from_data_range(offset_ptr, field_leaf_opl); - U64 offset64 = cv_u64_from_numeric(&offset); - U8 *name_ptr = offset_ptr + offset.encoded_size; - String8 name = str8_cstring_capped(name_ptr, field_leaf_opl); - - // rjf: bump next read pointer past variable length parts - next_read_ptr = name.str+name.size+1; - - // rjf: emit member - RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); - mem->kind = RDI_MemberKind_DataField; - mem->name = name; - mem->type = p2r_type_ptr_from_itype(lf->itype); - mem->off = (U32)offset64; - }break; - - //- rjf: STMEMBER - case CV_LeafKind_STMEMBER: - { - // TODO(rjf): handle attribs - - // rjf: unpack leaf - CV_LeafStMember *lf = (CV_LeafStMember *)field_leaf_first; - U8 *name_ptr = (U8 *)(lf+1); - String8 name = str8_cstring_capped(name_ptr, field_leaf_opl); - - // rjf: bump next read pointer past variable length parts - next_read_ptr = name.str+name.size+1; - - // rjf: emit member - RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); - mem->kind = RDI_MemberKind_StaticData; - mem->name = name; - mem->type = p2r_type_ptr_from_itype(lf->itype); - }break; - - //- rjf: METHOD - case CV_LeafKind_METHOD: - { - // rjf: unpack leaf - CV_LeafMethod *lf = (CV_LeafMethod *)field_leaf_first; - U8 *name_ptr = (U8 *)(lf+1); - String8 name = str8_cstring_capped(name_ptr, field_leaf_opl); - - // rjf: bump next read pointer past variable length parts - next_read_ptr = name.str+name.size+1; - - //- rjf: method list itype -> range - CV_RecRange *method_list_range = &in->tpi_leaf->leaf_ranges.ranges[lf->list_itype-in->tpi_leaf->itype_first]; - - //- rjf: skip bad method lists - if(method_list_range->off+method_list_range->hdr.size > in->tpi_leaf->data.size || - method_list_range->hdr.size < 2 || - method_list_range->hdr.kind != CV_LeafKind_METHODLIST) - { - break; - } - - //- rjf: loop through all methods & emit members - U8 *method_list_first = in->tpi_leaf->data.str + method_list_range->off + 2; - U8 *method_list_opl = method_list_first + method_list_range->hdr.size-2; - for(U8 *method_read_ptr = method_list_first, *next_method_read_ptr = method_list_opl; - method_read_ptr < method_list_opl; - method_read_ptr = next_method_read_ptr) - { - CV_LeafMethodListMember *method = (CV_LeafMethodListMember*)method_read_ptr; - CV_MethodProp prop = CV_FieldAttribs_ExtractMethodProp(method->attribs); - RDIM_Type *method_type = p2r_type_ptr_from_itype(method->itype); - next_method_read_ptr = (U8 *)(method+1); - - // TODO(allen): PROBLEM - // We only get offsets for virtual functions (the "vbaseoff") from - // "Intro" and "PureIntro". In C++ inheritance, when we have a chain - // of inheritance (let's just talk single inheritance for now) the - // first class in the chain that introduces a new virtual function - // has this "Intro" method. If a later class in the chain redefines - // the virtual function it only has a "Virtual" method which does - // not update the offset. There is a "Virtual" and "PureVirtual" - // variant of "Virtual". The "Pure" in either case means there - // is no concrete procedure. When there is no "Pure" the method - // should have a corresponding procedure symbol id. - // - // The issue is we will want to mark all of our virtual methods as - // virtual and give them an offset, but that means we have to do - // some extra figuring to propogate offsets from "Intro" methods - // to "Virtual" methods in inheritance trees. That is - IF we want - // to start preserving the offsets of virtuals. There is room in - // the method struct to make this work, but for now I've just - // decided to drop this information. It is not urgently useful to - // us and greatly complicates matters. - - // rjf: read vbaseoff - U32 vbaseoff = 0; - if(prop == CV_MethodProp_Intro || prop == CV_MethodProp_PureIntro) - { - if(next_method_read_ptr+4 <= method_list_opl) - { - vbaseoff = *(U32 *)next_method_read_ptr; - } - next_method_read_ptr += 4; - } - - // rjf: emit method - switch(prop) - { - default: - { - RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); - mem->kind = RDI_MemberKind_Method; - mem->name = name; - mem->type = method_type; - }break; - case CV_MethodProp_Static: - { - RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); - mem->kind = RDI_MemberKind_StaticMethod; - mem->name = name; - mem->type = method_type; - }break; - case CV_MethodProp_Virtual: - case CV_MethodProp_PureVirtual: - case CV_MethodProp_Intro: - case CV_MethodProp_PureIntro: - { - RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); - mem->kind = RDI_MemberKind_VirtualMethod; - mem->name = name; - mem->type = method_type; - }break; - } - } - - }break; - - //- rjf: ONEMETHOD - case CV_LeafKind_ONEMETHOD: - { - // TODO(rjf): handle attribs - - // rjf: unpack leaf - CV_LeafOneMethod *lf = (CV_LeafOneMethod *)field_leaf_first; - CV_MethodProp prop = CV_FieldAttribs_ExtractMethodProp(lf->attribs); - U8 *vbaseoff_ptr = (U8 *)(lf+1); - U8 *vbaseoff_opl_ptr = vbaseoff_ptr; - U32 vbaseoff = 0; - if(prop == CV_MethodProp_Intro || prop == CV_MethodProp_PureIntro) - { - vbaseoff = *(U32 *)(vbaseoff_ptr); - vbaseoff_opl_ptr += sizeof(U32); - } - U8 *name_ptr = vbaseoff_opl_ptr; - String8 name = str8_cstring_capped(name_ptr, field_leaf_opl); - RDIM_Type *method_type = p2r_type_ptr_from_itype(lf->itype); - - // rjf: bump next read pointer past variable length parts - next_read_ptr = name.str+name.size+1; - - // rjf: emit method - switch(prop) - { - default: - { - RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); - mem->kind = RDI_MemberKind_Method; - mem->name = name; - mem->type = method_type; - }break; - - case CV_MethodProp_Static: - { - RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); - mem->kind = RDI_MemberKind_StaticMethod; - mem->name = name; - mem->type = method_type; - }break; - - case CV_MethodProp_Virtual: - case CV_MethodProp_PureVirtual: - case CV_MethodProp_Intro: - case CV_MethodProp_PureIntro: - { - RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); - mem->kind = RDI_MemberKind_VirtualMethod; - mem->name = name; - mem->type = method_type; - }break; - } - }break; - - //- rjf: NESTTYPE - case CV_LeafKind_NESTTYPE: - { - // rjf: unpack leaf - CV_LeafNestType *lf = (CV_LeafNestType *)field_leaf_first; - U8 *name_ptr = (U8 *)(lf+1); - String8 name = str8_cstring_capped(name_ptr, field_leaf_opl); - - // rjf: bump next read pointer past variable length parts - next_read_ptr = name.str+name.size+1; - - // rjf: emit member - RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); - mem->kind = RDI_MemberKind_NestedType; - mem->name = name; - mem->type = p2r_type_ptr_from_itype(lf->itype); - }break; - - //- rjf: NESTTYPEEX - case CV_LeafKind_NESTTYPEEX: - { - // TODO(rjf): handle attribs - - // rjf: unpack leaf - CV_LeafNestTypeEx *lf = (CV_LeafNestTypeEx *)field_leaf_first; - U8 *name_ptr = (U8 *)(lf+1); - String8 name = str8_cstring_capped(name_ptr, field_leaf_opl); - - // rjf: bump next read pointer past variable length parts - next_read_ptr = name.str+name.size+1; - - // rjf: emit member - RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); - mem->kind = RDI_MemberKind_NestedType; - mem->name = name; - mem->type = p2r_type_ptr_from_itype(lf->itype); - }break; - - //- rjf: BCLASS - case CV_LeafKind_BCLASS: - { - // TODO(rjf): log on bad offset - - // rjf: unpack leaf - CV_LeafBClass *lf = (CV_LeafBClass *)field_leaf_first; - U8 *offset_ptr = (U8 *)(lf+1); - CV_NumericParsed offset = cv_numeric_from_data_range(offset_ptr, field_leaf_opl); - U64 offset64 = cv_u64_from_numeric(&offset); - - // rjf: bump next read pointer past variable length parts - next_read_ptr = offset_ptr+offset.encoded_size; - - // rjf: emit member - RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); - mem->kind = RDI_MemberKind_Base; - mem->type = p2r_type_ptr_from_itype(lf->itype); - mem->off = (U32)offset64; - }break; - - //- rjf: VBCLASS/IVBCLASS - case CV_LeafKind_VBCLASS: - case CV_LeafKind_IVBCLASS: - { - // TODO(rjf): log on bad offsets - // TODO(rjf): handle attribs - // TODO(rjf): offsets? - - // rjf: unpack leaf - CV_LeafVBClass *lf = (CV_LeafVBClass *)field_leaf_first; - U8 *num1_ptr = (U8 *)(lf+1); - CV_NumericParsed num1 = cv_numeric_from_data_range(num1_ptr, field_leaf_opl); - U8 *num2_ptr = num1_ptr + num1.encoded_size; - CV_NumericParsed num2 = cv_numeric_from_data_range(num2_ptr, field_leaf_opl); - - // rjf: bump next read pointer past header - next_read_ptr = (U8 *)(lf+1); - - // rjf: emit member - RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); - mem->kind = RDI_MemberKind_VirtualBase; - mem->type = p2r_type_ptr_from_itype(lf->itype); - }break; - - //- rjf: VFUNCTAB - case CV_LeafKind_VFUNCTAB: - { - CV_LeafVFuncTab *lf = (CV_LeafVFuncTab *)field_leaf_first; - - // rjf: bump next read pointer past header - next_read_ptr = (U8 *)(lf+1); - - // NOTE(rjf): currently no-op this case - (void)lf; - }break; - } - - // rjf: align-up next field - next_read_ptr = (U8 *)AlignPow2((U64)next_read_ptr, 4); - } - } - } - - scratch_end(scratch); - }break; - - //////////////////////// - //- rjf: enums -> equip enumerates - // - case CV_LeafKind_ENUM: - { - CV_LeafEnum *lf = (CV_LeafEnum *)itype_leaf_first; - if(lf->props & CV_TypeProp_FwdRef) - { - break; - } - field_itype = lf->field_itype; - }goto equip_enum_vals; - equip_enum_vals:; - { - Temp scratch = scratch_begin(&arena, 1); - - //- rjf: grab UDT info - RDIM_UDT *dst_udt = dst_type->udt; - if(dst_udt == 0) - { - dst_udt = dst_type->udt = rdim_udt_chunk_list_push(arena, udts, udts_chunk_cap); - dst_udt->self_type = dst_type; - } - - //- rjf: gather all fields - typedef struct FieldListTask FieldListTask; - struct FieldListTask - { - FieldListTask *next; - CV_TypeId itype; - }; - FieldListTask start_fl_task = {0, field_itype}; - FieldListTask *fl_todo_stack = &start_fl_task; - FieldListTask *fl_done_stack = 0; - for(;fl_todo_stack != 0;) - { - //- rjf: take & unpack task - FieldListTask *fl_task = fl_todo_stack; - SLLStackPop(fl_todo_stack); - SLLStackPush(fl_done_stack, fl_task); - CV_TypeId field_list_itype = fl_task->itype; - - //- rjf: skip bad itypes - if(field_list_itype < in->tpi_leaf->itype_first || in->tpi_leaf->itype_opl <= field_list_itype) - { - continue; - } - - //- rjf: field list itype -> range - CV_RecRange *range = &in->tpi_leaf->leaf_ranges.ranges[field_list_itype-in->tpi_leaf->itype_first]; - - //- rjf: skip bad headers - if(range->off+range->hdr.size > in->tpi_leaf->data.size || - range->hdr.size < 2 || - range->hdr.kind != CV_LeafKind_FIELDLIST) - { - continue; - } - - //- rjf: loop over all fields - { - U8 *field_list_first = in->tpi_leaf->data.str+range->off+2; - U8 *field_list_opl = field_list_first+range->hdr.size-2; - for(U8 *read_ptr = field_list_first, *next_read_ptr = field_list_opl; - read_ptr < field_list_opl; - read_ptr = next_read_ptr) - { - // rjf: unpack field - CV_LeafKind field_kind = *(CV_LeafKind *)read_ptr; - U64 field_leaf_header_size = cv_header_struct_size_from_leaf_kind(field_kind); - U8 *field_leaf_first = read_ptr+2; - U8 *field_leaf_opl = field_leaf_first+range->hdr.size-2; - next_read_ptr = field_leaf_opl; - - // rjf: skip out-of-bounds fields - if(field_leaf_first+field_leaf_header_size > field_list_opl) - { - continue; - } - - // rjf: process field - switch(field_kind) - { - //- rjf: unhandled/invalid cases - default: - { - // TODO(rjf): log - }break; - - //- rjf: INDEX - case CV_LeafKind_INDEX: - { - // rjf: unpack leaf - CV_LeafIndex *lf = (CV_LeafIndex *)field_leaf_first; - CV_TypeId new_itype = lf->itype; - - // rjf: determine if index itype is new - B32 is_new = 1; - for(FieldListTask *t = fl_done_stack; t != 0; t = t->next) - { - if(t->itype == new_itype) - { - is_new = 0; - break; - } - } - - // rjf: if new -> push task to follow new itype - if(is_new) - { - FieldListTask *new_task = push_array(scratch.arena, FieldListTask, 1); - SLLStackPush(fl_todo_stack, new_task); - new_task->itype = new_itype; - } - }break; - - //- rjf: ENUMERATE - case CV_LeafKind_ENUMERATE: - { - // TODO(rjf): attribs - - // rjf: unpack leaf - CV_LeafEnumerate *lf = (CV_LeafEnumerate *)field_leaf_first; - U8 *val_ptr = (U8 *)(lf+1); - CV_NumericParsed val = cv_numeric_from_data_range(val_ptr, field_leaf_opl); - U64 val64 = cv_u64_from_numeric(&val); - U8 *name_ptr = val_ptr + val.encoded_size; - String8 name = str8_cstring_capped(name_ptr, field_leaf_opl); - - // rjf: bump next read pointer past variable length parts - next_read_ptr = name.str+name.size+1; - - // rjf: emit member - RDIM_UDTEnumVal *enum_val = rdim_udt_push_enum_val(arena, udts, dst_udt); - enum_val->name = name; - enum_val->val = val64; - }break; - } - - // rjf: align-up next field - next_read_ptr = (U8 *)AlignPow2((U64)next_read_ptr, 4); - } - } - } - - scratch_end(scratch); - }break; - } - } - } -#undef p2r_type_ptr_from_itype - return udts; -} - -//////////////////////////////// -//~ rjf: Symbol Stream Conversion Path & Thread - -internal TS_TASK_FUNCTION_DEF(p2r_symbol_stream_convert_task__entry_point) -{ - Temp scratch = scratch_begin(&arena, 1); - P2R_SymbolStreamConvertIn *in = (P2R_SymbolStreamConvertIn *)p; -#define p2r_type_ptr_from_itype(itype) ((in->itype_type_ptrs && (itype) < in->tpi_leaf->itype_opl) ? (in->itype_type_ptrs[(in->itype_fwd_map[(itype)] ? in->itype_fwd_map[(itype)] : (itype))]) : 0) - - ////////////////////////// - //- rjf: set up outputs for this sym stream - // - U64 sym_procedures_chunk_cap = 1024; - U64 sym_global_variables_chunk_cap = 1024; - U64 sym_thread_variables_chunk_cap = 1024; - U64 sym_scopes_chunk_cap = 1024; - U64 sym_inline_sites_chunk_cap = 1024; - RDIM_SymbolChunkList sym_procedures = {0}; - RDIM_SymbolChunkList sym_global_variables = {0}; - RDIM_SymbolChunkList sym_thread_variables = {0}; - RDIM_ScopeChunkList sym_scopes = {0}; - RDIM_InlineSiteChunkList sym_inline_sites = {0}; - - ////////////////////////// - //- rjf: symbols pass 1: produce procedure frame info map (procedure -> frame info) - // - U64 procedure_frameprocs_count = 0; - U64 procedure_frameprocs_cap = (in->sym_ranges_opl - in->sym_ranges_first); - CV_SymFrameproc **procedure_frameprocs = push_array_no_zero(scratch.arena, CV_SymFrameproc *, procedure_frameprocs_cap); - ProfScope("symbols pass 1: produce procedure frame info map (procedure -> frame info)") - { - U64 procedure_num = 0; - CV_RecRange *rec_ranges_first = in->sym->sym_ranges.ranges + in->sym_ranges_first; - CV_RecRange *rec_ranges_opl = in->sym->sym_ranges.ranges + in->sym_ranges_opl; - for(CV_RecRange *rec_range = rec_ranges_first; - rec_range < rec_ranges_opl; - rec_range += 1) - { - //- rjf: rec range -> symbol info range - U64 sym_off_first = rec_range->off + 2; - U64 sym_off_opl = rec_range->off + rec_range->hdr.size; - - //- rjf: skip invalid ranges - if(sym_off_opl > in->sym->data.size || sym_off_first > in->sym->data.size || sym_off_first > sym_off_opl) - { - continue; - } - - //- rjf: unpack symbol info - CV_SymKind kind = rec_range->hdr.kind; - U64 sym_header_struct_size = cv_header_struct_size_from_sym_kind(kind); - void *sym_header_struct_base = in->sym->data.str + sym_off_first; - - //- rjf: skip bad sizes - if(sym_off_first + sym_header_struct_size > sym_off_opl) - { - continue; - } - - //- rjf: consume symbol based on kind - switch(kind) - { - default:{}break; - - //- rjf: FRAMEPROC - case CV_SymKind_FRAMEPROC: - { - if(procedure_num == 0) { break; } - if(procedure_num > procedure_frameprocs_cap) { break; } - CV_SymFrameproc *frameproc = (CV_SymFrameproc*)sym_header_struct_base; - procedure_frameprocs[procedure_num-1] = frameproc; - procedure_frameprocs_count = Max(procedure_frameprocs_count, procedure_num); - }break; - - //- rjf: LPROC32/GPROC32 - case CV_SymKind_LPROC32: - case CV_SymKind_GPROC32: - { - procedure_num += 1; - }break; - } - } - U64 scratch_overkill = sizeof(procedure_frameprocs[0])*(procedure_frameprocs_cap-procedure_frameprocs_count); - arena_put_back(scratch.arena, scratch_overkill); - } - - ////////////////////////// - //- rjf: symbols pass 2: construct all symbols, given procedure frame info map - // - ProfScope("symbols pass 2: construct all symbols, given procedure frame info map") - { - RDIM_LocationSet *defrange_target = 0; - B32 defrange_target_is_param = 0; - U64 procedure_num = 0; - U64 procedure_base_voff = 0; - CV_RecRange *rec_ranges_first = in->sym->sym_ranges.ranges + in->sym_ranges_first; - CV_RecRange *rec_ranges_opl = in->sym->sym_ranges.ranges + in->sym_ranges_opl; - typedef struct P2R_ScopeNode P2R_ScopeNode; - struct P2R_ScopeNode - { - P2R_ScopeNode *next; - RDIM_Scope *scope; - }; - P2R_ScopeNode *top_scope_node = 0; - P2R_ScopeNode *free_scope_node = 0; - RDIM_LineTable *inline_site_line_table = in->first_inline_site_line_table; - for(CV_RecRange *rec_range = rec_ranges_first; - rec_range < rec_ranges_opl; - rec_range += 1) - { - //- rjf: rec range -> symbol info range - U64 sym_off_first = rec_range->off + 2; - U64 sym_off_opl = rec_range->off + rec_range->hdr.size; - - //- rjf: skip invalid ranges - if(sym_off_opl > in->sym->data.size || sym_off_first > in->sym->data.size || sym_off_first > sym_off_opl) - { - continue; - } - - //- rjf: unpack symbol info - CV_SymKind kind = rec_range->hdr.kind; - U64 sym_header_struct_size = cv_header_struct_size_from_sym_kind(kind); - void *sym_header_struct_base = in->sym->data.str + sym_off_first; - void *sym_data_opl = in->sym->data.str + sym_off_opl; - - //- rjf: skip bad sizes - if(sym_off_first + sym_header_struct_size > sym_off_opl) - { - continue; - } - - //- rjf: consume symbol based on kind - switch(kind) - { - default:{}break; - - //- rjf: END - case CV_SymKind_END: - { - P2R_ScopeNode *n = top_scope_node; - if(n != 0) - { - SLLStackPop(top_scope_node); - SLLStackPush(free_scope_node, n); - } - defrange_target = 0; - defrange_target_is_param = 0; - }break; - - //- rjf: BLOCK32 - case CV_SymKind_BLOCK32: - { - // rjf: unpack sym - CV_SymBlock32 *block32 = (CV_SymBlock32 *)sym_header_struct_base; - - // rjf: build scope, insert into current parent scope - RDIM_Scope *scope = rdim_scope_chunk_list_push(arena, &sym_scopes, sym_scopes_chunk_cap); - { - if(top_scope_node == 0) - { - // TODO(rjf): log - } - if(top_scope_node != 0) - { - RDIM_Scope *top_scope = top_scope_node->scope; - SLLQueuePush_N(top_scope->first_child, top_scope->last_child, scope, next_sibling); - scope->parent_scope = top_scope; - scope->symbol = top_scope->symbol; - } - COFF_SectionHeader *section = (0 < block32->sec && block32->sec <= in->coff_sections->count) ? &in->coff_sections->sections[block32->sec-1] : 0; - if(section != 0) - { - U64 voff_first = section->voff + block32->off; - U64 voff_last = voff_first + block32->len; - RDIM_Rng1U64 voff_range = {voff_first, voff_last}; - rdim_scope_push_voff_range(arena, &sym_scopes, scope, voff_range); - } - } - - // rjf: push this scope to scope stack - { - P2R_ScopeNode *node = free_scope_node; - if(node != 0) { SLLStackPop(free_scope_node); } - else { node = push_array_no_zero(scratch.arena, P2R_ScopeNode, 1); } - node->scope = scope; - SLLStackPush(top_scope_node, node); - } - }break; - - //- rjf: LDATA32/GDATA32 - case CV_SymKind_LDATA32: - case CV_SymKind_GDATA32: - { - // rjf: unpack sym - CV_SymData32 *data32 = (CV_SymData32 *)sym_header_struct_base; - String8 name = str8_cstring_capped(data32+1, sym_data_opl); - COFF_SectionHeader *section = (0 < data32->sec && data32->sec <= in->coff_sections->count) ? &in->coff_sections->sections[data32->sec-1] : 0; - U64 voff = (section ? section->voff : 0) + data32->off; - - // rjf: determine if this is an exact duplicate global - // - // PDB likes to have duplicates of these spread across different - // symbol streams so we deduplicate across the entire translation - // context. - // - B32 is_duplicate = 0; - { - // TODO(rjf): @important global symbol dedup - } - - // rjf: is not duplicate -> push new global - if(!is_duplicate) - { - // rjf: unpack global variable's type - RDIM_Type *type = p2r_type_ptr_from_itype(data32->itype); - - // rjf: unpack global's container type - RDIM_Type *container_type = 0; - U64 container_name_opl = p2r_end_of_cplusplus_container_name(name); - if(container_name_opl > 2) - { - String8 container_name = str8(name.str, container_name_opl - 2); - CV_TypeId cv_type_id = pdb_tpi_first_itype_from_name(in->tpi_hash, in->tpi_leaf, container_name, 0); - container_type = p2r_type_ptr_from_itype(cv_type_id); - } - - // rjf: unpack global's container symbol - RDIM_Symbol *container_symbol = 0; - if(container_type == 0 && top_scope_node != 0) - { - container_symbol = top_scope_node->scope->symbol; - } - - // rjf: build symbol - RDIM_Symbol *symbol = rdim_symbol_chunk_list_push(arena, &sym_global_variables, sym_global_variables_chunk_cap); - symbol->is_extern = (kind == CV_SymKind_GDATA32); - symbol->name = name; - symbol->type = type; - symbol->offset = voff; - symbol->container_symbol = container_symbol; - symbol->container_type = container_type; - } - }break; - - //- rjf: LPROC32/GPROC32 - case CV_SymKind_LPROC32: - case CV_SymKind_GPROC32: - { - // rjf: unpack sym - CV_SymProc32 *proc32 = (CV_SymProc32 *)sym_header_struct_base; - String8 name = str8_cstring_capped(proc32+1, sym_data_opl); - RDIM_Type *type = p2r_type_ptr_from_itype(proc32->itype); - - // rjf: unpack proc's container type - RDIM_Type *container_type = 0; - U64 container_name_opl = p2r_end_of_cplusplus_container_name(name); - if(container_name_opl > 2 && in->tpi_hash != 0 && in->tpi_leaf != 0) - { - String8 container_name = str8(name.str, container_name_opl - 2); - CV_TypeId cv_type_id = pdb_tpi_first_itype_from_name(in->tpi_hash, in->tpi_leaf, container_name, 0); - container_type = p2r_type_ptr_from_itype(cv_type_id); - } - - // rjf: unpack proc's container symbol - RDIM_Symbol *container_symbol = 0; - if(container_type == 0 && top_scope_node != 0) - { - container_symbol = top_scope_node->scope->symbol; - } - - // rjf: build procedure's root scope - // - // NOTE: even if there could be a containing scope at this point (which should be - // illegal in C/C++ but not necessarily in another language) we would not use - // it here because these scopes refer to the ranges of code that make up a - // procedure *not* the namespaces, so a procedure's root scope always has - // no parent. - RDIM_Scope *procedure_root_scope = rdim_scope_chunk_list_push(arena, &sym_scopes, sym_scopes_chunk_cap); - { - COFF_SectionHeader *section = (0 < proc32->sec && proc32->sec <= in->coff_sections->count) ? &in->coff_sections->sections[proc32->sec-1] : 0; - if(section != 0) - { - U64 voff_first = section->voff + proc32->off; - U64 voff_last = voff_first + proc32->len; - RDIM_Rng1U64 voff_range = {voff_first, voff_last}; - rdim_scope_push_voff_range(arena, &sym_scopes, procedure_root_scope, voff_range); - procedure_base_voff = voff_first; - } - } - - // rjf: root scope voff minimum range -> link name - String8 link_name = {0}; - if(procedure_root_scope->voff_ranges.min != 0) - { - U64 voff = procedure_root_scope->voff_ranges.min; - U64 hash = p2r_hash_from_voff(voff); - U64 bucket_idx = hash%in->link_name_map->buckets_count; - P2R_LinkNameNode *node = 0; - for(P2R_LinkNameNode *n = in->link_name_map->buckets[bucket_idx]; n != 0; n = n->next) - { - if(n->voff == voff) - { - link_name = n->name; - break; - } - } - } - - // rjf: build procedure symbol - RDIM_Symbol *procedure_symbol = rdim_symbol_chunk_list_push(arena, &sym_procedures, sym_procedures_chunk_cap); - procedure_symbol->is_extern = (kind == CV_SymKind_GPROC32); - procedure_symbol->name = name; - procedure_symbol->link_name = link_name; - procedure_symbol->type = type; - procedure_symbol->container_symbol = container_symbol; - procedure_symbol->container_type = container_type; - procedure_symbol->root_scope = procedure_root_scope; - - // rjf: fill root scope's symbol - procedure_root_scope->symbol = procedure_symbol; - - // rjf: push scope to scope stack - { - P2R_ScopeNode *node = free_scope_node; - if(node != 0) { SLLStackPop(free_scope_node); } - else { node = push_array_no_zero(scratch.arena, P2R_ScopeNode, 1); } - node->scope = procedure_root_scope; - SLLStackPush(top_scope_node, node); - } - - // rjf: increment procedure counter - procedure_num += 1; - }break; - - //- rjf: REGREL32 - case CV_SymKind_REGREL32: - { - // TODO(rjf): apparently some of the information here may end up being - // redundant with "better" information from CV_SymKind_LOCAL record. - // we don't currently handle this, but if those cases arise then it - // will obviously be better to prefer the better information from both - // records. - - // rjf: no containing scope? -> malformed data; locals cannot be produced - // outside of a containing scope - if(top_scope_node == 0) - { - break; - } - - // rjf: unpack sym - CV_SymRegrel32 *regrel32 = (CV_SymRegrel32 *)sym_header_struct_base; - String8 name = str8_cstring_capped(regrel32+1, sym_data_opl); - RDIM_Type *type = p2r_type_ptr_from_itype(regrel32->itype); - CV_Reg cv_reg = regrel32->reg; - U32 var_off = regrel32->reg_off; - - // rjf: determine if this is a parameter - RDI_LocalKind local_kind = RDI_LocalKind_Variable; - { - B32 is_stack_reg = 0; - switch(in->arch) - { - default:{}break; - case RDI_Arch_X86:{is_stack_reg = (cv_reg == CV_Regx86_ESP);}break; - case RDI_Arch_X64:{is_stack_reg = (cv_reg == CV_Regx64_RSP);}break; - } - if(is_stack_reg) - { - U32 frame_size = 0xFFFFFFFF; - if(procedure_num != 0 && procedure_frameprocs[procedure_num-1] != 0 && procedure_num < procedure_frameprocs_count) - { - CV_SymFrameproc *frameproc = procedure_frameprocs[procedure_num-1]; - frame_size = frameproc->frame_size; - } - if(var_off > frame_size) - { - local_kind = RDI_LocalKind_Parameter; - } - } - } - - // rjf: build local - RDIM_Scope *scope = top_scope_node->scope; - RDIM_Local *local = rdim_scope_push_local(arena, &sym_scopes, scope); - local->kind = local_kind; - local->name = name; - local->type = type; - - // rjf: add location info to local - if(type != 0) - { - // rjf: determine if we need an extra indirection to the value - B32 extra_indirection_to_value = 0; - switch(in->arch) - { - case RDI_Arch_X86: - { - extra_indirection_to_value = (local_kind == RDI_LocalKind_Parameter && (type->byte_size > 4 || !IsPow2OrZero(type->byte_size))); - }break; - case RDI_Arch_X64: - { - extra_indirection_to_value = (local_kind == RDI_LocalKind_Parameter && (type->byte_size > 8 || !IsPow2OrZero(type->byte_size))); - }break; - } - - // rjf: get raddbg register code - RDI_RegCode reg_code = p2r_rdi_reg_code_from_cv_reg_code(in->arch, cv_reg); - // TODO(rjf): real byte_size & byte_pos from cv_reg goes here - U32 byte_size = 8; - U32 byte_pos = 0; - - // rjf: set location case - RDIM_Location *loc = p2r_location_from_addr_reg_off(arena, in->arch, reg_code, byte_size, byte_pos, (S64)(S32)var_off, extra_indirection_to_value); - RDIM_Rng1U64 voff_range = {0, max_U64}; - rdim_location_set_push_case(arena, &sym_scopes, &local->locset, voff_range, loc); - } - }break; - - //- rjf: LTHREAD32/GTHREAD32 - case CV_SymKind_LTHREAD32: - case CV_SymKind_GTHREAD32: - { - // rjf: unpack sym - CV_SymThread32 *thread32 = (CV_SymThread32 *)sym_header_struct_base; - String8 name = str8_cstring_capped(thread32+1, sym_data_opl); - U32 tls_off = thread32->tls_off; - RDIM_Type *type = p2r_type_ptr_from_itype(thread32->itype); - - // rjf: unpack thread variable's container type - RDIM_Type *container_type = 0; - U64 container_name_opl = p2r_end_of_cplusplus_container_name(name); - if(container_name_opl > 2) - { - String8 container_name = str8(name.str, container_name_opl - 2); - CV_TypeId cv_type_id = pdb_tpi_first_itype_from_name(in->tpi_hash, in->tpi_leaf, container_name, 0); - container_type = p2r_type_ptr_from_itype(cv_type_id); - } - - // rjf: unpack thread variable's container symbol - RDIM_Symbol *container_symbol = 0; - if(container_type == 0 && top_scope_node != 0) - { - container_symbol = top_scope_node->scope->symbol; - } - - // rjf: build symbol - RDIM_Symbol *tvar = rdim_symbol_chunk_list_push(arena, &sym_thread_variables, sym_thread_variables_chunk_cap); - tvar->name = name; - tvar->type = type; - tvar->is_extern = (kind == CV_SymKind_GTHREAD32); - tvar->offset = tls_off; - tvar->container_type = container_type; - tvar->container_symbol = container_symbol; - }break; - - //- rjf: LOCAL - case CV_SymKind_LOCAL: - { - // rjf: no containing scope? -> malformed data; locals cannot be produced - // outside of a containing scope - if(top_scope_node == 0) - { - break; - } - - // rjf: unpack sym - CV_SymLocal *slocal = (CV_SymLocal *)sym_header_struct_base; - String8 name = str8_cstring_capped(slocal+1, sym_data_opl); - RDIM_Type *type = p2r_type_ptr_from_itype(slocal->itype); - - // rjf: determine if this symbol encodes the beginning of a global modification - B32 is_global_modification = 0; - if((slocal->flags & CV_LocalFlag_Global) || - (slocal->flags & CV_LocalFlag_Static)) - { - is_global_modification = 1; - } - - // rjf: is global modification -> emit global modification symbol - if(is_global_modification) - { - // TODO(rjf): add global modification symbols - defrange_target = 0; - defrange_target_is_param = 0; - } - - // rjf: is not a global modification -> emit a local variable - if(!is_global_modification) - { - // rjf: determine local kind - RDI_LocalKind local_kind = RDI_LocalKind_Variable; - if(slocal->flags & CV_LocalFlag_Param) - { - local_kind = RDI_LocalKind_Parameter; - } - - // rjf: build local - RDIM_Scope *scope = top_scope_node->scope; - RDIM_Local *local = rdim_scope_push_local(arena, &sym_scopes, scope); - local->kind = local_kind; - local->name = name; - local->type = type; - - // rjf: save defrange target, for subsequent defrange symbols - defrange_target = &local->locset; - defrange_target_is_param = (local_kind == RDI_LocalKind_Parameter); - } - }break; - - //- rjf: DEFRANGE_REGISTESR - case CV_SymKind_DEFRANGE_REGISTER: - { - // rjf: no defrange target? -> somehow we got to a defrange symbol without first seeing - // a local - break immediately - if(defrange_target == 0) - { - break; - } - - // rjf: unpack sym - CV_SymDefrangeRegister *defrange_register = (CV_SymDefrangeRegister*)sym_header_struct_base; - CV_Reg cv_reg = defrange_register->reg; - CV_LvarAddrRange *range = &defrange_register->range; - COFF_SectionHeader *range_section = (0 < range->sec && range->sec <= in->coff_sections->count) ? &in->coff_sections->sections[range->sec-1] : 0; - CV_LvarAddrGap *gaps = (CV_LvarAddrGap*)(defrange_register+1); - U64 gap_count = ((U8*)sym_data_opl - (U8*)gaps) / sizeof(*gaps); - RDI_RegCode reg_code = p2r_rdi_reg_code_from_cv_reg_code(in->arch, cv_reg); - - // rjf: build location - RDIM_Location *location = rdim_push_location_val_reg(arena, reg_code); - - // rjf: emit locations over ranges - p2r_location_over_lvar_addr_range(arena, &sym_scopes, defrange_target, location, range, range_section, gaps, gap_count); - }break; - - //- rjf: DEFRANGE_FRAMEPOINTER_REL - case CV_SymKind_DEFRANGE_FRAMEPOINTER_REL: - { - // rjf: no defrange target? -> somehow we got to a defrange symbol without first seeing - // a local - break immediately - if(defrange_target == 0) - { - break; - } - - // rjf: find current procedure's frameproc - CV_SymFrameproc *frameproc = 0; - if(procedure_num != 0 && procedure_num <= procedure_frameprocs_count && procedure_frameprocs[procedure_num-1] != 0) - { - frameproc = procedure_frameprocs[procedure_num-1]; - } - - // rjf: no current valid frameproc? -> somehow we got a to a framepointer-relative defrange - // without having an actually active procedure - break - if(frameproc == 0) - { - break; - } - - // rjf: unpack sym - CV_SymDefrangeFramepointerRel *defrange_fprel = (CV_SymDefrangeFramepointerRel*)sym_header_struct_base; - CV_LvarAddrRange *range = &defrange_fprel->range; - COFF_SectionHeader *range_section = (0 < range->sec && range->sec <= in->coff_sections->count) ? &in->coff_sections->sections[range->sec-1] : 0; - CV_LvarAddrGap *gaps = (CV_LvarAddrGap*)(defrange_fprel + 1); - U64 gap_count = ((U8*)sym_data_opl - (U8*)gaps) / sizeof(*gaps); - - // rjf: select frame pointer register - CV_EncodedFramePtrReg encoded_fp_reg = p2r_cv_encoded_fp_reg_from_frameproc(frameproc, defrange_target_is_param); - RDI_RegCode fp_register_code = p2r_reg_code_from_arch_encoded_fp_reg(in->arch, encoded_fp_reg); - - // rjf: build location - B32 extra_indirection = 0; - U32 byte_size = rdi_addr_size_from_arch(in->arch); - U32 byte_pos = 0; - S64 var_off = (S64)defrange_fprel->off; - RDIM_Location *location = p2r_location_from_addr_reg_off(arena, in->arch, fp_register_code, byte_size, byte_pos, var_off, extra_indirection); - - // rjf: emit locations over ranges - p2r_location_over_lvar_addr_range(arena, &sym_scopes, defrange_target, location, range, range_section, gaps, gap_count); - }break; - - //- rjf: DEFRANGE_SUBFIELD_REGISTER - case CV_SymKind_DEFRANGE_SUBFIELD_REGISTER: - { - // rjf: no defrange target? -> somehow we got to a defrange symbol without first seeing - // a local - break immediately - if(defrange_target == 0) - { - break; - } - - // rjf: unpack sym - CV_SymDefrangeSubfieldRegister *defrange_subfield_register = (CV_SymDefrangeSubfieldRegister*)sym_header_struct_base; - CV_Reg cv_reg = defrange_subfield_register->reg; - CV_LvarAddrRange *range = &defrange_subfield_register->range; - COFF_SectionHeader *range_section = (0 < range->sec && range->sec <= in->coff_sections->count) ? &in->coff_sections->sections[range->sec-1] : 0; - CV_LvarAddrGap *gaps = (CV_LvarAddrGap*)(defrange_subfield_register + 1); - U64 gap_count = ((U8*)sym_data_opl - (U8*)gaps) / sizeof(*gaps); - RDI_RegCode reg_code = p2r_rdi_reg_code_from_cv_reg_code(in->arch, cv_reg); - - // rjf: skip "subfield" location info - currently not supported - if(defrange_subfield_register->field_offset != 0) - { - break; - } - - // rjf: build location - RDIM_Location *location = rdim_push_location_val_reg(arena, reg_code); - - // rjf: emit locations over ranges - p2r_location_over_lvar_addr_range(arena, &sym_scopes, defrange_target, location, range, range_section, gaps, gap_count); - }break; - - //- rjf: DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE - case CV_SymKind_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE: - { - // rjf: no defrange target? -> somehow we got to a defrange symbol without first seeing - // a local - break immediately - if(defrange_target == 0) - { - break; - } - - // rjf: find current procedure's frameproc - CV_SymFrameproc *frameproc = 0; - if(procedure_num != 0 && procedure_num <= procedure_frameprocs_count && procedure_frameprocs[procedure_num-1] != 0) - { - frameproc = procedure_frameprocs[procedure_num-1]; - } - - // rjf: no current valid frameproc? -> somehow we got a to a framepointer-relative defrange - // without having an actually active procedure - break - if(frameproc == 0) - { - break; - } - - // rjf: unpack sym - CV_SymDefrangeFramepointerRelFullScope *defrange_fprel_full_scope = (CV_SymDefrangeFramepointerRelFullScope*)sym_header_struct_base; - CV_EncodedFramePtrReg encoded_fp_reg = p2r_cv_encoded_fp_reg_from_frameproc(frameproc, defrange_target_is_param); - RDI_RegCode fp_register_code = p2r_reg_code_from_arch_encoded_fp_reg(in->arch, encoded_fp_reg); - - // rjf: build location - B32 extra_indirection = 0; - U32 byte_size = rdi_addr_size_from_arch(in->arch); - U32 byte_pos = 0; - S64 var_off = (S64)defrange_fprel_full_scope->off; - RDIM_Location *location = p2r_location_from_addr_reg_off(arena, in->arch, fp_register_code, byte_size, byte_pos, var_off, extra_indirection); - - // rjf: emit location over ranges - RDIM_Rng1U64 voff_range = {0, max_U64}; - rdim_location_set_push_case(arena, &sym_scopes, defrange_target, voff_range, location); - }break; - - //- rjf: DEFRANGE_REGISTER_REL - case CV_SymKind_DEFRANGE_REGISTER_REL: - { - // rjf: no defrange target? -> somehow we got to a defrange symbol without first seeing - // a local - break immediately - if(defrange_target == 0) - { - break; - } - - // rjf: unpack sym - CV_SymDefrangeRegisterRel *defrange_register_rel = (CV_SymDefrangeRegisterRel*)sym_header_struct_base; - CV_Reg cv_reg = defrange_register_rel->reg; - RDI_RegCode reg_code = p2r_rdi_reg_code_from_cv_reg_code(in->arch, cv_reg); - CV_LvarAddrRange *range = &defrange_register_rel->range; - COFF_SectionHeader *range_section = (0 < range->sec && range->sec <= in->coff_sections->count) ? &in->coff_sections->sections[range->sec-1] : 0; - CV_LvarAddrGap *gaps = (CV_LvarAddrGap*)(defrange_register_rel + 1); - U64 gap_count = ((U8*)sym_data_opl - (U8*)gaps) / sizeof(*gaps); - - // rjf: build location - // TODO(rjf): offset & size from cv_reg code - U32 byte_size = rdi_addr_size_from_arch(in->arch); - U32 byte_pos = 0; - B32 extra_indirection_to_value = 0; - S64 var_off = defrange_register_rel->reg_off; - RDIM_Location *location = p2r_location_from_addr_reg_off(arena, in->arch, reg_code, byte_size, byte_pos, var_off, extra_indirection_to_value); - - // rjf: emit locations over ranges - p2r_location_over_lvar_addr_range(arena, &sym_scopes, defrange_target, location, range, range_section, gaps, gap_count); - }break; - - //- rjf: FILESTATIC - case CV_SymKind_FILESTATIC: - { - CV_SymFileStatic *file_static = (CV_SymFileStatic*)sym_header_struct_base; - String8 name = str8_cstring_capped(file_static+1, sym_data_opl); - RDIM_Type *type = p2r_type_ptr_from_itype(file_static->itype); - // TODO(rjf): emit a global modifier symbol - defrange_target = 0; - defrange_target_is_param = 0; - }break; - - //- rjf: INLINESITE - case CV_SymKind_INLINESITE: - { - // rjf: unpack sym - CV_SymInlineSite *sym = (CV_SymInlineSite *)sym_header_struct_base; - String8 binary_annots = str8((U8 *)(sym+1), rec_range->hdr.size - sizeof(rec_range->hdr.kind) - sizeof(*sym)); - - // rjf: extract external info about inline site - String8 name = str8_zero(); - RDIM_Type *type = 0; - RDIM_Type *owner = 0; - if(in->ipi_leaf != 0 && in->ipi_leaf->itype_first <= sym->inlinee && sym->inlinee < in->ipi_leaf->itype_opl) - { - CV_RecRange rec_range = in->ipi_leaf->leaf_ranges.ranges[sym->inlinee - in->ipi_leaf->itype_first]; - String8 rec_data = str8_substr(in->ipi_leaf->data, rng_1u64(rec_range.off, rec_range.off + rec_range.hdr.size)); - void *raw_leaf = rec_data.str + sizeof(U16); - - // rjf: extract method inline info - if(rec_range.hdr.kind == CV_LeafIDKind_MFUNC_ID && - rec_range.hdr.size >= sizeof(CV_LeafMFuncId)) - { - CV_LeafMFuncId *mfunc_id = (CV_LeafMFuncId*)raw_leaf; - name = str8_cstring_capped(mfunc_id + 1, rec_data.str + rec_data.size); - type = p2r_type_ptr_from_itype(mfunc_id->itype); - owner = mfunc_id->owner_itype != 0 ? p2r_type_ptr_from_itype(mfunc_id->owner_itype) : 0; - } - - // rjf: extract non-method function inline info - else if(rec_range.hdr.kind == CV_LeafIDKind_FUNC_ID && - rec_range.hdr.size >= sizeof(CV_LeafFuncId)) - { - CV_LeafFuncId *func_id = (CV_LeafFuncId*)raw_leaf; - name = str8_cstring_capped(func_id + 1, rec_data.str + rec_data.size); - type = p2r_type_ptr_from_itype(func_id->itype); - owner = func_id->scope_string_id != 0 ? p2r_type_ptr_from_itype(func_id->scope_string_id) : 0; - } - } - - // rjf: build inline site - RDIM_InlineSite *inline_site = rdim_inline_site_chunk_list_push(arena, &sym_inline_sites, sym_inline_sites_chunk_cap); - inline_site->name = name; - inline_site->type = type; - inline_site->owner = owner; - inline_site->line_table = inline_site_line_table; - - // rjf: increment to next inline site line table in this unit - if(inline_site_line_table != 0 && inline_site_line_table->chunk != 0) - { - RDIM_LineTableChunkNode *chunk = inline_site_line_table->chunk; - U64 current_idx = (U64)(inline_site_line_table - chunk->v); - if(current_idx+1 < chunk->count) - { - inline_site_line_table += 1; - } - else - { - chunk = chunk->next; - inline_site_line_table = 0; - if(chunk != 0) - { - inline_site_line_table = chunk->v; - } - } - } - - // rjf: build scope - RDIM_Scope *scope = rdim_scope_chunk_list_push(arena, &sym_scopes, sym_scopes_chunk_cap); - scope->inline_site = inline_site; - if(top_scope_node == 0) - { - // TODO(rjf): log - } - if(top_scope_node != 0) - { - RDIM_Scope *top_scope = top_scope_node->scope; - SLLQueuePush_N(top_scope->first_child, top_scope->last_child, scope, next_sibling); - scope->parent_scope = top_scope; - scope->symbol = top_scope->symbol; - } - - // rjf: push this scope to scope stack - { - P2R_ScopeNode *node = free_scope_node; - if(node != 0) { SLLStackPop(free_scope_node); } - else { node = push_array_no_zero(scratch.arena, P2R_ScopeNode, 1); } - node->scope = scope; - SLLStackPush(top_scope_node, node); - } - - // rjf: parse offset ranges of this inline site - attach to scope - { - U32 code_length = 0; - U32 code_offset = 0; - U32 last_code_offset = code_offset; - U32 last_code_length = code_length; - U64 read_off = 0; - U64 read_off_opl = binary_annots.size; - for(B32 good = 1; read_off < read_off_opl && good;) - { - // rjf: decode next annotation op - U32 op = CV_InlineBinaryAnnotation_Null; - read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &op); - - // rjf: apply op - switch(op) - { - default:{good = 1;}break; - case CV_InlineBinaryAnnotation_Null: - { - good = 0; - }break; - case CV_InlineBinaryAnnotation_CodeOffset: - { - read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_offset); - }break; - case CV_InlineBinaryAnnotation_ChangeCodeOffsetBase: - { - good = 0; - // TODO(rjf): currently untested/unknown - first guess below: - // - // U32 delta = 0; - // read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &delta); - // code_offset_base = code_offset; - // code_offset_end = code_offset + delta; - // code_offset += delta; - }break; - case CV_InlineBinaryAnnotation_ChangeCodeOffset: - { - U32 delta = 0; - read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &delta); - code_offset += delta; - }break; - case CV_InlineBinaryAnnotation_ChangeCodeLength: - { - code_length = 0; - read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_length); - }break; - case CV_InlineBinaryAnnotation_ChangeCodeOffsetAndLineOffset: - { - U32 code_offset_and_line_offset = 0; - read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_offset_and_line_offset); - U32 code_delta = (code_offset_and_line_offset & 0xf); - code_offset += code_delta; - }break; - case CV_InlineBinaryAnnotation_ChangeCodeLengthAndCodeOffset: - { - U32 offset_delta = 0; - read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_length); - read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &offset_delta); - code_offset += offset_delta; - }break; - } - - // rjf: gather new ranges - if(last_code_length != code_length) - { - // rjf: convert current state machine state to [first_voff, opl_voff) range - RDIM_Rng1U64 voff_range = - { - procedure_base_voff + code_offset, - procedure_base_voff + code_offset + code_length, - }; - - // rjf: attempt to extend last-added range to cover this range, if possible - if(scope->voff_ranges.last != 0 && scope->voff_ranges.last->v.max == voff_range.min) - { - scope->voff_ranges.last->v.max = voff_range.max; - } - - // rjf: cannot add to previous range? -> build new range & add to scope - else - { - rdim_scope_push_voff_range(arena, &sym_scopes, scope, voff_range); - } - - // rjf: advance - code_offset += code_length; - code_length = 0; - } - - // rjf: update prev/current states - last_code_offset = code_offset; - last_code_length = code_length; - } - } - }break; - - //- rjf: INLINESITE_END - case CV_SymKind_INLINESITE_END: - { - P2R_ScopeNode *n = top_scope_node; - if(n != 0) - { - SLLStackPop(top_scope_node); - SLLStackPush(free_scope_node, n); - } - defrange_target = 0; - defrange_target_is_param = 0; - }break; - } - } - } - - ////////////////////////// - //- rjf: allocate & fill output - // - P2R_SymbolStreamConvertOut *out = push_array(arena, P2R_SymbolStreamConvertOut, 1); - { - out->procedures = sym_procedures; - out->global_variables = sym_global_variables; - out->thread_variables = sym_thread_variables; - out->scopes = sym_scopes; - out->inline_sites = sym_inline_sites; - } - -#undef p2r_type_ptr_from_itype - scratch_end(scratch); - return out; -} - -//////////////////////////////// -//~ rjf: Top-Level Conversion Entry Point - -internal P2R_Convert2Bake * -p2r_convert(Arena *arena, P2R_User2Convert *in) -{ - Temp scratch = scratch_begin(&arena, 1); - - ////////////////////////////////////////////////////////////// - //- rjf: parse MSF structure - // - MSF_Parsed *msf = 0; - if(in->input_pdb_data.size != 0) ProfScope("parse MSF structure") - { - msf = msf_parsed_from_data(arena, in->input_pdb_data); - } - - ////////////////////////////////////////////////////////////// - //- rjf: parse PDB auth_guid & named streams table - // - PDB_NamedStreamTable *named_streams = 0; - COFF_Guid auth_guid = {0}; - if(msf != 0) ProfScope("parse PDB auth_guid & named streams table") - { - Temp scratch = scratch_begin(&arena, 1); - String8 info_data = msf_data_from_stream(msf, PDB_FixedStream_PdbInfo); - PDB_Info *info = pdb_info_from_data(scratch.arena, info_data); - named_streams = pdb_named_stream_table_from_info(arena, info); - MemoryCopyStruct(&auth_guid, &info->auth_guid); - scratch_end(scratch); - } - - ////////////////////////////////////////////////////////////// - //- rjf: parse PDB strtbl - // - PDB_Strtbl *strtbl = 0; - if(named_streams != 0) ProfScope("parse PDB strtbl") - { - MSF_StreamNumber strtbl_sn = named_streams->sn[PDB_NamedStream_STRTABLE]; - String8 strtbl_data = msf_data_from_stream(msf, strtbl_sn); - strtbl = pdb_strtbl_from_data(arena, strtbl_data); - } - - ////////////////////////////////////////////////////////////// - //- rjf: parse dbi - // - PDB_DbiParsed *dbi = 0; - if(msf != 0) ProfScope("parse dbi") - { - String8 dbi_data = msf_data_from_stream(msf, PDB_FixedStream_Dbi); - dbi = pdb_dbi_from_data(arena, dbi_data); - } - - ////////////////////////////////////////////////////////////// - //- rjf: parse tpi - // - PDB_TpiParsed *tpi = 0; - if(msf != 0) ProfScope("parse tpi") - { - String8 tpi_data = msf_data_from_stream(msf, PDB_FixedStream_Tpi); - tpi = pdb_tpi_from_data(arena, tpi_data); - } - - ////////////////////////////////////////////////////////////// - //- rjf: parse ipi - // - PDB_TpiParsed *ipi = 0; - if(msf != 0) ProfScope("parse ipi") - { - String8 ipi_data = msf_data_from_stream(msf, PDB_FixedStream_Ipi); - ipi = pdb_tpi_from_data(arena, ipi_data); - } - - ////////////////////////////////////////////////////////////// - //- rjf: parse coff sections - // - PDB_CoffSectionArray *coff_sections = 0; - U64 coff_section_count = 0; - if(dbi != 0) ProfScope("parse coff sections") - { - MSF_StreamNumber section_stream = dbi->dbg_streams[PDB_DbiStream_SECTION_HEADER]; - String8 section_data = msf_data_from_stream(msf, section_stream); - coff_sections = pdb_coff_section_array_from_data(arena, section_data); - coff_section_count = coff_sections->count; - } - - ////////////////////////////////////////////////////////////// - //- rjf: parse gsi - // - PDB_GsiParsed *gsi = 0; - if(dbi != 0) ProfScope("parse gsi") - { - String8 gsi_data = msf_data_from_stream(msf, dbi->gsi_sn); - gsi = pdb_gsi_from_data(arena, gsi_data); - } - - ////////////////////////////////////////////////////////////// - //- rjf: parse psi - // - PDB_GsiParsed *psi_gsi_part = 0; - if(dbi != 0) ProfScope("parse psi") - { - String8 psi_data = msf_data_from_stream(msf, dbi->psi_sn); - String8 psi_data_gsi_part = str8_range(psi_data.str + sizeof(PDB_PsiHeader), psi_data.str + psi_data.size); - psi_gsi_part = pdb_gsi_from_data(arena, psi_data_gsi_part); - } - - ////////////////////////////////////////////////////////////// - //- rjf: kickoff EXE hash - // - P2R_EXEHashIn exe_hash_in = {in->input_exe_data}; - TS_Ticket exe_hash_ticket = ts_kickoff(p2r_exe_hash_task__entry_point, 0, &exe_hash_in); - - ////////////////////////////////////////////////////////////// - //- rjf: kickoff TPI hash parse - // - P2R_TPIHashParseIn tpi_hash_in = {0}; - TS_Ticket tpi_hash_ticket = {0}; - if(tpi != 0) - { - tpi_hash_in.strtbl = strtbl; - tpi_hash_in.tpi = tpi; - tpi_hash_in.hash_data = msf_data_from_stream(msf, tpi->hash_sn); - tpi_hash_in.aux_data = msf_data_from_stream(msf, tpi->hash_sn_aux); - tpi_hash_ticket = ts_kickoff(p2r_tpi_hash_parse_task__entry_point, 0, &tpi_hash_in); - } - - ////////////////////////////////////////////////////////////// - //- rjf: kickoff TPI leaf parse - // - P2R_TPILeafParseIn tpi_leaf_in = {0}; - TS_Ticket tpi_leaf_ticket = {0}; - if(tpi != 0) - { - tpi_leaf_in.leaf_data = pdb_leaf_data_from_tpi(tpi); - tpi_leaf_in.itype_first = tpi->itype_first; - tpi_leaf_ticket = ts_kickoff(p2r_tpi_leaf_parse_task__entry_point, 0, &tpi_leaf_in); - } - - ////////////////////////////////////////////////////////////// - //- rjf: kickoff IPI hash parse - // - P2R_TPIHashParseIn ipi_hash_in = {0}; - TS_Ticket ipi_hash_ticket = {0}; - if(ipi != 0) - { - ipi_hash_in.strtbl = strtbl; - ipi_hash_in.tpi = ipi; - ipi_hash_in.hash_data = msf_data_from_stream(msf, ipi->hash_sn); - ipi_hash_in.aux_data = msf_data_from_stream(msf, ipi->hash_sn_aux); - ipi_hash_ticket = ts_kickoff(p2r_tpi_hash_parse_task__entry_point, 0, &ipi_hash_in); - } - - ////////////////////////////////////////////////////////////// - //- rjf: kickoff IPI leaf parse - // - P2R_TPILeafParseIn ipi_leaf_in = {0}; - TS_Ticket ipi_leaf_ticket = {0}; - if(ipi != 0) - { - ipi_leaf_in.leaf_data = pdb_leaf_data_from_tpi(ipi); - ipi_leaf_in.itype_first = ipi->itype_first; - ipi_leaf_ticket = ts_kickoff(p2r_tpi_leaf_parse_task__entry_point, 0, &ipi_leaf_in); - } - - ////////////////////////////////////////////////////////////// - //- rjf: kickoff top-level global symbol stream parse - // - P2R_SymbolStreamParseIn sym_parse_in = {dbi ? msf_data_from_stream(msf, dbi->sym_sn) : str8_zero()}; - TS_Ticket sym_parse_ticket = !dbi ? ts_ticket_zero() : ts_kickoff(p2r_symbol_stream_parse_task__entry_point, 0, &sym_parse_in); - - ////////////////////////////////////////////////////////////// - //- rjf: kickoff compilation unit parses - // - P2R_CompUnitParseIn comp_unit_parse_in = {dbi ? pdb_data_from_dbi_range(dbi, PDB_DbiRange_ModuleInfo) : str8_zero()}; - P2R_CompUnitContributionsParseIn comp_unit_contributions_parse_in = {dbi ? pdb_data_from_dbi_range(dbi, PDB_DbiRange_SecCon) : str8_zero(), coff_sections}; - TS_Ticket comp_unit_parse_ticket = !dbi ? ts_ticket_zero() : ts_kickoff(p2r_comp_unit_parse_task__entry_point, 0, &comp_unit_parse_in); - TS_Ticket comp_unit_contributions_parse_ticket = !dbi ? ts_ticket_zero() : ts_kickoff(p2r_comp_unit_contributions_parse_task__entry_point, 0, &comp_unit_contributions_parse_in); - - ////////////////////////////////////////////////////////////// - //- rjf: join compilation unit parses - // - PDB_CompUnitArray *comp_units = 0; - U64 comp_unit_count = 0; - PDB_CompUnitContributionArray *comp_unit_contributions = 0; - U64 comp_unit_contribution_count = 0; - { - comp_units = ts_join_struct(comp_unit_parse_ticket, max_U64, PDB_CompUnitArray); - comp_unit_contributions = ts_join_struct(comp_unit_contributions_parse_ticket, max_U64, PDB_CompUnitContributionArray); - comp_unit_count = comp_units ? comp_units->count : 0; - comp_unit_contribution_count = comp_unit_contributions ? comp_unit_contributions->count : 0; - } - - ////////////////////////////////////////////////////////////// - //- rjf: parse syms & line info for each compilation unit - // - CV_SymParsed **sym_for_unit = push_array(arena, CV_SymParsed *, comp_unit_count); - CV_C13Parsed **c13_for_unit = push_array(arena, CV_C13Parsed *, comp_unit_count); - if(comp_units != 0) ProfScope("parse syms & line info for each compilation unit") - { - //- rjf: kick off tasks - P2R_SymbolStreamParseIn *sym_tasks_inputs = push_array(scratch.arena, P2R_SymbolStreamParseIn, comp_unit_count); - TS_Ticket *sym_tasks_tickets = push_array(scratch.arena, TS_Ticket, comp_unit_count); - P2R_C13StreamParseIn *c13_tasks_inputs = push_array(scratch.arena, P2R_C13StreamParseIn, comp_unit_count); - TS_Ticket *c13_tasks_tickets = push_array(scratch.arena, TS_Ticket, comp_unit_count); - for(U64 idx = 0; idx < comp_unit_count; idx += 1) - { - PDB_CompUnit *unit = comp_units->units[idx]; - sym_tasks_inputs[idx].data = pdb_data_from_unit_range(msf, unit, PDB_DbiCompUnitRange_Symbols); - sym_tasks_tickets[idx] = ts_kickoff(p2r_symbol_stream_parse_task__entry_point, 0, &sym_tasks_inputs[idx]); - c13_tasks_inputs[idx].data = pdb_data_from_unit_range(msf, unit, PDB_DbiCompUnitRange_C13); - c13_tasks_inputs[idx].strtbl = strtbl; - c13_tasks_inputs[idx].coff_sections = coff_sections; - c13_tasks_tickets[idx] = ts_kickoff(p2r_c13_stream_parse_task__entry_point, 0, &c13_tasks_inputs[idx]); - } - - //- rjf: join tasks - for(U64 idx = 0; idx < comp_unit_count; idx += 1) - { - sym_for_unit[idx] = ts_join_struct(sym_tasks_tickets[idx], max_U64, CV_SymParsed); - c13_for_unit[idx] = ts_join_struct(c13_tasks_tickets[idx], max_U64, CV_C13Parsed); - } - } - - ////////////////////////////////////////////////////////////// - //- rjf: calculate EXE's max voff - // - U64 exe_voff_max = 0; - if(coff_sections != 0) - { - COFF_SectionHeader *coff_sec_ptr = coff_sections->sections; - COFF_SectionHeader *coff_ptr_opl = coff_sec_ptr + coff_section_count; - for(;coff_sec_ptr < coff_ptr_opl; coff_sec_ptr += 1) - { - U64 sec_voff_max = coff_sec_ptr->voff + coff_sec_ptr->vsize; - exe_voff_max = Max(exe_voff_max, sec_voff_max); - } - } - - ////////////////////////////////////////////////////////////// - //- rjf: determine architecture - // - RDI_Arch arch = RDI_Arch_NULL; - U64 arch_addr_size = 0; - { - // TODO(rjf): in some cases, the first compilation unit has a zero - // architecture, as it's sometimes used as a "nil" unit. this causes bugs - // in later stages of conversion - particularly, this was detected via - // busted location info. so i've converted this to a scan-until-we-find-an- - // architecture. however, this may still be fundamentally insufficient, - // because Nick has informed me that x86 units can be linked with x64 - // units, meaning the appropriate architecture at any point in time is not - // a top-level concept, and is rather dependent on to which compilation - // unit particular symbols belong. so in the future, to support that (odd) - // case, we'll need to not only have this be a top-level "contextual" piece - // of info, but to use the appropriate compilation unit's architecture when - // possible. assuming, of course, that we care about supporting that case. - for(U64 comp_unit_idx = 0; comp_unit_idx < comp_unit_count; comp_unit_idx += 1) - { - if(sym_for_unit[comp_unit_idx] != 0) - { - arch = p2r_rdi_arch_from_cv_arch(sym_for_unit[comp_unit_idx]->info.arch); - if(arch != RDI_Arch_NULL) - { - break; - } - } - } - arch_addr_size = rdi_addr_size_from_arch(arch); - } - - ////////////////////////////////////////////////////////////// - //- rjf: join EXE hash - // - U64 exe_hash = *ts_join_struct(exe_hash_ticket, max_U64, U64); - - ////////////////////////////////////////////////////////////// - //- rjf: produce top-level-info - // - RDIM_TopLevelInfo top_level_info = {0}; - { - top_level_info.arch = arch; - top_level_info.exe_name = str8_skip_last_slash(in->input_exe_name); - top_level_info.exe_hash = exe_hash; - top_level_info.voff_max = exe_voff_max; - top_level_info.producer_name = str8_lit(BUILD_TITLE_STRING_LITERAL); - } - - ////////////////////////////////////////////////////////////// - //- rjf: build binary sections list - // - RDIM_BinarySectionList binary_sections = {0}; - if(coff_sections != 0) ProfScope("build binary section list") - { - COFF_SectionHeader *coff_ptr = coff_sections->sections; - COFF_SectionHeader *coff_opl = coff_ptr + coff_section_count; - for(;coff_ptr < coff_opl; coff_ptr += 1) - { - char *name_first = (char*)coff_ptr->name; - char *name_opl = name_first + sizeof(coff_ptr->name); - RDIM_BinarySection *sec = rdim_binary_section_list_push(arena, &binary_sections); - sec->name = str8_cstring_capped(name_first, name_opl); - sec->flags = p2r_rdi_binary_section_flags_from_coff_section_flags(coff_ptr->flags); - sec->voff_first = coff_ptr->voff; - sec->voff_opl = coff_ptr->voff+coff_ptr->vsize; - sec->foff_first = coff_ptr->foff; - sec->foff_opl = coff_ptr->foff+coff_ptr->fsize; - } - } - - ////////////////////////////////////////////////////////////// - //- rjf: kick off unit conversion & source file collection - // - P2R_UnitConvertIn unit_convert_in = {strtbl, coff_sections, comp_units, comp_unit_contributions, sym_for_unit, c13_for_unit}; - TS_Ticket unit_convert_ticket = ts_kickoff(p2r_units_convert_task__entry_point, 0, &unit_convert_in); - - ////////////////////////////////////////////////////////////// - //- rjf: join global sym stream parse - // - CV_SymParsed *sym = ts_join_struct(sym_parse_ticket, max_U64, CV_SymParsed); - - ////////////////////////////// - //- rjf: predict symbol count - // - U64 symbol_count_prediction = 0; - ProfScope("predict symbol count") - { - U64 rec_range_count = 0; - if(sym != 0) - { - rec_range_count += sym->sym_ranges.count; - } - for(U64 comp_unit_idx = 0; comp_unit_idx < comp_unit_count; comp_unit_idx += 1) - { - CV_SymParsed *unit_sym = sym_for_unit[comp_unit_idx]; - rec_range_count += unit_sym->sym_ranges.count; - } - symbol_count_prediction = rec_range_count/8; - if(symbol_count_prediction < 256) - { - symbol_count_prediction = 256; - } - } - - ////////////////////////////////////////////////////////////// - //- rjf: kick off link name map production - // - P2R_LinkNameMap link_name_map__in_progress = {0}; - P2R_LinkNameMapBuildIn link_name_map_build_in = {0}; - TS_Ticket link_name_map_ticket = {0}; - if(sym != 0) ProfScope("kick off link name map build task") - { - link_name_map__in_progress.buckets_count = symbol_count_prediction; - link_name_map__in_progress.buckets = push_array(arena, P2R_LinkNameNode *, link_name_map__in_progress.buckets_count); - link_name_map_build_in.sym = sym; - link_name_map_build_in.coff_sections = coff_sections; - link_name_map_build_in.link_name_map = &link_name_map__in_progress; - link_name_map_ticket = ts_kickoff(p2r_link_name_map_build_task__entry_point, 0, &link_name_map_build_in); - } - - ////////////////////////////////////////////////////////////// - //- rjf: join ipi/tpi hash/leaf parses - // - PDB_TpiHashParsed *tpi_hash = 0; - CV_LeafParsed *tpi_leaf = 0; - PDB_TpiHashParsed *ipi_hash = 0; - CV_LeafParsed *ipi_leaf = 0; - { - tpi_hash = ts_join_struct(tpi_hash_ticket, max_U64, PDB_TpiHashParsed); - tpi_leaf = ts_join_struct(tpi_leaf_ticket, max_U64, CV_LeafParsed); - ipi_hash = ts_join_struct(ipi_hash_ticket, max_U64, PDB_TpiHashParsed); - ipi_leaf = ts_join_struct(ipi_leaf_ticket, max_U64, CV_LeafParsed); - } - - ////////////////////////////////////////////////////////////// - //- rjf: types pass 1: produce type forward resolution map - // - // this map is used to resolve usage of "incomplete structs" in codeview's - // type info. this often happens when e.g. "struct Foo" is used to refer to - // a later-defined "Foo", which actually contains members and so on. we want - // to hook types up to their actual destination complete types wherever - // possible, and so this map can be used to do that in subsequent stages. - // - CV_TypeId *itype_fwd_map = 0; - CV_TypeId itype_first = 0; - CV_TypeId itype_opl = 0; - if(in->flags & P2R_ConvertFlag_Types) ProfScope("types pass 1: produce type forward resolution map") - { - //- rjf: allocate forward resolution map - itype_first = tpi_leaf->itype_first; - itype_opl = tpi_leaf->itype_opl; - itype_fwd_map = push_array(arena, CV_TypeId, (U64)itype_opl); - - //- rjf: kick off tasks to fill forward resolution map - U64 task_size_itypes = 1024; - U64 tasks_count = ((U64)itype_opl+(task_size_itypes-1))/task_size_itypes; - P2R_ITypeFwdMapFillIn *tasks_inputs = push_array(scratch.arena, P2R_ITypeFwdMapFillIn, tasks_count); - TS_Ticket *tasks_tickets = push_array(scratch.arena, TS_Ticket, tasks_count); - for(U64 idx = 0; idx < tasks_count; idx += 1) - { - tasks_inputs[idx].tpi_hash = tpi_hash; - tasks_inputs[idx].tpi_leaf = tpi_leaf; - tasks_inputs[idx].itype_first = idx*task_size_itypes; - tasks_inputs[idx].itype_opl = tasks_inputs[idx].itype_first + task_size_itypes; - tasks_inputs[idx].itype_opl = ClampTop(tasks_inputs[idx].itype_opl, itype_opl); - tasks_inputs[idx].itype_fwd_map = itype_fwd_map; - tasks_tickets[idx] = ts_kickoff(p2r_itype_fwd_map_fill_task__entry_point, 0, &tasks_inputs[idx]); - } - - //- rjf: join all tasks - for(U64 idx = 0; idx < tasks_count; idx += 1) - { - ts_join(tasks_tickets[idx], max_U64); - } - } - - ////////////////////////////////////////////////////////////// - //- rjf: types pass 2: produce per-itype itype chain - // - // this pass is to ensure that subsequent passes always produce types for - // dependent itypes first - guaranteeing rdi's "only reference backward" - // rule (which eliminates cycles). each itype slot gets a list of itypes, - // starting with the deepest dependency - when types are produced per-itype, - // this chain is walked, so that deeper dependencies are built first, and - // as such, always show up *earlier* in the actually built types. - // - P2R_TypeIdChain **itype_chains = 0; - if(in->flags & P2R_ConvertFlag_Types) ProfScope("types pass 2: produce per-itype itype chain (for producing dependent types first)") - { - //- rjf: allocate itype chain table - itype_chains = push_array(arena, P2R_TypeIdChain *, (U64)itype_opl); - - //- rjf: kick off tasks to fill itype chain table - U64 task_size_itypes = 1024; - U64 tasks_count = ((U64)itype_opl+(task_size_itypes-1))/task_size_itypes; - P2R_ITypeChainBuildIn *tasks_inputs = push_array(scratch.arena, P2R_ITypeChainBuildIn, tasks_count); - TS_Ticket *tasks_tickets = push_array(scratch.arena, TS_Ticket, tasks_count); - for(U64 idx = 0; idx < tasks_count; idx += 1) - { - tasks_inputs[idx].tpi_leaf = tpi_leaf; - tasks_inputs[idx].itype_first = idx*task_size_itypes; - tasks_inputs[idx].itype_opl = tasks_inputs[idx].itype_first + task_size_itypes; - tasks_inputs[idx].itype_opl = ClampTop(tasks_inputs[idx].itype_opl, itype_opl); - tasks_inputs[idx].itype_chains = itype_chains; - tasks_inputs[idx].itype_fwd_map = itype_fwd_map; - tasks_tickets[idx] = ts_kickoff(p2r_itype_chain_build_task__entry_point, 0, &tasks_inputs[idx]); - } - - //- rjf: join all tasks - for(U64 idx = 0; idx < tasks_count; idx += 1) - { - ts_join(tasks_tickets[idx], max_U64); - } - } - - ////////////////////////////////////////////////////////////// - //- rjf: types pass 3: construct all types from TPI - // - // this doesn't gather struct/class/union/enum members, which is done by - // subsequent passes, to build RDI "UDT" information, which is distinct - // from regular type info. - // - RDIM_Type **itype_type_ptrs = 0; - RDIM_TypeChunkList all_types = {0}; -#define p2r_type_ptr_from_itype(itype) ((itype_type_ptrs && (itype) < itype_opl) ? (itype_type_ptrs[(itype_fwd_map[(itype)] ? itype_fwd_map[(itype)] : (itype))]) : 0) - if(in->flags & P2R_ConvertFlag_Types) ProfScope("types pass 3: construct all root/stub types from TPI") - { - itype_type_ptrs = push_array(arena, RDIM_Type *, (U64)(itype_opl)); - for(CV_TypeId root_itype = 0; root_itype < itype_opl; root_itype += 1) - { - for(P2R_TypeIdChain *itype_chain = itype_chains[root_itype]; - itype_chain != 0; - itype_chain = itype_chain->next) - { - CV_TypeId itype = (root_itype != itype_chain->itype && itype_chain->itype < itype_opl && itype_fwd_map[itype_chain->itype]) ? itype_fwd_map[itype_chain->itype] : itype_chain->itype; - B32 itype_is_basic = (itype < 0x1000); - - ////////////////////////// - //- rjf: skip forward-reference itypes - all future resolutions will - // reference whatever this itype resolves to, and so there is no point - // in filling out this slot - // - if(itype_fwd_map[root_itype] != 0) - { - continue; - } - - ////////////////////////// - //- rjf: skip already produced dependencies - // - if(itype_type_ptrs[itype] != 0) - { - continue; - } - - ////////////////////////// - //- rjf: build basic type - // - if(itype_is_basic) - { - RDIM_Type *dst_type = 0; - - // rjf: unpack itype - CV_BasicPointerKind cv_basic_ptr_kind = CV_BasicPointerKindFromTypeId(itype); - CV_BasicType cv_basic_type_code = CV_BasicTypeFromTypeId(itype); - - // rjf: get basic type slot, fill if unfilled - RDIM_Type *basic_type = itype_type_ptrs[cv_basic_type_code]; - if(basic_type == 0) - { - RDI_TypeKind type_kind = p2r_rdi_type_kind_from_cv_basic_type(cv_basic_type_code); - U32 byte_size = rdi_size_from_basic_type_kind(type_kind); - basic_type = dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); - if(byte_size == 0xffffffff) - { - byte_size = arch_addr_size; - } - basic_type->kind = type_kind; - basic_type->name = cv_type_name_from_basic_type(cv_basic_type_code); - basic_type->byte_size = byte_size; - } - - // rjf: nonzero ptr kind -> form ptr type to basic tpye - if(cv_basic_ptr_kind != 0) - { - dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); - dst_type->kind = RDI_TypeKind_Ptr; - dst_type->byte_size = arch_addr_size; - dst_type->direct_type = basic_type; - } - - // rjf: fill this itype's slot with the finished type - itype_type_ptrs[itype] = dst_type; - } - - ////////////////////////// - //- rjf: build non-basic type - // - if(!itype_is_basic && itype >= itype_first) - { - RDIM_Type *dst_type = 0; - CV_RecRange *range = &tpi_leaf->leaf_ranges.ranges[itype-itype_first]; - CV_LeafKind kind = range->hdr.kind; - U64 header_struct_size = cv_header_struct_size_from_leaf_kind(kind); - if(range->off+range->hdr.size <= tpi_leaf->data.size && - range->off+2+header_struct_size <= tpi_leaf->data.size && - range->hdr.size >= 2) - { - U8 *itype_leaf_first = tpi_leaf->data.str + range->off+2; - U8 *itype_leaf_opl = itype_leaf_first + range->hdr.size-2; - switch(kind) - { - //- rjf: MODIFIER - case CV_LeafKind_MODIFIER: - { - // rjf: unpack leaf - CV_LeafModifier *lf = (CV_LeafModifier *)itype_leaf_first; - - // rjf: cv -> rdi flags - RDI_TypeModifierFlags flags = 0; - if(lf->flags & CV_ModifierFlag_Const) {flags |= RDI_TypeModifierFlag_Const;} - if(lf->flags & CV_ModifierFlag_Volatile) {flags |= RDI_TypeModifierFlag_Volatile;} - - // rjf: fill type - if(flags == 0) - { - dst_type = p2r_type_ptr_from_itype(lf->itype); - } - else - { - dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); - dst_type->kind = RDI_TypeKind_Modifier; - dst_type->flags = flags; - dst_type->direct_type = p2r_type_ptr_from_itype(lf->itype); - dst_type->byte_size = dst_type->direct_type ? dst_type->direct_type->byte_size : 0; - } - }break; - - //- rjf: POINTER - case CV_LeafKind_POINTER: - { - // TODO(rjf): if ptr_mode in {PtrMem, PtrMethod} then output a member pointer instead - - // rjf: unpack leaf - CV_LeafPointer *lf = (CV_LeafPointer *)itype_leaf_first; - RDIM_Type *direct_type = p2r_type_ptr_from_itype(lf->itype); - CV_PointerKind ptr_kind = CV_PointerAttribs_ExtractKind(lf->attribs); - CV_PointerMode ptr_mode = CV_PointerAttribs_ExtractMode(lf->attribs); - U32 ptr_size = CV_PointerAttribs_ExtractSize(lf->attribs); - - // rjf: cv -> rdi modifier flags - RDI_TypeModifierFlags modifier_flags = 0; - if(lf->attribs & CV_PointerAttrib_Const) {modifier_flags |= RDI_TypeModifierFlag_Const;} - if(lf->attribs & CV_PointerAttrib_Volatile) {modifier_flags |= RDI_TypeModifierFlag_Volatile;} - - // rjf: cv info -> rdi pointer type kind - RDI_TypeKind type_kind = RDI_TypeKind_Ptr; - { - if(lf->attribs & CV_PointerAttrib_LRef) - { - type_kind = RDI_TypeKind_LRef; - } - else if(lf->attribs & CV_PointerAttrib_RRef) - { - type_kind = RDI_TypeKind_RRef; - } - if(ptr_mode == CV_PointerMode_LRef) - { - type_kind = RDI_TypeKind_LRef; - } - else if(ptr_mode == CV_PointerMode_RRef) - { - type_kind = RDI_TypeKind_RRef; - } - } - - // rjf: fill type - if(modifier_flags != 0) - { - RDIM_Type *pointer_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); - dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); - dst_type->kind = RDI_TypeKind_Modifier; - dst_type->flags = modifier_flags; - dst_type->direct_type = pointer_type; - dst_type->byte_size = arch_addr_size; - pointer_type->kind = type_kind; - pointer_type->byte_size = arch_addr_size; - pointer_type->direct_type = direct_type; - } - else - { - dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); - dst_type->kind = type_kind; - dst_type->byte_size = arch_addr_size; - dst_type->direct_type = direct_type; - } - }break; - - //- rjf: PROCEDURE - case CV_LeafKind_PROCEDURE: - { - // TODO(rjf): handle call_kind & attribs - - // rjf: unpack leaf - CV_LeafProcedure *lf = (CV_LeafProcedure *)itype_leaf_first; - RDIM_Type *ret_type = p2r_type_ptr_from_itype(lf->ret_itype); - - // rjf: fill type's basics - dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); - dst_type->kind = RDI_TypeKind_Function; - dst_type->byte_size = arch_addr_size; - dst_type->direct_type = ret_type; - - // rjf: unpack arglist range - CV_RecRange *arglist_range = &tpi_leaf->leaf_ranges.ranges[lf->arg_itype-itype_first]; - if(arglist_range->hdr.kind != CV_LeafKind_ARGLIST || - arglist_range->hdr.size<2 || - arglist_range->off + arglist_range->hdr.size > tpi_leaf->data.size) - { - break; - } - U8 *arglist_first = tpi_leaf->data.str + arglist_range->off + 2; - U8 *arglist_opl = arglist_first+arglist_range->hdr.size-2; - if(arglist_first + sizeof(CV_LeafArgList) > arglist_opl) - { - break; - } - - // rjf: unpack arglist info - CV_LeafArgList *arglist = (CV_LeafArgList*)arglist_first; - CV_TypeId *arglist_itypes_base = (CV_TypeId *)(arglist+1); - U32 arglist_itypes_count = arglist->count; - - // rjf: build param type array - RDIM_Type **params = push_array(arena, RDIM_Type *, arglist_itypes_count); - for(U32 idx = 0; idx < arglist_itypes_count; idx += 1) - { - params[idx] = p2r_type_ptr_from_itype(arglist_itypes_base[idx]); - } - - // rjf: fill dst type - dst_type->count = arglist_itypes_count; - dst_type->param_types = params; - }break; - - //- rjf: MFUNCTION - case CV_LeafKind_MFUNCTION: - { - // TODO(rjf): handle call_kind & attribs - // TODO(rjf): preserve "this_adjust" - - // rjf: unpack leaf - CV_LeafMFunction *lf = (CV_LeafMFunction *)itype_leaf_first; - RDIM_Type *ret_type = p2r_type_ptr_from_itype(lf->ret_itype); - - // rjf: fill type - dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); - dst_type->kind = (lf->this_itype != 0) ? RDI_TypeKind_Method : RDI_TypeKind_Function; - dst_type->byte_size = arch_addr_size; - dst_type->direct_type = ret_type; - - // rjf: unpack arglist range - CV_RecRange *arglist_range = &tpi_leaf->leaf_ranges.ranges[lf->arg_itype-itype_first]; - if(arglist_range->hdr.kind != CV_LeafKind_ARGLIST || - arglist_range->hdr.size<2 || - arglist_range->off + arglist_range->hdr.size > tpi_leaf->data.size) - { - break; - } - U8 *arglist_first = tpi_leaf->data.str + arglist_range->off + 2; - U8 *arglist_opl = arglist_first+arglist_range->hdr.size-2; - if(arglist_first + sizeof(CV_LeafArgList) > arglist_opl) - { - break; - } - - // rjf: unpack arglist info - CV_LeafArgList *arglist = (CV_LeafArgList*)arglist_first; - CV_TypeId *arglist_itypes_base = (CV_TypeId *)(arglist+1); - U32 arglist_itypes_count = arglist->count; - - // rjf: build param type array - RDIM_Type **params = push_array(arena, RDIM_Type *, arglist_itypes_count+1); - for(U32 idx = 0; idx < arglist_itypes_count; idx += 1) - { - params[idx+1] = p2r_type_ptr_from_itype(arglist_itypes_base[idx]); - } - params[0] = p2r_type_ptr_from_itype(lf->this_itype); - - // rjf: fill dst type - dst_type->count = arglist_itypes_count+1; - dst_type->param_types = params; - }break; - - //- rjf: BITFIELD - case CV_LeafKind_BITFIELD: - { - // rjf: unpack leaf - CV_LeafBitField *lf = (CV_LeafBitField *)itype_leaf_first; - RDIM_Type *direct_type = p2r_type_ptr_from_itype(lf->itype); - - // rjf: fill type - dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); - dst_type->kind = RDI_TypeKind_Bitfield; - dst_type->off = lf->pos; - dst_type->count = lf->len; - dst_type->byte_size = direct_type?direct_type->byte_size:0; - dst_type->direct_type = direct_type; - }break; - - //- rjf: ARRAY - case CV_LeafKind_ARRAY: - { - // rjf: unpack leaf - CV_LeafArray *lf = (CV_LeafArray *)itype_leaf_first; - RDIM_Type *direct_type = p2r_type_ptr_from_itype(lf->entry_itype); - U8 *numeric_ptr = (U8*)(lf + 1); - CV_NumericParsed array_count = cv_numeric_from_data_range(numeric_ptr, itype_leaf_opl); - U64 full_size = cv_u64_from_numeric(&array_count); - - // rjf: fill type - dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); - dst_type->kind = RDI_TypeKind_Array; - dst_type->direct_type = direct_type; - dst_type->byte_size = full_size; - dst_type->count = (direct_type && direct_type->byte_size) ? (dst_type->byte_size/direct_type->byte_size) : 0; - }break; - - //- rjf: CLASS/STRUCTURE - case CV_LeafKind_CLASS: - case CV_LeafKind_STRUCTURE: - { - // TODO(rjf): handle props - - // rjf: unpack leaf - CV_LeafStruct *lf = (CV_LeafStruct *)itype_leaf_first; - U8 *numeric_ptr = (U8*)(lf + 1); - CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, itype_leaf_opl); - U64 size_u64 = cv_u64_from_numeric(&size); - U8 *name_ptr = numeric_ptr + size.encoded_size; - String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); - - // rjf: fill type - dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); - if(lf->props & CV_TypeProp_FwdRef) - { - dst_type->kind = (kind == CV_LeafKind_CLASS ? RDI_TypeKind_IncompleteClass : RDI_TypeKind_IncompleteStruct); - dst_type->name = name; - } - else - { - dst_type->kind = (kind == CV_LeafKind_CLASS ? RDI_TypeKind_Class : RDI_TypeKind_Struct); - dst_type->byte_size = (U32)size_u64; - dst_type->name = name; - } - }break; - - //- rjf: CLASS2/STRUCT2 - case CV_LeafKind_CLASS2: - case CV_LeafKind_STRUCT2: - { - // TODO(rjf): handle props - - // rjf: unpack leaf - CV_LeafStruct2 *lf = (CV_LeafStruct2 *)itype_leaf_first; - U8 *numeric_ptr = (U8*)(lf + 1); - CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, itype_leaf_opl); - U64 size_u64 = cv_u64_from_numeric(&size); - U8 *name_ptr = numeric_ptr + size.encoded_size; - String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); - - // rjf: fill type - dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); - if(lf->props & CV_TypeProp_FwdRef) - { - dst_type->kind = (kind == CV_LeafKind_CLASS2 ? RDI_TypeKind_IncompleteClass : RDI_TypeKind_IncompleteStruct); - dst_type->name = name; - } - else - { - dst_type->kind = (kind == CV_LeafKind_CLASS2 ? RDI_TypeKind_Class : RDI_TypeKind_Struct); - dst_type->byte_size = (U32)size_u64; - dst_type->name = name; - } - }break; - - //- rjf: UNION - case CV_LeafKind_UNION: - { - // TODO(rjf): handle props - - // rjf: unpack leaf - CV_LeafUnion *lf = (CV_LeafUnion *)itype_leaf_first; - U8 *numeric_ptr = (U8*)(lf + 1); - CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, itype_leaf_opl); - U64 size_u64 = cv_u64_from_numeric(&size); - U8 *name_ptr = numeric_ptr + size.encoded_size; - String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); - - // rjf: fill type - dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); - if(lf->props & CV_TypeProp_FwdRef) - { - dst_type->kind = RDI_TypeKind_IncompleteUnion; - dst_type->name = name; - } - else - { - dst_type->kind = RDI_TypeKind_Union; - dst_type->byte_size = (U32)size_u64; - dst_type->name = name; - } - }break; - - //- rjf: ENUM - case CV_LeafKind_ENUM: - { - // TODO(rjf): handle props - - // rjf: unpack leaf - CV_LeafEnum *lf = (CV_LeafEnum *)itype_leaf_first; - RDIM_Type *direct_type = p2r_type_ptr_from_itype(lf->base_itype); - U8 *name_ptr = (U8 *)(lf + 1); - String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); - - // rjf: fill type - dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); - if(lf->props & CV_TypeProp_FwdRef) - { - dst_type->kind = RDI_TypeKind_IncompleteEnum; - dst_type->name = name; - } - else - { - dst_type->kind = RDI_TypeKind_Enum; - dst_type->direct_type = direct_type; - dst_type->byte_size = direct_type ? direct_type->byte_size : 0; - dst_type->name = name; - } - }break; - } - } - - //- rjf: store finalized type to this itype's slot - itype_type_ptrs[itype] = dst_type; - } - } - } - } - - ////////////////////////////////////////////////////////////// - //- rjf: types pass 4: kick off UDT build - // - U64 udt_task_size_itypes = 4096; - U64 udt_tasks_count = ((U64)itype_opl+(udt_task_size_itypes-1))/udt_task_size_itypes; - P2R_UDTConvertIn *udt_tasks_inputs = push_array(scratch.arena, P2R_UDTConvertIn, udt_tasks_count); - TS_Ticket *udt_tasks_tickets = push_array(scratch.arena, TS_Ticket, udt_tasks_count); - if(in->flags & P2R_ConvertFlag_UDTs) ProfScope("types pass 4: kick off UDT build") - { - for(U64 idx = 0; idx < udt_tasks_count; idx += 1) - { - udt_tasks_inputs[idx].tpi_leaf = tpi_leaf; - udt_tasks_inputs[idx].itype_first = idx*udt_task_size_itypes; - udt_tasks_inputs[idx].itype_opl = udt_tasks_inputs[idx].itype_first + udt_task_size_itypes; - udt_tasks_inputs[idx].itype_opl = ClampTop(udt_tasks_inputs[idx].itype_opl, itype_opl); - udt_tasks_inputs[idx].itype_fwd_map = itype_fwd_map; - udt_tasks_inputs[idx].itype_type_ptrs = itype_type_ptrs; - udt_tasks_tickets[idx] = ts_kickoff(p2r_udt_convert_task__entry_point, 0, &udt_tasks_inputs[idx]); - } - } - - ////////////////////////////////////////////////////////////// - //- rjf: join link name map building task - // - P2R_LinkNameMap *link_name_map = 0; - ProfScope("join link name map building task") - { - ts_join(link_name_map_ticket, max_U64); - link_name_map = &link_name_map__in_progress; - } - - ////////////////////////////////////////////////////////////// - //- rjf: join unit conversion & src file & line table tasks - // - RDIM_UnitChunkList all_units = {0}; - RDIM_SrcFileChunkList all_src_files = {0}; - RDIM_LineTableChunkList all_line_tables = {0}; - RDIM_LineTable **units_first_inline_site_line_tables = 0; - ProfScope("join unit conversion & src file tasks") - { - P2R_UnitConvertOut *out = ts_join_struct(unit_convert_ticket, max_U64, P2R_UnitConvertOut); - all_units = out->units; - all_src_files = out->src_files; - all_line_tables = out->line_tables; - units_first_inline_site_line_tables = out->units_first_inline_site_line_tables; - } - - ////////////////////////////////////////////////////////////// - //- rjf: produce symbols from all streams - // - RDIM_SymbolChunkList all_procedures = {0}; - RDIM_SymbolChunkList all_global_variables = {0}; - RDIM_SymbolChunkList all_thread_variables = {0}; - RDIM_ScopeChunkList all_scopes = {0}; - RDIM_InlineSiteChunkList all_inline_sites = {0}; - ProfScope("produce symbols from all streams") - { - //////////////////////////// - //- rjf: kick off all symbol conversion tasks - // - U64 global_stream_subdivision_tasks_count = sym ? (sym->sym_ranges.count+16383)/16384 : 0; - U64 global_stream_syms_per_task = sym ? sym->sym_ranges.count/global_stream_subdivision_tasks_count : 0; - U64 tasks_count = comp_unit_count + global_stream_subdivision_tasks_count; - P2R_SymbolStreamConvertIn *tasks_inputs = push_array(scratch.arena, P2R_SymbolStreamConvertIn, tasks_count); - TS_Ticket *tasks_tickets = push_array(scratch.arena, TS_Ticket, tasks_count); - ProfScope("kick off all symbol conversion tasks") - { - for(U64 idx = 0; idx < tasks_count; idx += 1) - { - tasks_inputs[idx].arch = arch; - tasks_inputs[idx].coff_sections = coff_sections; - tasks_inputs[idx].tpi_hash = tpi_hash; - tasks_inputs[idx].tpi_leaf = tpi_leaf; - tasks_inputs[idx].ipi_leaf = ipi_leaf; - tasks_inputs[idx].itype_fwd_map = itype_fwd_map; - tasks_inputs[idx].itype_type_ptrs = itype_type_ptrs; - tasks_inputs[idx].link_name_map = link_name_map; - if(idx < global_stream_subdivision_tasks_count) - { - tasks_inputs[idx].sym = sym; - tasks_inputs[idx].sym_ranges_first= idx*global_stream_syms_per_task; - tasks_inputs[idx].sym_ranges_opl = tasks_inputs[idx].sym_ranges_first + global_stream_syms_per_task; - tasks_inputs[idx].sym_ranges_opl = ClampTop(tasks_inputs[idx].sym_ranges_opl, sym->sym_ranges.count); - } - else - { - tasks_inputs[idx].sym = sym_for_unit[idx-global_stream_subdivision_tasks_count]; - tasks_inputs[idx].sym_ranges_first= 0; - tasks_inputs[idx].sym_ranges_opl = sym_for_unit[idx-global_stream_subdivision_tasks_count]->sym_ranges.count; - tasks_inputs[idx].first_inline_site_line_table = units_first_inline_site_line_tables[idx-global_stream_subdivision_tasks_count]; - } - tasks_tickets[idx] = ts_kickoff(p2r_symbol_stream_convert_task__entry_point, 0, &tasks_inputs[idx]); - } - } - - //////////////////////////// - //- rjf: join tasks, merge with top-level collections - // - ProfScope("join tasks, merge with top-level collections") - { - for(U64 idx = 0; idx < tasks_count; idx += 1) - { - P2R_SymbolStreamConvertOut *out = ts_join_struct(tasks_tickets[idx], max_U64, P2R_SymbolStreamConvertOut); - rdim_symbol_chunk_list_concat_in_place(&all_procedures, &out->procedures); - rdim_symbol_chunk_list_concat_in_place(&all_global_variables, &out->global_variables); - rdim_symbol_chunk_list_concat_in_place(&all_thread_variables, &out->thread_variables); - rdim_scope_chunk_list_concat_in_place(&all_scopes, &out->scopes); - rdim_inline_site_chunk_list_concat_in_place(&all_inline_sites,&out->inline_sites); - } - } - } - - ////////////////////////////////////////////////////////////// - //- rjf: types pass 5: join UDT build tasks - // - RDIM_UDTChunkList all_udts = {0}; - for(U64 idx = 0; idx < udt_tasks_count; idx += 1) - { - RDIM_UDTChunkList *udts = ts_join_struct(udt_tasks_tickets[idx], max_U64, RDIM_UDTChunkList); - rdim_udt_chunk_list_concat_in_place(&all_udts, udts); - } - - ////////////////////////////////////////////////////////////// - //- rjf: fill output - // - P2R_Convert2Bake *out = push_array(arena, P2R_Convert2Bake, 1); - { - out->bake_params.top_level_info = top_level_info; - out->bake_params.binary_sections = binary_sections; - out->bake_params.units = all_units; - out->bake_params.types = all_types; - out->bake_params.udts = all_udts; - out->bake_params.src_files = all_src_files; - out->bake_params.line_tables = all_line_tables; - out->bake_params.global_variables = all_global_variables; - out->bake_params.thread_variables = all_thread_variables; - out->bake_params.procedures = all_procedures; - out->bake_params.scopes = all_scopes; - out->bake_params.inline_sites = all_inline_sites; - } - - scratch_end(scratch); - return out; -} - -//////////////////////////////// -//~ rjf: Baking Stage Tasks - -//- rjf: bake string map building - -#define p2r_make_string_map_if_needed() do {if(in->maps[thread_idx] == 0) ProfScope("make map") {in->maps[thread_idx] = rdim_bake_string_map_loose_make(arena, in->top);}} while(0) - -internal TS_TASK_FUNCTION_DEF(p2r_bake_src_files_strings_task__entry_point) -{ - P2R_BakeSrcFilesStringsIn *in = (P2R_BakeSrcFilesStringsIn *)p; - p2r_make_string_map_if_needed(); - ProfScope("bake src file strings") rdim_bake_string_map_loose_push_src_files(arena, in->top, in->maps[thread_idx], in->list); - return 0; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_units_strings_task__entry_point) -{ - P2R_BakeUnitsStringsIn *in = (P2R_BakeUnitsStringsIn *)p; - p2r_make_string_map_if_needed(); - ProfScope("bake unit strings") rdim_bake_string_map_loose_push_units(arena, in->top, in->maps[thread_idx], in->list); - return 0; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_types_strings_task__entry_point) -{ - P2R_BakeTypesStringsIn *in = (P2R_BakeTypesStringsIn *)p; - p2r_make_string_map_if_needed(); - ProfScope("bake type strings") - { - for(P2R_BakeTypesStringsInNode *n = in->first; n != 0; n = n->next) - { - rdim_bake_string_map_loose_push_type_slice(arena, in->top, in->maps[thread_idx], n->v, n->count); - } - } - return 0; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_udts_strings_task__entry_point) -{ - P2R_BakeUDTsStringsIn *in = (P2R_BakeUDTsStringsIn *)p; - p2r_make_string_map_if_needed(); - ProfScope("bake udt strings") - { - for(P2R_BakeUDTsStringsInNode *n = in->first; n != 0; n = n->next) - { - rdim_bake_string_map_loose_push_udt_slice(arena, in->top, in->maps[thread_idx], n->v, n->count); - } - } - return 0; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_symbols_strings_task__entry_point) -{ - P2R_BakeSymbolsStringsIn *in = (P2R_BakeSymbolsStringsIn *)p; - p2r_make_string_map_if_needed(); - ProfScope("bake symbol strings") - { - for(P2R_BakeSymbolsStringsInNode *n = in->first; n != 0; n = n->next) - { - rdim_bake_string_map_loose_push_symbol_slice(arena, in->top, in->maps[thread_idx], n->v, n->count); - } - } - return 0; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_scopes_strings_task__entry_point) -{ - P2R_BakeScopesStringsIn *in = (P2R_BakeScopesStringsIn *)p; - p2r_make_string_map_if_needed(); - ProfScope("bake scope strings") - { - for(P2R_BakeScopesStringsInNode *n = in->first; n != 0; n = n->next) - { - rdim_bake_string_map_loose_push_scope_slice(arena, in->top, in->maps[thread_idx], n->v, n->count); - } - } - return 0; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_line_tables_task__entry_point) -{ - P2R_BakeLineTablesIn *in = (P2R_BakeLineTablesIn *)p; - RDIM_LineTableBakeResult *out = push_array(arena, RDIM_LineTableBakeResult, 1); - ProfScope("bake line tables") *out = rdim_bake_line_tables(arena, in->line_tables); - return out; -} - -#undef p2r_make_string_map_if_needed - -//- rjf: bake string map joining - -internal TS_TASK_FUNCTION_DEF(p2r_bake_string_map_join_task__entry_point) -{ - P2R_JoinBakeStringMapSlotsIn *in = (P2R_JoinBakeStringMapSlotsIn *)p; - ProfScope("join bake string maps") - { - for(U64 src_map_idx = 0; src_map_idx < in->src_maps_count; src_map_idx += 1) - { - for(U64 slot_idx = in->slot_idx_range.min; slot_idx < in->slot_idx_range.max; slot_idx += 1) - { - B32 src_slots_good = (in->src_maps[src_map_idx] != 0 && in->src_maps[src_map_idx]->slots != 0); - B32 dst_slot_is_zero = (in->dst_map->slots[slot_idx] == 0); - if(src_slots_good && dst_slot_is_zero) - { - in->dst_map->slots[slot_idx] = in->src_maps[src_map_idx]->slots[slot_idx]; - } - else if(src_slots_good && in->src_maps[src_map_idx]->slots[slot_idx] != 0) - { - rdim_bake_string_chunk_list_concat_in_place(in->dst_map->slots[slot_idx], in->src_maps[src_map_idx]->slots[slot_idx]); - } - } - } - } - return 0; -} - -//- rjf: bake string map sorting - -internal TS_TASK_FUNCTION_DEF(p2r_bake_string_map_sort_task__entry_point) -{ - P2R_SortBakeStringMapSlotsIn *in = (P2R_SortBakeStringMapSlotsIn *)p; - ProfScope("sort bake string chunk list map range") - { - for(U64 slot_idx = in->slot_idx; - slot_idx < in->slot_idx+in->slot_count; - slot_idx += 1) - { - if(in->src_map->slots[slot_idx] != 0) - { - if(in->src_map->slots[slot_idx]->total_count > 1) - { - in->dst_map->slots[slot_idx] = push_array(arena, RDIM_BakeStringChunkList, 1); - *in->dst_map->slots[slot_idx] = rdim_bake_string_chunk_list_sorted_from_unsorted(arena, in->src_map->slots[slot_idx]); - } - else - { - in->dst_map->slots[slot_idx] = in->src_map->slots[slot_idx]; - } - } - } - } - return 0; -} - -//- rjf: pass 1: interner/deduper map builds - -internal TS_TASK_FUNCTION_DEF(p2r_build_bake_name_map_task__entry_point) -{ - P2R_BuildBakeNameMapIn *in = (P2R_BuildBakeNameMapIn *)p; - RDIM_BakeNameMap *name_map = 0; - ProfScope("build name map %i", in->k) name_map = rdim_bake_name_map_from_kind_params(arena, in->k, in->params); - return name_map; -} - -//- rjf: pass 2: string-map-dependent debug info stream builds - -internal TS_TASK_FUNCTION_DEF(p2r_bake_units_task__entry_point) -{ - P2R_BakeUnitsIn *in = (P2R_BakeUnitsIn *)p; - RDIM_UnitBakeResult *out = push_array(arena, RDIM_UnitBakeResult, 1); - ProfScope("bake units") *out = rdim_bake_units(arena, in->strings, in->path_tree, in->units); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_unit_vmap_task__entry_point) -{ - P2R_BakeUnitVMapIn *in = (P2R_BakeUnitVMapIn *)p; - RDIM_UnitVMapBakeResult *out = push_array(arena, RDIM_UnitVMapBakeResult, 1); - ProfScope("bake unit vmap") *out = rdim_bake_unit_vmap(arena, in->units); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_src_files_task__entry_point) -{ - P2R_BakeSrcFilesIn *in = (P2R_BakeSrcFilesIn *)p; - RDIM_SrcFileBakeResult *out = push_array(arena, RDIM_SrcFileBakeResult, 1); - ProfScope("bake src files") *out = rdim_bake_src_files(arena, in->strings, in->path_tree, in->src_files); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_udts_task__entry_point) -{ - P2R_BakeUDTsIn *in = (P2R_BakeUDTsIn *)p; - RDIM_UDTBakeResult *out = push_array(arena, RDIM_UDTBakeResult, 1); - ProfScope("bake udts") *out = rdim_bake_udts(arena, in->strings, in->udts); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_global_variables_task__entry_point) -{ - P2R_BakeGlobalVariablesIn *in = (P2R_BakeGlobalVariablesIn *)p; - RDIM_GlobalVariableBakeResult *out = push_array(arena, RDIM_GlobalVariableBakeResult, 1); - ProfScope("bake global variables") *out = rdim_bake_global_variables(arena, in->strings, in->global_variables); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_global_vmap_task__entry_point) -{ - P2R_BakeGlobalVMapIn *in = (P2R_BakeGlobalVMapIn *)p; - RDIM_GlobalVMapBakeResult *out = push_array(arena, RDIM_GlobalVMapBakeResult, 1); - ProfScope("bake global vmap") *out = rdim_bake_global_vmap(arena, in->global_variables); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_thread_variables_task__entry_point) -{ - P2R_BakeThreadVariablesIn *in = (P2R_BakeThreadVariablesIn *)p; - RDIM_ThreadVariableBakeResult *out = push_array(arena, RDIM_ThreadVariableBakeResult, 1); - ProfScope("bake thread variables") *out = rdim_bake_thread_variables(arena, in->strings, in->thread_variables); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_procedures_task__entry_point) -{ - P2R_BakeProceduresIn *in = (P2R_BakeProceduresIn *)p; - RDIM_ProcedureBakeResult *out = push_array(arena, RDIM_ProcedureBakeResult, 1); - ProfScope("bake procedures") *out = rdim_bake_procedures(arena, in->strings, in->procedures); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_scopes_task__entry_point) -{ - P2R_BakeScopesIn *in = (P2R_BakeScopesIn *)p; - RDIM_ScopeBakeResult *out = push_array(arena, RDIM_ScopeBakeResult, 1); - ProfScope("bake scopes") *out = rdim_bake_scopes(arena, in->strings, in->scopes); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_scope_vmap_task__entry_point) -{ - P2R_BakeScopeVMapIn *in = (P2R_BakeScopeVMapIn *)p; - RDIM_ScopeVMapBakeResult *out = push_array(arena, RDIM_ScopeVMapBakeResult, 1); - ProfScope("bake scope vmap") *out = rdim_bake_scope_vmap(arena, in->scopes); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_inline_sites_task__entry_point) -{ - P2R_BakeInlineSitesIn *in = (P2R_BakeInlineSitesIn *)p; - RDIM_InlineSiteBakeResult *out = push_array(arena, RDIM_InlineSiteBakeResult, 1); - ProfScope("bake inline sites") *out = rdim_bake_inline_sites(arena, in->strings, in->inline_sites); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_file_paths_task__entry_point) -{ - P2R_BakeFilePathsIn *in = (P2R_BakeFilePathsIn *)p; - RDIM_FilePathBakeResult *out = push_array(arena, RDIM_FilePathBakeResult, 1); - ProfScope("bake file paths") *out = rdim_bake_file_paths(arena, in->strings, in->path_tree); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_strings_task__entry_point) -{ - P2R_BakeStringsIn *in = (P2R_BakeStringsIn *)p; - RDIM_StringBakeResult *out = push_array(arena, RDIM_StringBakeResult, 1); - ProfScope("bake strings") *out = rdim_bake_strings(arena, in->strings); - return out; -} - -//- rjf: pass 3: idx-run-map-dependent debug info stream builds - -internal TS_TASK_FUNCTION_DEF(p2r_bake_type_nodes_task__entry_point) -{ - P2R_BakeTypeNodesIn *in = (P2R_BakeTypeNodesIn *)p; - RDIM_TypeNodeBakeResult *out = push_array(arena, RDIM_TypeNodeBakeResult, 1); - ProfScope("bake type nodes") *out = rdim_bake_types(arena, in->strings, in->idx_runs, in->types); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_name_map_task__entry_point) -{ - P2R_BakeNameMapIn *in = (P2R_BakeNameMapIn *)p; - RDIM_NameMapBakeResult *out = push_array(arena, RDIM_NameMapBakeResult, 1); - ProfScope("bake name map %i", in->kind) *out = rdim_bake_name_map(arena, in->strings, in->idx_runs, in->map); - return out; -} - -internal TS_TASK_FUNCTION_DEF(p2r_bake_idx_runs_task__entry_point) -{ - P2R_BakeIdxRunsIn *in = (P2R_BakeIdxRunsIn *)p; - RDIM_IndexRunBakeResult *out = push_array(arena, RDIM_IndexRunBakeResult, 1); - ProfScope("bake idx runs") *out = rdim_bake_index_runs(arena, in->idx_runs); - return out; -} - -//////////////////////////////// -//~ rjf: Top-Level Baking Entry Point - -internal P2R_Bake2Serialize * -p2r_bake(Arena *arena, P2R_Convert2Bake *in) -{ - Temp scratch = scratch_begin(&arena, 1); - RDIM_BakeParams *in_params = &in->bake_params; - P2R_Bake2Serialize *out = push_array(arena, P2R_Bake2Serialize, 1); - RDIM_BakeResults *out_results = &out->bake_results; - - ////////////////////////////// - //- rjf: kick off line tables baking - // - TS_Ticket bake_line_tables_ticket = {0}; - { - P2R_BakeLineTablesIn *in = push_array(scratch.arena, P2R_BakeLineTablesIn, 1); - in->line_tables = &in_params->line_tables; - bake_line_tables_ticket = ts_kickoff(p2r_bake_line_tables_task__entry_point, 0, in); - } - - ////////////////////////////// - //- rjf: build interned path tree - // - RDIM_BakePathTree *path_tree = 0; - ProfScope("build interned path tree") - { - path_tree = rdim_bake_path_tree_from_params(arena, in_params); - } - - ////////////////////////////// - //- rjf: kick off string map building tasks - // - RDIM_BakeStringMapTopology bake_string_map_topology = {(in_params->procedures.total_count*1 + - in_params->global_variables.total_count*1 + - in_params->thread_variables.total_count*1 + - in_params->types.total_count/2)}; - RDIM_BakeStringMapLoose **bake_string_maps__in_progress = push_array(scratch.arena, RDIM_BakeStringMapLoose *, ts_thread_count()); - TS_TicketList bake_string_map_build_tickets = {0}; - { - // rjf: src files - ProfScope("kick off src files string map build task") - { - P2R_BakeSrcFilesStringsIn *in = push_array(scratch.arena, P2R_BakeSrcFilesStringsIn, 1); - in->top = &bake_string_map_topology; - in->maps = bake_string_maps__in_progress; - in->list = &in_params->src_files; - ts_ticket_list_push(scratch.arena, &bake_string_map_build_tickets, ts_kickoff(p2r_bake_src_files_strings_task__entry_point, 0, in)); - } - - // rjf: units - ProfScope("kick off units string map build task") - { - P2R_BakeUnitsStringsIn *in = push_array(scratch.arena, P2R_BakeUnitsStringsIn, 1); - in->top = &bake_string_map_topology; - in->maps = bake_string_maps__in_progress; - in->list = &in_params->units; - ts_ticket_list_push(scratch.arena, &bake_string_map_build_tickets, ts_kickoff(p2r_bake_units_strings_task__entry_point, 0, in)); - } - - // rjf: types - ProfScope("kick off types string map build tasks") - { - U64 items_per_task = 4096; - U64 num_tasks = (in_params->types.total_count+items_per_task-1)/items_per_task; - RDIM_TypeChunkNode *chunk = in_params->types.first; - U64 chunk_off = 0; - for(U64 task_idx = 0; task_idx < num_tasks; task_idx += 1) - { - P2R_BakeTypesStringsIn *in = push_array(scratch.arena, P2R_BakeTypesStringsIn, 1); - in->top = &bake_string_map_topology; - in->maps = bake_string_maps__in_progress; - U64 items_left = items_per_task; - for(;chunk != 0 && items_left > 0;) - { - U64 items_in_this_chunk = Min(items_per_task, chunk->count-chunk_off); - P2R_BakeTypesStringsInNode *n = push_array(scratch.arena, P2R_BakeTypesStringsInNode, 1); - SLLQueuePush(in->first, in->last, n); - n->v = chunk->v + chunk_off; - n->count = items_in_this_chunk; - chunk_off += items_in_this_chunk; - items_left -= items_in_this_chunk; - if(chunk_off >= chunk->count) - { - chunk = chunk->next; - chunk_off = 0; - } - } - ts_ticket_list_push(scratch.arena, &bake_string_map_build_tickets, ts_kickoff(p2r_bake_types_strings_task__entry_point, 0, in)); - } - } - - // rjf: UDTs - ProfScope("kick off udts string map build tasks") - { - U64 items_per_task = 4096; - U64 num_tasks = (in_params->udts.total_count+items_per_task-1)/items_per_task; - RDIM_UDTChunkNode *chunk = in_params->udts.first; - U64 chunk_off = 0; - for(U64 task_idx = 0; task_idx < num_tasks; task_idx += 1) - { - P2R_BakeUDTsStringsIn *in = push_array(scratch.arena, P2R_BakeUDTsStringsIn, 1); - in->top = &bake_string_map_topology; - in->maps = bake_string_maps__in_progress; - U64 items_left = items_per_task; - for(;chunk != 0 && items_left > 0;) - { - U64 items_in_this_chunk = Min(items_per_task, chunk->count-chunk_off); - P2R_BakeUDTsStringsInNode *n = push_array(scratch.arena, P2R_BakeUDTsStringsInNode, 1); - SLLQueuePush(in->first, in->last, n); - n->v = chunk->v + chunk_off; - n->count = items_in_this_chunk; - chunk_off += items_in_this_chunk; - items_left -= items_in_this_chunk; - if(chunk_off >= chunk->count) - { - chunk = chunk->next; - chunk_off = 0; - } - } - ts_ticket_list_push(scratch.arena, &bake_string_map_build_tickets, ts_kickoff(p2r_bake_udts_strings_task__entry_point, 0, in)); - } - } - - // rjf: symbols - ProfScope("kick off symbols string map build tasks") - { - RDIM_SymbolChunkList *symbol_lists[] = - { - &in_params->global_variables, - &in_params->thread_variables, - &in_params->procedures, - }; - for(U64 list_idx = 0; list_idx < ArrayCount(symbol_lists); list_idx += 1) - { - U64 items_per_task = 4096; - U64 num_tasks = (symbol_lists[list_idx]->total_count+items_per_task-1)/items_per_task; - RDIM_SymbolChunkNode *chunk = symbol_lists[list_idx]->first; - U64 chunk_off = 0; - for(U64 task_idx = 0; task_idx < num_tasks; task_idx += 1) - { - P2R_BakeSymbolsStringsIn *in = push_array(scratch.arena, P2R_BakeSymbolsStringsIn, 1); - in->top = &bake_string_map_topology; - in->maps = bake_string_maps__in_progress; - U64 items_left = items_per_task; - for(;chunk != 0 && items_left > 0;) - { - U64 items_in_this_chunk = Min(items_per_task, chunk->count-chunk_off); - P2R_BakeSymbolsStringsInNode *n = push_array(scratch.arena, P2R_BakeSymbolsStringsInNode, 1); - SLLQueuePush(in->first, in->last, n); - n->v = chunk->v + chunk_off; - n->count = items_in_this_chunk; - chunk_off += items_in_this_chunk; - items_left -= items_in_this_chunk; - if(chunk_off >= chunk->count) - { - chunk = chunk->next; - chunk_off = 0; - } - } - ts_ticket_list_push(scratch.arena, &bake_string_map_build_tickets, ts_kickoff(p2r_bake_symbols_strings_task__entry_point, 0, in)); - } - } - } - - // rjf: scope chunks - ProfScope("kick off scope chunks string map build tasks") - { - U64 items_per_task = 4096; - U64 num_tasks = (in_params->scopes.total_count+items_per_task-1)/items_per_task; - RDIM_ScopeChunkNode *chunk = in_params->scopes.first; - U64 chunk_off = 0; - for(U64 task_idx = 0; task_idx < num_tasks; task_idx += 1) - { - P2R_BakeScopesStringsIn *in = push_array(scratch.arena, P2R_BakeScopesStringsIn, 1); - in->top = &bake_string_map_topology; - in->maps = bake_string_maps__in_progress; - U64 items_left = items_per_task; - for(;chunk != 0 && items_left > 0;) - { - U64 items_in_this_chunk = Min(items_per_task, chunk->count-chunk_off); - P2R_BakeScopesStringsInNode *n = push_array(scratch.arena, P2R_BakeScopesStringsInNode, 1); - SLLQueuePush(in->first, in->last, n); - n->v = chunk->v + chunk_off; - n->count = items_in_this_chunk; - chunk_off += items_in_this_chunk; - items_left -= items_in_this_chunk; - if(chunk_off >= chunk->count) - { - chunk = chunk->next; - chunk_off = 0; - } - } - ts_ticket_list_push(scratch.arena, &bake_string_map_build_tickets, ts_kickoff(p2r_bake_scopes_strings_task__entry_point, 0, in)); - } - } - } - - ////////////////////////////// - //- rjf: kick off name map building tasks - // - P2R_BuildBakeNameMapIn build_bake_name_map_in[RDI_NameMapKind_COUNT] = {0}; - TS_Ticket build_bake_name_map_ticket[RDI_NameMapKind_COUNT] = {0}; - for(RDI_NameMapKind k = (RDI_NameMapKind)(RDI_NameMapKind_NULL+1); - k < RDI_NameMapKind_COUNT; - k = (RDI_NameMapKind)(k+1)) - { - build_bake_name_map_in[k].k = k; - build_bake_name_map_in[k].params = in_params; - build_bake_name_map_ticket[k] = ts_kickoff(p2r_build_bake_name_map_task__entry_point, 0, &build_bake_name_map_in[k]); - } - - ////////////////////////////// - //- rjf: join string map building tasks - // - ProfScope("join string map building tasks") - { - for(TS_TicketNode *n = bake_string_map_build_tickets.first; n != 0; n = n->next) - { - ts_join(n->v, max_U64); - } - } - - ////////////////////////////// - //- rjf: produce joined string map - // - RDIM_BakeStringMapLoose *unsorted_bake_string_map = rdim_bake_string_map_loose_make(arena, &bake_string_map_topology); - ProfScope("produce joined string map") - { - U64 slots_per_task = 16384; - U64 num_tasks = (bake_string_map_topology.slots_count+slots_per_task-1)/slots_per_task; - TS_Ticket *task_tickets = push_array(scratch.arena, TS_Ticket, num_tasks); - - // rjf: kickoff tasks - for(U64 task_idx = 0; task_idx < num_tasks; task_idx += 1) - { - P2R_JoinBakeStringMapSlotsIn *in = push_array(scratch.arena, P2R_JoinBakeStringMapSlotsIn, 1); - in->top = &bake_string_map_topology; - in->src_maps = bake_string_maps__in_progress; - in->src_maps_count = ts_thread_count(); - in->dst_map = unsorted_bake_string_map; - in->slot_idx_range = r1u64(task_idx*slots_per_task, task_idx*slots_per_task + slots_per_task); - in->slot_idx_range.max = Min(in->slot_idx_range.max, in->top->slots_count); - task_tickets[task_idx] = ts_kickoff(p2r_bake_string_map_join_task__entry_point, 0, in); - } - - // rjf: join tasks - for(U64 task_idx = 0; task_idx < num_tasks; task_idx += 1) - { - ts_join(task_tickets[task_idx], max_U64); - } - - // rjf: insert small top-level stuff - rdim_bake_string_map_loose_push_top_level_info(arena, &bake_string_map_topology, unsorted_bake_string_map, &in_params->top_level_info); - rdim_bake_string_map_loose_push_binary_sections(arena, &bake_string_map_topology, unsorted_bake_string_map, &in_params->binary_sections); - rdim_bake_string_map_loose_push_path_tree(arena, &bake_string_map_topology, unsorted_bake_string_map, path_tree); - } - - ////////////////////////////// - //- rjf: kick off string map sorting tasks - // - TS_TicketList sort_bake_string_map_task_tickets = {0}; - RDIM_BakeStringMapLoose *sorted_bake_string_map__in_progress = rdim_bake_string_map_loose_make(arena, &bake_string_map_topology); - { - U64 slots_per_task = 4096; - U64 num_tasks = (bake_string_map_topology.slots_count+slots_per_task-1)/slots_per_task; - for(U64 task_idx = 0; task_idx < num_tasks; task_idx += 1) - { - P2R_SortBakeStringMapSlotsIn *in = push_array(scratch.arena, P2R_SortBakeStringMapSlotsIn, 1); - { - in->top = &bake_string_map_topology; - in->src_map = unsorted_bake_string_map; - in->dst_map = sorted_bake_string_map__in_progress; - in->slot_idx = task_idx*slots_per_task; - in->slot_count = slots_per_task; - if(in->slot_idx+in->slot_count > bake_string_map_topology.slots_count) - { - in->slot_count = bake_string_map_topology.slots_count - in->slot_idx; - } - } - ts_ticket_list_push(scratch.arena, &sort_bake_string_map_task_tickets, ts_kickoff(p2r_bake_string_map_sort_task__entry_point, 0, in)); - } - } - - ////////////////////////////// - //- rjf: join string map sorting tasks - // - ProfScope("join string map sorting tasks") - { - for(TS_TicketNode *n = sort_bake_string_map_task_tickets.first; n != 0; n = n->next) - { - ts_join(n->v, max_U64); - } - } - RDIM_BakeStringMapLoose *sorted_bake_string_map = sorted_bake_string_map__in_progress; - - ////////////////////////////// - //- rjf: build finalized string map - // - ProfBegin("build finalized string map base indices"); - RDIM_BakeStringMapBaseIndices bake_string_map_base_idxes = rdim_bake_string_map_base_indices_from_map_loose(arena, &bake_string_map_topology, sorted_bake_string_map); - ProfEnd(); - ProfBegin("build finalized string map"); - RDIM_BakeStringMapTight bake_strings = rdim_bake_string_map_tight_from_loose(arena, &bake_string_map_topology, &bake_string_map_base_idxes, sorted_bake_string_map); - ProfEnd(); - - ////////////////////////////// - //- rjf: kick off pass 2 tasks - // - P2R_BakeUnitsIn bake_units_top_level_in = {&bake_strings, path_tree, &in_params->units}; - TS_Ticket bake_units_ticket = ts_kickoff(p2r_bake_units_task__entry_point, 0, &bake_units_top_level_in); - P2R_BakeUnitVMapIn bake_unit_vmap_in = {&in_params->units}; - TS_Ticket bake_unit_vmap_ticket = ts_kickoff(p2r_bake_unit_vmap_task__entry_point, 0, &bake_unit_vmap_in); - P2R_BakeSrcFilesIn bake_src_files_in = {&bake_strings, path_tree, &in_params->src_files}; - TS_Ticket bake_src_files_ticket = ts_kickoff(p2r_bake_src_files_task__entry_point, 0, &bake_src_files_in); - P2R_BakeUDTsIn bake_udts_in = {&bake_strings, &in_params->udts}; - TS_Ticket bake_udts_ticket = ts_kickoff(p2r_bake_udts_task__entry_point, 0, &bake_udts_in); - P2R_BakeGlobalVariablesIn bake_global_variables_in = {&bake_strings, &in_params->global_variables}; - TS_Ticket bake_global_variables_ticket = ts_kickoff(p2r_bake_global_variables_task__entry_point, 0, &bake_global_variables_in); - P2R_BakeGlobalVMapIn bake_global_vmap_in = {&in_params->global_variables}; - TS_Ticket bake_global_vmap_ticket = ts_kickoff(p2r_bake_global_vmap_task__entry_point, 0, &bake_global_vmap_in); - P2R_BakeThreadVariablesIn bake_thread_variables_in = {&bake_strings, &in_params->thread_variables}; - TS_Ticket bake_thread_variables_ticket = ts_kickoff(p2r_bake_thread_variables_task__entry_point, 0, &bake_thread_variables_in); - P2R_BakeProceduresIn bake_procedures_in = {&bake_strings, &in_params->procedures}; - TS_Ticket bake_procedures_ticket = ts_kickoff(p2r_bake_procedures_task__entry_point, 0, &bake_procedures_in); - P2R_BakeScopesIn bake_scopes_in = {&bake_strings, &in_params->scopes}; - TS_Ticket bake_scopes_ticket = ts_kickoff(p2r_bake_scopes_task__entry_point, 0, &bake_scopes_in); - P2R_BakeScopeVMapIn bake_scope_vmap_in = {&in_params->scopes}; - TS_Ticket bake_scope_vmap_ticket = ts_kickoff(p2r_bake_scope_vmap_task__entry_point, 0, &bake_scope_vmap_in); - P2R_BakeInlineSitesIn bake_inline_sites_in = {&bake_strings, &in_params->inline_sites}; - TS_Ticket bake_inline_sites_ticket = ts_kickoff(p2r_bake_inline_sites_task__entry_point, 0, &bake_inline_sites_in); - P2R_BakeFilePathsIn bake_file_paths_in = {&bake_strings, path_tree}; - TS_Ticket bake_file_paths_ticket = ts_kickoff(p2r_bake_file_paths_task__entry_point, 0, &bake_file_paths_in); - P2R_BakeStringsIn bake_strings_in = {&bake_strings}; - TS_Ticket bake_strings_ticket = ts_kickoff(p2r_bake_strings_task__entry_point, 0, &bake_strings_in); - - ////////////////////////////// - //- rjf: join name map building tasks - // - RDIM_BakeNameMap *name_maps[RDI_NameMapKind_COUNT] = {0}; - ProfScope("join name map building tasks") - { - for(RDI_NameMapKind k = (RDI_NameMapKind)(RDI_NameMapKind_NULL+1); - k < RDI_NameMapKind_COUNT; - k = (RDI_NameMapKind)(k+1)) - { - name_maps[k] = ts_join_struct(build_bake_name_map_ticket[k], max_U64, RDIM_BakeNameMap); - } - } - - ////////////////////////////// - //- rjf: build interned idx run map - // - RDIM_BakeIdxRunMap *idx_runs = 0; - ProfScope("build interned idx run map") - { - idx_runs = rdim_bake_idx_run_map_from_params(arena, name_maps, in_params); - } - - ////////////////////////////// - //- rjf: do small top-level bakes - // - ProfScope("top level info") out_results->top_level_info = rdim_bake_top_level_info(arena, &bake_strings, &in_params->top_level_info); - ProfScope("binary sections") out_results->binary_sections = rdim_bake_binary_sections(arena, &bake_strings, &in_params->binary_sections); - ProfScope("top level name maps section") out_results->top_level_name_maps = rdim_bake_name_maps_top_level(arena, &bake_strings, idx_runs, name_maps); - - ////////////////////////////// - //- rjf: kick off pass 3 tasks - // - P2R_BakeTypeNodesIn bake_type_nodes_in = {&bake_strings, idx_runs, &in_params->types}; - TS_Ticket bake_type_nodes_ticket = ts_kickoff(p2r_bake_type_nodes_task__entry_point, 0, &bake_type_nodes_in); - TS_Ticket bake_name_maps_tickets[RDI_NameMapKind_COUNT] = {0}; - { - for(EachNonZeroEnumVal(RDI_NameMapKind, k)) - { - if(name_maps[k] == 0 || name_maps[k]->name_count == 0) - { - continue; - } - P2R_BakeNameMapIn *in = push_array(scratch.arena, P2R_BakeNameMapIn, 1); - in->strings = &bake_strings; - in->idx_runs = idx_runs; - in->map = name_maps[k]; - in->kind = k; - bake_name_maps_tickets[k] = ts_kickoff(p2r_bake_name_map_task__entry_point, 0, in); - } - } - P2R_BakeIdxRunsIn bake_idx_runs_in = {idx_runs}; - TS_Ticket bake_idx_runs_ticket = ts_kickoff(p2r_bake_idx_runs_task__entry_point, 0, &bake_idx_runs_in); - - ////////////////////////////// - //- rjf: join remaining completed bakes - // - ProfScope("top-level units info") out_results->units = *ts_join_struct(bake_units_ticket, max_U64, RDIM_UnitBakeResult); - ProfScope("unit vmap") out_results->unit_vmap = *ts_join_struct(bake_unit_vmap_ticket, max_U64, RDIM_UnitVMapBakeResult); - ProfScope("source files") out_results->src_files = *ts_join_struct(bake_src_files_ticket, max_U64, RDIM_SrcFileBakeResult); - ProfScope("UDTs") out_results->udts = *ts_join_struct(bake_udts_ticket, max_U64, RDIM_UDTBakeResult); - ProfScope("global variables") out_results->global_variables = *ts_join_struct(bake_global_variables_ticket, max_U64, RDIM_GlobalVariableBakeResult); - ProfScope("global vmap") out_results->global_vmap = *ts_join_struct(bake_global_vmap_ticket, max_U64, RDIM_GlobalVMapBakeResult); - ProfScope("thread variables") out_results->thread_variables = *ts_join_struct(bake_thread_variables_ticket, max_U64, RDIM_ThreadVariableBakeResult); - ProfScope("procedures") out_results->procedures = *ts_join_struct(bake_procedures_ticket, max_U64, RDIM_ProcedureBakeResult); - ProfScope("scopes") out_results->scopes = *ts_join_struct(bake_scopes_ticket, max_U64, RDIM_ScopeBakeResult); - ProfScope("scope vmap") out_results->scope_vmap = *ts_join_struct(bake_scope_vmap_ticket, max_U64, RDIM_ScopeVMapBakeResult); - ProfScope("inline sites") out_results->inline_sites = *ts_join_struct(bake_inline_sites_ticket, max_U64, RDIM_InlineSiteBakeResult); - ProfScope("file paths") out_results->file_paths = *ts_join_struct(bake_file_paths_ticket, max_U64, RDIM_FilePathBakeResult); - ProfScope("strings") out_results->strings = *ts_join_struct(bake_strings_ticket, max_U64, RDIM_StringBakeResult); - ProfScope("type nodes") out_results->type_nodes = *ts_join_struct(bake_type_nodes_ticket, max_U64, RDIM_TypeNodeBakeResult); - ProfScope("idx runs") out_results->idx_runs = *ts_join_struct(bake_idx_runs_ticket, max_U64, RDIM_IndexRunBakeResult); - ProfScope("line tables") out_results->line_tables = *ts_join_struct(bake_line_tables_ticket, max_U64, RDIM_LineTableBakeResult); - - ////////////////////////////// - //- rjf: join individual name map bakes - // - RDIM_NameMapBakeResult name_map_bakes[RDI_NameMapKind_COUNT] = {0}; - ProfScope("name maps") - { - for(EachNonZeroEnumVal(RDI_NameMapKind, k)) - { - RDIM_NameMapBakeResult *bake = ts_join_struct(bake_name_maps_tickets[k], max_U64, RDIM_NameMapBakeResult); - if(bake != 0) - { - name_map_bakes[k] = *bake; - } - } - } - - ////////////////////////////// - //- rjf: join all individual name map bakes - // - ProfScope("join all name map bakes into final name map bake") - { - out_results->name_maps = rdim_name_map_bake_results_combine(arena, name_map_bakes, ArrayCount(name_map_bakes)); - } - - scratch_end(scratch); - return out; -} - -//////////////////////////////// -//~ rjf: Top-Level Compression Entry Point - -internal P2R_Serialize2File * -p2r_compress(Arena *arena, P2R_Serialize2File *in) -{ - P2R_Serialize2File *out = push_array(arena, P2R_Serialize2File, 1); - { - //- rjf: set up compression context - rr_lzb_simple_context ctx = {0}; - ctx.m_tableSizeBits = 14; - ctx.m_hashTable = push_array(arena, U16, 1<bundle.sections[k]; - RDIM_SerializedSection *dst = &out->bundle.sections[k]; - MemoryCopyStruct(dst, src); - - // rjf: determine if this section should be compressed - B32 should_compress = 1; - - // rjf: compress if needed - if(should_compress) - { - MemoryZero(ctx.m_hashTable, sizeof(U16)*(1<data = push_array_no_zero(arena, U8, src->encoded_size); - dst->encoded_size = rr_lzb_simple_encode_veryfast(&ctx, src->data, src->encoded_size, dst->data); - dst->unpacked_size = src->encoded_size; - dst->encoding = RDI_SectionEncoding_LZB; - } - } - } - return out; -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +// TODO(rjf): eliminate redundant null checks, just always allocate +// empty results, and have nulls gracefully fall through +// +// (search for != 0 instances, inserted to prevent prior crashes) + +//////////////////////////////// +//~ rjf: Basic Helpers + +internal U64 +p2r_end_of_cplusplus_container_name(String8 str) +{ + // NOTE: This finds the index one past the last "::" contained in str. + // if no "::" is contained in str, then the returned index is 0. + // The intent is that [0,clamp_bot(0,result - 2)) gives the + // "container name" and [result,str.size) gives the leaf name. + U64 result = 0; + if(str.size >= 2) + { + for(U64 i = str.size; i >= 2; i -= 1) + { + if(str.str[i - 2] == ':' && str.str[i - 1] == ':') + { + result = i; + break; + } + } + } + return(result); +} + +internal U64 +p2r_hash_from_voff(U64 voff) +{ + U64 hash = (voff >> 3) ^ ((7 & voff) << 6); + return hash; +} + +//////////////////////////////// +//~ rjf: Command Line -> Conversion Inputs + +internal P2R_User2Convert * +p2r_user2convert_from_cmdln(Arena *arena, CmdLine *cmdline) +{ + P2R_User2Convert *result = push_array(arena, P2R_User2Convert, 1); + + //- rjf: get input pdb + { + String8 input_name = cmd_line_string(cmdline, str8_lit("pdb")); + if(input_name.size == 0) + { + str8_list_push(arena, &result->errors, str8_lit("Missing required parameter: '--pdb:'")); + } + if(input_name.size > 0) + { + String8 input_data = os_data_from_file_path(arena, input_name); + if(input_data.size == 0) + { + str8_list_pushf(arena, &result->errors, "Could not load input PDB file from '%S'", input_name); + } + if(input_data.size != 0) + { + result->input_pdb_name = input_name; + result->input_pdb_data = input_data; + } + } + } + + //- rjf: get input exe + { + String8 input_name = cmd_line_string(cmdline, str8_lit("exe")); + if(input_name.size > 0) + { + String8 input_data = os_data_from_file_path(arena, input_name); + if(input_data.size == 0) + { + str8_list_pushf(arena, &result->errors, "Could not load input EXE file from '%S'", input_name); + } + if(input_data.size != 0) + { + result->input_exe_name = input_name; + result->input_exe_data = input_data; + } + } + } + + //- rjf: get output name + { + result->output_name = cmd_line_string(cmdline, str8_lit("out")); + if(result->output_name.size == 0) + { + str8_list_pushf(arena, &result->errors, "Missing required parameter: '--out:'"); + } + } + + //- rjf: define string -> flag bits +#define FlagNameMapXList \ +Case("sections", BinarySections)\ +Case("units", Units)\ +Case("procedures", Procedures)\ +Case("globals", GlobalVariables)\ +Case("threadvars", ThreadVariables)\ +Case("scopes", Scopes)\ +Case("locals", Locals)\ +Case("types", Types)\ +Case("udts", UDTs)\ +Case("lines", LineInfo)\ +Case("globals_name_map", GlobalVariableNameMap)\ +Case("threadvars_name_map", ThreadVariableNameMap)\ +Case("procedure_name_map", ProcedureNameMap)\ +Case("type_name_map", TypeNameMap)\ +Case("link_name_map", LinkNameProcedureNameMap)\ +Case("source_path_name_map",NormalSourcePathNameMap)\ + + //- rjf: get flags + { + result->flags = P2R_ConvertFlag_All; + String8List only_names = cmd_line_strings(cmdline, str8_lit("only")); + String8List omit_names = cmd_line_strings(cmdline, str8_lit("only")); + if(only_names.node_count != 0) + { + result->flags = 0; + for(String8Node *n = only_names.first; n != 0; n = n->next) + { + String8 string = n->string; +#define Case(str, flag) if(str8_match(string, str8_lit(str), StringMatchFlag_CaseInsensitive)) {result->flags |= P2R_ConvertFlag_##flag;} + FlagNameMapXList; +#undef Case + } + } + if(omit_names.node_count != 0) + { + for(String8Node *n = omit_names.first; n != 0; n = n->next) + { + String8 string = n->string; +#define Case(str, flag) if(str8_match(string, str8_lit(str), StringMatchFlag_CaseInsensitive)) {result->flags &= ~P2R_ConvertFlag_##flag;} + FlagNameMapXList; +#undef Case + } + } + } + +#undef FlagNameMapXList + return result; +} + +//////////////////////////////// +//~ rjf: COFF <-> RDI Canonical Conversions + +internal RDI_BinarySectionFlags +p2r_rdi_binary_section_flags_from_coff_section_flags(COFF_SectionFlags flags) +{ + RDI_BinarySectionFlags result = 0; + if(flags & COFF_SectionFlag_MEM_READ) + { + result |= RDI_BinarySectionFlag_Read; + } + if(flags & COFF_SectionFlag_MEM_WRITE) + { + result |= RDI_BinarySectionFlag_Write; + } + if(flags & COFF_SectionFlag_MEM_EXECUTE) + { + result |= RDI_BinarySectionFlag_Execute; + } + return(result); +} + +//////////////////////////////// +//~ rjf: CodeView <-> RDI Canonical Conversions + +internal RDI_Arch +p2r_rdi_arch_from_cv_arch(CV_Arch cv_arch) +{ + RDI_Arch result = 0; + switch(cv_arch) + { + case CV_Arch_8086: result = RDI_Arch_X86; break; + case CV_Arch_X64: result = RDI_Arch_X64; break; + //case CV_Arch_8080: break; + //case CV_Arch_80286: break; + //case CV_Arch_80386: break; + //case CV_Arch_80486: break; + //case CV_Arch_PENTIUM: break; + //case CV_Arch_PENTIUMII: break; + //case CV_Arch_PENTIUMIII: break; + //case CV_Arch_MIPS: break; + //case CV_Arch_MIPS16: break; + //case CV_Arch_MIPS32: break; + //case CV_Arch_MIPS64: break; + //case CV_Arch_MIPSI: break; + //case CV_Arch_MIPSII: break; + //case CV_Arch_MIPSIII: break; + //case CV_Arch_MIPSIV: break; + //case CV_Arch_MIPSV: break; + //case CV_Arch_M68000: break; + //case CV_Arch_M68010: break; + //case CV_Arch_M68020: break; + //case CV_Arch_M68030: break; + //case CV_Arch_M68040: break; + //case CV_Arch_ALPHA: break; + //case CV_Arch_ALPHA_21164: break; + //case CV_Arch_ALPHA_21164A: break; + //case CV_Arch_ALPHA_21264: break; + //case CV_Arch_ALPHA_21364: break; + //case CV_Arch_PPC601: break; + //case CV_Arch_PPC603: break; + //case CV_Arch_PPC604: break; + //case CV_Arch_PPC620: break; + //case CV_Arch_PPCFP: break; + //case CV_Arch_PPCBE: break; + //case CV_Arch_SH3: break; + //case CV_Arch_SH3E: break; + //case CV_Arch_SH3DSP: break; + //case CV_Arch_SH4: break; + //case CV_Arch_SHMEDIA: break; + //case CV_Arch_ARM3: break; + //case CV_Arch_ARM4: break; + //case CV_Arch_ARM4T: break; + //case CV_Arch_ARM5: break; + //case CV_Arch_ARM5T: break; + //case CV_Arch_ARM6: break; + //case CV_Arch_ARM_XMAC: break; + //case CV_Arch_ARM_WMMX: break; + //case CV_Arch_ARM7: break; + //case CV_Arch_OMNI: break; + //case CV_Arch_IA64_1: break; + //case CV_Arch_IA64_2: break; + //case CV_Arch_CEE: break; + //case CV_Arch_AM33: break; + //case CV_Arch_M32R: break; + //case CV_Arch_TRICORE: break; + //case CV_Arch_EBC: break; + //case CV_Arch_THUMB: break; + //case CV_Arch_ARMNT: break; + //case CV_Arch_ARM64: break; + //case CV_Arch_D3D11_SHADER: break; + } + return(result); +} + +internal RDI_RegCode +p2r_rdi_reg_code_from_cv_reg_code(RDI_Arch arch, CV_Reg reg_code) +{ + RDI_RegCode result = 0; + switch(arch) + { + case RDI_Arch_X86: + { + switch(reg_code) + { +#define X(CVN,C,RDN,BP,BZ) case C: result = RDI_RegCodeX86_##RDN; break; + CV_Reg_X86_XList(X) +#undef X + } + }break; + case RDI_Arch_X64: + { + switch(reg_code) + { +#define X(CVN,C,RDN,BP,BZ) case C: result = RDI_RegCodeX64_##RDN; break; + CV_Reg_X64_XList(X) +#undef X + } + }break; + } + return(result); +} + +internal RDI_Language +p2r_rdi_language_from_cv_language(CV_Language cv_language) +{ + RDI_Language result = 0; + switch(cv_language) + { + case CV_Language_C: result = RDI_Language_C; break; + case CV_Language_CXX: result = RDI_Language_CPlusPlus; break; + //case CV_Language_FORTRAN: result = ; break; + //case CV_Language_MASM: result = ; break; + //case CV_Language_PASCAL: result = ; break; + //case CV_Language_BASIC: result = ; break; + //case CV_Language_COBOL: result = ; break; + //case CV_Language_LINK: result = ; break; + //case CV_Language_CVTRES: result = ; break; + //case CV_Language_CVTPGD: result = ; break; + //case CV_Language_CSHARP: result = ; break; + //case CV_Language_VB: result = ; break; + //case CV_Language_ILASM: result = ; break; + //case CV_Language_JAVA: result = ; break; + //case CV_Language_JSCRIPT: result = ; break; + //case CV_Language_MSIL: result = ; break; + //case CV_Language_HLSL: result = ; break; + } + return(result); +} + +internal RDI_TypeKind +p2r_rdi_type_kind_from_cv_basic_type(CV_BasicType basic_type) +{ + RDI_TypeKind result = RDI_TypeKind_NULL; + switch(basic_type) + { + case CV_BasicType_VOID: {result = RDI_TypeKind_Void;}break; + case CV_BasicType_HRESULT: {result = RDI_TypeKind_Handle;}break; + + case CV_BasicType_RCHAR: + case CV_BasicType_CHAR: + case CV_BasicType_CHAR8: + {result = RDI_TypeKind_Char8;}break; + + case CV_BasicType_UCHAR: {result = RDI_TypeKind_UChar8;}break; + case CV_BasicType_WCHAR: {result = RDI_TypeKind_UChar16;}break; + case CV_BasicType_CHAR16: {result = RDI_TypeKind_Char16;}break; + case CV_BasicType_CHAR32: {result = RDI_TypeKind_Char32;}break; + + case CV_BasicType_BOOL8: + case CV_BasicType_INT8: + {result = RDI_TypeKind_S8;}break; + + case CV_BasicType_BOOL16: + case CV_BasicType_INT16: + case CV_BasicType_SHORT: + {result = RDI_TypeKind_S16;}break; + + case CV_BasicType_BOOL32: + case CV_BasicType_INT32: + case CV_BasicType_LONG: + {result = RDI_TypeKind_S32;}break; + + case CV_BasicType_BOOL64: + case CV_BasicType_INT64: + case CV_BasicType_QUAD: + {result = RDI_TypeKind_S64;}break; + + case CV_BasicType_INT128: + case CV_BasicType_OCT: + {result = RDI_TypeKind_S128;}break; + + case CV_BasicType_UINT8: {result = RDI_TypeKind_U8;}break; + + case CV_BasicType_UINT16: + case CV_BasicType_USHORT: + {result = RDI_TypeKind_U16;}break; + + case CV_BasicType_UINT32: + case CV_BasicType_ULONG: + {result = RDI_TypeKind_U32;}break; + + case CV_BasicType_UINT64: + case CV_BasicType_UQUAD: + {result = RDI_TypeKind_U64;}break; + + case CV_BasicType_UINT128: + case CV_BasicType_UOCT: + {result = RDI_TypeKind_U128;}break; + + case CV_BasicType_FLOAT16:{result = RDI_TypeKind_F16;}break; + case CV_BasicType_FLOAT32:{result = RDI_TypeKind_F32;}break; + case CV_BasicType_FLOAT32PP:{result = RDI_TypeKind_F32PP;}break; + case CV_BasicType_FLOAT48:{result = RDI_TypeKind_F48;}break; + case CV_BasicType_FLOAT64:{result = RDI_TypeKind_F64;}break; + case CV_BasicType_FLOAT80:{result = RDI_TypeKind_F80;}break; + case CV_BasicType_FLOAT128:{result = RDI_TypeKind_F128;}break; + case CV_BasicType_COMPLEX32:{result = RDI_TypeKind_ComplexF32;}break; + case CV_BasicType_COMPLEX64:{result = RDI_TypeKind_ComplexF64;}break; + case CV_BasicType_COMPLEX80:{result = RDI_TypeKind_ComplexF80;}break; + case CV_BasicType_COMPLEX128:{result = RDI_TypeKind_ComplexF128;}break; + case CV_BasicType_PTR:{result = RDI_TypeKind_Handle;}break; + } + return result; +} + +//////////////////////////////// +//~ rjf: Location Info Building Helpers + +internal RDIM_Location * +p2r_location_from_addr_reg_off(Arena *arena, RDI_Arch arch, RDI_RegCode reg_code, U32 reg_byte_size, U32 reg_byte_pos, S64 offset, B32 extra_indirection) +{ + RDIM_Location *result = 0; + if(0 <= offset && offset <= (S64)max_U16) + { + if(extra_indirection) + { + result = rdim_push_location_addr_addr_reg_plus_u16(arena, reg_code, (U16)offset); + } + else + { + result = rdim_push_location_addr_reg_plus_u16(arena, reg_code, (U16)offset); + } + } + else + { + RDIM_EvalBytecode bytecode = {0}; + U32 regread_param = RDI_EncodeRegReadParam(reg_code, reg_byte_size, reg_byte_pos); + rdim_bytecode_push_op(arena, &bytecode, RDI_EvalOp_RegRead, regread_param); + rdim_bytecode_push_sconst(arena, &bytecode, offset); + rdim_bytecode_push_op(arena, &bytecode, RDI_EvalOp_Add, 0); + if(extra_indirection) + { + U64 addr_size = rdi_addr_size_from_arch(arch); + rdim_bytecode_push_op(arena, &bytecode, RDI_EvalOp_MemRead, addr_size); + } + result = rdim_push_location_addr_bytecode_stream(arena, &bytecode); + } + return result; +} + +internal CV_EncodedFramePtrReg +p2r_cv_encoded_fp_reg_from_frameproc(CV_SymFrameproc *frameproc, B32 param_base) +{ + CV_EncodedFramePtrReg result = 0; + CV_FrameprocFlags flags = frameproc->flags; + if(param_base) + { + result = CV_FrameprocFlags_ExtractParamBasePointer(flags); + } + else + { + result = CV_FrameprocFlags_ExtractLocalBasePointer(flags); + } + return result; +} + +internal RDI_RegCode +p2r_reg_code_from_arch_encoded_fp_reg(RDI_Arch arch, CV_EncodedFramePtrReg encoded_reg) +{ + RDI_RegCode result = 0; + switch(arch) + { + case RDI_Arch_X86: + { + switch(encoded_reg) + { + case CV_EncodedFramePtrReg_StackPtr: + { + // TODO(allen): support CV_AllReg_VFRAME + // TODO(allen): error + }break; + case CV_EncodedFramePtrReg_FramePtr: + { + result = RDI_RegCodeX86_ebp; + }break; + case CV_EncodedFramePtrReg_BasePtr: + { + result = RDI_RegCodeX86_ebx; + }break; + } + }break; + case RDI_Arch_X64: + { + switch(encoded_reg) + { + case CV_EncodedFramePtrReg_StackPtr: + { + result = RDI_RegCodeX64_rsp; + }break; + case CV_EncodedFramePtrReg_FramePtr: + { + result = RDI_RegCodeX64_rbp; + }break; + case CV_EncodedFramePtrReg_BasePtr: + { + result = RDI_RegCodeX64_r13; + }break; + } + }break; + } + return(result); +} + +internal void +p2r_location_over_lvar_addr_range(Arena *arena, RDIM_ScopeChunkList *scopes, RDIM_LocationSet *locset, RDIM_Location *location, CV_LvarAddrRange *range, COFF_SectionHeader *section, CV_LvarAddrGap *gaps, U64 gap_count) +{ + //- rjf: extract range info + U64 voff_first = 0; + U64 voff_opl = 0; + if(section != 0) + { + voff_first = section->voff + range->off; + voff_opl = voff_first + range->len; + } + + //- rjf: emit ranges + CV_LvarAddrGap *gap_ptr = gaps; + U64 voff_cursor = voff_first; + for(U64 i = 0; i < gap_count; i += 1, gap_ptr += 1) + { + U64 voff_gap_first = voff_first + gap_ptr->off; + U64 voff_gap_opl = voff_gap_first + gap_ptr->len; + if(voff_cursor < voff_gap_first) + { + RDIM_Rng1U64 voff_range = {voff_cursor, voff_gap_first}; + rdim_location_set_push_case(arena, scopes, locset, voff_range, location); + } + voff_cursor = voff_gap_opl; + } + + //- rjf: emit remaining range + if(voff_cursor < voff_opl) + { + RDIM_Rng1U64 voff_range = {voff_cursor, voff_opl}; + rdim_location_set_push_case(arena, scopes, locset, voff_range, location); + } +} + +//////////////////////////////// +//~ rjf: Initial Parsing & Preparation Pass Tasks + +internal TS_TASK_FUNCTION_DEF(p2r_exe_hash_task__entry_point) +{ + P2R_EXEHashIn *in = (P2R_EXEHashIn *)p; + U64 *out = push_array(arena, U64, 1); + ProfScope("hash exe") *out = rdi_hash(in->exe_data.str, in->exe_data.size); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_tpi_hash_parse_task__entry_point) +{ + P2R_TPIHashParseIn *in = (P2R_TPIHashParseIn *)p; + void *out = 0; + ProfScope("parse tpi hash") out = pdb_tpi_hash_from_data(arena, in->strtbl, in->tpi, in->hash_data, in->aux_data); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_tpi_leaf_parse_task__entry_point) +{ + P2R_TPILeafParseIn *in = (P2R_TPILeafParseIn *)p; + void *out = 0; + ProfScope("parse tpi leaf") out = cv_leaf_from_data(arena, in->leaf_data, in->itype_first); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_symbol_stream_parse_task__entry_point) +{ + P2R_SymbolStreamParseIn *in = (P2R_SymbolStreamParseIn *)p; + void *out = 0; + ProfScope("parse symbol stream") out = cv_sym_from_data(arena, in->data, 4); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_c13_stream_parse_task__entry_point) +{ + P2R_C13StreamParseIn *in = (P2R_C13StreamParseIn *)p; + void *out = 0; + ProfScope("parse c13 stream") out = cv_c13_parsed_from_data(arena, in->data, in->strtbl, in->coff_sections); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_comp_unit_parse_task__entry_point) +{ + P2R_CompUnitParseIn *in = (P2R_CompUnitParseIn *)p; + void *out = 0; + ProfScope("parse comp units") out = pdb_comp_unit_array_from_data(arena, in->data); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_comp_unit_contributions_parse_task__entry_point) +{ + P2R_CompUnitContributionsParseIn *in = (P2R_CompUnitContributionsParseIn *)p; + void *out = 0; + ProfScope("parse comp unit contributions") out = pdb_comp_unit_contribution_array_from_data(arena, in->data, in->coff_sections); + return out; +} + +//////////////////////////////// +//~ rjf: Unit Conversion Tasks + +internal TS_TASK_FUNCTION_DEF(p2r_units_convert_task__entry_point) +{ + Temp scratch = scratch_begin(&arena, 1); + P2R_UnitConvertIn *in = (P2R_UnitConvertIn *)p; + P2R_UnitConvertOut *out = push_array(arena, P2R_UnitConvertOut, 1); + ProfScope("build units, initial src file map, & collect unit source files") + if(in->comp_units != 0) + { + U64 units_chunk_cap = in->comp_units->count; + P2R_SrcFileMap src_file_map = {0}; + src_file_map.slots_count = 65536; + src_file_map.slots = push_array(scratch.arena, P2R_SrcFileNode *, src_file_map.slots_count); + + //////////////////////////// + //- rjf: pass 1: build per-unit info & per-unit line tables + // + ProfScope("pass 1: build per-unit info & per-unit line tables") + for(U64 comp_unit_idx = 0; comp_unit_idx < in->comp_units->count; comp_unit_idx += 1) + { + PDB_CompUnit *pdb_unit = in->comp_units->units[comp_unit_idx]; + CV_SymParsed *pdb_unit_sym = in->comp_unit_syms[comp_unit_idx]; + CV_C13Parsed *pdb_unit_c13 = in->comp_unit_c13s[comp_unit_idx]; + + //- rjf: produce unit name + String8 unit_name = pdb_unit->obj_name; + if(unit_name.size != 0) + { + String8 unit_name_past_last_slash = str8_skip_last_slash(unit_name); + if(unit_name_past_last_slash.size != 0) + { + unit_name = unit_name_past_last_slash; + } + } + + //- rjf: produce obj name + String8 obj_name = pdb_unit->obj_name; + if(str8_match(obj_name, str8_lit("* Linker *"), 0) || + str8_match(obj_name, str8_lit("Import:"), StringMatchFlag_RightSideSloppy)) + { + MemoryZeroStruct(&obj_name); + } + + //- rjf: build this unit's line table, fill out primary line info (inline info added after) + RDIM_LineTable *line_table = 0; + for(CV_C13SubSectionNode *node = pdb_unit_c13->first_sub_section; + node != 0; + node = node->next) + { + if(node->kind == CV_C13SubSectionKind_Lines) + { + for(CV_C13LinesParsedNode *lines_n = node->lines_first; + lines_n != 0; + lines_n = lines_n->next) + { + CV_C13LinesParsed *lines = &lines_n->v; + + // rjf: file name -> normalized file path + String8 file_path = lines->file_name; + String8 file_path_normalized = lower_from_str8(scratch.arena, str8_skip_chop_whitespace(file_path)); + for(U64 idx = 0; idx < file_path_normalized.size; idx += 1) + { + if(file_path_normalized.str[idx] == '\\') + { + file_path_normalized.str[idx] = '/'; + } + } + + // rjf: normalized file path -> source file node + U64 file_path_normalized_hash = rdi_hash(file_path_normalized.str, file_path_normalized.size); + U64 src_file_slot = file_path_normalized_hash%src_file_map.slots_count; + P2R_SrcFileNode *src_file_node = 0; + for(P2R_SrcFileNode *n = src_file_map.slots[src_file_slot]; n != 0; n = n->next) + { + if(str8_match(n->src_file->normal_full_path, file_path_normalized, 0)) + { + src_file_node = n; + break; + } + } + if(src_file_node == 0) + { + src_file_node = push_array(scratch.arena, P2R_SrcFileNode, 1); + SLLStackPush(src_file_map.slots[src_file_slot], src_file_node); + src_file_node->src_file = rdim_src_file_chunk_list_push(arena, &out->src_files, 4096); + src_file_node->src_file->normal_full_path = push_str8_copy(arena, file_path_normalized); + } + + // rjf: push sequence into both line table & source file's line map + if(lines->line_count != 0) + { + if(line_table == 0) + { + line_table = rdim_line_table_chunk_list_push(arena, &out->line_tables, 256); + } + RDIM_LineSequence *seq = rdim_line_table_push_sequence(arena, &out->line_tables, line_table, src_file_node->src_file, lines->voffs, lines->line_nums, lines->col_nums, lines->line_count); + rdim_src_file_push_line_sequence(arena, &out->src_files, src_file_node->src_file, seq); + } + } + } + } + + //- rjf: build unit + RDIM_Unit *dst_unit = rdim_unit_chunk_list_push(arena, &out->units, units_chunk_cap); + dst_unit->unit_name = unit_name; + dst_unit->compiler_name = pdb_unit_sym->info.compiler_name; + dst_unit->object_file = obj_name; + dst_unit->archive_file = pdb_unit->group_name; + dst_unit->language = p2r_rdi_language_from_cv_language(pdb_unit_sym->info.language); + dst_unit->line_table = line_table; + } + + //////////////////////////// + //- rjf: pass 2: build per-unit voff ranges from comp unit contributions table + // + PDB_CompUnitContribution *contrib_ptr = in->comp_unit_contributions->contributions; + PDB_CompUnitContribution *contrib_opl = contrib_ptr + in->comp_unit_contributions->count; + ProfScope("pass 2: build per-unit voff ranges from comp unit contributions table") + for(;contrib_ptr < contrib_opl; contrib_ptr += 1) + { + if(contrib_ptr->mod < in->comp_units->count) + { + RDIM_Unit *unit = &out->units.first->v[contrib_ptr->mod]; + RDIM_Rng1U64 range = {contrib_ptr->voff_first, contrib_ptr->voff_opl}; + rdim_rng1u64_list_push(arena, &unit->voff_ranges, range); + } + } + + //////////////////////////// + //- rjf: pass 3: parse all inlinee line tables + // + out->units_first_inline_site_line_tables = push_array(arena, RDIM_LineTable *, in->comp_units->count); + ProfScope("pass 3: parse all inlinee line tables") + for(U64 comp_unit_idx = 0; comp_unit_idx < in->comp_units->count; comp_unit_idx += 1) + { + CV_SymParsed *unit_sym = in->comp_unit_syms[comp_unit_idx]; + CV_C13Parsed *unit_c13 = in->comp_unit_c13s[comp_unit_idx]; + CV_RecRange *rec_ranges_first = unit_sym->sym_ranges.ranges; + CV_RecRange *rec_ranges_opl = rec_ranges_first+unit_sym->sym_ranges.count; + U64 base_voff = 0; + for(CV_RecRange *rec_range = rec_ranges_first; + rec_range < rec_ranges_opl; + rec_range += 1) + { + //- rjf: rec range -> symbol info range + U64 sym_off_first = rec_range->off + 2; + U64 sym_off_opl = rec_range->off + rec_range->hdr.size; + + //- rjf: skip invalid ranges + if(sym_off_opl > unit_sym->data.size || sym_off_first > unit_sym->data.size || sym_off_first > sym_off_opl) + { + continue; + } + + //- rjf: unpack symbol info + CV_SymKind kind = rec_range->hdr.kind; + U64 sym_header_struct_size = cv_header_struct_size_from_sym_kind(kind); + void *sym_header_struct_base = unit_sym->data.str + sym_off_first; + void *sym_data_opl = unit_sym->data.str + sym_off_opl; + + //- rjf: skip bad sizes + if(sym_off_first + sym_header_struct_size > sym_off_opl) + { + continue; + } + + //- rjf: process symbol + switch(kind) + { + default:{}break; + + //- rjf: LPROC32/GPROC32 (gather base address) + case CV_SymKind_LPROC32: + case CV_SymKind_GPROC32: + { + CV_SymProc32 *proc32 = (CV_SymProc32 *)sym_header_struct_base; + COFF_SectionHeader *section = (0 < proc32->sec && proc32->sec <= in->coff_sections->count) ? &in->coff_sections->sections[proc32->sec-1] : 0; + if(section != 0) + { + base_voff = section->voff + proc32->off; + } + }break; + + //- rjf: INLINESITE + case CV_SymKind_INLINESITE: + { + // rjf: unpack sym + CV_SymInlineSite *sym = (CV_SymInlineSite *)sym_header_struct_base; + String8 binary_annots = str8((U8 *)(sym+1), rec_range->hdr.size - sizeof(rec_range->hdr.kind) - sizeof(*sym)); + + // rjf: map inlinee -> parsed cv c13 inlinee line info + CV_C13InlineeLinesParsed *inlinee_lines_parsed = 0; + { + U64 hash = cv_hash_from_item_id(sym->inlinee); + U64 slot_idx = hash%unit_c13->inlinee_lines_parsed_slots_count; + for(CV_C13InlineeLinesParsedNode *n = unit_c13->inlinee_lines_parsed_slots[slot_idx]; n != 0; n = n->hash_next) + { + if(n->v.inlinee == sym->inlinee) + { + inlinee_lines_parsed = &n->v; + break; + } + } + } + + // rjf: build line table, fill with parsed binary annotations + RDIM_LineTable *line_table = 0; + if(inlinee_lines_parsed != 0) + { + // rjf: state machine registers + CV_InlineRangeKind range_kind = 0; + U32 code_length = 0; + U32 code_offset = 0; + U32 last_code_offset = code_offset; + String8 file_name = inlinee_lines_parsed->file_name; + String8 last_file_name = file_name; + S32 line = (S32)inlinee_lines_parsed->first_source_ln; + S32 last_line = line; + S32 column = 1; + S32 last_column = column; + + // rjf: gathered lines + typedef struct LineChunk LineChunk; + struct LineChunk + { + LineChunk *next; + U64 cap; + U64 count; + U64 *voffs; // [line_count + 1] (sorted) + U32 *line_nums; // [line_count] + U16 *col_nums; // [2*line_count] + }; + LineChunk *first_line_chunk = 0; + LineChunk *last_line_chunk = 0; + U64 total_line_chunk_line_count = 0; + + // rjf: grab checksums sub-section + CV_C13SubSectionNode *file_chksms = unit_c13->file_chksms_sub_section; + + // rjf: decode loop + U64 read_off = 0; + U64 read_off_opl = binary_annots.size; + for(B32 good = 1; read_off < read_off_opl && good;) + { + // rjf: decode next annotation op + U32 op = CV_InlineBinaryAnnotation_Null; + read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &op); + + // rjf: apply op + switch(op) + { + default:{good = 0;}break; + case CV_InlineBinaryAnnotation_Null: + { + good = 0; + }break; + case CV_InlineBinaryAnnotation_CodeOffset: + { + read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_offset); + }break; + case CV_InlineBinaryAnnotation_ChangeCodeOffsetBase: + { + good = 0; + // TODO(rjf): currently untested/unknown - first guess below: + // + // U32 delta = 0; + // read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &delta); + // code_offset_base = code_offset; + // code_offset_end = code_offset + delta; + // code_offset += delta; + }break; + case CV_InlineBinaryAnnotation_ChangeCodeOffset: + { + U32 delta = 0; + read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &delta); + code_offset += delta; + }break; + case CV_InlineBinaryAnnotation_ChangeCodeLength: + { + code_length = 0; + read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_length); + }break; + case CV_InlineBinaryAnnotation_ChangeFile: + { + U32 new_file_off = 0; + read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &new_file_off); + String8 new_file_name = {0}; + if(new_file_off + sizeof(CV_C13Checksum) <= file_chksms->size) + { + CV_C13Checksum *checksum = (CV_C13Checksum*)(unit_c13->data.str + file_chksms->off + new_file_off); + U32 name_off = checksum->name_off; + new_file_name = pdb_strtbl_string_from_off(in->pdb_strtbl, name_off); + } + file_name = new_file_name; + }break; + case CV_InlineBinaryAnnotation_ChangeLineOffset: + { + S32 delta = 0; + read_off += cv_decode_inline_annot_s32(binary_annots, read_off, &delta); + line += delta; + }break; + case CV_InlineBinaryAnnotation_ChangeLineEndDelta: + { + good = 0; + // TODO(rjf): currently untested/unknown - first guess below: + // + // S32 end_delta = 1; + // read_off += cv_decode_inline_annot_s32(binary_annots, read_off, &end_delta); + // line += end_delta; + }break; + case CV_InlineBinaryAnnotation_ChangeRangeKind: + { + good = 0; + // TODO(rjf): currently untested/unknown - first guess below: + // + // read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &range_kind); + }break; + case CV_InlineBinaryAnnotation_ChangeColumnStart: + { + good = 0; + // TODO(rjf): currently untested/unknown - first guess below: + // + // S32 delta = 0; + // read_off += cv_decode_inline_annot_s32(binary_annots, read_off, &delta); + // column += delta; + }break; + case CV_InlineBinaryAnnotation_ChangeColumnEndDelta: + { + // TODO(rjf): currently untested/unknown - first guess below: + // + // S32 end_delta = 0; + // read_off += cv_decode_inline_annot_s32(binary_annots, read_off, &end_delta); + // column += end_delta; + }break; + case CV_InlineBinaryAnnotation_ChangeCodeOffsetAndLineOffset: + { + U32 code_offset_and_line_offset = 0; + read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_offset_and_line_offset); + U32 code_delta = (code_offset_and_line_offset & 0xf); + S32 line_delta = cv_inline_annot_signed_from_unsigned_operand(code_offset_and_line_offset >> 4); + code_offset += code_delta; + line += line_delta; + }break; + case CV_InlineBinaryAnnotation_ChangeCodeLengthAndCodeOffset: + { + U32 offset_delta = 0; + read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_length); + read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &offset_delta); + code_offset += offset_delta; + }break; + case CV_InlineBinaryAnnotation_ChangeColumnEnd: + { + // TODO(rjf): currently untested/unknown - first guess below: + // + // U32 column_end = 0; + // read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &column_end); + }break; + } + + // rjf: gather new lines + if(!good || line != last_line || code_offset != last_code_offset) + { + LineChunk *chunk = last_line_chunk; + if(chunk == 0 || chunk->count+1 >= chunk->cap) + { + chunk = push_array(scratch.arena, LineChunk, 1); + SLLQueuePush(first_line_chunk, last_line_chunk, chunk); + chunk->cap = 256; + chunk->voffs = push_array_no_zero(scratch.arena, U64, chunk->cap); + chunk->line_nums = push_array_no_zero(scratch.arena, U32, chunk->cap); + } + chunk->voffs[chunk->count] = base_voff + code_offset; + chunk->voffs[chunk->count+1] = base_voff + code_offset + code_length; + chunk->line_nums[chunk->count] = (U32)line; + chunk->count += 1; + total_line_chunk_line_count += 1; + } + + // rjf: push line sequence to line table & source file + if(!good || (op == CV_InlineBinaryAnnotation_ChangeFile && !str8_match(last_file_name, file_name, 0))) + { + String8 seq_file_name = last_file_name; // NOTE(rjf): `file_name` is possibly changed to the next sequence, so use previous + + // rjf: file name -> normalized file path + String8 file_path = seq_file_name; + String8 file_path_normalized = lower_from_str8(scratch.arena, str8_skip_chop_whitespace(file_path)); + for(U64 idx = 0; idx < file_path_normalized.size; idx += 1) + { + if(file_path_normalized.str[idx] == '\\') + { + file_path_normalized.str[idx] = '/'; + } + } + + // rjf: normalized file path -> source file node + U64 file_path_normalized_hash = rdi_hash(file_path_normalized.str, file_path_normalized.size); + U64 src_file_slot = file_path_normalized_hash%src_file_map.slots_count; + P2R_SrcFileNode *src_file_node = 0; + for(P2R_SrcFileNode *n = src_file_map.slots[src_file_slot]; n != 0; n = n->next) + { + if(str8_match(n->src_file->normal_full_path, file_path_normalized, 0)) + { + src_file_node = n; + break; + } + } + if(src_file_node == 0) + { + src_file_node = push_array(scratch.arena, P2R_SrcFileNode, 1); + SLLStackPush(src_file_map.slots[src_file_slot], src_file_node); + src_file_node->src_file = rdim_src_file_chunk_list_push(arena, &out->src_files, 4096); + src_file_node->src_file->normal_full_path = push_str8_copy(arena, file_path_normalized); + } + + // rjf: gather all lines + RDI_U64 *voffs = push_array_no_zero(arena, RDI_U64, total_line_chunk_line_count+1); + RDI_U32 *line_nums = push_array_no_zero(arena, RDI_U32, total_line_chunk_line_count); + RDI_U64 line_count = total_line_chunk_line_count; + { + U64 dst_idx = 0; + for(LineChunk *chunk = first_line_chunk; chunk != 0; chunk = chunk->next) + { + MemoryCopy(voffs+dst_idx, chunk->voffs, sizeof(U64)*chunk->count); + MemoryCopy(line_nums+dst_idx, chunk->line_nums, sizeof(U32)*chunk->count); + dst_idx += chunk->count; + } + voffs[dst_idx] = 0xffffffffffffffffull; + } + + // rjf: push + if(line_count != 0) + { + if(line_table == 0) + { + line_table = rdim_line_table_chunk_list_push(arena, &out->line_tables, 256); + if(out->units_first_inline_site_line_tables[comp_unit_idx] == 0) + { + out->units_first_inline_site_line_tables[comp_unit_idx] = line_table; + } + } + RDIM_LineSequence *seq = rdim_line_table_push_sequence(arena, &out->line_tables, line_table, src_file_node->src_file, voffs, line_nums, 0, line_count); + rdim_src_file_push_line_sequence(arena, &out->src_files, src_file_node->src_file, seq); + } + + // rjf: clear line chunks for subsequent sequences + first_line_chunk = last_line_chunk = 0; + total_line_chunk_line_count = 0; + } + + // rjf: update prev/current states + last_file_name = file_name; + last_line = line; + last_column = column; + last_code_offset = code_offset; + } + } + }break; + } + } + } + } + scratch_end(scratch); + return out; +} + +//////////////////////////////// +//~ rjf: Link Name Map Building Tasks + +internal TS_TASK_FUNCTION_DEF(p2r_link_name_map_build_task__entry_point) +{ + P2R_LinkNameMapBuildIn *in = (P2R_LinkNameMapBuildIn *)p; + CV_RecRange *rec_ranges_first = in->sym->sym_ranges.ranges; + CV_RecRange *rec_ranges_opl = rec_ranges_first + in->sym->sym_ranges.count; + for(CV_RecRange *rec_range = rec_ranges_first; + rec_range < rec_ranges_opl; + rec_range += 1) + { + //- rjf: unpack symbol range info + CV_SymKind kind = rec_range->hdr.kind; + U64 header_struct_size = cv_header_struct_size_from_sym_kind(kind); + U8 *sym_first = in->sym->data.str + rec_range->off + 2; + U8 *sym_opl = sym_first + rec_range->hdr.size; + + //- rjf: skip bad ranges + if(sym_opl > in->sym->data.str + in->sym->data.size || sym_first + header_struct_size > in->sym->data.str + in->sym->data.size) + { + continue; + } + + //- rjf: consume symbol + switch(kind) + { + default:{}break; + case CV_SymKind_PUB32: + { + // rjf: unpack sym + CV_SymPub32 *pub32 = (CV_SymPub32 *)sym_first; + String8 name = str8_cstring_capped(pub32+1, sym_opl); + COFF_SectionHeader *section = (0 < pub32->sec && pub32->sec <= in->coff_sections->count) ? &in->coff_sections->sections[pub32->sec-1] : 0; + U64 voff = 0; + if(section != 0) + { + voff = section->voff + pub32->off; + } + + // rjf: commit to link name map + U64 hash = p2r_hash_from_voff(voff); + U64 bucket_idx = hash%in->link_name_map->buckets_count; + P2R_LinkNameNode *node = push_array(arena, P2R_LinkNameNode, 1); + SLLStackPush(in->link_name_map->buckets[bucket_idx], node); + node->voff = voff; + node->name = name; + in->link_name_map->link_name_count += 1; + in->link_name_map->bucket_collision_count += (node->next != 0); + }break; + } + } + return 0; +} + +//////////////////////////////// +//~ rjf: Type Parsing/Conversion Tasks + +internal TS_TASK_FUNCTION_DEF(p2r_itype_fwd_map_fill_task__entry_point) +{ + P2R_ITypeFwdMapFillIn *in = (P2R_ITypeFwdMapFillIn *)p; + ProfScope("fill itype fwd map") for(CV_TypeId itype = in->itype_first; itype < in->itype_opl; itype += 1) + { + //- rjf: skip if not in the actually stored itype range + if(itype < in->tpi_leaf->itype_first) + { + continue; + } + + //- rjf: determine if this itype resolves to another + CV_TypeId itype_fwd = 0; + CV_RecRange *range = &in->tpi_leaf->leaf_ranges.ranges[itype-in->tpi_leaf->itype_first]; + CV_LeafKind kind = range->hdr.kind; + U64 header_struct_size = cv_header_struct_size_from_leaf_kind(kind); + if(range->off+range->hdr.size <= in->tpi_leaf->data.size && + range->off+2+header_struct_size <= in->tpi_leaf->data.size && + range->hdr.size >= 2) + { + U8 *itype_leaf_first = in->tpi_leaf->data.str + range->off+2; + U8 *itype_leaf_opl = itype_leaf_first + range->hdr.size-2; + switch(kind) + { + default:{}break; + + //- rjf: CLASS/STRUCTURE + case CV_LeafKind_CLASS: + case CV_LeafKind_STRUCTURE: + { + // rjf: unpack leaf header + CV_LeafStruct *lf_struct = (CV_LeafStruct *)itype_leaf_first; + + // rjf: has fwd ref flag -> lookup itype that this itype resolves to + if(lf_struct->props & CV_TypeProp_FwdRef) + { + // rjf: unpack rest of leaf + U8 *numeric_ptr = (U8 *)(lf_struct + 1); + CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, itype_leaf_opl); + U8 *name_ptr = numeric_ptr + size.encoded_size; + String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); + U8 *unique_name_ptr = name_ptr + name.size + 1; + String8 unique_name = str8_cstring_capped(unique_name_ptr, itype_leaf_opl); + + // rjf: lookup + B32 do_unique_name_lookup = (((lf_struct->props & CV_TypeProp_Scoped) != 0) && + ((lf_struct->props & CV_TypeProp_HasUniqueName) != 0)); + itype_fwd = pdb_tpi_first_itype_from_name(in->tpi_hash, in->tpi_leaf, do_unique_name_lookup?unique_name:name, do_unique_name_lookup); + } + }break; + + //- rjf: CLASS2/STRUCT2 + case CV_LeafKind_CLASS2: + case CV_LeafKind_STRUCT2: + { + // rjf: unpack leaf header + CV_LeafStruct2 *lf_struct = (CV_LeafStruct2 *)itype_leaf_first; + + // rjf: has fwd ref flag -> lookup itype that this itype resolves to + if(lf_struct->props & CV_TypeProp_FwdRef) + { + // rjf: unpack rest of leaf + U8 *numeric_ptr = (U8 *)(lf_struct + 1); + CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, itype_leaf_opl); + U8 *name_ptr = (U8 *)numeric_ptr + size.encoded_size; + String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); + U8 *unique_name_ptr = name_ptr + name.size + 1; + String8 unique_name = str8_cstring_capped(unique_name_ptr, itype_leaf_opl); + + // rjf: lookup + B32 do_unique_name_lookup = (((lf_struct->props & CV_TypeProp_Scoped) != 0) && + ((lf_struct->props & CV_TypeProp_HasUniqueName) != 0)); + itype_fwd = pdb_tpi_first_itype_from_name(in->tpi_hash, in->tpi_leaf, do_unique_name_lookup?unique_name:name, do_unique_name_lookup); + } + }break; + + //- rjf: UNION + case CV_LeafKind_UNION: + { + // rjf: unpack leaf + CV_LeafUnion *lf_union = (CV_LeafUnion *)itype_leaf_first; + U8 *numeric_ptr = (U8 *)(lf_union + 1); + CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, itype_leaf_opl); + U8 *name_ptr = numeric_ptr + size.encoded_size; + String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); + U8 *unique_name_ptr = name_ptr + name.size + 1; + String8 unique_name = str8_cstring_capped(unique_name_ptr, itype_leaf_opl); + + // rjf: has fwd ref flag -> lookup itype that this itype resolves tos + if(lf_union->props & CV_TypeProp_FwdRef) + { + B32 do_unique_name_lookup = (((lf_union->props & CV_TypeProp_Scoped) != 0) && + ((lf_union->props & CV_TypeProp_HasUniqueName) != 0)); + itype_fwd = pdb_tpi_first_itype_from_name(in->tpi_hash, in->tpi_leaf, do_unique_name_lookup?unique_name:name, do_unique_name_lookup); + } + }break; + + //- rjf: ENUM + case CV_LeafKind_ENUM: + { + // rjf: unpack leaf + CV_LeafEnum *lf_enum = (CV_LeafEnum*)itype_leaf_first; + U8 *name_ptr = (U8 *)(lf_enum + 1); + String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); + U8 *unique_name_ptr = name_ptr + name.size + 1; + String8 unique_name = str8_cstring_capped(unique_name_ptr, itype_leaf_opl); + + // rjf: has fwd ref flag -> lookup itype that this itype resolves to + if(lf_enum->props & CV_TypeProp_FwdRef) + { + B32 do_unique_name_lookup = (((lf_enum->props & CV_TypeProp_Scoped) != 0) && + ((lf_enum->props & CV_TypeProp_HasUniqueName) != 0)); + itype_fwd = pdb_tpi_first_itype_from_name(in->tpi_hash, in->tpi_leaf, do_unique_name_lookup?unique_name:name, do_unique_name_lookup); + } + }break; + } + } + + //- rjf: if the forwarded itype is nonzero & in TPI range -> save to map + if(itype_fwd != 0 && itype_fwd < in->tpi_leaf->itype_opl) + { + in->itype_fwd_map[itype] = itype_fwd; + } + } + return 0; +} + +internal TS_TASK_FUNCTION_DEF(p2r_itype_chain_build_task__entry_point) +{ + Temp scratch = scratch_begin(&arena, 1); + P2R_ITypeChainBuildIn *in = (P2R_ITypeChainBuildIn *)p; + ProfScope("dependency itype chain build") + { + for(CV_TypeId itype = in->itype_first; itype < in->itype_opl; itype += 1) + { + //- rjf: push initial itype - should be final-visited-itype for this itype + { + P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); + c->itype = itype; + SLLStackPush(in->itype_chains[itype], c); + } + + //- rjf: skip basic types for dependency walk + if(itype < in->tpi_leaf->itype_first) + { + continue; + } + + //- rjf: walk dependent types, push to chain + P2R_TypeIdChain start_walk_task = {0, itype}; + P2R_TypeIdChain *first_walk_task = &start_walk_task; + P2R_TypeIdChain *last_walk_task = &start_walk_task; + for(P2R_TypeIdChain *walk_task = first_walk_task; + walk_task != 0; + walk_task = walk_task->next) + { + CV_TypeId walk_itype = in->itype_fwd_map[walk_task->itype] ? in->itype_fwd_map[walk_task->itype] : walk_task->itype; + if(walk_itype < in->tpi_leaf->itype_first) + { + continue; + } + CV_RecRange *range = &in->tpi_leaf->leaf_ranges.ranges[walk_itype-in->tpi_leaf->itype_first]; + CV_LeafKind kind = range->hdr.kind; + U64 header_struct_size = cv_header_struct_size_from_leaf_kind(kind); + if(range->off+range->hdr.size <= in->tpi_leaf->data.size && + range->off+2+header_struct_size <= in->tpi_leaf->data.size && + range->hdr.size >= 2) + { + U8 *itype_leaf_first = in->tpi_leaf->data.str + range->off+2; + U8 *itype_leaf_opl = itype_leaf_first + range->hdr.size-2; + switch(kind) + { + default:{}break; + + //- rjf: MODIFIER + case CV_LeafKind_MODIFIER: + { + CV_LeafModifier *lf = (CV_LeafModifier *)itype_leaf_first; + + // rjf: push dependent itype to chain + { + P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); + c->itype = lf->itype; + SLLStackPush(in->itype_chains[itype], c); + } + + // rjf: push task to walk dependency itype + { + P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); + c->itype = lf->itype; + SLLQueuePush(first_walk_task, last_walk_task, c); + } + }break; + + //- rjf: POINTER + case CV_LeafKind_POINTER: + { + CV_LeafModifier *lf = (CV_LeafModifier *)itype_leaf_first; + + // rjf: push dependent itype to chain + { + P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); + c->itype = lf->itype; + SLLStackPush(in->itype_chains[itype], c); + } + + // rjf: push task to walk dependency itype + { + P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); + c->itype = lf->itype; + SLLQueuePush(first_walk_task, last_walk_task, c); + } + }break; + + //- rjf: PROCEDURE + case CV_LeafKind_PROCEDURE: + { + CV_LeafProcedure *lf = (CV_LeafProcedure *)itype_leaf_first; + + // rjf: push return itypes to chain + { + P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); + c->itype = lf->ret_itype; + SLLStackPush(in->itype_chains[itype], c); + } + + // rjf: push task to walk return itype + { + P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); + c->itype = lf->ret_itype; + SLLQueuePush(first_walk_task, last_walk_task, c); + } + + // rjf: unpack arglist range + CV_RecRange *arglist_range = &in->tpi_leaf->leaf_ranges.ranges[lf->arg_itype-in->tpi_leaf->itype_first]; + if(arglist_range->hdr.kind != CV_LeafKind_ARGLIST || + arglist_range->hdr.size<2 || + arglist_range->off + arglist_range->hdr.size > in->tpi_leaf->data.size) + { + break; + } + U8 *arglist_first = in->tpi_leaf->data.str + arglist_range->off + 2; + U8 *arglist_opl = arglist_first+arglist_range->hdr.size-2; + if(arglist_first + sizeof(CV_LeafArgList) > arglist_opl) + { + break; + } + + // rjf: unpack arglist info + CV_LeafArgList *arglist = (CV_LeafArgList*)arglist_first; + CV_TypeId *arglist_itypes_base = (CV_TypeId *)(arglist+1); + U32 arglist_itypes_count = arglist->count; + + // rjf: push arg types to chain + for(U32 idx = 0; idx < arglist_itypes_count; idx += 1) + { + P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); + c->itype = arglist_itypes_base[idx]; + SLLStackPush(in->itype_chains[itype], c); + } + + // rjf: push task to walk arg types + for(U32 idx = 0; idx < arglist_itypes_count; idx += 1) + { + P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); + c->itype = arglist_itypes_base[idx]; + SLLQueuePush(first_walk_task, last_walk_task, c); + } + }break; + + //- rjf: MFUNCTION + case CV_LeafKind_MFUNCTION: + { + CV_LeafMFunction *lf = (CV_LeafMFunction *)itype_leaf_first; + + // rjf: push dependent itypes to chain + { + P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); + c->itype = lf->ret_itype; + SLLStackPush(in->itype_chains[itype], c); + } + { + P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); + c->itype = lf->arg_itype; + SLLStackPush(in->itype_chains[itype], c); + } + { + P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); + c->itype = lf->this_itype; + SLLStackPush(in->itype_chains[itype], c); + } + + // rjf: push task to walk dependency itypes + { + P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); + c->itype = lf->ret_itype; + SLLQueuePush(first_walk_task, last_walk_task, c); + } + { + P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); + c->itype = lf->arg_itype; + SLLQueuePush(first_walk_task, last_walk_task, c); + } + { + P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); + c->itype = lf->this_itype; + SLLQueuePush(first_walk_task, last_walk_task, c); + } + + // rjf: unpack arglist range + CV_RecRange *arglist_range = &in->tpi_leaf->leaf_ranges.ranges[lf->arg_itype-in->tpi_leaf->itype_first]; + if(arglist_range->hdr.kind != CV_LeafKind_ARGLIST || + arglist_range->hdr.size<2 || + arglist_range->off + arglist_range->hdr.size > in->tpi_leaf->data.size) + { + break; + } + U8 *arglist_first = in->tpi_leaf->data.str + arglist_range->off + 2; + U8 *arglist_opl = arglist_first+arglist_range->hdr.size-2; + if(arglist_first + sizeof(CV_LeafArgList) > arglist_opl) + { + break; + } + + // rjf: unpack arglist info + CV_LeafArgList *arglist = (CV_LeafArgList*)arglist_first; + CV_TypeId *arglist_itypes_base = (CV_TypeId *)(arglist+1); + U32 arglist_itypes_count = arglist->count; + + // rjf: push arg types to chain + for(U32 idx = 0; idx < arglist_itypes_count; idx += 1) + { + P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); + c->itype = arglist_itypes_base[idx]; + SLLStackPush(in->itype_chains[itype], c); + } + + // rjf: push task to walk arg types + for(U32 idx = 0; idx < arglist_itypes_count; idx += 1) + { + P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); + c->itype = arglist_itypes_base[idx]; + SLLQueuePush(first_walk_task, last_walk_task, c); + } + }break; + + //- rjf: BITFIELD + case CV_LeafKind_BITFIELD: + { + CV_LeafBitField *lf = (CV_LeafBitField *)itype_leaf_first; + + // rjf: push dependent itype to chain + { + P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); + c->itype = lf->itype; + SLLStackPush(in->itype_chains[itype], c); + } + + // rjf: push task to walk dependency itype + { + P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); + c->itype = lf->itype; + SLLQueuePush(first_walk_task, last_walk_task, c); + } + }break; + + //- rjf: ARRAY + case CV_LeafKind_ARRAY: + { + CV_LeafArray *lf = (CV_LeafArray *)itype_leaf_first; + + // rjf: push dependent itypes to chain + { + P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); + c->itype = lf->entry_itype; + SLLStackPush(in->itype_chains[itype], c); + } + { + P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); + c->itype = lf->index_itype; + SLLStackPush(in->itype_chains[itype], c); + } + + // rjf: push task to walk dependency itypes + { + P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); + c->itype = lf->entry_itype; + SLLQueuePush(first_walk_task, last_walk_task, c); + } + { + P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); + c->itype = lf->index_itype; + SLLQueuePush(first_walk_task, last_walk_task, c); + } + }break; + + //- rjf: ENUM + case CV_LeafKind_ENUM: + { + CV_LeafEnum *lf = (CV_LeafEnum *)itype_leaf_first; + + // rjf: push dependent itypes to chain + { + P2R_TypeIdChain *c = push_array(arena, P2R_TypeIdChain, 1); + c->itype = lf->base_itype; + SLLStackPush(in->itype_chains[itype], c); + } + + // rjf: push task to walk dependency itypes + { + P2R_TypeIdChain *c = push_array(scratch.arena, P2R_TypeIdChain, 1); + c->itype = lf->base_itype; + SLLQueuePush(first_walk_task, last_walk_task, c); + } + }break; + } + } + } + } + } + scratch_end(scratch); + return 0; +} + +//////////////////////////////// +//~ rjf: UDT Conversion Tasks + +internal TS_TASK_FUNCTION_DEF(p2r_udt_convert_task__entry_point) +{ + P2R_UDTConvertIn *in = (P2R_UDTConvertIn *)p; +#define p2r_type_ptr_from_itype(itype) ((in->itype_type_ptrs && (itype) < in->tpi_leaf->itype_opl) ? (in->itype_type_ptrs[(in->itype_fwd_map[(itype)] ? in->itype_fwd_map[(itype)] : (itype))]) : 0) + RDIM_UDTChunkList *udts = push_array(arena, RDIM_UDTChunkList, 1); + RDI_U64 udts_chunk_cap = 1024; + ProfScope("convert UDT info") + { + for(CV_TypeId itype = in->itype_first; itype < in->itype_opl; itype += 1) + { + //- rjf: skip basics + if(itype < in->tpi_leaf->itype_first) { continue; } + + //- rjf: grab type for this itype - skip if empty + RDIM_Type *dst_type = in->itype_type_ptrs[itype]; + if(dst_type == 0) { continue; } + + //- rjf: unpack itype leaf range - skip if out-of-range + CV_RecRange *range = &in->tpi_leaf->leaf_ranges.ranges[itype-in->tpi_leaf->itype_first]; + CV_LeafKind kind = range->hdr.kind; + U64 header_struct_size = cv_header_struct_size_from_leaf_kind(kind); + U8 *itype_leaf_first = in->tpi_leaf->data.str + range->off+2; + U8 *itype_leaf_opl = itype_leaf_first + range->hdr.size-2; + if(range->off+range->hdr.size > in->tpi_leaf->data.size || + range->off+2+header_struct_size > in->tpi_leaf->data.size || + range->hdr.size < 2) + { + continue; + } + + //- rjf: build UDT + CV_TypeId field_itype = 0; + switch(kind) + { + default:{}break; + + //////////////////////// + //- rjf: structs/unions/classes -> equip members + // + case CV_LeafKind_CLASS: + case CV_LeafKind_STRUCTURE: + { + CV_LeafStruct *lf = (CV_LeafStruct *)itype_leaf_first; + if(lf->props & CV_TypeProp_FwdRef) + { + break; + } + field_itype = lf->field_itype; + }goto equip_members; + case CV_LeafKind_UNION: + { + CV_LeafUnion *lf = (CV_LeafUnion *)itype_leaf_first; + if(lf->props & CV_TypeProp_FwdRef) + { + break; + } + field_itype = lf->field_itype; + }goto equip_members; + case CV_LeafKind_CLASS2: + case CV_LeafKind_STRUCT2: + { + CV_LeafStruct2 *lf = (CV_LeafStruct2 *)itype_leaf_first; + if(lf->props & CV_TypeProp_FwdRef) + { + break; + } + field_itype = lf->field_itype; + }goto equip_members; + equip_members: + { + Temp scratch = scratch_begin(&arena, 1); + + //- rjf: grab UDT info + RDIM_UDT *dst_udt = dst_type->udt; + if(dst_udt == 0) + { + dst_udt = dst_type->udt = rdim_udt_chunk_list_push(arena, udts, udts_chunk_cap); + dst_udt->self_type = dst_type; + } + + //- rjf: gather all fields + typedef struct FieldListTask FieldListTask; + struct FieldListTask + { + FieldListTask *next; + CV_TypeId itype; + }; + FieldListTask start_fl_task = {0, field_itype}; + FieldListTask *fl_todo_stack = &start_fl_task; + FieldListTask *fl_done_stack = 0; + for(;fl_todo_stack != 0;) + { + //- rjf: take & unpack task + FieldListTask *fl_task = fl_todo_stack; + SLLStackPop(fl_todo_stack); + SLLStackPush(fl_done_stack, fl_task); + CV_TypeId field_list_itype = fl_task->itype; + + //- rjf: skip bad itypes + if(field_list_itype < in->tpi_leaf->itype_first || in->tpi_leaf->itype_opl <= field_list_itype) + { + continue; + } + + //- rjf: field list itype -> range + CV_RecRange *range = &in->tpi_leaf->leaf_ranges.ranges[field_list_itype-in->tpi_leaf->itype_first]; + + //- rjf: skip bad headers + if(range->off+range->hdr.size > in->tpi_leaf->data.size || + range->hdr.size < 2 || + range->hdr.kind != CV_LeafKind_FIELDLIST) + { + continue; + } + + //- rjf: loop over all fields + { + U8 *field_list_first = in->tpi_leaf->data.str+range->off+2; + U8 *field_list_opl = field_list_first+range->hdr.size-2; + for(U8 *read_ptr = field_list_first, *next_read_ptr = field_list_opl; + read_ptr < field_list_opl; + read_ptr = next_read_ptr) + { + // rjf: unpack field + CV_LeafKind field_kind = *(CV_LeafKind *)read_ptr; + U64 field_leaf_header_size = cv_header_struct_size_from_leaf_kind(field_kind); + U8 *field_leaf_first = read_ptr+2; + U8 *field_leaf_opl = field_list_opl; + next_read_ptr = field_leaf_opl; + + // rjf: skip out-of-bounds fields + if(field_leaf_first+field_leaf_header_size > field_list_opl) + { + continue; + } + + // rjf: process field + switch(field_kind) + { + //- rjf: unhandled/invalid cases + default: + { + // TODO(rjf): log + }break; + + //- rjf: INDEX + case CV_LeafKind_INDEX: + { + // rjf: unpack leaf + CV_LeafIndex *lf = (CV_LeafIndex *)field_leaf_first; + CV_TypeId new_itype = lf->itype; + + // rjf: bump next read pointer past header + next_read_ptr = (U8 *)(lf+1); + + // rjf: determine if index itype is new + B32 is_new = 1; + for(FieldListTask *t = fl_done_stack; t != 0; t = t->next) + { + if(t->itype == new_itype) + { + is_new = 0; + break; + } + } + + // rjf: if new -> push task to follow new itype + if(is_new) + { + FieldListTask *new_task = push_array(scratch.arena, FieldListTask, 1); + SLLStackPush(fl_todo_stack, new_task); + new_task->itype = new_itype; + } + }break; + + //- rjf: MEMBER + case CV_LeafKind_MEMBER: + { + // TODO(rjf): log on bad offset + + // rjf: unpack leaf + CV_LeafMember *lf = (CV_LeafMember *)field_leaf_first; + U8 *offset_ptr = (U8 *)(lf+1); + CV_NumericParsed offset = cv_numeric_from_data_range(offset_ptr, field_leaf_opl); + U64 offset64 = cv_u64_from_numeric(&offset); + U8 *name_ptr = offset_ptr + offset.encoded_size; + String8 name = str8_cstring_capped(name_ptr, field_leaf_opl); + + // rjf: bump next read pointer past variable length parts + next_read_ptr = name.str+name.size+1; + + // rjf: emit member + RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); + mem->kind = RDI_MemberKind_DataField; + mem->name = name; + mem->type = p2r_type_ptr_from_itype(lf->itype); + mem->off = (U32)offset64; + }break; + + //- rjf: STMEMBER + case CV_LeafKind_STMEMBER: + { + // TODO(rjf): handle attribs + + // rjf: unpack leaf + CV_LeafStMember *lf = (CV_LeafStMember *)field_leaf_first; + U8 *name_ptr = (U8 *)(lf+1); + String8 name = str8_cstring_capped(name_ptr, field_leaf_opl); + + // rjf: bump next read pointer past variable length parts + next_read_ptr = name.str+name.size+1; + + // rjf: emit member + RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); + mem->kind = RDI_MemberKind_StaticData; + mem->name = name; + mem->type = p2r_type_ptr_from_itype(lf->itype); + }break; + + //- rjf: METHOD + case CV_LeafKind_METHOD: + { + // rjf: unpack leaf + CV_LeafMethod *lf = (CV_LeafMethod *)field_leaf_first; + U8 *name_ptr = (U8 *)(lf+1); + String8 name = str8_cstring_capped(name_ptr, field_leaf_opl); + + // rjf: bump next read pointer past variable length parts + next_read_ptr = name.str+name.size+1; + + //- rjf: method list itype -> range + CV_RecRange *method_list_range = &in->tpi_leaf->leaf_ranges.ranges[lf->list_itype-in->tpi_leaf->itype_first]; + + //- rjf: skip bad method lists + if(method_list_range->off+method_list_range->hdr.size > in->tpi_leaf->data.size || + method_list_range->hdr.size < 2 || + method_list_range->hdr.kind != CV_LeafKind_METHODLIST) + { + break; + } + + //- rjf: loop through all methods & emit members + U8 *method_list_first = in->tpi_leaf->data.str + method_list_range->off + 2; + U8 *method_list_opl = method_list_first + method_list_range->hdr.size-2; + for(U8 *method_read_ptr = method_list_first, *next_method_read_ptr = method_list_opl; + method_read_ptr < method_list_opl; + method_read_ptr = next_method_read_ptr) + { + CV_LeafMethodListMember *method = (CV_LeafMethodListMember*)method_read_ptr; + CV_MethodProp prop = CV_FieldAttribs_ExtractMethodProp(method->attribs); + RDIM_Type *method_type = p2r_type_ptr_from_itype(method->itype); + next_method_read_ptr = (U8 *)(method+1); + + // TODO(allen): PROBLEM + // We only get offsets for virtual functions (the "vbaseoff") from + // "Intro" and "PureIntro". In C++ inheritance, when we have a chain + // of inheritance (let's just talk single inheritance for now) the + // first class in the chain that introduces a new virtual function + // has this "Intro" method. If a later class in the chain redefines + // the virtual function it only has a "Virtual" method which does + // not update the offset. There is a "Virtual" and "PureVirtual" + // variant of "Virtual". The "Pure" in either case means there + // is no concrete procedure. When there is no "Pure" the method + // should have a corresponding procedure symbol id. + // + // The issue is we will want to mark all of our virtual methods as + // virtual and give them an offset, but that means we have to do + // some extra figuring to propogate offsets from "Intro" methods + // to "Virtual" methods in inheritance trees. That is - IF we want + // to start preserving the offsets of virtuals. There is room in + // the method struct to make this work, but for now I've just + // decided to drop this information. It is not urgently useful to + // us and greatly complicates matters. + + // rjf: read vbaseoff + U32 vbaseoff = 0; + if(prop == CV_MethodProp_Intro || prop == CV_MethodProp_PureIntro) + { + if(next_method_read_ptr+4 <= method_list_opl) + { + vbaseoff = *(U32 *)next_method_read_ptr; + } + next_method_read_ptr += 4; + } + + // rjf: emit method + switch(prop) + { + default: + { + RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); + mem->kind = RDI_MemberKind_Method; + mem->name = name; + mem->type = method_type; + }break; + case CV_MethodProp_Static: + { + RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); + mem->kind = RDI_MemberKind_StaticMethod; + mem->name = name; + mem->type = method_type; + }break; + case CV_MethodProp_Virtual: + case CV_MethodProp_PureVirtual: + case CV_MethodProp_Intro: + case CV_MethodProp_PureIntro: + { + RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); + mem->kind = RDI_MemberKind_VirtualMethod; + mem->name = name; + mem->type = method_type; + }break; + } + } + + }break; + + //- rjf: ONEMETHOD + case CV_LeafKind_ONEMETHOD: + { + // TODO(rjf): handle attribs + + // rjf: unpack leaf + CV_LeafOneMethod *lf = (CV_LeafOneMethod *)field_leaf_first; + CV_MethodProp prop = CV_FieldAttribs_ExtractMethodProp(lf->attribs); + U8 *vbaseoff_ptr = (U8 *)(lf+1); + U8 *vbaseoff_opl_ptr = vbaseoff_ptr; + U32 vbaseoff = 0; + if(prop == CV_MethodProp_Intro || prop == CV_MethodProp_PureIntro) + { + vbaseoff = *(U32 *)(vbaseoff_ptr); + vbaseoff_opl_ptr += sizeof(U32); + } + U8 *name_ptr = vbaseoff_opl_ptr; + String8 name = str8_cstring_capped(name_ptr, field_leaf_opl); + RDIM_Type *method_type = p2r_type_ptr_from_itype(lf->itype); + + // rjf: bump next read pointer past variable length parts + next_read_ptr = name.str+name.size+1; + + // rjf: emit method + switch(prop) + { + default: + { + RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); + mem->kind = RDI_MemberKind_Method; + mem->name = name; + mem->type = method_type; + }break; + + case CV_MethodProp_Static: + { + RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); + mem->kind = RDI_MemberKind_StaticMethod; + mem->name = name; + mem->type = method_type; + }break; + + case CV_MethodProp_Virtual: + case CV_MethodProp_PureVirtual: + case CV_MethodProp_Intro: + case CV_MethodProp_PureIntro: + { + RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); + mem->kind = RDI_MemberKind_VirtualMethod; + mem->name = name; + mem->type = method_type; + }break; + } + }break; + + //- rjf: NESTTYPE + case CV_LeafKind_NESTTYPE: + { + // rjf: unpack leaf + CV_LeafNestType *lf = (CV_LeafNestType *)field_leaf_first; + U8 *name_ptr = (U8 *)(lf+1); + String8 name = str8_cstring_capped(name_ptr, field_leaf_opl); + + // rjf: bump next read pointer past variable length parts + next_read_ptr = name.str+name.size+1; + + // rjf: emit member + RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); + mem->kind = RDI_MemberKind_NestedType; + mem->name = name; + mem->type = p2r_type_ptr_from_itype(lf->itype); + }break; + + //- rjf: NESTTYPEEX + case CV_LeafKind_NESTTYPEEX: + { + // TODO(rjf): handle attribs + + // rjf: unpack leaf + CV_LeafNestTypeEx *lf = (CV_LeafNestTypeEx *)field_leaf_first; + U8 *name_ptr = (U8 *)(lf+1); + String8 name = str8_cstring_capped(name_ptr, field_leaf_opl); + + // rjf: bump next read pointer past variable length parts + next_read_ptr = name.str+name.size+1; + + // rjf: emit member + RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); + mem->kind = RDI_MemberKind_NestedType; + mem->name = name; + mem->type = p2r_type_ptr_from_itype(lf->itype); + }break; + + //- rjf: BCLASS + case CV_LeafKind_BCLASS: + { + // TODO(rjf): log on bad offset + + // rjf: unpack leaf + CV_LeafBClass *lf = (CV_LeafBClass *)field_leaf_first; + U8 *offset_ptr = (U8 *)(lf+1); + CV_NumericParsed offset = cv_numeric_from_data_range(offset_ptr, field_leaf_opl); + U64 offset64 = cv_u64_from_numeric(&offset); + + // rjf: bump next read pointer past variable length parts + next_read_ptr = offset_ptr+offset.encoded_size; + + // rjf: emit member + RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); + mem->kind = RDI_MemberKind_Base; + mem->type = p2r_type_ptr_from_itype(lf->itype); + mem->off = (U32)offset64; + }break; + + //- rjf: VBCLASS/IVBCLASS + case CV_LeafKind_VBCLASS: + case CV_LeafKind_IVBCLASS: + { + // TODO(rjf): log on bad offsets + // TODO(rjf): handle attribs + // TODO(rjf): offsets? + + // rjf: unpack leaf + CV_LeafVBClass *lf = (CV_LeafVBClass *)field_leaf_first; + U8 *num1_ptr = (U8 *)(lf+1); + CV_NumericParsed num1 = cv_numeric_from_data_range(num1_ptr, field_leaf_opl); + U8 *num2_ptr = num1_ptr + num1.encoded_size; + CV_NumericParsed num2 = cv_numeric_from_data_range(num2_ptr, field_leaf_opl); + + // rjf: bump next read pointer past header + next_read_ptr = (U8 *)(lf+1); + + // rjf: emit member + RDIM_UDTMember *mem = rdim_udt_push_member(arena, udts, dst_udt); + mem->kind = RDI_MemberKind_VirtualBase; + mem->type = p2r_type_ptr_from_itype(lf->itype); + }break; + + //- rjf: VFUNCTAB + case CV_LeafKind_VFUNCTAB: + { + CV_LeafVFuncTab *lf = (CV_LeafVFuncTab *)field_leaf_first; + + // rjf: bump next read pointer past header + next_read_ptr = (U8 *)(lf+1); + + // NOTE(rjf): currently no-op this case + (void)lf; + }break; + } + + // rjf: align-up next field + next_read_ptr = (U8 *)AlignPow2((U64)next_read_ptr, 4); + } + } + } + + scratch_end(scratch); + }break; + + //////////////////////// + //- rjf: enums -> equip enumerates + // + case CV_LeafKind_ENUM: + { + CV_LeafEnum *lf = (CV_LeafEnum *)itype_leaf_first; + if(lf->props & CV_TypeProp_FwdRef) + { + break; + } + field_itype = lf->field_itype; + }goto equip_enum_vals; + equip_enum_vals:; + { + Temp scratch = scratch_begin(&arena, 1); + + //- rjf: grab UDT info + RDIM_UDT *dst_udt = dst_type->udt; + if(dst_udt == 0) + { + dst_udt = dst_type->udt = rdim_udt_chunk_list_push(arena, udts, udts_chunk_cap); + dst_udt->self_type = dst_type; + } + + //- rjf: gather all fields + typedef struct FieldListTask FieldListTask; + struct FieldListTask + { + FieldListTask *next; + CV_TypeId itype; + }; + FieldListTask start_fl_task = {0, field_itype}; + FieldListTask *fl_todo_stack = &start_fl_task; + FieldListTask *fl_done_stack = 0; + for(;fl_todo_stack != 0;) + { + //- rjf: take & unpack task + FieldListTask *fl_task = fl_todo_stack; + SLLStackPop(fl_todo_stack); + SLLStackPush(fl_done_stack, fl_task); + CV_TypeId field_list_itype = fl_task->itype; + + //- rjf: skip bad itypes + if(field_list_itype < in->tpi_leaf->itype_first || in->tpi_leaf->itype_opl <= field_list_itype) + { + continue; + } + + //- rjf: field list itype -> range + CV_RecRange *range = &in->tpi_leaf->leaf_ranges.ranges[field_list_itype-in->tpi_leaf->itype_first]; + + //- rjf: skip bad headers + if(range->off+range->hdr.size > in->tpi_leaf->data.size || + range->hdr.size < 2 || + range->hdr.kind != CV_LeafKind_FIELDLIST) + { + continue; + } + + //- rjf: loop over all fields + { + U8 *field_list_first = in->tpi_leaf->data.str+range->off+2; + U8 *field_list_opl = field_list_first+range->hdr.size-2; + for(U8 *read_ptr = field_list_first, *next_read_ptr = field_list_opl; + read_ptr < field_list_opl; + read_ptr = next_read_ptr) + { + // rjf: unpack field + CV_LeafKind field_kind = *(CV_LeafKind *)read_ptr; + U64 field_leaf_header_size = cv_header_struct_size_from_leaf_kind(field_kind); + U8 *field_leaf_first = read_ptr+2; + U8 *field_leaf_opl = field_leaf_first+range->hdr.size-2; + next_read_ptr = field_leaf_opl; + + // rjf: skip out-of-bounds fields + if(field_leaf_first+field_leaf_header_size > field_list_opl) + { + continue; + } + + // rjf: process field + switch(field_kind) + { + //- rjf: unhandled/invalid cases + default: + { + // TODO(rjf): log + }break; + + //- rjf: INDEX + case CV_LeafKind_INDEX: + { + // rjf: unpack leaf + CV_LeafIndex *lf = (CV_LeafIndex *)field_leaf_first; + CV_TypeId new_itype = lf->itype; + + // rjf: determine if index itype is new + B32 is_new = 1; + for(FieldListTask *t = fl_done_stack; t != 0; t = t->next) + { + if(t->itype == new_itype) + { + is_new = 0; + break; + } + } + + // rjf: if new -> push task to follow new itype + if(is_new) + { + FieldListTask *new_task = push_array(scratch.arena, FieldListTask, 1); + SLLStackPush(fl_todo_stack, new_task); + new_task->itype = new_itype; + } + }break; + + //- rjf: ENUMERATE + case CV_LeafKind_ENUMERATE: + { + // TODO(rjf): attribs + + // rjf: unpack leaf + CV_LeafEnumerate *lf = (CV_LeafEnumerate *)field_leaf_first; + U8 *val_ptr = (U8 *)(lf+1); + CV_NumericParsed val = cv_numeric_from_data_range(val_ptr, field_leaf_opl); + U64 val64 = cv_u64_from_numeric(&val); + U8 *name_ptr = val_ptr + val.encoded_size; + String8 name = str8_cstring_capped(name_ptr, field_leaf_opl); + + // rjf: bump next read pointer past variable length parts + next_read_ptr = name.str+name.size+1; + + // rjf: emit member + RDIM_UDTEnumVal *enum_val = rdim_udt_push_enum_val(arena, udts, dst_udt); + enum_val->name = name; + enum_val->val = val64; + }break; + } + + // rjf: align-up next field + next_read_ptr = (U8 *)AlignPow2((U64)next_read_ptr, 4); + } + } + } + + scratch_end(scratch); + }break; + } + } + } +#undef p2r_type_ptr_from_itype + return udts; +} + +//////////////////////////////// +//~ rjf: Symbol Stream Conversion Path & Thread + +internal TS_TASK_FUNCTION_DEF(p2r_symbol_stream_convert_task__entry_point) +{ + Temp scratch = scratch_begin(&arena, 1); + P2R_SymbolStreamConvertIn *in = (P2R_SymbolStreamConvertIn *)p; +#define p2r_type_ptr_from_itype(itype) ((in->itype_type_ptrs && (itype) < in->tpi_leaf->itype_opl) ? (in->itype_type_ptrs[(in->itype_fwd_map[(itype)] ? in->itype_fwd_map[(itype)] : (itype))]) : 0) + + ////////////////////////// + //- rjf: set up outputs for this sym stream + // + U64 sym_procedures_chunk_cap = 1024; + U64 sym_global_variables_chunk_cap = 1024; + U64 sym_thread_variables_chunk_cap = 1024; + U64 sym_scopes_chunk_cap = 1024; + U64 sym_inline_sites_chunk_cap = 1024; + RDIM_SymbolChunkList sym_procedures = {0}; + RDIM_SymbolChunkList sym_global_variables = {0}; + RDIM_SymbolChunkList sym_thread_variables = {0}; + RDIM_ScopeChunkList sym_scopes = {0}; + RDIM_InlineSiteChunkList sym_inline_sites = {0}; + + ////////////////////////// + //- rjf: symbols pass 1: produce procedure frame info map (procedure -> frame info) + // + U64 procedure_frameprocs_count = 0; + U64 procedure_frameprocs_cap = (in->sym_ranges_opl - in->sym_ranges_first); + CV_SymFrameproc **procedure_frameprocs = push_array_no_zero(scratch.arena, CV_SymFrameproc *, procedure_frameprocs_cap); + ProfScope("symbols pass 1: produce procedure frame info map (procedure -> frame info)") + { + U64 procedure_num = 0; + CV_RecRange *rec_ranges_first = in->sym->sym_ranges.ranges + in->sym_ranges_first; + CV_RecRange *rec_ranges_opl = in->sym->sym_ranges.ranges + in->sym_ranges_opl; + for(CV_RecRange *rec_range = rec_ranges_first; + rec_range < rec_ranges_opl; + rec_range += 1) + { + //- rjf: rec range -> symbol info range + U64 sym_off_first = rec_range->off + 2; + U64 sym_off_opl = rec_range->off + rec_range->hdr.size; + + //- rjf: skip invalid ranges + if(sym_off_opl > in->sym->data.size || sym_off_first > in->sym->data.size || sym_off_first > sym_off_opl) + { + continue; + } + + //- rjf: unpack symbol info + CV_SymKind kind = rec_range->hdr.kind; + U64 sym_header_struct_size = cv_header_struct_size_from_sym_kind(kind); + void *sym_header_struct_base = in->sym->data.str + sym_off_first; + + //- rjf: skip bad sizes + if(sym_off_first + sym_header_struct_size > sym_off_opl) + { + continue; + } + + //- rjf: consume symbol based on kind + switch(kind) + { + default:{}break; + + //- rjf: FRAMEPROC + case CV_SymKind_FRAMEPROC: + { + if(procedure_num == 0) { break; } + if(procedure_num > procedure_frameprocs_cap) { break; } + CV_SymFrameproc *frameproc = (CV_SymFrameproc*)sym_header_struct_base; + procedure_frameprocs[procedure_num-1] = frameproc; + procedure_frameprocs_count = Max(procedure_frameprocs_count, procedure_num); + }break; + + //- rjf: LPROC32/GPROC32 + case CV_SymKind_LPROC32: + case CV_SymKind_GPROC32: + { + procedure_num += 1; + }break; + } + } + U64 scratch_overkill = sizeof(procedure_frameprocs[0])*(procedure_frameprocs_cap-procedure_frameprocs_count); + arena_pop(scratch.arena, scratch_overkill); + } + + ////////////////////////// + //- rjf: symbols pass 2: construct all symbols, given procedure frame info map + // + ProfScope("symbols pass 2: construct all symbols, given procedure frame info map") + { + RDIM_LocationSet *defrange_target = 0; + B32 defrange_target_is_param = 0; + U64 procedure_num = 0; + U64 procedure_base_voff = 0; + CV_RecRange *rec_ranges_first = in->sym->sym_ranges.ranges + in->sym_ranges_first; + CV_RecRange *rec_ranges_opl = in->sym->sym_ranges.ranges + in->sym_ranges_opl; + typedef struct P2R_ScopeNode P2R_ScopeNode; + struct P2R_ScopeNode + { + P2R_ScopeNode *next; + RDIM_Scope *scope; + }; + P2R_ScopeNode *top_scope_node = 0; + P2R_ScopeNode *free_scope_node = 0; + RDIM_LineTable *inline_site_line_table = in->first_inline_site_line_table; + for(CV_RecRange *rec_range = rec_ranges_first; + rec_range < rec_ranges_opl; + rec_range += 1) + { + //- rjf: rec range -> symbol info range + U64 sym_off_first = rec_range->off + 2; + U64 sym_off_opl = rec_range->off + rec_range->hdr.size; + + //- rjf: skip invalid ranges + if(sym_off_opl > in->sym->data.size || sym_off_first > in->sym->data.size || sym_off_first > sym_off_opl) + { + continue; + } + + //- rjf: unpack symbol info + CV_SymKind kind = rec_range->hdr.kind; + U64 sym_header_struct_size = cv_header_struct_size_from_sym_kind(kind); + void *sym_header_struct_base = in->sym->data.str + sym_off_first; + void *sym_data_opl = in->sym->data.str + sym_off_opl; + + //- rjf: skip bad sizes + if(sym_off_first + sym_header_struct_size > sym_off_opl) + { + continue; + } + + //- rjf: consume symbol based on kind + switch(kind) + { + default:{}break; + + //- rjf: END + case CV_SymKind_END: + { + P2R_ScopeNode *n = top_scope_node; + if(n != 0) + { + SLLStackPop(top_scope_node); + SLLStackPush(free_scope_node, n); + } + defrange_target = 0; + defrange_target_is_param = 0; + }break; + + //- rjf: BLOCK32 + case CV_SymKind_BLOCK32: + { + // rjf: unpack sym + CV_SymBlock32 *block32 = (CV_SymBlock32 *)sym_header_struct_base; + + // rjf: build scope, insert into current parent scope + RDIM_Scope *scope = rdim_scope_chunk_list_push(arena, &sym_scopes, sym_scopes_chunk_cap); + { + if(top_scope_node == 0) + { + // TODO(rjf): log + } + if(top_scope_node != 0) + { + RDIM_Scope *top_scope = top_scope_node->scope; + SLLQueuePush_N(top_scope->first_child, top_scope->last_child, scope, next_sibling); + scope->parent_scope = top_scope; + scope->symbol = top_scope->symbol; + } + COFF_SectionHeader *section = (0 < block32->sec && block32->sec <= in->coff_sections->count) ? &in->coff_sections->sections[block32->sec-1] : 0; + if(section != 0) + { + U64 voff_first = section->voff + block32->off; + U64 voff_last = voff_first + block32->len; + RDIM_Rng1U64 voff_range = {voff_first, voff_last}; + rdim_scope_push_voff_range(arena, &sym_scopes, scope, voff_range); + } + } + + // rjf: push this scope to scope stack + { + P2R_ScopeNode *node = free_scope_node; + if(node != 0) { SLLStackPop(free_scope_node); } + else { node = push_array_no_zero(scratch.arena, P2R_ScopeNode, 1); } + node->scope = scope; + SLLStackPush(top_scope_node, node); + } + }break; + + //- rjf: LDATA32/GDATA32 + case CV_SymKind_LDATA32: + case CV_SymKind_GDATA32: + { + // rjf: unpack sym + CV_SymData32 *data32 = (CV_SymData32 *)sym_header_struct_base; + String8 name = str8_cstring_capped(data32+1, sym_data_opl); + COFF_SectionHeader *section = (0 < data32->sec && data32->sec <= in->coff_sections->count) ? &in->coff_sections->sections[data32->sec-1] : 0; + U64 voff = (section ? section->voff : 0) + data32->off; + + // rjf: determine if this is an exact duplicate global + // + // PDB likes to have duplicates of these spread across different + // symbol streams so we deduplicate across the entire translation + // context. + // + B32 is_duplicate = 0; + { + // TODO(rjf): @important global symbol dedup + } + + // rjf: is not duplicate -> push new global + if(!is_duplicate) + { + // rjf: unpack global variable's type + RDIM_Type *type = p2r_type_ptr_from_itype(data32->itype); + + // rjf: unpack global's container type + RDIM_Type *container_type = 0; + U64 container_name_opl = p2r_end_of_cplusplus_container_name(name); + if(container_name_opl > 2) + { + String8 container_name = str8(name.str, container_name_opl - 2); + CV_TypeId cv_type_id = pdb_tpi_first_itype_from_name(in->tpi_hash, in->tpi_leaf, container_name, 0); + container_type = p2r_type_ptr_from_itype(cv_type_id); + } + + // rjf: unpack global's container symbol + RDIM_Symbol *container_symbol = 0; + if(container_type == 0 && top_scope_node != 0) + { + container_symbol = top_scope_node->scope->symbol; + } + + // rjf: build symbol + RDIM_Symbol *symbol = rdim_symbol_chunk_list_push(arena, &sym_global_variables, sym_global_variables_chunk_cap); + symbol->is_extern = (kind == CV_SymKind_GDATA32); + symbol->name = name; + symbol->type = type; + symbol->offset = voff; + symbol->container_symbol = container_symbol; + symbol->container_type = container_type; + } + }break; + + //- rjf: LPROC32/GPROC32 + case CV_SymKind_LPROC32: + case CV_SymKind_GPROC32: + { + // rjf: unpack sym + CV_SymProc32 *proc32 = (CV_SymProc32 *)sym_header_struct_base; + String8 name = str8_cstring_capped(proc32+1, sym_data_opl); + RDIM_Type *type = p2r_type_ptr_from_itype(proc32->itype); + + // rjf: unpack proc's container type + RDIM_Type *container_type = 0; + U64 container_name_opl = p2r_end_of_cplusplus_container_name(name); + if(container_name_opl > 2 && in->tpi_hash != 0 && in->tpi_leaf != 0) + { + String8 container_name = str8(name.str, container_name_opl - 2); + CV_TypeId cv_type_id = pdb_tpi_first_itype_from_name(in->tpi_hash, in->tpi_leaf, container_name, 0); + container_type = p2r_type_ptr_from_itype(cv_type_id); + } + + // rjf: unpack proc's container symbol + RDIM_Symbol *container_symbol = 0; + if(container_type == 0 && top_scope_node != 0) + { + container_symbol = top_scope_node->scope->symbol; + } + + // rjf: build procedure's root scope + // + // NOTE: even if there could be a containing scope at this point (which should be + // illegal in C/C++ but not necessarily in another language) we would not use + // it here because these scopes refer to the ranges of code that make up a + // procedure *not* the namespaces, so a procedure's root scope always has + // no parent. + RDIM_Scope *procedure_root_scope = rdim_scope_chunk_list_push(arena, &sym_scopes, sym_scopes_chunk_cap); + { + COFF_SectionHeader *section = (0 < proc32->sec && proc32->sec <= in->coff_sections->count) ? &in->coff_sections->sections[proc32->sec-1] : 0; + if(section != 0) + { + U64 voff_first = section->voff + proc32->off; + U64 voff_last = voff_first + proc32->len; + RDIM_Rng1U64 voff_range = {voff_first, voff_last}; + rdim_scope_push_voff_range(arena, &sym_scopes, procedure_root_scope, voff_range); + procedure_base_voff = voff_first; + } + } + + // rjf: root scope voff minimum range -> link name + String8 link_name = {0}; + if(procedure_root_scope->voff_ranges.min != 0) + { + U64 voff = procedure_root_scope->voff_ranges.min; + U64 hash = p2r_hash_from_voff(voff); + U64 bucket_idx = hash%in->link_name_map->buckets_count; + P2R_LinkNameNode *node = 0; + for(P2R_LinkNameNode *n = in->link_name_map->buckets[bucket_idx]; n != 0; n = n->next) + { + if(n->voff == voff) + { + link_name = n->name; + break; + } + } + } + + // rjf: build procedure symbol + RDIM_Symbol *procedure_symbol = rdim_symbol_chunk_list_push(arena, &sym_procedures, sym_procedures_chunk_cap); + procedure_symbol->is_extern = (kind == CV_SymKind_GPROC32); + procedure_symbol->name = name; + procedure_symbol->link_name = link_name; + procedure_symbol->type = type; + procedure_symbol->container_symbol = container_symbol; + procedure_symbol->container_type = container_type; + procedure_symbol->root_scope = procedure_root_scope; + + // rjf: fill root scope's symbol + procedure_root_scope->symbol = procedure_symbol; + + // rjf: push scope to scope stack + { + P2R_ScopeNode *node = free_scope_node; + if(node != 0) { SLLStackPop(free_scope_node); } + else { node = push_array_no_zero(scratch.arena, P2R_ScopeNode, 1); } + node->scope = procedure_root_scope; + SLLStackPush(top_scope_node, node); + } + + // rjf: increment procedure counter + procedure_num += 1; + }break; + + //- rjf: REGREL32 + case CV_SymKind_REGREL32: + { + // TODO(rjf): apparently some of the information here may end up being + // redundant with "better" information from CV_SymKind_LOCAL record. + // we don't currently handle this, but if those cases arise then it + // will obviously be better to prefer the better information from both + // records. + + // rjf: no containing scope? -> malformed data; locals cannot be produced + // outside of a containing scope + if(top_scope_node == 0) + { + break; + } + + // rjf: unpack sym + CV_SymRegrel32 *regrel32 = (CV_SymRegrel32 *)sym_header_struct_base; + String8 name = str8_cstring_capped(regrel32+1, sym_data_opl); + RDIM_Type *type = p2r_type_ptr_from_itype(regrel32->itype); + CV_Reg cv_reg = regrel32->reg; + U32 var_off = regrel32->reg_off; + + // rjf: determine if this is a parameter + RDI_LocalKind local_kind = RDI_LocalKind_Variable; + { + B32 is_stack_reg = 0; + switch(in->arch) + { + default:{}break; + case RDI_Arch_X86:{is_stack_reg = (cv_reg == CV_Regx86_ESP);}break; + case RDI_Arch_X64:{is_stack_reg = (cv_reg == CV_Regx64_RSP);}break; + } + if(is_stack_reg) + { + U32 frame_size = 0xFFFFFFFF; + if(procedure_num != 0 && procedure_frameprocs[procedure_num-1] != 0 && procedure_num < procedure_frameprocs_count) + { + CV_SymFrameproc *frameproc = procedure_frameprocs[procedure_num-1]; + frame_size = frameproc->frame_size; + } + if(var_off > frame_size) + { + local_kind = RDI_LocalKind_Parameter; + } + } + } + + // rjf: build local + RDIM_Scope *scope = top_scope_node->scope; + RDIM_Local *local = rdim_scope_push_local(arena, &sym_scopes, scope); + local->kind = local_kind; + local->name = name; + local->type = type; + + // rjf: add location info to local + if(type != 0) + { + // rjf: determine if we need an extra indirection to the value + B32 extra_indirection_to_value = 0; + switch(in->arch) + { + case RDI_Arch_X86: + { + extra_indirection_to_value = (local_kind == RDI_LocalKind_Parameter && (type->byte_size > 4 || !IsPow2OrZero(type->byte_size))); + }break; + case RDI_Arch_X64: + { + extra_indirection_to_value = (local_kind == RDI_LocalKind_Parameter && (type->byte_size > 8 || !IsPow2OrZero(type->byte_size))); + }break; + } + + // rjf: get raddbg register code + RDI_RegCode reg_code = p2r_rdi_reg_code_from_cv_reg_code(in->arch, cv_reg); + // TODO(rjf): real byte_size & byte_pos from cv_reg goes here + U32 byte_size = 8; + U32 byte_pos = 0; + + // rjf: set location case + RDIM_Location *loc = p2r_location_from_addr_reg_off(arena, in->arch, reg_code, byte_size, byte_pos, (S64)(S32)var_off, extra_indirection_to_value); + RDIM_Rng1U64 voff_range = {0, max_U64}; + rdim_location_set_push_case(arena, &sym_scopes, &local->locset, voff_range, loc); + } + }break; + + //- rjf: LTHREAD32/GTHREAD32 + case CV_SymKind_LTHREAD32: + case CV_SymKind_GTHREAD32: + { + // rjf: unpack sym + CV_SymThread32 *thread32 = (CV_SymThread32 *)sym_header_struct_base; + String8 name = str8_cstring_capped(thread32+1, sym_data_opl); + U32 tls_off = thread32->tls_off; + RDIM_Type *type = p2r_type_ptr_from_itype(thread32->itype); + + // rjf: unpack thread variable's container type + RDIM_Type *container_type = 0; + U64 container_name_opl = p2r_end_of_cplusplus_container_name(name); + if(container_name_opl > 2) + { + String8 container_name = str8(name.str, container_name_opl - 2); + CV_TypeId cv_type_id = pdb_tpi_first_itype_from_name(in->tpi_hash, in->tpi_leaf, container_name, 0); + container_type = p2r_type_ptr_from_itype(cv_type_id); + } + + // rjf: unpack thread variable's container symbol + RDIM_Symbol *container_symbol = 0; + if(container_type == 0 && top_scope_node != 0) + { + container_symbol = top_scope_node->scope->symbol; + } + + // rjf: build symbol + RDIM_Symbol *tvar = rdim_symbol_chunk_list_push(arena, &sym_thread_variables, sym_thread_variables_chunk_cap); + tvar->name = name; + tvar->type = type; + tvar->is_extern = (kind == CV_SymKind_GTHREAD32); + tvar->offset = tls_off; + tvar->container_type = container_type; + tvar->container_symbol = container_symbol; + }break; + + //- rjf: LOCAL + case CV_SymKind_LOCAL: + { + // rjf: no containing scope? -> malformed data; locals cannot be produced + // outside of a containing scope + if(top_scope_node == 0) + { + break; + } + + // rjf: unpack sym + CV_SymLocal *slocal = (CV_SymLocal *)sym_header_struct_base; + String8 name = str8_cstring_capped(slocal+1, sym_data_opl); + RDIM_Type *type = p2r_type_ptr_from_itype(slocal->itype); + + // rjf: determine if this symbol encodes the beginning of a global modification + B32 is_global_modification = 0; + if((slocal->flags & CV_LocalFlag_Global) || + (slocal->flags & CV_LocalFlag_Static)) + { + is_global_modification = 1; + } + + // rjf: is global modification -> emit global modification symbol + if(is_global_modification) + { + // TODO(rjf): add global modification symbols + defrange_target = 0; + defrange_target_is_param = 0; + } + + // rjf: is not a global modification -> emit a local variable + if(!is_global_modification) + { + // rjf: determine local kind + RDI_LocalKind local_kind = RDI_LocalKind_Variable; + if(slocal->flags & CV_LocalFlag_Param) + { + local_kind = RDI_LocalKind_Parameter; + } + + // rjf: build local + RDIM_Scope *scope = top_scope_node->scope; + RDIM_Local *local = rdim_scope_push_local(arena, &sym_scopes, scope); + local->kind = local_kind; + local->name = name; + local->type = type; + + // rjf: save defrange target, for subsequent defrange symbols + defrange_target = &local->locset; + defrange_target_is_param = (local_kind == RDI_LocalKind_Parameter); + } + }break; + + //- rjf: DEFRANGE_REGISTESR + case CV_SymKind_DEFRANGE_REGISTER: + { + // rjf: no defrange target? -> somehow we got to a defrange symbol without first seeing + // a local - break immediately + if(defrange_target == 0) + { + break; + } + + // rjf: unpack sym + CV_SymDefrangeRegister *defrange_register = (CV_SymDefrangeRegister*)sym_header_struct_base; + CV_Reg cv_reg = defrange_register->reg; + CV_LvarAddrRange *range = &defrange_register->range; + COFF_SectionHeader *range_section = (0 < range->sec && range->sec <= in->coff_sections->count) ? &in->coff_sections->sections[range->sec-1] : 0; + CV_LvarAddrGap *gaps = (CV_LvarAddrGap*)(defrange_register+1); + U64 gap_count = ((U8*)sym_data_opl - (U8*)gaps) / sizeof(*gaps); + RDI_RegCode reg_code = p2r_rdi_reg_code_from_cv_reg_code(in->arch, cv_reg); + + // rjf: build location + RDIM_Location *location = rdim_push_location_val_reg(arena, reg_code); + + // rjf: emit locations over ranges + p2r_location_over_lvar_addr_range(arena, &sym_scopes, defrange_target, location, range, range_section, gaps, gap_count); + }break; + + //- rjf: DEFRANGE_FRAMEPOINTER_REL + case CV_SymKind_DEFRANGE_FRAMEPOINTER_REL: + { + // rjf: no defrange target? -> somehow we got to a defrange symbol without first seeing + // a local - break immediately + if(defrange_target == 0) + { + break; + } + + // rjf: find current procedure's frameproc + CV_SymFrameproc *frameproc = 0; + if(procedure_num != 0 && procedure_num <= procedure_frameprocs_count && procedure_frameprocs[procedure_num-1] != 0) + { + frameproc = procedure_frameprocs[procedure_num-1]; + } + + // rjf: no current valid frameproc? -> somehow we got a to a framepointer-relative defrange + // without having an actually active procedure - break + if(frameproc == 0) + { + break; + } + + // rjf: unpack sym + CV_SymDefrangeFramepointerRel *defrange_fprel = (CV_SymDefrangeFramepointerRel*)sym_header_struct_base; + CV_LvarAddrRange *range = &defrange_fprel->range; + COFF_SectionHeader *range_section = (0 < range->sec && range->sec <= in->coff_sections->count) ? &in->coff_sections->sections[range->sec-1] : 0; + CV_LvarAddrGap *gaps = (CV_LvarAddrGap*)(defrange_fprel + 1); + U64 gap_count = ((U8*)sym_data_opl - (U8*)gaps) / sizeof(*gaps); + + // rjf: select frame pointer register + CV_EncodedFramePtrReg encoded_fp_reg = p2r_cv_encoded_fp_reg_from_frameproc(frameproc, defrange_target_is_param); + RDI_RegCode fp_register_code = p2r_reg_code_from_arch_encoded_fp_reg(in->arch, encoded_fp_reg); + + // rjf: build location + B32 extra_indirection = 0; + U32 byte_size = rdi_addr_size_from_arch(in->arch); + U32 byte_pos = 0; + S64 var_off = (S64)defrange_fprel->off; + RDIM_Location *location = p2r_location_from_addr_reg_off(arena, in->arch, fp_register_code, byte_size, byte_pos, var_off, extra_indirection); + + // rjf: emit locations over ranges + p2r_location_over_lvar_addr_range(arena, &sym_scopes, defrange_target, location, range, range_section, gaps, gap_count); + }break; + + //- rjf: DEFRANGE_SUBFIELD_REGISTER + case CV_SymKind_DEFRANGE_SUBFIELD_REGISTER: + { + // rjf: no defrange target? -> somehow we got to a defrange symbol without first seeing + // a local - break immediately + if(defrange_target == 0) + { + break; + } + + // rjf: unpack sym + CV_SymDefrangeSubfieldRegister *defrange_subfield_register = (CV_SymDefrangeSubfieldRegister*)sym_header_struct_base; + CV_Reg cv_reg = defrange_subfield_register->reg; + CV_LvarAddrRange *range = &defrange_subfield_register->range; + COFF_SectionHeader *range_section = (0 < range->sec && range->sec <= in->coff_sections->count) ? &in->coff_sections->sections[range->sec-1] : 0; + CV_LvarAddrGap *gaps = (CV_LvarAddrGap*)(defrange_subfield_register + 1); + U64 gap_count = ((U8*)sym_data_opl - (U8*)gaps) / sizeof(*gaps); + RDI_RegCode reg_code = p2r_rdi_reg_code_from_cv_reg_code(in->arch, cv_reg); + + // rjf: skip "subfield" location info - currently not supported + if(defrange_subfield_register->field_offset != 0) + { + break; + } + + // rjf: build location + RDIM_Location *location = rdim_push_location_val_reg(arena, reg_code); + + // rjf: emit locations over ranges + p2r_location_over_lvar_addr_range(arena, &sym_scopes, defrange_target, location, range, range_section, gaps, gap_count); + }break; + + //- rjf: DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE + case CV_SymKind_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE: + { + // rjf: no defrange target? -> somehow we got to a defrange symbol without first seeing + // a local - break immediately + if(defrange_target == 0) + { + break; + } + + // rjf: find current procedure's frameproc + CV_SymFrameproc *frameproc = 0; + if(procedure_num != 0 && procedure_num <= procedure_frameprocs_count && procedure_frameprocs[procedure_num-1] != 0) + { + frameproc = procedure_frameprocs[procedure_num-1]; + } + + // rjf: no current valid frameproc? -> somehow we got a to a framepointer-relative defrange + // without having an actually active procedure - break + if(frameproc == 0) + { + break; + } + + // rjf: unpack sym + CV_SymDefrangeFramepointerRelFullScope *defrange_fprel_full_scope = (CV_SymDefrangeFramepointerRelFullScope*)sym_header_struct_base; + CV_EncodedFramePtrReg encoded_fp_reg = p2r_cv_encoded_fp_reg_from_frameproc(frameproc, defrange_target_is_param); + RDI_RegCode fp_register_code = p2r_reg_code_from_arch_encoded_fp_reg(in->arch, encoded_fp_reg); + + // rjf: build location + B32 extra_indirection = 0; + U32 byte_size = rdi_addr_size_from_arch(in->arch); + U32 byte_pos = 0; + S64 var_off = (S64)defrange_fprel_full_scope->off; + RDIM_Location *location = p2r_location_from_addr_reg_off(arena, in->arch, fp_register_code, byte_size, byte_pos, var_off, extra_indirection); + + // rjf: emit location over ranges + RDIM_Rng1U64 voff_range = {0, max_U64}; + rdim_location_set_push_case(arena, &sym_scopes, defrange_target, voff_range, location); + }break; + + //- rjf: DEFRANGE_REGISTER_REL + case CV_SymKind_DEFRANGE_REGISTER_REL: + { + // rjf: no defrange target? -> somehow we got to a defrange symbol without first seeing + // a local - break immediately + if(defrange_target == 0) + { + break; + } + + // rjf: unpack sym + CV_SymDefrangeRegisterRel *defrange_register_rel = (CV_SymDefrangeRegisterRel*)sym_header_struct_base; + CV_Reg cv_reg = defrange_register_rel->reg; + RDI_RegCode reg_code = p2r_rdi_reg_code_from_cv_reg_code(in->arch, cv_reg); + CV_LvarAddrRange *range = &defrange_register_rel->range; + COFF_SectionHeader *range_section = (0 < range->sec && range->sec <= in->coff_sections->count) ? &in->coff_sections->sections[range->sec-1] : 0; + CV_LvarAddrGap *gaps = (CV_LvarAddrGap*)(defrange_register_rel + 1); + U64 gap_count = ((U8*)sym_data_opl - (U8*)gaps) / sizeof(*gaps); + + // rjf: build location + // TODO(rjf): offset & size from cv_reg code + U32 byte_size = rdi_addr_size_from_arch(in->arch); + U32 byte_pos = 0; + B32 extra_indirection_to_value = 0; + S64 var_off = defrange_register_rel->reg_off; + RDIM_Location *location = p2r_location_from_addr_reg_off(arena, in->arch, reg_code, byte_size, byte_pos, var_off, extra_indirection_to_value); + + // rjf: emit locations over ranges + p2r_location_over_lvar_addr_range(arena, &sym_scopes, defrange_target, location, range, range_section, gaps, gap_count); + }break; + + //- rjf: FILESTATIC + case CV_SymKind_FILESTATIC: + { + CV_SymFileStatic *file_static = (CV_SymFileStatic*)sym_header_struct_base; + String8 name = str8_cstring_capped(file_static+1, sym_data_opl); + RDIM_Type *type = p2r_type_ptr_from_itype(file_static->itype); + // TODO(rjf): emit a global modifier symbol + defrange_target = 0; + defrange_target_is_param = 0; + }break; + + //- rjf: INLINESITE + case CV_SymKind_INLINESITE: + { + // rjf: unpack sym + CV_SymInlineSite *sym = (CV_SymInlineSite *)sym_header_struct_base; + String8 binary_annots = str8((U8 *)(sym+1), rec_range->hdr.size - sizeof(rec_range->hdr.kind) - sizeof(*sym)); + + // rjf: extract external info about inline site + String8 name = str8_zero(); + RDIM_Type *type = 0; + RDIM_Type *owner = 0; + if(in->ipi_leaf != 0 && in->ipi_leaf->itype_first <= sym->inlinee && sym->inlinee < in->ipi_leaf->itype_opl) + { + CV_RecRange rec_range = in->ipi_leaf->leaf_ranges.ranges[sym->inlinee - in->ipi_leaf->itype_first]; + String8 rec_data = str8_substr(in->ipi_leaf->data, rng_1u64(rec_range.off, rec_range.off + rec_range.hdr.size)); + void *raw_leaf = rec_data.str + sizeof(U16); + + // rjf: extract method inline info + if(rec_range.hdr.kind == CV_LeafIDKind_MFUNC_ID && + rec_range.hdr.size >= sizeof(CV_LeafMFuncId)) + { + CV_LeafMFuncId *mfunc_id = (CV_LeafMFuncId*)raw_leaf; + name = str8_cstring_capped(mfunc_id + 1, rec_data.str + rec_data.size); + type = p2r_type_ptr_from_itype(mfunc_id->itype); + owner = mfunc_id->owner_itype != 0 ? p2r_type_ptr_from_itype(mfunc_id->owner_itype) : 0; + } + + // rjf: extract non-method function inline info + else if(rec_range.hdr.kind == CV_LeafIDKind_FUNC_ID && + rec_range.hdr.size >= sizeof(CV_LeafFuncId)) + { + CV_LeafFuncId *func_id = (CV_LeafFuncId*)raw_leaf; + name = str8_cstring_capped(func_id + 1, rec_data.str + rec_data.size); + type = p2r_type_ptr_from_itype(func_id->itype); + owner = func_id->scope_string_id != 0 ? p2r_type_ptr_from_itype(func_id->scope_string_id) : 0; + } + } + + // rjf: build inline site + RDIM_InlineSite *inline_site = rdim_inline_site_chunk_list_push(arena, &sym_inline_sites, sym_inline_sites_chunk_cap); + inline_site->name = name; + inline_site->type = type; + inline_site->owner = owner; + inline_site->line_table = inline_site_line_table; + + // rjf: increment to next inline site line table in this unit + if(inline_site_line_table != 0 && inline_site_line_table->chunk != 0) + { + RDIM_LineTableChunkNode *chunk = inline_site_line_table->chunk; + U64 current_idx = (U64)(inline_site_line_table - chunk->v); + if(current_idx+1 < chunk->count) + { + inline_site_line_table += 1; + } + else + { + chunk = chunk->next; + inline_site_line_table = 0; + if(chunk != 0) + { + inline_site_line_table = chunk->v; + } + } + } + + // rjf: build scope + RDIM_Scope *scope = rdim_scope_chunk_list_push(arena, &sym_scopes, sym_scopes_chunk_cap); + scope->inline_site = inline_site; + if(top_scope_node == 0) + { + // TODO(rjf): log + } + if(top_scope_node != 0) + { + RDIM_Scope *top_scope = top_scope_node->scope; + SLLQueuePush_N(top_scope->first_child, top_scope->last_child, scope, next_sibling); + scope->parent_scope = top_scope; + scope->symbol = top_scope->symbol; + } + + // rjf: push this scope to scope stack + { + P2R_ScopeNode *node = free_scope_node; + if(node != 0) { SLLStackPop(free_scope_node); } + else { node = push_array_no_zero(scratch.arena, P2R_ScopeNode, 1); } + node->scope = scope; + SLLStackPush(top_scope_node, node); + } + + // rjf: parse offset ranges of this inline site - attach to scope + { + U32 code_length = 0; + U32 code_offset = 0; + U32 last_code_offset = code_offset; + U32 last_code_length = code_length; + U64 read_off = 0; + U64 read_off_opl = binary_annots.size; + for(B32 good = 1; read_off < read_off_opl && good;) + { + // rjf: decode next annotation op + U32 op = CV_InlineBinaryAnnotation_Null; + read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &op); + + // rjf: apply op + switch(op) + { + default:{good = 1;}break; + case CV_InlineBinaryAnnotation_Null: + { + good = 0; + }break; + case CV_InlineBinaryAnnotation_CodeOffset: + { + read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_offset); + }break; + case CV_InlineBinaryAnnotation_ChangeCodeOffsetBase: + { + good = 0; + // TODO(rjf): currently untested/unknown - first guess below: + // + // U32 delta = 0; + // read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &delta); + // code_offset_base = code_offset; + // code_offset_end = code_offset + delta; + // code_offset += delta; + }break; + case CV_InlineBinaryAnnotation_ChangeCodeOffset: + { + U32 delta = 0; + read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &delta); + code_offset += delta; + }break; + case CV_InlineBinaryAnnotation_ChangeCodeLength: + { + code_length = 0; + read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_length); + }break; + case CV_InlineBinaryAnnotation_ChangeCodeOffsetAndLineOffset: + { + U32 code_offset_and_line_offset = 0; + read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_offset_and_line_offset); + U32 code_delta = (code_offset_and_line_offset & 0xf); + code_offset += code_delta; + }break; + case CV_InlineBinaryAnnotation_ChangeCodeLengthAndCodeOffset: + { + U32 offset_delta = 0; + read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &code_length); + read_off += cv_decode_inline_annot_u32(binary_annots, read_off, &offset_delta); + code_offset += offset_delta; + }break; + } + + // rjf: gather new ranges + if(last_code_length != code_length) + { + // rjf: convert current state machine state to [first_voff, opl_voff) range + RDIM_Rng1U64 voff_range = + { + procedure_base_voff + code_offset, + procedure_base_voff + code_offset + code_length, + }; + + // rjf: attempt to extend last-added range to cover this range, if possible + if(scope->voff_ranges.last != 0 && scope->voff_ranges.last->v.max == voff_range.min) + { + scope->voff_ranges.last->v.max = voff_range.max; + } + + // rjf: cannot add to previous range? -> build new range & add to scope + else + { + rdim_scope_push_voff_range(arena, &sym_scopes, scope, voff_range); + } + + // rjf: advance + code_offset += code_length; + code_length = 0; + } + + // rjf: update prev/current states + last_code_offset = code_offset; + last_code_length = code_length; + } + } + }break; + + //- rjf: INLINESITE_END + case CV_SymKind_INLINESITE_END: + { + P2R_ScopeNode *n = top_scope_node; + if(n != 0) + { + SLLStackPop(top_scope_node); + SLLStackPush(free_scope_node, n); + } + defrange_target = 0; + defrange_target_is_param = 0; + }break; + } + } + } + + ////////////////////////// + //- rjf: allocate & fill output + // + P2R_SymbolStreamConvertOut *out = push_array(arena, P2R_SymbolStreamConvertOut, 1); + { + out->procedures = sym_procedures; + out->global_variables = sym_global_variables; + out->thread_variables = sym_thread_variables; + out->scopes = sym_scopes; + out->inline_sites = sym_inline_sites; + } + +#undef p2r_type_ptr_from_itype + scratch_end(scratch); + return out; +} + +//////////////////////////////// +//~ rjf: Top-Level Conversion Entry Point + +internal P2R_Convert2Bake * +p2r_convert(Arena *arena, P2R_User2Convert *in) +{ + Temp scratch = scratch_begin(&arena, 1); + + ////////////////////////////////////////////////////////////// + //- rjf: parse MSF structure + // + MSF_Parsed *msf = 0; + if(in->input_pdb_data.size != 0) ProfScope("parse MSF structure") + { + msf = msf_parsed_from_data(arena, in->input_pdb_data); + } + + ////////////////////////////////////////////////////////////// + //- rjf: parse PDB auth_guid & named streams table + // + PDB_NamedStreamTable *named_streams = 0; + COFF_Guid auth_guid = {0}; + if(msf != 0) ProfScope("parse PDB auth_guid & named streams table") + { + Temp scratch = scratch_begin(&arena, 1); + String8 info_data = msf_data_from_stream(msf, PDB_FixedStream_PdbInfo); + PDB_Info *info = pdb_info_from_data(scratch.arena, info_data); + named_streams = pdb_named_stream_table_from_info(arena, info); + MemoryCopyStruct(&auth_guid, &info->auth_guid); + scratch_end(scratch); + } + + ////////////////////////////////////////////////////////////// + //- rjf: parse PDB strtbl + // + PDB_Strtbl *strtbl = 0; + if(named_streams != 0) ProfScope("parse PDB strtbl") + { + MSF_StreamNumber strtbl_sn = named_streams->sn[PDB_NamedStream_STRTABLE]; + String8 strtbl_data = msf_data_from_stream(msf, strtbl_sn); + strtbl = pdb_strtbl_from_data(arena, strtbl_data); + } + + ////////////////////////////////////////////////////////////// + //- rjf: parse dbi + // + PDB_DbiParsed *dbi = 0; + if(msf != 0) ProfScope("parse dbi") + { + String8 dbi_data = msf_data_from_stream(msf, PDB_FixedStream_Dbi); + dbi = pdb_dbi_from_data(arena, dbi_data); + } + + ////////////////////////////////////////////////////////////// + //- rjf: parse tpi + // + PDB_TpiParsed *tpi = 0; + if(msf != 0) ProfScope("parse tpi") + { + String8 tpi_data = msf_data_from_stream(msf, PDB_FixedStream_Tpi); + tpi = pdb_tpi_from_data(arena, tpi_data); + } + + ////////////////////////////////////////////////////////////// + //- rjf: parse ipi + // + PDB_TpiParsed *ipi = 0; + if(msf != 0) ProfScope("parse ipi") + { + String8 ipi_data = msf_data_from_stream(msf, PDB_FixedStream_Ipi); + ipi = pdb_tpi_from_data(arena, ipi_data); + } + + ////////////////////////////////////////////////////////////// + //- rjf: parse coff sections + // + PDB_CoffSectionArray *coff_sections = 0; + U64 coff_section_count = 0; + if(dbi != 0) ProfScope("parse coff sections") + { + MSF_StreamNumber section_stream = dbi->dbg_streams[PDB_DbiStream_SECTION_HEADER]; + String8 section_data = msf_data_from_stream(msf, section_stream); + coff_sections = pdb_coff_section_array_from_data(arena, section_data); + coff_section_count = coff_sections->count; + } + + ////////////////////////////////////////////////////////////// + //- rjf: parse gsi + // + PDB_GsiParsed *gsi = 0; + if(dbi != 0) ProfScope("parse gsi") + { + String8 gsi_data = msf_data_from_stream(msf, dbi->gsi_sn); + gsi = pdb_gsi_from_data(arena, gsi_data); + } + + ////////////////////////////////////////////////////////////// + //- rjf: parse psi + // + PDB_GsiParsed *psi_gsi_part = 0; + if(dbi != 0) ProfScope("parse psi") + { + String8 psi_data = msf_data_from_stream(msf, dbi->psi_sn); + String8 psi_data_gsi_part = str8_range(psi_data.str + sizeof(PDB_PsiHeader), psi_data.str + psi_data.size); + psi_gsi_part = pdb_gsi_from_data(arena, psi_data_gsi_part); + } + + ////////////////////////////////////////////////////////////// + //- rjf: kickoff EXE hash + // + P2R_EXEHashIn exe_hash_in = {in->input_exe_data}; + TS_Ticket exe_hash_ticket = ts_kickoff(p2r_exe_hash_task__entry_point, 0, &exe_hash_in); + + ////////////////////////////////////////////////////////////// + //- rjf: kickoff TPI hash parse + // + P2R_TPIHashParseIn tpi_hash_in = {0}; + TS_Ticket tpi_hash_ticket = {0}; + if(tpi != 0) + { + tpi_hash_in.strtbl = strtbl; + tpi_hash_in.tpi = tpi; + tpi_hash_in.hash_data = msf_data_from_stream(msf, tpi->hash_sn); + tpi_hash_in.aux_data = msf_data_from_stream(msf, tpi->hash_sn_aux); + tpi_hash_ticket = ts_kickoff(p2r_tpi_hash_parse_task__entry_point, 0, &tpi_hash_in); + } + + ////////////////////////////////////////////////////////////// + //- rjf: kickoff TPI leaf parse + // + P2R_TPILeafParseIn tpi_leaf_in = {0}; + TS_Ticket tpi_leaf_ticket = {0}; + if(tpi != 0) + { + tpi_leaf_in.leaf_data = pdb_leaf_data_from_tpi(tpi); + tpi_leaf_in.itype_first = tpi->itype_first; + tpi_leaf_ticket = ts_kickoff(p2r_tpi_leaf_parse_task__entry_point, 0, &tpi_leaf_in); + } + + ////////////////////////////////////////////////////////////// + //- rjf: kickoff IPI hash parse + // + P2R_TPIHashParseIn ipi_hash_in = {0}; + TS_Ticket ipi_hash_ticket = {0}; + if(ipi != 0) + { + ipi_hash_in.strtbl = strtbl; + ipi_hash_in.tpi = ipi; + ipi_hash_in.hash_data = msf_data_from_stream(msf, ipi->hash_sn); + ipi_hash_in.aux_data = msf_data_from_stream(msf, ipi->hash_sn_aux); + ipi_hash_ticket = ts_kickoff(p2r_tpi_hash_parse_task__entry_point, 0, &ipi_hash_in); + } + + ////////////////////////////////////////////////////////////// + //- rjf: kickoff IPI leaf parse + // + P2R_TPILeafParseIn ipi_leaf_in = {0}; + TS_Ticket ipi_leaf_ticket = {0}; + if(ipi != 0) + { + ipi_leaf_in.leaf_data = pdb_leaf_data_from_tpi(ipi); + ipi_leaf_in.itype_first = ipi->itype_first; + ipi_leaf_ticket = ts_kickoff(p2r_tpi_leaf_parse_task__entry_point, 0, &ipi_leaf_in); + } + + ////////////////////////////////////////////////////////////// + //- rjf: kickoff top-level global symbol stream parse + // + P2R_SymbolStreamParseIn sym_parse_in = {dbi ? msf_data_from_stream(msf, dbi->sym_sn) : str8_zero()}; + TS_Ticket sym_parse_ticket = !dbi ? ts_ticket_zero() : ts_kickoff(p2r_symbol_stream_parse_task__entry_point, 0, &sym_parse_in); + + ////////////////////////////////////////////////////////////// + //- rjf: kickoff compilation unit parses + // + P2R_CompUnitParseIn comp_unit_parse_in = {dbi ? pdb_data_from_dbi_range(dbi, PDB_DbiRange_ModuleInfo) : str8_zero()}; + P2R_CompUnitContributionsParseIn comp_unit_contributions_parse_in = {dbi ? pdb_data_from_dbi_range(dbi, PDB_DbiRange_SecCon) : str8_zero(), coff_sections}; + TS_Ticket comp_unit_parse_ticket = !dbi ? ts_ticket_zero() : ts_kickoff(p2r_comp_unit_parse_task__entry_point, 0, &comp_unit_parse_in); + TS_Ticket comp_unit_contributions_parse_ticket = !dbi ? ts_ticket_zero() : ts_kickoff(p2r_comp_unit_contributions_parse_task__entry_point, 0, &comp_unit_contributions_parse_in); + + ////////////////////////////////////////////////////////////// + //- rjf: join compilation unit parses + // + PDB_CompUnitArray *comp_units = 0; + U64 comp_unit_count = 0; + PDB_CompUnitContributionArray *comp_unit_contributions = 0; + U64 comp_unit_contribution_count = 0; + { + comp_units = ts_join_struct(comp_unit_parse_ticket, max_U64, PDB_CompUnitArray); + comp_unit_contributions = ts_join_struct(comp_unit_contributions_parse_ticket, max_U64, PDB_CompUnitContributionArray); + comp_unit_count = comp_units ? comp_units->count : 0; + comp_unit_contribution_count = comp_unit_contributions ? comp_unit_contributions->count : 0; + } + + ////////////////////////////////////////////////////////////// + //- rjf: parse syms & line info for each compilation unit + // + CV_SymParsed **sym_for_unit = push_array(arena, CV_SymParsed *, comp_unit_count); + CV_C13Parsed **c13_for_unit = push_array(arena, CV_C13Parsed *, comp_unit_count); + if(comp_units != 0) ProfScope("parse syms & line info for each compilation unit") + { + //- rjf: kick off tasks + P2R_SymbolStreamParseIn *sym_tasks_inputs = push_array(scratch.arena, P2R_SymbolStreamParseIn, comp_unit_count); + TS_Ticket *sym_tasks_tickets = push_array(scratch.arena, TS_Ticket, comp_unit_count); + P2R_C13StreamParseIn *c13_tasks_inputs = push_array(scratch.arena, P2R_C13StreamParseIn, comp_unit_count); + TS_Ticket *c13_tasks_tickets = push_array(scratch.arena, TS_Ticket, comp_unit_count); + for(U64 idx = 0; idx < comp_unit_count; idx += 1) + { + PDB_CompUnit *unit = comp_units->units[idx]; + sym_tasks_inputs[idx].data = pdb_data_from_unit_range(msf, unit, PDB_DbiCompUnitRange_Symbols); + sym_tasks_tickets[idx] = ts_kickoff(p2r_symbol_stream_parse_task__entry_point, 0, &sym_tasks_inputs[idx]); + c13_tasks_inputs[idx].data = pdb_data_from_unit_range(msf, unit, PDB_DbiCompUnitRange_C13); + c13_tasks_inputs[idx].strtbl = strtbl; + c13_tasks_inputs[idx].coff_sections = coff_sections; + c13_tasks_tickets[idx] = ts_kickoff(p2r_c13_stream_parse_task__entry_point, 0, &c13_tasks_inputs[idx]); + } + + //- rjf: join tasks + for(U64 idx = 0; idx < comp_unit_count; idx += 1) + { + sym_for_unit[idx] = ts_join_struct(sym_tasks_tickets[idx], max_U64, CV_SymParsed); + c13_for_unit[idx] = ts_join_struct(c13_tasks_tickets[idx], max_U64, CV_C13Parsed); + } + } + + ////////////////////////////////////////////////////////////// + //- rjf: calculate EXE's max voff + // + U64 exe_voff_max = 0; + if(coff_sections != 0) + { + COFF_SectionHeader *coff_sec_ptr = coff_sections->sections; + COFF_SectionHeader *coff_ptr_opl = coff_sec_ptr + coff_section_count; + for(;coff_sec_ptr < coff_ptr_opl; coff_sec_ptr += 1) + { + U64 sec_voff_max = coff_sec_ptr->voff + coff_sec_ptr->vsize; + exe_voff_max = Max(exe_voff_max, sec_voff_max); + } + } + + ////////////////////////////////////////////////////////////// + //- rjf: determine architecture + // + RDI_Arch arch = RDI_Arch_NULL; + U64 arch_addr_size = 0; + { + // TODO(rjf): in some cases, the first compilation unit has a zero + // architecture, as it's sometimes used as a "nil" unit. this causes bugs + // in later stages of conversion - particularly, this was detected via + // busted location info. so i've converted this to a scan-until-we-find-an- + // architecture. however, this may still be fundamentally insufficient, + // because Nick has informed me that x86 units can be linked with x64 + // units, meaning the appropriate architecture at any point in time is not + // a top-level concept, and is rather dependent on to which compilation + // unit particular symbols belong. so in the future, to support that (odd) + // case, we'll need to not only have this be a top-level "contextual" piece + // of info, but to use the appropriate compilation unit's architecture when + // possible. assuming, of course, that we care about supporting that case. + for(U64 comp_unit_idx = 0; comp_unit_idx < comp_unit_count; comp_unit_idx += 1) + { + if(sym_for_unit[comp_unit_idx] != 0) + { + arch = p2r_rdi_arch_from_cv_arch(sym_for_unit[comp_unit_idx]->info.arch); + if(arch != RDI_Arch_NULL) + { + break; + } + } + } + arch_addr_size = rdi_addr_size_from_arch(arch); + } + + ////////////////////////////////////////////////////////////// + //- rjf: join EXE hash + // + U64 exe_hash = *ts_join_struct(exe_hash_ticket, max_U64, U64); + + ////////////////////////////////////////////////////////////// + //- rjf: produce top-level-info + // + RDIM_TopLevelInfo top_level_info = {0}; + { + top_level_info.arch = arch; + top_level_info.exe_name = str8_skip_last_slash(in->input_exe_name); + top_level_info.exe_hash = exe_hash; + top_level_info.voff_max = exe_voff_max; + top_level_info.producer_name = str8_lit(BUILD_TITLE_STRING_LITERAL); + } + + ////////////////////////////////////////////////////////////// + //- rjf: build binary sections list + // + RDIM_BinarySectionList binary_sections = {0}; + if(coff_sections != 0) ProfScope("build binary section list") + { + COFF_SectionHeader *coff_ptr = coff_sections->sections; + COFF_SectionHeader *coff_opl = coff_ptr + coff_section_count; + for(;coff_ptr < coff_opl; coff_ptr += 1) + { + char *name_first = (char*)coff_ptr->name; + char *name_opl = name_first + sizeof(coff_ptr->name); + RDIM_BinarySection *sec = rdim_binary_section_list_push(arena, &binary_sections); + sec->name = str8_cstring_capped(name_first, name_opl); + sec->flags = p2r_rdi_binary_section_flags_from_coff_section_flags(coff_ptr->flags); + sec->voff_first = coff_ptr->voff; + sec->voff_opl = coff_ptr->voff+coff_ptr->vsize; + sec->foff_first = coff_ptr->foff; + sec->foff_opl = coff_ptr->foff+coff_ptr->fsize; + } + } + + ////////////////////////////////////////////////////////////// + //- rjf: kick off unit conversion & source file collection + // + P2R_UnitConvertIn unit_convert_in = {strtbl, coff_sections, comp_units, comp_unit_contributions, sym_for_unit, c13_for_unit}; + TS_Ticket unit_convert_ticket = ts_kickoff(p2r_units_convert_task__entry_point, 0, &unit_convert_in); + + ////////////////////////////////////////////////////////////// + //- rjf: join global sym stream parse + // + CV_SymParsed *sym = ts_join_struct(sym_parse_ticket, max_U64, CV_SymParsed); + + ////////////////////////////// + //- rjf: predict symbol count + // + U64 symbol_count_prediction = 0; + ProfScope("predict symbol count") + { + U64 rec_range_count = 0; + if(sym != 0) + { + rec_range_count += sym->sym_ranges.count; + } + for(U64 comp_unit_idx = 0; comp_unit_idx < comp_unit_count; comp_unit_idx += 1) + { + CV_SymParsed *unit_sym = sym_for_unit[comp_unit_idx]; + rec_range_count += unit_sym->sym_ranges.count; + } + symbol_count_prediction = rec_range_count/8; + if(symbol_count_prediction < 256) + { + symbol_count_prediction = 256; + } + } + + ////////////////////////////////////////////////////////////// + //- rjf: kick off link name map production + // + P2R_LinkNameMap link_name_map__in_progress = {0}; + P2R_LinkNameMapBuildIn link_name_map_build_in = {0}; + TS_Ticket link_name_map_ticket = {0}; + if(sym != 0) ProfScope("kick off link name map build task") + { + link_name_map__in_progress.buckets_count = symbol_count_prediction; + link_name_map__in_progress.buckets = push_array(arena, P2R_LinkNameNode *, link_name_map__in_progress.buckets_count); + link_name_map_build_in.sym = sym; + link_name_map_build_in.coff_sections = coff_sections; + link_name_map_build_in.link_name_map = &link_name_map__in_progress; + link_name_map_ticket = ts_kickoff(p2r_link_name_map_build_task__entry_point, 0, &link_name_map_build_in); + } + + ////////////////////////////////////////////////////////////// + //- rjf: join ipi/tpi hash/leaf parses + // + PDB_TpiHashParsed *tpi_hash = 0; + CV_LeafParsed *tpi_leaf = 0; + PDB_TpiHashParsed *ipi_hash = 0; + CV_LeafParsed *ipi_leaf = 0; + { + tpi_hash = ts_join_struct(tpi_hash_ticket, max_U64, PDB_TpiHashParsed); + tpi_leaf = ts_join_struct(tpi_leaf_ticket, max_U64, CV_LeafParsed); + ipi_hash = ts_join_struct(ipi_hash_ticket, max_U64, PDB_TpiHashParsed); + ipi_leaf = ts_join_struct(ipi_leaf_ticket, max_U64, CV_LeafParsed); + } + + ////////////////////////////////////////////////////////////// + //- rjf: types pass 1: produce type forward resolution map + // + // this map is used to resolve usage of "incomplete structs" in codeview's + // type info. this often happens when e.g. "struct Foo" is used to refer to + // a later-defined "Foo", which actually contains members and so on. we want + // to hook types up to their actual destination complete types wherever + // possible, and so this map can be used to do that in subsequent stages. + // + CV_TypeId *itype_fwd_map = 0; + CV_TypeId itype_first = 0; + CV_TypeId itype_opl = 0; + if(in->flags & P2R_ConvertFlag_Types) ProfScope("types pass 1: produce type forward resolution map") + { + //- rjf: allocate forward resolution map + itype_first = tpi_leaf->itype_first; + itype_opl = tpi_leaf->itype_opl; + itype_fwd_map = push_array(arena, CV_TypeId, (U64)itype_opl); + + //- rjf: kick off tasks to fill forward resolution map + U64 task_size_itypes = 1024; + U64 tasks_count = ((U64)itype_opl+(task_size_itypes-1))/task_size_itypes; + P2R_ITypeFwdMapFillIn *tasks_inputs = push_array(scratch.arena, P2R_ITypeFwdMapFillIn, tasks_count); + TS_Ticket *tasks_tickets = push_array(scratch.arena, TS_Ticket, tasks_count); + for(U64 idx = 0; idx < tasks_count; idx += 1) + { + tasks_inputs[idx].tpi_hash = tpi_hash; + tasks_inputs[idx].tpi_leaf = tpi_leaf; + tasks_inputs[idx].itype_first = idx*task_size_itypes; + tasks_inputs[idx].itype_opl = tasks_inputs[idx].itype_first + task_size_itypes; + tasks_inputs[idx].itype_opl = ClampTop(tasks_inputs[idx].itype_opl, itype_opl); + tasks_inputs[idx].itype_fwd_map = itype_fwd_map; + tasks_tickets[idx] = ts_kickoff(p2r_itype_fwd_map_fill_task__entry_point, 0, &tasks_inputs[idx]); + } + + //- rjf: join all tasks + for(U64 idx = 0; idx < tasks_count; idx += 1) + { + ts_join(tasks_tickets[idx], max_U64); + } + } + + ////////////////////////////////////////////////////////////// + //- rjf: types pass 2: produce per-itype itype chain + // + // this pass is to ensure that subsequent passes always produce types for + // dependent itypes first - guaranteeing rdi's "only reference backward" + // rule (which eliminates cycles). each itype slot gets a list of itypes, + // starting with the deepest dependency - when types are produced per-itype, + // this chain is walked, so that deeper dependencies are built first, and + // as such, always show up *earlier* in the actually built types. + // + P2R_TypeIdChain **itype_chains = 0; + if(in->flags & P2R_ConvertFlag_Types) ProfScope("types pass 2: produce per-itype itype chain (for producing dependent types first)") + { + //- rjf: allocate itype chain table + itype_chains = push_array(arena, P2R_TypeIdChain *, (U64)itype_opl); + + //- rjf: kick off tasks to fill itype chain table + U64 task_size_itypes = 1024; + U64 tasks_count = ((U64)itype_opl+(task_size_itypes-1))/task_size_itypes; + P2R_ITypeChainBuildIn *tasks_inputs = push_array(scratch.arena, P2R_ITypeChainBuildIn, tasks_count); + TS_Ticket *tasks_tickets = push_array(scratch.arena, TS_Ticket, tasks_count); + for(U64 idx = 0; idx < tasks_count; idx += 1) + { + tasks_inputs[idx].tpi_leaf = tpi_leaf; + tasks_inputs[idx].itype_first = idx*task_size_itypes; + tasks_inputs[idx].itype_opl = tasks_inputs[idx].itype_first + task_size_itypes; + tasks_inputs[idx].itype_opl = ClampTop(tasks_inputs[idx].itype_opl, itype_opl); + tasks_inputs[idx].itype_chains = itype_chains; + tasks_inputs[idx].itype_fwd_map = itype_fwd_map; + tasks_tickets[idx] = ts_kickoff(p2r_itype_chain_build_task__entry_point, 0, &tasks_inputs[idx]); + } + + //- rjf: join all tasks + for(U64 idx = 0; idx < tasks_count; idx += 1) + { + ts_join(tasks_tickets[idx], max_U64); + } + } + + ////////////////////////////////////////////////////////////// + //- rjf: types pass 3: construct all types from TPI + // + // this doesn't gather struct/class/union/enum members, which is done by + // subsequent passes, to build RDI "UDT" information, which is distinct + // from regular type info. + // + RDIM_Type **itype_type_ptrs = 0; + RDIM_TypeChunkList all_types = {0}; +#define p2r_type_ptr_from_itype(itype) ((itype_type_ptrs && (itype) < itype_opl) ? (itype_type_ptrs[(itype_fwd_map[(itype)] ? itype_fwd_map[(itype)] : (itype))]) : 0) + if(in->flags & P2R_ConvertFlag_Types) ProfScope("types pass 3: construct all root/stub types from TPI") + { + itype_type_ptrs = push_array(arena, RDIM_Type *, (U64)(itype_opl)); + for(CV_TypeId root_itype = 0; root_itype < itype_opl; root_itype += 1) + { + for(P2R_TypeIdChain *itype_chain = itype_chains[root_itype]; + itype_chain != 0; + itype_chain = itype_chain->next) + { + CV_TypeId itype = (root_itype != itype_chain->itype && itype_chain->itype < itype_opl && itype_fwd_map[itype_chain->itype]) ? itype_fwd_map[itype_chain->itype] : itype_chain->itype; + B32 itype_is_basic = (itype < 0x1000); + + ////////////////////////// + //- rjf: skip forward-reference itypes - all future resolutions will + // reference whatever this itype resolves to, and so there is no point + // in filling out this slot + // + if(itype_fwd_map[root_itype] != 0) + { + continue; + } + + ////////////////////////// + //- rjf: skip already produced dependencies + // + if(itype_type_ptrs[itype] != 0) + { + continue; + } + + ////////////////////////// + //- rjf: build basic type + // + if(itype_is_basic) + { + RDIM_Type *dst_type = 0; + + // rjf: unpack itype + CV_BasicPointerKind cv_basic_ptr_kind = CV_BasicPointerKindFromTypeId(itype); + CV_BasicType cv_basic_type_code = CV_BasicTypeFromTypeId(itype); + + // rjf: get basic type slot, fill if unfilled + RDIM_Type *basic_type = itype_type_ptrs[cv_basic_type_code]; + if(basic_type == 0) + { + RDI_TypeKind type_kind = p2r_rdi_type_kind_from_cv_basic_type(cv_basic_type_code); + U32 byte_size = rdi_size_from_basic_type_kind(type_kind); + basic_type = dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); + if(byte_size == 0xffffffff) + { + byte_size = arch_addr_size; + } + basic_type->kind = type_kind; + basic_type->name = cv_type_name_from_basic_type(cv_basic_type_code); + basic_type->byte_size = byte_size; + } + + // rjf: nonzero ptr kind -> form ptr type to basic tpye + if(cv_basic_ptr_kind != 0) + { + dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); + dst_type->kind = RDI_TypeKind_Ptr; + dst_type->byte_size = arch_addr_size; + dst_type->direct_type = basic_type; + } + + // rjf: fill this itype's slot with the finished type + itype_type_ptrs[itype] = dst_type; + } + + ////////////////////////// + //- rjf: build non-basic type + // + if(!itype_is_basic && itype >= itype_first) + { + RDIM_Type *dst_type = 0; + CV_RecRange *range = &tpi_leaf->leaf_ranges.ranges[itype-itype_first]; + CV_LeafKind kind = range->hdr.kind; + U64 header_struct_size = cv_header_struct_size_from_leaf_kind(kind); + if(range->off+range->hdr.size <= tpi_leaf->data.size && + range->off+2+header_struct_size <= tpi_leaf->data.size && + range->hdr.size >= 2) + { + U8 *itype_leaf_first = tpi_leaf->data.str + range->off+2; + U8 *itype_leaf_opl = itype_leaf_first + range->hdr.size-2; + switch(kind) + { + //- rjf: MODIFIER + case CV_LeafKind_MODIFIER: + { + // rjf: unpack leaf + CV_LeafModifier *lf = (CV_LeafModifier *)itype_leaf_first; + + // rjf: cv -> rdi flags + RDI_TypeModifierFlags flags = 0; + if(lf->flags & CV_ModifierFlag_Const) {flags |= RDI_TypeModifierFlag_Const;} + if(lf->flags & CV_ModifierFlag_Volatile) {flags |= RDI_TypeModifierFlag_Volatile;} + + // rjf: fill type + if(flags == 0) + { + dst_type = p2r_type_ptr_from_itype(lf->itype); + } + else + { + dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); + dst_type->kind = RDI_TypeKind_Modifier; + dst_type->flags = flags; + dst_type->direct_type = p2r_type_ptr_from_itype(lf->itype); + dst_type->byte_size = dst_type->direct_type ? dst_type->direct_type->byte_size : 0; + } + }break; + + //- rjf: POINTER + case CV_LeafKind_POINTER: + { + // TODO(rjf): if ptr_mode in {PtrMem, PtrMethod} then output a member pointer instead + + // rjf: unpack leaf + CV_LeafPointer *lf = (CV_LeafPointer *)itype_leaf_first; + RDIM_Type *direct_type = p2r_type_ptr_from_itype(lf->itype); + CV_PointerKind ptr_kind = CV_PointerAttribs_ExtractKind(lf->attribs); + CV_PointerMode ptr_mode = CV_PointerAttribs_ExtractMode(lf->attribs); + U32 ptr_size = CV_PointerAttribs_ExtractSize(lf->attribs); + + // rjf: cv -> rdi modifier flags + RDI_TypeModifierFlags modifier_flags = 0; + if(lf->attribs & CV_PointerAttrib_Const) {modifier_flags |= RDI_TypeModifierFlag_Const;} + if(lf->attribs & CV_PointerAttrib_Volatile) {modifier_flags |= RDI_TypeModifierFlag_Volatile;} + + // rjf: cv info -> rdi pointer type kind + RDI_TypeKind type_kind = RDI_TypeKind_Ptr; + { + if(lf->attribs & CV_PointerAttrib_LRef) + { + type_kind = RDI_TypeKind_LRef; + } + else if(lf->attribs & CV_PointerAttrib_RRef) + { + type_kind = RDI_TypeKind_RRef; + } + if(ptr_mode == CV_PointerMode_LRef) + { + type_kind = RDI_TypeKind_LRef; + } + else if(ptr_mode == CV_PointerMode_RRef) + { + type_kind = RDI_TypeKind_RRef; + } + } + + // rjf: fill type + if(modifier_flags != 0) + { + RDIM_Type *pointer_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); + dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); + dst_type->kind = RDI_TypeKind_Modifier; + dst_type->flags = modifier_flags; + dst_type->direct_type = pointer_type; + dst_type->byte_size = arch_addr_size; + pointer_type->kind = type_kind; + pointer_type->byte_size = arch_addr_size; + pointer_type->direct_type = direct_type; + } + else + { + dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); + dst_type->kind = type_kind; + dst_type->byte_size = arch_addr_size; + dst_type->direct_type = direct_type; + } + }break; + + //- rjf: PROCEDURE + case CV_LeafKind_PROCEDURE: + { + // TODO(rjf): handle call_kind & attribs + + // rjf: unpack leaf + CV_LeafProcedure *lf = (CV_LeafProcedure *)itype_leaf_first; + RDIM_Type *ret_type = p2r_type_ptr_from_itype(lf->ret_itype); + + // rjf: fill type's basics + dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); + dst_type->kind = RDI_TypeKind_Function; + dst_type->byte_size = arch_addr_size; + dst_type->direct_type = ret_type; + + // rjf: unpack arglist range + CV_RecRange *arglist_range = &tpi_leaf->leaf_ranges.ranges[lf->arg_itype-itype_first]; + if(arglist_range->hdr.kind != CV_LeafKind_ARGLIST || + arglist_range->hdr.size<2 || + arglist_range->off + arglist_range->hdr.size > tpi_leaf->data.size) + { + break; + } + U8 *arglist_first = tpi_leaf->data.str + arglist_range->off + 2; + U8 *arglist_opl = arglist_first+arglist_range->hdr.size-2; + if(arglist_first + sizeof(CV_LeafArgList) > arglist_opl) + { + break; + } + + // rjf: unpack arglist info + CV_LeafArgList *arglist = (CV_LeafArgList*)arglist_first; + CV_TypeId *arglist_itypes_base = (CV_TypeId *)(arglist+1); + U32 arglist_itypes_count = arglist->count; + + // rjf: build param type array + RDIM_Type **params = push_array(arena, RDIM_Type *, arglist_itypes_count); + for(U32 idx = 0; idx < arglist_itypes_count; idx += 1) + { + params[idx] = p2r_type_ptr_from_itype(arglist_itypes_base[idx]); + } + + // rjf: fill dst type + dst_type->count = arglist_itypes_count; + dst_type->param_types = params; + }break; + + //- rjf: MFUNCTION + case CV_LeafKind_MFUNCTION: + { + // TODO(rjf): handle call_kind & attribs + // TODO(rjf): preserve "this_adjust" + + // rjf: unpack leaf + CV_LeafMFunction *lf = (CV_LeafMFunction *)itype_leaf_first; + RDIM_Type *ret_type = p2r_type_ptr_from_itype(lf->ret_itype); + + // rjf: fill type + dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); + dst_type->kind = (lf->this_itype != 0) ? RDI_TypeKind_Method : RDI_TypeKind_Function; + dst_type->byte_size = arch_addr_size; + dst_type->direct_type = ret_type; + + // rjf: unpack arglist range + CV_RecRange *arglist_range = &tpi_leaf->leaf_ranges.ranges[lf->arg_itype-itype_first]; + if(arglist_range->hdr.kind != CV_LeafKind_ARGLIST || + arglist_range->hdr.size<2 || + arglist_range->off + arglist_range->hdr.size > tpi_leaf->data.size) + { + break; + } + U8 *arglist_first = tpi_leaf->data.str + arglist_range->off + 2; + U8 *arglist_opl = arglist_first+arglist_range->hdr.size-2; + if(arglist_first + sizeof(CV_LeafArgList) > arglist_opl) + { + break; + } + + // rjf: unpack arglist info + CV_LeafArgList *arglist = (CV_LeafArgList*)arglist_first; + CV_TypeId *arglist_itypes_base = (CV_TypeId *)(arglist+1); + U32 arglist_itypes_count = arglist->count; + + // rjf: build param type array + RDIM_Type **params = push_array(arena, RDIM_Type *, arglist_itypes_count+1); + for(U32 idx = 0; idx < arglist_itypes_count; idx += 1) + { + params[idx+1] = p2r_type_ptr_from_itype(arglist_itypes_base[idx]); + } + params[0] = p2r_type_ptr_from_itype(lf->this_itype); + + // rjf: fill dst type + dst_type->count = arglist_itypes_count+1; + dst_type->param_types = params; + }break; + + //- rjf: BITFIELD + case CV_LeafKind_BITFIELD: + { + // rjf: unpack leaf + CV_LeafBitField *lf = (CV_LeafBitField *)itype_leaf_first; + RDIM_Type *direct_type = p2r_type_ptr_from_itype(lf->itype); + + // rjf: fill type + dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); + dst_type->kind = RDI_TypeKind_Bitfield; + dst_type->off = lf->pos; + dst_type->count = lf->len; + dst_type->byte_size = direct_type?direct_type->byte_size:0; + dst_type->direct_type = direct_type; + }break; + + //- rjf: ARRAY + case CV_LeafKind_ARRAY: + { + // rjf: unpack leaf + CV_LeafArray *lf = (CV_LeafArray *)itype_leaf_first; + RDIM_Type *direct_type = p2r_type_ptr_from_itype(lf->entry_itype); + U8 *numeric_ptr = (U8*)(lf + 1); + CV_NumericParsed array_count = cv_numeric_from_data_range(numeric_ptr, itype_leaf_opl); + U64 full_size = cv_u64_from_numeric(&array_count); + + // rjf: fill type + dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); + dst_type->kind = RDI_TypeKind_Array; + dst_type->direct_type = direct_type; + dst_type->byte_size = full_size; + dst_type->count = (direct_type && direct_type->byte_size) ? (dst_type->byte_size/direct_type->byte_size) : 0; + }break; + + //- rjf: CLASS/STRUCTURE + case CV_LeafKind_CLASS: + case CV_LeafKind_STRUCTURE: + { + // TODO(rjf): handle props + + // rjf: unpack leaf + CV_LeafStruct *lf = (CV_LeafStruct *)itype_leaf_first; + U8 *numeric_ptr = (U8*)(lf + 1); + CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, itype_leaf_opl); + U64 size_u64 = cv_u64_from_numeric(&size); + U8 *name_ptr = numeric_ptr + size.encoded_size; + String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); + + // rjf: fill type + dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); + if(lf->props & CV_TypeProp_FwdRef) + { + dst_type->kind = (kind == CV_LeafKind_CLASS ? RDI_TypeKind_IncompleteClass : RDI_TypeKind_IncompleteStruct); + dst_type->name = name; + } + else + { + dst_type->kind = (kind == CV_LeafKind_CLASS ? RDI_TypeKind_Class : RDI_TypeKind_Struct); + dst_type->byte_size = (U32)size_u64; + dst_type->name = name; + } + }break; + + //- rjf: CLASS2/STRUCT2 + case CV_LeafKind_CLASS2: + case CV_LeafKind_STRUCT2: + { + // TODO(rjf): handle props + + // rjf: unpack leaf + CV_LeafStruct2 *lf = (CV_LeafStruct2 *)itype_leaf_first; + U8 *numeric_ptr = (U8*)(lf + 1); + CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, itype_leaf_opl); + U64 size_u64 = cv_u64_from_numeric(&size); + U8 *name_ptr = numeric_ptr + size.encoded_size; + String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); + + // rjf: fill type + dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); + if(lf->props & CV_TypeProp_FwdRef) + { + dst_type->kind = (kind == CV_LeafKind_CLASS2 ? RDI_TypeKind_IncompleteClass : RDI_TypeKind_IncompleteStruct); + dst_type->name = name; + } + else + { + dst_type->kind = (kind == CV_LeafKind_CLASS2 ? RDI_TypeKind_Class : RDI_TypeKind_Struct); + dst_type->byte_size = (U32)size_u64; + dst_type->name = name; + } + }break; + + //- rjf: UNION + case CV_LeafKind_UNION: + { + // TODO(rjf): handle props + + // rjf: unpack leaf + CV_LeafUnion *lf = (CV_LeafUnion *)itype_leaf_first; + U8 *numeric_ptr = (U8*)(lf + 1); + CV_NumericParsed size = cv_numeric_from_data_range(numeric_ptr, itype_leaf_opl); + U64 size_u64 = cv_u64_from_numeric(&size); + U8 *name_ptr = numeric_ptr + size.encoded_size; + String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); + + // rjf: fill type + dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); + if(lf->props & CV_TypeProp_FwdRef) + { + dst_type->kind = RDI_TypeKind_IncompleteUnion; + dst_type->name = name; + } + else + { + dst_type->kind = RDI_TypeKind_Union; + dst_type->byte_size = (U32)size_u64; + dst_type->name = name; + } + }break; + + //- rjf: ENUM + case CV_LeafKind_ENUM: + { + // TODO(rjf): handle props + + // rjf: unpack leaf + CV_LeafEnum *lf = (CV_LeafEnum *)itype_leaf_first; + RDIM_Type *direct_type = p2r_type_ptr_from_itype(lf->base_itype); + U8 *name_ptr = (U8 *)(lf + 1); + String8 name = str8_cstring_capped(name_ptr, itype_leaf_opl); + + // rjf: fill type + dst_type = rdim_type_chunk_list_push(arena, &all_types, (U64)itype_opl); + if(lf->props & CV_TypeProp_FwdRef) + { + dst_type->kind = RDI_TypeKind_IncompleteEnum; + dst_type->name = name; + } + else + { + dst_type->kind = RDI_TypeKind_Enum; + dst_type->direct_type = direct_type; + dst_type->byte_size = direct_type ? direct_type->byte_size : 0; + dst_type->name = name; + } + }break; + } + } + + //- rjf: store finalized type to this itype's slot + itype_type_ptrs[itype] = dst_type; + } + } + } + } + + ////////////////////////////////////////////////////////////// + //- rjf: types pass 4: kick off UDT build + // + U64 udt_task_size_itypes = 4096; + U64 udt_tasks_count = ((U64)itype_opl+(udt_task_size_itypes-1))/udt_task_size_itypes; + P2R_UDTConvertIn *udt_tasks_inputs = push_array(scratch.arena, P2R_UDTConvertIn, udt_tasks_count); + TS_Ticket *udt_tasks_tickets = push_array(scratch.arena, TS_Ticket, udt_tasks_count); + if(in->flags & P2R_ConvertFlag_UDTs) ProfScope("types pass 4: kick off UDT build") + { + for(U64 idx = 0; idx < udt_tasks_count; idx += 1) + { + udt_tasks_inputs[idx].tpi_leaf = tpi_leaf; + udt_tasks_inputs[idx].itype_first = idx*udt_task_size_itypes; + udt_tasks_inputs[idx].itype_opl = udt_tasks_inputs[idx].itype_first + udt_task_size_itypes; + udt_tasks_inputs[idx].itype_opl = ClampTop(udt_tasks_inputs[idx].itype_opl, itype_opl); + udt_tasks_inputs[idx].itype_fwd_map = itype_fwd_map; + udt_tasks_inputs[idx].itype_type_ptrs = itype_type_ptrs; + udt_tasks_tickets[idx] = ts_kickoff(p2r_udt_convert_task__entry_point, 0, &udt_tasks_inputs[idx]); + } + } + + ////////////////////////////////////////////////////////////// + //- rjf: join link name map building task + // + P2R_LinkNameMap *link_name_map = 0; + ProfScope("join link name map building task") + { + ts_join(link_name_map_ticket, max_U64); + link_name_map = &link_name_map__in_progress; + } + + ////////////////////////////////////////////////////////////// + //- rjf: join unit conversion & src file & line table tasks + // + RDIM_UnitChunkList all_units = {0}; + RDIM_SrcFileChunkList all_src_files = {0}; + RDIM_LineTableChunkList all_line_tables = {0}; + RDIM_LineTable **units_first_inline_site_line_tables = 0; + ProfScope("join unit conversion & src file tasks") + { + P2R_UnitConvertOut *out = ts_join_struct(unit_convert_ticket, max_U64, P2R_UnitConvertOut); + all_units = out->units; + all_src_files = out->src_files; + all_line_tables = out->line_tables; + units_first_inline_site_line_tables = out->units_first_inline_site_line_tables; + } + + ////////////////////////////////////////////////////////////// + //- rjf: produce symbols from all streams + // + RDIM_SymbolChunkList all_procedures = {0}; + RDIM_SymbolChunkList all_global_variables = {0}; + RDIM_SymbolChunkList all_thread_variables = {0}; + RDIM_ScopeChunkList all_scopes = {0}; + RDIM_InlineSiteChunkList all_inline_sites = {0}; + ProfScope("produce symbols from all streams") + { + //////////////////////////// + //- rjf: kick off all symbol conversion tasks + // + U64 global_stream_subdivision_tasks_count = sym ? (sym->sym_ranges.count+16383)/16384 : 0; + U64 global_stream_syms_per_task = sym ? sym->sym_ranges.count/global_stream_subdivision_tasks_count : 0; + U64 tasks_count = comp_unit_count + global_stream_subdivision_tasks_count; + P2R_SymbolStreamConvertIn *tasks_inputs = push_array(scratch.arena, P2R_SymbolStreamConvertIn, tasks_count); + TS_Ticket *tasks_tickets = push_array(scratch.arena, TS_Ticket, tasks_count); + ProfScope("kick off all symbol conversion tasks") + { + for(U64 idx = 0; idx < tasks_count; idx += 1) + { + tasks_inputs[idx].arch = arch; + tasks_inputs[idx].coff_sections = coff_sections; + tasks_inputs[idx].tpi_hash = tpi_hash; + tasks_inputs[idx].tpi_leaf = tpi_leaf; + tasks_inputs[idx].ipi_leaf = ipi_leaf; + tasks_inputs[idx].itype_fwd_map = itype_fwd_map; + tasks_inputs[idx].itype_type_ptrs = itype_type_ptrs; + tasks_inputs[idx].link_name_map = link_name_map; + if(idx < global_stream_subdivision_tasks_count) + { + tasks_inputs[idx].sym = sym; + tasks_inputs[idx].sym_ranges_first= idx*global_stream_syms_per_task; + tasks_inputs[idx].sym_ranges_opl = tasks_inputs[idx].sym_ranges_first + global_stream_syms_per_task; + tasks_inputs[idx].sym_ranges_opl = ClampTop(tasks_inputs[idx].sym_ranges_opl, sym->sym_ranges.count); + } + else + { + tasks_inputs[idx].sym = sym_for_unit[idx-global_stream_subdivision_tasks_count]; + tasks_inputs[idx].sym_ranges_first= 0; + tasks_inputs[idx].sym_ranges_opl = sym_for_unit[idx-global_stream_subdivision_tasks_count]->sym_ranges.count; + tasks_inputs[idx].first_inline_site_line_table = units_first_inline_site_line_tables[idx-global_stream_subdivision_tasks_count]; + } + tasks_tickets[idx] = ts_kickoff(p2r_symbol_stream_convert_task__entry_point, 0, &tasks_inputs[idx]); + } + } + + //////////////////////////// + //- rjf: join tasks, merge with top-level collections + // + ProfScope("join tasks, merge with top-level collections") + { + for(U64 idx = 0; idx < tasks_count; idx += 1) + { + P2R_SymbolStreamConvertOut *out = ts_join_struct(tasks_tickets[idx], max_U64, P2R_SymbolStreamConvertOut); + rdim_symbol_chunk_list_concat_in_place(&all_procedures, &out->procedures); + rdim_symbol_chunk_list_concat_in_place(&all_global_variables, &out->global_variables); + rdim_symbol_chunk_list_concat_in_place(&all_thread_variables, &out->thread_variables); + rdim_scope_chunk_list_concat_in_place(&all_scopes, &out->scopes); + rdim_inline_site_chunk_list_concat_in_place(&all_inline_sites,&out->inline_sites); + } + } + } + + ////////////////////////////////////////////////////////////// + //- rjf: types pass 5: join UDT build tasks + // + RDIM_UDTChunkList all_udts = {0}; + for(U64 idx = 0; idx < udt_tasks_count; idx += 1) + { + RDIM_UDTChunkList *udts = ts_join_struct(udt_tasks_tickets[idx], max_U64, RDIM_UDTChunkList); + rdim_udt_chunk_list_concat_in_place(&all_udts, udts); + } + + ////////////////////////////////////////////////////////////// + //- rjf: fill output + // + P2R_Convert2Bake *out = push_array(arena, P2R_Convert2Bake, 1); + { + out->bake_params.top_level_info = top_level_info; + out->bake_params.binary_sections = binary_sections; + out->bake_params.units = all_units; + out->bake_params.types = all_types; + out->bake_params.udts = all_udts; + out->bake_params.src_files = all_src_files; + out->bake_params.line_tables = all_line_tables; + out->bake_params.global_variables = all_global_variables; + out->bake_params.thread_variables = all_thread_variables; + out->bake_params.procedures = all_procedures; + out->bake_params.scopes = all_scopes; + out->bake_params.inline_sites = all_inline_sites; + } + + scratch_end(scratch); + return out; +} + +//////////////////////////////// +//~ rjf: Baking Stage Tasks + +//- rjf: bake string map building + +#define p2r_make_string_map_if_needed() do {if(in->maps[thread_idx] == 0) ProfScope("make map") {in->maps[thread_idx] = rdim_bake_string_map_loose_make(arena, in->top);}} while(0) + +internal TS_TASK_FUNCTION_DEF(p2r_bake_src_files_strings_task__entry_point) +{ + P2R_BakeSrcFilesStringsIn *in = (P2R_BakeSrcFilesStringsIn *)p; + p2r_make_string_map_if_needed(); + ProfScope("bake src file strings") rdim_bake_string_map_loose_push_src_files(arena, in->top, in->maps[thread_idx], in->list); + return 0; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_units_strings_task__entry_point) +{ + P2R_BakeUnitsStringsIn *in = (P2R_BakeUnitsStringsIn *)p; + p2r_make_string_map_if_needed(); + ProfScope("bake unit strings") rdim_bake_string_map_loose_push_units(arena, in->top, in->maps[thread_idx], in->list); + return 0; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_types_strings_task__entry_point) +{ + P2R_BakeTypesStringsIn *in = (P2R_BakeTypesStringsIn *)p; + p2r_make_string_map_if_needed(); + ProfScope("bake type strings") + { + for(P2R_BakeTypesStringsInNode *n = in->first; n != 0; n = n->next) + { + rdim_bake_string_map_loose_push_type_slice(arena, in->top, in->maps[thread_idx], n->v, n->count); + } + } + return 0; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_udts_strings_task__entry_point) +{ + P2R_BakeUDTsStringsIn *in = (P2R_BakeUDTsStringsIn *)p; + p2r_make_string_map_if_needed(); + ProfScope("bake udt strings") + { + for(P2R_BakeUDTsStringsInNode *n = in->first; n != 0; n = n->next) + { + rdim_bake_string_map_loose_push_udt_slice(arena, in->top, in->maps[thread_idx], n->v, n->count); + } + } + return 0; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_symbols_strings_task__entry_point) +{ + P2R_BakeSymbolsStringsIn *in = (P2R_BakeSymbolsStringsIn *)p; + p2r_make_string_map_if_needed(); + ProfScope("bake symbol strings") + { + for(P2R_BakeSymbolsStringsInNode *n = in->first; n != 0; n = n->next) + { + rdim_bake_string_map_loose_push_symbol_slice(arena, in->top, in->maps[thread_idx], n->v, n->count); + } + } + return 0; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_scopes_strings_task__entry_point) +{ + P2R_BakeScopesStringsIn *in = (P2R_BakeScopesStringsIn *)p; + p2r_make_string_map_if_needed(); + ProfScope("bake scope strings") + { + for(P2R_BakeScopesStringsInNode *n = in->first; n != 0; n = n->next) + { + rdim_bake_string_map_loose_push_scope_slice(arena, in->top, in->maps[thread_idx], n->v, n->count); + } + } + return 0; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_line_tables_task__entry_point) +{ + P2R_BakeLineTablesIn *in = (P2R_BakeLineTablesIn *)p; + RDIM_LineTableBakeResult *out = push_array(arena, RDIM_LineTableBakeResult, 1); + ProfScope("bake line tables") *out = rdim_bake_line_tables(arena, in->line_tables); + return out; +} + +#undef p2r_make_string_map_if_needed + +//- rjf: bake string map joining + +internal TS_TASK_FUNCTION_DEF(p2r_bake_string_map_join_task__entry_point) +{ + P2R_JoinBakeStringMapSlotsIn *in = (P2R_JoinBakeStringMapSlotsIn *)p; + ProfScope("join bake string maps") + { + for(U64 src_map_idx = 0; src_map_idx < in->src_maps_count; src_map_idx += 1) + { + for(U64 slot_idx = in->slot_idx_range.min; slot_idx < in->slot_idx_range.max; slot_idx += 1) + { + B32 src_slots_good = (in->src_maps[src_map_idx] != 0 && in->src_maps[src_map_idx]->slots != 0); + B32 dst_slot_is_zero = (in->dst_map->slots[slot_idx] == 0); + if(src_slots_good && dst_slot_is_zero) + { + in->dst_map->slots[slot_idx] = in->src_maps[src_map_idx]->slots[slot_idx]; + } + else if(src_slots_good && in->src_maps[src_map_idx]->slots[slot_idx] != 0) + { + rdim_bake_string_chunk_list_concat_in_place(in->dst_map->slots[slot_idx], in->src_maps[src_map_idx]->slots[slot_idx]); + } + } + } + } + return 0; +} + +//- rjf: bake string map sorting + +internal TS_TASK_FUNCTION_DEF(p2r_bake_string_map_sort_task__entry_point) +{ + P2R_SortBakeStringMapSlotsIn *in = (P2R_SortBakeStringMapSlotsIn *)p; + ProfScope("sort bake string chunk list map range") + { + for(U64 slot_idx = in->slot_idx; + slot_idx < in->slot_idx+in->slot_count; + slot_idx += 1) + { + if(in->src_map->slots[slot_idx] != 0) + { + if(in->src_map->slots[slot_idx]->total_count > 1) + { + in->dst_map->slots[slot_idx] = push_array(arena, RDIM_BakeStringChunkList, 1); + *in->dst_map->slots[slot_idx] = rdim_bake_string_chunk_list_sorted_from_unsorted(arena, in->src_map->slots[slot_idx]); + } + else + { + in->dst_map->slots[slot_idx] = in->src_map->slots[slot_idx]; + } + } + } + } + return 0; +} + +//- rjf: pass 1: interner/deduper map builds + +internal TS_TASK_FUNCTION_DEF(p2r_build_bake_name_map_task__entry_point) +{ + P2R_BuildBakeNameMapIn *in = (P2R_BuildBakeNameMapIn *)p; + RDIM_BakeNameMap *name_map = 0; + ProfScope("build name map %i", in->k) name_map = rdim_bake_name_map_from_kind_params(arena, in->k, in->params); + return name_map; +} + +//- rjf: pass 2: string-map-dependent debug info stream builds + +internal TS_TASK_FUNCTION_DEF(p2r_bake_units_task__entry_point) +{ + P2R_BakeUnitsIn *in = (P2R_BakeUnitsIn *)p; + RDIM_UnitBakeResult *out = push_array(arena, RDIM_UnitBakeResult, 1); + ProfScope("bake units") *out = rdim_bake_units(arena, in->strings, in->path_tree, in->units); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_unit_vmap_task__entry_point) +{ + P2R_BakeUnitVMapIn *in = (P2R_BakeUnitVMapIn *)p; + RDIM_UnitVMapBakeResult *out = push_array(arena, RDIM_UnitVMapBakeResult, 1); + ProfScope("bake unit vmap") *out = rdim_bake_unit_vmap(arena, in->units); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_src_files_task__entry_point) +{ + P2R_BakeSrcFilesIn *in = (P2R_BakeSrcFilesIn *)p; + RDIM_SrcFileBakeResult *out = push_array(arena, RDIM_SrcFileBakeResult, 1); + ProfScope("bake src files") *out = rdim_bake_src_files(arena, in->strings, in->path_tree, in->src_files); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_udts_task__entry_point) +{ + P2R_BakeUDTsIn *in = (P2R_BakeUDTsIn *)p; + RDIM_UDTBakeResult *out = push_array(arena, RDIM_UDTBakeResult, 1); + ProfScope("bake udts") *out = rdim_bake_udts(arena, in->strings, in->udts); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_global_variables_task__entry_point) +{ + P2R_BakeGlobalVariablesIn *in = (P2R_BakeGlobalVariablesIn *)p; + RDIM_GlobalVariableBakeResult *out = push_array(arena, RDIM_GlobalVariableBakeResult, 1); + ProfScope("bake global variables") *out = rdim_bake_global_variables(arena, in->strings, in->global_variables); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_global_vmap_task__entry_point) +{ + P2R_BakeGlobalVMapIn *in = (P2R_BakeGlobalVMapIn *)p; + RDIM_GlobalVMapBakeResult *out = push_array(arena, RDIM_GlobalVMapBakeResult, 1); + ProfScope("bake global vmap") *out = rdim_bake_global_vmap(arena, in->global_variables); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_thread_variables_task__entry_point) +{ + P2R_BakeThreadVariablesIn *in = (P2R_BakeThreadVariablesIn *)p; + RDIM_ThreadVariableBakeResult *out = push_array(arena, RDIM_ThreadVariableBakeResult, 1); + ProfScope("bake thread variables") *out = rdim_bake_thread_variables(arena, in->strings, in->thread_variables); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_procedures_task__entry_point) +{ + P2R_BakeProceduresIn *in = (P2R_BakeProceduresIn *)p; + RDIM_ProcedureBakeResult *out = push_array(arena, RDIM_ProcedureBakeResult, 1); + ProfScope("bake procedures") *out = rdim_bake_procedures(arena, in->strings, in->procedures); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_scopes_task__entry_point) +{ + P2R_BakeScopesIn *in = (P2R_BakeScopesIn *)p; + RDIM_ScopeBakeResult *out = push_array(arena, RDIM_ScopeBakeResult, 1); + ProfScope("bake scopes") *out = rdim_bake_scopes(arena, in->strings, in->scopes); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_scope_vmap_task__entry_point) +{ + P2R_BakeScopeVMapIn *in = (P2R_BakeScopeVMapIn *)p; + RDIM_ScopeVMapBakeResult *out = push_array(arena, RDIM_ScopeVMapBakeResult, 1); + ProfScope("bake scope vmap") *out = rdim_bake_scope_vmap(arena, in->scopes); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_inline_sites_task__entry_point) +{ + P2R_BakeInlineSitesIn *in = (P2R_BakeInlineSitesIn *)p; + RDIM_InlineSiteBakeResult *out = push_array(arena, RDIM_InlineSiteBakeResult, 1); + ProfScope("bake inline sites") *out = rdim_bake_inline_sites(arena, in->strings, in->inline_sites); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_file_paths_task__entry_point) +{ + P2R_BakeFilePathsIn *in = (P2R_BakeFilePathsIn *)p; + RDIM_FilePathBakeResult *out = push_array(arena, RDIM_FilePathBakeResult, 1); + ProfScope("bake file paths") *out = rdim_bake_file_paths(arena, in->strings, in->path_tree); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_strings_task__entry_point) +{ + P2R_BakeStringsIn *in = (P2R_BakeStringsIn *)p; + RDIM_StringBakeResult *out = push_array(arena, RDIM_StringBakeResult, 1); + ProfScope("bake strings") *out = rdim_bake_strings(arena, in->strings); + return out; +} + +//- rjf: pass 3: idx-run-map-dependent debug info stream builds + +internal TS_TASK_FUNCTION_DEF(p2r_bake_type_nodes_task__entry_point) +{ + P2R_BakeTypeNodesIn *in = (P2R_BakeTypeNodesIn *)p; + RDIM_TypeNodeBakeResult *out = push_array(arena, RDIM_TypeNodeBakeResult, 1); + ProfScope("bake type nodes") *out = rdim_bake_types(arena, in->strings, in->idx_runs, in->types); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_name_map_task__entry_point) +{ + P2R_BakeNameMapIn *in = (P2R_BakeNameMapIn *)p; + RDIM_NameMapBakeResult *out = push_array(arena, RDIM_NameMapBakeResult, 1); + ProfScope("bake name map %i", in->kind) *out = rdim_bake_name_map(arena, in->strings, in->idx_runs, in->map); + return out; +} + +internal TS_TASK_FUNCTION_DEF(p2r_bake_idx_runs_task__entry_point) +{ + P2R_BakeIdxRunsIn *in = (P2R_BakeIdxRunsIn *)p; + RDIM_IndexRunBakeResult *out = push_array(arena, RDIM_IndexRunBakeResult, 1); + ProfScope("bake idx runs") *out = rdim_bake_index_runs(arena, in->idx_runs); + return out; +} + +//////////////////////////////// +//~ rjf: Top-Level Baking Entry Point + +internal P2R_Bake2Serialize * +p2r_bake(Arena *arena, P2R_Convert2Bake *in) +{ + Temp scratch = scratch_begin(&arena, 1); + RDIM_BakeParams *in_params = &in->bake_params; + P2R_Bake2Serialize *out = push_array(arena, P2R_Bake2Serialize, 1); + RDIM_BakeResults *out_results = &out->bake_results; + + ////////////////////////////// + //- rjf: kick off line tables baking + // + TS_Ticket bake_line_tables_ticket = {0}; + { + P2R_BakeLineTablesIn *in = push_array(scratch.arena, P2R_BakeLineTablesIn, 1); + in->line_tables = &in_params->line_tables; + bake_line_tables_ticket = ts_kickoff(p2r_bake_line_tables_task__entry_point, 0, in); + } + + ////////////////////////////// + //- rjf: build interned path tree + // + RDIM_BakePathTree *path_tree = 0; + ProfScope("build interned path tree") + { + path_tree = rdim_bake_path_tree_from_params(arena, in_params); + } + + ////////////////////////////// + //- rjf: kick off string map building tasks + // + RDIM_BakeStringMapTopology bake_string_map_topology = {(in_params->procedures.total_count*1 + + in_params->global_variables.total_count*1 + + in_params->thread_variables.total_count*1 + + in_params->types.total_count/2)}; + RDIM_BakeStringMapLoose **bake_string_maps__in_progress = push_array(scratch.arena, RDIM_BakeStringMapLoose *, ts_thread_count()); + TS_TicketList bake_string_map_build_tickets = {0}; + { + // rjf: src files + ProfScope("kick off src files string map build task") + { + P2R_BakeSrcFilesStringsIn *in = push_array(scratch.arena, P2R_BakeSrcFilesStringsIn, 1); + in->top = &bake_string_map_topology; + in->maps = bake_string_maps__in_progress; + in->list = &in_params->src_files; + ts_ticket_list_push(scratch.arena, &bake_string_map_build_tickets, ts_kickoff(p2r_bake_src_files_strings_task__entry_point, 0, in)); + } + + // rjf: units + ProfScope("kick off units string map build task") + { + P2R_BakeUnitsStringsIn *in = push_array(scratch.arena, P2R_BakeUnitsStringsIn, 1); + in->top = &bake_string_map_topology; + in->maps = bake_string_maps__in_progress; + in->list = &in_params->units; + ts_ticket_list_push(scratch.arena, &bake_string_map_build_tickets, ts_kickoff(p2r_bake_units_strings_task__entry_point, 0, in)); + } + + // rjf: types + ProfScope("kick off types string map build tasks") + { + U64 items_per_task = 4096; + U64 num_tasks = (in_params->types.total_count+items_per_task-1)/items_per_task; + RDIM_TypeChunkNode *chunk = in_params->types.first; + U64 chunk_off = 0; + for(U64 task_idx = 0; task_idx < num_tasks; task_idx += 1) + { + P2R_BakeTypesStringsIn *in = push_array(scratch.arena, P2R_BakeTypesStringsIn, 1); + in->top = &bake_string_map_topology; + in->maps = bake_string_maps__in_progress; + U64 items_left = items_per_task; + for(;chunk != 0 && items_left > 0;) + { + U64 items_in_this_chunk = Min(items_per_task, chunk->count-chunk_off); + P2R_BakeTypesStringsInNode *n = push_array(scratch.arena, P2R_BakeTypesStringsInNode, 1); + SLLQueuePush(in->first, in->last, n); + n->v = chunk->v + chunk_off; + n->count = items_in_this_chunk; + chunk_off += items_in_this_chunk; + items_left -= items_in_this_chunk; + if(chunk_off >= chunk->count) + { + chunk = chunk->next; + chunk_off = 0; + } + } + ts_ticket_list_push(scratch.arena, &bake_string_map_build_tickets, ts_kickoff(p2r_bake_types_strings_task__entry_point, 0, in)); + } + } + + // rjf: UDTs + ProfScope("kick off udts string map build tasks") + { + U64 items_per_task = 4096; + U64 num_tasks = (in_params->udts.total_count+items_per_task-1)/items_per_task; + RDIM_UDTChunkNode *chunk = in_params->udts.first; + U64 chunk_off = 0; + for(U64 task_idx = 0; task_idx < num_tasks; task_idx += 1) + { + P2R_BakeUDTsStringsIn *in = push_array(scratch.arena, P2R_BakeUDTsStringsIn, 1); + in->top = &bake_string_map_topology; + in->maps = bake_string_maps__in_progress; + U64 items_left = items_per_task; + for(;chunk != 0 && items_left > 0;) + { + U64 items_in_this_chunk = Min(items_per_task, chunk->count-chunk_off); + P2R_BakeUDTsStringsInNode *n = push_array(scratch.arena, P2R_BakeUDTsStringsInNode, 1); + SLLQueuePush(in->first, in->last, n); + n->v = chunk->v + chunk_off; + n->count = items_in_this_chunk; + chunk_off += items_in_this_chunk; + items_left -= items_in_this_chunk; + if(chunk_off >= chunk->count) + { + chunk = chunk->next; + chunk_off = 0; + } + } + ts_ticket_list_push(scratch.arena, &bake_string_map_build_tickets, ts_kickoff(p2r_bake_udts_strings_task__entry_point, 0, in)); + } + } + + // rjf: symbols + ProfScope("kick off symbols string map build tasks") + { + RDIM_SymbolChunkList *symbol_lists[] = + { + &in_params->global_variables, + &in_params->thread_variables, + &in_params->procedures, + }; + for(U64 list_idx = 0; list_idx < ArrayCount(symbol_lists); list_idx += 1) + { + U64 items_per_task = 4096; + U64 num_tasks = (symbol_lists[list_idx]->total_count+items_per_task-1)/items_per_task; + RDIM_SymbolChunkNode *chunk = symbol_lists[list_idx]->first; + U64 chunk_off = 0; + for(U64 task_idx = 0; task_idx < num_tasks; task_idx += 1) + { + P2R_BakeSymbolsStringsIn *in = push_array(scratch.arena, P2R_BakeSymbolsStringsIn, 1); + in->top = &bake_string_map_topology; + in->maps = bake_string_maps__in_progress; + U64 items_left = items_per_task; + for(;chunk != 0 && items_left > 0;) + { + U64 items_in_this_chunk = Min(items_per_task, chunk->count-chunk_off); + P2R_BakeSymbolsStringsInNode *n = push_array(scratch.arena, P2R_BakeSymbolsStringsInNode, 1); + SLLQueuePush(in->first, in->last, n); + n->v = chunk->v + chunk_off; + n->count = items_in_this_chunk; + chunk_off += items_in_this_chunk; + items_left -= items_in_this_chunk; + if(chunk_off >= chunk->count) + { + chunk = chunk->next; + chunk_off = 0; + } + } + ts_ticket_list_push(scratch.arena, &bake_string_map_build_tickets, ts_kickoff(p2r_bake_symbols_strings_task__entry_point, 0, in)); + } + } + } + + // rjf: scope chunks + ProfScope("kick off scope chunks string map build tasks") + { + U64 items_per_task = 4096; + U64 num_tasks = (in_params->scopes.total_count+items_per_task-1)/items_per_task; + RDIM_ScopeChunkNode *chunk = in_params->scopes.first; + U64 chunk_off = 0; + for(U64 task_idx = 0; task_idx < num_tasks; task_idx += 1) + { + P2R_BakeScopesStringsIn *in = push_array(scratch.arena, P2R_BakeScopesStringsIn, 1); + in->top = &bake_string_map_topology; + in->maps = bake_string_maps__in_progress; + U64 items_left = items_per_task; + for(;chunk != 0 && items_left > 0;) + { + U64 items_in_this_chunk = Min(items_per_task, chunk->count-chunk_off); + P2R_BakeScopesStringsInNode *n = push_array(scratch.arena, P2R_BakeScopesStringsInNode, 1); + SLLQueuePush(in->first, in->last, n); + n->v = chunk->v + chunk_off; + n->count = items_in_this_chunk; + chunk_off += items_in_this_chunk; + items_left -= items_in_this_chunk; + if(chunk_off >= chunk->count) + { + chunk = chunk->next; + chunk_off = 0; + } + } + ts_ticket_list_push(scratch.arena, &bake_string_map_build_tickets, ts_kickoff(p2r_bake_scopes_strings_task__entry_point, 0, in)); + } + } + } + + ////////////////////////////// + //- rjf: kick off name map building tasks + // + P2R_BuildBakeNameMapIn build_bake_name_map_in[RDI_NameMapKind_COUNT] = {0}; + TS_Ticket build_bake_name_map_ticket[RDI_NameMapKind_COUNT] = {0}; + for(RDI_NameMapKind k = (RDI_NameMapKind)(RDI_NameMapKind_NULL+1); + k < RDI_NameMapKind_COUNT; + k = (RDI_NameMapKind)(k+1)) + { + build_bake_name_map_in[k].k = k; + build_bake_name_map_in[k].params = in_params; + build_bake_name_map_ticket[k] = ts_kickoff(p2r_build_bake_name_map_task__entry_point, 0, &build_bake_name_map_in[k]); + } + + ////////////////////////////// + //- rjf: join string map building tasks + // + ProfScope("join string map building tasks") + { + for(TS_TicketNode *n = bake_string_map_build_tickets.first; n != 0; n = n->next) + { + ts_join(n->v, max_U64); + } + } + + ////////////////////////////// + //- rjf: produce joined string map + // + RDIM_BakeStringMapLoose *unsorted_bake_string_map = rdim_bake_string_map_loose_make(arena, &bake_string_map_topology); + ProfScope("produce joined string map") + { + U64 slots_per_task = 16384; + U64 num_tasks = (bake_string_map_topology.slots_count+slots_per_task-1)/slots_per_task; + TS_Ticket *task_tickets = push_array(scratch.arena, TS_Ticket, num_tasks); + + // rjf: kickoff tasks + for(U64 task_idx = 0; task_idx < num_tasks; task_idx += 1) + { + P2R_JoinBakeStringMapSlotsIn *in = push_array(scratch.arena, P2R_JoinBakeStringMapSlotsIn, 1); + in->top = &bake_string_map_topology; + in->src_maps = bake_string_maps__in_progress; + in->src_maps_count = ts_thread_count(); + in->dst_map = unsorted_bake_string_map; + in->slot_idx_range = r1u64(task_idx*slots_per_task, task_idx*slots_per_task + slots_per_task); + in->slot_idx_range.max = Min(in->slot_idx_range.max, in->top->slots_count); + task_tickets[task_idx] = ts_kickoff(p2r_bake_string_map_join_task__entry_point, 0, in); + } + + // rjf: join tasks + for(U64 task_idx = 0; task_idx < num_tasks; task_idx += 1) + { + ts_join(task_tickets[task_idx], max_U64); + } + + // rjf: insert small top-level stuff + rdim_bake_string_map_loose_push_top_level_info(arena, &bake_string_map_topology, unsorted_bake_string_map, &in_params->top_level_info); + rdim_bake_string_map_loose_push_binary_sections(arena, &bake_string_map_topology, unsorted_bake_string_map, &in_params->binary_sections); + rdim_bake_string_map_loose_push_path_tree(arena, &bake_string_map_topology, unsorted_bake_string_map, path_tree); + } + + ////////////////////////////// + //- rjf: kick off string map sorting tasks + // + TS_TicketList sort_bake_string_map_task_tickets = {0}; + RDIM_BakeStringMapLoose *sorted_bake_string_map__in_progress = rdim_bake_string_map_loose_make(arena, &bake_string_map_topology); + { + U64 slots_per_task = 4096; + U64 num_tasks = (bake_string_map_topology.slots_count+slots_per_task-1)/slots_per_task; + for(U64 task_idx = 0; task_idx < num_tasks; task_idx += 1) + { + P2R_SortBakeStringMapSlotsIn *in = push_array(scratch.arena, P2R_SortBakeStringMapSlotsIn, 1); + { + in->top = &bake_string_map_topology; + in->src_map = unsorted_bake_string_map; + in->dst_map = sorted_bake_string_map__in_progress; + in->slot_idx = task_idx*slots_per_task; + in->slot_count = slots_per_task; + if(in->slot_idx+in->slot_count > bake_string_map_topology.slots_count) + { + in->slot_count = bake_string_map_topology.slots_count - in->slot_idx; + } + } + ts_ticket_list_push(scratch.arena, &sort_bake_string_map_task_tickets, ts_kickoff(p2r_bake_string_map_sort_task__entry_point, 0, in)); + } + } + + ////////////////////////////// + //- rjf: join string map sorting tasks + // + ProfScope("join string map sorting tasks") + { + for(TS_TicketNode *n = sort_bake_string_map_task_tickets.first; n != 0; n = n->next) + { + ts_join(n->v, max_U64); + } + } + RDIM_BakeStringMapLoose *sorted_bake_string_map = sorted_bake_string_map__in_progress; + + ////////////////////////////// + //- rjf: build finalized string map + // + ProfBegin("build finalized string map base indices"); + RDIM_BakeStringMapBaseIndices bake_string_map_base_idxes = rdim_bake_string_map_base_indices_from_map_loose(arena, &bake_string_map_topology, sorted_bake_string_map); + ProfEnd(); + ProfBegin("build finalized string map"); + RDIM_BakeStringMapTight bake_strings = rdim_bake_string_map_tight_from_loose(arena, &bake_string_map_topology, &bake_string_map_base_idxes, sorted_bake_string_map); + ProfEnd(); + + ////////////////////////////// + //- rjf: kick off pass 2 tasks + // + P2R_BakeUnitsIn bake_units_top_level_in = {&bake_strings, path_tree, &in_params->units}; + TS_Ticket bake_units_ticket = ts_kickoff(p2r_bake_units_task__entry_point, 0, &bake_units_top_level_in); + P2R_BakeUnitVMapIn bake_unit_vmap_in = {&in_params->units}; + TS_Ticket bake_unit_vmap_ticket = ts_kickoff(p2r_bake_unit_vmap_task__entry_point, 0, &bake_unit_vmap_in); + P2R_BakeSrcFilesIn bake_src_files_in = {&bake_strings, path_tree, &in_params->src_files}; + TS_Ticket bake_src_files_ticket = ts_kickoff(p2r_bake_src_files_task__entry_point, 0, &bake_src_files_in); + P2R_BakeUDTsIn bake_udts_in = {&bake_strings, &in_params->udts}; + TS_Ticket bake_udts_ticket = ts_kickoff(p2r_bake_udts_task__entry_point, 0, &bake_udts_in); + P2R_BakeGlobalVariablesIn bake_global_variables_in = {&bake_strings, &in_params->global_variables}; + TS_Ticket bake_global_variables_ticket = ts_kickoff(p2r_bake_global_variables_task__entry_point, 0, &bake_global_variables_in); + P2R_BakeGlobalVMapIn bake_global_vmap_in = {&in_params->global_variables}; + TS_Ticket bake_global_vmap_ticket = ts_kickoff(p2r_bake_global_vmap_task__entry_point, 0, &bake_global_vmap_in); + P2R_BakeThreadVariablesIn bake_thread_variables_in = {&bake_strings, &in_params->thread_variables}; + TS_Ticket bake_thread_variables_ticket = ts_kickoff(p2r_bake_thread_variables_task__entry_point, 0, &bake_thread_variables_in); + P2R_BakeProceduresIn bake_procedures_in = {&bake_strings, &in_params->procedures}; + TS_Ticket bake_procedures_ticket = ts_kickoff(p2r_bake_procedures_task__entry_point, 0, &bake_procedures_in); + P2R_BakeScopesIn bake_scopes_in = {&bake_strings, &in_params->scopes}; + TS_Ticket bake_scopes_ticket = ts_kickoff(p2r_bake_scopes_task__entry_point, 0, &bake_scopes_in); + P2R_BakeScopeVMapIn bake_scope_vmap_in = {&in_params->scopes}; + TS_Ticket bake_scope_vmap_ticket = ts_kickoff(p2r_bake_scope_vmap_task__entry_point, 0, &bake_scope_vmap_in); + P2R_BakeInlineSitesIn bake_inline_sites_in = {&bake_strings, &in_params->inline_sites}; + TS_Ticket bake_inline_sites_ticket = ts_kickoff(p2r_bake_inline_sites_task__entry_point, 0, &bake_inline_sites_in); + P2R_BakeFilePathsIn bake_file_paths_in = {&bake_strings, path_tree}; + TS_Ticket bake_file_paths_ticket = ts_kickoff(p2r_bake_file_paths_task__entry_point, 0, &bake_file_paths_in); + P2R_BakeStringsIn bake_strings_in = {&bake_strings}; + TS_Ticket bake_strings_ticket = ts_kickoff(p2r_bake_strings_task__entry_point, 0, &bake_strings_in); + + ////////////////////////////// + //- rjf: join name map building tasks + // + RDIM_BakeNameMap *name_maps[RDI_NameMapKind_COUNT] = {0}; + ProfScope("join name map building tasks") + { + for(RDI_NameMapKind k = (RDI_NameMapKind)(RDI_NameMapKind_NULL+1); + k < RDI_NameMapKind_COUNT; + k = (RDI_NameMapKind)(k+1)) + { + name_maps[k] = ts_join_struct(build_bake_name_map_ticket[k], max_U64, RDIM_BakeNameMap); + } + } + + ////////////////////////////// + //- rjf: build interned idx run map + // + RDIM_BakeIdxRunMap *idx_runs = 0; + ProfScope("build interned idx run map") + { + idx_runs = rdim_bake_idx_run_map_from_params(arena, name_maps, in_params); + } + + ////////////////////////////// + //- rjf: do small top-level bakes + // + ProfScope("top level info") out_results->top_level_info = rdim_bake_top_level_info(arena, &bake_strings, &in_params->top_level_info); + ProfScope("binary sections") out_results->binary_sections = rdim_bake_binary_sections(arena, &bake_strings, &in_params->binary_sections); + ProfScope("top level name maps section") out_results->top_level_name_maps = rdim_bake_name_maps_top_level(arena, &bake_strings, idx_runs, name_maps); + + ////////////////////////////// + //- rjf: kick off pass 3 tasks + // + P2R_BakeTypeNodesIn bake_type_nodes_in = {&bake_strings, idx_runs, &in_params->types}; + TS_Ticket bake_type_nodes_ticket = ts_kickoff(p2r_bake_type_nodes_task__entry_point, 0, &bake_type_nodes_in); + TS_Ticket bake_name_maps_tickets[RDI_NameMapKind_COUNT] = {0}; + { + for(EachNonZeroEnumVal(RDI_NameMapKind, k)) + { + if(name_maps[k] == 0 || name_maps[k]->name_count == 0) + { + continue; + } + P2R_BakeNameMapIn *in = push_array(scratch.arena, P2R_BakeNameMapIn, 1); + in->strings = &bake_strings; + in->idx_runs = idx_runs; + in->map = name_maps[k]; + in->kind = k; + bake_name_maps_tickets[k] = ts_kickoff(p2r_bake_name_map_task__entry_point, 0, in); + } + } + P2R_BakeIdxRunsIn bake_idx_runs_in = {idx_runs}; + TS_Ticket bake_idx_runs_ticket = ts_kickoff(p2r_bake_idx_runs_task__entry_point, 0, &bake_idx_runs_in); + + ////////////////////////////// + //- rjf: join remaining completed bakes + // + ProfScope("top-level units info") out_results->units = *ts_join_struct(bake_units_ticket, max_U64, RDIM_UnitBakeResult); + ProfScope("unit vmap") out_results->unit_vmap = *ts_join_struct(bake_unit_vmap_ticket, max_U64, RDIM_UnitVMapBakeResult); + ProfScope("source files") out_results->src_files = *ts_join_struct(bake_src_files_ticket, max_U64, RDIM_SrcFileBakeResult); + ProfScope("UDTs") out_results->udts = *ts_join_struct(bake_udts_ticket, max_U64, RDIM_UDTBakeResult); + ProfScope("global variables") out_results->global_variables = *ts_join_struct(bake_global_variables_ticket, max_U64, RDIM_GlobalVariableBakeResult); + ProfScope("global vmap") out_results->global_vmap = *ts_join_struct(bake_global_vmap_ticket, max_U64, RDIM_GlobalVMapBakeResult); + ProfScope("thread variables") out_results->thread_variables = *ts_join_struct(bake_thread_variables_ticket, max_U64, RDIM_ThreadVariableBakeResult); + ProfScope("procedures") out_results->procedures = *ts_join_struct(bake_procedures_ticket, max_U64, RDIM_ProcedureBakeResult); + ProfScope("scopes") out_results->scopes = *ts_join_struct(bake_scopes_ticket, max_U64, RDIM_ScopeBakeResult); + ProfScope("scope vmap") out_results->scope_vmap = *ts_join_struct(bake_scope_vmap_ticket, max_U64, RDIM_ScopeVMapBakeResult); + ProfScope("inline sites") out_results->inline_sites = *ts_join_struct(bake_inline_sites_ticket, max_U64, RDIM_InlineSiteBakeResult); + ProfScope("file paths") out_results->file_paths = *ts_join_struct(bake_file_paths_ticket, max_U64, RDIM_FilePathBakeResult); + ProfScope("strings") out_results->strings = *ts_join_struct(bake_strings_ticket, max_U64, RDIM_StringBakeResult); + ProfScope("type nodes") out_results->type_nodes = *ts_join_struct(bake_type_nodes_ticket, max_U64, RDIM_TypeNodeBakeResult); + ProfScope("idx runs") out_results->idx_runs = *ts_join_struct(bake_idx_runs_ticket, max_U64, RDIM_IndexRunBakeResult); + ProfScope("line tables") out_results->line_tables = *ts_join_struct(bake_line_tables_ticket, max_U64, RDIM_LineTableBakeResult); + + ////////////////////////////// + //- rjf: join individual name map bakes + // + RDIM_NameMapBakeResult name_map_bakes[RDI_NameMapKind_COUNT] = {0}; + ProfScope("name maps") + { + for(EachNonZeroEnumVal(RDI_NameMapKind, k)) + { + RDIM_NameMapBakeResult *bake = ts_join_struct(bake_name_maps_tickets[k], max_U64, RDIM_NameMapBakeResult); + if(bake != 0) + { + name_map_bakes[k] = *bake; + } + } + } + + ////////////////////////////// + //- rjf: join all individual name map bakes + // + ProfScope("join all name map bakes into final name map bake") + { + out_results->name_maps = rdim_name_map_bake_results_combine(arena, name_map_bakes, ArrayCount(name_map_bakes)); + } + + scratch_end(scratch); + return out; +} + +//////////////////////////////// +//~ rjf: Top-Level Compression Entry Point + +internal P2R_Serialize2File * +p2r_compress(Arena *arena, P2R_Serialize2File *in) +{ + P2R_Serialize2File *out = push_array(arena, P2R_Serialize2File, 1); + { + //- rjf: set up compression context + rr_lzb_simple_context ctx = {0}; + ctx.m_tableSizeBits = 14; + ctx.m_hashTable = push_array(arena, U16, 1<bundle.sections[k]; + RDIM_SerializedSection *dst = &out->bundle.sections[k]; + MemoryCopyStruct(dst, src); + + // rjf: determine if this section should be compressed + B32 should_compress = 1; + + // rjf: compress if needed + if(should_compress) + { + MemoryZero(ctx.m_hashTable, sizeof(U16)*(1<data = push_array_no_zero(arena, U8, src->encoded_size); + dst->encoded_size = rr_lzb_simple_encode_veryfast(&ctx, src->data, src->encoded_size, dst->data); + dst->unpacked_size = src->encoded_size; + dst->encoding = RDI_SectionEncoding_LZB; + } + } + } + return out; +} diff --git a/src/rdi_from_pdb/rdi_from_pdb_main.c b/src/rdi_from_pdb/rdi_from_pdb_main.c index ed757af4..898aafa7 100644 --- a/src/rdi_from_pdb/rdi_from_pdb_main.c +++ b/src/rdi_from_pdb/rdi_from_pdb_main.c @@ -1,132 +1,132 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Build Options - -#define BUILD_VERSION_MAJOR 0 -#define BUILD_VERSION_MINOR 9 -#define BUILD_VERSION_PATCH 11 -#define BUILD_RELEASE_PHASE_STRING_LITERAL "ALPHA" -#define BUILD_TITLE "rdi_from_pdb" -#define BUILD_CONSOLE_INTERFACE 1 - -//////////////////////////////// -//~ rjf: Includes - -//- rjf: [lib] -#include "lib_rdi_format/rdi_format.h" -#include "lib_rdi_format/rdi_format.c" -#include "third_party/rad_lzb_simple/rad_lzb_simple.h" -#include "third_party/rad_lzb_simple/rad_lzb_simple.c" - -//- rjf: [h] -#include "base/base_inc.h" -#include "os/os_inc.h" -#include "task_system/task_system.h" -#include "rdi_make/rdi_make_local.h" -#include "coff/coff.h" -#include "codeview/codeview.h" -#include "codeview/codeview_stringize.h" -#include "msf/msf.h" -#include "pdb/pdb.h" -#include "pdb/pdb_stringize.h" -#include "rdi_from_pdb.h" - -//- rjf: [c] -#include "base/base_inc.c" -#include "os/os_inc.c" -#include "task_system/task_system.c" -#include "rdi_make/rdi_make_local.c" -#include "coff/coff.c" -#include "codeview/codeview.c" -#include "codeview/codeview_stringize.c" -#include "msf/msf.c" -#include "pdb/pdb.c" -#include "pdb/pdb_stringize.c" -#include "rdi_from_pdb.c" - -//////////////////////////////// -//~ rjf: Entry Point - -internal void -entry_point(CmdLine *cmdline) -{ - //- rjf: initialize state, unpack command line - Arena *arena = arena_alloc(); - B32 do_help = (cmd_line_has_flag(cmdline, str8_lit("help")) || - cmd_line_has_flag(cmdline, str8_lit("h")) || - cmd_line_has_flag(cmdline, str8_lit("?"))); - P2R_User2Convert *user2convert = p2r_user2convert_from_cmdln(arena, cmdline); - - //- rjf: display help - if(do_help || user2convert->errors.node_count != 0) - { - fprintf(stderr, "--- rdi_from_pdb --------------------------------------------------------------\n\n"); - - fprintf(stderr, "This utility converts debug information from PDBs into the RAD Debug Info\n"); - fprintf(stderr, "format. The following arguments are accepted:\n\n"); - - fprintf(stderr, "--exe: [optional] Specifies the path of the executable file for which the\n"); - fprintf(stderr, " debug info was generated.\n"); - fprintf(stderr, "--pdb: Specifies the path of the PDB debug info file to\n"); - fprintf(stderr, " convert.\n"); - fprintf(stderr, "--out: Specifies the path at which the output Breakpad debug\n"); - fprintf(stderr, " info will be written.\n\n"); - - if(!do_help) - { - for(String8Node *n = user2convert->errors.first; n != 0; n = n->next) - { - fprintf(stderr, "error(input): %.*s\n", str8_varg(n->string)); - } - } - os_exit_process(0); - } - - //- rjf: convert - P2R_Convert2Bake *convert2bake = 0; - ProfScope("convert") - { - convert2bake = p2r_convert(arena, user2convert); - } - - //- rjf: bake - P2R_Bake2Serialize *bake2srlz = 0; - ProfScope("bake") - { - bake2srlz = p2r_bake(arena, convert2bake); - } - - //- rjf: serialize - P2R_Serialize2File *srlz2file = 0; - ProfScope("serialize") - { - srlz2file = push_array(arena, P2R_Serialize2File, 1); - srlz2file->bundle = rdim_serialized_section_bundle_from_bake_results(&bake2srlz->bake_results); - } - - //- rjf: compress - P2R_Serialize2File *srlz2file_compressed = srlz2file; - if(cmd_line_has_flag(cmdline, str8_lit("compress"))) ProfScope("compress") - { - srlz2file_compressed = push_array(arena, P2R_Serialize2File, 1); - srlz2file_compressed = p2r_compress(arena, srlz2file); - } - - //- rjf: serialize - String8List blobs = rdim_file_blobs_from_section_bundle(arena, &srlz2file_compressed->bundle); - - //- rjf: write - ProfScope("write") - { - OS_Handle output_file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_Write, user2convert->output_name); - U64 off = 0; - for(String8Node *n = blobs.first; n != 0; n = n->next) - { - os_file_write(output_file, r1u64(off, off+n->string.size), n->string.str); - off += n->string.size; - } - os_file_close(output_file); - } -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Build Options + +#define BUILD_VERSION_MAJOR 0 +#define BUILD_VERSION_MINOR 9 +#define BUILD_VERSION_PATCH 11 +#define BUILD_RELEASE_PHASE_STRING_LITERAL "ALPHA" +#define BUILD_TITLE "rdi_from_pdb" +#define BUILD_CONSOLE_INTERFACE 1 + +//////////////////////////////// +//~ rjf: Includes + +//- rjf: [lib] +#include "lib_rdi_format/rdi_format.h" +#include "lib_rdi_format/rdi_format.c" +#include "third_party/rad_lzb_simple/rad_lzb_simple.h" +#include "third_party/rad_lzb_simple/rad_lzb_simple.c" + +//- rjf: [h] +#include "base/base_inc.h" +#include "os/os_inc.h" +#include "task_system/task_system.h" +#include "rdi_make/rdi_make_local.h" +#include "coff/coff.h" +#include "codeview/codeview.h" +#include "codeview/codeview_stringize.h" +#include "msf/msf.h" +#include "pdb/pdb.h" +#include "pdb/pdb_stringize.h" +#include "rdi_from_pdb.h" + +//- rjf: [c] +#include "base/base_inc.c" +#include "os/os_inc.c" +#include "task_system/task_system.c" +#include "rdi_make/rdi_make_local.c" +#include "coff/coff.c" +#include "codeview/codeview.c" +#include "codeview/codeview_stringize.c" +#include "msf/msf.c" +#include "pdb/pdb.c" +#include "pdb/pdb_stringize.c" +#include "rdi_from_pdb.c" + +//////////////////////////////// +//~ rjf: Entry Point + +internal void +entry_point(CmdLine *cmdline) +{ + //- rjf: initialize state, unpack command line + Arena *arena = arena_alloc(); + B32 do_help = (cmd_line_has_flag(cmdline, str8_lit("help")) || + cmd_line_has_flag(cmdline, str8_lit("h")) || + cmd_line_has_flag(cmdline, str8_lit("?"))); + P2R_User2Convert *user2convert = p2r_user2convert_from_cmdln(arena, cmdline); + + //- rjf: display help + if(do_help || user2convert->errors.node_count != 0) + { + fprintf(stderr, "--- rdi_from_pdb --------------------------------------------------------------\n\n"); + + fprintf(stderr, "This utility converts debug information from PDBs into the RAD Debug Info\n"); + fprintf(stderr, "format. The following arguments are accepted:\n\n"); + + fprintf(stderr, "--exe: [optional] Specifies the path of the executable file for which the\n"); + fprintf(stderr, " debug info was generated.\n"); + fprintf(stderr, "--pdb: Specifies the path of the PDB debug info file to\n"); + fprintf(stderr, " convert.\n"); + fprintf(stderr, "--out: Specifies the path at which the output Breakpad debug\n"); + fprintf(stderr, " info will be written.\n\n"); + + if(!do_help) + { + for(String8Node *n = user2convert->errors.first; n != 0; n = n->next) + { + fprintf(stderr, "error(input): %.*s\n", str8_varg(n->string)); + } + } + os_abort(0); + } + + //- rjf: convert + P2R_Convert2Bake *convert2bake = 0; + ProfScope("convert") + { + convert2bake = p2r_convert(arena, user2convert); + } + + //- rjf: bake + P2R_Bake2Serialize *bake2srlz = 0; + ProfScope("bake") + { + bake2srlz = p2r_bake(arena, convert2bake); + } + + //- rjf: serialize + P2R_Serialize2File *srlz2file = 0; + ProfScope("serialize") + { + srlz2file = push_array(arena, P2R_Serialize2File, 1); + srlz2file->bundle = rdim_serialized_section_bundle_from_bake_results(&bake2srlz->bake_results); + } + + //- rjf: compress + P2R_Serialize2File *srlz2file_compressed = srlz2file; + if(cmd_line_has_flag(cmdline, str8_lit("compress"))) ProfScope("compress") + { + srlz2file_compressed = push_array(arena, P2R_Serialize2File, 1); + srlz2file_compressed = p2r_compress(arena, srlz2file); + } + + //- rjf: serialize + String8List blobs = rdim_file_blobs_from_section_bundle(arena, &srlz2file_compressed->bundle); + + //- rjf: write + ProfScope("write") + { + OS_Handle output_file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_Write, user2convert->output_name); + U64 off = 0; + for(String8Node *n = blobs.first; n != 0; n = n->next) + { + os_file_write(output_file, r1u64(off, off+n->string.size), n->string.str); + off += n->string.size; + } + os_file_close(output_file); + } +} diff --git a/src/render/d3d11/render_d3d11.c b/src/render/d3d11/render_d3d11.c index 6e7af460..25389300 100644 --- a/src/render/d3d11/render_d3d11.c +++ b/src/render/d3d11/render_d3d11.c @@ -1,1541 +1,1541 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#undef RADDBG_LAYER_COLOR -#define RADDBG_LAYER_COLOR 0.80f, 0.60f, 0.20f - -//////////////////////////////// -//~ rjf: Input Layout Element Tables - -global D3D11_INPUT_ELEMENT_DESC r_d3d11_g_rect_ilay_elements[] = -{ - { "POS", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, - { "TEX", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, - { "COL", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, - { "COL", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, - { "COL", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, - { "COL", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, - { "CRAD", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, - { "STY", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, -}; - -global D3D11_INPUT_ELEMENT_DESC r_d3d11_g_mesh_ilay_elements[] = -{ - { "POS", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "NOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "TEX", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "COL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, -}; - -//////////////////////////////// -//~ rjf: Generated Code - -#include "generated/render_d3d11.meta.c" - -//////////////////////////////// -//~ rjf: Helpers - -internal R_D3D11_Window * -r_d3d11_window_from_handle(R_Handle handle) -{ - R_D3D11_Window *window = (R_D3D11_Window *)handle.u64[0]; - if(window->generation != handle.u64[1]) - { - window = &r_d3d11_window_nil; - } - return window; -} - -internal R_Handle -r_d3d11_handle_from_window(R_D3D11_Window *window) -{ - R_Handle handle = {0}; - handle.u64[0] = (U64)window; - handle.u64[1] = window->generation; - return handle; -} - -internal R_D3D11_Tex2D * -r_d3d11_tex2d_from_handle(R_Handle handle) -{ - R_D3D11_Tex2D *texture = (R_D3D11_Tex2D *)handle.u64[0]; - if(texture == 0 || texture->generation != handle.u64[1]) - { - texture = &r_d3d11_tex2d_nil; - } - return texture; -} - -internal R_Handle -r_d3d11_handle_from_tex2d(R_D3D11_Tex2D *texture) -{ - R_Handle handle = {0}; - handle.u64[0] = (U64)texture; - handle.u64[1] = texture->generation; - return handle; -} - -internal R_D3D11_Buffer * -r_d3d11_buffer_from_handle(R_Handle handle) -{ - R_D3D11_Buffer *buffer = (R_D3D11_Buffer *)handle.u64[0]; - if(buffer == 0 || buffer->generation != handle.u64[1]) - { - buffer = &r_d3d11_buffer_nil; - } - return buffer; -} - -internal R_Handle -r_d3d11_handle_from_buffer(R_D3D11_Buffer *buffer) -{ - R_Handle handle = {0}; - handle.u64[0] = (U64)buffer; - handle.u64[1] = buffer->generation; - return handle; -} - -internal ID3D11Buffer * -r_d3d11_instance_buffer_from_size(U64 size) -{ - ID3D11Buffer *buffer = r_d3d11_state->instance_scratch_buffer_64kb; - if(size > KB(64)) - { - U64 flushed_buffer_size = size; - flushed_buffer_size += MB(1)-1; - flushed_buffer_size -= flushed_buffer_size%MB(1); - - // rjf: build buffer - { - D3D11_BUFFER_DESC desc = {0}; - { - desc.ByteWidth = flushed_buffer_size; - desc.Usage = D3D11_USAGE_DYNAMIC; - desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; - desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; - } - HRESULT error = r_d3d11_state->device->lpVtbl->CreateBuffer(r_d3d11_state->device, &desc, 0, &buffer); - } - - // rjf: push buffer to flush list - R_D3D11_FlushBuffer *n = push_array(r_d3d11_state->buffer_flush_arena, R_D3D11_FlushBuffer, 1); - n->buffer = buffer; - SLLQueuePush(r_d3d11_state->first_buffer_to_flush, r_d3d11_state->last_buffer_to_flush, n); - } - return buffer; -} - -internal void -r_usage_access_flags_from_resource_kind(R_ResourceKind kind, D3D11_USAGE *out_d3d11_usage, UINT *out_cpu_access_flags) -{ - switch(kind) - { - case R_ResourceKind_Static: - { - *out_d3d11_usage = D3D11_USAGE_IMMUTABLE; - *out_cpu_access_flags = 0; - }break; - case R_ResourceKind_Dynamic: - { - *out_d3d11_usage = D3D11_USAGE_DEFAULT; - *out_cpu_access_flags = 0; - }break; - case R_ResourceKind_Stream: - { - *out_d3d11_usage = D3D11_USAGE_DYNAMIC; - *out_cpu_access_flags = D3D11_CPU_ACCESS_WRITE; - }break; - default: - { - InvalidPath; - } - } -} - -//////////////////////////////// -//~ rjf: Backend Hook Implementations - -//- rjf: top-level layer initialization - -r_hook void -r_init(CmdLine *cmdln) -{ - ProfBeginFunction(); - HRESULT error = 0; - Arena *arena = arena_alloc(); - r_d3d11_state = push_array(arena, R_D3D11_State, 1); - r_d3d11_state->arena = arena; - r_d3d11_state->device_rw_mutex = os_rw_mutex_alloc(); - - //- rjf: create base device - ProfBegin("create base device"); - UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; -#if BUILD_DEBUG - if(cmd_line_has_flag(cmdln, str8_lit("d3d11_debug"))) - { - creation_flags |= D3D11_CREATE_DEVICE_DEBUG; - } -#endif - D3D_FEATURE_LEVEL feature_levels[] = { D3D_FEATURE_LEVEL_11_0 }; - D3D_DRIVER_TYPE driver_type = D3D_DRIVER_TYPE_HARDWARE; - if(cmd_line_has_flag(cmdln, str8_lit("force_d3d11_software"))) - { - driver_type = D3D_DRIVER_TYPE_WARP; - } - error = D3D11CreateDevice(0, - driver_type, - 0, - creation_flags, - feature_levels, ArrayCount(feature_levels), - D3D11_SDK_VERSION, - &r_d3d11_state->base_device, 0, &r_d3d11_state->base_device_ctx); - if(FAILED(error) && driver_type == D3D_DRIVER_TYPE_HARDWARE) - { - // try with WARP driver as backup solution in case HW device is not available - error = D3D11CreateDevice(0, - D3D_DRIVER_TYPE_WARP, - 0, - creation_flags, - feature_levels, ArrayCount(feature_levels), - D3D11_SDK_VERSION, - &r_d3d11_state->base_device, 0, &r_d3d11_state->base_device_ctx); - } - if(FAILED(error)) - { - char buffer[256] = {0}; - raddbg_snprintf(buffer, sizeof(buffer), "D3D11 device creation failure (%lx). The process is terminating.", error); - os_graphical_message(1, str8_lit("Fatal Error"), str8_cstring(buffer)); - os_exit_process(1); - } - ProfEnd(); - - //- rjf: enable break-on-error -#if BUILD_DEBUG - if(cmd_line_has_flag(cmdln, str8_lit("d3d11_debug"))) ProfScope("enable break-on-error") - { - ID3D11InfoQueue *info = 0; - error = r_d3d11_state->base_device->lpVtbl->QueryInterface(r_d3d11_state->base_device, &IID_ID3D11InfoQueue, (void **)(&info)); - if(SUCCEEDED(error)) - { - error = info->lpVtbl->SetBreakOnSeverity(info, D3D11_MESSAGE_SEVERITY_CORRUPTION, TRUE); - error = info->lpVtbl->SetBreakOnSeverity(info, D3D11_MESSAGE_SEVERITY_ERROR, TRUE); - info->lpVtbl->Release(info); - } - } -#endif - - //- rjf: get main device - ProfBegin("get main device"); - error = r_d3d11_state->base_device->lpVtbl->QueryInterface(r_d3d11_state->base_device, &IID_ID3D11Device1, (void **)(&r_d3d11_state->device)); - error = r_d3d11_state->base_device_ctx->lpVtbl->QueryInterface(r_d3d11_state->base_device_ctx, &IID_ID3D11DeviceContext1, (void **)(&r_d3d11_state->device_ctx)); - ProfEnd(); - - //- rjf: get dxgi device/adapter/factory - ProfBegin("get dxgi device/adapter/factory"); - error = r_d3d11_state->device->lpVtbl->QueryInterface(r_d3d11_state->device, &IID_IDXGIDevice1, (void **)(&r_d3d11_state->dxgi_device)); - error = r_d3d11_state->dxgi_device->lpVtbl->GetAdapter(r_d3d11_state->dxgi_device, &r_d3d11_state->dxgi_adapter); - error = r_d3d11_state->dxgi_adapter->lpVtbl->GetParent(r_d3d11_state->dxgi_adapter, &IID_IDXGIFactory2, (void **)(&r_d3d11_state->dxgi_factory)); - ProfEnd(); - - //- rjf: create main rasterizer - ProfScope("create main rasterizer") - { - D3D11_RASTERIZER_DESC1 desc = {D3D11_FILL_SOLID}; - { - desc.FillMode = D3D11_FILL_SOLID; - desc.CullMode = D3D11_CULL_BACK; - desc.ScissorEnable = 1; - } - error = r_d3d11_state->device->lpVtbl->CreateRasterizerState1(r_d3d11_state->device, &desc, &r_d3d11_state->main_rasterizer); - } - - //- rjf: create main blend state - ProfScope("create main blend state") - { - D3D11_BLEND_DESC desc = {0}; - { - desc.RenderTarget[0].BlendEnable = 1; - desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; - desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; - desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; - desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; - desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; - desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; - desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - } - error = r_d3d11_state->device->lpVtbl->CreateBlendState(r_d3d11_state->device, &desc, &r_d3d11_state->main_blend_state); - } - - //- rjf: create empty blend state - ProfScope("create empty blend state") - { - D3D11_BLEND_DESC desc = {0}; - { - desc.RenderTarget[0].BlendEnable = FALSE; - desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - } - error = r_d3d11_state->device->lpVtbl->CreateBlendState(r_d3d11_state->device, &desc, &r_d3d11_state->no_blend_state); - } - - //- rjf: create nearest-neighbor sampler - ProfScope("create nearest-neighbor sampler") - { - D3D11_SAMPLER_DESC desc = zero_struct; - { - desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; - desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; - desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; - desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; - desc.ComparisonFunc = D3D11_COMPARISON_NEVER; - } - error = r_d3d11_state->device->lpVtbl->CreateSamplerState(r_d3d11_state->device, &desc, &r_d3d11_state->samplers[R_Tex2DSampleKind_Nearest]); - } - - //- rjf: create bilinear sampler - ProfScope("create bilinear sampler") - { - D3D11_SAMPLER_DESC desc = zero_struct; - { - desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; - desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; - desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; - desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; - desc.ComparisonFunc = D3D11_COMPARISON_NEVER; - } - error = r_d3d11_state->device->lpVtbl->CreateSamplerState(r_d3d11_state->device, &desc, &r_d3d11_state->samplers[R_Tex2DSampleKind_Linear]); - } - - //- rjf: create noop depth/stencil state - ProfScope("create noop depth/stencil state") - { - D3D11_DEPTH_STENCIL_DESC desc = {0}; - { - desc.DepthEnable = FALSE; - desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; - desc.DepthFunc = D3D11_COMPARISON_LESS; - } - error = r_d3d11_state->device->lpVtbl->CreateDepthStencilState(r_d3d11_state->device, &desc, &r_d3d11_state->noop_depth_stencil); - } - - //- rjf: create plain depth/stencil state - ProfScope("create plain depth/stencil state") - { - D3D11_DEPTH_STENCIL_DESC desc = {0}; - { - desc.DepthEnable = TRUE; - desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; - desc.DepthFunc = D3D11_COMPARISON_LESS; - } - error = r_d3d11_state->device->lpVtbl->CreateDepthStencilState(r_d3d11_state->device, &desc, &r_d3d11_state->plain_depth_stencil); - } - - //- rjf: create buffers - ProfScope("create buffers") - { - D3D11_BUFFER_DESC desc = {0}; - { - desc.ByteWidth = KB(64); - desc.Usage = D3D11_USAGE_DYNAMIC; - desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; - desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; - } - error = r_d3d11_state->device->lpVtbl->CreateBuffer(r_d3d11_state->device, &desc, 0, &r_d3d11_state->instance_scratch_buffer_64kb); - } - - //- rjf: build vertex shaders & input layouts - ProfScope("build vertex shaders & input layouts") - for(R_D3D11_VShadKind kind = (R_D3D11_VShadKind)0; - kind < R_D3D11_VShadKind_COUNT; - kind = (R_D3D11_VShadKind)(kind+1)) - { - String8 source = *r_d3d11_g_vshad_kind_source_table[kind]; - String8 source_name = r_d3d11_g_vshad_kind_source_name_table[kind]; - D3D11_INPUT_ELEMENT_DESC *ilay_elements = r_d3d11_g_vshad_kind_elements_ptr_table[kind]; - U64 ilay_elements_count = r_d3d11_g_vshad_kind_elements_count_table[kind]; - - // rjf: compile vertex shader - ID3DBlob *vshad_source_blob = 0; - ID3DBlob *vshad_source_errors = 0; - ID3D11VertexShader *vshad = 0; - ProfScope("compile vertex shader") - { - error = D3DCompile(source.str, - source.size, - (char *)source_name.str, - 0, - 0, - "vs_main", - "vs_5_0", - 0, - 0, - &vshad_source_blob, - &vshad_source_errors); - String8 errors = {0}; - if(FAILED(error)) - { - errors = str8((U8 *)vshad_source_errors->lpVtbl->GetBufferPointer(vshad_source_errors), - (U64)vshad_source_errors->lpVtbl->GetBufferSize(vshad_source_errors)); - os_graphical_message(1, str8_lit("Vertex Shader Compilation Failure"), errors); - } - else - { - error = r_d3d11_state->device->lpVtbl->CreateVertexShader(r_d3d11_state->device, vshad_source_blob->lpVtbl->GetBufferPointer(vshad_source_blob), vshad_source_blob->lpVtbl->GetBufferSize(vshad_source_blob), 0, &vshad); - } - } - - // rjf: make input layout - ID3D11InputLayout *ilay = 0; - if(ilay_elements != 0) - { - error = r_d3d11_state->device->lpVtbl->CreateInputLayout(r_d3d11_state->device, ilay_elements, ilay_elements_count, - vshad_source_blob->lpVtbl->GetBufferPointer(vshad_source_blob), - vshad_source_blob->lpVtbl->GetBufferSize(vshad_source_blob), - &ilay); - } - - vshad_source_blob->lpVtbl->Release(vshad_source_blob); - - // rjf: store - r_d3d11_state->vshads[kind] = vshad; - r_d3d11_state->ilays[kind] = ilay; - } - - //- rjf: build pixel shaders - for(R_D3D11_PShadKind kind = (R_D3D11_PShadKind)0; - kind < R_D3D11_PShadKind_COUNT; - kind = (R_D3D11_PShadKind)(kind+1)) - { - String8 source = *r_d3d11_g_pshad_kind_source_table[kind]; - String8 source_name = r_d3d11_g_pshad_kind_source_name_table[kind]; - - // rjf: compile pixel shader - ID3DBlob *pshad_source_blob = 0; - ID3DBlob *pshad_source_errors = 0; - ID3D11PixelShader *pshad = 0; - ProfScope("compile pixel shader") - { - error = D3DCompile(source.str, - source.size, - (char *)source_name.str, - 0, - 0, - "ps_main", - "ps_5_0", - 0, - 0, - &pshad_source_blob, - &pshad_source_errors); - String8 errors = {0}; - if(FAILED(error)) - { - errors = str8((U8 *)pshad_source_errors->lpVtbl->GetBufferPointer(pshad_source_errors), - (U64)pshad_source_errors->lpVtbl->GetBufferSize(pshad_source_errors)); - os_graphical_message(1, str8_lit("Pixel Shader Compilation Failure"), errors); - } - else - { - error = r_d3d11_state->device->lpVtbl->CreatePixelShader(r_d3d11_state->device, pshad_source_blob->lpVtbl->GetBufferPointer(pshad_source_blob), pshad_source_blob->lpVtbl->GetBufferSize(pshad_source_blob), 0, &pshad); - } - } - - pshad_source_blob->lpVtbl->Release(pshad_source_blob); - - // rjf: store - r_d3d11_state->pshads[kind] = pshad; - } - - //- rjf: build uniform type buffers - ProfScope("build uniform type buffers") - for(R_D3D11_UniformTypeKind kind = (R_D3D11_UniformTypeKind)0; - kind < R_D3D11_UniformTypeKind_COUNT; - kind = (R_D3D11_UniformTypeKind)(kind+1)) - { - ID3D11Buffer *buffer = 0; - { - D3D11_BUFFER_DESC desc = {0}; - { - desc.ByteWidth = r_d3d11_g_uniform_type_kind_size_table[kind]; - desc.ByteWidth += 15; - desc.ByteWidth -= desc.ByteWidth % 16; - desc.Usage = D3D11_USAGE_DYNAMIC; - desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; - desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; - } - r_d3d11_state->device->lpVtbl->CreateBuffer(r_d3d11_state->device, &desc, 0, &buffer); - } - r_d3d11_state->uniform_type_kind_buffers[kind] = buffer; - } - - //- rjf: create backup texture - ProfScope("create backup texture") - { - U32 backup_texture_data[] = - { - 0xff00ffff, 0x330033ff, - 0x330033ff, 0xff00ffff, - }; - r_d3d11_state->backup_texture = r_tex2d_alloc(R_ResourceKind_Static, v2s32(2, 2), R_Tex2DFormat_RGBA8, backup_texture_data); - } - - //- rjf: initialize buffer flush state - { - r_d3d11_state->buffer_flush_arena = arena_alloc(); - } - - ProfEnd(); -} - -//- rjf: window setup/teardown - -r_hook R_Handle -r_window_equip(OS_Handle handle) -{ - ProfBeginFunction(); - R_Handle result = {0}; - OS_MutexScopeW(r_d3d11_state->device_rw_mutex) - { - //- rjf: allocate per-window-state - R_D3D11_Window *window = r_d3d11_state->first_free_window; - { - if(window == 0) - { - window = push_array(r_d3d11_state->arena, R_D3D11_Window, 1); - } - else - { - U64 gen = window->generation; - SLLStackPop(r_d3d11_state->first_free_window); - MemoryZeroStruct(window); - window->generation = gen; - } - window->generation += 1; - } - - //- rjf: map os window handle -> hwnd - HWND hwnd = {0}; - { - W32_Window *w32_layer_window = w32_window_from_os_window(handle); - hwnd = w32_hwnd_from_window(w32_layer_window); - } - - //- rjf: create swapchain - DXGI_SWAP_CHAIN_DESC1 swapchain_desc = {0}; - { - swapchain_desc.Width = 0; // NOTE(rjf): use window width - swapchain_desc.Height = 0; // NOTE(rjf): use window height - swapchain_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - swapchain_desc.Stereo = FALSE; - swapchain_desc.SampleDesc.Count = 1; - swapchain_desc.SampleDesc.Quality = 0; - swapchain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swapchain_desc.BufferCount = 2; - swapchain_desc.Scaling = DXGI_SCALING_STRETCH; - swapchain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; - swapchain_desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; - swapchain_desc.Flags = 0; - } - HRESULT error = r_d3d11_state->dxgi_factory->lpVtbl->CreateSwapChainForHwnd(r_d3d11_state->dxgi_factory, (IUnknown *)r_d3d11_state->device, hwnd, &swapchain_desc, 0, 0, &window->swapchain); - if(FAILED(error)) - { - char buffer[256] = {0}; - raddbg_snprintf(buffer, sizeof(buffer), "DXGI swap chain creation failure (%lx). The process is terminating.", error); - os_graphical_message(1, str8_lit("Fatal Error"), str8_cstring(buffer)); - os_exit_process(1); - } - - r_d3d11_state->dxgi_factory->lpVtbl->MakeWindowAssociation(r_d3d11_state->dxgi_factory, hwnd, DXGI_MWA_NO_ALT_ENTER); - - //- rjf: create framebuffer & view - window->swapchain->lpVtbl->GetBuffer(window->swapchain, 0, &IID_ID3D11Texture2D, (void **)(&window->framebuffer)); - r_d3d11_state->device->lpVtbl->CreateRenderTargetView(r_d3d11_state->device, (ID3D11Resource *)window->framebuffer, 0, &window->framebuffer_rtv); - - result = r_d3d11_handle_from_window(window); - } - ProfEnd(); - return result; -} - -r_hook void -r_window_unequip(OS_Handle handle, R_Handle equip_handle) -{ - ProfBeginFunction(); - OS_MutexScopeW(r_d3d11_state->device_rw_mutex) - { - R_D3D11_Window *window = r_d3d11_window_from_handle(equip_handle); - window->stage_color_srv->lpVtbl->Release(window->stage_color_srv); - window->stage_color_rtv->lpVtbl->Release(window->stage_color_rtv); - window->stage_color->lpVtbl->Release(window->stage_color); - window->stage_scratch_color_srv->lpVtbl->Release(window->stage_scratch_color_srv); - window->stage_scratch_color_rtv->lpVtbl->Release(window->stage_scratch_color_rtv); - window->stage_scratch_color->lpVtbl->Release(window->stage_scratch_color); - window->framebuffer_rtv->lpVtbl->Release(window->framebuffer_rtv); - window->framebuffer->lpVtbl->Release(window->framebuffer); - window->swapchain->lpVtbl->Release(window->swapchain); - window->generation += 1; - SLLStackPush(r_d3d11_state->first_free_window, window); - } - ProfEnd(); -} - -//- rjf: textures - -r_hook R_Handle -r_tex2d_alloc(R_ResourceKind kind, Vec2S32 size, R_Tex2DFormat format, void *data) -{ - ProfBeginFunction(); - - //- rjf: allocate - R_D3D11_Tex2D *texture = 0; - OS_MutexScopeW(r_d3d11_state->device_rw_mutex) - { - texture = r_d3d11_state->first_free_tex2d; - if(texture == 0) - { - texture = push_array(r_d3d11_state->arena, R_D3D11_Tex2D, 1); - } - else - { - U64 gen = texture->generation; - SLLStackPop(r_d3d11_state->first_free_tex2d); - MemoryZeroStruct(texture); - texture->generation = gen; - } - texture->generation += 1; - } - - D3D11_USAGE d3d11_usage = D3D11_USAGE_DEFAULT; - UINT cpu_access_flags = 0; - r_usage_access_flags_from_resource_kind(kind, &d3d11_usage, &cpu_access_flags); - if (kind == R_ResourceKind_Static) - { - Assert(data != 0 && "static texture must have initial data provided"); - } - - //- rjf: format -> dxgi format - DXGI_FORMAT dxgi_format = DXGI_FORMAT_R8G8B8A8_UNORM; - { - switch(format) - { - default:{}break; - case R_Tex2DFormat_R8: {dxgi_format = DXGI_FORMAT_R8_UNORM;}break; - case R_Tex2DFormat_RG8: {dxgi_format = DXGI_FORMAT_R8G8_UNORM;}break; - case R_Tex2DFormat_RGBA8: {dxgi_format = DXGI_FORMAT_R8G8B8A8_UNORM;}break; - case R_Tex2DFormat_BGRA8: {dxgi_format = DXGI_FORMAT_B8G8R8A8_UNORM;}break; - case R_Tex2DFormat_R16: {dxgi_format = DXGI_FORMAT_R16_UNORM;}break; - case R_Tex2DFormat_RGBA16:{dxgi_format = DXGI_FORMAT_R16G16B16A16_UNORM;}break; - case R_Tex2DFormat_R32: {dxgi_format = DXGI_FORMAT_R32_FLOAT;}break; - case R_Tex2DFormat_RG32: {dxgi_format = DXGI_FORMAT_R32G32_FLOAT;}break; - case R_Tex2DFormat_RGBA32:{dxgi_format = DXGI_FORMAT_R32G32B32A32_FLOAT;}break; - } - } - - //- rjf: prep initial data, if passed - D3D11_SUBRESOURCE_DATA initial_data_ = {0}; - D3D11_SUBRESOURCE_DATA *initial_data = 0; - if(data != 0) - { - initial_data = &initial_data_; - initial_data->pSysMem = data; - initial_data->SysMemPitch = r_tex2d_format_bytes_per_pixel_table[format] * size.x; - } - - //- rjf: create texture - D3D11_TEXTURE2D_DESC texture_desc = {0}; - { - texture_desc.Width = size.x; - texture_desc.Height = size.y; - texture_desc.MipLevels = 1; - texture_desc.ArraySize = 1; - texture_desc.Format = dxgi_format; - texture_desc.SampleDesc.Count = 1; - texture_desc.Usage = d3d11_usage; - texture_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; - texture_desc.CPUAccessFlags = cpu_access_flags; - } - r_d3d11_state->device->lpVtbl->CreateTexture2D(r_d3d11_state->device, &texture_desc, initial_data, &texture->texture); - - //- rjf: create texture srv - r_d3d11_state->device->lpVtbl->CreateShaderResourceView(r_d3d11_state->device, (ID3D11Resource *)texture->texture, 0, &texture->view); - - //- rjf: fill basics - { - texture->kind = kind; - texture->size = size; - texture->format = format; - } - - R_Handle result = r_d3d11_handle_from_tex2d(texture); - ProfEnd(); - return result; -} - -r_hook void -r_tex2d_release(R_Handle handle) -{ - ProfBeginFunction(); - OS_MutexScopeW(r_d3d11_state->device_rw_mutex) - { - R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(handle); - SLLStackPush(r_d3d11_state->first_to_free_tex2d, texture); - } - ProfEnd(); -} - -r_hook R_ResourceKind -r_kind_from_tex2d(R_Handle handle) -{ - R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(handle); - return texture->kind; -} - -r_hook Vec2S32 -r_size_from_tex2d(R_Handle handle) -{ - R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(handle); - return texture->size; -} - -r_hook R_Tex2DFormat -r_format_from_tex2d(R_Handle handle) -{ - R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(handle); - return texture->format; -} - -r_hook void -r_fill_tex2d_region(R_Handle handle, Rng2S32 subrect, void *data) -{ - ProfBeginFunction(); - OS_MutexScopeW(r_d3d11_state->device_rw_mutex) - { - R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(handle); - Assert(texture->kind == R_ResourceKind_Dynamic && "only dynamic texture can update region"); - U64 bytes_per_pixel = r_tex2d_format_bytes_per_pixel_table[texture->format]; - Vec2S32 dim = v2s32(subrect.x1 - subrect.x0, subrect.y1 - subrect.y0); - D3D11_BOX dst_box = - { - (UINT)subrect.x0, (UINT)subrect.y0, 0, - (UINT)subrect.x1, (UINT)subrect.y1, 1, - }; - r_d3d11_state->device_ctx->lpVtbl->UpdateSubresource(r_d3d11_state->device_ctx, (ID3D11Resource *)texture->texture, 0, &dst_box, data, dim.x*bytes_per_pixel, 0); - } - ProfEnd(); -} - -//- rjf: buffers - -r_hook R_Handle -r_buffer_alloc(R_ResourceKind kind, U64 size, void *data) -{ - ProfBeginFunction(); - - //- rjf: allocate - R_D3D11_Buffer *buffer = 0; - OS_MutexScopeW(r_d3d11_state->device_rw_mutex) - { - buffer = r_d3d11_state->first_free_buffer; - if(buffer == 0) - { - buffer = push_array(r_d3d11_state->arena, R_D3D11_Buffer, 1); - } - else - { - U64 gen = buffer->generation; - SLLStackPop(r_d3d11_state->first_free_buffer); - MemoryZeroStruct(buffer); - buffer->generation = gen; - } - buffer->generation += 1; - } - - D3D11_USAGE d3d11_usage = D3D11_USAGE_DEFAULT; - UINT cpu_access_flags = 0; - r_usage_access_flags_from_resource_kind(kind, &d3d11_usage, &cpu_access_flags); - if (kind == R_ResourceKind_Static) - { - Assert(data != 0 && "static buffer must have initial data provided"); - } - - //- rjf: prep initial data, if passed - D3D11_SUBRESOURCE_DATA initial_data_ = {0}; - D3D11_SUBRESOURCE_DATA *initial_data = 0; - if(data != 0) - { - initial_data = &initial_data_; - initial_data->pSysMem = data; - } - - //- rjf: create buffer - D3D11_BUFFER_DESC desc = {0}; - { - desc.ByteWidth = size; - desc.Usage = d3d11_usage; - desc.BindFlags = D3D11_BIND_VERTEX_BUFFER|D3D11_BIND_INDEX_BUFFER; - desc.CPUAccessFlags = cpu_access_flags; - } - r_d3d11_state->device->lpVtbl->CreateBuffer(r_d3d11_state->device, &desc, initial_data, &buffer->buffer); - - //- rjf: fill basics - { - buffer->kind = kind; - buffer->size = size; - } - - R_Handle result = r_d3d11_handle_from_buffer(buffer); - ProfEnd(); - return result; -} - -r_hook void -r_buffer_release(R_Handle handle) -{ - ProfBeginFunction(); - OS_MutexScopeW(r_d3d11_state->device_rw_mutex) - { - R_D3D11_Buffer *buffer = r_d3d11_buffer_from_handle(handle); - SLLStackPush(r_d3d11_state->first_to_free_buffer, buffer); - } - ProfEnd(); -} - -//- rjf: frame markers - -r_hook void -r_begin_frame(void) -{ - OS_MutexScopeW(r_d3d11_state->device_rw_mutex) - { - // NOTE(rjf): no-op - } -} - -r_hook void -r_end_frame(void) -{ - OS_MutexScopeW(r_d3d11_state->device_rw_mutex) - { - for(R_D3D11_FlushBuffer *buffer = r_d3d11_state->first_buffer_to_flush; buffer != 0; buffer = buffer->next) - { - buffer->buffer->lpVtbl->Release(buffer->buffer); - } - for(R_D3D11_Tex2D *tex = r_d3d11_state->first_to_free_tex2d, *next = 0; - tex != 0; - tex = next) - { - next = tex->next; - tex->view->lpVtbl->Release(tex->view); - tex->texture->lpVtbl->Release(tex->texture); - tex->generation += 1; - SLLStackPush(r_d3d11_state->first_free_tex2d, tex); - } - for(R_D3D11_Buffer *buf = r_d3d11_state->first_to_free_buffer, *next = 0; - buf != 0; - buf = next) - { - next = buf->next; - buf->buffer->lpVtbl->Release(buf->buffer); - buf->generation += 1; - SLLStackPush(r_d3d11_state->first_free_buffer, buf); - } - arena_clear(r_d3d11_state->buffer_flush_arena); - r_d3d11_state->first_buffer_to_flush = r_d3d11_state->last_buffer_to_flush = 0; - r_d3d11_state->first_to_free_tex2d = 0; - r_d3d11_state->first_to_free_buffer = 0; - } -} - -r_hook void -r_window_begin_frame(OS_Handle window, R_Handle window_equip) -{ - ProfBeginFunction(); - OS_MutexScopeW(r_d3d11_state->device_rw_mutex) - { - R_D3D11_Window *wnd = r_d3d11_window_from_handle(window_equip); - ID3D11DeviceContext1 *d_ctx = r_d3d11_state->device_ctx; - - //- rjf: get resolution - Rng2F32 client_rect = os_client_rect_from_window(window); - Vec2S32 resolution = {(S32)(client_rect.x1 - client_rect.x0), (S32)(client_rect.y1 - client_rect.y0)}; - - //- rjf: resolution change - B32 resize_done = 0; - if(wnd->last_resolution.x != resolution.x || - wnd->last_resolution.y != resolution.y) - { - resize_done = 1; - wnd->last_resolution = resolution; - - // rjf: release screen-sized render target resources, if there - if(wnd->stage_scratch_color_srv){wnd->stage_scratch_color_srv->lpVtbl->Release(wnd->stage_scratch_color_srv);} - if(wnd->stage_scratch_color_rtv){wnd->stage_scratch_color_rtv->lpVtbl->Release(wnd->stage_scratch_color_rtv);} - if(wnd->stage_scratch_color) {wnd->stage_scratch_color->lpVtbl->Release(wnd->stage_scratch_color);} - if(wnd->stage_color_srv) {wnd->stage_color_srv->lpVtbl->Release(wnd->stage_color_srv);} - if(wnd->stage_color_rtv) {wnd->stage_color_rtv->lpVtbl->Release(wnd->stage_color_rtv);} - if(wnd->stage_color) {wnd->stage_color->lpVtbl->Release(wnd->stage_color);} - if(wnd->geo3d_color_srv) {wnd->geo3d_color_srv->lpVtbl->Release(wnd->geo3d_color_srv);} - if(wnd->geo3d_color_rtv) {wnd->geo3d_color_rtv->lpVtbl->Release(wnd->geo3d_color_rtv);} - if(wnd->geo3d_color) {wnd->geo3d_color->lpVtbl->Release(wnd->geo3d_color);} - if(wnd->geo3d_depth_srv) {wnd->geo3d_depth_srv->lpVtbl->Release(wnd->geo3d_depth_srv);} - if(wnd->geo3d_depth_dsv) {wnd->geo3d_depth_dsv->lpVtbl->Release(wnd->geo3d_depth_dsv);} - if(wnd->geo3d_depth) {wnd->geo3d_depth->lpVtbl->Release(wnd->geo3d_depth);} - - // rjf: resize swapchain & main framebuffer - wnd->framebuffer_rtv->lpVtbl->Release(wnd->framebuffer_rtv); - wnd->framebuffer->lpVtbl->Release(wnd->framebuffer); - wnd->swapchain->lpVtbl->ResizeBuffers(wnd->swapchain, 0, 0, 0, DXGI_FORMAT_UNKNOWN, 0); - wnd->swapchain->lpVtbl->GetBuffer(wnd->swapchain, 0, &IID_ID3D11Texture2D, (void **)(&wnd->framebuffer)); - r_d3d11_state->device->lpVtbl->CreateRenderTargetView(r_d3d11_state->device, (ID3D11Resource *)wnd->framebuffer, 0, &wnd->framebuffer_rtv); - - // rjf: create stage color targets - { - D3D11_TEXTURE2D_DESC color_desc = zero_struct; - { - wnd->framebuffer->lpVtbl->GetDesc(wnd->framebuffer, &color_desc); - color_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - color_desc.BindFlags = D3D11_BIND_RENDER_TARGET|D3D11_BIND_SHADER_RESOURCE; - } - D3D11_RENDER_TARGET_VIEW_DESC rtv_desc = zero_struct; - { - rtv_desc.Format = color_desc.Format; - rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; - } - D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc = zero_struct; - { - srv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srv_desc.Texture2D.MipLevels = -1; - } - r_d3d11_state->device->lpVtbl->CreateTexture2D(r_d3d11_state->device, &color_desc, 0, &wnd->stage_color); - r_d3d11_state->device->lpVtbl->CreateRenderTargetView(r_d3d11_state->device, (ID3D11Resource *)wnd->stage_color, &rtv_desc, &wnd->stage_color_rtv); - r_d3d11_state->device->lpVtbl->CreateShaderResourceView(r_d3d11_state->device, (ID3D11Resource *)wnd->stage_color, &srv_desc, &wnd->stage_color_srv); - r_d3d11_state->device->lpVtbl->CreateTexture2D(r_d3d11_state->device, &color_desc, 0, &wnd->stage_scratch_color); - r_d3d11_state->device->lpVtbl->CreateRenderTargetView(r_d3d11_state->device, (ID3D11Resource *)wnd->stage_scratch_color, &rtv_desc, &wnd->stage_scratch_color_rtv); - r_d3d11_state->device->lpVtbl->CreateShaderResourceView(r_d3d11_state->device, (ID3D11Resource *)wnd->stage_scratch_color, &srv_desc, &wnd->stage_scratch_color_srv); - } - - // rjf: create geo3d targets - { - D3D11_TEXTURE2D_DESC color_desc = zero_struct; - { - wnd->framebuffer->lpVtbl->GetDesc(wnd->framebuffer, &color_desc); - color_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - color_desc.BindFlags = D3D11_BIND_RENDER_TARGET|D3D11_BIND_SHADER_RESOURCE; - } - D3D11_RENDER_TARGET_VIEW_DESC color_rtv_desc = zero_struct; - { - color_rtv_desc.Format = color_desc.Format; - color_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; - } - D3D11_SHADER_RESOURCE_VIEW_DESC color_srv_desc = zero_struct; - { - color_srv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - color_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - color_srv_desc.Texture2D.MipLevels = -1; - } - D3D11_TEXTURE2D_DESC depth_desc = zero_struct; - { - wnd->framebuffer->lpVtbl->GetDesc(wnd->framebuffer, &depth_desc); - depth_desc.Format = DXGI_FORMAT_R24G8_TYPELESS; - depth_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL|D3D11_BIND_SHADER_RESOURCE; - } - D3D11_DEPTH_STENCIL_VIEW_DESC depth_dsv_desc = zero_struct; - { - depth_dsv_desc.Flags = 0; - depth_dsv_desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; - depth_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; - depth_dsv_desc.Texture2D.MipSlice = 0; - } - D3D11_SHADER_RESOURCE_VIEW_DESC depth_srv_desc = zero_struct; - { - depth_srv_desc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS; - depth_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - depth_srv_desc.Texture2D.MostDetailedMip = 0; - depth_srv_desc.Texture2D.MipLevels = -1; - } - r_d3d11_state->device->lpVtbl->CreateTexture2D(r_d3d11_state->device, &color_desc, 0, &wnd->geo3d_color); - r_d3d11_state->device->lpVtbl->CreateRenderTargetView(r_d3d11_state->device, (ID3D11Resource *)wnd->geo3d_color, &color_rtv_desc, &wnd->geo3d_color_rtv); - r_d3d11_state->device->lpVtbl->CreateShaderResourceView(r_d3d11_state->device, (ID3D11Resource *)wnd->geo3d_color, &color_srv_desc, &wnd->geo3d_color_srv); - r_d3d11_state->device->lpVtbl->CreateTexture2D(r_d3d11_state->device, &depth_desc, 0, &wnd->geo3d_depth); - r_d3d11_state->device->lpVtbl->CreateDepthStencilView(r_d3d11_state->device, (ID3D11Resource *)wnd->geo3d_depth, &depth_dsv_desc, &wnd->geo3d_depth_dsv); - r_d3d11_state->device->lpVtbl->CreateShaderResourceView(r_d3d11_state->device, (ID3D11Resource *)wnd->geo3d_depth, &depth_srv_desc, &wnd->geo3d_depth_srv); - } - } - - //- rjf: clear framebuffers - Vec4F32 clear_color = {0, 0, 0, 0}; - d_ctx->lpVtbl->ClearRenderTargetView(d_ctx, wnd->framebuffer_rtv, clear_color.v); - d_ctx->lpVtbl->ClearRenderTargetView(d_ctx, wnd->stage_color_rtv, clear_color.v); - if(resize_done) - { - d_ctx->lpVtbl->Flush(d_ctx); - } - } - ProfEnd(); -} - -r_hook void -r_window_end_frame(OS_Handle window, R_Handle window_equip) -{ - ProfBeginFunction(); - OS_MutexScopeW(r_d3d11_state->device_rw_mutex) - { - R_D3D11_Window *wnd = r_d3d11_window_from_handle(window_equip); - ID3D11DeviceContext1 *d_ctx = r_d3d11_state->device_ctx; - - //////////////////////////// - //- rjf: finalize, by writing staging buffer out to window framebuffer - // - { - ID3D11SamplerState *sampler = r_d3d11_state->samplers[R_Tex2DSampleKind_Nearest]; - ID3D11VertexShader *vshad = r_d3d11_state->vshads[R_D3D11_VShadKind_Finalize]; - ID3D11PixelShader *pshad = r_d3d11_state->pshads[R_D3D11_PShadKind_Finalize]; - - // rjf: setup output merger - d_ctx->lpVtbl->OMSetRenderTargets(d_ctx, 1, &wnd->framebuffer_rtv, 0); - d_ctx->lpVtbl->OMSetDepthStencilState(d_ctx, r_d3d11_state->noop_depth_stencil, 0); - d_ctx->lpVtbl->OMSetBlendState(d_ctx, r_d3d11_state->main_blend_state, 0, 0xffffffff); - - // rjf: set up rasterizer - Vec2S32 resolution = wnd->last_resolution; - D3D11_VIEWPORT viewport = { 0.0f, 0.0f, (F32)resolution.x, (F32)resolution.y, 0.0f, 1.0f }; - d_ctx->lpVtbl->RSSetViewports(d_ctx, 1, &viewport); - d_ctx->lpVtbl->RSSetState(d_ctx, (ID3D11RasterizerState *)r_d3d11_state->main_rasterizer); - - // rjf: setup input assembly - d_ctx->lpVtbl->IASetPrimitiveTopology(d_ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); - d_ctx->lpVtbl->IASetInputLayout(d_ctx, 0); - - // rjf: setup shaders - d_ctx->lpVtbl->VSSetShader(d_ctx, vshad, 0, 0); - d_ctx->lpVtbl->PSSetShader(d_ctx, pshad, 0, 0); - d_ctx->lpVtbl->PSSetShaderResources(d_ctx, 0, 1, &wnd->stage_color_srv); - d_ctx->lpVtbl->PSSetSamplers(d_ctx, 0, 1, &sampler); - - // rjf: setup scissor rect - { - D3D11_RECT rect = {0}; - rect.left = 0; - rect.right = (LONG)wnd->last_resolution.x; - rect.top = 0; - rect.bottom = (LONG)wnd->last_resolution.y; - d_ctx->lpVtbl->RSSetScissorRects(d_ctx, 1, &rect); - } - - // rjf: draw - d_ctx->lpVtbl->Draw(d_ctx, 4, 0); - } - - //////////////////////////// - //- rjf: present - // - HRESULT error = wnd->swapchain->lpVtbl->Present(wnd->swapchain, 1, 0); - if(FAILED(error)) - { - char buffer[256] = {0}; - raddbg_snprintf(buffer, sizeof(buffer), "D3D11 present failure (%lx). The process is terminating.", error); - os_graphical_message(1, str8_lit("Fatal Error"), str8_cstring(buffer)); - os_exit_process(1); - } - d_ctx->lpVtbl->ClearState(d_ctx); - } - ProfEnd(); -} - -//- rjf: render pass submission - -r_hook void -r_window_submit(OS_Handle window, R_Handle window_equip, R_PassList *passes) -{ - ProfBeginFunction(); - OS_MutexScopeW(r_d3d11_state->device_rw_mutex) - { - //////////////////////////// - //- rjf: unpack arguments - // - R_D3D11_Window *wnd = r_d3d11_window_from_handle(window_equip); - ID3D11DeviceContext1 *d_ctx = r_d3d11_state->device_ctx; - - //////////////////////////// - //- rjf: do passes - // - for(R_PassNode *pass_n = passes->first; pass_n != 0; pass_n = pass_n->next) - { - R_Pass *pass = &pass_n->v; - switch(pass->kind) - { - default:{}break; - - //////////////////////// - //- rjf: ui rendering pass - // - case R_PassKind_UI: - { - //- rjf: unpack params - R_PassParams_UI *params = pass->params_ui; - R_BatchGroup2DList *rect_batch_groups = ¶ms->rects; - - //- rjf: set up rasterizer - Vec2S32 resolution = wnd->last_resolution; - D3D11_VIEWPORT viewport = { 0.0f, 0.0f, (F32)resolution.x, (F32)resolution.y, 0.0f, 1.0f }; - d_ctx->lpVtbl->RSSetViewports(d_ctx, 1, &viewport); - d_ctx->lpVtbl->RSSetState(d_ctx, (ID3D11RasterizerState *)r_d3d11_state->main_rasterizer); - - //- rjf: draw each batch group - for(R_BatchGroup2DNode *group_n = rect_batch_groups->first; group_n != 0; group_n = group_n->next) - { - R_BatchList *batches = &group_n->batches; - R_BatchGroup2DParams *group_params = &group_n->params; - - // rjf: unpack pipeline info - ID3D11SamplerState *sampler = r_d3d11_state->samplers[group_params->tex_sample_kind]; - ID3D11VertexShader *vshad = r_d3d11_state->vshads[R_D3D11_VShadKind_Rect]; - ID3D11InputLayout *ilay = r_d3d11_state->ilays[R_D3D11_VShadKind_Rect]; - ID3D11PixelShader *pshad = r_d3d11_state->pshads[R_D3D11_PShadKind_Rect]; - ID3D11Buffer *uniforms_buffer = r_d3d11_state->uniform_type_kind_buffers[R_D3D11_UniformTypeKind_Rect]; - - // rjf: get & fill buffer - ID3D11Buffer *buffer = r_d3d11_instance_buffer_from_size(batches->byte_count); - { - D3D11_MAPPED_SUBRESOURCE sub_rsrc = {0}; - d_ctx->lpVtbl->Map(d_ctx, (ID3D11Resource *)buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &sub_rsrc); - U8 *dst_ptr = (U8 *)sub_rsrc.pData; - U64 off = 0; - for(R_BatchNode *batch_n = batches->first; batch_n != 0; batch_n = batch_n->next) - { - MemoryCopy(dst_ptr+off, batch_n->v.v, batch_n->v.byte_count); - off += batch_n->v.byte_count; - } - d_ctx->lpVtbl->Unmap(d_ctx, (ID3D11Resource *)buffer, 0); - } - - // rjf: get texture - R_Handle texture_handle = group_params->tex; - if(r_handle_match(texture_handle, r_handle_zero())) - { - texture_handle = r_d3d11_state->backup_texture; - } - R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(texture_handle); - - // rjf: get texture sample map matrix, based on format - Vec4F32 texture_sample_channel_map[] = - { - {1, 0, 0, 0}, - {0, 1, 0, 0}, - {0, 0, 1, 0}, - {0, 0, 0, 1}, - }; - switch(texture->format) - { - default: break; - case R_Tex2DFormat_R8: - { - MemoryZeroArray(texture_sample_channel_map); - texture_sample_channel_map[0] = v4f32(1, 1, 1, 1); - }break; - } - - // rjf: upload uniforms - R_D3D11_Uniforms_Rect uniforms = {0}; - { - uniforms.viewport_size = v2f32(resolution.x, resolution.y); - uniforms.opacity = 1-group_params->transparency; - MemoryCopyArray(uniforms.texture_sample_channel_map, texture_sample_channel_map); - uniforms.texture_t2d_size = v2f32(texture->size.x, texture->size.y); - uniforms.xform[0] = v4f32(group_params->xform.v[0][0], group_params->xform.v[1][0], group_params->xform.v[2][0], 0); - uniforms.xform[1] = v4f32(group_params->xform.v[0][1], group_params->xform.v[1][1], group_params->xform.v[2][1], 0); - uniforms.xform[2] = v4f32(group_params->xform.v[0][2], group_params->xform.v[1][2], group_params->xform.v[2][2], 0); - Vec2F32 xform_2x2_col0 = v2f32(uniforms.xform[0].x, uniforms.xform[1].x); - Vec2F32 xform_2x2_col1 = v2f32(uniforms.xform[0].y, uniforms.xform[1].y); - uniforms.xform_scale.x = length_2f32(xform_2x2_col0); - uniforms.xform_scale.y = length_2f32(xform_2x2_col1); - } - { - D3D11_MAPPED_SUBRESOURCE sub_rsrc = {0}; - d_ctx->lpVtbl->Map(d_ctx, (ID3D11Resource *)uniforms_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &sub_rsrc); - MemoryCopy((U8 *)sub_rsrc.pData, &uniforms, sizeof(uniforms)); - d_ctx->lpVtbl->Unmap(d_ctx, (ID3D11Resource *)uniforms_buffer, 0); - } - - // rjf: setup output merger - d_ctx->lpVtbl->OMSetRenderTargets(d_ctx, 1, &wnd->stage_color_rtv, 0); - d_ctx->lpVtbl->OMSetDepthStencilState(d_ctx, r_d3d11_state->noop_depth_stencil, 0); - d_ctx->lpVtbl->OMSetBlendState(d_ctx, r_d3d11_state->main_blend_state, 0, 0xffffffff); - - // rjf: setup input assembly - U32 stride = batches->bytes_per_inst; - U32 offset = 0; - d_ctx->lpVtbl->IASetPrimitiveTopology(d_ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); - d_ctx->lpVtbl->IASetInputLayout(d_ctx, ilay); - d_ctx->lpVtbl->IASetVertexBuffers(d_ctx, 0, 1, &buffer, &stride, &offset); - - // rjf: setup shaders - d_ctx->lpVtbl->VSSetShader(d_ctx, vshad, 0, 0); - d_ctx->lpVtbl->VSSetConstantBuffers(d_ctx, 0, 1, &uniforms_buffer); - d_ctx->lpVtbl->PSSetShader(d_ctx, pshad, 0, 0); - d_ctx->lpVtbl->PSSetConstantBuffers(d_ctx, 0, 1, &uniforms_buffer); - d_ctx->lpVtbl->PSSetShaderResources(d_ctx, 0, 1, &texture->view); - d_ctx->lpVtbl->PSSetSamplers(d_ctx, 0, 1, &sampler); - - // rjf: setup scissor rect - { - Rng2F32 clip = group_params->clip; - D3D11_RECT rect = {0}; - { - if(clip.x0 == 0 && clip.y0 == 0 && clip.x1 == 0 && clip.y1 == 0) - { - rect.left = 0; - rect.right = (LONG)wnd->last_resolution.x; - rect.top = 0; - rect.bottom = (LONG)wnd->last_resolution.y; - } - else if(clip.x0 > clip.x1 || clip.y0 > clip.y1) - { - rect.left = 0; - rect.right = 0; - rect.top = 0; - rect.bottom = 0; - } - else - { - rect.left = (LONG)clip.x0; - rect.right = (LONG)clip.x1; - rect.top = (LONG)clip.y0; - rect.bottom = (LONG)clip.y1; - } - } - d_ctx->lpVtbl->RSSetScissorRects(d_ctx, 1, &rect); - } - - // rjf: draw - d_ctx->lpVtbl->DrawInstanced(d_ctx, 4, batches->byte_count / batches->bytes_per_inst, 0, 0); - } - }break; - - //////////////////////// - //- rjf: blur rendering pass - // - case R_PassKind_Blur: - { - R_PassParams_Blur *params = pass->params_blur; - ID3D11SamplerState *sampler = r_d3d11_state->samplers[R_Tex2DSampleKind_Linear]; - ID3D11VertexShader *vshad = r_d3d11_state->vshads[R_D3D11_VShadKind_Blur]; - ID3D11PixelShader *pshad = r_d3d11_state->pshads[R_D3D11_PShadKind_Blur]; - ID3D11Buffer *uniforms_buffer = r_d3d11_state->uniform_type_kind_buffers[R_D3D11_VShadKind_Blur]; - - // rjf: setup output merger - d_ctx->lpVtbl->OMSetDepthStencilState(d_ctx, r_d3d11_state->noop_depth_stencil, 0); - d_ctx->lpVtbl->OMSetBlendState(d_ctx, r_d3d11_state->no_blend_state, 0, 0xffffffff); - - // rjf: set up viewport - Vec2S32 resolution = wnd->last_resolution; - D3D11_VIEWPORT viewport = { 0.0f, 0.0f, (F32)resolution.x, (F32)resolution.y, 0.0f, 1.0f }; - d_ctx->lpVtbl->RSSetViewports(d_ctx, 1, &viewport); - d_ctx->lpVtbl->RSSetState(d_ctx, (ID3D11RasterizerState *)r_d3d11_state->main_rasterizer); - - // rjf: setup input assembly - d_ctx->lpVtbl->IASetPrimitiveTopology(d_ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); - d_ctx->lpVtbl->IASetInputLayout(d_ctx, 0); - - // rjf: setup shaders - d_ctx->lpVtbl->VSSetShader(d_ctx, vshad, 0, 0); - d_ctx->lpVtbl->VSSetConstantBuffers(d_ctx, 0, 1, &uniforms_buffer); - d_ctx->lpVtbl->PSSetShader(d_ctx, pshad, 0, 0); - d_ctx->lpVtbl->PSSetSamplers(d_ctx, 0, 1, &sampler); - - // rjf: setup scissor rect - { - D3D11_RECT rect = { 0 }; - rect.left = 0; - rect.right = (LONG)wnd->last_resolution.x; - rect.top = 0; - rect.bottom = (LONG)wnd->last_resolution.y; - d_ctx->lpVtbl->RSSetScissorRects(d_ctx, 1, &rect); - } - - // rjf: set up uniforms - R_D3D11_Uniforms_Blur uniforms = { 0 }; - { - F32 weights[ArrayCount(uniforms.kernel)*2] = {0}; - - F32 blur_size = Min(params->blur_size, ArrayCount(weights)); - U64 blur_count = (U64)round_f32(blur_size); - - F32 stdev = (blur_size-1.f)/2.f; - F32 one_over_root_2pi_stdev2 = 1/sqrt_f32(2*pi32*stdev*stdev); - F32 euler32 = 2.718281828459045f; - - weights[0] = 1.f; - if(stdev > 0.f) - { - for(U64 idx = 0; idx < blur_count; idx += 1) - { - F32 kernel_x = (F32)idx; - weights[idx] = one_over_root_2pi_stdev2*pow_f32(euler32, -kernel_x*kernel_x/(2.f*stdev*stdev)); - } - } - if(weights[0] > 1.f) - { - MemoryZeroArray(weights); - weights[0] = 1.f; - } - else - { - // prepare weights & offsets for bilinear lookup - // blur filter wants to calculate w0*pixel[pos] + w1*pixel[pos+1] + ... - // with bilinear filter we can do this calulation by doing only w*sample(pos+t) = w*((1-t)*pixel[pos] + t*pixel[pos+1]) - // we can see w0=w*(1-t) and w1=w*t - // thus w=w0+w1 and t=w1/w - for (U64 idx = 1; idx < blur_count; idx += 2) - { - F32 w0 = weights[idx + 0]; - F32 w1 = weights[idx + 1]; - F32 w = w0 + w1; - F32 t = w1 / w; - - // each kernel element is float2(weight, offset) - // weights & offsets are adjusted for bilinear sampling - // zw elements are not used, a bit of waste but it allows for simpler shader code - uniforms.kernel[(idx+1)/2] = v4f32(w, (F32)idx + t, 0, 0); - } - } - uniforms.kernel[0].x = weights[0]; - - // technically we need just direction be different - // but there are 256 bytes of usable space anyway for each constant buffer chunk - - uniforms.passes[Axis2_X].viewport_size = v2f32(resolution.x, resolution.y); - uniforms.passes[Axis2_X].rect = params->rect; - uniforms.passes[Axis2_X].direction = v2f32(1.f / resolution.x, 0); - uniforms.passes[Axis2_X].blur_count = 1 + blur_count / 2; // 2x smaller because of bilinear sampling - MemoryCopyArray(uniforms.passes[Axis2_X].corner_radii.v, params->corner_radii); - - uniforms.passes[Axis2_Y].viewport_size = v2f32(resolution.x, resolution.y); - uniforms.passes[Axis2_Y].rect = params->rect; - uniforms.passes[Axis2_Y].direction = v2f32(0, 1.f / resolution.y); - uniforms.passes[Axis2_Y].blur_count = 1 + blur_count / 2; // 2x smaller because of bilinear sampling - MemoryCopyArray(uniforms.passes[Axis2_Y].corner_radii.v, params->corner_radii); - - D3D11_MAPPED_SUBRESOURCE sub_rsrc = {0}; - d_ctx->lpVtbl->Map(d_ctx, (ID3D11Resource *)uniforms_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &sub_rsrc); - MemoryCopy((U8 *)sub_rsrc.pData, &uniforms, sizeof(uniforms)); - d_ctx->lpVtbl->Unmap(d_ctx, (ID3D11Resource *)uniforms_buffer, 0); - } - - ID3D11Buffer *uniforms_buffers[] = { uniforms_buffer, uniforms_buffer }; - - U32 uniform_offset[Axis2_COUNT][2] = - { - { 0 * sizeof(R_D3D11_Uniforms_BlurPass) / 16, (U32)OffsetOf(R_D3D11_Uniforms_Blur, kernel) / 16 }, - { 1 * sizeof(R_D3D11_Uniforms_BlurPass) / 16, (U32)OffsetOf(R_D3D11_Uniforms_Blur, kernel) / 16 }, - }; - - U32 uniform_count[Axis2_COUNT][2] = - { - { sizeof(R_D3D11_Uniforms_BlurPass) / 16, sizeof(uniforms.kernel) / 16 }, - { sizeof(R_D3D11_Uniforms_BlurPass) / 16, sizeof(uniforms.kernel) / 16 }, - }; - - // rjf: setup scissor rect - { - Rng2F32 clip = params->clip; - D3D11_RECT rect = {0}; - { - if(clip.x0 == 0 && clip.y0 == 0 && clip.x1 == 0 && clip.y1 == 0) - { - rect.left = 0; - rect.right = (LONG)wnd->last_resolution.x; - rect.top = 0; - rect.bottom = (LONG)wnd->last_resolution.y; - } - else if(clip.x0 > clip.x1 || clip.y0 > clip.y1) - { - rect.left = 0; - rect.right = 0; - rect.top = 0; - rect.bottom = 0; - } - else - { - rect.left = (LONG)clip.x0; - rect.right = (LONG)clip.x1; - rect.top = (LONG)clip.y0; - rect.bottom = (LONG)clip.y1; - } - } - d_ctx->lpVtbl->RSSetScissorRects(d_ctx, 1, &rect); - } - - // rjf: for unsetting srv - ID3D11ShaderResourceView* srv = 0; - - // horizontal pass - d_ctx->lpVtbl->OMSetRenderTargets(d_ctx, 1, &wnd->stage_scratch_color_rtv, 0); - d_ctx->lpVtbl->PSSetConstantBuffers1(d_ctx, 0, ArrayCount(uniforms_buffers), uniforms_buffers, uniform_offset[Axis2_X], uniform_count[Axis2_X]); - d_ctx->lpVtbl->PSSetShaderResources(d_ctx, 0, 1, &wnd->stage_color_srv); - d_ctx->lpVtbl->Draw(d_ctx, 4, 0); - d_ctx->lpVtbl->PSSetShaderResources(d_ctx, 0, 1, &srv); - - // vertical pass - d_ctx->lpVtbl->OMSetRenderTargets(d_ctx, 1, &wnd->stage_color_rtv, 0); - d_ctx->lpVtbl->PSSetConstantBuffers1(d_ctx, 0, ArrayCount(uniforms_buffers), uniforms_buffers, uniform_offset[Axis2_Y], uniform_count[Axis2_Y]); - d_ctx->lpVtbl->PSSetShaderResources(d_ctx, 0, 1, &wnd->stage_scratch_color_srv); - d_ctx->lpVtbl->Draw(d_ctx, 4, 0); - d_ctx->lpVtbl->PSSetShaderResources(d_ctx, 0, 1, &srv); - }break; - - - //////////////////////// - //- rjf: 3d geometry rendering pass - // - case R_PassKind_Geo3D: - { - //- rjf: unpack params - R_PassParams_Geo3D *params = pass->params_geo3d; - R_BatchGroup3DMap *mesh_group_map = ¶ms->mesh_batches; - - //- rjf: set up rasterizer - Vec2F32 viewport_dim = dim_2f32(params->viewport); - D3D11_VIEWPORT viewport = { params->viewport.x0, params->viewport.y0, viewport_dim.x, viewport_dim.y, 0.f, 1.f }; - d_ctx->lpVtbl->RSSetViewports(d_ctx, 1, &viewport); - d_ctx->lpVtbl->RSSetState(d_ctx, (ID3D11RasterizerState *)r_d3d11_state->main_rasterizer); - - //- rjf: clear render targets - { - Vec4F32 bg_color = v4f32(0, 0, 0, 0); - d_ctx->lpVtbl->ClearRenderTargetView(d_ctx, wnd->geo3d_color_rtv, bg_color.v); - d_ctx->lpVtbl->ClearDepthStencilView(d_ctx, wnd->geo3d_depth_dsv, D3D11_CLEAR_DEPTH, 1.f, 0); - } - - //- rjf: draw mesh batches - { - // rjf: grab pipeline info - ID3D11VertexShader *vshad = r_d3d11_state->vshads[R_D3D11_VShadKind_Mesh]; - ID3D11InputLayout *ilay = r_d3d11_state->ilays[R_D3D11_VShadKind_Mesh]; - ID3D11PixelShader *pshad = r_d3d11_state->pshads[R_D3D11_PShadKind_Mesh]; - ID3D11Buffer *uniforms_buffer = r_d3d11_state->uniform_type_kind_buffers[R_D3D11_VShadKind_Mesh]; - - // rjf: setup output merger - d_ctx->lpVtbl->OMSetRenderTargets(d_ctx, 1, &wnd->geo3d_color_rtv, wnd->geo3d_depth_dsv); - d_ctx->lpVtbl->OMSetDepthStencilState(d_ctx, r_d3d11_state->plain_depth_stencil, 0); - d_ctx->lpVtbl->OMSetBlendState(d_ctx, r_d3d11_state->main_blend_state, 0, 0xffffffff); - - // rjf: draw all batches - for(U64 slot_idx = 0; slot_idx < mesh_group_map->slots_count; slot_idx += 1) - { - for(R_BatchGroup3DMapNode *n = mesh_group_map->slots[slot_idx]; n != 0; n = n->next) - { - // rjf: unpack group params - R_BatchList *batches = &n->batches; - R_BatchGroup3DParams *group_params = &n->params; - R_D3D11_Buffer *mesh_vertices = r_d3d11_buffer_from_handle(group_params->mesh_vertices); - R_D3D11_Buffer *mesh_indices = r_d3d11_buffer_from_handle(group_params->mesh_indices); - - // rjf: setup input assembly - U32 stride = 11 * sizeof(F32); - U32 offset = 0; - d_ctx->lpVtbl->IASetPrimitiveTopology(d_ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - d_ctx->lpVtbl->IASetInputLayout(d_ctx, ilay); - d_ctx->lpVtbl->IASetVertexBuffers(d_ctx, 0, 1, &mesh_vertices->buffer, &stride, &offset); - d_ctx->lpVtbl->IASetIndexBuffer(d_ctx, mesh_indices->buffer, DXGI_FORMAT_R32_UINT, 0); - - // rjf: setup uniforms buffer - R_D3D11_Uniforms_Mesh uniforms = {0}; - { - uniforms.xform = mul_4x4f32(params->projection, params->view); - } - { - D3D11_MAPPED_SUBRESOURCE sub_rsrc = {0}; - d_ctx->lpVtbl->Map(d_ctx, (ID3D11Resource *)uniforms_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &sub_rsrc); - MemoryCopy((U8 *)sub_rsrc.pData, &uniforms, sizeof(uniforms)); - d_ctx->lpVtbl->Unmap(d_ctx, (ID3D11Resource *)uniforms_buffer, 0); - } - - - // rjf: setup shaders - d_ctx->lpVtbl->VSSetShader(d_ctx, vshad, 0, 0); - d_ctx->lpVtbl->VSSetConstantBuffers(d_ctx, 0, 1, &uniforms_buffer); - d_ctx->lpVtbl->PSSetShader(d_ctx, pshad, 0, 0); - d_ctx->lpVtbl->PSSetConstantBuffers(d_ctx, 0, 1, &uniforms_buffer); - - // rjf: setup scissor rect - { - D3D11_RECT rect = {0}; - { - rect.left = 0; - rect.right = (LONG)wnd->last_resolution.x; - rect.top = 0; - rect.bottom = (LONG)wnd->last_resolution.y; - } - d_ctx->lpVtbl->RSSetScissorRects(d_ctx, 1, &rect); - } - - // rjf: draw - d_ctx->lpVtbl->DrawIndexed(d_ctx, mesh_indices->size/sizeof(U32), 0, 0); - } - } - } - - //- rjf: composite to main staging buffer - { - ID3D11SamplerState *sampler = r_d3d11_state->samplers[R_Tex2DSampleKind_Nearest]; - ID3D11VertexShader *vshad = r_d3d11_state->vshads[R_D3D11_VShadKind_Geo3DComposite]; - ID3D11PixelShader *pshad = r_d3d11_state->pshads[R_D3D11_PShadKind_Geo3DComposite]; - - // rjf: setup output merger - d_ctx->lpVtbl->OMSetRenderTargets(d_ctx, 1, &wnd->stage_color_rtv, 0); - d_ctx->lpVtbl->OMSetDepthStencilState(d_ctx, r_d3d11_state->noop_depth_stencil, 0); - d_ctx->lpVtbl->OMSetBlendState(d_ctx, r_d3d11_state->main_blend_state, 0, 0xffffffff); - - // rjf: set up rasterizer - Vec2S32 resolution = wnd->last_resolution; - D3D11_VIEWPORT viewport = { 0.0f, 0.0f, (F32)resolution.x, (F32)resolution.y, 0.0f, 1.0f }; - d_ctx->lpVtbl->RSSetViewports(d_ctx, 1, &viewport); - d_ctx->lpVtbl->RSSetState(d_ctx, (ID3D11RasterizerState *)r_d3d11_state->main_rasterizer); - - // rjf: setup input assembly - d_ctx->lpVtbl->IASetPrimitiveTopology(d_ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); - d_ctx->lpVtbl->IASetInputLayout(d_ctx, 0); - - // rjf: setup shaders - d_ctx->lpVtbl->VSSetShader(d_ctx, vshad, 0, 0); - d_ctx->lpVtbl->PSSetShader(d_ctx, pshad, 0, 0); - d_ctx->lpVtbl->PSSetShaderResources(d_ctx, 0, 1, &wnd->geo3d_color_srv); - d_ctx->lpVtbl->PSSetSamplers(d_ctx, 0, 1, &sampler); - - // rjf: setup scissor rect - { - D3D11_RECT rect = {0}; - Rng2F32 clip = params->clip; - if(clip.x0 == 0 && clip.y0 == 0 && clip.x1 == 0 && clip.y1 == 0) - { - rect.left = 0; - rect.right = (LONG)wnd->last_resolution.x; - rect.top = 0; - rect.bottom = (LONG)wnd->last_resolution.y; - } - else if(clip.x0 > clip.x1 || clip.y0 > clip.y1) - { - rect.left = 0; - rect.right = 0; - rect.top = 0; - rect.bottom = 0; - } - else - { - rect.left = (LONG)clip.x0; - rect.right = (LONG)clip.x1; - rect.top = (LONG)clip.y0; - rect.bottom = (LONG)clip.y1; - } - d_ctx->lpVtbl->RSSetScissorRects(d_ctx, 1, &rect); - } - - // rjf: draw - d_ctx->lpVtbl->Draw(d_ctx, 4, 0); - } - }break; - } - } - } - ProfEnd(); -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#undef RADDBG_LAYER_COLOR +#define RADDBG_LAYER_COLOR 0.80f, 0.60f, 0.20f + +//////////////////////////////// +//~ rjf: Input Layout Element Tables + +global D3D11_INPUT_ELEMENT_DESC r_d3d11_g_rect_ilay_elements[] = +{ + { "POS", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, + { "TEX", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, + { "COL", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, + { "COL", 1, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, + { "COL", 2, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, + { "COL", 3, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, + { "CRAD", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, + { "STY", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, +}; + +global D3D11_INPUT_ELEMENT_DESC r_d3d11_g_mesh_ilay_elements[] = +{ + { "POS", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "NOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "TEX", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "COL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, +}; + +//////////////////////////////// +//~ rjf: Generated Code + +#include "generated/render_d3d11.meta.c" + +//////////////////////////////// +//~ rjf: Helpers + +internal R_D3D11_Window * +r_d3d11_window_from_handle(R_Handle handle) +{ + R_D3D11_Window *window = (R_D3D11_Window *)handle.u64[0]; + if(window->generation != handle.u64[1]) + { + window = &r_d3d11_window_nil; + } + return window; +} + +internal R_Handle +r_d3d11_handle_from_window(R_D3D11_Window *window) +{ + R_Handle handle = {0}; + handle.u64[0] = (U64)window; + handle.u64[1] = window->generation; + return handle; +} + +internal R_D3D11_Tex2D * +r_d3d11_tex2d_from_handle(R_Handle handle) +{ + R_D3D11_Tex2D *texture = (R_D3D11_Tex2D *)handle.u64[0]; + if(texture == 0 || texture->generation != handle.u64[1]) + { + texture = &r_d3d11_tex2d_nil; + } + return texture; +} + +internal R_Handle +r_d3d11_handle_from_tex2d(R_D3D11_Tex2D *texture) +{ + R_Handle handle = {0}; + handle.u64[0] = (U64)texture; + handle.u64[1] = texture->generation; + return handle; +} + +internal R_D3D11_Buffer * +r_d3d11_buffer_from_handle(R_Handle handle) +{ + R_D3D11_Buffer *buffer = (R_D3D11_Buffer *)handle.u64[0]; + if(buffer == 0 || buffer->generation != handle.u64[1]) + { + buffer = &r_d3d11_buffer_nil; + } + return buffer; +} + +internal R_Handle +r_d3d11_handle_from_buffer(R_D3D11_Buffer *buffer) +{ + R_Handle handle = {0}; + handle.u64[0] = (U64)buffer; + handle.u64[1] = buffer->generation; + return handle; +} + +internal ID3D11Buffer * +r_d3d11_instance_buffer_from_size(U64 size) +{ + ID3D11Buffer *buffer = r_d3d11_state->instance_scratch_buffer_64kb; + if(size > KB(64)) + { + U64 flushed_buffer_size = size; + flushed_buffer_size += MB(1)-1; + flushed_buffer_size -= flushed_buffer_size%MB(1); + + // rjf: build buffer + { + D3D11_BUFFER_DESC desc = {0}; + { + desc.ByteWidth = flushed_buffer_size; + desc.Usage = D3D11_USAGE_DYNAMIC; + desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + } + HRESULT error = r_d3d11_state->device->lpVtbl->CreateBuffer(r_d3d11_state->device, &desc, 0, &buffer); + } + + // rjf: push buffer to flush list + R_D3D11_FlushBuffer *n = push_array(r_d3d11_state->buffer_flush_arena, R_D3D11_FlushBuffer, 1); + n->buffer = buffer; + SLLQueuePush(r_d3d11_state->first_buffer_to_flush, r_d3d11_state->last_buffer_to_flush, n); + } + return buffer; +} + +internal void +r_usage_access_flags_from_resource_kind(R_ResourceKind kind, D3D11_USAGE *out_d3d11_usage, UINT *out_cpu_access_flags) +{ + switch(kind) + { + case R_ResourceKind_Static: + { + *out_d3d11_usage = D3D11_USAGE_IMMUTABLE; + *out_cpu_access_flags = 0; + }break; + case R_ResourceKind_Dynamic: + { + *out_d3d11_usage = D3D11_USAGE_DEFAULT; + *out_cpu_access_flags = 0; + }break; + case R_ResourceKind_Stream: + { + *out_d3d11_usage = D3D11_USAGE_DYNAMIC; + *out_cpu_access_flags = D3D11_CPU_ACCESS_WRITE; + }break; + default: + { + InvalidPath; + } + } +} + +//////////////////////////////// +//~ rjf: Backend Hook Implementations + +//- rjf: top-level layer initialization + +r_hook void +r_init(CmdLine *cmdln) +{ + ProfBeginFunction(); + HRESULT error = 0; + Arena *arena = arena_alloc(); + r_d3d11_state = push_array(arena, R_D3D11_State, 1); + r_d3d11_state->arena = arena; + r_d3d11_state->device_rw_mutex = os_rw_mutex_alloc(); + + //- rjf: create base device + ProfBegin("create base device"); + UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; +#if BUILD_DEBUG + if(cmd_line_has_flag(cmdln, str8_lit("d3d11_debug"))) + { + creation_flags |= D3D11_CREATE_DEVICE_DEBUG; + } +#endif + D3D_FEATURE_LEVEL feature_levels[] = { D3D_FEATURE_LEVEL_11_0 }; + D3D_DRIVER_TYPE driver_type = D3D_DRIVER_TYPE_HARDWARE; + if(cmd_line_has_flag(cmdln, str8_lit("force_d3d11_software"))) + { + driver_type = D3D_DRIVER_TYPE_WARP; + } + error = D3D11CreateDevice(0, + driver_type, + 0, + creation_flags, + feature_levels, ArrayCount(feature_levels), + D3D11_SDK_VERSION, + &r_d3d11_state->base_device, 0, &r_d3d11_state->base_device_ctx); + if(FAILED(error) && driver_type == D3D_DRIVER_TYPE_HARDWARE) + { + // try with WARP driver as backup solution in case HW device is not available + error = D3D11CreateDevice(0, + D3D_DRIVER_TYPE_WARP, + 0, + creation_flags, + feature_levels, ArrayCount(feature_levels), + D3D11_SDK_VERSION, + &r_d3d11_state->base_device, 0, &r_d3d11_state->base_device_ctx); + } + if(FAILED(error)) + { + char buffer[256] = {0}; + raddbg_snprintf(buffer, sizeof(buffer), "D3D11 device creation failure (%lx). The process is terminating.", error); + os_graphical_message(1, str8_lit("Fatal Error"), str8_cstring(buffer)); + os_abort(1); + } + ProfEnd(); + + //- rjf: enable break-on-error +#if BUILD_DEBUG + if(cmd_line_has_flag(cmdln, str8_lit("d3d11_debug"))) ProfScope("enable break-on-error") + { + ID3D11InfoQueue *info = 0; + error = r_d3d11_state->base_device->lpVtbl->QueryInterface(r_d3d11_state->base_device, &IID_ID3D11InfoQueue, (void **)(&info)); + if(SUCCEEDED(error)) + { + error = info->lpVtbl->SetBreakOnSeverity(info, D3D11_MESSAGE_SEVERITY_CORRUPTION, TRUE); + error = info->lpVtbl->SetBreakOnSeverity(info, D3D11_MESSAGE_SEVERITY_ERROR, TRUE); + info->lpVtbl->Release(info); + } + } +#endif + + //- rjf: get main device + ProfBegin("get main device"); + error = r_d3d11_state->base_device->lpVtbl->QueryInterface(r_d3d11_state->base_device, &IID_ID3D11Device1, (void **)(&r_d3d11_state->device)); + error = r_d3d11_state->base_device_ctx->lpVtbl->QueryInterface(r_d3d11_state->base_device_ctx, &IID_ID3D11DeviceContext1, (void **)(&r_d3d11_state->device_ctx)); + ProfEnd(); + + //- rjf: get dxgi device/adapter/factory + ProfBegin("get dxgi device/adapter/factory"); + error = r_d3d11_state->device->lpVtbl->QueryInterface(r_d3d11_state->device, &IID_IDXGIDevice1, (void **)(&r_d3d11_state->dxgi_device)); + error = r_d3d11_state->dxgi_device->lpVtbl->GetAdapter(r_d3d11_state->dxgi_device, &r_d3d11_state->dxgi_adapter); + error = r_d3d11_state->dxgi_adapter->lpVtbl->GetParent(r_d3d11_state->dxgi_adapter, &IID_IDXGIFactory2, (void **)(&r_d3d11_state->dxgi_factory)); + ProfEnd(); + + //- rjf: create main rasterizer + ProfScope("create main rasterizer") + { + D3D11_RASTERIZER_DESC1 desc = {D3D11_FILL_SOLID}; + { + desc.FillMode = D3D11_FILL_SOLID; + desc.CullMode = D3D11_CULL_BACK; + desc.ScissorEnable = 1; + } + error = r_d3d11_state->device->lpVtbl->CreateRasterizerState1(r_d3d11_state->device, &desc, &r_d3d11_state->main_rasterizer); + } + + //- rjf: create main blend state + ProfScope("create main blend state") + { + D3D11_BLEND_DESC desc = {0}; + { + desc.RenderTarget[0].BlendEnable = 1; + desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; + desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; + desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + } + error = r_d3d11_state->device->lpVtbl->CreateBlendState(r_d3d11_state->device, &desc, &r_d3d11_state->main_blend_state); + } + + //- rjf: create empty blend state + ProfScope("create empty blend state") + { + D3D11_BLEND_DESC desc = {0}; + { + desc.RenderTarget[0].BlendEnable = FALSE; + desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + } + error = r_d3d11_state->device->lpVtbl->CreateBlendState(r_d3d11_state->device, &desc, &r_d3d11_state->no_blend_state); + } + + //- rjf: create nearest-neighbor sampler + ProfScope("create nearest-neighbor sampler") + { + D3D11_SAMPLER_DESC desc = zero_struct; + { + desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; + desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; + desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; + desc.ComparisonFunc = D3D11_COMPARISON_NEVER; + } + error = r_d3d11_state->device->lpVtbl->CreateSamplerState(r_d3d11_state->device, &desc, &r_d3d11_state->samplers[R_Tex2DSampleKind_Nearest]); + } + + //- rjf: create bilinear sampler + ProfScope("create bilinear sampler") + { + D3D11_SAMPLER_DESC desc = zero_struct; + { + desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; + desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; + desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; + desc.ComparisonFunc = D3D11_COMPARISON_NEVER; + } + error = r_d3d11_state->device->lpVtbl->CreateSamplerState(r_d3d11_state->device, &desc, &r_d3d11_state->samplers[R_Tex2DSampleKind_Linear]); + } + + //- rjf: create noop depth/stencil state + ProfScope("create noop depth/stencil state") + { + D3D11_DEPTH_STENCIL_DESC desc = {0}; + { + desc.DepthEnable = FALSE; + desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; + desc.DepthFunc = D3D11_COMPARISON_LESS; + } + error = r_d3d11_state->device->lpVtbl->CreateDepthStencilState(r_d3d11_state->device, &desc, &r_d3d11_state->noop_depth_stencil); + } + + //- rjf: create plain depth/stencil state + ProfScope("create plain depth/stencil state") + { + D3D11_DEPTH_STENCIL_DESC desc = {0}; + { + desc.DepthEnable = TRUE; + desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; + desc.DepthFunc = D3D11_COMPARISON_LESS; + } + error = r_d3d11_state->device->lpVtbl->CreateDepthStencilState(r_d3d11_state->device, &desc, &r_d3d11_state->plain_depth_stencil); + } + + //- rjf: create buffers + ProfScope("create buffers") + { + D3D11_BUFFER_DESC desc = {0}; + { + desc.ByteWidth = KB(64); + desc.Usage = D3D11_USAGE_DYNAMIC; + desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + } + error = r_d3d11_state->device->lpVtbl->CreateBuffer(r_d3d11_state->device, &desc, 0, &r_d3d11_state->instance_scratch_buffer_64kb); + } + + //- rjf: build vertex shaders & input layouts + ProfScope("build vertex shaders & input layouts") + for(R_D3D11_VShadKind kind = (R_D3D11_VShadKind)0; + kind < R_D3D11_VShadKind_COUNT; + kind = (R_D3D11_VShadKind)(kind+1)) + { + String8 source = *r_d3d11_g_vshad_kind_source_table[kind]; + String8 source_name = r_d3d11_g_vshad_kind_source_name_table[kind]; + D3D11_INPUT_ELEMENT_DESC *ilay_elements = r_d3d11_g_vshad_kind_elements_ptr_table[kind]; + U64 ilay_elements_count = r_d3d11_g_vshad_kind_elements_count_table[kind]; + + // rjf: compile vertex shader + ID3DBlob *vshad_source_blob = 0; + ID3DBlob *vshad_source_errors = 0; + ID3D11VertexShader *vshad = 0; + ProfScope("compile vertex shader") + { + error = D3DCompile(source.str, + source.size, + (char *)source_name.str, + 0, + 0, + "vs_main", + "vs_5_0", + 0, + 0, + &vshad_source_blob, + &vshad_source_errors); + String8 errors = {0}; + if(FAILED(error)) + { + errors = str8((U8 *)vshad_source_errors->lpVtbl->GetBufferPointer(vshad_source_errors), + (U64)vshad_source_errors->lpVtbl->GetBufferSize(vshad_source_errors)); + os_graphical_message(1, str8_lit("Vertex Shader Compilation Failure"), errors); + } + else + { + error = r_d3d11_state->device->lpVtbl->CreateVertexShader(r_d3d11_state->device, vshad_source_blob->lpVtbl->GetBufferPointer(vshad_source_blob), vshad_source_blob->lpVtbl->GetBufferSize(vshad_source_blob), 0, &vshad); + } + } + + // rjf: make input layout + ID3D11InputLayout *ilay = 0; + if(ilay_elements != 0) + { + error = r_d3d11_state->device->lpVtbl->CreateInputLayout(r_d3d11_state->device, ilay_elements, ilay_elements_count, + vshad_source_blob->lpVtbl->GetBufferPointer(vshad_source_blob), + vshad_source_blob->lpVtbl->GetBufferSize(vshad_source_blob), + &ilay); + } + + vshad_source_blob->lpVtbl->Release(vshad_source_blob); + + // rjf: store + r_d3d11_state->vshads[kind] = vshad; + r_d3d11_state->ilays[kind] = ilay; + } + + //- rjf: build pixel shaders + for(R_D3D11_PShadKind kind = (R_D3D11_PShadKind)0; + kind < R_D3D11_PShadKind_COUNT; + kind = (R_D3D11_PShadKind)(kind+1)) + { + String8 source = *r_d3d11_g_pshad_kind_source_table[kind]; + String8 source_name = r_d3d11_g_pshad_kind_source_name_table[kind]; + + // rjf: compile pixel shader + ID3DBlob *pshad_source_blob = 0; + ID3DBlob *pshad_source_errors = 0; + ID3D11PixelShader *pshad = 0; + ProfScope("compile pixel shader") + { + error = D3DCompile(source.str, + source.size, + (char *)source_name.str, + 0, + 0, + "ps_main", + "ps_5_0", + 0, + 0, + &pshad_source_blob, + &pshad_source_errors); + String8 errors = {0}; + if(FAILED(error)) + { + errors = str8((U8 *)pshad_source_errors->lpVtbl->GetBufferPointer(pshad_source_errors), + (U64)pshad_source_errors->lpVtbl->GetBufferSize(pshad_source_errors)); + os_graphical_message(1, str8_lit("Pixel Shader Compilation Failure"), errors); + } + else + { + error = r_d3d11_state->device->lpVtbl->CreatePixelShader(r_d3d11_state->device, pshad_source_blob->lpVtbl->GetBufferPointer(pshad_source_blob), pshad_source_blob->lpVtbl->GetBufferSize(pshad_source_blob), 0, &pshad); + } + } + + pshad_source_blob->lpVtbl->Release(pshad_source_blob); + + // rjf: store + r_d3d11_state->pshads[kind] = pshad; + } + + //- rjf: build uniform type buffers + ProfScope("build uniform type buffers") + for(R_D3D11_UniformTypeKind kind = (R_D3D11_UniformTypeKind)0; + kind < R_D3D11_UniformTypeKind_COUNT; + kind = (R_D3D11_UniformTypeKind)(kind+1)) + { + ID3D11Buffer *buffer = 0; + { + D3D11_BUFFER_DESC desc = {0}; + { + desc.ByteWidth = r_d3d11_g_uniform_type_kind_size_table[kind]; + desc.ByteWidth += 15; + desc.ByteWidth -= desc.ByteWidth % 16; + desc.Usage = D3D11_USAGE_DYNAMIC; + desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + } + r_d3d11_state->device->lpVtbl->CreateBuffer(r_d3d11_state->device, &desc, 0, &buffer); + } + r_d3d11_state->uniform_type_kind_buffers[kind] = buffer; + } + + //- rjf: create backup texture + ProfScope("create backup texture") + { + U32 backup_texture_data[] = + { + 0xff00ffff, 0x330033ff, + 0x330033ff, 0xff00ffff, + }; + r_d3d11_state->backup_texture = r_tex2d_alloc(R_ResourceKind_Static, v2s32(2, 2), R_Tex2DFormat_RGBA8, backup_texture_data); + } + + //- rjf: initialize buffer flush state + { + r_d3d11_state->buffer_flush_arena = arena_alloc(); + } + + ProfEnd(); +} + +//- rjf: window setup/teardown + +r_hook R_Handle +r_window_equip(OS_Handle handle) +{ + ProfBeginFunction(); + R_Handle result = {0}; + OS_MutexScopeW(r_d3d11_state->device_rw_mutex) + { + //- rjf: allocate per-window-state + R_D3D11_Window *window = r_d3d11_state->first_free_window; + { + if(window == 0) + { + window = push_array(r_d3d11_state->arena, R_D3D11_Window, 1); + } + else + { + U64 gen = window->generation; + SLLStackPop(r_d3d11_state->first_free_window); + MemoryZeroStruct(window); + window->generation = gen; + } + window->generation += 1; + } + + //- rjf: map os window handle -> hwnd + HWND hwnd = {0}; + { + OS_W32_Window *w32_layer_window = os_w32_window_from_handle(handle); + hwnd = os_w32_hwnd_from_window(w32_layer_window); + } + + //- rjf: create swapchain + DXGI_SWAP_CHAIN_DESC1 swapchain_desc = {0}; + { + swapchain_desc.Width = 0; // NOTE(rjf): use window width + swapchain_desc.Height = 0; // NOTE(rjf): use window height + swapchain_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + swapchain_desc.Stereo = FALSE; + swapchain_desc.SampleDesc.Count = 1; + swapchain_desc.SampleDesc.Quality = 0; + swapchain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapchain_desc.BufferCount = 2; + swapchain_desc.Scaling = DXGI_SCALING_STRETCH; + swapchain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; + swapchain_desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; + swapchain_desc.Flags = 0; + } + HRESULT error = r_d3d11_state->dxgi_factory->lpVtbl->CreateSwapChainForHwnd(r_d3d11_state->dxgi_factory, (IUnknown *)r_d3d11_state->device, hwnd, &swapchain_desc, 0, 0, &window->swapchain); + if(FAILED(error)) + { + char buffer[256] = {0}; + raddbg_snprintf(buffer, sizeof(buffer), "DXGI swap chain creation failure (%lx). The process is terminating.", error); + os_graphical_message(1, str8_lit("Fatal Error"), str8_cstring(buffer)); + os_abort(1); + } + + r_d3d11_state->dxgi_factory->lpVtbl->MakeWindowAssociation(r_d3d11_state->dxgi_factory, hwnd, DXGI_MWA_NO_ALT_ENTER); + + //- rjf: create framebuffer & view + window->swapchain->lpVtbl->GetBuffer(window->swapchain, 0, &IID_ID3D11Texture2D, (void **)(&window->framebuffer)); + r_d3d11_state->device->lpVtbl->CreateRenderTargetView(r_d3d11_state->device, (ID3D11Resource *)window->framebuffer, 0, &window->framebuffer_rtv); + + result = r_d3d11_handle_from_window(window); + } + ProfEnd(); + return result; +} + +r_hook void +r_window_unequip(OS_Handle handle, R_Handle equip_handle) +{ + ProfBeginFunction(); + OS_MutexScopeW(r_d3d11_state->device_rw_mutex) + { + R_D3D11_Window *window = r_d3d11_window_from_handle(equip_handle); + window->stage_color_srv->lpVtbl->Release(window->stage_color_srv); + window->stage_color_rtv->lpVtbl->Release(window->stage_color_rtv); + window->stage_color->lpVtbl->Release(window->stage_color); + window->stage_scratch_color_srv->lpVtbl->Release(window->stage_scratch_color_srv); + window->stage_scratch_color_rtv->lpVtbl->Release(window->stage_scratch_color_rtv); + window->stage_scratch_color->lpVtbl->Release(window->stage_scratch_color); + window->framebuffer_rtv->lpVtbl->Release(window->framebuffer_rtv); + window->framebuffer->lpVtbl->Release(window->framebuffer); + window->swapchain->lpVtbl->Release(window->swapchain); + window->generation += 1; + SLLStackPush(r_d3d11_state->first_free_window, window); + } + ProfEnd(); +} + +//- rjf: textures + +r_hook R_Handle +r_tex2d_alloc(R_ResourceKind kind, Vec2S32 size, R_Tex2DFormat format, void *data) +{ + ProfBeginFunction(); + + //- rjf: allocate + R_D3D11_Tex2D *texture = 0; + OS_MutexScopeW(r_d3d11_state->device_rw_mutex) + { + texture = r_d3d11_state->first_free_tex2d; + if(texture == 0) + { + texture = push_array(r_d3d11_state->arena, R_D3D11_Tex2D, 1); + } + else + { + U64 gen = texture->generation; + SLLStackPop(r_d3d11_state->first_free_tex2d); + MemoryZeroStruct(texture); + texture->generation = gen; + } + texture->generation += 1; + } + + D3D11_USAGE d3d11_usage = D3D11_USAGE_DEFAULT; + UINT cpu_access_flags = 0; + r_usage_access_flags_from_resource_kind(kind, &d3d11_usage, &cpu_access_flags); + if (kind == R_ResourceKind_Static) + { + Assert(data != 0 && "static texture must have initial data provided"); + } + + //- rjf: format -> dxgi format + DXGI_FORMAT dxgi_format = DXGI_FORMAT_R8G8B8A8_UNORM; + { + switch(format) + { + default:{}break; + case R_Tex2DFormat_R8: {dxgi_format = DXGI_FORMAT_R8_UNORM;}break; + case R_Tex2DFormat_RG8: {dxgi_format = DXGI_FORMAT_R8G8_UNORM;}break; + case R_Tex2DFormat_RGBA8: {dxgi_format = DXGI_FORMAT_R8G8B8A8_UNORM;}break; + case R_Tex2DFormat_BGRA8: {dxgi_format = DXGI_FORMAT_B8G8R8A8_UNORM;}break; + case R_Tex2DFormat_R16: {dxgi_format = DXGI_FORMAT_R16_UNORM;}break; + case R_Tex2DFormat_RGBA16:{dxgi_format = DXGI_FORMAT_R16G16B16A16_UNORM;}break; + case R_Tex2DFormat_R32: {dxgi_format = DXGI_FORMAT_R32_FLOAT;}break; + case R_Tex2DFormat_RG32: {dxgi_format = DXGI_FORMAT_R32G32_FLOAT;}break; + case R_Tex2DFormat_RGBA32:{dxgi_format = DXGI_FORMAT_R32G32B32A32_FLOAT;}break; + } + } + + //- rjf: prep initial data, if passed + D3D11_SUBRESOURCE_DATA initial_data_ = {0}; + D3D11_SUBRESOURCE_DATA *initial_data = 0; + if(data != 0) + { + initial_data = &initial_data_; + initial_data->pSysMem = data; + initial_data->SysMemPitch = r_tex2d_format_bytes_per_pixel_table[format] * size.x; + } + + //- rjf: create texture + D3D11_TEXTURE2D_DESC texture_desc = {0}; + { + texture_desc.Width = size.x; + texture_desc.Height = size.y; + texture_desc.MipLevels = 1; + texture_desc.ArraySize = 1; + texture_desc.Format = dxgi_format; + texture_desc.SampleDesc.Count = 1; + texture_desc.Usage = d3d11_usage; + texture_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + texture_desc.CPUAccessFlags = cpu_access_flags; + } + r_d3d11_state->device->lpVtbl->CreateTexture2D(r_d3d11_state->device, &texture_desc, initial_data, &texture->texture); + + //- rjf: create texture srv + r_d3d11_state->device->lpVtbl->CreateShaderResourceView(r_d3d11_state->device, (ID3D11Resource *)texture->texture, 0, &texture->view); + + //- rjf: fill basics + { + texture->kind = kind; + texture->size = size; + texture->format = format; + } + + R_Handle result = r_d3d11_handle_from_tex2d(texture); + ProfEnd(); + return result; +} + +r_hook void +r_tex2d_release(R_Handle handle) +{ + ProfBeginFunction(); + OS_MutexScopeW(r_d3d11_state->device_rw_mutex) + { + R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(handle); + SLLStackPush(r_d3d11_state->first_to_free_tex2d, texture); + } + ProfEnd(); +} + +r_hook R_ResourceKind +r_kind_from_tex2d(R_Handle handle) +{ + R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(handle); + return texture->kind; +} + +r_hook Vec2S32 +r_size_from_tex2d(R_Handle handle) +{ + R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(handle); + return texture->size; +} + +r_hook R_Tex2DFormat +r_format_from_tex2d(R_Handle handle) +{ + R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(handle); + return texture->format; +} + +r_hook void +r_fill_tex2d_region(R_Handle handle, Rng2S32 subrect, void *data) +{ + ProfBeginFunction(); + OS_MutexScopeW(r_d3d11_state->device_rw_mutex) + { + R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(handle); + Assert(texture->kind == R_ResourceKind_Dynamic && "only dynamic texture can update region"); + U64 bytes_per_pixel = r_tex2d_format_bytes_per_pixel_table[texture->format]; + Vec2S32 dim = v2s32(subrect.x1 - subrect.x0, subrect.y1 - subrect.y0); + D3D11_BOX dst_box = + { + (UINT)subrect.x0, (UINT)subrect.y0, 0, + (UINT)subrect.x1, (UINT)subrect.y1, 1, + }; + r_d3d11_state->device_ctx->lpVtbl->UpdateSubresource(r_d3d11_state->device_ctx, (ID3D11Resource *)texture->texture, 0, &dst_box, data, dim.x*bytes_per_pixel, 0); + } + ProfEnd(); +} + +//- rjf: buffers + +r_hook R_Handle +r_buffer_alloc(R_ResourceKind kind, U64 size, void *data) +{ + ProfBeginFunction(); + + //- rjf: allocate + R_D3D11_Buffer *buffer = 0; + OS_MutexScopeW(r_d3d11_state->device_rw_mutex) + { + buffer = r_d3d11_state->first_free_buffer; + if(buffer == 0) + { + buffer = push_array(r_d3d11_state->arena, R_D3D11_Buffer, 1); + } + else + { + U64 gen = buffer->generation; + SLLStackPop(r_d3d11_state->first_free_buffer); + MemoryZeroStruct(buffer); + buffer->generation = gen; + } + buffer->generation += 1; + } + + D3D11_USAGE d3d11_usage = D3D11_USAGE_DEFAULT; + UINT cpu_access_flags = 0; + r_usage_access_flags_from_resource_kind(kind, &d3d11_usage, &cpu_access_flags); + if (kind == R_ResourceKind_Static) + { + Assert(data != 0 && "static buffer must have initial data provided"); + } + + //- rjf: prep initial data, if passed + D3D11_SUBRESOURCE_DATA initial_data_ = {0}; + D3D11_SUBRESOURCE_DATA *initial_data = 0; + if(data != 0) + { + initial_data = &initial_data_; + initial_data->pSysMem = data; + } + + //- rjf: create buffer + D3D11_BUFFER_DESC desc = {0}; + { + desc.ByteWidth = size; + desc.Usage = d3d11_usage; + desc.BindFlags = D3D11_BIND_VERTEX_BUFFER|D3D11_BIND_INDEX_BUFFER; + desc.CPUAccessFlags = cpu_access_flags; + } + r_d3d11_state->device->lpVtbl->CreateBuffer(r_d3d11_state->device, &desc, initial_data, &buffer->buffer); + + //- rjf: fill basics + { + buffer->kind = kind; + buffer->size = size; + } + + R_Handle result = r_d3d11_handle_from_buffer(buffer); + ProfEnd(); + return result; +} + +r_hook void +r_buffer_release(R_Handle handle) +{ + ProfBeginFunction(); + OS_MutexScopeW(r_d3d11_state->device_rw_mutex) + { + R_D3D11_Buffer *buffer = r_d3d11_buffer_from_handle(handle); + SLLStackPush(r_d3d11_state->first_to_free_buffer, buffer); + } + ProfEnd(); +} + +//- rjf: frame markers + +r_hook void +r_begin_frame(void) +{ + OS_MutexScopeW(r_d3d11_state->device_rw_mutex) + { + // NOTE(rjf): no-op + } +} + +r_hook void +r_end_frame(void) +{ + OS_MutexScopeW(r_d3d11_state->device_rw_mutex) + { + for(R_D3D11_FlushBuffer *buffer = r_d3d11_state->first_buffer_to_flush; buffer != 0; buffer = buffer->next) + { + buffer->buffer->lpVtbl->Release(buffer->buffer); + } + for(R_D3D11_Tex2D *tex = r_d3d11_state->first_to_free_tex2d, *next = 0; + tex != 0; + tex = next) + { + next = tex->next; + tex->view->lpVtbl->Release(tex->view); + tex->texture->lpVtbl->Release(tex->texture); + tex->generation += 1; + SLLStackPush(r_d3d11_state->first_free_tex2d, tex); + } + for(R_D3D11_Buffer *buf = r_d3d11_state->first_to_free_buffer, *next = 0; + buf != 0; + buf = next) + { + next = buf->next; + buf->buffer->lpVtbl->Release(buf->buffer); + buf->generation += 1; + SLLStackPush(r_d3d11_state->first_free_buffer, buf); + } + arena_clear(r_d3d11_state->buffer_flush_arena); + r_d3d11_state->first_buffer_to_flush = r_d3d11_state->last_buffer_to_flush = 0; + r_d3d11_state->first_to_free_tex2d = 0; + r_d3d11_state->first_to_free_buffer = 0; + } +} + +r_hook void +r_window_begin_frame(OS_Handle window, R_Handle window_equip) +{ + ProfBeginFunction(); + OS_MutexScopeW(r_d3d11_state->device_rw_mutex) + { + R_D3D11_Window *wnd = r_d3d11_window_from_handle(window_equip); + ID3D11DeviceContext1 *d_ctx = r_d3d11_state->device_ctx; + + //- rjf: get resolution + Rng2F32 client_rect = os_client_rect_from_window(window); + Vec2S32 resolution = {(S32)(client_rect.x1 - client_rect.x0), (S32)(client_rect.y1 - client_rect.y0)}; + + //- rjf: resolution change + B32 resize_done = 0; + if(wnd->last_resolution.x != resolution.x || + wnd->last_resolution.y != resolution.y) + { + resize_done = 1; + wnd->last_resolution = resolution; + + // rjf: release screen-sized render target resources, if there + if(wnd->stage_scratch_color_srv){wnd->stage_scratch_color_srv->lpVtbl->Release(wnd->stage_scratch_color_srv);} + if(wnd->stage_scratch_color_rtv){wnd->stage_scratch_color_rtv->lpVtbl->Release(wnd->stage_scratch_color_rtv);} + if(wnd->stage_scratch_color) {wnd->stage_scratch_color->lpVtbl->Release(wnd->stage_scratch_color);} + if(wnd->stage_color_srv) {wnd->stage_color_srv->lpVtbl->Release(wnd->stage_color_srv);} + if(wnd->stage_color_rtv) {wnd->stage_color_rtv->lpVtbl->Release(wnd->stage_color_rtv);} + if(wnd->stage_color) {wnd->stage_color->lpVtbl->Release(wnd->stage_color);} + if(wnd->geo3d_color_srv) {wnd->geo3d_color_srv->lpVtbl->Release(wnd->geo3d_color_srv);} + if(wnd->geo3d_color_rtv) {wnd->geo3d_color_rtv->lpVtbl->Release(wnd->geo3d_color_rtv);} + if(wnd->geo3d_color) {wnd->geo3d_color->lpVtbl->Release(wnd->geo3d_color);} + if(wnd->geo3d_depth_srv) {wnd->geo3d_depth_srv->lpVtbl->Release(wnd->geo3d_depth_srv);} + if(wnd->geo3d_depth_dsv) {wnd->geo3d_depth_dsv->lpVtbl->Release(wnd->geo3d_depth_dsv);} + if(wnd->geo3d_depth) {wnd->geo3d_depth->lpVtbl->Release(wnd->geo3d_depth);} + + // rjf: resize swapchain & main framebuffer + wnd->framebuffer_rtv->lpVtbl->Release(wnd->framebuffer_rtv); + wnd->framebuffer->lpVtbl->Release(wnd->framebuffer); + wnd->swapchain->lpVtbl->ResizeBuffers(wnd->swapchain, 0, 0, 0, DXGI_FORMAT_UNKNOWN, 0); + wnd->swapchain->lpVtbl->GetBuffer(wnd->swapchain, 0, &IID_ID3D11Texture2D, (void **)(&wnd->framebuffer)); + r_d3d11_state->device->lpVtbl->CreateRenderTargetView(r_d3d11_state->device, (ID3D11Resource *)wnd->framebuffer, 0, &wnd->framebuffer_rtv); + + // rjf: create stage color targets + { + D3D11_TEXTURE2D_DESC color_desc = zero_struct; + { + wnd->framebuffer->lpVtbl->GetDesc(wnd->framebuffer, &color_desc); + color_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + color_desc.BindFlags = D3D11_BIND_RENDER_TARGET|D3D11_BIND_SHADER_RESOURCE; + } + D3D11_RENDER_TARGET_VIEW_DESC rtv_desc = zero_struct; + { + rtv_desc.Format = color_desc.Format; + rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + } + D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc = zero_struct; + { + srv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srv_desc.Texture2D.MipLevels = -1; + } + r_d3d11_state->device->lpVtbl->CreateTexture2D(r_d3d11_state->device, &color_desc, 0, &wnd->stage_color); + r_d3d11_state->device->lpVtbl->CreateRenderTargetView(r_d3d11_state->device, (ID3D11Resource *)wnd->stage_color, &rtv_desc, &wnd->stage_color_rtv); + r_d3d11_state->device->lpVtbl->CreateShaderResourceView(r_d3d11_state->device, (ID3D11Resource *)wnd->stage_color, &srv_desc, &wnd->stage_color_srv); + r_d3d11_state->device->lpVtbl->CreateTexture2D(r_d3d11_state->device, &color_desc, 0, &wnd->stage_scratch_color); + r_d3d11_state->device->lpVtbl->CreateRenderTargetView(r_d3d11_state->device, (ID3D11Resource *)wnd->stage_scratch_color, &rtv_desc, &wnd->stage_scratch_color_rtv); + r_d3d11_state->device->lpVtbl->CreateShaderResourceView(r_d3d11_state->device, (ID3D11Resource *)wnd->stage_scratch_color, &srv_desc, &wnd->stage_scratch_color_srv); + } + + // rjf: create geo3d targets + { + D3D11_TEXTURE2D_DESC color_desc = zero_struct; + { + wnd->framebuffer->lpVtbl->GetDesc(wnd->framebuffer, &color_desc); + color_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + color_desc.BindFlags = D3D11_BIND_RENDER_TARGET|D3D11_BIND_SHADER_RESOURCE; + } + D3D11_RENDER_TARGET_VIEW_DESC color_rtv_desc = zero_struct; + { + color_rtv_desc.Format = color_desc.Format; + color_rtv_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + } + D3D11_SHADER_RESOURCE_VIEW_DESC color_srv_desc = zero_struct; + { + color_srv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + color_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + color_srv_desc.Texture2D.MipLevels = -1; + } + D3D11_TEXTURE2D_DESC depth_desc = zero_struct; + { + wnd->framebuffer->lpVtbl->GetDesc(wnd->framebuffer, &depth_desc); + depth_desc.Format = DXGI_FORMAT_R24G8_TYPELESS; + depth_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL|D3D11_BIND_SHADER_RESOURCE; + } + D3D11_DEPTH_STENCIL_VIEW_DESC depth_dsv_desc = zero_struct; + { + depth_dsv_desc.Flags = 0; + depth_dsv_desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; + depth_dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; + depth_dsv_desc.Texture2D.MipSlice = 0; + } + D3D11_SHADER_RESOURCE_VIEW_DESC depth_srv_desc = zero_struct; + { + depth_srv_desc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS; + depth_srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + depth_srv_desc.Texture2D.MostDetailedMip = 0; + depth_srv_desc.Texture2D.MipLevels = -1; + } + r_d3d11_state->device->lpVtbl->CreateTexture2D(r_d3d11_state->device, &color_desc, 0, &wnd->geo3d_color); + r_d3d11_state->device->lpVtbl->CreateRenderTargetView(r_d3d11_state->device, (ID3D11Resource *)wnd->geo3d_color, &color_rtv_desc, &wnd->geo3d_color_rtv); + r_d3d11_state->device->lpVtbl->CreateShaderResourceView(r_d3d11_state->device, (ID3D11Resource *)wnd->geo3d_color, &color_srv_desc, &wnd->geo3d_color_srv); + r_d3d11_state->device->lpVtbl->CreateTexture2D(r_d3d11_state->device, &depth_desc, 0, &wnd->geo3d_depth); + r_d3d11_state->device->lpVtbl->CreateDepthStencilView(r_d3d11_state->device, (ID3D11Resource *)wnd->geo3d_depth, &depth_dsv_desc, &wnd->geo3d_depth_dsv); + r_d3d11_state->device->lpVtbl->CreateShaderResourceView(r_d3d11_state->device, (ID3D11Resource *)wnd->geo3d_depth, &depth_srv_desc, &wnd->geo3d_depth_srv); + } + } + + //- rjf: clear framebuffers + Vec4F32 clear_color = {0, 0, 0, 0}; + d_ctx->lpVtbl->ClearRenderTargetView(d_ctx, wnd->framebuffer_rtv, clear_color.v); + d_ctx->lpVtbl->ClearRenderTargetView(d_ctx, wnd->stage_color_rtv, clear_color.v); + if(resize_done) + { + d_ctx->lpVtbl->Flush(d_ctx); + } + } + ProfEnd(); +} + +r_hook void +r_window_end_frame(OS_Handle window, R_Handle window_equip) +{ + ProfBeginFunction(); + OS_MutexScopeW(r_d3d11_state->device_rw_mutex) + { + R_D3D11_Window *wnd = r_d3d11_window_from_handle(window_equip); + ID3D11DeviceContext1 *d_ctx = r_d3d11_state->device_ctx; + + //////////////////////////// + //- rjf: finalize, by writing staging buffer out to window framebuffer + // + { + ID3D11SamplerState *sampler = r_d3d11_state->samplers[R_Tex2DSampleKind_Nearest]; + ID3D11VertexShader *vshad = r_d3d11_state->vshads[R_D3D11_VShadKind_Finalize]; + ID3D11PixelShader *pshad = r_d3d11_state->pshads[R_D3D11_PShadKind_Finalize]; + + // rjf: setup output merger + d_ctx->lpVtbl->OMSetRenderTargets(d_ctx, 1, &wnd->framebuffer_rtv, 0); + d_ctx->lpVtbl->OMSetDepthStencilState(d_ctx, r_d3d11_state->noop_depth_stencil, 0); + d_ctx->lpVtbl->OMSetBlendState(d_ctx, r_d3d11_state->main_blend_state, 0, 0xffffffff); + + // rjf: set up rasterizer + Vec2S32 resolution = wnd->last_resolution; + D3D11_VIEWPORT viewport = { 0.0f, 0.0f, (F32)resolution.x, (F32)resolution.y, 0.0f, 1.0f }; + d_ctx->lpVtbl->RSSetViewports(d_ctx, 1, &viewport); + d_ctx->lpVtbl->RSSetState(d_ctx, (ID3D11RasterizerState *)r_d3d11_state->main_rasterizer); + + // rjf: setup input assembly + d_ctx->lpVtbl->IASetPrimitiveTopology(d_ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + d_ctx->lpVtbl->IASetInputLayout(d_ctx, 0); + + // rjf: setup shaders + d_ctx->lpVtbl->VSSetShader(d_ctx, vshad, 0, 0); + d_ctx->lpVtbl->PSSetShader(d_ctx, pshad, 0, 0); + d_ctx->lpVtbl->PSSetShaderResources(d_ctx, 0, 1, &wnd->stage_color_srv); + d_ctx->lpVtbl->PSSetSamplers(d_ctx, 0, 1, &sampler); + + // rjf: setup scissor rect + { + D3D11_RECT rect = {0}; + rect.left = 0; + rect.right = (LONG)wnd->last_resolution.x; + rect.top = 0; + rect.bottom = (LONG)wnd->last_resolution.y; + d_ctx->lpVtbl->RSSetScissorRects(d_ctx, 1, &rect); + } + + // rjf: draw + d_ctx->lpVtbl->Draw(d_ctx, 4, 0); + } + + //////////////////////////// + //- rjf: present + // + HRESULT error = wnd->swapchain->lpVtbl->Present(wnd->swapchain, 1, 0); + if(FAILED(error)) + { + char buffer[256] = {0}; + raddbg_snprintf(buffer, sizeof(buffer), "D3D11 present failure (%lx). The process is terminating.", error); + os_graphical_message(1, str8_lit("Fatal Error"), str8_cstring(buffer)); + os_abort(1); + } + d_ctx->lpVtbl->ClearState(d_ctx); + } + ProfEnd(); +} + +//- rjf: render pass submission + +r_hook void +r_window_submit(OS_Handle window, R_Handle window_equip, R_PassList *passes) +{ + ProfBeginFunction(); + OS_MutexScopeW(r_d3d11_state->device_rw_mutex) + { + //////////////////////////// + //- rjf: unpack arguments + // + R_D3D11_Window *wnd = r_d3d11_window_from_handle(window_equip); + ID3D11DeviceContext1 *d_ctx = r_d3d11_state->device_ctx; + + //////////////////////////// + //- rjf: do passes + // + for(R_PassNode *pass_n = passes->first; pass_n != 0; pass_n = pass_n->next) + { + R_Pass *pass = &pass_n->v; + switch(pass->kind) + { + default:{}break; + + //////////////////////// + //- rjf: ui rendering pass + // + case R_PassKind_UI: + { + //- rjf: unpack params + R_PassParams_UI *params = pass->params_ui; + R_BatchGroup2DList *rect_batch_groups = ¶ms->rects; + + //- rjf: set up rasterizer + Vec2S32 resolution = wnd->last_resolution; + D3D11_VIEWPORT viewport = { 0.0f, 0.0f, (F32)resolution.x, (F32)resolution.y, 0.0f, 1.0f }; + d_ctx->lpVtbl->RSSetViewports(d_ctx, 1, &viewport); + d_ctx->lpVtbl->RSSetState(d_ctx, (ID3D11RasterizerState *)r_d3d11_state->main_rasterizer); + + //- rjf: draw each batch group + for(R_BatchGroup2DNode *group_n = rect_batch_groups->first; group_n != 0; group_n = group_n->next) + { + R_BatchList *batches = &group_n->batches; + R_BatchGroup2DParams *group_params = &group_n->params; + + // rjf: unpack pipeline info + ID3D11SamplerState *sampler = r_d3d11_state->samplers[group_params->tex_sample_kind]; + ID3D11VertexShader *vshad = r_d3d11_state->vshads[R_D3D11_VShadKind_Rect]; + ID3D11InputLayout *ilay = r_d3d11_state->ilays[R_D3D11_VShadKind_Rect]; + ID3D11PixelShader *pshad = r_d3d11_state->pshads[R_D3D11_PShadKind_Rect]; + ID3D11Buffer *uniforms_buffer = r_d3d11_state->uniform_type_kind_buffers[R_D3D11_UniformTypeKind_Rect]; + + // rjf: get & fill buffer + ID3D11Buffer *buffer = r_d3d11_instance_buffer_from_size(batches->byte_count); + { + D3D11_MAPPED_SUBRESOURCE sub_rsrc = {0}; + d_ctx->lpVtbl->Map(d_ctx, (ID3D11Resource *)buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &sub_rsrc); + U8 *dst_ptr = (U8 *)sub_rsrc.pData; + U64 off = 0; + for(R_BatchNode *batch_n = batches->first; batch_n != 0; batch_n = batch_n->next) + { + MemoryCopy(dst_ptr+off, batch_n->v.v, batch_n->v.byte_count); + off += batch_n->v.byte_count; + } + d_ctx->lpVtbl->Unmap(d_ctx, (ID3D11Resource *)buffer, 0); + } + + // rjf: get texture + R_Handle texture_handle = group_params->tex; + if(r_handle_match(texture_handle, r_handle_zero())) + { + texture_handle = r_d3d11_state->backup_texture; + } + R_D3D11_Tex2D *texture = r_d3d11_tex2d_from_handle(texture_handle); + + // rjf: get texture sample map matrix, based on format + Vec4F32 texture_sample_channel_map[] = + { + {1, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, 1}, + }; + switch(texture->format) + { + default: break; + case R_Tex2DFormat_R8: + { + MemoryZeroArray(texture_sample_channel_map); + texture_sample_channel_map[0] = v4f32(1, 1, 1, 1); + }break; + } + + // rjf: upload uniforms + R_D3D11_Uniforms_Rect uniforms = {0}; + { + uniforms.viewport_size = v2f32(resolution.x, resolution.y); + uniforms.opacity = 1-group_params->transparency; + MemoryCopyArray(uniforms.texture_sample_channel_map, texture_sample_channel_map); + uniforms.texture_t2d_size = v2f32(texture->size.x, texture->size.y); + uniforms.xform[0] = v4f32(group_params->xform.v[0][0], group_params->xform.v[1][0], group_params->xform.v[2][0], 0); + uniforms.xform[1] = v4f32(group_params->xform.v[0][1], group_params->xform.v[1][1], group_params->xform.v[2][1], 0); + uniforms.xform[2] = v4f32(group_params->xform.v[0][2], group_params->xform.v[1][2], group_params->xform.v[2][2], 0); + Vec2F32 xform_2x2_col0 = v2f32(uniforms.xform[0].x, uniforms.xform[1].x); + Vec2F32 xform_2x2_col1 = v2f32(uniforms.xform[0].y, uniforms.xform[1].y); + uniforms.xform_scale.x = length_2f32(xform_2x2_col0); + uniforms.xform_scale.y = length_2f32(xform_2x2_col1); + } + { + D3D11_MAPPED_SUBRESOURCE sub_rsrc = {0}; + d_ctx->lpVtbl->Map(d_ctx, (ID3D11Resource *)uniforms_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &sub_rsrc); + MemoryCopy((U8 *)sub_rsrc.pData, &uniforms, sizeof(uniforms)); + d_ctx->lpVtbl->Unmap(d_ctx, (ID3D11Resource *)uniforms_buffer, 0); + } + + // rjf: setup output merger + d_ctx->lpVtbl->OMSetRenderTargets(d_ctx, 1, &wnd->stage_color_rtv, 0); + d_ctx->lpVtbl->OMSetDepthStencilState(d_ctx, r_d3d11_state->noop_depth_stencil, 0); + d_ctx->lpVtbl->OMSetBlendState(d_ctx, r_d3d11_state->main_blend_state, 0, 0xffffffff); + + // rjf: setup input assembly + U32 stride = batches->bytes_per_inst; + U32 offset = 0; + d_ctx->lpVtbl->IASetPrimitiveTopology(d_ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + d_ctx->lpVtbl->IASetInputLayout(d_ctx, ilay); + d_ctx->lpVtbl->IASetVertexBuffers(d_ctx, 0, 1, &buffer, &stride, &offset); + + // rjf: setup shaders + d_ctx->lpVtbl->VSSetShader(d_ctx, vshad, 0, 0); + d_ctx->lpVtbl->VSSetConstantBuffers(d_ctx, 0, 1, &uniforms_buffer); + d_ctx->lpVtbl->PSSetShader(d_ctx, pshad, 0, 0); + d_ctx->lpVtbl->PSSetConstantBuffers(d_ctx, 0, 1, &uniforms_buffer); + d_ctx->lpVtbl->PSSetShaderResources(d_ctx, 0, 1, &texture->view); + d_ctx->lpVtbl->PSSetSamplers(d_ctx, 0, 1, &sampler); + + // rjf: setup scissor rect + { + Rng2F32 clip = group_params->clip; + D3D11_RECT rect = {0}; + { + if(clip.x0 == 0 && clip.y0 == 0 && clip.x1 == 0 && clip.y1 == 0) + { + rect.left = 0; + rect.right = (LONG)wnd->last_resolution.x; + rect.top = 0; + rect.bottom = (LONG)wnd->last_resolution.y; + } + else if(clip.x0 > clip.x1 || clip.y0 > clip.y1) + { + rect.left = 0; + rect.right = 0; + rect.top = 0; + rect.bottom = 0; + } + else + { + rect.left = (LONG)clip.x0; + rect.right = (LONG)clip.x1; + rect.top = (LONG)clip.y0; + rect.bottom = (LONG)clip.y1; + } + } + d_ctx->lpVtbl->RSSetScissorRects(d_ctx, 1, &rect); + } + + // rjf: draw + d_ctx->lpVtbl->DrawInstanced(d_ctx, 4, batches->byte_count / batches->bytes_per_inst, 0, 0); + } + }break; + + //////////////////////// + //- rjf: blur rendering pass + // + case R_PassKind_Blur: + { + R_PassParams_Blur *params = pass->params_blur; + ID3D11SamplerState *sampler = r_d3d11_state->samplers[R_Tex2DSampleKind_Linear]; + ID3D11VertexShader *vshad = r_d3d11_state->vshads[R_D3D11_VShadKind_Blur]; + ID3D11PixelShader *pshad = r_d3d11_state->pshads[R_D3D11_PShadKind_Blur]; + ID3D11Buffer *uniforms_buffer = r_d3d11_state->uniform_type_kind_buffers[R_D3D11_VShadKind_Blur]; + + // rjf: setup output merger + d_ctx->lpVtbl->OMSetDepthStencilState(d_ctx, r_d3d11_state->noop_depth_stencil, 0); + d_ctx->lpVtbl->OMSetBlendState(d_ctx, r_d3d11_state->no_blend_state, 0, 0xffffffff); + + // rjf: set up viewport + Vec2S32 resolution = wnd->last_resolution; + D3D11_VIEWPORT viewport = { 0.0f, 0.0f, (F32)resolution.x, (F32)resolution.y, 0.0f, 1.0f }; + d_ctx->lpVtbl->RSSetViewports(d_ctx, 1, &viewport); + d_ctx->lpVtbl->RSSetState(d_ctx, (ID3D11RasterizerState *)r_d3d11_state->main_rasterizer); + + // rjf: setup input assembly + d_ctx->lpVtbl->IASetPrimitiveTopology(d_ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + d_ctx->lpVtbl->IASetInputLayout(d_ctx, 0); + + // rjf: setup shaders + d_ctx->lpVtbl->VSSetShader(d_ctx, vshad, 0, 0); + d_ctx->lpVtbl->VSSetConstantBuffers(d_ctx, 0, 1, &uniforms_buffer); + d_ctx->lpVtbl->PSSetShader(d_ctx, pshad, 0, 0); + d_ctx->lpVtbl->PSSetSamplers(d_ctx, 0, 1, &sampler); + + // rjf: setup scissor rect + { + D3D11_RECT rect = { 0 }; + rect.left = 0; + rect.right = (LONG)wnd->last_resolution.x; + rect.top = 0; + rect.bottom = (LONG)wnd->last_resolution.y; + d_ctx->lpVtbl->RSSetScissorRects(d_ctx, 1, &rect); + } + + // rjf: set up uniforms + R_D3D11_Uniforms_Blur uniforms = { 0 }; + { + F32 weights[ArrayCount(uniforms.kernel)*2] = {0}; + + F32 blur_size = Min(params->blur_size, ArrayCount(weights)); + U64 blur_count = (U64)round_f32(blur_size); + + F32 stdev = (blur_size-1.f)/2.f; + F32 one_over_root_2pi_stdev2 = 1/sqrt_f32(2*pi32*stdev*stdev); + F32 euler32 = 2.718281828459045f; + + weights[0] = 1.f; + if(stdev > 0.f) + { + for(U64 idx = 0; idx < blur_count; idx += 1) + { + F32 kernel_x = (F32)idx; + weights[idx] = one_over_root_2pi_stdev2*pow_f32(euler32, -kernel_x*kernel_x/(2.f*stdev*stdev)); + } + } + if(weights[0] > 1.f) + { + MemoryZeroArray(weights); + weights[0] = 1.f; + } + else + { + // prepare weights & offsets for bilinear lookup + // blur filter wants to calculate w0*pixel[pos] + w1*pixel[pos+1] + ... + // with bilinear filter we can do this calulation by doing only w*sample(pos+t) = w*((1-t)*pixel[pos] + t*pixel[pos+1]) + // we can see w0=w*(1-t) and w1=w*t + // thus w=w0+w1 and t=w1/w + for (U64 idx = 1; idx < blur_count; idx += 2) + { + F32 w0 = weights[idx + 0]; + F32 w1 = weights[idx + 1]; + F32 w = w0 + w1; + F32 t = w1 / w; + + // each kernel element is float2(weight, offset) + // weights & offsets are adjusted for bilinear sampling + // zw elements are not used, a bit of waste but it allows for simpler shader code + uniforms.kernel[(idx+1)/2] = v4f32(w, (F32)idx + t, 0, 0); + } + } + uniforms.kernel[0].x = weights[0]; + + // technically we need just direction be different + // but there are 256 bytes of usable space anyway for each constant buffer chunk + + uniforms.passes[Axis2_X].viewport_size = v2f32(resolution.x, resolution.y); + uniforms.passes[Axis2_X].rect = params->rect; + uniforms.passes[Axis2_X].direction = v2f32(1.f / resolution.x, 0); + uniforms.passes[Axis2_X].blur_count = 1 + blur_count / 2; // 2x smaller because of bilinear sampling + MemoryCopyArray(uniforms.passes[Axis2_X].corner_radii.v, params->corner_radii); + + uniforms.passes[Axis2_Y].viewport_size = v2f32(resolution.x, resolution.y); + uniforms.passes[Axis2_Y].rect = params->rect; + uniforms.passes[Axis2_Y].direction = v2f32(0, 1.f / resolution.y); + uniforms.passes[Axis2_Y].blur_count = 1 + blur_count / 2; // 2x smaller because of bilinear sampling + MemoryCopyArray(uniforms.passes[Axis2_Y].corner_radii.v, params->corner_radii); + + D3D11_MAPPED_SUBRESOURCE sub_rsrc = {0}; + d_ctx->lpVtbl->Map(d_ctx, (ID3D11Resource *)uniforms_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &sub_rsrc); + MemoryCopy((U8 *)sub_rsrc.pData, &uniforms, sizeof(uniforms)); + d_ctx->lpVtbl->Unmap(d_ctx, (ID3D11Resource *)uniforms_buffer, 0); + } + + ID3D11Buffer *uniforms_buffers[] = { uniforms_buffer, uniforms_buffer }; + + U32 uniform_offset[Axis2_COUNT][2] = + { + { 0 * sizeof(R_D3D11_Uniforms_BlurPass) / 16, (U32)OffsetOf(R_D3D11_Uniforms_Blur, kernel) / 16 }, + { 1 * sizeof(R_D3D11_Uniforms_BlurPass) / 16, (U32)OffsetOf(R_D3D11_Uniforms_Blur, kernel) / 16 }, + }; + + U32 uniform_count[Axis2_COUNT][2] = + { + { sizeof(R_D3D11_Uniforms_BlurPass) / 16, sizeof(uniforms.kernel) / 16 }, + { sizeof(R_D3D11_Uniforms_BlurPass) / 16, sizeof(uniforms.kernel) / 16 }, + }; + + // rjf: setup scissor rect + { + Rng2F32 clip = params->clip; + D3D11_RECT rect = {0}; + { + if(clip.x0 == 0 && clip.y0 == 0 && clip.x1 == 0 && clip.y1 == 0) + { + rect.left = 0; + rect.right = (LONG)wnd->last_resolution.x; + rect.top = 0; + rect.bottom = (LONG)wnd->last_resolution.y; + } + else if(clip.x0 > clip.x1 || clip.y0 > clip.y1) + { + rect.left = 0; + rect.right = 0; + rect.top = 0; + rect.bottom = 0; + } + else + { + rect.left = (LONG)clip.x0; + rect.right = (LONG)clip.x1; + rect.top = (LONG)clip.y0; + rect.bottom = (LONG)clip.y1; + } + } + d_ctx->lpVtbl->RSSetScissorRects(d_ctx, 1, &rect); + } + + // rjf: for unsetting srv + ID3D11ShaderResourceView* srv = 0; + + // horizontal pass + d_ctx->lpVtbl->OMSetRenderTargets(d_ctx, 1, &wnd->stage_scratch_color_rtv, 0); + d_ctx->lpVtbl->PSSetConstantBuffers1(d_ctx, 0, ArrayCount(uniforms_buffers), uniforms_buffers, uniform_offset[Axis2_X], uniform_count[Axis2_X]); + d_ctx->lpVtbl->PSSetShaderResources(d_ctx, 0, 1, &wnd->stage_color_srv); + d_ctx->lpVtbl->Draw(d_ctx, 4, 0); + d_ctx->lpVtbl->PSSetShaderResources(d_ctx, 0, 1, &srv); + + // vertical pass + d_ctx->lpVtbl->OMSetRenderTargets(d_ctx, 1, &wnd->stage_color_rtv, 0); + d_ctx->lpVtbl->PSSetConstantBuffers1(d_ctx, 0, ArrayCount(uniforms_buffers), uniforms_buffers, uniform_offset[Axis2_Y], uniform_count[Axis2_Y]); + d_ctx->lpVtbl->PSSetShaderResources(d_ctx, 0, 1, &wnd->stage_scratch_color_srv); + d_ctx->lpVtbl->Draw(d_ctx, 4, 0); + d_ctx->lpVtbl->PSSetShaderResources(d_ctx, 0, 1, &srv); + }break; + + + //////////////////////// + //- rjf: 3d geometry rendering pass + // + case R_PassKind_Geo3D: + { + //- rjf: unpack params + R_PassParams_Geo3D *params = pass->params_geo3d; + R_BatchGroup3DMap *mesh_group_map = ¶ms->mesh_batches; + + //- rjf: set up rasterizer + Vec2F32 viewport_dim = dim_2f32(params->viewport); + D3D11_VIEWPORT viewport = { params->viewport.x0, params->viewport.y0, viewport_dim.x, viewport_dim.y, 0.f, 1.f }; + d_ctx->lpVtbl->RSSetViewports(d_ctx, 1, &viewport); + d_ctx->lpVtbl->RSSetState(d_ctx, (ID3D11RasterizerState *)r_d3d11_state->main_rasterizer); + + //- rjf: clear render targets + { + Vec4F32 bg_color = v4f32(0, 0, 0, 0); + d_ctx->lpVtbl->ClearRenderTargetView(d_ctx, wnd->geo3d_color_rtv, bg_color.v); + d_ctx->lpVtbl->ClearDepthStencilView(d_ctx, wnd->geo3d_depth_dsv, D3D11_CLEAR_DEPTH, 1.f, 0); + } + + //- rjf: draw mesh batches + { + // rjf: grab pipeline info + ID3D11VertexShader *vshad = r_d3d11_state->vshads[R_D3D11_VShadKind_Mesh]; + ID3D11InputLayout *ilay = r_d3d11_state->ilays[R_D3D11_VShadKind_Mesh]; + ID3D11PixelShader *pshad = r_d3d11_state->pshads[R_D3D11_PShadKind_Mesh]; + ID3D11Buffer *uniforms_buffer = r_d3d11_state->uniform_type_kind_buffers[R_D3D11_VShadKind_Mesh]; + + // rjf: setup output merger + d_ctx->lpVtbl->OMSetRenderTargets(d_ctx, 1, &wnd->geo3d_color_rtv, wnd->geo3d_depth_dsv); + d_ctx->lpVtbl->OMSetDepthStencilState(d_ctx, r_d3d11_state->plain_depth_stencil, 0); + d_ctx->lpVtbl->OMSetBlendState(d_ctx, r_d3d11_state->main_blend_state, 0, 0xffffffff); + + // rjf: draw all batches + for(U64 slot_idx = 0; slot_idx < mesh_group_map->slots_count; slot_idx += 1) + { + for(R_BatchGroup3DMapNode *n = mesh_group_map->slots[slot_idx]; n != 0; n = n->next) + { + // rjf: unpack group params + R_BatchList *batches = &n->batches; + R_BatchGroup3DParams *group_params = &n->params; + R_D3D11_Buffer *mesh_vertices = r_d3d11_buffer_from_handle(group_params->mesh_vertices); + R_D3D11_Buffer *mesh_indices = r_d3d11_buffer_from_handle(group_params->mesh_indices); + + // rjf: setup input assembly + U32 stride = 11 * sizeof(F32); + U32 offset = 0; + d_ctx->lpVtbl->IASetPrimitiveTopology(d_ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + d_ctx->lpVtbl->IASetInputLayout(d_ctx, ilay); + d_ctx->lpVtbl->IASetVertexBuffers(d_ctx, 0, 1, &mesh_vertices->buffer, &stride, &offset); + d_ctx->lpVtbl->IASetIndexBuffer(d_ctx, mesh_indices->buffer, DXGI_FORMAT_R32_UINT, 0); + + // rjf: setup uniforms buffer + R_D3D11_Uniforms_Mesh uniforms = {0}; + { + uniforms.xform = mul_4x4f32(params->projection, params->view); + } + { + D3D11_MAPPED_SUBRESOURCE sub_rsrc = {0}; + d_ctx->lpVtbl->Map(d_ctx, (ID3D11Resource *)uniforms_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &sub_rsrc); + MemoryCopy((U8 *)sub_rsrc.pData, &uniforms, sizeof(uniforms)); + d_ctx->lpVtbl->Unmap(d_ctx, (ID3D11Resource *)uniforms_buffer, 0); + } + + + // rjf: setup shaders + d_ctx->lpVtbl->VSSetShader(d_ctx, vshad, 0, 0); + d_ctx->lpVtbl->VSSetConstantBuffers(d_ctx, 0, 1, &uniforms_buffer); + d_ctx->lpVtbl->PSSetShader(d_ctx, pshad, 0, 0); + d_ctx->lpVtbl->PSSetConstantBuffers(d_ctx, 0, 1, &uniforms_buffer); + + // rjf: setup scissor rect + { + D3D11_RECT rect = {0}; + { + rect.left = 0; + rect.right = (LONG)wnd->last_resolution.x; + rect.top = 0; + rect.bottom = (LONG)wnd->last_resolution.y; + } + d_ctx->lpVtbl->RSSetScissorRects(d_ctx, 1, &rect); + } + + // rjf: draw + d_ctx->lpVtbl->DrawIndexed(d_ctx, mesh_indices->size/sizeof(U32), 0, 0); + } + } + } + + //- rjf: composite to main staging buffer + { + ID3D11SamplerState *sampler = r_d3d11_state->samplers[R_Tex2DSampleKind_Nearest]; + ID3D11VertexShader *vshad = r_d3d11_state->vshads[R_D3D11_VShadKind_Geo3DComposite]; + ID3D11PixelShader *pshad = r_d3d11_state->pshads[R_D3D11_PShadKind_Geo3DComposite]; + + // rjf: setup output merger + d_ctx->lpVtbl->OMSetRenderTargets(d_ctx, 1, &wnd->stage_color_rtv, 0); + d_ctx->lpVtbl->OMSetDepthStencilState(d_ctx, r_d3d11_state->noop_depth_stencil, 0); + d_ctx->lpVtbl->OMSetBlendState(d_ctx, r_d3d11_state->main_blend_state, 0, 0xffffffff); + + // rjf: set up rasterizer + Vec2S32 resolution = wnd->last_resolution; + D3D11_VIEWPORT viewport = { 0.0f, 0.0f, (F32)resolution.x, (F32)resolution.y, 0.0f, 1.0f }; + d_ctx->lpVtbl->RSSetViewports(d_ctx, 1, &viewport); + d_ctx->lpVtbl->RSSetState(d_ctx, (ID3D11RasterizerState *)r_d3d11_state->main_rasterizer); + + // rjf: setup input assembly + d_ctx->lpVtbl->IASetPrimitiveTopology(d_ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + d_ctx->lpVtbl->IASetInputLayout(d_ctx, 0); + + // rjf: setup shaders + d_ctx->lpVtbl->VSSetShader(d_ctx, vshad, 0, 0); + d_ctx->lpVtbl->PSSetShader(d_ctx, pshad, 0, 0); + d_ctx->lpVtbl->PSSetShaderResources(d_ctx, 0, 1, &wnd->geo3d_color_srv); + d_ctx->lpVtbl->PSSetSamplers(d_ctx, 0, 1, &sampler); + + // rjf: setup scissor rect + { + D3D11_RECT rect = {0}; + Rng2F32 clip = params->clip; + if(clip.x0 == 0 && clip.y0 == 0 && clip.x1 == 0 && clip.y1 == 0) + { + rect.left = 0; + rect.right = (LONG)wnd->last_resolution.x; + rect.top = 0; + rect.bottom = (LONG)wnd->last_resolution.y; + } + else if(clip.x0 > clip.x1 || clip.y0 > clip.y1) + { + rect.left = 0; + rect.right = 0; + rect.top = 0; + rect.bottom = 0; + } + else + { + rect.left = (LONG)clip.x0; + rect.right = (LONG)clip.x1; + rect.top = (LONG)clip.y0; + rect.bottom = (LONG)clip.y1; + } + d_ctx->lpVtbl->RSSetScissorRects(d_ctx, 1, &rect); + } + + // rjf: draw + d_ctx->lpVtbl->Draw(d_ctx, 4, 0); + } + }break; + } + } + } + ProfEnd(); +} diff --git a/src/task_system/task_system.c b/src/task_system/task_system.c index 15c38a0a..788d8260 100644 --- a/src/task_system/task_system.c +++ b/src/task_system/task_system.c @@ -1,227 +1,227 @@ -//////////////////////////////// -//~ rjf: Basic Type Functions - -internal TS_Ticket -ts_ticket_zero(void) -{ - TS_Ticket ticket = {0}; - return ticket; -} - -internal void -ts_ticket_list_push(Arena *arena, TS_TicketList *list, TS_Ticket ticket) -{ - TS_TicketNode *n = push_array(arena, TS_TicketNode, 1); - n->v = ticket; - SLLQueuePush(list->first, list->last, n); - list->count += 1; -} - -//////////////////////////////// -//~ rjf: Top-Level Layer Initialization - -internal void -ts_init(void) -{ - Arena *arena = arena_alloc(); - ts_shared = push_array(arena, TS_Shared, 1); - ts_shared->arena = arena; - ts_shared->artifact_slots_count = 1024; - ts_shared->artifact_stripes_count = Min(ts_shared->artifact_slots_count, os_logical_core_count()); - ts_shared->artifact_slots = push_array(arena, TS_TaskArtifactSlot, ts_shared->artifact_slots_count); - ts_shared->artifact_stripes = push_array(arena, TS_TaskArtifactStripe, ts_shared->artifact_stripes_count); - for(U64 idx = 0; idx < ts_shared->artifact_stripes_count; idx += 1) - { - ts_shared->artifact_stripes[idx].arena = arena_alloc(); - ts_shared->artifact_stripes[idx].cv = os_condition_variable_alloc(); - ts_shared->artifact_stripes[idx].rw_mutex = os_rw_mutex_alloc(); - } - ts_shared->u2t_ring_size = MB(1); - ts_shared->u2t_ring_base = push_array_no_zero(arena, U8, ts_shared->u2t_ring_size); - ts_shared->u2t_ring_mutex = os_mutex_alloc(); - ts_shared->u2t_ring_cv = os_condition_variable_alloc(); - ts_shared->task_threads_count = os_logical_core_count()-1; - ts_shared->task_threads = push_array(arena, TS_TaskThread, ts_shared->task_threads_count); - for(U64 idx = 0; idx < ts_shared->task_threads_count; idx += 1) - { - ts_shared->task_threads[idx].arena = arena_alloc(); - ts_shared->task_threads[idx].thread = os_launch_thread(ts_task_thread__entry_point, (void *)idx, 0); - } -} - -//////////////////////////////// -//~ rjf: Top-Level Accessors - -internal U64 -ts_thread_count(void) -{ - return ts_shared->task_threads_count; -} - -//////////////////////////////// -//~ rjf: High-Level Task Kickoff / Joining - -internal TS_Ticket -ts_kickoff(TS_TaskFunctionType *entry_point, Arena **optional_arena_ptr, void *p) -{ - ProfBeginFunction(); - - // rjf: obtain number & slot/stripe for next artifact - U64 artifact_num = ins_atomic_u64_inc_eval(&ts_shared->artifact_num_gen); - U64 slot_idx = artifact_num%ts_shared->artifact_slots_count; - U64 stripe_idx = slot_idx%ts_shared->artifact_stripes_count; - TS_TaskArtifactSlot *slot = &ts_shared->artifact_slots[slot_idx]; - TS_TaskArtifactStripe *stripe = &ts_shared->artifact_stripes[stripe_idx]; - - // rjf: allocate artifact - TS_TaskArtifact *artifact = 0; - ProfScope("allocate artifact") - { - OS_MutexScopeW(stripe->rw_mutex) - { - artifact = stripe->free_artifact; - if(artifact != 0) - { - SLLStackPop(stripe->free_artifact); - } - else - { - artifact = push_array_no_zero(stripe->arena, TS_TaskArtifact, 1); - } - artifact->num = artifact_num; - artifact->task_is_done = 0; - artifact->result = 0; - } - } - - // rjf: form ticket out of artifact info - TS_Ticket ticket = {artifact_num, (U64)artifact}; - - // rjf: push task info to task ring buffer - ProfScope("push task info to task ring buffer") - { - OS_MutexScope(ts_shared->u2t_ring_mutex) for(;;) - { - U64 unconsumed_size = ts_shared->u2t_ring_write_pos - ts_shared->u2t_ring_read_pos; - U64 available_size = ts_shared->u2t_ring_size-unconsumed_size; - if(available_size >= sizeof(entry_point) + sizeof(p) + sizeof(ticket)) - { - Arena *task_arena = 0; - if(optional_arena_ptr != 0) - { - task_arena = *optional_arena_ptr; - } - ts_shared->u2t_ring_write_pos += ring_write_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_write_pos, &entry_point); - ts_shared->u2t_ring_write_pos += ring_write_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_write_pos, &task_arena); - ts_shared->u2t_ring_write_pos += ring_write_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_write_pos, &p); - ts_shared->u2t_ring_write_pos += ring_write_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_write_pos, &ticket); - if(optional_arena_ptr != 0) - { - *optional_arena_ptr = 0; - } - break; - } - os_condition_variable_wait(ts_shared->u2t_ring_cv, ts_shared->u2t_ring_mutex, max_U64); - } - os_condition_variable_signal(ts_shared->u2t_ring_cv); - } - - ProfEnd(); - return ticket; -} - -internal void * -ts_join(TS_Ticket ticket, U64 endt_us) -{ - void *result = 0; - U64 artifact_num = ticket.u64[0]; - U64 slot_idx = artifact_num%ts_shared->artifact_slots_count; - U64 stripe_idx = slot_idx%ts_shared->artifact_stripes_count; - TS_TaskArtifactSlot *slot = &ts_shared->artifact_slots[slot_idx]; - TS_TaskArtifactStripe *stripe = &ts_shared->artifact_stripes[stripe_idx]; - TS_TaskArtifact *artifact = (TS_TaskArtifact *)ticket.u64[1]; - if(artifact != 0) - { - OS_MutexScopeR(stripe->rw_mutex) for(;;) - { - B64 task_is_done = artifact->task_is_done; - if(task_is_done) - { - OS_MutexScopeRWPromote(stripe->rw_mutex) - { - result = artifact->result; - SLLStackPush(stripe->free_artifact, artifact); - } - break; - } - if(os_now_microseconds() >= endt_us) - { - break; - } - os_condition_variable_wait_rw_r(stripe->cv, stripe->rw_mutex, endt_us); - } - } - return result; -} - -//////////////////////////////// -//~ rjf: Task Threads - -internal void -ts_u2t_dequeue_task(TS_TaskFunctionType **entry_point_out, Arena **arena_out, void **p_out, TS_Ticket *ticket_out) -{ - OS_MutexScope(ts_shared->u2t_ring_mutex) for(;;) - { - U64 unconsumed_size = ts_shared->u2t_ring_write_pos - ts_shared->u2t_ring_read_pos; - if(unconsumed_size >= sizeof(*entry_point_out) + sizeof(*p_out) + sizeof(*ticket_out)) - { - ts_shared->u2t_ring_read_pos += ring_read_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_read_pos, entry_point_out); - ts_shared->u2t_ring_read_pos += ring_read_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_read_pos, arena_out); - ts_shared->u2t_ring_read_pos += ring_read_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_read_pos, p_out); - ts_shared->u2t_ring_read_pos += ring_read_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_read_pos, ticket_out); - break; - } - os_condition_variable_wait(ts_shared->u2t_ring_cv, ts_shared->u2t_ring_mutex, max_U64); - } - os_condition_variable_broadcast(ts_shared->u2t_ring_cv); -} - -internal void -ts_task_thread__entry_point(void *p) -{ - U64 thread_idx = (U64)p; - ThreadNameF("[ts] task thread #%I64u", thread_idx); - TS_TaskThread *thread = &ts_shared->task_threads[thread_idx]; - for(;;) - { - //- rjf: grab next task - TS_TaskFunctionType *task_function = 0; - Arena *task_arena = 0; - void *task_params = 0; - TS_Ticket task_ticket = {0}; - ts_u2t_dequeue_task(&task_function, &task_arena, &task_params, &task_ticket); - - //- rjf: use task thread's arena if none specified - if(task_arena == 0) - { - task_arena = thread->arena; - } - - //- rjf: run task - void *task_result = task_function(task_arena, thread_idx, task_params); - - //- rjf: store into artifact - U64 artifact_num = task_ticket.u64[0]; - U64 slot_idx = artifact_num%ts_shared->artifact_slots_count; - U64 stripe_idx = slot_idx%ts_shared->artifact_stripes_count; - TS_TaskArtifactSlot *slot = &ts_shared->artifact_slots[slot_idx]; - TS_TaskArtifactStripe *stripe = &ts_shared->artifact_stripes[stripe_idx]; - TS_TaskArtifact *artifact = (TS_TaskArtifact *)task_ticket.u64[1]; - OS_MutexScopeW(stripe->rw_mutex) - { - artifact->task_is_done = 1; - artifact->result = task_result; - } - os_condition_variable_broadcast(stripe->cv); - } -} +//////////////////////////////// +//~ rjf: Basic Type Functions + +internal TS_Ticket +ts_ticket_zero(void) +{ + TS_Ticket ticket = {0}; + return ticket; +} + +internal void +ts_ticket_list_push(Arena *arena, TS_TicketList *list, TS_Ticket ticket) +{ + TS_TicketNode *n = push_array(arena, TS_TicketNode, 1); + n->v = ticket; + SLLQueuePush(list->first, list->last, n); + list->count += 1; +} + +//////////////////////////////// +//~ rjf: Top-Level Layer Initialization + +internal void +ts_init(void) +{ + Arena *arena = arena_alloc(); + ts_shared = push_array(arena, TS_Shared, 1); + ts_shared->arena = arena; + ts_shared->artifact_slots_count = 1024; + ts_shared->artifact_stripes_count = Min(ts_shared->artifact_slots_count, os_get_system_info()->logical_processor_count); + ts_shared->artifact_slots = push_array(arena, TS_TaskArtifactSlot, ts_shared->artifact_slots_count); + ts_shared->artifact_stripes = push_array(arena, TS_TaskArtifactStripe, ts_shared->artifact_stripes_count); + for(U64 idx = 0; idx < ts_shared->artifact_stripes_count; idx += 1) + { + ts_shared->artifact_stripes[idx].arena = arena_alloc(); + ts_shared->artifact_stripes[idx].cv = os_condition_variable_alloc(); + ts_shared->artifact_stripes[idx].rw_mutex = os_rw_mutex_alloc(); + } + ts_shared->u2t_ring_size = MB(1); + ts_shared->u2t_ring_base = push_array_no_zero(arena, U8, ts_shared->u2t_ring_size); + ts_shared->u2t_ring_mutex = os_mutex_alloc(); + ts_shared->u2t_ring_cv = os_condition_variable_alloc(); + ts_shared->task_threads_count = os_get_system_info()->logical_processor_count-1; + ts_shared->task_threads = push_array(arena, TS_TaskThread, ts_shared->task_threads_count); + for(U64 idx = 0; idx < ts_shared->task_threads_count; idx += 1) + { + ts_shared->task_threads[idx].arena = arena_alloc(); + ts_shared->task_threads[idx].thread = os_thread_launch(ts_task_thread__entry_point, (void *)idx, 0); + } +} + +//////////////////////////////// +//~ rjf: Top-Level Accessors + +internal U64 +ts_thread_count(void) +{ + return ts_shared->task_threads_count; +} + +//////////////////////////////// +//~ rjf: High-Level Task Kickoff / Joining + +internal TS_Ticket +ts_kickoff(TS_TaskFunctionType *entry_point, Arena **optional_arena_ptr, void *p) +{ + ProfBeginFunction(); + + // rjf: obtain number & slot/stripe for next artifact + U64 artifact_num = ins_atomic_u64_inc_eval(&ts_shared->artifact_num_gen); + U64 slot_idx = artifact_num%ts_shared->artifact_slots_count; + U64 stripe_idx = slot_idx%ts_shared->artifact_stripes_count; + TS_TaskArtifactSlot *slot = &ts_shared->artifact_slots[slot_idx]; + TS_TaskArtifactStripe *stripe = &ts_shared->artifact_stripes[stripe_idx]; + + // rjf: allocate artifact + TS_TaskArtifact *artifact = 0; + ProfScope("allocate artifact") + { + OS_MutexScopeW(stripe->rw_mutex) + { + artifact = stripe->free_artifact; + if(artifact != 0) + { + SLLStackPop(stripe->free_artifact); + } + else + { + artifact = push_array_no_zero(stripe->arena, TS_TaskArtifact, 1); + } + artifact->num = artifact_num; + artifact->task_is_done = 0; + artifact->result = 0; + } + } + + // rjf: form ticket out of artifact info + TS_Ticket ticket = {artifact_num, (U64)artifact}; + + // rjf: push task info to task ring buffer + ProfScope("push task info to task ring buffer") + { + OS_MutexScope(ts_shared->u2t_ring_mutex) for(;;) + { + U64 unconsumed_size = ts_shared->u2t_ring_write_pos - ts_shared->u2t_ring_read_pos; + U64 available_size = ts_shared->u2t_ring_size-unconsumed_size; + if(available_size >= sizeof(entry_point) + sizeof(p) + sizeof(ticket)) + { + Arena *task_arena = 0; + if(optional_arena_ptr != 0) + { + task_arena = *optional_arena_ptr; + } + ts_shared->u2t_ring_write_pos += ring_write_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_write_pos, &entry_point); + ts_shared->u2t_ring_write_pos += ring_write_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_write_pos, &task_arena); + ts_shared->u2t_ring_write_pos += ring_write_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_write_pos, &p); + ts_shared->u2t_ring_write_pos += ring_write_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_write_pos, &ticket); + if(optional_arena_ptr != 0) + { + *optional_arena_ptr = 0; + } + break; + } + os_condition_variable_wait(ts_shared->u2t_ring_cv, ts_shared->u2t_ring_mutex, max_U64); + } + os_condition_variable_signal(ts_shared->u2t_ring_cv); + } + + ProfEnd(); + return ticket; +} + +internal void * +ts_join(TS_Ticket ticket, U64 endt_us) +{ + void *result = 0; + U64 artifact_num = ticket.u64[0]; + U64 slot_idx = artifact_num%ts_shared->artifact_slots_count; + U64 stripe_idx = slot_idx%ts_shared->artifact_stripes_count; + TS_TaskArtifactSlot *slot = &ts_shared->artifact_slots[slot_idx]; + TS_TaskArtifactStripe *stripe = &ts_shared->artifact_stripes[stripe_idx]; + TS_TaskArtifact *artifact = (TS_TaskArtifact *)ticket.u64[1]; + if(artifact != 0) + { + OS_MutexScopeR(stripe->rw_mutex) for(;;) + { + B64 task_is_done = artifact->task_is_done; + if(task_is_done) + { + OS_MutexScopeRWPromote(stripe->rw_mutex) + { + result = artifact->result; + SLLStackPush(stripe->free_artifact, artifact); + } + break; + } + if(os_now_microseconds() >= endt_us) + { + break; + } + os_condition_variable_wait_rw_r(stripe->cv, stripe->rw_mutex, endt_us); + } + } + return result; +} + +//////////////////////////////// +//~ rjf: Task Threads + +internal void +ts_u2t_dequeue_task(TS_TaskFunctionType **entry_point_out, Arena **arena_out, void **p_out, TS_Ticket *ticket_out) +{ + OS_MutexScope(ts_shared->u2t_ring_mutex) for(;;) + { + U64 unconsumed_size = ts_shared->u2t_ring_write_pos - ts_shared->u2t_ring_read_pos; + if(unconsumed_size >= sizeof(*entry_point_out) + sizeof(*p_out) + sizeof(*ticket_out)) + { + ts_shared->u2t_ring_read_pos += ring_read_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_read_pos, entry_point_out); + ts_shared->u2t_ring_read_pos += ring_read_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_read_pos, arena_out); + ts_shared->u2t_ring_read_pos += ring_read_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_read_pos, p_out); + ts_shared->u2t_ring_read_pos += ring_read_struct(ts_shared->u2t_ring_base, ts_shared->u2t_ring_size, ts_shared->u2t_ring_read_pos, ticket_out); + break; + } + os_condition_variable_wait(ts_shared->u2t_ring_cv, ts_shared->u2t_ring_mutex, max_U64); + } + os_condition_variable_broadcast(ts_shared->u2t_ring_cv); +} + +internal void +ts_task_thread__entry_point(void *p) +{ + U64 thread_idx = (U64)p; + ThreadNameF("[ts] task thread #%I64u", thread_idx); + TS_TaskThread *thread = &ts_shared->task_threads[thread_idx]; + for(;;) + { + //- rjf: grab next task + TS_TaskFunctionType *task_function = 0; + Arena *task_arena = 0; + void *task_params = 0; + TS_Ticket task_ticket = {0}; + ts_u2t_dequeue_task(&task_function, &task_arena, &task_params, &task_ticket); + + //- rjf: use task thread's arena if none specified + if(task_arena == 0) + { + task_arena = thread->arena; + } + + //- rjf: run task + void *task_result = task_function(task_arena, thread_idx, task_params); + + //- rjf: store into artifact + U64 artifact_num = task_ticket.u64[0]; + U64 slot_idx = artifact_num%ts_shared->artifact_slots_count; + U64 stripe_idx = slot_idx%ts_shared->artifact_stripes_count; + TS_TaskArtifactSlot *slot = &ts_shared->artifact_slots[slot_idx]; + TS_TaskArtifactStripe *stripe = &ts_shared->artifact_stripes[stripe_idx]; + TS_TaskArtifact *artifact = (TS_TaskArtifact *)task_ticket.u64[1]; + OS_MutexScopeW(stripe->rw_mutex) + { + artifact->task_is_done = 1; + artifact->result = task_result; + } + os_condition_variable_broadcast(stripe->cv); + } +} diff --git a/src/text_cache/text_cache.c b/src/text_cache/text_cache.c index 3e8b7d60..e5e41d03 100644 --- a/src/text_cache/text_cache.c +++ b/src/text_cache/text_cache.c @@ -1,2386 +1,2386 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Basic Helpers - -internal TXT_LangKind -txt_lang_kind_from_extension(String8 extension) -{ - TXT_LangKind kind = TXT_LangKind_Null; - if(str8_match(extension, str8_lit("c"), 0) || - str8_match(extension, str8_lit("h"), 0)) - { - kind = TXT_LangKind_C; - } - else if(str8_match(extension, str8_lit("cpp"), StringMatchFlag_CaseInsensitive) || - str8_match(extension, str8_lit("cxx"), StringMatchFlag_CaseInsensitive) || - str8_match(extension, str8_lit("cc"), StringMatchFlag_CaseInsensitive) || - str8_match(extension, str8_lit("c++"), StringMatchFlag_CaseInsensitive) || - str8_match(extension, str8_lit("ixx"), StringMatchFlag_CaseInsensitive) || - str8_match(extension, str8_lit("cxxm"), StringMatchFlag_CaseInsensitive) || - str8_match(extension, str8_lit("c++m"), StringMatchFlag_CaseInsensitive) || - str8_match(extension, str8_lit("ccm"), StringMatchFlag_CaseInsensitive) || - str8_match(extension, str8_lit("cppm"), StringMatchFlag_CaseInsensitive) || - str8_match(extension, str8_lit("mpp"), StringMatchFlag_CaseInsensitive) || - str8_match(extension, str8_lit("C"), 0) || - str8_match(extension, str8_lit("hpp"), StringMatchFlag_CaseInsensitive) || - str8_match(extension, str8_lit("hxx"), StringMatchFlag_CaseInsensitive) || - str8_match(extension, str8_lit("hh"), StringMatchFlag_CaseInsensitive) || - str8_match(extension, str8_lit("h++"), StringMatchFlag_CaseInsensitive) || - str8_match(extension, str8_lit("H"), 0)) - { - kind = TXT_LangKind_CPlusPlus; - } - else if(str8_match(extension, str8_lit("odin"), StringMatchFlag_CaseInsensitive)) - { - kind = TXT_LangKind_Odin; - } - else if(str8_match(extension, str8_lit("jai"), StringMatchFlag_CaseInsensitive)) - { - kind = TXT_LangKind_Jai; - } - else if(str8_match(extension, str8_lit("zig"), StringMatchFlag_CaseInsensitive)) - { - kind = TXT_LangKind_Zig; - } - return kind; -} - -internal String8 -txt_extension_from_lang_kind(TXT_LangKind kind) -{ - String8 result = {0}; - switch(kind) - { - case TXT_LangKind_Null: - case TXT_LangKind_COUNT: - case TXT_LangKind_DisasmX64Intel: - {}break; - case TXT_LangKind_C: {result = str8_lit("c");}break; - case TXT_LangKind_CPlusPlus: {result = str8_lit("cpp");}break; - case TXT_LangKind_Odin: {result = str8_lit("odin");}break; - case TXT_LangKind_Jai: {result = str8_lit("jai");}break; - case TXT_LangKind_Zig: {result = str8_lit("zig");}break; - } - return result; -} - -internal TXT_LangKind -txt_lang_kind_from_architecture(Architecture arch) -{ - TXT_LangKind kind = TXT_LangKind_Null; - switch(arch) - { - default:{}break; - case Architecture_x64:{kind = TXT_LangKind_DisasmX64Intel;}break; - } - return kind; -} - -internal TXT_LangLexFunctionType * -txt_lex_function_from_lang_kind(TXT_LangKind kind) -{ - TXT_LangLexFunctionType *fn = 0; - switch(kind) - { - default:{}break; - case TXT_LangKind_C: {fn = txt_token_array_from_string__c_cpp;}break; - case TXT_LangKind_CPlusPlus: {fn = txt_token_array_from_string__c_cpp;}break; - case TXT_LangKind_Odin: {fn = txt_token_array_from_string__odin;}break; - case TXT_LangKind_Jai: {fn = txt_token_array_from_string__jai;}break; - case TXT_LangKind_Zig: {fn = txt_token_array_from_string__zig;}break; - case TXT_LangKind_DisasmX64Intel:{fn = txt_token_array_from_string__disasm_x64_intel;}break; - } - return fn; -} - -//////////////////////////////// -//~ rjf: Token Type Functions - -internal void -txt_token_chunk_list_push(Arena *arena, TXT_TokenChunkList *list, U64 cap, TXT_Token *token) -{ - TXT_TokenChunkNode *node = list->last; - if(node == 0 || node->count >= node->cap) - { - node = push_array(arena, TXT_TokenChunkNode, 1); - SLLQueuePush(list->first, list->last, node); - node->cap = cap; - node->v = push_array_no_zero(arena, TXT_Token, node->cap); - list->chunk_count += 1; - } - MemoryCopyStruct(&node->v[node->count], token); - node->count += 1; - list->token_count += 1; -} - -internal void -txt_token_list_push(Arena *arena, TXT_TokenList *list, TXT_Token *token) -{ - TXT_TokenNode *node = push_array(arena, TXT_TokenNode, 1); - MemoryCopyStruct(&node->v, token); - SLLQueuePush(list->first, list->last, node); - list->count += 1; -} - -internal TXT_TokenArray -txt_token_array_from_chunk_list(Arena *arena, TXT_TokenChunkList *list) -{ - TXT_TokenArray array = {0}; - array.count = list->token_count; - array.v = push_array_no_zero(arena, TXT_Token, array.count); - U64 idx = 0; - for(TXT_TokenChunkNode *n = list->first; n != 0; n = n->next) - { - MemoryCopy(array.v+idx, n->v, n->count*sizeof(TXT_Token)); - idx += n->count; - } - return array; -} - -internal TXT_TokenArray -txt_token_array_from_list(Arena *arena, TXT_TokenList *list) -{ - TXT_TokenArray array = {0}; - array.count = list->count; - array.v = push_array_no_zero(arena, TXT_Token, array.count); - U64 idx = 0; - for(TXT_TokenNode *n = list->first; n != 0; n = n->next) - { - MemoryCopyStruct(array.v+idx, &n->v); - idx += 1; - } - return array; -} - -//////////////////////////////// -//~ rjf: Lexing Functions - -internal TXT_TokenArray -txt_token_array_from_string__c_cpp(Arena *arena, U64 *bytes_processed_counter, String8 string) -{ - Temp scratch = scratch_begin(&arena, 1); - - //- rjf: generate token list - TXT_TokenChunkList tokens = {0}; - { - B32 comment_is_single_line = 0; - B32 string_is_char = 0; - TXT_TokenKind active_token_kind = TXT_TokenKind_Null; - U64 active_token_start_idx = 0; - B32 escaped = 0; - B32 next_escaped = 0; - U64 byte_process_start_idx = 0; - for(U64 idx = 0; idx <= string.size;) - { - U8 byte = (idx+0 < string.size) ? (string.str[idx+0]) : 0; - U8 next_byte = (idx+1 < string.size) ? (string.str[idx+1]) : 0; - - // rjf: update counter - if(bytes_processed_counter != 0 && ((idx-byte_process_start_idx) >= 1000 || idx == string.size)) - { - ins_atomic_u64_add_eval(bytes_processed_counter, (idx-byte_process_start_idx)); - byte_process_start_idx = idx; - } - - // rjf: escaping - if(escaped && (byte != '\r' && byte != '\n')) - { - next_escaped = 0; - } - else if(!escaped && byte == '\\') - { - next_escaped = 1; - } - - // rjf: take starter, determine active token kind - if(active_token_kind == TXT_TokenKind_Null) - { - // rjf: use next bytes to start a new token - if(0){} - else if(char_is_space(byte)) { active_token_kind = TXT_TokenKind_Whitespace; } - else if(byte == '_' || - byte == '$' || - char_is_alpha(byte)) { active_token_kind = TXT_TokenKind_Identifier; } - else if(char_is_digit(byte, 10) || - (byte == '.' && - 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 == '~' || 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; } - else if(byte == '#') { active_token_kind = TXT_TokenKind_Meta; } - - // rjf: start new token - if(active_token_kind != TXT_TokenKind_Null) - { - active_token_start_idx = idx; - } - - // rjf: invalid token kind -> emit error - else - { - TXT_Token token = {TXT_TokenKind_Error, r1u64(idx, idx+1)}; - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - } - - // rjf: look for ender - U64 ender_pad = 0; - B32 ender_found = 0; - if(active_token_kind != TXT_TokenKind_Null && idx>active_token_start_idx) - { - if(idx == string.size) - { - ender_pad = 0; - ender_found = 1; - } - else switch(active_token_kind) - { - default:break; - case TXT_TokenKind_Whitespace: - { - ender_found = !char_is_space(byte); - }break; - case TXT_TokenKind_Identifier: - { - ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '$'); - }break; - case TXT_TokenKind_Numeric: - { - ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '.' && byte != '\''); - }break; - case TXT_TokenKind_String: - { - ender_found = (!escaped && ((!string_is_char && byte == '"') || (string_is_char && byte == '\''))); - ender_pad += 1; - }break; - case TXT_TokenKind_Symbol: - { - ender_found = (byte != '~' && byte != '!' && - byte != '%' && byte != '^' && - byte != '&' && byte != '*' && - byte != '(' && byte != ')' && - byte != '-' && byte != '=' && - byte != '+' && byte != '[' && - byte != ']' && byte != '{' && - byte != '}' && byte != ':' && - byte != ';' && byte != ',' && - byte != '.' && byte != '<' && - byte != '>' && byte != '/' && - byte != '?' && byte != '|'); - }break; - case TXT_TokenKind_Comment: - { - 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; - } - }break; - case TXT_TokenKind_Meta: - { - ender_found = (!escaped && (byte == '\r' || byte == '\n')); - }break; - } - } - - // rjf: next byte is ender => emit token - if(ender_found) - { - TXT_Token token = {active_token_kind, r1u64(active_token_start_idx, idx+ender_pad)}; - active_token_kind = TXT_TokenKind_Null; - - // rjf: identifier -> keyword in special cases - if(token.kind == TXT_TokenKind_Identifier) - { - read_only local_persist String8 cpp_keywords[] = - { - str8_lit_comp("alignas"), - str8_lit_comp("alignof"), - str8_lit_comp("and"), - str8_lit_comp("and_eq"), - str8_lit_comp("asm"), - str8_lit_comp("atomic_cancel"), - str8_lit_comp("atomic_commit"), - str8_lit_comp("atomic_noexcept"), - str8_lit_comp("auto"), - str8_lit_comp("bitand"), - str8_lit_comp("bitor"), - str8_lit_comp("bool"), - str8_lit_comp("break"), - str8_lit_comp("case"), - str8_lit_comp("catch"), - str8_lit_comp("char"), - str8_lit_comp("char8_t"), - str8_lit_comp("char16_t"), - str8_lit_comp("char32_t"), - str8_lit_comp("class"), - str8_lit_comp("compl"), - str8_lit_comp("concept"), - str8_lit_comp("const"), - str8_lit_comp("consteval"), - str8_lit_comp("constexpr"), - str8_lit_comp("constinit"), - str8_lit_comp("const_cast"), - str8_lit_comp("continue"), - str8_lit_comp("co_await"), - str8_lit_comp("co_return"), - str8_lit_comp("co_yield"), - str8_lit_comp("decltype"), - str8_lit_comp("default"), - str8_lit_comp("delete"), - str8_lit_comp("do"), - str8_lit_comp("double"), - str8_lit_comp("dynamic_cast"), - str8_lit_comp("else"), - str8_lit_comp("enum"), - str8_lit_comp("explicit"), - str8_lit_comp("export"), - str8_lit_comp("extern"), - str8_lit_comp("false"), - str8_lit_comp("float"), - str8_lit_comp("for"), - str8_lit_comp("friend"), - str8_lit_comp("goto"), - str8_lit_comp("if"), - str8_lit_comp("inline"), - str8_lit_comp("int"), - str8_lit_comp("long"), - str8_lit_comp("mutable"), - str8_lit_comp("namespace"), - str8_lit_comp("new"), - str8_lit_comp("noexcept"), - str8_lit_comp("not"), - str8_lit_comp("not_eq"), - str8_lit_comp("nullptr"), - str8_lit_comp("operator"), - str8_lit_comp("or"), - str8_lit_comp("or_eq"), - str8_lit_comp("private"), - str8_lit_comp("protected"), - str8_lit_comp("public"), - str8_lit_comp("reflexpr"), - str8_lit_comp("register"), - str8_lit_comp("reinterpret_cast"), - str8_lit_comp("requires"), - str8_lit_comp("return"), - str8_lit_comp("short"), - str8_lit_comp("signed"), - str8_lit_comp("sizeof"), - str8_lit_comp("static"), - str8_lit_comp("static_assert"), - str8_lit_comp("static_cast"), - str8_lit_comp("struct"), - str8_lit_comp("switch"), - str8_lit_comp("synchronized"), - str8_lit_comp("template"), - str8_lit_comp("this"), - str8_lit_comp("thread_local"), - str8_lit_comp("throw"), - str8_lit_comp("true"), - str8_lit_comp("try"), - str8_lit_comp("typedef"), - str8_lit_comp("typeid"), - str8_lit_comp("typename"), - str8_lit_comp("union"), - str8_lit_comp("unsigned"), - str8_lit_comp("using"), - str8_lit_comp("virtual"), - str8_lit_comp("void"), - str8_lit_comp("volatile"), - str8_lit_comp("wchar_t"), - str8_lit_comp("while"), - str8_lit_comp("xor"), - str8_lit_comp("xor_eq"), - }; - String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); - for(U64 keyword_idx = 0; keyword_idx < ArrayCount(cpp_keywords); keyword_idx += 1) - { - if(str8_match(cpp_keywords[keyword_idx], token_string, 0)) - { - token.kind = TXT_TokenKind_Keyword; - break; - } - } - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - - // rjf: split symbols by maximum-munch-rule - else if(token.kind == TXT_TokenKind_Symbol) - { - read_only local_persist String8 c_cpp_multichar_symbol_strings[] = - { - str8_lit_comp("<<"), - str8_lit_comp(">>"), - str8_lit_comp("<="), - str8_lit_comp(">="), - str8_lit_comp("=="), - str8_lit_comp("!="), - str8_lit_comp("&&"), - str8_lit_comp("||"), - str8_lit_comp("|="), - str8_lit_comp("&="), - str8_lit_comp("^="), - str8_lit_comp("~="), - str8_lit_comp("+="), - str8_lit_comp("-="), - str8_lit_comp("*="), - str8_lit_comp("/="), - str8_lit_comp("%="), - str8_lit_comp("<<="), - str8_lit_comp(">>="), - str8_lit_comp("->"), - }; - String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); - for(U64 off = 0, next_off = token_string.size; off < token_string.size; off = next_off) - { - B32 found = 0; - for(U64 idx = 0; idx < ArrayCount(c_cpp_multichar_symbol_strings); idx += 1) - { - if(str8_match(str8_substr(string, r1u64(off, off+c_cpp_multichar_symbol_strings[idx].size)), - c_cpp_multichar_symbol_strings[idx], - 0)) - { - found = 1; - next_off = off + c_cpp_multichar_symbol_strings[idx].size; - TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - break; - } - } - if(!found) - { - next_off = off+1; - TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - } - } - - // rjf: all other tokens - else - { - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - - // rjf: increment by ender padding - idx += ender_pad; - } - - // rjf: advance by 1 byte if we haven't found an ender - if(!ender_found) - { - idx += 1; - } - escaped = next_escaped; - } - } - - //- rjf: token list -> token array - TXT_TokenArray result = txt_token_array_from_chunk_list(arena, &tokens); - scratch_end(scratch); - return result; -} - -internal TXT_TokenArray -txt_token_array_from_string__odin(Arena *arena, U64 *bytes_processed_counter, String8 string) -{ - Temp scratch = scratch_begin(&arena, 1); - - //- rjf: generate token list - TXT_TokenChunkList tokens = {0}; - { - B32 comment_is_single_line = 0; - B32 string_is_char = 0; - TXT_TokenKind active_token_kind = TXT_TokenKind_Null; - U64 active_token_start_idx = 0; - B32 escaped = 0; - B32 next_escaped = 0; - U64 byte_process_start_idx = 0; - for(U64 idx = 0; idx <= string.size;) - { - U8 byte = (idx+0 < string.size) ? (string.str[idx+0]) : 0; - U8 next_byte = (idx+1 < string.size) ? (string.str[idx+1]) : 0; - - // rjf: update counter - if(bytes_processed_counter != 0 && ((idx-byte_process_start_idx) >= 1000 || idx == string.size)) - { - ins_atomic_u64_add_eval(bytes_processed_counter, (idx-byte_process_start_idx)); - byte_process_start_idx = idx; - } - - // rjf: escaping - if(escaped && (byte != '\r' && byte != '\n')) - { - next_escaped = 0; - } - else if(!escaped && byte == '\\') - { - next_escaped = 1; - } - - // rjf: take starter, determine active token kind - if(active_token_kind == TXT_TokenKind_Null) - { - // rjf: use next bytes to start a new token - if(0){} - else if(char_is_space(byte)) { active_token_kind = TXT_TokenKind_Whitespace; } - else if(byte == '_' || - byte == '$' || - char_is_alpha(byte)) { active_token_kind = TXT_TokenKind_Identifier; } - else if(char_is_digit(byte, 10) || - (byte == '.' && - 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 == '~' || 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; } - else if(byte == '#') { active_token_kind = TXT_TokenKind_Meta; } - - // rjf: start new token - if(active_token_kind != TXT_TokenKind_Null) - { - active_token_start_idx = idx; - } - - // rjf: invalid token kind -> emit error - else - { - TXT_Token token = {TXT_TokenKind_Error, r1u64(idx, idx+1)}; - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - } - - // rjf: look for ender - U64 ender_pad = 0; - B32 ender_found = 0; - if(active_token_kind != TXT_TokenKind_Null && idx>active_token_start_idx) - { - if(idx == string.size) - { - ender_pad = 0; - ender_found = 1; - } - else switch(active_token_kind) - { - default:break; - case TXT_TokenKind_Whitespace: - { - ender_found = !char_is_space(byte); - }break; - case TXT_TokenKind_Identifier: - { - ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '$'); - }break; - case TXT_TokenKind_Numeric: - { - ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '.' && byte != '\''); - }break; - case TXT_TokenKind_String: - { - ender_found = (!escaped && ((!string_is_char && byte == '"') || (string_is_char && byte == '\''))); - ender_pad += 1; - }break; - case TXT_TokenKind_Symbol: - { - ender_found = (byte != '~' && byte != '!' && - byte != '%' && byte != '^' && - byte != '&' && byte != '*' && - byte != '(' && byte != ')' && - byte != '-' && byte != '=' && - byte != '+' && byte != '[' && - byte != ']' && byte != '{' && - byte != '}' && byte != ':' && - byte != ';' && byte != ',' && - byte != '.' && byte != '<' && - byte != '>' && byte != '/' && - byte != '?' && byte != '|'); - }break; - case TXT_TokenKind_Comment: - { - 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; - } - }break; - case TXT_TokenKind_Meta: - { - ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '$'); - }break; - } - } - - // rjf: next byte is ender => emit token - if(ender_found) - { - TXT_Token token = {active_token_kind, r1u64(active_token_start_idx, idx+ender_pad)}; - active_token_kind = TXT_TokenKind_Null; - - // rjf: identifier -> keyword in special cases - if(token.kind == TXT_TokenKind_Identifier) - { - read_only local_persist String8 odin_keywords[] = - { - str8_lit_comp("align_of"), - str8_lit_comp("asm"), - str8_lit_comp("auto_cast"), - str8_lit_comp("bit_set"), - str8_lit_comp("break"), - str8_lit_comp("case"), - str8_lit_comp("cast"), - str8_lit_comp("context"), - str8_lit_comp("continue"), - str8_lit_comp("defer"), - str8_lit_comp("distinct"), - str8_lit_comp("do"), - str8_lit_comp("dynamic"), - str8_lit_comp("else"), - str8_lit_comp("enum"), - str8_lit_comp("fallthrough"), - str8_lit_comp("for"), - str8_lit_comp("foreign"), - str8_lit_comp("if"), - str8_lit_comp("in"), - str8_lit_comp("map"), - str8_lit_comp("matrix"), - str8_lit_comp("not_in"), - str8_lit_comp("or_break"), - str8_lit_comp("or_continue"), - str8_lit_comp("or_else"), - str8_lit_comp("or_return"), - str8_lit_comp("package"), - str8_lit_comp("proc"), - str8_lit_comp("return"), - str8_lit_comp("size_of"), - str8_lit_comp("struct"), - str8_lit_comp("switch"), - str8_lit_comp("transmute"), - str8_lit_comp("typeid"), - str8_lit_comp("union"), - str8_lit_comp("using"), - str8_lit_comp("when"), - str8_lit_comp("where"), - str8_lit_comp("import"), - }; - String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); - for(U64 keyword_idx = 0; keyword_idx < ArrayCount(odin_keywords); keyword_idx += 1) - { - if(str8_match(odin_keywords[keyword_idx], token_string, 0)) - { - token.kind = TXT_TokenKind_Keyword; - break; - } - } - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - - // rjf: split symbols by maximum-munch-rule - else if(token.kind == TXT_TokenKind_Symbol) - { - read_only local_persist String8 odin_multichar_symbol_strings[] = - { - str8_lit_comp("<<"), - str8_lit_comp(">>"), - str8_lit_comp("<="), - str8_lit_comp(">="), - str8_lit_comp("=="), - str8_lit_comp("!="), - str8_lit_comp("&&"), - str8_lit_comp("||"), - str8_lit_comp("|="), - str8_lit_comp("&="), - str8_lit_comp("^="), - str8_lit_comp("~="), - str8_lit_comp("+="), - str8_lit_comp("-="), - str8_lit_comp("*="), - str8_lit_comp("/="), - str8_lit_comp("%="), - str8_lit_comp("<<="), - str8_lit_comp(">>="), - str8_lit_comp("->"), - }; - String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); - for(U64 off = 0, next_off = token_string.size; off < token_string.size; off = next_off) - { - B32 found = 0; - for(U64 idx = 0; idx < ArrayCount(odin_multichar_symbol_strings); idx += 1) - { - if(str8_match(str8_substr(string, r1u64(off, off+odin_multichar_symbol_strings[idx].size)), - odin_multichar_symbol_strings[idx], - 0)) - { - found = 1; - next_off = off + odin_multichar_symbol_strings[idx].size; - TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - break; - } - } - if(!found) - { - next_off = off+1; - TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - } - } - - // rjf: all other tokens - else - { - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - - // rjf: increment by ender padding - idx += ender_pad; - } - - // rjf: advance by 1 byte if we haven't found an ender - if(!ender_found) - { - idx += 1; - } - escaped = next_escaped; - } - } - - //- rjf: token list -> token array - TXT_TokenArray result = txt_token_array_from_chunk_list(arena, &tokens); - scratch_end(scratch); - return result; -} - -internal TXT_TokenArray -txt_token_array_from_string__jai(Arena *arena, U64 *bytes_processed_counter, String8 string) -{ - Temp scratch = scratch_begin(&arena, 1); - - //- rjf: generate token list - TXT_TokenChunkList tokens = {0}; - { - B32 comment_is_single_line = 0; - B32 string_is_char = 0; - TXT_TokenKind active_token_kind = TXT_TokenKind_Null; - U64 active_token_start_idx = 0; - B32 escaped = 0; - B32 next_escaped = 0; - U64 byte_process_start_idx = 0; - for(U64 idx = 0; idx <= string.size;) - { - U8 byte = (idx+0 < string.size) ? (string.str[idx+0]) : 0; - U8 next_byte = (idx+1 < string.size) ? (string.str[idx+1]) : 0; - - // rjf: update counter - if(bytes_processed_counter != 0 && ((idx-byte_process_start_idx) >= 1000 || idx == string.size)) - { - ins_atomic_u64_add_eval(bytes_processed_counter, (idx-byte_process_start_idx)); - byte_process_start_idx = idx; - } - - // rjf: escaping - if(escaped && (byte != '\r' && byte != '\n')) - { - next_escaped = 0; - } - else if(!escaped && byte == '\\') - { - next_escaped = 1; - } - - // rjf: take starter, determine active token kind - if(active_token_kind == TXT_TokenKind_Null) - { - // rjf: use next bytes to start a new token - if(0){} - else if(char_is_space(byte)) { active_token_kind = TXT_TokenKind_Whitespace; } - else if(byte == '_' || - byte == '$' || - char_is_alpha(byte)) { active_token_kind = TXT_TokenKind_Identifier; } - else if(char_is_digit(byte, 10) || - (byte == '.' && - 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 == '~' || 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; } - else if(byte == '#') { active_token_kind = TXT_TokenKind_Meta; } - - // rjf: start new token - if(active_token_kind != TXT_TokenKind_Null) - { - active_token_start_idx = idx; - } - - // rjf: invalid token kind -> emit error - else - { - TXT_Token token = {TXT_TokenKind_Error, r1u64(idx, idx+1)}; - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - } - - // rjf: look for ender - U64 ender_pad = 0; - B32 ender_found = 0; - if(active_token_kind != TXT_TokenKind_Null && idx>active_token_start_idx) - { - if(idx == string.size) - { - ender_pad = 0; - ender_found = 1; - } - else switch(active_token_kind) - { - default:break; - case TXT_TokenKind_Whitespace: - { - ender_found = !char_is_space(byte); - }break; - case TXT_TokenKind_Identifier: - { - ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '$'); - }break; - case TXT_TokenKind_Numeric: - { - ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '.' && byte != '\''); - }break; - case TXT_TokenKind_String: - { - ender_found = (!escaped && ((!string_is_char && byte == '"') || (string_is_char && byte == '\''))); - ender_pad += 1; - }break; - case TXT_TokenKind_Symbol: - { - ender_found = (byte != '~' && byte != '!' && - byte != '%' && byte != '^' && - byte != '&' && byte != '*' && - byte != '(' && byte != ')' && - byte != '-' && byte != '=' && - byte != '+' && byte != '[' && - byte != ']' && byte != '{' && - byte != '}' && byte != ':' && - byte != ';' && byte != ',' && - byte != '.' && byte != '<' && - byte != '>' && byte != '/' && - byte != '?' && byte != '|'); - }break; - case TXT_TokenKind_Comment: - { - 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; - } - }break; - case TXT_TokenKind_Meta: - { - ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '$'); - }break; - } - } - - // rjf: next byte is ender => emit token - if(ender_found) - { - TXT_Token token = {active_token_kind, r1u64(active_token_start_idx, idx+ender_pad)}; - active_token_kind = TXT_TokenKind_Null; - - // rjf: identifier -> keyword in special cases - if(token.kind == TXT_TokenKind_Identifier) - { - read_only local_persist String8 jai_keywords[] = - { - str8_lit_comp("bool"), - str8_lit_comp("true"), - str8_lit_comp("false"), - str8_lit_comp("int"), - str8_lit_comp("s8"), - str8_lit_comp("u8"), - str8_lit_comp("s16"), - str8_lit_comp("u16"), - str8_lit_comp("s32"), - str8_lit_comp("u32"), - str8_lit_comp("s64"), - str8_lit_comp("u64"), - str8_lit_comp("s128"), - str8_lit_comp("u128"), - str8_lit_comp("float"), - str8_lit_comp("float32"), - str8_lit_comp("float64"), - str8_lit_comp("void"), - str8_lit_comp("enum"), - str8_lit_comp("enum_flags"), - str8_lit_comp("size_of"), - str8_lit_comp("string"), - str8_lit_comp("type_of"), - str8_lit_comp("cast"), - str8_lit_comp("if"), - str8_lit_comp("ifs"), - str8_lit_comp("then"), - str8_lit_comp("else"), - str8_lit_comp("case"), - str8_lit_comp("for"), - str8_lit_comp("while"), - str8_lit_comp("break"), - str8_lit_comp("continue"), - str8_lit_comp("remove"), - str8_lit_comp("return"), - str8_lit_comp("inline"), - str8_lit_comp("null"), - str8_lit_comp("defer"), - str8_lit_comp("xx"), - }; - String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); - for(U64 keyword_idx = 0; keyword_idx < ArrayCount(jai_keywords); keyword_idx += 1) - { - if(str8_match(jai_keywords[keyword_idx], token_string, 0)) - { - token.kind = TXT_TokenKind_Keyword; - break; - } - } - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - - // rjf: split symbols by maximum-munch-rule - else if(token.kind == TXT_TokenKind_Symbol) - { - read_only local_persist String8 jai_multichar_symbol_strings[] = - { - str8_lit_comp("<<"), - str8_lit_comp(">>"), - str8_lit_comp("<="), - str8_lit_comp(">="), - str8_lit_comp("=="), - str8_lit_comp("!="), - str8_lit_comp("&&"), - str8_lit_comp("||"), - str8_lit_comp("|="), - str8_lit_comp("&="), - str8_lit_comp("^="), - str8_lit_comp("~="), - str8_lit_comp("+="), - str8_lit_comp("-="), - str8_lit_comp("*="), - str8_lit_comp("/="), - str8_lit_comp("%="), - str8_lit_comp("<<="), - str8_lit_comp(">>="), - str8_lit_comp("->"), - }; - String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); - for(U64 off = 0, next_off = token_string.size; off < token_string.size; off = next_off) - { - B32 found = 0; - for(U64 idx = 0; idx < ArrayCount(jai_multichar_symbol_strings); idx += 1) - { - if(str8_match(str8_substr(string, r1u64(off, off+jai_multichar_symbol_strings[idx].size)), - jai_multichar_symbol_strings[idx], - 0)) - { - found = 1; - next_off = off + jai_multichar_symbol_strings[idx].size; - TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - break; - } - } - if(!found) - { - next_off = off+1; - TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - } - } - - // rjf: all other tokens - else - { - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - - // rjf: increment by ender padding - idx += ender_pad; - } - - // rjf: advance by 1 byte if we haven't found an ender - if(!ender_found) - { - idx += 1; - } - escaped = next_escaped; - } - } - - //- rjf: token list -> token array - TXT_TokenArray result = txt_token_array_from_chunk_list(arena, &tokens); - scratch_end(scratch); - return result; -} - -internal TXT_TokenArray -txt_token_array_from_string__zig(Arena *arena, U64 *bytes_processed_counter, String8 string) -{ - Temp scratch = scratch_begin(&arena, 1); - - //- rjf: generate token list - TXT_TokenChunkList tokens = {0}; - { - B32 string_is_char = 0; - B32 string_is_line = 0; - TXT_TokenKind active_token_kind = TXT_TokenKind_Null; - U64 active_token_start_idx = 0; - B32 escaped = 0; - B32 next_escaped = 0; - U64 byte_process_start_idx = 0; - for(U64 idx = 0; idx <= string.size;) - { - U8 byte = (idx+0 < string.size) ? (string.str[idx+0]) : 0; - U8 next_byte = (idx+1 < string.size) ? (string.str[idx+1]) : 0; - - // rjf: update counter - if(bytes_processed_counter != 0 && ((idx-byte_process_start_idx) >= 1000 || idx == string.size)) - { - ins_atomic_u64_add_eval(bytes_processed_counter, (idx-byte_process_start_idx)); - byte_process_start_idx = idx; - } - - // rjf: escaping - if(escaped && (byte != '\r' && byte != '\n')) - { - next_escaped = 0; - } - else if(!escaped && byte == '\\') - { - next_escaped = 1; - } - - // rjf: take starter, determine active token kind - if(active_token_kind == TXT_TokenKind_Null) - { - // rjf: use next bytes to start a new token - if(0){} - else if(char_is_space(byte)) { active_token_kind = TXT_TokenKind_Whitespace; } - else if(byte == '_' || - char_is_alpha(byte)) { active_token_kind = TXT_TokenKind_Identifier; } - else if(char_is_digit(byte, 10) || - (byte == '.' && - 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_String; string_is_line = 1; } - else if(byte == '/' && next_byte == '/') { active_token_kind = TXT_TokenKind_Comment; } - 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 == '|' || - byte == 'c') { active_token_kind = TXT_TokenKind_Symbol; } - - // rjf: start new token - if(active_token_kind != TXT_TokenKind_Null) - { - active_token_start_idx = idx; - } - - // rjf: invalid token kind -> emit error - else - { - TXT_Token token = {TXT_TokenKind_Error, r1u64(idx, idx+1)}; - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - } - - // rjf: look for ender - U64 ender_pad = 0; - B32 ender_found = 0; - if(active_token_kind != TXT_TokenKind_Null && idx>active_token_start_idx) - { - if(idx == string.size) - { - ender_pad = 0; - ender_found = 1; - } - else switch(active_token_kind) - { - default:break; - case TXT_TokenKind_Whitespace: - { - ender_found = !char_is_space(byte); - }break; - case TXT_TokenKind_Identifier: - { - ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '$'); - }break; - case TXT_TokenKind_Numeric: - { - ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '.' && byte != '\''); - }break; - case TXT_TokenKind_String: - { - if (string_is_line) - { - ender_found = (!escaped && (byte == '\r' || byte == '\n')); - } - else - { - ender_found = (!escaped && ((!string_is_char && byte == '"') || (string_is_char && byte == '\''))); - ender_pad += 1; - } - }break; - case TXT_TokenKind_Symbol: - { - ender_found = (byte != '~' && byte != '!' && - byte != '%' && byte != '^' && - byte != '&' && byte != '*' && - byte != '(' && byte != ')' && - byte != '-' && byte != '=' && - byte != '+' && byte != '[' && - byte != ']' && byte != '{' && - byte != '}' && byte != ':' && - byte != ';' && byte != ',' && - byte != '.' && byte != '<' && - byte != '>' && byte != '/' && - byte != '?' && byte != '|' && - byte != 'c'); - }break; - case TXT_TokenKind_Comment: - { - ender_found = (!escaped && (byte == '\r' || byte == '\n')); - }break; - } - } - - // rjf: next byte is ender => emit token - if(ender_found) - { - TXT_Token token = {active_token_kind, r1u64(active_token_start_idx, idx+ender_pad)}; - active_token_kind = TXT_TokenKind_Null; - - // rjf: identifier -> keyword in special cases - if(token.kind == TXT_TokenKind_Identifier) - { - read_only local_persist String8 zig_keywords[] = - { - str8_lit_comp("addrspace"), - str8_lit_comp("align"), - str8_lit_comp("allowzero"), - str8_lit_comp("and"), - str8_lit_comp("anyframe"), - str8_lit_comp("anytype"), - str8_lit_comp("asm"), - str8_lit_comp("async"), - str8_lit_comp("await"), - str8_lit_comp("break"), - str8_lit_comp("callconv"), - str8_lit_comp("catch"), - str8_lit_comp("comptime"), - str8_lit_comp("const"), - str8_lit_comp("continue"), - str8_lit_comp("defer"), - str8_lit_comp("else"), - str8_lit_comp("enum"), - str8_lit_comp("errdefer"), - str8_lit_comp("error"), - str8_lit_comp("export"), - str8_lit_comp("extern"), - str8_lit_comp("fn"), - str8_lit_comp("for"), - str8_lit_comp("if"), - str8_lit_comp("inline"), - str8_lit_comp("noalias"), - str8_lit_comp("nosuspend"), - str8_lit_comp("noinline"), - str8_lit_comp("opaque"), - str8_lit_comp("or"), - str8_lit_comp("orelse"), - str8_lit_comp("packed"), - str8_lit_comp("pub"), - str8_lit_comp("resume"), - str8_lit_comp("return"), - str8_lit_comp("linksection"), - str8_lit_comp("struct"), - str8_lit_comp("suspend"), - str8_lit_comp("switch"), - str8_lit_comp("test"), - str8_lit_comp("threadlocal"), - str8_lit_comp("try"), - str8_lit_comp("union"), - str8_lit_comp("unreachable"), - str8_lit_comp("usingnamespace"), - str8_lit_comp("var"), - str8_lit_comp("volatile"), - str8_lit_comp("while"), - }; - String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); - for(U64 keyword_idx = 0; keyword_idx < ArrayCount(zig_keywords); keyword_idx += 1) - { - if(str8_match(zig_keywords[keyword_idx], token_string, 0)) - { - token.kind = TXT_TokenKind_Keyword; - break; - } - } - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - - // rjf: split symbols by maximum-munch-rule - else if(token.kind == TXT_TokenKind_Symbol) - { - read_only local_persist String8 zig_multichar_symbol_strings[] = - { - str8_lit_comp("<<"), - str8_lit_comp(">>"), - str8_lit_comp("<="), - str8_lit_comp(">="), - str8_lit_comp("=="), - str8_lit_comp("!="), - str8_lit_comp("&&"), - str8_lit_comp("||"), - str8_lit_comp("|="), - str8_lit_comp("&="), - str8_lit_comp("^="), - str8_lit_comp("~="), - str8_lit_comp("+="), - str8_lit_comp("-="), - str8_lit_comp("*="), - str8_lit_comp("/="), - str8_lit_comp("%="), - str8_lit_comp("<<="), - str8_lit_comp(">>="), - str8_lit_comp("->"), - }; - String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); - for(U64 off = 0, next_off = token_string.size; off < token_string.size; off = next_off) - { - B32 found = 0; - for(U64 idx = 0; idx < ArrayCount(zig_multichar_symbol_strings); idx += 1) - { - if(str8_match(str8_substr(string, r1u64(off, off+zig_multichar_symbol_strings[idx].size)), - zig_multichar_symbol_strings[idx], - 0)) - { - found = 1; - next_off = off + zig_multichar_symbol_strings[idx].size; - TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - break; - } - } - if(!found) - { - next_off = off+1; - TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - } - } - - // rjf: all other tokens - else - { - txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); - } - - // rjf: increment by ender padding - idx += ender_pad; - } - - // rjf: advance by 1 byte if we haven't found an ender - if(!ender_found) - { - idx += 1; - } - escaped = next_escaped; - } - } - - //- rjf: token list -> token array - TXT_TokenArray result = txt_token_array_from_chunk_list(arena, &tokens); - scratch_end(scratch); - return result; -} - -internal TXT_TokenArray -txt_token_array_from_string__disasm_x64_intel(Arena *arena, U64 *bytes_processed_counter, String8 string) -{ - Temp scratch = scratch_begin(&arena, 1); - - //- rjf: parse tokens - TXT_TokenChunkList tokens = {0}; - { - TXT_TokenKind active_token_kind = TXT_TokenKind_Null; - U64 active_token_start_off = 0; - U64 off = 0; - B32 escaped = 0; - B32 string_is_char = 0; - S32 brace_nest = 0; - S32 paren_nest = 0; - for(U64 advance = 0; off <= string.size; off += advance) - { - U8 byte = (off+0 < string.size) ? string.str[off+0] : 0; - U8 next_byte = (off+1 < string.size) ? string.str[off+1] : 0; - B32 ender_found = 0; - advance = (active_token_kind != TXT_TokenKind_Null ? 1 : 0); - if(off == string.size && active_token_kind != TXT_TokenKind_Null) - { - ender_found = 1; - advance = 1; - } - switch(active_token_kind) - { - default: - case TXT_TokenKind_Null: - { - if(byte == ' ' || byte == '\t' || byte == '\v' || byte == '\f' || byte == '\r' || byte == '\n') - { - active_token_start_off = off; - active_token_kind = TXT_TokenKind_Whitespace; - advance = 1; - } - else if(byte == '>' && brace_nest == 0 && paren_nest == 0) - { - active_token_start_off = off; - active_token_kind = TXT_TokenKind_Comment; - advance = 1; - } - else if(('a' <= byte && byte <= 'z') || ('A' <= byte && byte <= 'Z') || byte == '_') - { - active_token_start_off = off; - active_token_kind = TXT_TokenKind_Keyword; - advance = 1; - } - else if(byte == '\'') - { - active_token_start_off = off; - active_token_kind = TXT_TokenKind_String; - advance = 1; - string_is_char = 1; - } - else if(byte == '"') - { - active_token_start_off = off; - active_token_kind = TXT_TokenKind_String; - advance = 1; - string_is_char = 0; - } - else if(('0' <= byte && byte <= '9') || (byte == '.' && '0' <= next_byte && next_byte <= '9')) - { - active_token_start_off = off; - active_token_kind = TXT_TokenKind_Numeric; - advance = 1; - } - else if(byte == '~' || byte == '!' || byte == '%' || byte == '^' || - byte == '&' || byte == '*' || byte == '(' || byte == ')' || - byte == '-' || byte == '=' || byte == '+' || byte == '[' || - byte == ']' || byte == '{' || byte == '}' || byte == ';' || - byte == ':' || byte == '?' || byte == '/' || byte == '<' || - byte == '>' || byte == ',' || byte == '.') - { - active_token_start_off = off; - active_token_kind = TXT_TokenKind_Symbol; - advance = 1; - if(byte == '{') - { - brace_nest += 1; - } - else if(byte == '}') - { - brace_nest -= 1; - } - if(byte == '(') - { - paren_nest += 1; - } - else if(byte == ')') - { - paren_nest -= 1; - } - } - else - { - active_token_start_off = off; - active_token_kind = TXT_TokenKind_Error; - advance = 1; - } - }break; - case TXT_TokenKind_Whitespace: - if(byte != ' ' && byte != '\t' && byte != '\v' && byte != '\f') - { - ender_found = 1; - advance = 0; - }break; - case TXT_TokenKind_Keyword: - if((byte < 'a' || 'z' < byte) && (byte < 'A' || 'Z' < byte) && (byte < '0' || '9' < byte) && byte != '_') - { - ender_found = 1; - advance = 0; - }break; - case TXT_TokenKind_String: - { - U8 ender_byte = string_is_char ? '\'' : '"'; - if(!escaped && byte == ender_byte) - { - ender_found = 1; - advance = 1; - } - else if(escaped) - { - escaped = 0; - advance = 1; - } - else if(byte == '\\') - { - escaped = 1; - advance = 1; - } - else - { - U8 byte_class = utf8_class[byte>>3]; - if(byte_class > 1) - { - advance = (U64)byte_class; - } - } - }break; - case TXT_TokenKind_Numeric: - if((byte < 'a' || 'z' < byte) && (byte < 'A' || 'Z' < byte) && (byte < '0' || '9' < byte) && byte != '.') - { - ender_found = 1; - advance = 0; - }break; - case TXT_TokenKind_Symbol: - if(1) - { - // NOTE(rjf): avoiding maximum munch rule for now - ender_found = 1; - advance = 0; - } - 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 != '.') - { - ender_found = 1; - advance = 0; - }break; - case TXT_TokenKind_Error: - { - ender_found = 1; - advance = 0; - }break; - case TXT_TokenKind_Comment: - if(byte == '\n') - { - ender_found = 1; - advance = 1; - }break; - } - if(ender_found != 0) - { - if(brace_nest != 0 && active_token_kind == TXT_TokenKind_Keyword) - { - active_token_kind = TXT_TokenKind_Numeric; - } - if(paren_nest != 0 && active_token_kind == TXT_TokenKind_Keyword) - { - active_token_kind = TXT_TokenKind_Identifier; - } - TXT_Token token = {active_token_kind, r1u64(active_token_start_off, off+advance)}; - txt_token_chunk_list_push(arena, &tokens, 1024, &token); - active_token_kind = TXT_TokenKind_Null; - active_token_start_off = token.range.max; - } - } - } - - //- rjf: token list -> token array - TXT_TokenArray result = txt_token_array_from_chunk_list(arena, &tokens); - scratch_end(scratch); - return result; -} - -//////////////////////////////// -//~ rjf: Main Layer Initialization - -internal void -txt_init(void) -{ - Arena *arena = arena_alloc(); - txt_shared = push_array(arena, TXT_Shared, 1); - txt_shared->arena = arena; - txt_shared->slots_count = 1024; - txt_shared->slots = push_array(arena, TXT_Slot, txt_shared->slots_count); - txt_shared->stripes_count = Min(txt_shared->slots_count, os_logical_core_count()); - txt_shared->stripes = push_array(arena, TXT_Stripe, txt_shared->stripes_count); - txt_shared->stripes_free_nodes = push_array(arena, TXT_Node *, txt_shared->stripes_count); - for(U64 idx = 0; idx < txt_shared->stripes_count; idx += 1) - { - txt_shared->stripes[idx].arena = arena_alloc(); - txt_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); - txt_shared->stripes[idx].cv = os_condition_variable_alloc(); - } - txt_shared->u2p_ring_size = KB(64); - txt_shared->u2p_ring_base = push_array_no_zero(arena, U8, txt_shared->u2p_ring_size); - txt_shared->u2p_ring_cv = os_condition_variable_alloc(); - txt_shared->u2p_ring_mutex = os_mutex_alloc(); - txt_shared->parse_thread_count = Clamp(1, os_logical_core_count()-1, 4); - txt_shared->parse_threads = push_array(arena, OS_Handle, txt_shared->parse_thread_count); - for(U64 idx = 0; idx < txt_shared->parse_thread_count; idx += 1) - { - txt_shared->parse_threads[idx] = os_launch_thread(txt_parse_thread__entry_point, (void *)idx, 0); - } - txt_shared->evictor_thread = os_launch_thread(txt_evictor_thread__entry_point, 0, 0); -} - -//////////////////////////////// -//~ rjf: Thread Context Initialization - -internal void -txt_tctx_ensure_inited(void) -{ - if(txt_tctx == 0) - { - Arena *arena = arena_alloc(); - txt_tctx = push_array(arena, TXT_TCTX, 1); - txt_tctx->arena = arena; - } -} - -//////////////////////////////// -//~ rjf: User Clock - -internal void -txt_user_clock_tick(void) -{ - ins_atomic_u64_inc_eval(&txt_shared->user_clock_idx); -} - -internal U64 -txt_user_clock_idx(void) -{ - return ins_atomic_u64_eval(&txt_shared->user_clock_idx); -} - -//////////////////////////////// -//~ rjf: Scoped Access - -internal TXT_Scope * -txt_scope_open(void) -{ - txt_tctx_ensure_inited(); - TXT_Scope *scope = txt_tctx->free_scope; - if(scope) - { - SLLStackPop(txt_tctx->free_scope); - } - else - { - scope = push_array_no_zero(txt_tctx->arena, TXT_Scope, 1); - } - MemoryZeroStruct(scope); - return scope; -} - -internal void -txt_scope_close(TXT_Scope *scope) -{ - for(TXT_Touch *touch = scope->top_touch, *next = 0; touch != 0; touch = next) - { - U128 hash = touch->hash; - next = touch->next; - U64 slot_idx = hash.u64[1]%txt_shared->slots_count; - U64 stripe_idx = slot_idx%txt_shared->stripes_count; - TXT_Slot *slot = &txt_shared->slots[slot_idx]; - TXT_Stripe *stripe = &txt_shared->stripes[stripe_idx]; - OS_MutexScopeR(stripe->rw_mutex) - { - for(TXT_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(hash, n->hash) && touch->lang == n->lang) - { - ins_atomic_u64_dec_eval(&n->scope_ref_count); - break; - } - } - } - SLLStackPush(txt_tctx->free_touch, touch); - } - SLLStackPush(txt_tctx->free_scope, scope); -} - -internal void -txt_scope_touch_node__stripe_r_guarded(TXT_Scope *scope, TXT_Node *node) -{ - TXT_Touch *touch = txt_tctx->free_touch; - ins_atomic_u64_inc_eval(&node->scope_ref_count); - ins_atomic_u64_eval_assign(&node->last_time_touched_us, os_now_microseconds()); - ins_atomic_u64_eval_assign(&node->last_user_clock_idx_touched, txt_user_clock_idx()); - if(touch != 0) - { - SLLStackPop(txt_tctx->free_touch); - } - else - { - touch = push_array_no_zero(txt_tctx->arena, TXT_Touch, 1); - } - MemoryZeroStruct(touch); - touch->hash = node->hash; - touch->lang = node->lang; - SLLStackPush(scope->top_touch, touch); -} - -//////////////////////////////// -//~ rjf: Cache Lookups - -internal TXT_TextInfo -txt_text_info_from_hash_lang(TXT_Scope *scope, U128 hash, TXT_LangKind lang) -{ - TXT_TextInfo info = {0}; - if(!u128_match(hash, u128_zero())) - { - U64 slot_idx = hash.u64[1]%txt_shared->slots_count; - U64 stripe_idx = slot_idx%txt_shared->stripes_count; - TXT_Slot *slot = &txt_shared->slots[slot_idx]; - TXT_Stripe *stripe = &txt_shared->stripes[stripe_idx]; - B32 found = 0; - OS_MutexScopeR(stripe->rw_mutex) - { - for(TXT_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(hash, n->hash) && n->lang == lang) - { - MemoryCopyStruct(&info, &n->info); - info.bytes_processed = ins_atomic_u64_eval(&n->info.bytes_processed); - info.bytes_to_process = ins_atomic_u64_eval(&n->info.bytes_to_process); - found = 1; - txt_scope_touch_node__stripe_r_guarded(scope, n); - break; - } - } - } - B32 node_is_new = 0; - if(!found) - { - OS_MutexScopeW(stripe->rw_mutex) - { - TXT_Node *node = 0; - for(TXT_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(hash, n->hash) && n->lang == lang) - { - node = n; - break; - } - } - if(node == 0) - { - node = txt_shared->stripes_free_nodes[stripe_idx]; - if(node) - { - SLLStackPop(txt_shared->stripes_free_nodes[stripe_idx]); - } - else - { - node = push_array_no_zero(stripe->arena, TXT_Node, 1); - } - MemoryZeroStruct(node); - DLLPushBack(slot->first, slot->last, node); - node->hash = hash; - node->lang = lang; - node_is_new = 1; - } - } - } - if(node_is_new) - { - txt_u2p_enqueue_req(hash, lang, max_U64); - } - } - return info; -} - -internal TXT_TextInfo -txt_text_info_from_key_lang(TXT_Scope *scope, U128 key, TXT_LangKind lang, U128 *hash_out) -{ - TXT_TextInfo result = {0}; - for(U64 rewind_idx = 0; rewind_idx < 2; rewind_idx += 1) - { - U128 hash = hs_hash_from_key(key, rewind_idx); - result = txt_text_info_from_hash_lang(scope, hash, lang); - if(result.lines_count != 0) - { - if(hash_out) - { - *hash_out = hash; - } - break; - } - } - return result; -} - -//////////////////////////////// -//~ rjf: Text Info Extractor Helpers - -internal U64 -txt_off_from_info_pt(TXT_TextInfo *info, 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); - } - return off; -} - -internal TxtPt -txt_pt_from_info_off__linear_scan(TXT_TextInfo *info, U64 off) -{ - TxtPt pt = {0}; - { - for(U64 line_idx = 0; line_idx < info->lines_count; line_idx += 1) - { - if(contains_1u64(info->lines_ranges[line_idx], off)) - { - pt.line = (S64)line_idx + 1; - pt.column = (S64)(off - info->lines_ranges[line_idx].min) + 1; - } - } - } - return pt; -} - -internal TXT_TokenArray -txt_token_array_from_info_line_num__linear_scan(TXT_TextInfo *info, S64 line_num) -{ - TXT_TokenArray line_tokens = {0}; - if(1 <= line_num && line_num <= info->lines_count) - { - Rng1U64 line_range = info->lines_ranges[line_num-1]; - for(U64 token_idx = 0; token_idx < info->tokens.count; token_idx += 1) - { - Rng1U64 token_range = info->tokens.v[token_idx].range; - Rng1U64 token_x_line = intersect_1u64(token_range, line_range); - if(token_x_line.max > token_x_line.min) - { - if(line_tokens.v == 0) - { - line_tokens.v = info->tokens.v+token_idx; - } - line_tokens.count += 1; - } - else if(line_tokens.v != 0) - { - break; - } - } - } - return line_tokens; -} - -internal Rng1U64 -txt_expr_off_range_from_line_off_range_string_tokens(U64 off, Rng1U64 line_range, String8 line_text, TXT_TokenArray *line_tokens) -{ - Rng1U64 result = {0}; - Temp scratch = scratch_begin(0, 0); - { - // rjf: unpack line info - TXT_Token *line_tokens_first = line_tokens->v; - TXT_Token *line_tokens_opl = line_tokens->v+line_tokens->count; - - // rjf: find token containing `off` - TXT_Token *pt_token = 0; - for(TXT_Token *token = line_tokens_first; - token < line_tokens_opl; - token += 1) - { - if(contains_1u64(token->range, off)) - { - Rng1U64 token_range_clamped = intersect_1u64(line_range, token->range); - String8 token_string = str8_substr(line_text, r1u64(token_range_clamped.max - line_range.min, token_range_clamped.max - line_range.min)); - B32 token_ender = 0; - switch(token->kind) - { - default:{}break; - case TXT_TokenKind_Symbol: - { - token_ender = (str8_match(token_string, str8_lit("]"), 0)); - }break; - case TXT_TokenKind_Identifier: - case TXT_TokenKind_Keyword: - case TXT_TokenKind_String: - case TXT_TokenKind_Meta: - { - token_ender = 1; - }break; - } - if(token_ender) - { - pt_token = token; - } - break; - } - } - - // rjf: found token containing `off`? -> mark that as our initial range - if(pt_token != 0) - { - result = pt_token->range; - } - - // rjf: walk back from pt_token - try to find plausible start of expression - if(pt_token != 0) - { - B32 walkback_done = 0; - S32 nest = 0; - for(TXT_Token *wb_token = pt_token; - wb_token >= line_tokens_first && walkback_done == 0; - wb_token -= 1) - { - Rng1U64 wb_token_range_clamped = intersect_1u64(line_range, wb_token->range); - String8 wb_token_string = str8_substr(line_text, r1u64(wb_token_range_clamped.min - line_range.min, wb_token_range_clamped.max - line_range.min)); - B32 include_wb_token = 0; - switch(wb_token->kind) - { - default:{}break; - case TXT_TokenKind_Symbol: - { - B32 is_scope_resolution = str8_match(wb_token_string, str8_lit("::"), 0); - B32 is_dot = str8_match(wb_token_string, str8_lit("."), 0); - B32 is_arrow = str8_match(wb_token_string, str8_lit("->"), 0); - B32 is_open_bracket = str8_match(wb_token_string, str8_lit("["), 0); - B32 is_close_bracket = str8_match(wb_token_string, str8_lit("]"), 0); - nest -= !!(is_open_bracket); - nest += !!(is_close_bracket); - if(is_scope_resolution || - is_dot || - is_arrow || - is_open_bracket|| - is_close_bracket) - { - include_wb_token = 1; - } - }break; - case TXT_TokenKind_Identifier: - { - include_wb_token = 1; - }break; - } - if(include_wb_token) - { - result = union_1u64(result, wb_token->range); - } - else if(nest == 0) - { - walkback_done = 1; - } - } - } - } - scratch_end(scratch); - return result; -} - -internal Rng1U64 -txt_expr_off_range_from_info_data_pt(TXT_TextInfo *info, String8 data, TxtPt pt) -{ - Rng1U64 result = {0}; - Temp scratch = scratch_begin(0, 0); - if(1 <= pt.line && pt.line <= info->lines_count) - { - // rjf: unpack line info - Rng1U64 line_range = info->lines_ranges[pt.line-1]; - String8 line_text = str8_substr(data, line_range); - TXT_LineTokensSlice line_tokens_slice = txt_line_tokens_slice_from_info_data_line_range(scratch.arena, info, data, r1s64(pt.line, pt.line)); - TXT_TokenArray line_tokens = line_tokens_slice.line_tokens[0]; - TXT_Token *line_tokens_first = line_tokens.v; - TXT_Token *line_tokens_opl = line_tokens.v+line_tokens.count; - U64 pt_off = line_range.min + (pt.column-1); - - // rjf: grab offset range of expression - result = txt_expr_off_range_from_line_off_range_string_tokens(pt_off, line_range, line_text, &line_tokens); - } - scratch_end(scratch); - return result; -} - -internal String8 -txt_string_from_info_data_txt_rng(TXT_TextInfo *info, String8 data, TxtRng rng) -{ - Rng1U64 rng_off = r1u64(txt_off_from_info_pt(info, rng.min), txt_off_from_info_pt(info, rng.max)); - String8 result = str8_substr(data, rng_off); - return result; -} - -internal String8 -txt_string_from_info_data_line_num(TXT_TextInfo *info, String8 data, S64 line_num) -{ - String8 result = {0}; - if(1 <= line_num && line_num <= info->lines_count) - { - result = str8_substr(data, info->lines_ranges[line_num-1]); - } - return result; -} - -internal TXT_LineTokensSlice -txt_line_tokens_slice_from_info_data_line_range(Arena *arena, TXT_TextInfo *info, String8 data, Rng1S64 line_range) -{ - TXT_LineTokensSlice result = {0}; - Temp scratch = scratch_begin(&arena, 1); - if(info->lines_count != 0) - { - Rng1S64 line_range_clamped = r1s64(Clamp(1, line_range.min, (S64)info->lines_count), Clamp(1, line_range.max, (S64)info->lines_count)); - U64 line_count = (U64)dim_1s64(line_range_clamped)+1; - - // rjf: allocate output arrays - result.line_tokens = push_array(arena, TXT_TokenArray, line_count); - - // rjf: binary search to find first token - TXT_Token *tokens_first = 0; - ProfScope("binary search to find first token") - { - Rng1U64 slice_range = r1u64(info->lines_ranges[line_range_clamped.min-1].min, info->lines_ranges[line_range_clamped.max-1].max); - U64 min_idx = 0; - U64 opl_idx = info->tokens.count; - for(;;) - { - U64 mid_idx = (opl_idx+min_idx)/2; - if(mid_idx >= opl_idx) - { - break; - } - TXT_Token *mid_token = &info->tokens.v[mid_idx]; - if(mid_token->range.min > slice_range.max) - { - opl_idx = mid_idx; - } - else if(mid_token->range.max < slice_range.min) - { - min_idx = mid_idx; - } - else if(tokens_first == 0 || mid_token->range.min < tokens_first->range.min) - { - tokens_first = mid_token; - opl_idx = mid_idx; - } - if(mid_idx == min_idx && mid_idx+1 == opl_idx) - { - break; - } - } - } - - // rjf: grab per-line tokens - TXT_TokenList *line_tokens_lists = push_array(scratch.arena, TXT_TokenList, line_count); - if(tokens_first != 0) ProfScope("grab per-line tokens") - { - TXT_Token *tokens_opl = info->tokens.v+info->tokens.count; - U64 line_slice_idx = 0; - for(TXT_Token *token = tokens_first; token < tokens_opl && line_slice_idx < line_count;) - { - if(token->range.min < info->lines_ranges[line_slice_idx+line_range.min-1].max) - { - if(token->range.max > info->lines_ranges[line_slice_idx+line_range.min-1].min) - { - txt_token_list_push(scratch.arena, &line_tokens_lists[line_slice_idx], token); - } - B32 need_token_advance = 0; - B32 need_line_advance = 0; - if(token->range.max >= info->lines_ranges[line_slice_idx+line_range.min-1].max) - { - need_line_advance = 1; - } - if(token->range.max <= info->lines_ranges[line_slice_idx+line_range.min-1].max) - { - need_token_advance += 1; - } - if(need_line_advance) { line_slice_idx += 1; } - if(need_token_advance) { token += 1; } - } - else - { - line_slice_idx += 1; - } - } - } - - // rjf: bake per-line tokens to arrays - for(U64 line_slice_idx = 0; line_slice_idx < line_count; line_slice_idx += 1) - { - result.line_tokens[line_slice_idx] = txt_token_array_from_list(arena, &line_tokens_lists[line_slice_idx]); - } - } - scratch_end(scratch); - return result; -} - -//////////////////////////////// -//~ rjf: Transfer Threads - -internal B32 -txt_u2p_enqueue_req(U128 hash, TXT_LangKind lang, U64 endt_us) -{ - B32 good = 0; - OS_MutexScope(txt_shared->u2p_ring_mutex) for(;;) - { - U64 unconsumed_size = txt_shared->u2p_ring_write_pos - txt_shared->u2p_ring_read_pos; - U64 available_size = txt_shared->u2p_ring_size - unconsumed_size; - if(available_size >= sizeof(hash)+sizeof(lang)) - { - good = 1; - txt_shared->u2p_ring_write_pos += ring_write_struct(txt_shared->u2p_ring_base, txt_shared->u2p_ring_size, txt_shared->u2p_ring_write_pos, &hash); - txt_shared->u2p_ring_write_pos += ring_write_struct(txt_shared->u2p_ring_base, txt_shared->u2p_ring_size, txt_shared->u2p_ring_write_pos, &lang); - break; - } - if(os_now_microseconds() >= endt_us) - { - break; - } - os_condition_variable_wait(txt_shared->u2p_ring_cv, txt_shared->u2p_ring_mutex, endt_us); - } - if(good) - { - os_condition_variable_broadcast(txt_shared->u2p_ring_cv); - } - return good; -} - -internal void -txt_u2p_dequeue_req(U128 *hash_out, TXT_LangKind *lang_out) -{ - OS_MutexScope(txt_shared->u2p_ring_mutex) for(;;) - { - U64 unconsumed_size = txt_shared->u2p_ring_write_pos - txt_shared->u2p_ring_read_pos; - if(unconsumed_size >= sizeof(*hash_out) + sizeof(*lang_out)) - { - txt_shared->u2p_ring_read_pos += ring_read_struct(txt_shared->u2p_ring_base, txt_shared->u2p_ring_size, txt_shared->u2p_ring_read_pos, hash_out); - txt_shared->u2p_ring_read_pos += ring_read_struct(txt_shared->u2p_ring_base, txt_shared->u2p_ring_size, txt_shared->u2p_ring_read_pos, lang_out); - break; - } - os_condition_variable_wait(txt_shared->u2p_ring_cv, txt_shared->u2p_ring_mutex, max_U64); - } - os_condition_variable_broadcast(txt_shared->u2p_ring_cv); -} - -internal void -txt_parse_thread__entry_point(void *p) -{ - for(;;) - { - //- rjf: get next key - U128 hash = {0}; - TXT_LangKind lang = TXT_LangKind_Null; - txt_u2p_dequeue_req(&hash, &lang); - HS_Scope *scope = hs_scope_open(); - - //- rjf: unpack hash - U64 slot_idx = hash.u64[1]%txt_shared->slots_count; - U64 stripe_idx = slot_idx%txt_shared->stripes_count; - TXT_Slot *slot = &txt_shared->slots[slot_idx]; - TXT_Stripe *stripe = &txt_shared->stripes[stripe_idx]; - - //- rjf: take task - B32 got_task = 0; - OS_MutexScopeR(stripe->rw_mutex) - { - for(TXT_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(n->hash, hash) && n->lang == lang) - { - got_task = !ins_atomic_u32_eval_cond_assign(&n->is_working, 1, 0); - break; - } - } - } - - //- rjf: hash -> data - String8 data = {0}; - if(got_task) - { - data = hs_data_from_hash(scope, hash); - } - - //- rjf: data -> text info - Arena *info_arena = 0; - TXT_TextInfo info = {0}; - if(got_task && data.size != 0) - { - info_arena = arena_alloc(); - - //- rjf: grab pointers to working counters - U64 *bytes_processed_ptr = 0; - U64 *bytes_to_process_ptr = 0; - OS_MutexScopeR(stripe->rw_mutex) - { - for(TXT_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(n->hash, hash) && n->lang == lang) - { - bytes_processed_ptr = &n->info.bytes_processed; - bytes_to_process_ptr = &n->info.bytes_to_process; - } - } - } - - //- rjf: set # of bytes to process - if(bytes_to_process_ptr) - { - // (line ending calc) (line counting) (line measuring) (lexing) - ins_atomic_u64_eval_assign(bytes_to_process_ptr, 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; - { - 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; - } - info.line_end_kind = line_end_kind; - } - - //- rjf: bump progress - if(bytes_processed_ptr) - { - ins_atomic_u64_eval_assign(bytes_processed_ptr, Min(data.size, 1024)); - } - - //- rjf: count # of lines - U64 line_count = 1; - U64 byte_process_start_idx = 0; - for(U64 idx = 0; idx < data.size; idx += 1) - { - if(data.str[idx] == '\n' || data.str[idx] == '\r') - { - line_count += 1; - if(data.str[idx] == '\r') - { - idx += 1; - } - } - if(idx && idx%1000 == 0) - { - ins_atomic_u64_add_eval(bytes_processed_ptr, 1000); - } - } - - //- rjf: bump progress - if(bytes_processed_ptr) - { - ins_atomic_u64_eval_assign(bytes_processed_ptr, Min(data.size, 1024) + data.size); - } - - //- rjf: allocate & store line ranges - info.lines_count = line_count; - info.lines_ranges = push_array_no_zero(info_arena, Rng1U64, info.lines_count); - U64 line_idx = 0; - U64 line_start_idx = 0; - for(U64 idx = 0; idx <= data.size; idx += 1) - { - if(idx == data.size || data.str[idx] == '\n' || data.str[idx] == '\r') - { - Rng1U64 line_range = r1u64(line_start_idx, idx); - U64 line_size = dim_1u64(line_range); - info.lines_ranges[line_idx] = line_range; - info.lines_max_size = Max(info.lines_max_size, line_size); - line_idx += 1; - line_start_idx = idx+1; - if(idx < data.size && data.str[idx] == '\r') - { - line_start_idx += 1; - idx += 1; - } - } - if(idx && idx%1000 == 0) - { - ins_atomic_u64_add_eval(bytes_processed_ptr, 1000); - } - } - - //- rjf: bump progress - if(bytes_processed_ptr) - { - ins_atomic_u64_eval_assign(bytes_processed_ptr, Min(data.size, 1024) + data.size + data.size); - } - - //- rjf: lang -> lex function - TXT_LangLexFunctionType *lex_function = txt_lex_function_from_lang_kind(lang); - - //- rjf: lex function * data -> tokens - TXT_TokenArray tokens = {0}; - if(lex_function != 0) - { - tokens = lex_function(info_arena, bytes_processed_ptr, data); - } - info.tokens = tokens; - - //- rjf: bump progress - if(bytes_processed_ptr) - { - ins_atomic_u64_eval_assign(bytes_processed_ptr, Min(data.size, 1024) + data.size + data.size + data.size*(lex_function != 0)); - } - } - - //- rjf: commit results to cache - if(got_task) OS_MutexScopeW(stripe->rw_mutex) - { - for(TXT_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(n->hash, hash) && n->lang == lang) - { - n->arena = info_arena; - info.bytes_processed = n->info.bytes_processed; - info.bytes_to_process = n->info.bytes_to_process; - MemoryCopyStruct(&n->info, &info); - ins_atomic_u32_eval_assign(&n->is_working, 0); - ins_atomic_u64_inc_eval(&n->load_count); - break; - } - } - } - - hs_scope_close(scope); - } -} - -//////////////////////////////// -//~ rjf: Evictor Threads - -internal void -txt_evictor_thread__entry_point(void *p) -{ - for(;;) - { - U64 check_time_us = os_now_microseconds(); - U64 check_time_user_clocks = txt_user_clock_idx(); - U64 evict_threshold_us = 10*1000000; - U64 evict_threshold_user_clocks = 10; - for(U64 slot_idx = 0; slot_idx < txt_shared->slots_count; slot_idx += 1) - { - U64 stripe_idx = slot_idx%txt_shared->stripes_count; - TXT_Slot *slot = &txt_shared->slots[slot_idx]; - TXT_Stripe *stripe = &txt_shared->stripes[stripe_idx]; - B32 slot_has_work = 0; - OS_MutexScopeR(stripe->rw_mutex) - { - for(TXT_Node *n = slot->first; n != 0; n = n->next) - { - if(n->scope_ref_count == 0 && - n->last_time_touched_us+evict_threshold_us <= check_time_us && - n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && - n->load_count != 0 && - n->is_working == 0) - { - slot_has_work = 1; - break; - } - } - } - if(slot_has_work) OS_MutexScopeW(stripe->rw_mutex) - { - for(TXT_Node *n = slot->first, *next = 0; n != 0; n = next) - { - next = n->next; - if(n->scope_ref_count == 0 && - n->last_time_touched_us+evict_threshold_us <= check_time_us && - n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && - n->load_count != 0 && - n->is_working == 0) - { - DLLRemove(slot->first, slot->last, n); - if(n->arena != 0) - { - arena_release(n->arena); - } - SLLStackPush(txt_shared->stripes_free_nodes[stripe_idx], n); - } - } - } - os_sleep_milliseconds(5); - } - os_sleep_milliseconds(1000); - } -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Basic Helpers + +internal TXT_LangKind +txt_lang_kind_from_extension(String8 extension) +{ + TXT_LangKind kind = TXT_LangKind_Null; + if(str8_match(extension, str8_lit("c"), 0) || + str8_match(extension, str8_lit("h"), 0)) + { + kind = TXT_LangKind_C; + } + else if(str8_match(extension, str8_lit("cpp"), StringMatchFlag_CaseInsensitive) || + str8_match(extension, str8_lit("cxx"), StringMatchFlag_CaseInsensitive) || + str8_match(extension, str8_lit("cc"), StringMatchFlag_CaseInsensitive) || + str8_match(extension, str8_lit("c++"), StringMatchFlag_CaseInsensitive) || + str8_match(extension, str8_lit("ixx"), StringMatchFlag_CaseInsensitive) || + str8_match(extension, str8_lit("cxxm"), StringMatchFlag_CaseInsensitive) || + str8_match(extension, str8_lit("c++m"), StringMatchFlag_CaseInsensitive) || + str8_match(extension, str8_lit("ccm"), StringMatchFlag_CaseInsensitive) || + str8_match(extension, str8_lit("cppm"), StringMatchFlag_CaseInsensitive) || + str8_match(extension, str8_lit("mpp"), StringMatchFlag_CaseInsensitive) || + str8_match(extension, str8_lit("C"), 0) || + str8_match(extension, str8_lit("hpp"), StringMatchFlag_CaseInsensitive) || + str8_match(extension, str8_lit("hxx"), StringMatchFlag_CaseInsensitive) || + str8_match(extension, str8_lit("hh"), StringMatchFlag_CaseInsensitive) || + str8_match(extension, str8_lit("h++"), StringMatchFlag_CaseInsensitive) || + str8_match(extension, str8_lit("H"), 0)) + { + kind = TXT_LangKind_CPlusPlus; + } + else if(str8_match(extension, str8_lit("odin"), StringMatchFlag_CaseInsensitive)) + { + kind = TXT_LangKind_Odin; + } + else if(str8_match(extension, str8_lit("jai"), StringMatchFlag_CaseInsensitive)) + { + kind = TXT_LangKind_Jai; + } + else if(str8_match(extension, str8_lit("zig"), StringMatchFlag_CaseInsensitive)) + { + kind = TXT_LangKind_Zig; + } + return kind; +} + +internal String8 +txt_extension_from_lang_kind(TXT_LangKind kind) +{ + String8 result = {0}; + switch(kind) + { + case TXT_LangKind_Null: + case TXT_LangKind_COUNT: + case TXT_LangKind_DisasmX64Intel: + {}break; + case TXT_LangKind_C: {result = str8_lit("c");}break; + case TXT_LangKind_CPlusPlus: {result = str8_lit("cpp");}break; + case TXT_LangKind_Odin: {result = str8_lit("odin");}break; + case TXT_LangKind_Jai: {result = str8_lit("jai");}break; + case TXT_LangKind_Zig: {result = str8_lit("zig");}break; + } + return result; +} + +internal TXT_LangKind +txt_lang_kind_from_architecture(Architecture arch) +{ + TXT_LangKind kind = TXT_LangKind_Null; + switch(arch) + { + default:{}break; + case Architecture_x64:{kind = TXT_LangKind_DisasmX64Intel;}break; + } + return kind; +} + +internal TXT_LangLexFunctionType * +txt_lex_function_from_lang_kind(TXT_LangKind kind) +{ + TXT_LangLexFunctionType *fn = 0; + switch(kind) + { + default:{}break; + case TXT_LangKind_C: {fn = txt_token_array_from_string__c_cpp;}break; + case TXT_LangKind_CPlusPlus: {fn = txt_token_array_from_string__c_cpp;}break; + case TXT_LangKind_Odin: {fn = txt_token_array_from_string__odin;}break; + case TXT_LangKind_Jai: {fn = txt_token_array_from_string__jai;}break; + case TXT_LangKind_Zig: {fn = txt_token_array_from_string__zig;}break; + case TXT_LangKind_DisasmX64Intel:{fn = txt_token_array_from_string__disasm_x64_intel;}break; + } + return fn; +} + +//////////////////////////////// +//~ rjf: Token Type Functions + +internal void +txt_token_chunk_list_push(Arena *arena, TXT_TokenChunkList *list, U64 cap, TXT_Token *token) +{ + TXT_TokenChunkNode *node = list->last; + if(node == 0 || node->count >= node->cap) + { + node = push_array(arena, TXT_TokenChunkNode, 1); + SLLQueuePush(list->first, list->last, node); + node->cap = cap; + node->v = push_array_no_zero(arena, TXT_Token, node->cap); + list->chunk_count += 1; + } + MemoryCopyStruct(&node->v[node->count], token); + node->count += 1; + list->token_count += 1; +} + +internal void +txt_token_list_push(Arena *arena, TXT_TokenList *list, TXT_Token *token) +{ + TXT_TokenNode *node = push_array(arena, TXT_TokenNode, 1); + MemoryCopyStruct(&node->v, token); + SLLQueuePush(list->first, list->last, node); + list->count += 1; +} + +internal TXT_TokenArray +txt_token_array_from_chunk_list(Arena *arena, TXT_TokenChunkList *list) +{ + TXT_TokenArray array = {0}; + array.count = list->token_count; + array.v = push_array_no_zero(arena, TXT_Token, array.count); + U64 idx = 0; + for(TXT_TokenChunkNode *n = list->first; n != 0; n = n->next) + { + MemoryCopy(array.v+idx, n->v, n->count*sizeof(TXT_Token)); + idx += n->count; + } + return array; +} + +internal TXT_TokenArray +txt_token_array_from_list(Arena *arena, TXT_TokenList *list) +{ + TXT_TokenArray array = {0}; + array.count = list->count; + array.v = push_array_no_zero(arena, TXT_Token, array.count); + U64 idx = 0; + for(TXT_TokenNode *n = list->first; n != 0; n = n->next) + { + MemoryCopyStruct(array.v+idx, &n->v); + idx += 1; + } + return array; +} + +//////////////////////////////// +//~ rjf: Lexing Functions + +internal TXT_TokenArray +txt_token_array_from_string__c_cpp(Arena *arena, U64 *bytes_processed_counter, String8 string) +{ + Temp scratch = scratch_begin(&arena, 1); + + //- rjf: generate token list + TXT_TokenChunkList tokens = {0}; + { + B32 comment_is_single_line = 0; + B32 string_is_char = 0; + TXT_TokenKind active_token_kind = TXT_TokenKind_Null; + U64 active_token_start_idx = 0; + B32 escaped = 0; + B32 next_escaped = 0; + U64 byte_process_start_idx = 0; + for(U64 idx = 0; idx <= string.size;) + { + U8 byte = (idx+0 < string.size) ? (string.str[idx+0]) : 0; + U8 next_byte = (idx+1 < string.size) ? (string.str[idx+1]) : 0; + + // rjf: update counter + if(bytes_processed_counter != 0 && ((idx-byte_process_start_idx) >= 1000 || idx == string.size)) + { + ins_atomic_u64_add_eval(bytes_processed_counter, (idx-byte_process_start_idx)); + byte_process_start_idx = idx; + } + + // rjf: escaping + if(escaped && (byte != '\r' && byte != '\n')) + { + next_escaped = 0; + } + else if(!escaped && byte == '\\') + { + next_escaped = 1; + } + + // rjf: take starter, determine active token kind + if(active_token_kind == TXT_TokenKind_Null) + { + // rjf: use next bytes to start a new token + if(0){} + else if(char_is_space(byte)) { active_token_kind = TXT_TokenKind_Whitespace; } + else if(byte == '_' || + byte == '$' || + char_is_alpha(byte)) { active_token_kind = TXT_TokenKind_Identifier; } + else if(char_is_digit(byte, 10) || + (byte == '.' && + 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 == '~' || 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; } + else if(byte == '#') { active_token_kind = TXT_TokenKind_Meta; } + + // rjf: start new token + if(active_token_kind != TXT_TokenKind_Null) + { + active_token_start_idx = idx; + } + + // rjf: invalid token kind -> emit error + else + { + TXT_Token token = {TXT_TokenKind_Error, r1u64(idx, idx+1)}; + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + } + + // rjf: look for ender + U64 ender_pad = 0; + B32 ender_found = 0; + if(active_token_kind != TXT_TokenKind_Null && idx>active_token_start_idx) + { + if(idx == string.size) + { + ender_pad = 0; + ender_found = 1; + } + else switch(active_token_kind) + { + default:break; + case TXT_TokenKind_Whitespace: + { + ender_found = !char_is_space(byte); + }break; + case TXT_TokenKind_Identifier: + { + ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '$'); + }break; + case TXT_TokenKind_Numeric: + { + ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '.' && byte != '\''); + }break; + case TXT_TokenKind_String: + { + ender_found = (!escaped && ((!string_is_char && byte == '"') || (string_is_char && byte == '\''))); + ender_pad += 1; + }break; + case TXT_TokenKind_Symbol: + { + ender_found = (byte != '~' && byte != '!' && + byte != '%' && byte != '^' && + byte != '&' && byte != '*' && + byte != '(' && byte != ')' && + byte != '-' && byte != '=' && + byte != '+' && byte != '[' && + byte != ']' && byte != '{' && + byte != '}' && byte != ':' && + byte != ';' && byte != ',' && + byte != '.' && byte != '<' && + byte != '>' && byte != '/' && + byte != '?' && byte != '|'); + }break; + case TXT_TokenKind_Comment: + { + 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; + } + }break; + case TXT_TokenKind_Meta: + { + ender_found = (!escaped && (byte == '\r' || byte == '\n')); + }break; + } + } + + // rjf: next byte is ender => emit token + if(ender_found) + { + TXT_Token token = {active_token_kind, r1u64(active_token_start_idx, idx+ender_pad)}; + active_token_kind = TXT_TokenKind_Null; + + // rjf: identifier -> keyword in special cases + if(token.kind == TXT_TokenKind_Identifier) + { + read_only local_persist String8 cpp_keywords[] = + { + str8_lit_comp("alignas"), + str8_lit_comp("alignof"), + str8_lit_comp("and"), + str8_lit_comp("and_eq"), + str8_lit_comp("asm"), + str8_lit_comp("atomic_cancel"), + str8_lit_comp("atomic_commit"), + str8_lit_comp("atomic_noexcept"), + str8_lit_comp("auto"), + str8_lit_comp("bitand"), + str8_lit_comp("bitor"), + str8_lit_comp("bool"), + str8_lit_comp("break"), + str8_lit_comp("case"), + str8_lit_comp("catch"), + str8_lit_comp("char"), + str8_lit_comp("char8_t"), + str8_lit_comp("char16_t"), + str8_lit_comp("char32_t"), + str8_lit_comp("class"), + str8_lit_comp("compl"), + str8_lit_comp("concept"), + str8_lit_comp("const"), + str8_lit_comp("consteval"), + str8_lit_comp("constexpr"), + str8_lit_comp("constinit"), + str8_lit_comp("const_cast"), + str8_lit_comp("continue"), + str8_lit_comp("co_await"), + str8_lit_comp("co_return"), + str8_lit_comp("co_yield"), + str8_lit_comp("decltype"), + str8_lit_comp("default"), + str8_lit_comp("delete"), + str8_lit_comp("do"), + str8_lit_comp("double"), + str8_lit_comp("dynamic_cast"), + str8_lit_comp("else"), + str8_lit_comp("enum"), + str8_lit_comp("explicit"), + str8_lit_comp("export"), + str8_lit_comp("extern"), + str8_lit_comp("false"), + str8_lit_comp("float"), + str8_lit_comp("for"), + str8_lit_comp("friend"), + str8_lit_comp("goto"), + str8_lit_comp("if"), + str8_lit_comp("inline"), + str8_lit_comp("int"), + str8_lit_comp("long"), + str8_lit_comp("mutable"), + str8_lit_comp("namespace"), + str8_lit_comp("new"), + str8_lit_comp("noexcept"), + str8_lit_comp("not"), + str8_lit_comp("not_eq"), + str8_lit_comp("nullptr"), + str8_lit_comp("operator"), + str8_lit_comp("or"), + str8_lit_comp("or_eq"), + str8_lit_comp("private"), + str8_lit_comp("protected"), + str8_lit_comp("public"), + str8_lit_comp("reflexpr"), + str8_lit_comp("register"), + str8_lit_comp("reinterpret_cast"), + str8_lit_comp("requires"), + str8_lit_comp("return"), + str8_lit_comp("short"), + str8_lit_comp("signed"), + str8_lit_comp("sizeof"), + str8_lit_comp("static"), + str8_lit_comp("static_assert"), + str8_lit_comp("static_cast"), + str8_lit_comp("struct"), + str8_lit_comp("switch"), + str8_lit_comp("synchronized"), + str8_lit_comp("template"), + str8_lit_comp("this"), + str8_lit_comp("thread_local"), + str8_lit_comp("throw"), + str8_lit_comp("true"), + str8_lit_comp("try"), + str8_lit_comp("typedef"), + str8_lit_comp("typeid"), + str8_lit_comp("typename"), + str8_lit_comp("union"), + str8_lit_comp("unsigned"), + str8_lit_comp("using"), + str8_lit_comp("virtual"), + str8_lit_comp("void"), + str8_lit_comp("volatile"), + str8_lit_comp("wchar_t"), + str8_lit_comp("while"), + str8_lit_comp("xor"), + str8_lit_comp("xor_eq"), + }; + String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); + for(U64 keyword_idx = 0; keyword_idx < ArrayCount(cpp_keywords); keyword_idx += 1) + { + if(str8_match(cpp_keywords[keyword_idx], token_string, 0)) + { + token.kind = TXT_TokenKind_Keyword; + break; + } + } + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + + // rjf: split symbols by maximum-munch-rule + else if(token.kind == TXT_TokenKind_Symbol) + { + read_only local_persist String8 c_cpp_multichar_symbol_strings[] = + { + str8_lit_comp("<<"), + str8_lit_comp(">>"), + str8_lit_comp("<="), + str8_lit_comp(">="), + str8_lit_comp("=="), + str8_lit_comp("!="), + str8_lit_comp("&&"), + str8_lit_comp("||"), + str8_lit_comp("|="), + str8_lit_comp("&="), + str8_lit_comp("^="), + str8_lit_comp("~="), + str8_lit_comp("+="), + str8_lit_comp("-="), + str8_lit_comp("*="), + str8_lit_comp("/="), + str8_lit_comp("%="), + str8_lit_comp("<<="), + str8_lit_comp(">>="), + str8_lit_comp("->"), + }; + String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); + for(U64 off = 0, next_off = token_string.size; off < token_string.size; off = next_off) + { + B32 found = 0; + for(U64 idx = 0; idx < ArrayCount(c_cpp_multichar_symbol_strings); idx += 1) + { + if(str8_match(str8_substr(string, r1u64(off, off+c_cpp_multichar_symbol_strings[idx].size)), + c_cpp_multichar_symbol_strings[idx], + 0)) + { + found = 1; + next_off = off + c_cpp_multichar_symbol_strings[idx].size; + TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + break; + } + } + if(!found) + { + next_off = off+1; + TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + } + } + + // rjf: all other tokens + else + { + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + + // rjf: increment by ender padding + idx += ender_pad; + } + + // rjf: advance by 1 byte if we haven't found an ender + if(!ender_found) + { + idx += 1; + } + escaped = next_escaped; + } + } + + //- rjf: token list -> token array + TXT_TokenArray result = txt_token_array_from_chunk_list(arena, &tokens); + scratch_end(scratch); + return result; +} + +internal TXT_TokenArray +txt_token_array_from_string__odin(Arena *arena, U64 *bytes_processed_counter, String8 string) +{ + Temp scratch = scratch_begin(&arena, 1); + + //- rjf: generate token list + TXT_TokenChunkList tokens = {0}; + { + B32 comment_is_single_line = 0; + B32 string_is_char = 0; + TXT_TokenKind active_token_kind = TXT_TokenKind_Null; + U64 active_token_start_idx = 0; + B32 escaped = 0; + B32 next_escaped = 0; + U64 byte_process_start_idx = 0; + for(U64 idx = 0; idx <= string.size;) + { + U8 byte = (idx+0 < string.size) ? (string.str[idx+0]) : 0; + U8 next_byte = (idx+1 < string.size) ? (string.str[idx+1]) : 0; + + // rjf: update counter + if(bytes_processed_counter != 0 && ((idx-byte_process_start_idx) >= 1000 || idx == string.size)) + { + ins_atomic_u64_add_eval(bytes_processed_counter, (idx-byte_process_start_idx)); + byte_process_start_idx = idx; + } + + // rjf: escaping + if(escaped && (byte != '\r' && byte != '\n')) + { + next_escaped = 0; + } + else if(!escaped && byte == '\\') + { + next_escaped = 1; + } + + // rjf: take starter, determine active token kind + if(active_token_kind == TXT_TokenKind_Null) + { + // rjf: use next bytes to start a new token + if(0){} + else if(char_is_space(byte)) { active_token_kind = TXT_TokenKind_Whitespace; } + else if(byte == '_' || + byte == '$' || + char_is_alpha(byte)) { active_token_kind = TXT_TokenKind_Identifier; } + else if(char_is_digit(byte, 10) || + (byte == '.' && + 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 == '~' || 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; } + else if(byte == '#') { active_token_kind = TXT_TokenKind_Meta; } + + // rjf: start new token + if(active_token_kind != TXT_TokenKind_Null) + { + active_token_start_idx = idx; + } + + // rjf: invalid token kind -> emit error + else + { + TXT_Token token = {TXT_TokenKind_Error, r1u64(idx, idx+1)}; + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + } + + // rjf: look for ender + U64 ender_pad = 0; + B32 ender_found = 0; + if(active_token_kind != TXT_TokenKind_Null && idx>active_token_start_idx) + { + if(idx == string.size) + { + ender_pad = 0; + ender_found = 1; + } + else switch(active_token_kind) + { + default:break; + case TXT_TokenKind_Whitespace: + { + ender_found = !char_is_space(byte); + }break; + case TXT_TokenKind_Identifier: + { + ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '$'); + }break; + case TXT_TokenKind_Numeric: + { + ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '.' && byte != '\''); + }break; + case TXT_TokenKind_String: + { + ender_found = (!escaped && ((!string_is_char && byte == '"') || (string_is_char && byte == '\''))); + ender_pad += 1; + }break; + case TXT_TokenKind_Symbol: + { + ender_found = (byte != '~' && byte != '!' && + byte != '%' && byte != '^' && + byte != '&' && byte != '*' && + byte != '(' && byte != ')' && + byte != '-' && byte != '=' && + byte != '+' && byte != '[' && + byte != ']' && byte != '{' && + byte != '}' && byte != ':' && + byte != ';' && byte != ',' && + byte != '.' && byte != '<' && + byte != '>' && byte != '/' && + byte != '?' && byte != '|'); + }break; + case TXT_TokenKind_Comment: + { + 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; + } + }break; + case TXT_TokenKind_Meta: + { + ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '$'); + }break; + } + } + + // rjf: next byte is ender => emit token + if(ender_found) + { + TXT_Token token = {active_token_kind, r1u64(active_token_start_idx, idx+ender_pad)}; + active_token_kind = TXT_TokenKind_Null; + + // rjf: identifier -> keyword in special cases + if(token.kind == TXT_TokenKind_Identifier) + { + read_only local_persist String8 odin_keywords[] = + { + str8_lit_comp("align_of"), + str8_lit_comp("asm"), + str8_lit_comp("auto_cast"), + str8_lit_comp("bit_set"), + str8_lit_comp("break"), + str8_lit_comp("case"), + str8_lit_comp("cast"), + str8_lit_comp("context"), + str8_lit_comp("continue"), + str8_lit_comp("defer"), + str8_lit_comp("distinct"), + str8_lit_comp("do"), + str8_lit_comp("dynamic"), + str8_lit_comp("else"), + str8_lit_comp("enum"), + str8_lit_comp("fallthrough"), + str8_lit_comp("for"), + str8_lit_comp("foreign"), + str8_lit_comp("if"), + str8_lit_comp("in"), + str8_lit_comp("map"), + str8_lit_comp("matrix"), + str8_lit_comp("not_in"), + str8_lit_comp("or_break"), + str8_lit_comp("or_continue"), + str8_lit_comp("or_else"), + str8_lit_comp("or_return"), + str8_lit_comp("package"), + str8_lit_comp("proc"), + str8_lit_comp("return"), + str8_lit_comp("size_of"), + str8_lit_comp("struct"), + str8_lit_comp("switch"), + str8_lit_comp("transmute"), + str8_lit_comp("typeid"), + str8_lit_comp("union"), + str8_lit_comp("using"), + str8_lit_comp("when"), + str8_lit_comp("where"), + str8_lit_comp("import"), + }; + String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); + for(U64 keyword_idx = 0; keyword_idx < ArrayCount(odin_keywords); keyword_idx += 1) + { + if(str8_match(odin_keywords[keyword_idx], token_string, 0)) + { + token.kind = TXT_TokenKind_Keyword; + break; + } + } + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + + // rjf: split symbols by maximum-munch-rule + else if(token.kind == TXT_TokenKind_Symbol) + { + read_only local_persist String8 odin_multichar_symbol_strings[] = + { + str8_lit_comp("<<"), + str8_lit_comp(">>"), + str8_lit_comp("<="), + str8_lit_comp(">="), + str8_lit_comp("=="), + str8_lit_comp("!="), + str8_lit_comp("&&"), + str8_lit_comp("||"), + str8_lit_comp("|="), + str8_lit_comp("&="), + str8_lit_comp("^="), + str8_lit_comp("~="), + str8_lit_comp("+="), + str8_lit_comp("-="), + str8_lit_comp("*="), + str8_lit_comp("/="), + str8_lit_comp("%="), + str8_lit_comp("<<="), + str8_lit_comp(">>="), + str8_lit_comp("->"), + }; + String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); + for(U64 off = 0, next_off = token_string.size; off < token_string.size; off = next_off) + { + B32 found = 0; + for(U64 idx = 0; idx < ArrayCount(odin_multichar_symbol_strings); idx += 1) + { + if(str8_match(str8_substr(string, r1u64(off, off+odin_multichar_symbol_strings[idx].size)), + odin_multichar_symbol_strings[idx], + 0)) + { + found = 1; + next_off = off + odin_multichar_symbol_strings[idx].size; + TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + break; + } + } + if(!found) + { + next_off = off+1; + TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + } + } + + // rjf: all other tokens + else + { + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + + // rjf: increment by ender padding + idx += ender_pad; + } + + // rjf: advance by 1 byte if we haven't found an ender + if(!ender_found) + { + idx += 1; + } + escaped = next_escaped; + } + } + + //- rjf: token list -> token array + TXT_TokenArray result = txt_token_array_from_chunk_list(arena, &tokens); + scratch_end(scratch); + return result; +} + +internal TXT_TokenArray +txt_token_array_from_string__jai(Arena *arena, U64 *bytes_processed_counter, String8 string) +{ + Temp scratch = scratch_begin(&arena, 1); + + //- rjf: generate token list + TXT_TokenChunkList tokens = {0}; + { + B32 comment_is_single_line = 0; + B32 string_is_char = 0; + TXT_TokenKind active_token_kind = TXT_TokenKind_Null; + U64 active_token_start_idx = 0; + B32 escaped = 0; + B32 next_escaped = 0; + U64 byte_process_start_idx = 0; + for(U64 idx = 0; idx <= string.size;) + { + U8 byte = (idx+0 < string.size) ? (string.str[idx+0]) : 0; + U8 next_byte = (idx+1 < string.size) ? (string.str[idx+1]) : 0; + + // rjf: update counter + if(bytes_processed_counter != 0 && ((idx-byte_process_start_idx) >= 1000 || idx == string.size)) + { + ins_atomic_u64_add_eval(bytes_processed_counter, (idx-byte_process_start_idx)); + byte_process_start_idx = idx; + } + + // rjf: escaping + if(escaped && (byte != '\r' && byte != '\n')) + { + next_escaped = 0; + } + else if(!escaped && byte == '\\') + { + next_escaped = 1; + } + + // rjf: take starter, determine active token kind + if(active_token_kind == TXT_TokenKind_Null) + { + // rjf: use next bytes to start a new token + if(0){} + else if(char_is_space(byte)) { active_token_kind = TXT_TokenKind_Whitespace; } + else if(byte == '_' || + byte == '$' || + char_is_alpha(byte)) { active_token_kind = TXT_TokenKind_Identifier; } + else if(char_is_digit(byte, 10) || + (byte == '.' && + 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 == '~' || 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; } + else if(byte == '#') { active_token_kind = TXT_TokenKind_Meta; } + + // rjf: start new token + if(active_token_kind != TXT_TokenKind_Null) + { + active_token_start_idx = idx; + } + + // rjf: invalid token kind -> emit error + else + { + TXT_Token token = {TXT_TokenKind_Error, r1u64(idx, idx+1)}; + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + } + + // rjf: look for ender + U64 ender_pad = 0; + B32 ender_found = 0; + if(active_token_kind != TXT_TokenKind_Null && idx>active_token_start_idx) + { + if(idx == string.size) + { + ender_pad = 0; + ender_found = 1; + } + else switch(active_token_kind) + { + default:break; + case TXT_TokenKind_Whitespace: + { + ender_found = !char_is_space(byte); + }break; + case TXT_TokenKind_Identifier: + { + ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '$'); + }break; + case TXT_TokenKind_Numeric: + { + ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '.' && byte != '\''); + }break; + case TXT_TokenKind_String: + { + ender_found = (!escaped && ((!string_is_char && byte == '"') || (string_is_char && byte == '\''))); + ender_pad += 1; + }break; + case TXT_TokenKind_Symbol: + { + ender_found = (byte != '~' && byte != '!' && + byte != '%' && byte != '^' && + byte != '&' && byte != '*' && + byte != '(' && byte != ')' && + byte != '-' && byte != '=' && + byte != '+' && byte != '[' && + byte != ']' && byte != '{' && + byte != '}' && byte != ':' && + byte != ';' && byte != ',' && + byte != '.' && byte != '<' && + byte != '>' && byte != '/' && + byte != '?' && byte != '|'); + }break; + case TXT_TokenKind_Comment: + { + 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; + } + }break; + case TXT_TokenKind_Meta: + { + ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '$'); + }break; + } + } + + // rjf: next byte is ender => emit token + if(ender_found) + { + TXT_Token token = {active_token_kind, r1u64(active_token_start_idx, idx+ender_pad)}; + active_token_kind = TXT_TokenKind_Null; + + // rjf: identifier -> keyword in special cases + if(token.kind == TXT_TokenKind_Identifier) + { + read_only local_persist String8 jai_keywords[] = + { + str8_lit_comp("bool"), + str8_lit_comp("true"), + str8_lit_comp("false"), + str8_lit_comp("int"), + str8_lit_comp("s8"), + str8_lit_comp("u8"), + str8_lit_comp("s16"), + str8_lit_comp("u16"), + str8_lit_comp("s32"), + str8_lit_comp("u32"), + str8_lit_comp("s64"), + str8_lit_comp("u64"), + str8_lit_comp("s128"), + str8_lit_comp("u128"), + str8_lit_comp("float"), + str8_lit_comp("float32"), + str8_lit_comp("float64"), + str8_lit_comp("void"), + str8_lit_comp("enum"), + str8_lit_comp("enum_flags"), + str8_lit_comp("size_of"), + str8_lit_comp("string"), + str8_lit_comp("type_of"), + str8_lit_comp("cast"), + str8_lit_comp("if"), + str8_lit_comp("ifs"), + str8_lit_comp("then"), + str8_lit_comp("else"), + str8_lit_comp("case"), + str8_lit_comp("for"), + str8_lit_comp("while"), + str8_lit_comp("break"), + str8_lit_comp("continue"), + str8_lit_comp("remove"), + str8_lit_comp("return"), + str8_lit_comp("inline"), + str8_lit_comp("null"), + str8_lit_comp("defer"), + str8_lit_comp("xx"), + }; + String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); + for(U64 keyword_idx = 0; keyword_idx < ArrayCount(jai_keywords); keyword_idx += 1) + { + if(str8_match(jai_keywords[keyword_idx], token_string, 0)) + { + token.kind = TXT_TokenKind_Keyword; + break; + } + } + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + + // rjf: split symbols by maximum-munch-rule + else if(token.kind == TXT_TokenKind_Symbol) + { + read_only local_persist String8 jai_multichar_symbol_strings[] = + { + str8_lit_comp("<<"), + str8_lit_comp(">>"), + str8_lit_comp("<="), + str8_lit_comp(">="), + str8_lit_comp("=="), + str8_lit_comp("!="), + str8_lit_comp("&&"), + str8_lit_comp("||"), + str8_lit_comp("|="), + str8_lit_comp("&="), + str8_lit_comp("^="), + str8_lit_comp("~="), + str8_lit_comp("+="), + str8_lit_comp("-="), + str8_lit_comp("*="), + str8_lit_comp("/="), + str8_lit_comp("%="), + str8_lit_comp("<<="), + str8_lit_comp(">>="), + str8_lit_comp("->"), + }; + String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); + for(U64 off = 0, next_off = token_string.size; off < token_string.size; off = next_off) + { + B32 found = 0; + for(U64 idx = 0; idx < ArrayCount(jai_multichar_symbol_strings); idx += 1) + { + if(str8_match(str8_substr(string, r1u64(off, off+jai_multichar_symbol_strings[idx].size)), + jai_multichar_symbol_strings[idx], + 0)) + { + found = 1; + next_off = off + jai_multichar_symbol_strings[idx].size; + TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + break; + } + } + if(!found) + { + next_off = off+1; + TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + } + } + + // rjf: all other tokens + else + { + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + + // rjf: increment by ender padding + idx += ender_pad; + } + + // rjf: advance by 1 byte if we haven't found an ender + if(!ender_found) + { + idx += 1; + } + escaped = next_escaped; + } + } + + //- rjf: token list -> token array + TXT_TokenArray result = txt_token_array_from_chunk_list(arena, &tokens); + scratch_end(scratch); + return result; +} + +internal TXT_TokenArray +txt_token_array_from_string__zig(Arena *arena, U64 *bytes_processed_counter, String8 string) +{ + Temp scratch = scratch_begin(&arena, 1); + + //- rjf: generate token list + TXT_TokenChunkList tokens = {0}; + { + B32 string_is_char = 0; + B32 string_is_line = 0; + TXT_TokenKind active_token_kind = TXT_TokenKind_Null; + U64 active_token_start_idx = 0; + B32 escaped = 0; + B32 next_escaped = 0; + U64 byte_process_start_idx = 0; + for(U64 idx = 0; idx <= string.size;) + { + U8 byte = (idx+0 < string.size) ? (string.str[idx+0]) : 0; + U8 next_byte = (idx+1 < string.size) ? (string.str[idx+1]) : 0; + + // rjf: update counter + if(bytes_processed_counter != 0 && ((idx-byte_process_start_idx) >= 1000 || idx == string.size)) + { + ins_atomic_u64_add_eval(bytes_processed_counter, (idx-byte_process_start_idx)); + byte_process_start_idx = idx; + } + + // rjf: escaping + if(escaped && (byte != '\r' && byte != '\n')) + { + next_escaped = 0; + } + else if(!escaped && byte == '\\') + { + next_escaped = 1; + } + + // rjf: take starter, determine active token kind + if(active_token_kind == TXT_TokenKind_Null) + { + // rjf: use next bytes to start a new token + if(0){} + else if(char_is_space(byte)) { active_token_kind = TXT_TokenKind_Whitespace; } + else if(byte == '_' || + char_is_alpha(byte)) { active_token_kind = TXT_TokenKind_Identifier; } + else if(char_is_digit(byte, 10) || + (byte == '.' && + 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_String; string_is_line = 1; } + else if(byte == '/' && next_byte == '/') { active_token_kind = TXT_TokenKind_Comment; } + 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 == '|' || + byte == 'c') { active_token_kind = TXT_TokenKind_Symbol; } + + // rjf: start new token + if(active_token_kind != TXT_TokenKind_Null) + { + active_token_start_idx = idx; + } + + // rjf: invalid token kind -> emit error + else + { + TXT_Token token = {TXT_TokenKind_Error, r1u64(idx, idx+1)}; + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + } + + // rjf: look for ender + U64 ender_pad = 0; + B32 ender_found = 0; + if(active_token_kind != TXT_TokenKind_Null && idx>active_token_start_idx) + { + if(idx == string.size) + { + ender_pad = 0; + ender_found = 1; + } + else switch(active_token_kind) + { + default:break; + case TXT_TokenKind_Whitespace: + { + ender_found = !char_is_space(byte); + }break; + case TXT_TokenKind_Identifier: + { + ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '$'); + }break; + case TXT_TokenKind_Numeric: + { + ender_found = (!char_is_alpha(byte) && !char_is_digit(byte, 10) && byte != '_' && byte != '.' && byte != '\''); + }break; + case TXT_TokenKind_String: + { + if (string_is_line) + { + ender_found = (!escaped && (byte == '\r' || byte == '\n')); + } + else + { + ender_found = (!escaped && ((!string_is_char && byte == '"') || (string_is_char && byte == '\''))); + ender_pad += 1; + } + }break; + case TXT_TokenKind_Symbol: + { + ender_found = (byte != '~' && byte != '!' && + byte != '%' && byte != '^' && + byte != '&' && byte != '*' && + byte != '(' && byte != ')' && + byte != '-' && byte != '=' && + byte != '+' && byte != '[' && + byte != ']' && byte != '{' && + byte != '}' && byte != ':' && + byte != ';' && byte != ',' && + byte != '.' && byte != '<' && + byte != '>' && byte != '/' && + byte != '?' && byte != '|' && + byte != 'c'); + }break; + case TXT_TokenKind_Comment: + { + ender_found = (!escaped && (byte == '\r' || byte == '\n')); + }break; + } + } + + // rjf: next byte is ender => emit token + if(ender_found) + { + TXT_Token token = {active_token_kind, r1u64(active_token_start_idx, idx+ender_pad)}; + active_token_kind = TXT_TokenKind_Null; + + // rjf: identifier -> keyword in special cases + if(token.kind == TXT_TokenKind_Identifier) + { + read_only local_persist String8 zig_keywords[] = + { + str8_lit_comp("addrspace"), + str8_lit_comp("align"), + str8_lit_comp("allowzero"), + str8_lit_comp("and"), + str8_lit_comp("anyframe"), + str8_lit_comp("anytype"), + str8_lit_comp("asm"), + str8_lit_comp("async"), + str8_lit_comp("await"), + str8_lit_comp("break"), + str8_lit_comp("callconv"), + str8_lit_comp("catch"), + str8_lit_comp("comptime"), + str8_lit_comp("const"), + str8_lit_comp("continue"), + str8_lit_comp("defer"), + str8_lit_comp("else"), + str8_lit_comp("enum"), + str8_lit_comp("errdefer"), + str8_lit_comp("error"), + str8_lit_comp("export"), + str8_lit_comp("extern"), + str8_lit_comp("fn"), + str8_lit_comp("for"), + str8_lit_comp("if"), + str8_lit_comp("inline"), + str8_lit_comp("noalias"), + str8_lit_comp("nosuspend"), + str8_lit_comp("noinline"), + str8_lit_comp("opaque"), + str8_lit_comp("or"), + str8_lit_comp("orelse"), + str8_lit_comp("packed"), + str8_lit_comp("pub"), + str8_lit_comp("resume"), + str8_lit_comp("return"), + str8_lit_comp("linksection"), + str8_lit_comp("struct"), + str8_lit_comp("suspend"), + str8_lit_comp("switch"), + str8_lit_comp("test"), + str8_lit_comp("threadlocal"), + str8_lit_comp("try"), + str8_lit_comp("union"), + str8_lit_comp("unreachable"), + str8_lit_comp("usingnamespace"), + str8_lit_comp("var"), + str8_lit_comp("volatile"), + str8_lit_comp("while"), + }; + String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); + for(U64 keyword_idx = 0; keyword_idx < ArrayCount(zig_keywords); keyword_idx += 1) + { + if(str8_match(zig_keywords[keyword_idx], token_string, 0)) + { + token.kind = TXT_TokenKind_Keyword; + break; + } + } + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + + // rjf: split symbols by maximum-munch-rule + else if(token.kind == TXT_TokenKind_Symbol) + { + read_only local_persist String8 zig_multichar_symbol_strings[] = + { + str8_lit_comp("<<"), + str8_lit_comp(">>"), + str8_lit_comp("<="), + str8_lit_comp(">="), + str8_lit_comp("=="), + str8_lit_comp("!="), + str8_lit_comp("&&"), + str8_lit_comp("||"), + str8_lit_comp("|="), + str8_lit_comp("&="), + str8_lit_comp("^="), + str8_lit_comp("~="), + str8_lit_comp("+="), + str8_lit_comp("-="), + str8_lit_comp("*="), + str8_lit_comp("/="), + str8_lit_comp("%="), + str8_lit_comp("<<="), + str8_lit_comp(">>="), + str8_lit_comp("->"), + }; + String8 token_string = str8_substr(string, r1u64(active_token_start_idx, idx+ender_pad)); + for(U64 off = 0, next_off = token_string.size; off < token_string.size; off = next_off) + { + B32 found = 0; + for(U64 idx = 0; idx < ArrayCount(zig_multichar_symbol_strings); idx += 1) + { + if(str8_match(str8_substr(string, r1u64(off, off+zig_multichar_symbol_strings[idx].size)), + zig_multichar_symbol_strings[idx], + 0)) + { + found = 1; + next_off = off + zig_multichar_symbol_strings[idx].size; + TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + break; + } + } + if(!found) + { + next_off = off+1; + TXT_Token token = {TXT_TokenKind_Symbol, r1u64(active_token_start_idx+off, active_token_start_idx+next_off)}; + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + } + } + + // rjf: all other tokens + else + { + txt_token_chunk_list_push(scratch.arena, &tokens, 4096, &token); + } + + // rjf: increment by ender padding + idx += ender_pad; + } + + // rjf: advance by 1 byte if we haven't found an ender + if(!ender_found) + { + idx += 1; + } + escaped = next_escaped; + } + } + + //- rjf: token list -> token array + TXT_TokenArray result = txt_token_array_from_chunk_list(arena, &tokens); + scratch_end(scratch); + return result; +} + +internal TXT_TokenArray +txt_token_array_from_string__disasm_x64_intel(Arena *arena, U64 *bytes_processed_counter, String8 string) +{ + Temp scratch = scratch_begin(&arena, 1); + + //- rjf: parse tokens + TXT_TokenChunkList tokens = {0}; + { + TXT_TokenKind active_token_kind = TXT_TokenKind_Null; + U64 active_token_start_off = 0; + U64 off = 0; + B32 escaped = 0; + B32 string_is_char = 0; + S32 brace_nest = 0; + S32 paren_nest = 0; + for(U64 advance = 0; off <= string.size; off += advance) + { + U8 byte = (off+0 < string.size) ? string.str[off+0] : 0; + U8 next_byte = (off+1 < string.size) ? string.str[off+1] : 0; + B32 ender_found = 0; + advance = (active_token_kind != TXT_TokenKind_Null ? 1 : 0); + if(off == string.size && active_token_kind != TXT_TokenKind_Null) + { + ender_found = 1; + advance = 1; + } + switch(active_token_kind) + { + default: + case TXT_TokenKind_Null: + { + if(byte == ' ' || byte == '\t' || byte == '\v' || byte == '\f' || byte == '\r' || byte == '\n') + { + active_token_start_off = off; + active_token_kind = TXT_TokenKind_Whitespace; + advance = 1; + } + else if(byte == '>' && brace_nest == 0 && paren_nest == 0) + { + active_token_start_off = off; + active_token_kind = TXT_TokenKind_Comment; + advance = 1; + } + else if(('a' <= byte && byte <= 'z') || ('A' <= byte && byte <= 'Z') || byte == '_') + { + active_token_start_off = off; + active_token_kind = TXT_TokenKind_Keyword; + advance = 1; + } + else if(byte == '\'') + { + active_token_start_off = off; + active_token_kind = TXT_TokenKind_String; + advance = 1; + string_is_char = 1; + } + else if(byte == '"') + { + active_token_start_off = off; + active_token_kind = TXT_TokenKind_String; + advance = 1; + string_is_char = 0; + } + else if(('0' <= byte && byte <= '9') || (byte == '.' && '0' <= next_byte && next_byte <= '9')) + { + active_token_start_off = off; + active_token_kind = TXT_TokenKind_Numeric; + advance = 1; + } + else if(byte == '~' || byte == '!' || byte == '%' || byte == '^' || + byte == '&' || byte == '*' || byte == '(' || byte == ')' || + byte == '-' || byte == '=' || byte == '+' || byte == '[' || + byte == ']' || byte == '{' || byte == '}' || byte == ';' || + byte == ':' || byte == '?' || byte == '/' || byte == '<' || + byte == '>' || byte == ',' || byte == '.') + { + active_token_start_off = off; + active_token_kind = TXT_TokenKind_Symbol; + advance = 1; + if(byte == '{') + { + brace_nest += 1; + } + else if(byte == '}') + { + brace_nest -= 1; + } + if(byte == '(') + { + paren_nest += 1; + } + else if(byte == ')') + { + paren_nest -= 1; + } + } + else + { + active_token_start_off = off; + active_token_kind = TXT_TokenKind_Error; + advance = 1; + } + }break; + case TXT_TokenKind_Whitespace: + if(byte != ' ' && byte != '\t' && byte != '\v' && byte != '\f') + { + ender_found = 1; + advance = 0; + }break; + case TXT_TokenKind_Keyword: + if((byte < 'a' || 'z' < byte) && (byte < 'A' || 'Z' < byte) && (byte < '0' || '9' < byte) && byte != '_') + { + ender_found = 1; + advance = 0; + }break; + case TXT_TokenKind_String: + { + U8 ender_byte = string_is_char ? '\'' : '"'; + if(!escaped && byte == ender_byte) + { + ender_found = 1; + advance = 1; + } + else if(escaped) + { + escaped = 0; + advance = 1; + } + else if(byte == '\\') + { + escaped = 1; + advance = 1; + } + else + { + U8 byte_class = utf8_class[byte>>3]; + if(byte_class > 1) + { + advance = (U64)byte_class; + } + } + }break; + case TXT_TokenKind_Numeric: + if((byte < 'a' || 'z' < byte) && (byte < 'A' || 'Z' < byte) && (byte < '0' || '9' < byte) && byte != '.') + { + ender_found = 1; + advance = 0; + }break; + case TXT_TokenKind_Symbol: + if(1) + { + // NOTE(rjf): avoiding maximum munch rule for now + ender_found = 1; + advance = 0; + } + 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 != '.') + { + ender_found = 1; + advance = 0; + }break; + case TXT_TokenKind_Error: + { + ender_found = 1; + advance = 0; + }break; + case TXT_TokenKind_Comment: + if(byte == '\n') + { + ender_found = 1; + advance = 1; + }break; + } + if(ender_found != 0) + { + if(brace_nest != 0 && active_token_kind == TXT_TokenKind_Keyword) + { + active_token_kind = TXT_TokenKind_Numeric; + } + if(paren_nest != 0 && active_token_kind == TXT_TokenKind_Keyword) + { + active_token_kind = TXT_TokenKind_Identifier; + } + TXT_Token token = {active_token_kind, r1u64(active_token_start_off, off+advance)}; + txt_token_chunk_list_push(arena, &tokens, 1024, &token); + active_token_kind = TXT_TokenKind_Null; + active_token_start_off = token.range.max; + } + } + } + + //- rjf: token list -> token array + TXT_TokenArray result = txt_token_array_from_chunk_list(arena, &tokens); + scratch_end(scratch); + return result; +} + +//////////////////////////////// +//~ rjf: Main Layer Initialization + +internal void +txt_init(void) +{ + Arena *arena = arena_alloc(); + txt_shared = push_array(arena, TXT_Shared, 1); + txt_shared->arena = arena; + txt_shared->slots_count = 1024; + txt_shared->slots = push_array(arena, TXT_Slot, txt_shared->slots_count); + txt_shared->stripes_count = Min(txt_shared->slots_count, os_get_system_info()->logical_processor_count); + txt_shared->stripes = push_array(arena, TXT_Stripe, txt_shared->stripes_count); + txt_shared->stripes_free_nodes = push_array(arena, TXT_Node *, txt_shared->stripes_count); + for(U64 idx = 0; idx < txt_shared->stripes_count; idx += 1) + { + txt_shared->stripes[idx].arena = arena_alloc(); + txt_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); + txt_shared->stripes[idx].cv = os_condition_variable_alloc(); + } + txt_shared->u2p_ring_size = KB(64); + txt_shared->u2p_ring_base = push_array_no_zero(arena, U8, txt_shared->u2p_ring_size); + txt_shared->u2p_ring_cv = os_condition_variable_alloc(); + txt_shared->u2p_ring_mutex = os_mutex_alloc(); + txt_shared->parse_thread_count = Clamp(1, os_get_system_info()->logical_processor_count-1, 4); + txt_shared->parse_threads = push_array(arena, OS_Handle, txt_shared->parse_thread_count); + for(U64 idx = 0; idx < txt_shared->parse_thread_count; idx += 1) + { + txt_shared->parse_threads[idx] = os_thread_launch(txt_parse_thread__entry_point, (void *)idx, 0); + } + txt_shared->evictor_thread = os_thread_launch(txt_evictor_thread__entry_point, 0, 0); +} + +//////////////////////////////// +//~ rjf: Thread Context Initialization + +internal void +txt_tctx_ensure_inited(void) +{ + if(txt_tctx == 0) + { + Arena *arena = arena_alloc(); + txt_tctx = push_array(arena, TXT_TCTX, 1); + txt_tctx->arena = arena; + } +} + +//////////////////////////////// +//~ rjf: User Clock + +internal void +txt_user_clock_tick(void) +{ + ins_atomic_u64_inc_eval(&txt_shared->user_clock_idx); +} + +internal U64 +txt_user_clock_idx(void) +{ + return ins_atomic_u64_eval(&txt_shared->user_clock_idx); +} + +//////////////////////////////// +//~ rjf: Scoped Access + +internal TXT_Scope * +txt_scope_open(void) +{ + txt_tctx_ensure_inited(); + TXT_Scope *scope = txt_tctx->free_scope; + if(scope) + { + SLLStackPop(txt_tctx->free_scope); + } + else + { + scope = push_array_no_zero(txt_tctx->arena, TXT_Scope, 1); + } + MemoryZeroStruct(scope); + return scope; +} + +internal void +txt_scope_close(TXT_Scope *scope) +{ + for(TXT_Touch *touch = scope->top_touch, *next = 0; touch != 0; touch = next) + { + U128 hash = touch->hash; + next = touch->next; + U64 slot_idx = hash.u64[1]%txt_shared->slots_count; + U64 stripe_idx = slot_idx%txt_shared->stripes_count; + TXT_Slot *slot = &txt_shared->slots[slot_idx]; + TXT_Stripe *stripe = &txt_shared->stripes[stripe_idx]; + OS_MutexScopeR(stripe->rw_mutex) + { + for(TXT_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(hash, n->hash) && touch->lang == n->lang) + { + ins_atomic_u64_dec_eval(&n->scope_ref_count); + break; + } + } + } + SLLStackPush(txt_tctx->free_touch, touch); + } + SLLStackPush(txt_tctx->free_scope, scope); +} + +internal void +txt_scope_touch_node__stripe_r_guarded(TXT_Scope *scope, TXT_Node *node) +{ + TXT_Touch *touch = txt_tctx->free_touch; + ins_atomic_u64_inc_eval(&node->scope_ref_count); + ins_atomic_u64_eval_assign(&node->last_time_touched_us, os_now_microseconds()); + ins_atomic_u64_eval_assign(&node->last_user_clock_idx_touched, txt_user_clock_idx()); + if(touch != 0) + { + SLLStackPop(txt_tctx->free_touch); + } + else + { + touch = push_array_no_zero(txt_tctx->arena, TXT_Touch, 1); + } + MemoryZeroStruct(touch); + touch->hash = node->hash; + touch->lang = node->lang; + SLLStackPush(scope->top_touch, touch); +} + +//////////////////////////////// +//~ rjf: Cache Lookups + +internal TXT_TextInfo +txt_text_info_from_hash_lang(TXT_Scope *scope, U128 hash, TXT_LangKind lang) +{ + TXT_TextInfo info = {0}; + if(!u128_match(hash, u128_zero())) + { + U64 slot_idx = hash.u64[1]%txt_shared->slots_count; + U64 stripe_idx = slot_idx%txt_shared->stripes_count; + TXT_Slot *slot = &txt_shared->slots[slot_idx]; + TXT_Stripe *stripe = &txt_shared->stripes[stripe_idx]; + B32 found = 0; + OS_MutexScopeR(stripe->rw_mutex) + { + for(TXT_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(hash, n->hash) && n->lang == lang) + { + MemoryCopyStruct(&info, &n->info); + info.bytes_processed = ins_atomic_u64_eval(&n->info.bytes_processed); + info.bytes_to_process = ins_atomic_u64_eval(&n->info.bytes_to_process); + found = 1; + txt_scope_touch_node__stripe_r_guarded(scope, n); + break; + } + } + } + B32 node_is_new = 0; + if(!found) + { + OS_MutexScopeW(stripe->rw_mutex) + { + TXT_Node *node = 0; + for(TXT_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(hash, n->hash) && n->lang == lang) + { + node = n; + break; + } + } + if(node == 0) + { + node = txt_shared->stripes_free_nodes[stripe_idx]; + if(node) + { + SLLStackPop(txt_shared->stripes_free_nodes[stripe_idx]); + } + else + { + node = push_array_no_zero(stripe->arena, TXT_Node, 1); + } + MemoryZeroStruct(node); + DLLPushBack(slot->first, slot->last, node); + node->hash = hash; + node->lang = lang; + node_is_new = 1; + } + } + } + if(node_is_new) + { + txt_u2p_enqueue_req(hash, lang, max_U64); + } + } + return info; +} + +internal TXT_TextInfo +txt_text_info_from_key_lang(TXT_Scope *scope, U128 key, TXT_LangKind lang, U128 *hash_out) +{ + TXT_TextInfo result = {0}; + for(U64 rewind_idx = 0; rewind_idx < 2; rewind_idx += 1) + { + U128 hash = hs_hash_from_key(key, rewind_idx); + result = txt_text_info_from_hash_lang(scope, hash, lang); + if(result.lines_count != 0) + { + if(hash_out) + { + *hash_out = hash; + } + break; + } + } + return result; +} + +//////////////////////////////// +//~ rjf: Text Info Extractor Helpers + +internal U64 +txt_off_from_info_pt(TXT_TextInfo *info, 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); + } + return off; +} + +internal TxtPt +txt_pt_from_info_off__linear_scan(TXT_TextInfo *info, U64 off) +{ + TxtPt pt = {0}; + { + for(U64 line_idx = 0; line_idx < info->lines_count; line_idx += 1) + { + if(contains_1u64(info->lines_ranges[line_idx], off)) + { + pt.line = (S64)line_idx + 1; + pt.column = (S64)(off - info->lines_ranges[line_idx].min) + 1; + } + } + } + return pt; +} + +internal TXT_TokenArray +txt_token_array_from_info_line_num__linear_scan(TXT_TextInfo *info, S64 line_num) +{ + TXT_TokenArray line_tokens = {0}; + if(1 <= line_num && line_num <= info->lines_count) + { + Rng1U64 line_range = info->lines_ranges[line_num-1]; + for(U64 token_idx = 0; token_idx < info->tokens.count; token_idx += 1) + { + Rng1U64 token_range = info->tokens.v[token_idx].range; + Rng1U64 token_x_line = intersect_1u64(token_range, line_range); + if(token_x_line.max > token_x_line.min) + { + if(line_tokens.v == 0) + { + line_tokens.v = info->tokens.v+token_idx; + } + line_tokens.count += 1; + } + else if(line_tokens.v != 0) + { + break; + } + } + } + return line_tokens; +} + +internal Rng1U64 +txt_expr_off_range_from_line_off_range_string_tokens(U64 off, Rng1U64 line_range, String8 line_text, TXT_TokenArray *line_tokens) +{ + Rng1U64 result = {0}; + Temp scratch = scratch_begin(0, 0); + { + // rjf: unpack line info + TXT_Token *line_tokens_first = line_tokens->v; + TXT_Token *line_tokens_opl = line_tokens->v+line_tokens->count; + + // rjf: find token containing `off` + TXT_Token *pt_token = 0; + for(TXT_Token *token = line_tokens_first; + token < line_tokens_opl; + token += 1) + { + if(contains_1u64(token->range, off)) + { + Rng1U64 token_range_clamped = intersect_1u64(line_range, token->range); + String8 token_string = str8_substr(line_text, r1u64(token_range_clamped.max - line_range.min, token_range_clamped.max - line_range.min)); + B32 token_ender = 0; + switch(token->kind) + { + default:{}break; + case TXT_TokenKind_Symbol: + { + token_ender = (str8_match(token_string, str8_lit("]"), 0)); + }break; + case TXT_TokenKind_Identifier: + case TXT_TokenKind_Keyword: + case TXT_TokenKind_String: + case TXT_TokenKind_Meta: + { + token_ender = 1; + }break; + } + if(token_ender) + { + pt_token = token; + } + break; + } + } + + // rjf: found token containing `off`? -> mark that as our initial range + if(pt_token != 0) + { + result = pt_token->range; + } + + // rjf: walk back from pt_token - try to find plausible start of expression + if(pt_token != 0) + { + B32 walkback_done = 0; + S32 nest = 0; + for(TXT_Token *wb_token = pt_token; + wb_token >= line_tokens_first && walkback_done == 0; + wb_token -= 1) + { + Rng1U64 wb_token_range_clamped = intersect_1u64(line_range, wb_token->range); + String8 wb_token_string = str8_substr(line_text, r1u64(wb_token_range_clamped.min - line_range.min, wb_token_range_clamped.max - line_range.min)); + B32 include_wb_token = 0; + switch(wb_token->kind) + { + default:{}break; + case TXT_TokenKind_Symbol: + { + B32 is_scope_resolution = str8_match(wb_token_string, str8_lit("::"), 0); + B32 is_dot = str8_match(wb_token_string, str8_lit("."), 0); + B32 is_arrow = str8_match(wb_token_string, str8_lit("->"), 0); + B32 is_open_bracket = str8_match(wb_token_string, str8_lit("["), 0); + B32 is_close_bracket = str8_match(wb_token_string, str8_lit("]"), 0); + nest -= !!(is_open_bracket); + nest += !!(is_close_bracket); + if(is_scope_resolution || + is_dot || + is_arrow || + is_open_bracket|| + is_close_bracket) + { + include_wb_token = 1; + } + }break; + case TXT_TokenKind_Identifier: + { + include_wb_token = 1; + }break; + } + if(include_wb_token) + { + result = union_1u64(result, wb_token->range); + } + else if(nest == 0) + { + walkback_done = 1; + } + } + } + } + scratch_end(scratch); + return result; +} + +internal Rng1U64 +txt_expr_off_range_from_info_data_pt(TXT_TextInfo *info, String8 data, TxtPt pt) +{ + Rng1U64 result = {0}; + Temp scratch = scratch_begin(0, 0); + if(1 <= pt.line && pt.line <= info->lines_count) + { + // rjf: unpack line info + Rng1U64 line_range = info->lines_ranges[pt.line-1]; + String8 line_text = str8_substr(data, line_range); + TXT_LineTokensSlice line_tokens_slice = txt_line_tokens_slice_from_info_data_line_range(scratch.arena, info, data, r1s64(pt.line, pt.line)); + TXT_TokenArray line_tokens = line_tokens_slice.line_tokens[0]; + TXT_Token *line_tokens_first = line_tokens.v; + TXT_Token *line_tokens_opl = line_tokens.v+line_tokens.count; + U64 pt_off = line_range.min + (pt.column-1); + + // rjf: grab offset range of expression + result = txt_expr_off_range_from_line_off_range_string_tokens(pt_off, line_range, line_text, &line_tokens); + } + scratch_end(scratch); + return result; +} + +internal String8 +txt_string_from_info_data_txt_rng(TXT_TextInfo *info, String8 data, TxtRng rng) +{ + Rng1U64 rng_off = r1u64(txt_off_from_info_pt(info, rng.min), txt_off_from_info_pt(info, rng.max)); + String8 result = str8_substr(data, rng_off); + return result; +} + +internal String8 +txt_string_from_info_data_line_num(TXT_TextInfo *info, String8 data, S64 line_num) +{ + String8 result = {0}; + if(1 <= line_num && line_num <= info->lines_count) + { + result = str8_substr(data, info->lines_ranges[line_num-1]); + } + return result; +} + +internal TXT_LineTokensSlice +txt_line_tokens_slice_from_info_data_line_range(Arena *arena, TXT_TextInfo *info, String8 data, Rng1S64 line_range) +{ + TXT_LineTokensSlice result = {0}; + Temp scratch = scratch_begin(&arena, 1); + if(info->lines_count != 0) + { + Rng1S64 line_range_clamped = r1s64(Clamp(1, line_range.min, (S64)info->lines_count), Clamp(1, line_range.max, (S64)info->lines_count)); + U64 line_count = (U64)dim_1s64(line_range_clamped)+1; + + // rjf: allocate output arrays + result.line_tokens = push_array(arena, TXT_TokenArray, line_count); + + // rjf: binary search to find first token + TXT_Token *tokens_first = 0; + ProfScope("binary search to find first token") + { + Rng1U64 slice_range = r1u64(info->lines_ranges[line_range_clamped.min-1].min, info->lines_ranges[line_range_clamped.max-1].max); + U64 min_idx = 0; + U64 opl_idx = info->tokens.count; + for(;;) + { + U64 mid_idx = (opl_idx+min_idx)/2; + if(mid_idx >= opl_idx) + { + break; + } + TXT_Token *mid_token = &info->tokens.v[mid_idx]; + if(mid_token->range.min > slice_range.max) + { + opl_idx = mid_idx; + } + else if(mid_token->range.max < slice_range.min) + { + min_idx = mid_idx; + } + else if(tokens_first == 0 || mid_token->range.min < tokens_first->range.min) + { + tokens_first = mid_token; + opl_idx = mid_idx; + } + if(mid_idx == min_idx && mid_idx+1 == opl_idx) + { + break; + } + } + } + + // rjf: grab per-line tokens + TXT_TokenList *line_tokens_lists = push_array(scratch.arena, TXT_TokenList, line_count); + if(tokens_first != 0) ProfScope("grab per-line tokens") + { + TXT_Token *tokens_opl = info->tokens.v+info->tokens.count; + U64 line_slice_idx = 0; + for(TXT_Token *token = tokens_first; token < tokens_opl && line_slice_idx < line_count;) + { + if(token->range.min < info->lines_ranges[line_slice_idx+line_range.min-1].max) + { + if(token->range.max > info->lines_ranges[line_slice_idx+line_range.min-1].min) + { + txt_token_list_push(scratch.arena, &line_tokens_lists[line_slice_idx], token); + } + B32 need_token_advance = 0; + B32 need_line_advance = 0; + if(token->range.max >= info->lines_ranges[line_slice_idx+line_range.min-1].max) + { + need_line_advance = 1; + } + if(token->range.max <= info->lines_ranges[line_slice_idx+line_range.min-1].max) + { + need_token_advance += 1; + } + if(need_line_advance) { line_slice_idx += 1; } + if(need_token_advance) { token += 1; } + } + else + { + line_slice_idx += 1; + } + } + } + + // rjf: bake per-line tokens to arrays + for(U64 line_slice_idx = 0; line_slice_idx < line_count; line_slice_idx += 1) + { + result.line_tokens[line_slice_idx] = txt_token_array_from_list(arena, &line_tokens_lists[line_slice_idx]); + } + } + scratch_end(scratch); + return result; +} + +//////////////////////////////// +//~ rjf: Transfer Threads + +internal B32 +txt_u2p_enqueue_req(U128 hash, TXT_LangKind lang, U64 endt_us) +{ + B32 good = 0; + OS_MutexScope(txt_shared->u2p_ring_mutex) for(;;) + { + U64 unconsumed_size = txt_shared->u2p_ring_write_pos - txt_shared->u2p_ring_read_pos; + U64 available_size = txt_shared->u2p_ring_size - unconsumed_size; + if(available_size >= sizeof(hash)+sizeof(lang)) + { + good = 1; + txt_shared->u2p_ring_write_pos += ring_write_struct(txt_shared->u2p_ring_base, txt_shared->u2p_ring_size, txt_shared->u2p_ring_write_pos, &hash); + txt_shared->u2p_ring_write_pos += ring_write_struct(txt_shared->u2p_ring_base, txt_shared->u2p_ring_size, txt_shared->u2p_ring_write_pos, &lang); + break; + } + if(os_now_microseconds() >= endt_us) + { + break; + } + os_condition_variable_wait(txt_shared->u2p_ring_cv, txt_shared->u2p_ring_mutex, endt_us); + } + if(good) + { + os_condition_variable_broadcast(txt_shared->u2p_ring_cv); + } + return good; +} + +internal void +txt_u2p_dequeue_req(U128 *hash_out, TXT_LangKind *lang_out) +{ + OS_MutexScope(txt_shared->u2p_ring_mutex) for(;;) + { + U64 unconsumed_size = txt_shared->u2p_ring_write_pos - txt_shared->u2p_ring_read_pos; + if(unconsumed_size >= sizeof(*hash_out) + sizeof(*lang_out)) + { + txt_shared->u2p_ring_read_pos += ring_read_struct(txt_shared->u2p_ring_base, txt_shared->u2p_ring_size, txt_shared->u2p_ring_read_pos, hash_out); + txt_shared->u2p_ring_read_pos += ring_read_struct(txt_shared->u2p_ring_base, txt_shared->u2p_ring_size, txt_shared->u2p_ring_read_pos, lang_out); + break; + } + os_condition_variable_wait(txt_shared->u2p_ring_cv, txt_shared->u2p_ring_mutex, max_U64); + } + os_condition_variable_broadcast(txt_shared->u2p_ring_cv); +} + +internal void +txt_parse_thread__entry_point(void *p) +{ + for(;;) + { + //- rjf: get next key + U128 hash = {0}; + TXT_LangKind lang = TXT_LangKind_Null; + txt_u2p_dequeue_req(&hash, &lang); + HS_Scope *scope = hs_scope_open(); + + //- rjf: unpack hash + U64 slot_idx = hash.u64[1]%txt_shared->slots_count; + U64 stripe_idx = slot_idx%txt_shared->stripes_count; + TXT_Slot *slot = &txt_shared->slots[slot_idx]; + TXT_Stripe *stripe = &txt_shared->stripes[stripe_idx]; + + //- rjf: take task + B32 got_task = 0; + OS_MutexScopeR(stripe->rw_mutex) + { + for(TXT_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(n->hash, hash) && n->lang == lang) + { + got_task = !ins_atomic_u32_eval_cond_assign(&n->is_working, 1, 0); + break; + } + } + } + + //- rjf: hash -> data + String8 data = {0}; + if(got_task) + { + data = hs_data_from_hash(scope, hash); + } + + //- rjf: data -> text info + Arena *info_arena = 0; + TXT_TextInfo info = {0}; + if(got_task && data.size != 0) + { + info_arena = arena_alloc(); + + //- rjf: grab pointers to working counters + U64 *bytes_processed_ptr = 0; + U64 *bytes_to_process_ptr = 0; + OS_MutexScopeR(stripe->rw_mutex) + { + for(TXT_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(n->hash, hash) && n->lang == lang) + { + bytes_processed_ptr = &n->info.bytes_processed; + bytes_to_process_ptr = &n->info.bytes_to_process; + } + } + } + + //- rjf: set # of bytes to process + if(bytes_to_process_ptr) + { + // (line ending calc) (line counting) (line measuring) (lexing) + ins_atomic_u64_eval_assign(bytes_to_process_ptr, 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; + { + 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; + } + info.line_end_kind = line_end_kind; + } + + //- rjf: bump progress + if(bytes_processed_ptr) + { + ins_atomic_u64_eval_assign(bytes_processed_ptr, Min(data.size, 1024)); + } + + //- rjf: count # of lines + U64 line_count = 1; + U64 byte_process_start_idx = 0; + for(U64 idx = 0; idx < data.size; idx += 1) + { + if(data.str[idx] == '\n' || data.str[idx] == '\r') + { + line_count += 1; + if(data.str[idx] == '\r') + { + idx += 1; + } + } + if(idx && idx%1000 == 0) + { + ins_atomic_u64_add_eval(bytes_processed_ptr, 1000); + } + } + + //- rjf: bump progress + if(bytes_processed_ptr) + { + ins_atomic_u64_eval_assign(bytes_processed_ptr, Min(data.size, 1024) + data.size); + } + + //- rjf: allocate & store line ranges + info.lines_count = line_count; + info.lines_ranges = push_array_no_zero(info_arena, Rng1U64, info.lines_count); + U64 line_idx = 0; + U64 line_start_idx = 0; + for(U64 idx = 0; idx <= data.size; idx += 1) + { + if(idx == data.size || data.str[idx] == '\n' || data.str[idx] == '\r') + { + Rng1U64 line_range = r1u64(line_start_idx, idx); + U64 line_size = dim_1u64(line_range); + info.lines_ranges[line_idx] = line_range; + info.lines_max_size = Max(info.lines_max_size, line_size); + line_idx += 1; + line_start_idx = idx+1; + if(idx < data.size && data.str[idx] == '\r') + { + line_start_idx += 1; + idx += 1; + } + } + if(idx && idx%1000 == 0) + { + ins_atomic_u64_add_eval(bytes_processed_ptr, 1000); + } + } + + //- rjf: bump progress + if(bytes_processed_ptr) + { + ins_atomic_u64_eval_assign(bytes_processed_ptr, Min(data.size, 1024) + data.size + data.size); + } + + //- rjf: lang -> lex function + TXT_LangLexFunctionType *lex_function = txt_lex_function_from_lang_kind(lang); + + //- rjf: lex function * data -> tokens + TXT_TokenArray tokens = {0}; + if(lex_function != 0) + { + tokens = lex_function(info_arena, bytes_processed_ptr, data); + } + info.tokens = tokens; + + //- rjf: bump progress + if(bytes_processed_ptr) + { + ins_atomic_u64_eval_assign(bytes_processed_ptr, Min(data.size, 1024) + data.size + data.size + data.size*(lex_function != 0)); + } + } + + //- rjf: commit results to cache + if(got_task) OS_MutexScopeW(stripe->rw_mutex) + { + for(TXT_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(n->hash, hash) && n->lang == lang) + { + n->arena = info_arena; + info.bytes_processed = n->info.bytes_processed; + info.bytes_to_process = n->info.bytes_to_process; + MemoryCopyStruct(&n->info, &info); + ins_atomic_u32_eval_assign(&n->is_working, 0); + ins_atomic_u64_inc_eval(&n->load_count); + break; + } + } + } + + hs_scope_close(scope); + } +} + +//////////////////////////////// +//~ rjf: Evictor Threads + +internal void +txt_evictor_thread__entry_point(void *p) +{ + for(;;) + { + U64 check_time_us = os_now_microseconds(); + U64 check_time_user_clocks = txt_user_clock_idx(); + U64 evict_threshold_us = 10*1000000; + U64 evict_threshold_user_clocks = 10; + for(U64 slot_idx = 0; slot_idx < txt_shared->slots_count; slot_idx += 1) + { + U64 stripe_idx = slot_idx%txt_shared->stripes_count; + TXT_Slot *slot = &txt_shared->slots[slot_idx]; + TXT_Stripe *stripe = &txt_shared->stripes[stripe_idx]; + B32 slot_has_work = 0; + OS_MutexScopeR(stripe->rw_mutex) + { + for(TXT_Node *n = slot->first; n != 0; n = n->next) + { + if(n->scope_ref_count == 0 && + n->last_time_touched_us+evict_threshold_us <= check_time_us && + n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && + n->load_count != 0 && + n->is_working == 0) + { + slot_has_work = 1; + break; + } + } + } + if(slot_has_work) OS_MutexScopeW(stripe->rw_mutex) + { + for(TXT_Node *n = slot->first, *next = 0; n != 0; n = next) + { + next = n->next; + if(n->scope_ref_count == 0 && + n->last_time_touched_us+evict_threshold_us <= check_time_us && + n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && + n->load_count != 0 && + n->is_working == 0) + { + DLLRemove(slot->first, slot->last, n); + if(n->arena != 0) + { + arena_release(n->arena); + } + SLLStackPush(txt_shared->stripes_free_nodes[stripe_idx], n); + } + } + } + os_sleep_milliseconds(5); + } + os_sleep_milliseconds(1000); + } +} diff --git a/src/texture_cache/texture_cache.c b/src/texture_cache/texture_cache.c index 744e2175..0f2484df 100644 --- a/src/texture_cache/texture_cache.c +++ b/src/texture_cache/texture_cache.c @@ -1,405 +1,405 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -//////////////////////////////// -//~ rjf: Basic Helpers - -internal TEX_Topology -tex_topology_make(Vec2S32 dim, R_Tex2DFormat fmt) -{ - TEX_Topology top = {0}; - top.dim.x = (S16)Clamp(0, dim.x, max_S32); - top.dim.y = (S16)Clamp(0, dim.y, max_S32); - top.fmt = fmt; - return top; -} - -//////////////////////////////// -//~ rjf: Main Layer Initialization - -internal void -tex_init(void) -{ - Arena *arena = arena_alloc(); - tex_shared = push_array(arena, TEX_Shared, 1); - tex_shared->arena = arena; - tex_shared->slots_count = 1024; - tex_shared->stripes_count = Min(tex_shared->slots_count, os_logical_core_count()); - tex_shared->slots = push_array(arena, TEX_Slot, tex_shared->slots_count); - tex_shared->stripes = push_array(arena, TEX_Stripe, tex_shared->stripes_count); - tex_shared->stripes_free_nodes = push_array(arena, TEX_Node *, tex_shared->stripes_count); - for(U64 idx = 0; idx < tex_shared->stripes_count; idx += 1) - { - tex_shared->stripes[idx].arena = arena_alloc(); - tex_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); - tex_shared->stripes[idx].cv = os_condition_variable_alloc(); - } - tex_shared->u2x_ring_size = KB(64); - tex_shared->u2x_ring_base = push_array_no_zero(arena, U8, tex_shared->u2x_ring_size); - tex_shared->u2x_ring_cv = os_condition_variable_alloc(); - tex_shared->u2x_ring_mutex = os_mutex_alloc(); - tex_shared->xfer_thread_count = Clamp(1, os_logical_core_count()-1, 4); - tex_shared->xfer_threads = push_array(arena, OS_Handle, tex_shared->xfer_thread_count); - for(U64 idx = 0; idx < tex_shared->xfer_thread_count; idx += 1) - { - tex_shared->xfer_threads[idx] = os_launch_thread(tex_xfer_thread__entry_point, (void *)idx, 0); - } - tex_shared->evictor_thread = os_launch_thread(tex_evictor_thread__entry_point, 0, 0); -} - -//////////////////////////////// -//~ rjf: Thread Context Initialization - -internal void -tex_tctx_ensure_inited(void) -{ - if(tex_tctx == 0) - { - Arena *arena = arena_alloc(); - tex_tctx = push_array(arena, TEX_TCTX, 1); - tex_tctx->arena = arena; - } -} - -//////////////////////////////// -//~ rjf: User Clock - -internal void -tex_user_clock_tick(void) -{ - ins_atomic_u64_inc_eval(&tex_shared->user_clock_idx); -} - -internal U64 -tex_user_clock_idx(void) -{ - return ins_atomic_u64_eval(&tex_shared->user_clock_idx); -} - -//////////////////////////////// -//~ rjf: Scoped Access - -internal TEX_Scope * -tex_scope_open(void) -{ - tex_tctx_ensure_inited(); - TEX_Scope *scope = tex_tctx->free_scope; - if(scope) - { - SLLStackPop(tex_tctx->free_scope); - } - else - { - scope = push_array_no_zero(tex_tctx->arena, TEX_Scope, 1); - } - MemoryZeroStruct(scope); - return scope; -} - -internal void -tex_scope_close(TEX_Scope *scope) -{ - for(TEX_Touch *touch = scope->top_touch, *next = 0; touch != 0; touch = next) - { - U128 hash = touch->hash; - next = touch->next; - U64 slot_idx = hash.u64[1]%tex_shared->slots_count; - U64 stripe_idx = slot_idx%tex_shared->stripes_count; - TEX_Slot *slot = &tex_shared->slots[slot_idx]; - TEX_Stripe *stripe = &tex_shared->stripes[stripe_idx]; - OS_MutexScopeR(stripe->rw_mutex) - { - for(TEX_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(hash, n->hash) && MemoryMatchStruct(&touch->topology, &n->topology)) - { - ins_atomic_u64_dec_eval(&n->scope_ref_count); - break; - } - } - } - SLLStackPush(tex_tctx->free_touch, touch); - } - SLLStackPush(tex_tctx->free_scope, scope); -} - -internal void -tex_scope_touch_node__stripe_r_guarded(TEX_Scope *scope, TEX_Node *node) -{ - TEX_Touch *touch = tex_tctx->free_touch; - ins_atomic_u64_inc_eval(&node->scope_ref_count); - ins_atomic_u64_eval_assign(&node->last_time_touched_us, os_now_microseconds()); - ins_atomic_u64_eval_assign(&node->last_user_clock_idx_touched, tex_user_clock_idx()); - if(touch != 0) - { - SLLStackPop(tex_tctx->free_touch); - } - else - { - touch = push_array_no_zero(tex_tctx->arena, TEX_Touch, 1); - } - MemoryZeroStruct(touch); - touch->hash = node->hash; - touch->topology = node->topology; - SLLStackPush(scope->top_touch, touch); -} - -//////////////////////////////// -//~ rjf: Cache Lookups - -internal R_Handle -tex_texture_from_hash_topology(TEX_Scope *scope, U128 hash, TEX_Topology topology) -{ - R_Handle handle = {0}; - { - U64 slot_idx = hash.u64[1]%tex_shared->slots_count; - U64 stripe_idx = slot_idx%tex_shared->stripes_count; - TEX_Slot *slot = &tex_shared->slots[slot_idx]; - TEX_Stripe *stripe = &tex_shared->stripes[stripe_idx]; - B32 found = 0; - B32 stale = 0; - OS_MutexScopeR(stripe->rw_mutex) - { - for(TEX_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(hash, n->hash) && MemoryMatchStruct(&topology, &n->topology)) - { - handle = n->texture; - found = !r_handle_match(r_handle_zero(), handle); - tex_scope_touch_node__stripe_r_guarded(scope, n); - break; - } - } - } - B32 node_is_new = 0; - if(!found) - { - OS_MutexScopeW(stripe->rw_mutex) - { - TEX_Node *node = 0; - for(TEX_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(hash, n->hash) && MemoryMatchStruct(&topology, &n->topology)) - { - node = n; - break; - } - } - if(node == 0) - { - node = tex_shared->stripes_free_nodes[stripe_idx]; - if(node) - { - SLLStackPop(tex_shared->stripes_free_nodes[stripe_idx]); - } - else - { - node = push_array_no_zero(stripe->arena, TEX_Node, 1); - } - MemoryZeroStruct(node); - DLLPushBack(slot->first, slot->last, node); - node->hash = hash; - MemoryCopyStruct(&node->topology, &topology); - node_is_new = 1; - } - } - } - if(node_is_new) - { - tex_u2x_enqueue_req(hash, topology, max_U64); - } - } - return handle; -} - -internal R_Handle -tex_texture_from_key_topology(TEX_Scope *scope, U128 key, TEX_Topology topology, U128 *hash_out) -{ - R_Handle handle = {0}; - for(U64 rewind_idx = 0; rewind_idx < 2; rewind_idx += 1) - { - U128 hash = hs_hash_from_key(key, rewind_idx); - handle = tex_texture_from_hash_topology(scope, hash, topology); - if(!r_handle_match(handle, r_handle_zero())) - { - if(hash_out) - { - *hash_out = hash; - } - break; - } - } - return handle; -} - -//////////////////////////////// -//~ rjf: Transfer Threads - -internal B32 -tex_u2x_enqueue_req(U128 hash, TEX_Topology top, U64 endt_us) -{ - B32 good = 0; - OS_MutexScope(tex_shared->u2x_ring_mutex) for(;;) - { - U64 unconsumed_size = tex_shared->u2x_ring_write_pos-tex_shared->u2x_ring_read_pos; - U64 available_size = tex_shared->u2x_ring_size-unconsumed_size; - if(available_size >= sizeof(hash)+sizeof(top)) - { - good = 1; - tex_shared->u2x_ring_write_pos += ring_write_struct(tex_shared->u2x_ring_base, tex_shared->u2x_ring_size, tex_shared->u2x_ring_write_pos, &hash); - tex_shared->u2x_ring_write_pos += ring_write_struct(tex_shared->u2x_ring_base, tex_shared->u2x_ring_size, tex_shared->u2x_ring_write_pos, &top); - break; - } - if(os_now_microseconds() >= endt_us) - { - break; - } - os_condition_variable_wait(tex_shared->u2x_ring_cv, tex_shared->u2x_ring_mutex, endt_us); - } - if(good) - { - os_condition_variable_broadcast(tex_shared->u2x_ring_cv); - } - return good; -} - -internal void -tex_u2x_dequeue_req(U128 *hash_out, TEX_Topology *top_out) -{ - OS_MutexScope(tex_shared->u2x_ring_mutex) for(;;) - { - U64 unconsumed_size = tex_shared->u2x_ring_write_pos-tex_shared->u2x_ring_read_pos; - if(unconsumed_size >= sizeof(*hash_out)+sizeof(*top_out)) - { - tex_shared->u2x_ring_read_pos += ring_read_struct(tex_shared->u2x_ring_base, tex_shared->u2x_ring_size, tex_shared->u2x_ring_read_pos, hash_out); - tex_shared->u2x_ring_read_pos += ring_read_struct(tex_shared->u2x_ring_base, tex_shared->u2x_ring_size, tex_shared->u2x_ring_read_pos, top_out); - break; - } - os_condition_variable_wait(tex_shared->u2x_ring_cv, tex_shared->u2x_ring_mutex, max_U64); - } - os_condition_variable_broadcast(tex_shared->u2x_ring_cv); -} - -internal void -tex_xfer_thread__entry_point(void *p) -{ - for(;;) - { - HS_Scope *scope = hs_scope_open(); - - //- rjf: decode - U128 hash = {0}; - TEX_Topology top = {0}; - tex_u2x_dequeue_req(&hash, &top); - - //- rjf: unpack hash - U64 slot_idx = hash.u64[1]%tex_shared->slots_count; - U64 stripe_idx = slot_idx%tex_shared->stripes_count; - TEX_Slot *slot = &tex_shared->slots[slot_idx]; - TEX_Stripe *stripe = &tex_shared->stripes[stripe_idx]; - - //- rjf: take task - B32 got_task = 0; - OS_MutexScopeR(stripe->rw_mutex) - { - for(TEX_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(n->hash, hash) && MemoryMatchStruct(&top, &n->topology)) - { - got_task = !ins_atomic_u32_eval_cond_assign(&n->is_working, 1, 0); - break; - } - } - } - - //- rjf: hash -> data - String8 data = {0}; - if(got_task) - { - data = hs_data_from_hash(scope, hash); - } - - //- rjf: data * topology -> texture - R_Handle texture = {0}; - if(got_task && top.dim.x > 0 && top.dim.y > 0 && data.size >= (U64)top.dim.x*(U64)top.dim.y*(U64)r_tex2d_format_bytes_per_pixel_table[top.fmt]) - { - texture = r_tex2d_alloc(R_ResourceKind_Static, v2s32(top.dim.x, top.dim.y), top.fmt, data.str); - } - - //- rjf: commit results to cache - if(got_task) OS_MutexScopeW(stripe->rw_mutex) - { - for(TEX_Node *n = slot->first; n != 0; n = n->next) - { - if(u128_match(n->hash, hash) && MemoryMatchStruct(&top, &n->topology)) - { - n->texture = texture; - ins_atomic_u32_eval_assign(&n->is_working, 0); - ins_atomic_u64_inc_eval(&n->load_count); - break; - } - } - } - - hs_scope_close(scope); - } -} - -//////////////////////////////// -//~ rjf: Evictor Threads - -internal void -tex_evictor_thread__entry_point(void *p) -{ - for(;;) - { - U64 check_time_us = os_now_microseconds(); - U64 check_time_user_clocks = tex_user_clock_idx(); - U64 evict_threshold_us = 10*1000000; - U64 evict_threshold_user_clocks = 10; - for(U64 slot_idx = 0; slot_idx < tex_shared->slots_count; slot_idx += 1) - { - U64 stripe_idx = slot_idx%tex_shared->stripes_count; - TEX_Slot *slot = &tex_shared->slots[slot_idx]; - TEX_Stripe *stripe = &tex_shared->stripes[stripe_idx]; - B32 slot_has_work = 0; - OS_MutexScopeR(stripe->rw_mutex) - { - for(TEX_Node *n = slot->first; n != 0; n = n->next) - { - if(n->scope_ref_count == 0 && - n->last_time_touched_us+evict_threshold_us <= check_time_us && - n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && - n->load_count != 0 && - n->is_working == 0) - { - slot_has_work = 1; - break; - } - } - } - if(slot_has_work) OS_MutexScopeW(stripe->rw_mutex) - { - for(TEX_Node *n = slot->first, *next = 0; n != 0; n = next) - { - next = n->next; - if(n->scope_ref_count == 0 && - n->last_time_touched_us+evict_threshold_us <= check_time_us && - n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && - n->load_count != 0 && - n->is_working == 0) - { - DLLRemove(slot->first, slot->last, n); - if(!r_handle_match(n->texture, r_handle_zero())) - { - r_tex2d_release(n->texture); - } - SLLStackPush(tex_shared->stripes_free_nodes[stripe_idx], n); - } - } - } - os_sleep_milliseconds(5); - } - os_sleep_milliseconds(1000); - } -} +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Basic Helpers + +internal TEX_Topology +tex_topology_make(Vec2S32 dim, R_Tex2DFormat fmt) +{ + TEX_Topology top = {0}; + top.dim.x = (S16)Clamp(0, dim.x, max_S32); + top.dim.y = (S16)Clamp(0, dim.y, max_S32); + top.fmt = fmt; + return top; +} + +//////////////////////////////// +//~ rjf: Main Layer Initialization + +internal void +tex_init(void) +{ + Arena *arena = arena_alloc(); + tex_shared = push_array(arena, TEX_Shared, 1); + tex_shared->arena = arena; + tex_shared->slots_count = 1024; + tex_shared->stripes_count = Min(tex_shared->slots_count, os_get_system_info()->logical_processor_count); + tex_shared->slots = push_array(arena, TEX_Slot, tex_shared->slots_count); + tex_shared->stripes = push_array(arena, TEX_Stripe, tex_shared->stripes_count); + tex_shared->stripes_free_nodes = push_array(arena, TEX_Node *, tex_shared->stripes_count); + for(U64 idx = 0; idx < tex_shared->stripes_count; idx += 1) + { + tex_shared->stripes[idx].arena = arena_alloc(); + tex_shared->stripes[idx].rw_mutex = os_rw_mutex_alloc(); + tex_shared->stripes[idx].cv = os_condition_variable_alloc(); + } + tex_shared->u2x_ring_size = KB(64); + tex_shared->u2x_ring_base = push_array_no_zero(arena, U8, tex_shared->u2x_ring_size); + tex_shared->u2x_ring_cv = os_condition_variable_alloc(); + tex_shared->u2x_ring_mutex = os_mutex_alloc(); + tex_shared->xfer_thread_count = Clamp(1, os_get_system_info()->logical_processor_count-1, 4); + tex_shared->xfer_threads = push_array(arena, OS_Handle, tex_shared->xfer_thread_count); + for(U64 idx = 0; idx < tex_shared->xfer_thread_count; idx += 1) + { + tex_shared->xfer_threads[idx] = os_thread_launch(tex_xfer_thread__entry_point, (void *)idx, 0); + } + tex_shared->evictor_thread = os_thread_launch(tex_evictor_thread__entry_point, 0, 0); +} + +//////////////////////////////// +//~ rjf: Thread Context Initialization + +internal void +tex_tctx_ensure_inited(void) +{ + if(tex_tctx == 0) + { + Arena *arena = arena_alloc(); + tex_tctx = push_array(arena, TEX_TCTX, 1); + tex_tctx->arena = arena; + } +} + +//////////////////////////////// +//~ rjf: User Clock + +internal void +tex_user_clock_tick(void) +{ + ins_atomic_u64_inc_eval(&tex_shared->user_clock_idx); +} + +internal U64 +tex_user_clock_idx(void) +{ + return ins_atomic_u64_eval(&tex_shared->user_clock_idx); +} + +//////////////////////////////// +//~ rjf: Scoped Access + +internal TEX_Scope * +tex_scope_open(void) +{ + tex_tctx_ensure_inited(); + TEX_Scope *scope = tex_tctx->free_scope; + if(scope) + { + SLLStackPop(tex_tctx->free_scope); + } + else + { + scope = push_array_no_zero(tex_tctx->arena, TEX_Scope, 1); + } + MemoryZeroStruct(scope); + return scope; +} + +internal void +tex_scope_close(TEX_Scope *scope) +{ + for(TEX_Touch *touch = scope->top_touch, *next = 0; touch != 0; touch = next) + { + U128 hash = touch->hash; + next = touch->next; + U64 slot_idx = hash.u64[1]%tex_shared->slots_count; + U64 stripe_idx = slot_idx%tex_shared->stripes_count; + TEX_Slot *slot = &tex_shared->slots[slot_idx]; + TEX_Stripe *stripe = &tex_shared->stripes[stripe_idx]; + OS_MutexScopeR(stripe->rw_mutex) + { + for(TEX_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(hash, n->hash) && MemoryMatchStruct(&touch->topology, &n->topology)) + { + ins_atomic_u64_dec_eval(&n->scope_ref_count); + break; + } + } + } + SLLStackPush(tex_tctx->free_touch, touch); + } + SLLStackPush(tex_tctx->free_scope, scope); +} + +internal void +tex_scope_touch_node__stripe_r_guarded(TEX_Scope *scope, TEX_Node *node) +{ + TEX_Touch *touch = tex_tctx->free_touch; + ins_atomic_u64_inc_eval(&node->scope_ref_count); + ins_atomic_u64_eval_assign(&node->last_time_touched_us, os_now_microseconds()); + ins_atomic_u64_eval_assign(&node->last_user_clock_idx_touched, tex_user_clock_idx()); + if(touch != 0) + { + SLLStackPop(tex_tctx->free_touch); + } + else + { + touch = push_array_no_zero(tex_tctx->arena, TEX_Touch, 1); + } + MemoryZeroStruct(touch); + touch->hash = node->hash; + touch->topology = node->topology; + SLLStackPush(scope->top_touch, touch); +} + +//////////////////////////////// +//~ rjf: Cache Lookups + +internal R_Handle +tex_texture_from_hash_topology(TEX_Scope *scope, U128 hash, TEX_Topology topology) +{ + R_Handle handle = {0}; + { + U64 slot_idx = hash.u64[1]%tex_shared->slots_count; + U64 stripe_idx = slot_idx%tex_shared->stripes_count; + TEX_Slot *slot = &tex_shared->slots[slot_idx]; + TEX_Stripe *stripe = &tex_shared->stripes[stripe_idx]; + B32 found = 0; + B32 stale = 0; + OS_MutexScopeR(stripe->rw_mutex) + { + for(TEX_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(hash, n->hash) && MemoryMatchStruct(&topology, &n->topology)) + { + handle = n->texture; + found = !r_handle_match(r_handle_zero(), handle); + tex_scope_touch_node__stripe_r_guarded(scope, n); + break; + } + } + } + B32 node_is_new = 0; + if(!found) + { + OS_MutexScopeW(stripe->rw_mutex) + { + TEX_Node *node = 0; + for(TEX_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(hash, n->hash) && MemoryMatchStruct(&topology, &n->topology)) + { + node = n; + break; + } + } + if(node == 0) + { + node = tex_shared->stripes_free_nodes[stripe_idx]; + if(node) + { + SLLStackPop(tex_shared->stripes_free_nodes[stripe_idx]); + } + else + { + node = push_array_no_zero(stripe->arena, TEX_Node, 1); + } + MemoryZeroStruct(node); + DLLPushBack(slot->first, slot->last, node); + node->hash = hash; + MemoryCopyStruct(&node->topology, &topology); + node_is_new = 1; + } + } + } + if(node_is_new) + { + tex_u2x_enqueue_req(hash, topology, max_U64); + } + } + return handle; +} + +internal R_Handle +tex_texture_from_key_topology(TEX_Scope *scope, U128 key, TEX_Topology topology, U128 *hash_out) +{ + R_Handle handle = {0}; + for(U64 rewind_idx = 0; rewind_idx < 2; rewind_idx += 1) + { + U128 hash = hs_hash_from_key(key, rewind_idx); + handle = tex_texture_from_hash_topology(scope, hash, topology); + if(!r_handle_match(handle, r_handle_zero())) + { + if(hash_out) + { + *hash_out = hash; + } + break; + } + } + return handle; +} + +//////////////////////////////// +//~ rjf: Transfer Threads + +internal B32 +tex_u2x_enqueue_req(U128 hash, TEX_Topology top, U64 endt_us) +{ + B32 good = 0; + OS_MutexScope(tex_shared->u2x_ring_mutex) for(;;) + { + U64 unconsumed_size = tex_shared->u2x_ring_write_pos-tex_shared->u2x_ring_read_pos; + U64 available_size = tex_shared->u2x_ring_size-unconsumed_size; + if(available_size >= sizeof(hash)+sizeof(top)) + { + good = 1; + tex_shared->u2x_ring_write_pos += ring_write_struct(tex_shared->u2x_ring_base, tex_shared->u2x_ring_size, tex_shared->u2x_ring_write_pos, &hash); + tex_shared->u2x_ring_write_pos += ring_write_struct(tex_shared->u2x_ring_base, tex_shared->u2x_ring_size, tex_shared->u2x_ring_write_pos, &top); + break; + } + if(os_now_microseconds() >= endt_us) + { + break; + } + os_condition_variable_wait(tex_shared->u2x_ring_cv, tex_shared->u2x_ring_mutex, endt_us); + } + if(good) + { + os_condition_variable_broadcast(tex_shared->u2x_ring_cv); + } + return good; +} + +internal void +tex_u2x_dequeue_req(U128 *hash_out, TEX_Topology *top_out) +{ + OS_MutexScope(tex_shared->u2x_ring_mutex) for(;;) + { + U64 unconsumed_size = tex_shared->u2x_ring_write_pos-tex_shared->u2x_ring_read_pos; + if(unconsumed_size >= sizeof(*hash_out)+sizeof(*top_out)) + { + tex_shared->u2x_ring_read_pos += ring_read_struct(tex_shared->u2x_ring_base, tex_shared->u2x_ring_size, tex_shared->u2x_ring_read_pos, hash_out); + tex_shared->u2x_ring_read_pos += ring_read_struct(tex_shared->u2x_ring_base, tex_shared->u2x_ring_size, tex_shared->u2x_ring_read_pos, top_out); + break; + } + os_condition_variable_wait(tex_shared->u2x_ring_cv, tex_shared->u2x_ring_mutex, max_U64); + } + os_condition_variable_broadcast(tex_shared->u2x_ring_cv); +} + +internal void +tex_xfer_thread__entry_point(void *p) +{ + for(;;) + { + HS_Scope *scope = hs_scope_open(); + + //- rjf: decode + U128 hash = {0}; + TEX_Topology top = {0}; + tex_u2x_dequeue_req(&hash, &top); + + //- rjf: unpack hash + U64 slot_idx = hash.u64[1]%tex_shared->slots_count; + U64 stripe_idx = slot_idx%tex_shared->stripes_count; + TEX_Slot *slot = &tex_shared->slots[slot_idx]; + TEX_Stripe *stripe = &tex_shared->stripes[stripe_idx]; + + //- rjf: take task + B32 got_task = 0; + OS_MutexScopeR(stripe->rw_mutex) + { + for(TEX_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(n->hash, hash) && MemoryMatchStruct(&top, &n->topology)) + { + got_task = !ins_atomic_u32_eval_cond_assign(&n->is_working, 1, 0); + break; + } + } + } + + //- rjf: hash -> data + String8 data = {0}; + if(got_task) + { + data = hs_data_from_hash(scope, hash); + } + + //- rjf: data * topology -> texture + R_Handle texture = {0}; + if(got_task && top.dim.x > 0 && top.dim.y > 0 && data.size >= (U64)top.dim.x*(U64)top.dim.y*(U64)r_tex2d_format_bytes_per_pixel_table[top.fmt]) + { + texture = r_tex2d_alloc(R_ResourceKind_Static, v2s32(top.dim.x, top.dim.y), top.fmt, data.str); + } + + //- rjf: commit results to cache + if(got_task) OS_MutexScopeW(stripe->rw_mutex) + { + for(TEX_Node *n = slot->first; n != 0; n = n->next) + { + if(u128_match(n->hash, hash) && MemoryMatchStruct(&top, &n->topology)) + { + n->texture = texture; + ins_atomic_u32_eval_assign(&n->is_working, 0); + ins_atomic_u64_inc_eval(&n->load_count); + break; + } + } + } + + hs_scope_close(scope); + } +} + +//////////////////////////////// +//~ rjf: Evictor Threads + +internal void +tex_evictor_thread__entry_point(void *p) +{ + for(;;) + { + U64 check_time_us = os_now_microseconds(); + U64 check_time_user_clocks = tex_user_clock_idx(); + U64 evict_threshold_us = 10*1000000; + U64 evict_threshold_user_clocks = 10; + for(U64 slot_idx = 0; slot_idx < tex_shared->slots_count; slot_idx += 1) + { + U64 stripe_idx = slot_idx%tex_shared->stripes_count; + TEX_Slot *slot = &tex_shared->slots[slot_idx]; + TEX_Stripe *stripe = &tex_shared->stripes[stripe_idx]; + B32 slot_has_work = 0; + OS_MutexScopeR(stripe->rw_mutex) + { + for(TEX_Node *n = slot->first; n != 0; n = n->next) + { + if(n->scope_ref_count == 0 && + n->last_time_touched_us+evict_threshold_us <= check_time_us && + n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && + n->load_count != 0 && + n->is_working == 0) + { + slot_has_work = 1; + break; + } + } + } + if(slot_has_work) OS_MutexScopeW(stripe->rw_mutex) + { + for(TEX_Node *n = slot->first, *next = 0; n != 0; n = next) + { + next = n->next; + if(n->scope_ref_count == 0 && + n->last_time_touched_us+evict_threshold_us <= check_time_us && + n->last_user_clock_idx_touched+evict_threshold_user_clocks <= check_time_user_clocks && + n->load_count != 0 && + n->is_working == 0) + { + DLLRemove(slot->first, slot->last, n); + if(!r_handle_match(n->texture, r_handle_zero())) + { + r_tex2d_release(n->texture); + } + SLLStackPush(tex_shared->stripes_free_nodes[stripe_idx], n); + } + } + } + os_sleep_milliseconds(5); + } + os_sleep_milliseconds(1000); + } +} diff --git a/src/ui/ui_core.c b/src/ui/ui_core.c index 32075443..cc1249a3 100644 --- a/src/ui/ui_core.c +++ b/src/ui/ui_core.c @@ -1,2995 +1,2995 @@ -// Copyright (c) 2024 Epic Games Tools -// Licensed under the MIT license (https://opensource.org/license/mit/) - -#undef RADDBG_LAYER_COLOR -#define RADDBG_LAYER_COLOR 0.80f, 0.40f, 0.35f - -//////////////////////////////// -//~ rjf: Globals - -thread_static UI_State *ui_state = 0; - -//////////////////////////////// -//~ rjf: Basic Type Functions - -internal U64 -ui_hash_from_string(U64 seed, String8 string) -{ - U64 result = seed; - for(U64 i = 0; i < string.size; i += 1) - { - result = ((result << 5) + result) + string.str[i]; - } - return result; -} - -internal String8 -ui_hash_part_from_key_string(String8 string) -{ - String8 result = string; - - // rjf: look for ### patterns, which can replace the entirety of the part of - // the string that is hashed. - U64 hash_replace_signifier_pos = str8_find_needle(string, 0, str8_lit("###"), 0); - if(hash_replace_signifier_pos < string.size) - { - result = str8_skip(string, hash_replace_signifier_pos); - } - - return result; -} - -internal String8 -ui_display_part_from_key_string(String8 string) -{ - U64 hash_pos = str8_find_needle(string, 0, str8_lit("##"), 0); - string.size = hash_pos; - return string; -} - -internal UI_Key -ui_key_zero(void) -{ - UI_Key result = {0}; - return result; -} - -internal UI_Key -ui_key_make(U64 v) -{ - UI_Key result = {v}; - return result; -} - -internal UI_Key -ui_key_from_string(UI_Key seed_key, String8 string) -{ - ProfBeginFunction(); - UI_Key result = {0}; - if(string.size != 0) - { - String8 hash_part = ui_hash_part_from_key_string(string); - result.u64[0] = ui_hash_from_string(seed_key.u64[0], hash_part); - } - ProfEnd(); - return result; -} - -internal UI_Key -ui_key_from_stringf(UI_Key seed_key, char *fmt, ...) -{ - Temp scratch = scratch_begin(0, 0); - va_list args; - va_start(args, fmt); - String8 string = push_str8fv(scratch.arena, fmt, args); - va_end(args); - UI_Key key = ui_key_from_string(seed_key, string); - scratch_end(scratch); - return key; -} - -internal B32 -ui_key_match(UI_Key a, UI_Key b) -{ - return a.u64[0] == b.u64[0]; -} - -//////////////////////////////// -//~ rjf: Event Type Functions - -internal UI_EventNode * -ui_event_list_push(Arena *arena, UI_EventList *list, UI_Event *v) -{ - UI_EventNode *n = push_array(arena, UI_EventNode, 1); - MemoryCopyStruct(&n->v, v); - n->v.string = push_str8_copy(arena, n->v.string); - DLLPushBack(list->first, list->last, n); - list->count += 1; - return n; -} - -internal void -ui_eat_event(UI_EventList *list, UI_EventNode *node) -{ - DLLRemove(list->first, list->last, node); - list->count -= 1; -} - -//////////////////////////////// -//~ rjf: Text Operation Functions - -internal B32 -ui_char_is_scan_boundary(U8 c) -{ - return (char_is_alpha(c) || char_is_digit(c, 10) || c == '_'); -} - -internal S64 -ui_scanned_column_from_column(String8 string, S64 start_column, Side side) -{ - S64 new_column = start_column; - S64 delta = (!!side)*2 - 1; - B32 found_text = 0; - B32 found_non_space = 0; - S64 start_off = delta < 0 ? delta : 0; - for(S64 col = start_column+start_off; 1 <= col && col <= string.size+1; col += delta) - { - U8 byte = (col <= string.size) ? string.str[col-1] : 0; - B32 is_non_space = !char_is_space(byte); - B32 is_name = ui_char_is_scan_boundary(byte); - if(((side == Side_Min) && (col == 1)) || - ((side == Side_Max) && (col == string.size+1)) || - (found_non_space && !is_non_space) || - (found_text && !is_name)) - { - new_column = col + (!side && col != 1); - break; - } - else if (!found_text && is_name) - { - found_text = 1; - } - else if (!found_non_space && is_non_space) - { - found_non_space = 1; - } - } - return new_column; -} - -internal UI_TxtOp -ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, TxtPt cursor, TxtPt mark) -{ - TxtPt next_cursor = cursor; - TxtPt next_mark = mark; - TxtRng range = {0}; - String8 replace = {0}; - String8 copy = {0}; - UI_TxtOpFlags flags = 0; - Vec2S32 delta = event->delta_2s32; - Vec2S32 original_delta = delta; - - //- rjf: resolve high-level delta into byte delta, based on unit - switch(event->delta_unit) - { - default:{}break; - case UI_EventDeltaUnit_Char: - { - // TODO(rjf): this should account for multi-byte characters in UTF-8... for now, just assume ASCII and - // no-op - }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; - }break; - case UI_EventDeltaUnit_Line: - case UI_EventDeltaUnit_Whole: - case UI_EventDeltaUnit_Page: - { - S64 first_nonwhitespace_column = 1; - for(U64 idx = 0; idx < string.size; idx += 1) - { - if(!char_is_space(string.str[idx])) - { - first_nonwhitespace_column = (S64)idx + 1; - 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); - }break; - } - - //- rjf: zero delta - if(!txt_pt_match(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)) - { - next_cursor.column += delta.x; - } - - //- rjf: cap at line - if(event->flags & UI_EventFlag_CapAtLine) - { - next_cursor.column = Clamp(1, next_cursor.column, (S64)(string.size+1)); - } - - //- rjf: in some cases, we want to pick a selection side based on the delta - if(!txt_pt_match(cursor, mark) && event->flags & UI_EventFlag_PickSelectSide) - { - if(original_delta.x < 0 || original_delta.y < 0) - { - next_cursor = next_mark = txt_pt_min(cursor, mark); - } - else if(original_delta.x > 0 || original_delta.y > 0) - { - next_cursor = next_mark = txt_pt_max(cursor, mark); - } - } - - //- rjf: copying - if(event->flags & UI_EventFlag_Copy) - { - if(cursor.line == mark.line) - { - copy = str8_substr(string, r1u64(cursor.column-1, mark.column-1)); - flags |= UI_TxtOpFlag_Copy; - } - else - { - flags |= UI_TxtOpFlag_Invalid; - } - } - - //- rjf: pasting - if(event->flags & UI_EventFlag_Paste) - { - range = txt_rng(cursor, mark); - replace = os_get_clipboard_text(arena); - next_cursor = next_mark = txt_pt(cursor.line, cursor.column+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(""); - next_cursor = next_mark = new_pos; - } - - //- rjf: stick mark to cursor, when we don't want to keep it in the same spot - if(!(event->flags & UI_EventFlag_KeepMark)) - { - next_mark = next_cursor; - } - - //- rjf: insertion - if(event->string.size != 0) - { - range = txt_rng(cursor, mark); - replace = push_str8_copy(arena, event->string); - next_cursor = next_mark = txt_pt(range.min.line, range.min.column + event->string.size); - } - - //- rjf: replace & commit -> replace entire range -#if 0 - if(event->flags & UI_EventFlag_ReplaceAndCommit) - { - range = txt_rng(txt_pt(cursor.line, 1), txt_pt(cursor.line, line.size+1)); - } -#endif - - //- rjf: determine if this event should be taken, based on bounds of cursor - { - if(next_cursor.column > string.size+1 || 1 > next_cursor.column || event->delta_2s32.y != 0) - { - flags |= UI_TxtOpFlag_Invalid; - } - next_cursor.column = Clamp(1, next_cursor.column, string.size+replace.size+1); - next_mark.column = Clamp(1, next_mark.column, string.size+replace.size+1); - } - - //- rjf: build+fill - UI_TxtOp op = {0}; - { - op.flags = flags; - op.replace = replace; - op.copy = copy; - op.range = range; - op.cursor = next_cursor; - op.mark = next_mark; - } - return op; -} - -internal String8 -ui_push_string_replace_range(Arena *arena, String8 string, Rng1S64 col_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) - { - range.min = 0; - } - if(range.max > string.size) - { - range.max = string.size; - } - - //- rjf: calculate new size - U64 old_size = string.size; - U64 new_size = old_size - (range.max - range.min) + replace.size; - - //- rjf: push+fill new string storage - U8 *push_base = push_array(arena, U8, new_size); - { - MemoryCopy(push_base+0, string.str, range.min); - MemoryCopy(push_base+range.min+replace.size, string.str+range.max, string.size-range.max); - if(replace.str != 0) - { - MemoryCopy(push_base+range.min, replace.str, replace.size); - } - } - - String8 result = str8(push_base, new_size); - return result; -} - -//////////////////////////////// -//~ rjf: Sizes - -internal UI_Size -ui_size(UI_SizeKind kind, F32 value, F32 strictness) -{ - UI_Size size = {kind, value, strictness}; - return size; -} - -//////////////////////////////// -//~ rjf: Scroll Point Type Functions - -internal UI_ScrollPt -ui_scroll_pt(S64 idx, F32 off) -{ - UI_ScrollPt pt = {idx, off}; - return pt; -} - -internal void -ui_scroll_pt_target_idx(UI_ScrollPt *v, S64 idx) -{ - v->off = mod_f32(v->off, 1.f) + (F32)(v->idx+(S64)v->off - idx); - v->idx = idx; -} - -internal void -ui_scroll_pt_clamp_idx(UI_ScrollPt *v, Rng1S64 range) -{ - if(v->idx < range.min || range.max < v->idx) - { - S64 clamped = range.min; - ui_scroll_pt_target_idx(v, clamped); - } -} - -//////////////////////////////// -//~ rjf: Boxes - -internal B32 -ui_box_is_nil(UI_Box *box) -{ - return box == 0 || box == &ui_g_nil_box; -} - -internal UI_BoxRec -ui_box_rec_df(UI_Box *box, UI_Box *root, U64 sib_member_off, U64 child_member_off) -{ - UI_BoxRec result = {0}; - result.next = &ui_g_nil_box; - if(!ui_box_is_nil(*MemberFromOffset(UI_Box **, box, child_member_off))) - { - result.next = *MemberFromOffset(UI_Box **, box, child_member_off); - result.push_count = 1; - } - else for(UI_Box *p = box; !ui_box_is_nil(p) && p != root; p = p->parent) - { - if(!ui_box_is_nil(*MemberFromOffset(UI_Box **, p, sib_member_off))) - { - result.next = *MemberFromOffset(UI_Box **, p, sib_member_off); - break; - } - result.pop_count += 1; - } - return result; -} - -internal void -ui_box_list_push(Arena *arena, UI_BoxList *list, UI_Box *box) -{ - UI_BoxNode *n = push_array(arena, UI_BoxNode, 1); - n->box = box; - SLLQueuePush(list->first, list->last, n); - list->count += 1; -} - -//////////////////////////////// -//~ rjf: State Building / Selecting - -internal UI_State * -ui_state_alloc(void) -{ - Arena *arena = arena_alloc(); - UI_State *ui = push_array(arena, UI_State, 1); - ui->arena = arena; - ui->build_arenas[0] = arena_alloc(); - ui->build_arenas[1] = arena_alloc(); - ui->drag_state_arena = arena_alloc(); - ui->string_hover_arena = arena_alloc(); - ui->box_table_size = 4096; - ui->box_table = push_array(arena, UI_BoxHashSlot, ui->box_table_size); - UI_InitStackNils(ui); - return ui; -} - -internal void -ui_state_release(UI_State *state) -{ - arena_release(state->string_hover_arena); - arena_release(state->drag_state_arena); - for(int i = 0; i < ArrayCount(state->build_arenas); i += 1) - { - arena_release(state->build_arenas[i]); - } - arena_release(state->arena); -} - -internal UI_Box * -ui_root_from_state(UI_State *state) -{ - return state->root; -} - -internal B32 -ui_animating_from_state(UI_State *state) -{ - return state->is_animating; -} - -internal void -ui_select_state(UI_State *state) -{ - ui_state = state; -} - -internal UI_State * -ui_get_selected_state(void) -{ - return ui_state; -} - -//////////////////////////////// -//~ rjf: Implicit State Accessors/Mutators - -//- rjf: per-frame info - -internal Arena * -ui_build_arena(void) -{ - Arena *result = ui_state->build_arenas[ui_state->build_index%ArrayCount(ui_state->build_arenas)]; - return result; -} - -internal OS_Handle -ui_window(void) -{ - return ui_state->window; -} - -internal UI_EventList * -ui_events(void) -{ - return ui_state->events; -} - -internal Vec2F32 -ui_mouse(void) -{ - return ui_state->mouse; -} - -internal F_Tag -ui_icon_font(void) -{ - return ui_state->icon_info.icon_font; -} - -internal String8 -ui_icon_string_from_kind(UI_IconKind icon_kind) -{ - return ui_state->icon_info.icon_kind_text_map[icon_kind]; -} - -internal F32 -ui_dt(void) -{ - return ui_state->animation_dt; -} - -//- rjf: event consumption helpers - -internal B32 -ui_key_press(OS_EventFlags mods, OS_Key key) -{ - UI_EventList *list = ui_events(); - B32 result = 0; - for(UI_EventNode *n = list->first; n != 0; n = n->next) - { - if(n->v.kind == UI_EventKind_Press && n->v.key == key && n->v.modifiers == mods) - { - result = 1; - ui_eat_event(list, n); - break; - } - } - return result; -} - -internal B32 -ui_key_release(OS_EventFlags mods, OS_Key key) -{ - UI_EventList *list = ui_events(); - B32 result = 0; - for(UI_EventNode *n = list->first; n != 0; n = n->next) - { - if(n->v.kind == UI_EventKind_Release && n->v.key == key && n->v.modifiers == mods) - { - result = 1; - ui_eat_event(list, n); - break; - } - } - return result; -} - -internal B32 -ui_text(U32 character) -{ - UI_EventList *list = ui_events(); - B32 result = 0; - Temp scratch = scratch_begin(0, 0); - String8 character_text = str8_from_32(scratch.arena, str32(&character, 1)); - for(UI_EventNode *n = list->first; n != 0; n = n->next) - { - if(n->v.kind == UI_EventKind_Text && str8_match(character_text, n->v.string, 0)) - { - result = 1; - ui_eat_event(list, n); - break; - } - } - scratch_end(scratch); - return result; -} - -internal B32 -ui_slot_press(UI_EventActionSlot slot) -{ - UI_EventList *list = ui_events(); - B32 result = 0; - for(UI_EventNode *n = list->first; n != 0; n = n->next) - { - if(n->v.kind == UI_EventKind_Press && n->v.slot == slot) - { - result = 1; - ui_eat_event(list, n); - break; - } - } - return result; -} - -//- rjf: drag data - -internal Vec2F32 -ui_drag_start_mouse(void) -{ - return ui_state->drag_start_mouse; -} - -internal Vec2F32 -ui_drag_delta(void) -{ - return sub_2f32(ui_mouse(), ui_state->drag_start_mouse); -} - -internal void -ui_store_drag_data(String8 string) -{ - arena_clear(ui_state->drag_state_arena); - ui_state->drag_state_data = push_str8_copy(ui_state->drag_state_arena, string); -} - -internal String8 -ui_get_drag_data(U64 min_required_size) -{ - if(ui_state->drag_state_data.size < min_required_size) - { - Temp scratch = scratch_begin(0, 0); - String8 str = {push_array(scratch.arena, U8, min_required_size), min_required_size}; - ui_store_drag_data(str); - scratch_end(scratch); - } - return ui_state->drag_state_data; -} - -//- rjf: hovered string info - -internal B32 -ui_string_hover_active(void) -{ - return (ui_state->build_index > 0 && ui_state->string_hover_build_index >= ui_state->build_index-1 && - os_now_microseconds() >= ui_state->string_hover_begin_us + 500000); -} - -internal U64 -ui_string_hover_begin_time_us(void) -{ - return ui_state->string_hover_begin_us; -} - -internal String8 -ui_string_hover_string(Arena *arena) -{ - String8 result = push_str8_copy(arena, ui_state->string_hover_string); - return result; -} - -internal D_FancyRunList -ui_string_hover_runs(Arena *arena) -{ - D_FancyRunList result = d_fancy_run_list_copy(arena, &ui_state->string_hover_fancy_runs); - return result; -} - -//- rjf: interaction keys - -internal UI_Key -ui_hot_key(void) -{ - return ui_state->hot_box_key; -} - -internal UI_Key -ui_active_key(UI_MouseButtonKind button_kind) -{ - return ui_state->active_box_key[button_kind]; -} - -internal UI_Key -ui_drop_hot_key(void) -{ - return ui_state->drop_hot_box_key; -} - -//- rjf: controls over interaction - -internal void -ui_kill_action(void) -{ - for(EachEnumVal(UI_MouseButtonKind, k)) - { - ui_state->active_box_key[k] = ui_key_zero(); - } -} - -//- rjf: box cache lookup - -internal UI_Box * -ui_box_from_key(UI_Key key) -{ - ProfBeginFunction(); - UI_Box *result = &ui_g_nil_box; - if(!ui_key_match(key, ui_key_zero())) - { - U64 slot = key.u64[0] % ui_state->box_table_size; - for(UI_Box *b = ui_state->box_table[slot].hash_first; !ui_box_is_nil(b); b = b->hash_next) - { - if(ui_key_match(b->key, key)) - { - result = b; - break; - } - } - } - ProfEnd(); - return result; -} - -//////////////////////////////// -//~ rjf: Top-Level Building API - -internal void -ui_begin_build(OS_Handle window, UI_EventList *events, UI_IconInfo *icon_info, UI_WidgetPaletteInfo *widget_palette_info, UI_AnimationInfo *animation_info, F32 real_dt, F32 animation_dt) -{ - //- rjf: reset per-build ui state - { - UI_InitStacks(ui_state); - ui_state->root = &ui_g_nil_box; - ui_state->ctx_menu_touched_this_frame = 0; - ui_state->is_animating = 0; - ui_state->clipboard_copy_key = ui_key_zero(); - ui_state->last_build_box_count = ui_state->build_box_count; - ui_state->build_box_count = 0; - ui_state->tooltip_open = 0; - ui_state->ctx_menu_changed = 0; - } - - //- rjf: detect mouse-moves - for(UI_EventNode *n = events->first; n != 0; n = n->next) - { - if(n->v.kind == UI_EventKind_MouseMove) - { - ui_state->last_time_mousemoved_us = os_now_microseconds(); - } - } - - //- rjf: fill build phase parameters - { - ui_state->events = events; - ui_state->window = window; - ui_state->mouse = (os_window_is_focused(window) || ui_state->last_time_mousemoved_us+500000 >= os_now_microseconds()) ? os_mouse_from_window(window) : v2f32(-100, -100); - ui_state->animation_dt = animation_dt; - MemoryZeroStruct(&ui_state->icon_info); - ui_state->icon_info.icon_font = icon_info->icon_font; - for(UI_IconKind icon_kind = UI_IconKind_Null; - icon_kind < UI_IconKind_COUNT; - icon_kind = (UI_IconKind)(icon_kind + 1)) - { - ui_state->icon_info.icon_kind_text_map[icon_kind] = push_str8_copy(ui_build_arena(), icon_info->icon_kind_text_map[icon_kind]); - } - MemoryCopyStruct(&ui_state->widget_palette_info, widget_palette_info); - MemoryCopyStruct(&ui_state->animation_info, animation_info); - } - - //- rjf: do default navigation - { - Temp scratch = scratch_begin(0, 0); - if(!ui_key_match(ui_state->default_nav_root_key, ui_key_zero())) - { - UI_Box *nav_root = ui_box_from_key(ui_state->default_nav_root_key); - if(!ui_box_is_nil(nav_root)) - { - //- rjf: no child has the active focus -> do navigation at this layer - if(ui_key_match(ui_key_zero(), nav_root->default_nav_focus_active_key)) - { - for(;;) - { - B32 moved = 0; - UI_Box *focus_box = ui_box_from_key(nav_root->default_nav_focus_next_hot_key); - UI_BoxList next_focus_box_candidates = {0}; - - // rjf: gather & consume events & nav actions - B32 nav_next = 0; - B32 nav_prev = 0; - Axis2 axis_lock = Axis2_Invalid; - if(ui_key_press(0, OS_Key_Tab)) - { - nav_next = 1; - } - if(ui_key_press(OS_EventFlag_Shift, OS_Key_Tab)) - { - nav_prev = 1; - } - for(UI_EventNode *node = events->first, *next = 0; node != 0; node = next) - { - next = node->next; - B32 taken = 0; - if(node->v.delta_2s32.x == 0 && node->v.delta_2s32.y == 0) - { - continue; - } - if(((node->v.delta_2s32.x > 0 && nav_root->flags & UI_BoxFlag_DefaultFocusNavX) || node->v.delta_2s32.x == 0) && - ((node->v.delta_2s32.y > 0 && nav_root->flags & UI_BoxFlag_DefaultFocusNavY) || node->v.delta_2s32.y == 0)) - { - taken = 1; - nav_next = 1; - } - if(((node->v.delta_2s32.x < 0 && nav_root->flags & UI_BoxFlag_DefaultFocusNavX) || node->v.delta_2s32.x == 0) && - ((node->v.delta_2s32.y < 0 && nav_root->flags & UI_BoxFlag_DefaultFocusNavY) || node->v.delta_2s32.y == 0)) - { - taken = 1; - nav_prev = 1; - } - if(node->v.flags & UI_EventFlag_ExplicitDirectional) - { - axis_lock = node->v.delta_2s32.x != 0 ? Axis2_X : Axis2_Y; - } - if(taken) - { - ui_eat_event(events, node); - } - } - - // rjf: [+] directional movement - if(nav_next) - { - UI_Box *search_start = ui_box_is_nil(focus_box) ? nav_root : focus_box; - U64 moved_in_axis[Axis2_COUNT] = {0}; - moved = 1; - for(UI_Box *box = search_start;;) - { - if(box != search_start && !(box->flags & UI_BoxFlag_FocusNavSkip) && (box->flags & UI_BoxFlag_Clickable || ui_box_is_nil(box)) && (axis_lock == Axis2_Invalid || moved_in_axis[axis_lock] > 0)) - { - ui_box_list_push(scratch.arena, &next_focus_box_candidates, box); - if(axis_lock == Axis2_Invalid || moved_in_axis[axis_lock] > 1) - { - break; - } - } - UI_Box *last_box = box; - if(!ui_box_is_nil(box->first)) - { - moved_in_axis[box->child_layout_axis] += 1; - box = box->first; - } - else for(UI_Box *p = box; !ui_box_is_nil(p) && p != nav_root; p = p->parent) - { - if(!ui_box_is_nil(p->next)) - { - moved_in_axis[p->parent->child_layout_axis] += 1; - box = p->next; - break; - } - } - if(last_box == box) - { - ui_box_list_push(scratch.arena, &next_focus_box_candidates, &ui_g_nil_box); - break; - } - } - } - - // rjf: [-] directional movement - if(nav_prev) - { - UI_Box *search_start = ui_box_is_nil(focus_box) ? nav_root : focus_box; - U64 moved_in_axis[Axis2_COUNT] = {0}; - moved = 1; - for(UI_Box *box = search_start;;) - { - if(box != search_start && !(box->flags & UI_BoxFlag_FocusNavSkip) && (box->flags & UI_BoxFlag_Clickable || ui_box_is_nil(box)) && (axis_lock == Axis2_Invalid || moved_in_axis[axis_lock] > 0)) - { - ui_box_list_push(scratch.arena, &next_focus_box_candidates, box); - if(axis_lock == Axis2_Invalid || moved_in_axis[axis_lock] > 1) - { - break; - } - } - UI_Box *last_box = box; - UI_Box *root_descendant = &ui_g_nil_box; - if(box == nav_root && box == search_start) - { - for(UI_Box *d = box->last; !ui_box_is_nil(d); d = d->last) - { - moved_in_axis[d->parent->child_layout_axis] += 1; - root_descendant = d; - } - } - UI_Box *prev_descendant = &ui_g_nil_box; - for(UI_Box *d = box->prev; !ui_box_is_nil(d); d = d->last) - { - moved_in_axis[d->parent->child_layout_axis] += 1; - prev_descendant = d; - } - if(!ui_box_is_nil(root_descendant)) - { - box = root_descendant; - } - else if(!ui_box_is_nil(prev_descendant)) - { - box = prev_descendant; - } - else if(box->parent != nav_root) - { - moved_in_axis[box->parent->child_layout_axis] += 1; - box = box->parent; - } - if(box == last_box) - { - ui_box_list_push(scratch.arena, &next_focus_box_candidates, &ui_g_nil_box); - break; - } - } - } - - // rjf: scan candidates and grab next focus box - UI_Box *next_focus_box = focus_box; - F32 best_distance_from_start = 1000000; - for(UI_BoxNode *n = next_focus_box_candidates.first; n != 0; n = n->next) - { - UI_Box *box = n->box; - F32 distance_from_start = 0; - if(axis_lock != Axis2_Invalid) - { - distance_from_start = abs_f32(center_2f32(box->rect).v[axis2_flip(axis_lock)] - center_2f32(focus_box->rect).v[axis2_flip(axis_lock)]); - } - if(distance_from_start < best_distance_from_start && box != focus_box) - { - next_focus_box = box; - best_distance_from_start = distance_from_start; - } - } - - // rjf: commit next focus box - nav_root->default_nav_focus_next_hot_key = next_focus_box->key; - - // rjf: no movement -> break - if(moved == 0) - { - break; - } - } - } - - //- rjf: some child has the active focus -> accept escape keys to pop from the active key stack - if(!ui_key_match(ui_key_zero(), nav_root->default_nav_focus_active_key)) - { - for(;ui_slot_press(UI_EventActionSlot_Cancel);) - { - UI_Box *prev_focus_root = nav_root; - for(UI_Box *focus_root = ui_box_from_key(nav_root->default_nav_focus_active_key); - !ui_box_is_nil(focus_root);) - { - UI_Box *next_focus_root = ui_box_from_key(focus_root->default_nav_focus_active_key); - if(ui_box_is_nil(next_focus_root)) - { - prev_focus_root->default_nav_focus_next_active_key = ui_key_zero(); - break; - } - else - { - prev_focus_root = focus_root; - focus_root = next_focus_root; - } - } - } - } - } - } - ui_state->default_nav_root_key = ui_key_zero(); - scratch_end(scratch); - } - - //- rjf: next-default-nav-focus keys -> current-default-nav-focus-keys - for(U64 slot_idx = 0; slot_idx < ui_state->box_table_size; slot_idx += 1) - { - for(UI_Box *box = ui_state->box_table[slot_idx].hash_first; - !ui_box_is_nil(box); - box = box->hash_next) - { - box->default_nav_focus_hot_key = box->default_nav_focus_next_hot_key; - box->default_nav_focus_active_key = box->default_nav_focus_next_active_key; - } - } - - //- rjf: build top-level root - { - Rng2F32 window_rect = os_client_rect_from_window(window); - Vec2F32 window_rect_size = dim_2f32(window_rect); - ui_set_next_fixed_width(window_rect_size.x); - ui_set_next_fixed_height(window_rect_size.y); - ui_set_next_child_layout_axis(Axis2_X); - UI_Box *root = ui_build_box_from_stringf(0, "###%I64x", window.u64[0]); - ui_push_parent(root); - ui_state->root = root; - } - - //- rjf: setup parent box for tooltip - UI_FixedX(ui_state->mouse.x+15.f) UI_FixedY(ui_state->mouse.y+15.f) UI_PrefWidth(ui_children_sum(1.f)) UI_PrefHeight(ui_children_sum(1.f)) - { - ui_set_next_child_layout_axis(Axis2_Y); - ui_state->tooltip_root = ui_build_box_from_stringf(0, "###tooltip_%I64x", window.u64[0]); - } - - //- rjf: setup parent box for context menu - ui_state->ctx_menu_open = ui_state->next_ctx_menu_open; - ui_state->ctx_menu_anchor_key = ui_state->next_ctx_menu_anchor_key; - { - UI_Box *anchor_box = ui_box_from_key(ui_state->ctx_menu_anchor_key); - if(!ui_box_is_nil(anchor_box)) - { - ui_state->ctx_menu_anchor_box_last_pos = anchor_box->rect.p0; - } - Vec2F32 anchor = add_2f32(ui_state->ctx_menu_anchor_box_last_pos, ui_state->ctx_menu_anchor_off); - UI_FixedX(anchor.x) UI_FixedY(anchor.y) UI_PrefWidth(ui_children_sum(1.f)) UI_PrefHeight(ui_children_sum(1.f)) - UI_Focus(UI_FocusKind_On) - UI_Squish(0.25f-ui_state->ctx_menu_open_t*0.25f) - UI_Transparency(1-ui_state->ctx_menu_open_t) - { - ui_set_next_child_layout_axis(Axis2_Y); - ui_state->ctx_menu_root = ui_build_box_from_stringf(UI_BoxFlag_Clickable|UI_BoxFlag_DrawDropShadow|(ui_state->ctx_menu_open*UI_BoxFlag_DefaultFocusNavY), "###ctx_menu_%I64x", window.u64[0]); - } - } - - //- rjf: reset hot if we don't have an active widget - { - B32 has_active = 0; - for(EachEnumVal(UI_MouseButtonKind, k)) - { - if(!ui_key_match(ui_state->active_box_key[k], ui_key_zero())) - { - has_active = 1; - } - } - if(!has_active) - { - ui_state->hot_box_key = ui_key_zero(); - } - } - - //- rjf: reset drop-hot key - { - ui_state->drop_hot_box_key = ui_key_zero(); - } - - //- rjf: reset active if our active box is disabled - for(EachEnumVal(UI_MouseButtonKind, k)) - { - if(!ui_key_match(ui_state->active_box_key[k], ui_key_zero())) - { - UI_Box *box = ui_box_from_key(ui_state->active_box_key[k]); - if(!ui_box_is_nil(box) && box->flags & UI_BoxFlag_Disabled) - { - ui_state->active_box_key[k] = ui_key_zero(); - } - } - } - - //- rjf: reset active keys if they have been pruned - for(EachEnumVal(UI_MouseButtonKind, k)) - { - UI_Box *box = ui_box_from_key(ui_state->active_box_key[k]); - if(ui_box_is_nil(box)) - { - ui_state->active_box_key[k] = ui_key_zero(); - } - } -} - -internal void -ui_end_build(void) -{ - ProfBeginFunction(); - - //- rjf: escape -> close context menu - if(ui_state->ctx_menu_open != 0 && ui_slot_press(UI_EventActionSlot_Cancel)) - { - ui_ctx_menu_close(); - } - - //- rjf: prune untouched or transient widgets in the cache - { - ProfBegin("ui prune unused widgets"); - for(U64 slot_idx = 0; slot_idx < ui_state->box_table_size; slot_idx += 1) - { - for(UI_Box *box = ui_state->box_table[slot_idx].hash_first, *next = 0; - !ui_box_is_nil(box); - box = next) - { - next = box->hash_next; - if(box->last_touched_build_index < ui_state->build_index || - ui_key_match(box->key, ui_key_zero())) - { - DLLRemove_NPZ(&ui_g_nil_box, ui_state->box_table[slot_idx].hash_first, ui_state->box_table[slot_idx].hash_last, box, hash_next, hash_prev); - SLLStackPush(ui_state->first_free_box, box); - } - } - } - ProfEnd(); - } - - //- rjf: layout box tree - { - ProfBegin("ui box tree layout"); - for(Axis2 axis = (Axis2)0; axis < Axis2_COUNT; axis = (Axis2)(axis + 1)) - { - ui_layout_root(ui_state->root, axis); - } - ProfEnd(); - } - - //- rjf: close ctx menu if untouched - if(!ui_state->ctx_menu_touched_this_frame) - { - ui_ctx_menu_close(); - } - - //- rjf: stick ctx menu to anchor - if(ui_state->ctx_menu_touched_this_frame && !ui_state->ctx_menu_changed) - { - UI_Box *anchor_box = ui_box_from_key(ui_state->ctx_menu_anchor_key); - if(!ui_box_is_nil(anchor_box)) - { - Rng2F32 root_rect = ui_state->ctx_menu_root->rect; - Vec2F32 pos = - { - anchor_box->rect.x0 + ui_state->ctx_menu_anchor_off.x, - anchor_box->rect.y0 + ui_state->ctx_menu_anchor_off.y, - }; - Vec2F32 shift = sub_2f32(pos, root_rect.p0); - Rng2F32 new_root_rect = shift_2f32(root_rect, shift); - ui_state->ctx_menu_root->fixed_position = new_root_rect.p0; - ui_state->ctx_menu_root->fixed_size = dim_2f32(new_root_rect); - ui_state->ctx_menu_root->rect = new_root_rect; - } - } - - //- rjf: ensure special floating roots are within screen bounds - UI_Box *floating_roots[] = {ui_state->tooltip_root, ui_state->ctx_menu_root}; - B32 force_contain[] = - { - (ui_key_match(ui_active_key(UI_MouseButtonKind_Left), ui_key_zero()) && - ui_key_match(ui_active_key(UI_MouseButtonKind_Right), ui_key_zero()) && - ui_key_match(ui_active_key(UI_MouseButtonKind_Middle), ui_key_zero())), - 1, - }; - for(U64 idx = 0; idx < ArrayCount(floating_roots); idx += 1) - { - UI_Box *root = floating_roots[idx]; - if(!ui_box_is_nil(root)) - { - Rng2F32 window_rect = os_client_rect_from_window(ui_window()); - Vec2F32 window_dim = dim_2f32(window_rect); - Rng2F32 root_rect = root->rect; - Vec2F32 shift = - { - -ClampBot(0, root_rect.x1 - window_rect.x1) * (force_contain[idx]), - -ClampBot(0, root_rect.y1 - window_rect.y1) * (force_contain[idx]), - }; - Rng2F32 new_root_rect = shift_2f32(root_rect, shift); - root->fixed_position = new_root_rect.p0; - root->fixed_size = dim_2f32(new_root_rect); - root->rect = new_root_rect; - for(Axis2 axis = (Axis2)0; axis < Axis2_COUNT; axis = (Axis2)(axis + 1)) - { - ui_calc_sizes_standalone__in_place_rec(root, axis); - ui_calc_sizes_upwards_dependent__in_place_rec(root, axis); - ui_calc_sizes_downwards_dependent__in_place_rec(root, axis); - ui_layout_enforce_constraints__in_place_rec(root, axis); - ui_layout_position__in_place_rec(root, axis); - } - } - } - - //- rjf: enforce child-rounding - { - for(U64 slot_idx = 0; slot_idx < ui_state->box_table_size; slot_idx += 1) - { - for(UI_Box *box = ui_state->box_table[slot_idx].hash_first; - !ui_box_is_nil(box); - box = box->hash_next) - { - if(box->flags & UI_BoxFlag_RoundChildrenByParent && - !ui_box_is_nil(box->first) && !ui_box_is_nil(box->last)) - { - box->first->corner_radii[Corner_00] = box->corner_radii[Corner_00]; - box->first->corner_radii[Corner_10] = box->corner_radii[Corner_10]; - box->last->corner_radii[Corner_01] = box->corner_radii[Corner_01]; - box->last->corner_radii[Corner_11] = box->corner_radii[Corner_11]; - } - } - } - } - - //- rjf: animate - { - ProfBegin("ui animate"); - F32 vast_rate = 1 - pow_f32(2, (-60.f * ui_state->animation_dt)); - F32 fast_rate = 1 - pow_f32(2, (-50.f * ui_state->animation_dt)); - F32 fish_rate = 1 - pow_f32(2, (-40.f * ui_state->animation_dt)); - F32 slow_rate = 1 - pow_f32(2, (-30.f * ui_state->animation_dt)); - F32 slug_rate = 1 - pow_f32(2, (-15.f * ui_state->animation_dt)); - F32 slaf_rate = 1 - pow_f32(2, (-8.f * ui_state->animation_dt)); - ui_state->ctx_menu_open_t += ((F32)!!ui_state->ctx_menu_open - ui_state->ctx_menu_open_t) * (ui_state->animation_info.flags & UI_AnimationInfoFlag_ContextMenuAnimations ? vast_rate : 1); - ui_state->is_animating = (ui_state->is_animating || abs_f32((F32)!!ui_state->ctx_menu_open - ui_state->ctx_menu_open_t) > 0.01f); - if(ui_state->ctx_menu_open_t >= 0.99f && ui_state->ctx_menu_open) - { - ui_state->ctx_menu_open_t = 1.f; - } - ui_state->tooltip_open_t += ((F32)!!ui_state->tooltip_open - ui_state->tooltip_open_t) * (ui_state->animation_info.flags & UI_AnimationInfoFlag_TooltipAnimations ? vast_rate : 1); - ui_state->is_animating = (ui_state->is_animating || abs_f32((F32)!!ui_state->tooltip_open - ui_state->tooltip_open_t) > 0.01f); - if(ui_state->tooltip_open_t >= 0.99f && ui_state->tooltip_open) - { - ui_state->tooltip_open_t = 1.f; - } - for(U64 slot_idx = 0; slot_idx < ui_state->box_table_size; slot_idx += 1) - { - for(UI_Box *box = ui_state->box_table[slot_idx].hash_first; - !ui_box_is_nil(box); - box = box->hash_next) - { - // rjf: grab states informing animation - B32 is_hot = ui_key_match(box->key, ui_state->hot_box_key); - B32 is_active = ui_key_match(box->key, ui_state->active_box_key[UI_MouseButtonKind_Left]); - B32 is_disabled = !!(box->flags & UI_BoxFlag_Disabled) && (box->first_disabled_build_index+10 < ui_state->build_index || - box->first_touched_build_index == box->first_disabled_build_index); - B32 is_focus_hot = !!(box->flags & UI_BoxFlag_FocusHot) && !(box->flags & UI_BoxFlag_FocusHotDisabled); - B32 is_focus_active = !!(box->flags & UI_BoxFlag_FocusActive) && !(box->flags & UI_BoxFlag_FocusActiveDisabled); - B32 is_focus_active_disabled = !!(box->flags & UI_BoxFlag_FocusActiveDisabled); - - // rjf: determine rates - F32 hot_rate = (ui_state->animation_info.flags & UI_AnimationInfoFlag_HotAnimations ? fast_rate : 1); - F32 active_rate = (ui_state->animation_info.flags & UI_AnimationInfoFlag_ActiveAnimations ? fast_rate : 1); - F32 disabled_rate = (ui_state->animation_info.flags & UI_AnimationInfoFlag_HotAnimations ? slow_rate : 1); - F32 focus_rate = (ui_state->animation_info.flags & UI_AnimationInfoFlag_FocusAnimations ? fast_rate : 1); - - // rjf: determine animating status - B32 box_is_animating = 0; - box_is_animating = (box_is_animating || abs_f32((F32)is_hot - box->hot_t) > 0.01f); - box_is_animating = (box_is_animating || abs_f32((F32)is_active - box->active_t) > 0.01f); - box_is_animating = (box_is_animating || abs_f32((F32)is_disabled - box->disabled_t) > 0.01f); - box_is_animating = (box_is_animating || abs_f32((F32)is_focus_hot - box->focus_hot_t) > 0.01f); - box_is_animating = (box_is_animating || abs_f32((F32)is_focus_active - box->focus_active_t) > 0.01f); - box_is_animating = (box_is_animating || abs_f32((F32)is_focus_active_disabled - box->focus_active_disabled_t) > 0.01f); - box_is_animating = (box_is_animating || abs_f32(box->view_off_target.x - box->view_off.x) > 0.5f); - box_is_animating = (box_is_animating || abs_f32(box->view_off_target.y - box->view_off.y) > 0.5f); - if(box->flags & UI_BoxFlag_AnimatePosX) - { - box_is_animating = (box_is_animating || abs_f32(box->fixed_position_animated.x - box->fixed_position.x) > 0.5f); - } - if(box->flags & UI_BoxFlag_AnimatePosY) - { - box_is_animating = (box_is_animating || abs_f32(box->fixed_position_animated.y - box->fixed_position.y) > 0.5f); - } - ui_state->is_animating = (ui_state->is_animating || box_is_animating); -#if 0 // NOTE(rjf): enable to debug animation-causing-frames (or not) - if(box_is_animating) - { - box->overlay_color = v4f32(1, 0, 0, 0.1f); - box->flags |= UI_BoxFlag_DrawOverlay; - } -#endif - - // rjf: animate interaction transition states - box->hot_t += hot_rate * ((F32)is_hot - box->hot_t); - box->active_t += active_rate * ((F32)is_active - box->active_t); - box->disabled_t += disabled_rate * ((F32)is_disabled - box->disabled_t); - box->focus_hot_t += focus_rate * ((F32)is_focus_hot - box->focus_hot_t); - box->focus_active_t += focus_rate * ((F32)is_focus_active - box->focus_active_t); - box->focus_active_disabled_t += focus_rate * ((F32)is_focus_active_disabled - box->focus_active_disabled_t); - - // rjf: animate positions - { - box->fixed_position_animated.x += fast_rate * (box->fixed_position.x - box->fixed_position_animated.x); - box->fixed_position_animated.y += fast_rate * (box->fixed_position.y - box->fixed_position_animated.y); - if(abs_f32(box->fixed_position.x - box->fixed_position_animated.x) < 1) - { - box->fixed_position_animated.x = box->fixed_position.x; - } - if(abs_f32(box->fixed_position.y - box->fixed_position_animated.y) < 1) - { - box->fixed_position_animated.y = box->fixed_position.y; - } - } - - // rjf: clamp view - if(box->flags & UI_BoxFlag_ViewClamp) - { - Vec2F32 max_view_off_target = - { - ClampBot(0, box->view_bounds.x - box->fixed_size.x), - ClampBot(0, box->view_bounds.y - box->fixed_size.y), - }; - if(box->flags & UI_BoxFlag_ViewClampX) { box->view_off_target.x = Clamp(0, box->view_off_target.x, max_view_off_target.x); } - if(box->flags & UI_BoxFlag_ViewClampY) { box->view_off_target.y = Clamp(0, box->view_off_target.y, max_view_off_target.y); } - } - - // rjf: animate view offset - { - box->view_off.x += fast_rate * (box->view_off_target.x - box->view_off.x); - box->view_off.y += fast_rate * (box->view_off_target.y - box->view_off.y); - if(abs_f32(box->view_off.x - box->view_off_target.x) < 2) - { - box->view_off.x = box->view_off_target.x; - } - if(abs_f32(box->view_off.y - box->view_off_target.y) < 2) - { - box->view_off.y = box->view_off_target.y; - } - } - } - } - ProfEnd(); - } - - //- rjf: animate context menu - if(ui_state->ctx_menu_open && !ui_box_is_nil(ui_state->ctx_menu_root) && !ui_state->ctx_menu_changed) - { - UI_Box *root = ui_state->ctx_menu_root; - Rng2F32 rect = root->rect; - root->rect.y1 = root->rect.y0 + dim_2f32(rect).y * ui_state->ctx_menu_open_t; - } - - //- rjf: fall-through interact with context menu - if(ui_state->ctx_menu_open) - { - ui_signal_from_box(ui_state->ctx_menu_root); - } - - //- rjf: close ctx menu if unconsumed clicks - { - UI_EventList *events = ui_events(); - for(UI_EventNode *n = events->first; n != 0; n = n->next) - { - UI_Event *event = &n->v; - if(event->kind == UI_EventKind_Press && - (event->key == OS_Key_LeftMouseButton || event->key == OS_Key_RightMouseButton)) - { - ui_ctx_menu_close(); - } - } - } - - //- rjf: hover cursor - { - UI_Box *hot = ui_box_from_key(ui_state->hot_box_key); - UI_Box *active = ui_box_from_key(ui_state->active_box_key[UI_MouseButtonKind_Left]); - UI_Box *box = ui_box_is_nil(active) ? hot : active; - OS_Cursor cursor = box->hover_cursor; - if(box->flags & UI_BoxFlag_Disabled && box->flags & UI_BoxFlag_Clickable) - { - cursor = OS_Cursor_Disabled; - } - if(os_window_is_focused(ui_state->window) || !ui_box_is_nil(active)) - { - os_set_cursor(cursor); - } - } - - //- rjf: clipboard commits - { - UI_Box *box = ui_box_from_key(ui_state->clipboard_copy_key); - if(!ui_box_is_nil(box)) - { - Temp scratch = scratch_begin(0, 0); - String8List strs = {0}; - UI_BoxRec rec = {0}; - for(UI_Box *b = box; !ui_box_is_nil(b); rec = ui_box_rec_df_pre(b, box), b = rec.next) - { - if(b->flags & UI_BoxFlag_DrawText && b->flags & UI_BoxFlag_HasDisplayString && !f_tag_match(b->font, ui_icon_font())) - { - String8 display_string = ui_box_display_string(b); - str8_list_push(scratch.arena, &strs, display_string); - } - } - if(strs.node_count != 0) - { - StringJoin join = {0}; - join.sep = str8_lit(" "); - String8 string = str8_list_join(scratch.arena, &strs, &join); - os_set_clipboard_text(string); - } - scratch_end(scratch); - } - } - - //- rjf: hovering possibly-truncated drawn text -> store text - { - B32 inactive = 1; - for(EachEnumVal(UI_MouseButtonKind, k)) - { - if(!ui_key_match(ui_key_zero(), ui_state->active_box_key[k])) - { - inactive = 0; - break; - } - } - if(inactive) - { - B32 found = 0; - for(UI_Box *box = ui_state->root, *next = 0; !ui_box_is_nil(box); box = next) - { - UI_BoxRec rec = ui_box_rec_df_pre(box, ui_state->root); - next = rec.next; - S32 pop_idx = 0; - for(UI_Box *b = box; !ui_box_is_nil(b) && pop_idx <= rec.pop_count; b = b->parent, pop_idx += 1) - { - if(b->flags & UI_BoxFlag_DrawText && !(b->flags & UI_BoxFlag_DisableTextTrunc)) - { - Rng2F32 rect = b->rect; - for(UI_Box *p = b->parent; !ui_box_is_nil(p); p = p->parent) - { - if(p->flags & UI_BoxFlag_Clip) - { - rect = intersect_2f32(rect, p->rect); - } - } - String8 box_display_string = ui_box_display_string(b); - Vec2F32 text_pos = ui_box_text_position(b); - Vec2F32 drawn_text_dim = b->display_string_runs.dim; - B32 text_is_truncated = (drawn_text_dim.x + text_pos.x > rect.x1); - B32 mouse_is_hovering = contains_2f32(r2f32p(text_pos.x, - rect.y0, - Min(text_pos.x+drawn_text_dim.x, rect.x1), - rect.y1), - ui_state->mouse); - if(text_is_truncated && mouse_is_hovering && !(b->flags & UI_BoxFlag_DisableTruncatedHover)) - { - if(!str8_match(box_display_string, ui_state->string_hover_string, 0)) - { - arena_clear(ui_state->string_hover_arena); - ui_state->string_hover_string = push_str8_copy(ui_state->string_hover_arena, box_display_string); - ui_state->string_hover_fancy_runs = d_fancy_run_list_copy(ui_state->string_hover_arena, &b->display_string_runs); - ui_state->string_hover_begin_us = os_now_microseconds(); - } - ui_state->string_hover_build_index = ui_state->build_index; - found = 1; - goto break_all_hover_string; - } - } - if(b != box && ui_key_match(b->key, ui_hot_key())) - { - goto break_all_hover_string; - } - if(b != box && contains_2f32(b->rect, ui_state->mouse) && b->flags & UI_BoxFlag_DrawText) - { - goto break_all_hover_string; - } - } - } - break_all_hover_string:; - if(!found) - { - arena_clear(ui_state->string_hover_arena); - ui_state->string_hover_build_index = 0; - MemoryZeroStruct(&ui_state->string_hover_string); - } - if(found && !ui_string_hover_active()) - { - ui_state->is_animating = 1; - } - } - } - - ui_state->build_index += 1; - arena_clear(ui_build_arena()); - ProfEnd(); -} - -internal void -ui_calc_sizes_standalone__in_place_rec(UI_Box *root, Axis2 axis) -{ - ProfBeginFunction(); - - switch(root->pref_size[axis].kind) - { - default:{}break; - case UI_SizeKind_Pixels: - { - root->fixed_size.v[axis] = root->pref_size[axis].value; - }break; - - case UI_SizeKind_TextContent: - { - F32 padding = root->pref_size[axis].value; - F32 text_size = root->display_string_runs.dim.x; - root->fixed_size.v[axis] = padding + text_size + root->text_padding*2; - }break; - } - - //- rjf: recurse - for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) - { - ui_calc_sizes_standalone__in_place_rec(child, axis); - } - - ProfEnd(); -} - -internal void -ui_calc_sizes_upwards_dependent__in_place_rec(UI_Box *root, Axis2 axis) -{ - ProfBeginFunction(); - - //- rjf: solve for all kinds that are upwards-dependent - switch(root->pref_size[axis].kind) - { - default: break; - - // rjf: if root has a parent percentage, figure out its size - case UI_SizeKind_ParentPct: - { - // rjf: find parent that has a fixed size - UI_Box *fixed_parent = &ui_g_nil_box; - for(UI_Box *p = root->parent; !ui_box_is_nil(p); p = p->parent) - { - if(p->flags & (UI_BoxFlag_FixedWidth<pref_size[axis].kind == UI_SizeKind_Pixels || - p->pref_size[axis].kind == UI_SizeKind_TextContent || - p->pref_size[axis].kind == UI_SizeKind_ParentPct) - { - fixed_parent = p; - break; - } - } - - // rjf: figure out root's size on this axis - F32 size = fixed_parent->fixed_size.v[axis] * root->pref_size[axis].value; - - // rjf: mutate root to have this size - root->fixed_size.v[axis] = size; - }break; - } - - //- rjf: recurse - for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) - { - ui_calc_sizes_upwards_dependent__in_place_rec(child, axis); - } - - ProfEnd(); -} - -internal void -ui_calc_sizes_downwards_dependent__in_place_rec(UI_Box *root, Axis2 axis) -{ - ProfBeginFunction(); - - //- rjf: recurse first. we may depend on children that have - // the same property - for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) - { - ui_calc_sizes_downwards_dependent__in_place_rec(child, axis); - } - - //- rjf: solve for all kinds that are downwards-dependent - switch(root->pref_size[axis].kind) - { - default: break; - - // rjf: sum children - case UI_SizeKind_ChildrenSum: - { - F32 sum = 0; - for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) - { - if(!(child->flags & (UI_BoxFlag_FloatingX<child_layout_axis) - { - sum += child->fixed_size.v[axis]; - } - else - { - sum = Max(sum, child->fixed_size.v[axis]); - } - } - } - - // rjf: figure out root's size on this axis - root->fixed_size.v[axis] = sum; - }break; - } - - ProfEnd(); -} - -internal void -ui_layout_enforce_constraints__in_place_rec(UI_Box *root, Axis2 axis) -{ - ProfBeginFunction(); - Temp scratch = scratch_begin(0, 0); - - // NOTE(rjf): The "layout axis" is the direction in which children - // of some node are intended to be laid out. - - //- rjf: fixup children sizes (if we're solving along the *non-layout* axis) - if(axis != root->child_layout_axis && !(root->flags & (UI_BoxFlag_AllowOverflowX << axis))) - { - F32 allowed_size = root->fixed_size.v[axis]; - for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) - { - if(!(child->flags & (UI_BoxFlag_FloatingX<fixed_size.v[axis]; - F32 violation = child_size - allowed_size; - F32 max_fixup = child_size; - F32 fixup = Clamp(0, violation, max_fixup); - if(fixup > 0) - { - child->fixed_size.v[axis] -= fixup; - } - } - } - - } - - //- rjf: fixup children sizes (in the direction of the layout axis) - if(axis == root->child_layout_axis && !(root->flags & (UI_BoxFlag_AllowOverflowX << axis))) - { - // rjf: figure out total allowed size & total size - F32 total_allowed_size = root->fixed_size.v[axis]; - F32 total_size = 0; - F32 total_weighted_size = 0; - for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) - { - if(!(child->flags & (UI_BoxFlag_FloatingX<fixed_size.v[axis]; - total_weighted_size += child->fixed_size.v[axis] * (1-child->pref_size[axis].strictness); - } - } - - // rjf: if we have a violation, we need to subtract some amount from all children - F32 violation = total_size - total_allowed_size; - if(violation > 0) - { - // rjf: figure out how much we can take in totality - F32 child_fixup_sum = 0; - F32 *child_fixups = push_array(scratch.arena, F32, root->child_count); - { - U64 child_idx = 0; - for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next, child_idx += 1) - { - if(!(child->flags & (UI_BoxFlag_FloatingX<fixed_size.v[axis] * (1-child->pref_size[axis].strictness); - fixup_size_this_child = ClampBot(0, fixup_size_this_child); - child_fixups[child_idx] = fixup_size_this_child; - child_fixup_sum += fixup_size_this_child; - } - } - } - - // rjf: fixup child sizes - { - U64 child_idx = 0; - for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next, child_idx += 1) - { - if(!(child->flags & (UI_BoxFlag_FloatingX<fixed_size.v[axis] -= child_fixups[child_idx] * fixup_pct; - } - } - } - } - - } - - //- rjf: fixup upwards-relative sizes - if(root->flags & (UI_BoxFlag_AllowOverflowX << axis)) - { - for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) - { - if(child->pref_size[axis].kind == UI_SizeKind_ParentPct) - { - child->fixed_size.v[axis] = root->fixed_size.v[axis] * child->pref_size[axis].value; - } - } - } - - //- rjf: recurse - for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) - { - ui_layout_enforce_constraints__in_place_rec(child, axis); - } - - scratch_end(scratch); - ProfEnd(); -} - -internal void -ui_layout_position__in_place_rec(UI_Box *root, Axis2 axis) -{ - ProfBeginFunction(); - F32 layout_position = 0; - - //- rjf: lay out children - F32 bounds = 0; - for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) - { - // rjf: grab original position - F32 original_position = Min(child->rect.p0.v[axis], child->rect.p1.v[axis]); - - // rjf: calculate fixed position & size - if(!(child->flags & (UI_BoxFlag_FloatingX<fixed_position.v[axis] = layout_position; - if(root->child_layout_axis == axis) - { - layout_position += child->fixed_size.v[axis]; - bounds += child->fixed_size.v[axis]; - } - else - { - bounds = Max(bounds, child->fixed_size.v[axis]); - } - } - - // rjf: determine final rect for child, given fixed_position & size - if(child->flags & (UI_BoxFlag_AnimatePosX<first_touched_build_index == child->last_touched_build_index) - { - child->fixed_position_animated = child->fixed_position; - } - child->rect.p0.v[axis] = root->rect.p0.v[axis] + child->fixed_position_animated.v[axis] - !(child->flags&(UI_BoxFlag_SkipViewOffX<view_off.v[axis]); - } - else - { - child->rect.p0.v[axis] = root->rect.p0.v[axis] + child->fixed_position.v[axis] - !(child->flags&(UI_BoxFlag_SkipViewOffX<view_off.v[axis]); - } - child->rect.p1.v[axis] = child->rect.p0.v[axis] + child->fixed_size.v[axis]; - child->rect.p0.x = floor_f32(child->rect.p0.x); - child->rect.p0.y = floor_f32(child->rect.p0.y); - child->rect.p1.x = floor_f32(child->rect.p1.x); - child->rect.p1.y = floor_f32(child->rect.p1.y); - - // rjf: grab new position - F32 new_position = Min(child->rect.p0.v[axis], child->rect.p1.v[axis]); - - // rjf: store position delta - child->position_delta.v[axis] = new_position - original_position; - } - - //- rjf: store view bounds - { - root->view_bounds.v[axis] = bounds; - } - - //- rjf: recurse - for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) - { - ui_layout_position__in_place_rec(child, axis); - } - - ProfEnd(); -} - -internal void -ui_layout_root(UI_Box *root, Axis2 axis) -{ - ProfBegin("ui layout pass (%s)", axis == Axis2_X ? "x" : "y"); - ui_calc_sizes_standalone__in_place_rec(root, axis); - ui_calc_sizes_upwards_dependent__in_place_rec(root, axis); - ui_calc_sizes_downwards_dependent__in_place_rec(root, axis); - ui_layout_enforce_constraints__in_place_rec(root, axis); - ui_layout_position__in_place_rec(root, axis); - ProfEnd(); -} - -//////////////////////////////// -//~ rjf: Box Building API - -//- rjf: spacers - -internal UI_Signal -ui_spacer(UI_Size size) -{ - UI_Box *parent = ui_top_parent(); - ui_set_next_pref_size(parent->child_layout_axis, size); - UI_Box *box = ui_build_box_from_key(0, ui_key_zero()); - UI_Signal interact = ui_signal_from_box(box); - return interact; -} - -//- rjf: tooltips - -internal void -ui_tooltip_begin_base(void) -{ - ui_state->tooltip_open = 1; - ui_push_parent(ui_root_from_state(ui_state)); - ui_push_parent(ui_state->tooltip_root); - ui_push_flags(0); - ui_push_text_raster_flags(ui_bottom_text_raster_flags()); - ui_push_palette(ui_bottom_palette()); -} - -internal void -ui_tooltip_end_base(void) -{ - ui_pop_palette(); - ui_pop_text_raster_flags(); - ui_pop_flags(); - ui_pop_parent(); - ui_pop_parent(); -} - -internal void -ui_tooltip_begin(void) -{ - ui_tooltip_begin_base(); - ui_push_palette(ui_state->widget_palette_info.tooltip_palette); - ui_set_next_squish(0.25f-ui_state->tooltip_open_t*0.25f); - ui_set_next_transparency(1-ui_state->tooltip_open_t); - UI_Flags(UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBackgroundBlur|UI_BoxFlag_DrawDropShadow) - UI_PrefWidth(ui_children_sum(1)) - UI_PrefHeight(ui_children_sum(1)) - UI_CornerRadius(ui_top_font_size()*0.25f) - ui_column_begin(); - UI_PrefWidth(ui_px(0, 1)) ui_spacer(ui_em(1.f, 1.f)); - UI_PrefWidth(ui_children_sum(1)) - UI_PrefHeight(ui_children_sum(1)) - ui_row_begin(); - UI_PrefHeight(ui_px(0, 1)) ui_spacer(ui_em(1.f, 1.f)); - UI_PrefWidth(ui_children_sum(1)) - UI_PrefHeight(ui_children_sum(1)) - ui_column_begin(); - ui_push_pref_width(ui_text_dim(10.f, 1.f)); - ui_push_pref_height(ui_em(2.f, 1.f)); - ui_push_text_alignment(UI_TextAlign_Center); -} - -internal void -ui_tooltip_end(void) -{ - ui_pop_text_alignment(); - ui_pop_pref_width(); - ui_pop_pref_height(); - ui_column_end(); - UI_PrefHeight(ui_px(0, 1)) ui_spacer(ui_em(1.f, 1.f)); - ui_row_end(); - UI_PrefWidth(ui_px(0, 1)) ui_spacer(ui_em(1.f, 1.f)); - ui_column_end(); - ui_pop_palette(); - ui_tooltip_end_base(); -} - -//- rjf: context menus - -internal void -ui_ctx_menu_open(UI_Key key, UI_Key anchor_box_key, Vec2F32 anchor_off) -{ - anchor_off.x = (F32)(int)anchor_off.x; - anchor_off.y = (F32)(int)anchor_off.y; - ui_state->next_ctx_menu_open = 1; - ui_state->ctx_menu_changed = 1; - ui_state->ctx_menu_open_t = 0; - ui_state->ctx_menu_key = key; - ui_state->next_ctx_menu_anchor_key = anchor_box_key; - ui_state->ctx_menu_anchor_off = anchor_off; - ui_state->ctx_menu_touched_this_frame = 1; - ui_state->ctx_menu_anchor_box_last_pos = v2f32(0, 0); - ui_state->ctx_menu_root->default_nav_focus_active_key = ui_key_zero(); - ui_state->ctx_menu_root->default_nav_focus_next_active_key = ui_key_zero(); -} - -internal void -ui_ctx_menu_close(void) -{ - ui_state->next_ctx_menu_open = 0; -} - -internal B32 -ui_begin_ctx_menu(UI_Key key) -{ - ui_push_parent(ui_root_from_state(ui_state)); - ui_push_parent(ui_state->ctx_menu_root); - ui_push_pref_width(ui_bottom_pref_width()); - ui_push_pref_height(ui_bottom_pref_height()); - ui_push_focus_hot(UI_FocusKind_Root); - ui_push_focus_active(UI_FocusKind_Root); - ui_push_palette(ui_state->widget_palette_info.ctx_menu_palette); - B32 is_open = ui_key_match(key, ui_state->ctx_menu_key) && ui_state->ctx_menu_open; - if(is_open != 0) - { - ui_state->ctx_menu_touched_this_frame = 1; - ui_state->ctx_menu_root->flags |= UI_BoxFlag_RoundChildrenByParent; - ui_state->ctx_menu_root->flags |= UI_BoxFlag_DrawBackgroundBlur; - ui_state->ctx_menu_root->flags |= UI_BoxFlag_DrawBackground; - ui_state->ctx_menu_root->flags |= UI_BoxFlag_DisableFocusOverlay; - ui_state->ctx_menu_root->flags |= UI_BoxFlag_DrawBorder; - ui_state->ctx_menu_root->flags |= UI_BoxFlag_Clip; - ui_state->ctx_menu_root->flags |= UI_BoxFlag_Clickable; - ui_state->ctx_menu_root->corner_radii[Corner_00] = ui_state->ctx_menu_root->corner_radii[Corner_01] = ui_state->ctx_menu_root->corner_radii[Corner_10] = ui_state->ctx_menu_root->corner_radii[Corner_11] = ui_top_font_size()*0.25f; - ui_state->ctx_menu_root->palette = ui_top_palette(); - ui_state->ctx_menu_root->blur_size = ui_top_blur_size(); - ui_spacer(ui_em(1.f, 1.f)); - } - ui_state->is_in_open_ctx_menu = is_open; - return is_open; -} - -internal void -ui_end_ctx_menu(void) -{ - if(ui_state->is_in_open_ctx_menu) - { - ui_state->is_in_open_ctx_menu = 0; - ui_spacer(ui_em(1.f, 1.f)); - } - ui_pop_palette(); - ui_pop_focus_active(); - ui_pop_focus_hot(); - ui_pop_pref_width(); - ui_pop_pref_height(); - ui_pop_parent(); - ui_pop_parent(); -} - -internal B32 -ui_ctx_menu_is_open(UI_Key key) -{ - return (ui_state->ctx_menu_open && ui_key_match(key, ui_state->ctx_menu_key)); -} - -internal B32 -ui_any_ctx_menu_is_open(void) -{ - return ui_state->ctx_menu_open; -} - -//- rjf: focus tree coloring - -internal B32 -ui_is_focus_hot(void) -{ - B32 result = (ui_state->focus_hot_stack.top->v == UI_FocusKind_On); - if(result) - { - for(UI_FocusHotNode *n = ui_state->focus_hot_stack.top; n != 0; n = n->next) - { - if(n->v == UI_FocusKind_Root) - { - break; - } - if(n->v == UI_FocusKind_Off) - { - result = 0; - break; - } - } - } - return result; -} - -internal B32 -ui_is_focus_active(void) -{ - B32 result = (ui_state->focus_active_stack.top->v == UI_FocusKind_On); - if(result) - { - for(UI_FocusActiveNode *n = ui_state->focus_active_stack.top; n != 0; n = n->next) - { - if(n->v == UI_FocusKind_Root) - { - break; - } - if(n->v == UI_FocusKind_Off) - { - result = 0; - break; - } - } - } - return result; -} - -//- rjf: implicit auto-managed tree-based focus state - -internal B32 -ui_is_key_auto_focus_active(UI_Key key) -{ - B32 result = 0; - if(!ui_key_match(ui_key_zero(), key)) - { - for(UI_Box *p = ui_top_parent(); !ui_box_is_nil(p); p = p->parent) - { - if(p->flags & UI_BoxFlag_FocusActive && ui_key_match(key, p->default_nav_focus_active_key)) - { - result = 1; - break; - } - } - } - return result; -} - -internal B32 -ui_is_key_auto_focus_hot(UI_Key key) -{ - B32 result = 0; - if(!ui_key_match(ui_key_zero(), key)) - { - for(UI_Box *p = ui_top_parent(); !ui_box_is_nil(p); p = p->parent) - { - if(p->flags & UI_BoxFlag_FocusHot && - ((!(p->flags & UI_BoxFlag_FocusHotDisabled) && - ui_key_match(key, p->default_nav_focus_hot_key)) || - ui_key_match(key, p->default_nav_focus_active_key))) - { - result = 1; - break; - } - } - } - return result; -} - -internal void -ui_set_auto_focus_active_key(UI_Key key) -{ - for(UI_Box *p = ui_top_parent(); !ui_box_is_nil(p); p = p->parent) - { - if(p->flags & UI_BoxFlag_DefaultFocusNav) - { - p->default_nav_focus_next_active_key = key; - break; - } - } -} - -internal void -ui_set_auto_focus_hot_key(UI_Key key) -{ - for(UI_Box *p = ui_top_parent(); !ui_box_is_nil(p); p = p->parent) - { - if(p->flags & UI_BoxFlag_DefaultFocusNav) - { - p->default_nav_focus_next_hot_key = key; - break; - } - } -} - -//- rjf: palette forming - -internal UI_Palette * -ui_build_palette_(UI_Palette *base, UI_Palette *overrides) -{ - UI_Palette *palette = push_array(ui_build_arena(), UI_Palette, 1); - if(base != 0) - { - MemoryCopyStruct(palette, base); - } - for(EachEnumVal(UI_ColorCode, code)) - { - if(overrides->colors[code].x != 0 || - overrides->colors[code].y != 0 || - overrides->colors[code].z != 0 || - overrides->colors[code].w != 0) - { - palette->colors[code] = overrides->colors[code]; - } - } - return palette; -} - -//- rjf: box node construction - -internal UI_Box * -ui_build_box_from_key(UI_BoxFlags flags, UI_Key key) -{ - ProfBeginFunction(); - ui_state->build_box_count += 1; - - //- rjf: grab active parent - UI_Box *parent = ui_top_parent(); - - //- rjf: try to get box - UI_BoxFlags last_flags = 0; - UI_Box *box = ui_box_from_key(key); - B32 box_first_frame = ui_box_is_nil(box); - last_flags = box->flags; - - //- rjf: zero key on duplicate - if(!box_first_frame && box->last_touched_build_index == ui_state->build_index) - { - box = &ui_g_nil_box; - key = ui_key_zero(); - box_first_frame = 1; - } - - //- rjf: gather info from box - B32 box_is_transient = ui_key_match(key, ui_key_zero()); - - //- rjf: allocate box if it doesn't yet exist - if(box_first_frame) - { - box = !box_is_transient ? ui_state->first_free_box : 0; - ui_state->is_animating = ui_state->is_animating || !box_is_transient; - if(!ui_box_is_nil(box)) - { - SLLStackPop(ui_state->first_free_box); - } - else - { - box = push_array_no_zero(box_is_transient ? ui_build_arena() : ui_state->arena, UI_Box, 1); - } - MemoryZeroStruct(box); - } - - //- rjf: zero out per-frame state - { - box->first = box->last = box->next = box->prev = box->parent = &ui_g_nil_box; - box->child_count = 0; - box->flags = 0; - box->hover_cursor = OS_Cursor_Pointer; - MemoryZeroArray(box->pref_size); - MemoryZeroStruct(&box->draw_bucket); - } - - //- rjf: hook into persistent state table - if(box_first_frame && !box_is_transient) - { - U64 slot = key.u64[0] % ui_state->box_table_size; - DLLInsert_NPZ(&ui_g_nil_box, ui_state->box_table[slot].hash_first, ui_state->box_table[slot].hash_last, ui_state->box_table[slot].hash_last, box, hash_next, hash_prev); - } - - //- rjf: hook into per-frame tree structure - if(!ui_box_is_nil(parent)) - { - DLLPushBack_NPZ(&ui_g_nil_box, parent->first, parent->last, box, next, prev); - parent->child_count += 1; - box->parent = parent; - } - - //- rjf: fill box - { - box->key = key; - box->flags = flags|ui_state->flags_stack.top->v; - box->fastpath_codepoint = ui_state->fastpath_codepoint_stack.top->v; - box->group_key = ui_state->group_key_stack.top->v; - - if(ui_is_focus_active() && (box->flags & UI_BoxFlag_DefaultFocusNav) && ui_key_match(ui_state->default_nav_root_key, ui_key_zero())) - { - ui_state->default_nav_root_key = box->key; - } - - if(box_first_frame) - { - box->first_touched_build_index = ui_state->build_index; - box->disabled_t = (F32)!!(box->flags & UI_BoxFlag_Disabled); - } - box->last_touched_build_index = ui_state->build_index; - - if(box->flags & UI_BoxFlag_Disabled && (!(last_flags & UI_BoxFlag_Disabled) || box_first_frame)) - { - box->first_disabled_build_index = ui_state->build_index; - } - - if(ui_state->fixed_x_stack.top != &ui_state->fixed_x_nil_stack_top) - { - box->flags |= UI_BoxFlag_FloatingX; - box->fixed_position.x = ui_state->fixed_x_stack.top->v; - } - if(ui_state->fixed_y_stack.top != &ui_state->fixed_y_nil_stack_top) - { - box->flags |= UI_BoxFlag_FloatingY; - box->fixed_position.y = ui_state->fixed_y_stack.top->v; - } - if(ui_state->fixed_width_stack.top != &ui_state->fixed_width_nil_stack_top) - { - box->flags |= UI_BoxFlag_FixedWidth; - box->fixed_size.x = ui_state->fixed_width_stack.top->v; - } - else - { - box->pref_size[Axis2_X] = ui_state->pref_width_stack.top->v; - } - if(ui_state->fixed_height_stack.top != &ui_state->fixed_height_nil_stack_top) - { - box->flags |= UI_BoxFlag_FixedHeight; - box->fixed_size.y = ui_state->fixed_height_stack.top->v; - } - else - { - box->pref_size[Axis2_Y] = ui_state->pref_height_stack.top->v; - } - - B32 is_auto_focus_active = ui_is_key_auto_focus_active(key); - B32 is_auto_focus_hot = ui_is_key_auto_focus_hot(key); - if(is_auto_focus_active) - { - ui_set_next_focus_active(UI_FocusKind_On); - } - if(is_auto_focus_hot) - { - ui_set_next_focus_hot(UI_FocusKind_On); - } - box->flags |= UI_BoxFlag_FocusHot * (ui_state->focus_hot_stack.top->v == UI_FocusKind_On); - box->flags |= UI_BoxFlag_FocusActive * (ui_state->focus_active_stack.top->v == UI_FocusKind_On); - if(box->flags & UI_BoxFlag_FocusHot && !ui_is_focus_hot()) - { - box->flags |= UI_BoxFlag_FocusHotDisabled; - } - if(box->flags & UI_BoxFlag_FocusActive && !ui_is_focus_active()) - { - box->flags |= UI_BoxFlag_FocusActiveDisabled; - } - - box->text_align = ui_state->text_alignment_stack.top->v; - box->child_layout_axis = ui_state->child_layout_axis_stack.top->v; - box->palette = ui_state->palette_stack.top->v; - box->font = ui_state->font_stack.top->v; - box->font_size = ui_state->font_size_stack.top->v; - box->tab_size = ui_state->tab_size_stack.top->v; - box->text_raster_flags = ui_state->text_raster_flags_stack.top->v; - box->corner_radii[Corner_00] = ui_state->corner_radius_00_stack.top->v; - box->corner_radii[Corner_01] = ui_state->corner_radius_01_stack.top->v; - box->corner_radii[Corner_10] = ui_state->corner_radius_10_stack.top->v; - box->corner_radii[Corner_11] = ui_state->corner_radius_11_stack.top->v; - box->blur_size = ui_state->blur_size_stack.top->v; - box->transparency = ui_state->transparency_stack.top->v; - box->squish = ui_state->squish_stack.top->v; - box->text_padding = ui_state->text_padding_stack.top->v; - box->hover_cursor = ui_state->hover_cursor_stack.top->v; - box->custom_draw = 0; - } - - //- rjf: auto-pop all stacks - { - UI_AutoPopStacks(ui_state); - } - - //- rjf: return - ProfEnd(); - return box; -} - -internal UI_Key -ui_active_seed_key(void) -{ - UI_Box *keyed_ancestor = &ui_g_nil_box; - { - for(UI_Box *p = ui_top_parent(); !ui_box_is_nil(p); p = p->parent) - { - if(!ui_key_match(ui_key_zero(), p->key)) - { - keyed_ancestor = p; - break; - } - } - } - return keyed_ancestor->key; -} - -internal UI_Box * -ui_build_box_from_string(UI_BoxFlags flags, String8 string) -{ - ProfBeginFunction(); - - //- rjf: grab active parent - UI_Box *parent = ui_top_parent(); - - //- rjf: figure out key - UI_Key key = ui_key_from_string(ui_active_seed_key(), string); - - //- rjf: build box from key, equip passed string - UI_Box *box = ui_build_box_from_key(flags, key); - if(flags & UI_BoxFlag_DrawText) - { - ui_box_equip_display_string(box, string); - } - - //- rjf: return - ProfEnd(); - return box; -} - -internal UI_Box * -ui_build_box_from_stringf(UI_BoxFlags flags, char *fmt, ...) -{ - Temp scratch = scratch_begin(0, 0); - va_list args; - va_start(args, fmt); - String8 string = push_str8fv(scratch.arena, fmt, args); - va_end(args); - UI_Box *box = ui_build_box_from_string(flags, string); - scratch_end(scratch); - return box; -} - -//- rjf: box node equipment - -internal void -ui_box_equip_display_string(UI_Box *box, String8 string) -{ - ProfBeginFunction(); - box->string = push_str8_copy(ui_build_arena(), string); - box->flags |= UI_BoxFlag_HasDisplayString; - UI_ColorCode text_color_code = (box->flags & UI_BoxFlag_DrawTextWeak ? UI_ColorCode_TextWeak : UI_ColorCode_Text); - if(box->flags & UI_BoxFlag_DrawText && (box->fastpath_codepoint == 0 || !(box->flags & UI_BoxFlag_DrawTextFastpathCodepoint))) - { - String8 display_string = ui_box_display_string(box); - D_FancyStringNode fancy_string_n = {0, {box->font, display_string, box->palette->colors[text_color_code], box->font_size, 0, 0}}; - D_FancyStringList fancy_strings = {&fancy_string_n, &fancy_string_n, 1}; - box->display_string_runs = d_fancy_run_list_from_fancy_string_list(ui_build_arena(), box->tab_size, box->text_raster_flags, &fancy_strings); - } - else if(box->flags & UI_BoxFlag_DrawText && box->flags & UI_BoxFlag_DrawTextFastpathCodepoint && box->fastpath_codepoint != 0) - { - Temp scratch = scratch_begin(0, 0); - String8 display_string = ui_box_display_string(box); - String32 fpcp32 = str32(&box->fastpath_codepoint, 1); - String8 fpcp = str8_from_32(scratch.arena, fpcp32); - U64 fpcp_pos = str8_find_needle(display_string, 0, fpcp, StringMatchFlag_CaseInsensitive); - if(fpcp_pos < display_string.size) - { - D_FancyStringNode pst_fancy_string_n = {0, {box->font, str8_skip(display_string, fpcp_pos+fpcp.size), box->palette->colors[text_color_code], box->font_size, 0, 0}}; - D_FancyStringNode cdp_fancy_string_n = {&pst_fancy_string_n, {box->font, str8_substr(display_string, r1u64(fpcp_pos, fpcp_pos+fpcp.size)), box->palette->colors[text_color_code], box->font_size, 3.f, 0}}; - D_FancyStringNode pre_fancy_string_n = {&cdp_fancy_string_n, {box->font, str8_prefix(display_string, fpcp_pos), box->palette->colors[text_color_code], box->font_size, 0, 0}}; - D_FancyStringList fancy_strings = {&pre_fancy_string_n, &pst_fancy_string_n, 3}; - box->display_string_runs = d_fancy_run_list_from_fancy_string_list(ui_build_arena(), box->tab_size, box->text_raster_flags, &fancy_strings); - } - else - { - D_FancyStringNode fancy_string_n = {0, {box->font, display_string, box->palette->colors[UI_ColorCode_Text], box->font_size, 0, 0}}; - D_FancyStringList fancy_strings = {&fancy_string_n, &fancy_string_n, 1}; - box->display_string_runs = d_fancy_run_list_from_fancy_string_list(ui_build_arena(), box->tab_size, box->text_raster_flags, &fancy_strings); - } - scratch_end(scratch); - } - ProfEnd(); -} - -internal void -ui_box_equip_display_fancy_strings(UI_Box *box, D_FancyStringList *strings) -{ - box->flags |= UI_BoxFlag_HasDisplayString; - box->string = d_string_from_fancy_string_list(ui_build_arena(), strings); - box->display_string_runs = d_fancy_run_list_from_fancy_string_list(ui_build_arena(), box->tab_size, box->text_raster_flags, strings); -} - -internal inline void -ui_box_equip_display_string_fancy_runs(UI_Box *box, String8 string, D_FancyRunList *runs) -{ - box->flags |= UI_BoxFlag_HasDisplayString; - box->string = push_str8_copy(ui_build_arena(), string); - box->display_string_runs = d_fancy_run_list_copy(ui_build_arena(), runs); -} - -internal inline void -ui_box_equip_fuzzy_match_ranges(UI_Box *box, FuzzyMatchRangeList *matches) -{ - box->flags |= UI_BoxFlag_HasFuzzyMatchRanges; - box->fuzzy_match_ranges = fuzzy_match_range_list_copy(ui_build_arena(), matches); -} - -internal void -ui_box_equip_draw_bucket(UI_Box *box, D_Bucket *bucket) -{ - box->flags |= UI_BoxFlag_DrawBucket; - if(box->draw_bucket != 0) - { - D_BucketScope(box->draw_bucket) d_sub_bucket(bucket); - } - else - { - box->draw_bucket = bucket; - } -} - -internal void -ui_box_equip_custom_draw(UI_Box *box, UI_BoxCustomDrawFunctionType *custom_draw, void *user_data) -{ - box->custom_draw = custom_draw; - box->custom_draw_user_data = user_data; -} - -//- rjf: box accessors / queries - -internal String8 -ui_box_display_string(UI_Box *box) -{ - String8 result = box->string; - if(!(box->flags & UI_BoxFlag_DisableIDString)) - { - result = ui_display_part_from_key_string(result); - } - return result; -} - -internal Vec2F32 -ui_box_text_position(UI_Box *box) -{ - Vec2F32 result = {0}; - F_Tag font = box->font; - F32 font_size = box->font_size; - F_Metrics font_metrics = f_metrics_from_tag_size(font, font_size); - result.y = floor_f32((box->rect.p0.y + box->rect.p1.y)/2.f) + font_metrics.capital_height/2.f; - if(!f_tag_match(font, ui_icon_font())) - { - result.y += font_metrics.descent/2; - } - switch(box->text_align) - { - default: - case UI_TextAlign_Left: - { - result.x = box->rect.p0.x + box->text_padding; - }break; - case UI_TextAlign_Center: - { - Vec2F32 text_dim = box->display_string_runs.dim; - result.x = floor_f32((box->rect.p0.x + box->rect.p1.x)/2 - text_dim.x/2); - result.x = ClampBot(result.x, box->rect.x0); - }break; - case UI_TextAlign_Right: - { - Vec2F32 text_dim = box->display_string_runs.dim; - result.x = round_f32((box->rect.p1.x) - text_dim.x - box->text_padding); - result.x = ClampBot(result.x, box->rect.x0); - }break; - } - result.x = floor_f32(result.x); - return result; -} - -internal U64 -ui_box_char_pos_from_xy(UI_Box *box, Vec2F32 xy) -{ - F_Tag font = box->font; - F32 font_size = box->font_size; - String8 line = ui_box_display_string(box); - U64 result = f_char_pos_from_tag_size_string_p(font, font_size, 0, box->tab_size, line, xy.x - ui_box_text_position(box).x); - return result; -} - -//////////////////////////////// -//~ rjf: Box Interaction - -internal UI_Signal -ui_signal_from_box(UI_Box *box) -{ - ProfBeginFunction(); - B32 is_focus_hot = box->flags & UI_BoxFlag_FocusHot && !(box->flags & UI_BoxFlag_FocusHotDisabled); - UI_Signal sig = {box}; - sig.event_flags |= os_get_event_flags(); - - ////////////////////////////// - //- rjf: calculate possibly-clipped box rectangle - // - Rng2F32 rect = box->rect; - for(UI_Box *b = box->parent; !ui_box_is_nil(b); b = b->parent) - { - if(b->flags & UI_BoxFlag_Clip) - { - rect = intersect_2f32(rect, b->rect); - } - } - - ////////////////////////////// - //- rjf: determine if we're under the context menu or not - // - B32 ctx_menu_is_ancestor = 0; - ProfScope("check context menu ancestor") - { - for(UI_Box *parent = box; !ui_box_is_nil(parent); parent = parent->parent) - { - if(parent == ui_state->ctx_menu_root) - { - ctx_menu_is_ancestor = 1; - break; - } - } - } - - ////////////////////////////// - //- rjf: calculate blacklist rectangles - // - Rng2F32 blacklist_rect = {0}; - if(!ctx_menu_is_ancestor && ui_state->ctx_menu_open) - { - blacklist_rect = ui_state->ctx_menu_root->rect; - } - - ////////////////////////////// - //- rjf: process events related to this box - // - B32 view_scrolled = 0; - for(UI_EventNode *n = ui_state->events->first, *next = 0; - n != 0; - n = next) - { - B32 taken = 0; - next = n->next; - UI_Event *evt = &n->v; - - //- rjf: unpack event - Vec2F32 evt_mouse = evt->pos; - B32 evt_mouse_in_bounds = !contains_2f32(blacklist_rect, evt_mouse) && contains_2f32(rect, evt_mouse); - UI_MouseButtonKind evt_mouse_button_kind = (evt->key == OS_Key_LeftMouseButton ? UI_MouseButtonKind_Left : - evt->key == OS_Key_MiddleMouseButton ? UI_MouseButtonKind_Middle : - evt->key == OS_Key_RightMouseButton ? UI_MouseButtonKind_Right : - UI_MouseButtonKind_Left); - B32 evt_key_is_mouse = (evt->key == OS_Key_LeftMouseButton || - evt->key == OS_Key_MiddleMouseButton || - evt->key == OS_Key_RightMouseButton); - sig.event_flags |= evt->modifiers; - - //- rjf: mouse presses in box -> set hot/active; mark signal accordingly - if(box->flags & UI_BoxFlag_MouseClickable && - evt->kind == UI_EventKind_Press && - evt_mouse_in_bounds && - evt_key_is_mouse) - { - ui_state->hot_box_key = box->key; - ui_state->active_box_key[evt_mouse_button_kind] = box->key; - sig.f |= (UI_SignalFlag_LeftPressed<drag_start_mouse = evt->pos; - if(ui_key_match(box->key, ui_state->press_key_history[evt_mouse_button_kind][0]) && - evt->timestamp_us-ui_state->press_timestamp_history_us[evt_mouse_button_kind][0] <= 1000000*os_double_click_time()) - { - sig.f |= (UI_SignalFlag_LeftDoubleClicked<key, ui_state->press_key_history[evt_mouse_button_kind][0]) && - ui_key_match(box->key, ui_state->press_key_history[evt_mouse_button_kind][1]) && - evt->timestamp_us-ui_state->press_timestamp_history_us[evt_mouse_button_kind][0] <= 1000000*os_double_click_time() && - ui_state->press_timestamp_history_us[evt_mouse_button_kind][0] - ui_state->press_timestamp_history_us[evt_mouse_button_kind][1] <= 1000000*os_double_click_time()) - { - sig.f |= (UI_SignalFlag_LeftTripleClicked<press_timestamp_history_us[evt_mouse_button_kind][1], &ui_state->press_timestamp_history_us[evt_mouse_button_kind][0], - sizeof(ui_state->press_timestamp_history_us[evt_mouse_button_kind][0]) * ArrayCount(ui_state->press_timestamp_history_us[evt_mouse_button_kind])-1); - MemoryCopy(&ui_state->press_key_history[evt_mouse_button_kind][1], &ui_state->press_key_history[evt_mouse_button_kind][0], - sizeof(ui_state->press_key_history[evt_mouse_button_kind][0]) * ArrayCount(ui_state->press_key_history[evt_mouse_button_kind])-1); - MemoryCopy(&ui_state->press_pos_history[evt_mouse_button_kind][1], &ui_state->press_pos_history[evt_mouse_button_kind][0], - sizeof(ui_state->press_pos_history[evt_mouse_button_kind][0]) * ArrayCount(ui_state->press_pos_history[evt_mouse_button_kind])-1); - ui_state->press_timestamp_history_us[evt_mouse_button_kind][0] = evt->timestamp_us; - ui_state->press_key_history[evt_mouse_button_kind][0] = box->key; - ui_state->press_pos_history[evt_mouse_button_kind][0] = evt_mouse; - taken = 1; - } - - //- rjf: mouse releases in active box -> unset active; mark signal accordingly - if(box->flags & UI_BoxFlag_MouseClickable && - evt->kind == UI_EventKind_Release && - ui_key_match(ui_state->active_box_key[evt_mouse_button_kind], box->key) && - evt_mouse_in_bounds && - evt_key_is_mouse) - { - ui_state->active_box_key[evt_mouse_button_kind] = ui_key_zero(); - sig.f |= (UI_SignalFlag_LeftReleased< unset hot/active - if(box->flags & UI_BoxFlag_MouseClickable && - evt->kind == UI_EventKind_Release && - ui_key_match(ui_state->active_box_key[evt_mouse_button_kind], box->key) && - !evt_mouse_in_bounds && - evt_key_is_mouse) - { - ui_state->hot_box_key = ui_key_zero(); - ui_state->active_box_key[evt_mouse_button_kind] = ui_key_zero(); - sig.f |= (UI_SignalFlag_LeftReleased< mark signal - if(box->flags & UI_BoxFlag_KeyboardClickable && - is_focus_hot && - evt->kind == UI_EventKind_Press && - evt->slot == UI_EventActionSlot_Accept) - { - sig.f |= UI_SignalFlag_KeyboardPressed; - taken = 1; - } - - //- rjf: focus is hot & copy event -> remember to copy this box tree's text content - if(is_focus_hot && - evt->flags & UI_EventFlag_Copy && - !ui_key_match(ui_key_zero(), box->key)) - { - ui_state->clipboard_copy_key = box->key; - taken = 1; - } - - //- rjf: ancestor is focused & fastpath codepoint pressed -> press - if(box->flags & UI_BoxFlag_Clickable && box->fastpath_codepoint != 0 && evt->string.size != 0) - { - B32 ancestor_is_focused = 0; - for(UI_Box *parent = box->parent; !ui_box_is_nil(parent); parent = parent->parent) - { - if(parent->flags & UI_BoxFlag_FocusActive) - { - ancestor_is_focused = 1; - if(parent->flags & UI_BoxFlag_FocusActiveDisabled || - !ui_key_match(parent->default_nav_focus_active_key, ui_key_zero())) - { - ancestor_is_focused = 0; - break; - } - } - } - if(ancestor_is_focused) - { - Temp scratch = scratch_begin(0, 0); - String32 insertion32 = str32_from_8(scratch.arena, evt->string); - if(insertion32.size == 1 && insertion32.str[0] == box->fastpath_codepoint) - { - taken = 1; - sig.f |= UI_SignalFlag_Clicked|UI_SignalFlag_Pressed; - } - scratch_end(scratch); - } - } - - //- rjf: scrolling - if(box->flags & UI_BoxFlag_Scroll && - evt->kind == UI_EventKind_Scroll && - evt->modifiers != OS_EventFlag_Ctrl && - evt_mouse_in_bounds) - { - Vec2F32 delta = evt->delta_2f32; - if(evt->modifiers & OS_EventFlag_Shift) - { - Swap(F32, delta.x, delta.y); - } - Vec2S16 delta16 = v2s16((S16)(delta.x/30.f), (S16)(delta.y/30.f)); - if(delta.x > 0 && delta16.x == 0) { delta16.x = +1; } - if(delta.x < 0 && delta16.x == 0) { delta16.x = -1; } - if(delta.y > 0 && delta16.y == 0) { delta16.y = +1; } - if(delta.y < 0 && delta16.y == 0) { delta16.y = -1; } - sig.scroll.x += delta16.x; - sig.scroll.y += delta16.y; - taken = 1; - } - - //- rjf: view scrolling - if(box->flags & UI_BoxFlag_ViewScroll && box->first_touched_build_index != box->last_touched_build_index && - evt->kind == UI_EventKind_Scroll && - evt->modifiers != OS_EventFlag_Ctrl && - evt_mouse_in_bounds) - { - Vec2F32 delta = evt->delta_2f32; - if(evt->modifiers & OS_EventFlag_Shift) - { - Swap(F32, delta.x, delta.y); - } - if(!(box->flags & UI_BoxFlag_ViewScrollX)) - { - if(delta.y == 0) - { - delta.y = delta.x; - } - delta.x = 0; - } - if(!(box->flags & UI_BoxFlag_ViewScrollY)) - { - if(delta.x == 0) - { - delta.x = delta.y; - } - delta.y = 0; - } - box->view_off_target.x += delta.x; - box->view_off_target.y += delta.y; - view_scrolled = 1; - taken = 1; - } - - //- rjf: taken -> eat event - if(taken) - { - ui_eat_event(ui_state->events, n); - } - } - - ////////////////////////////// - //- rjf: clamp view scrolling - // - if(view_scrolled && box->flags & UI_BoxFlag_ViewClamp) - { - Vec2F32 max_view_off_target = - { - ClampBot(0, box->view_bounds.x - box->fixed_size.x), - ClampBot(0, box->view_bounds.y - box->fixed_size.y), - }; - if(box->flags & UI_BoxFlag_ViewClampX) { box->view_off_target.x = Clamp(0, box->view_off_target.x, max_view_off_target.x); } - if(box->flags & UI_BoxFlag_ViewClampY) { box->view_off_target.y = Clamp(0, box->view_off_target.y, max_view_off_target.y); } - } - - ////////////////////////////// - //- rjf: active -> dragging - // - if(box->flags & UI_BoxFlag_MouseClickable) - { - for(EachEnumVal(UI_MouseButtonKind, k)) - { - if(ui_key_match(ui_state->active_box_key[k], box->key) || - sig.f & (UI_SignalFlag_LeftPressed< double-dragging - // - if(box->flags & UI_BoxFlag_MouseClickable) - { - for(EachEnumVal(UI_MouseButtonKind, k)) - { - if(sig.f & (UI_SignalFlag_LeftDragging<press_key_history[k][0], box->key) && - ui_key_match(ui_state->press_key_history[k][1], box->key) && - ui_state->press_timestamp_history_us[k][0] - ui_state->press_timestamp_history_us[k][1] <= 1000000*os_double_click_time() && - length_2f32(sub_2f32(ui_state->press_pos_history[k][0], ui_state->press_pos_history[k][1])) < 10.f) - { - sig.f |= (UI_SignalFlag_LeftDoubleDragging< triple-dragging - // - if(box->flags & UI_BoxFlag_MouseClickable) - { - for(EachEnumVal(UI_MouseButtonKind, k)) - { - if(sig.f & (UI_SignalFlag_LeftDragging<press_key_history[k][0], box->key) && - ui_key_match(ui_state->press_key_history[k][1], box->key) && - ui_key_match(ui_state->press_key_history[k][2], box->key) && - ui_state->press_timestamp_history_us[k][0] - ui_state->press_timestamp_history_us[k][1] <= 1000000*os_double_click_time() && - ui_state->press_timestamp_history_us[k][1] - ui_state->press_timestamp_history_us[k][2] <= 1000000*os_double_click_time() && - length_2f32(sub_2f32(ui_state->press_pos_history[k][0], ui_state->press_pos_history[k][1])) < 10.f && - length_2f32(sub_2f32(ui_state->press_pos_history[k][1], ui_state->press_pos_history[k][2])) < 10.f) - { - sig.f |= (UI_SignalFlag_LeftTripleDragging< always mark mouse-over - // - { - if(contains_2f32(rect, ui_state->mouse) && - !contains_2f32(blacklist_rect, ui_state->mouse)) - { - sig.f |= UI_SignalFlag_MouseOver; - } - } - - ////////////////////////////// - //- rjf: mouse is over this box's rect, no other hot key? -> set hot key, mark hovering - // - { - if(box->flags & UI_BoxFlag_MouseClickable && - contains_2f32(rect, ui_state->mouse) && - !contains_2f32(blacklist_rect, ui_state->mouse) && - (ui_key_match(ui_state->hot_box_key, ui_key_zero()) || ui_key_match(ui_state->hot_box_key, box->key)) && - (ui_key_match(ui_state->active_box_key[UI_MouseButtonKind_Left], ui_key_zero()) || ui_key_match(ui_state->active_box_key[UI_MouseButtonKind_Left], box->key)) && - (ui_key_match(ui_state->active_box_key[UI_MouseButtonKind_Middle], ui_key_zero()) || ui_key_match(ui_state->active_box_key[UI_MouseButtonKind_Middle], box->key)) && - (ui_key_match(ui_state->active_box_key[UI_MouseButtonKind_Right], ui_key_zero()) || ui_key_match(ui_state->active_box_key[UI_MouseButtonKind_Right], box->key))) - { - ui_state->hot_box_key = box->key; - sig.f |= UI_SignalFlag_Hovering; - } - } - - ////////////////////////////// - //- rjf: mouse is over this box's rect, currently-active-key has the same group key? -> set hot/active key - // - if(box->flags & UI_BoxFlag_MouseClickable && - contains_2f32(rect, ui_state->mouse) && - !contains_2f32(blacklist_rect, ui_state->mouse) && - !ui_key_match(ui_key_zero(), box->group_key)) - { - for(EachEnumVal(UI_MouseButtonKind, k)) - { - UI_Box *active_box = ui_box_from_key(ui_state->active_box_key[k]); - if(ui_key_match(box->group_key, active_box->group_key)) - { - ui_state->hot_box_key = box->key; - ui_state->active_box_key[k] = box->key; - sig.f |= UI_SignalFlag_Hovering|(UI_SignalFlag_Dragging< set drop hot key - // - { - if(box->flags & UI_BoxFlag_DropSite && - contains_2f32(rect, ui_state->mouse) && - !contains_2f32(blacklist_rect, ui_state->mouse) && - (ui_key_match(ui_state->drop_hot_box_key, ui_key_zero()) || ui_key_match(ui_state->drop_hot_box_key, box->key))) - { - ui_state->drop_hot_box_key = box->key; - } - } - - ////////////////////////////// - //- rjf: mouse is not over this box's rect, but this is the drop hot key? -> zero drop hot key - // - { - if(box->flags & UI_BoxFlag_DropSite && - (!contains_2f32(rect, ui_state->mouse) || - contains_2f32(blacklist_rect, ui_state->mouse)) && - ui_key_match(ui_state->drop_hot_box_key, box->key)) - { - ui_state->drop_hot_box_key = ui_key_zero(); - } - } - - ////////////////////////////// - //- rjf: clicking on something outside the context menu kills the context menu - // - if(!ctx_menu_is_ancestor && sig.f & (UI_SignalFlag_LeftPressed|UI_SignalFlag_RightPressed|UI_SignalFlag_MiddlePressed)) - { - ui_ctx_menu_close(); - } - - ////////////////////////////// - //- rjf: get default nav ancestor - // - UI_Box *default_nav_parent = &ui_g_nil_box; - for(UI_Box *p = ui_top_parent(); !ui_box_is_nil(p); p = p->parent) - { - if(p->flags & UI_BoxFlag_DefaultFocusNav) - { - default_nav_parent = p; - break; - } - } - - ////////////////////////////// - //- rjf: clicking in default nav -> set navigation state to this box - // - if(box->flags & UI_BoxFlag_ClickToFocus && sig.f&UI_SignalFlag_Pressed && !ui_box_is_nil(default_nav_parent)) - { - default_nav_parent->default_nav_focus_next_hot_key = box->key; - if(!ui_key_match(default_nav_parent->default_nav_focus_active_key, box->key)) - { - default_nav_parent->default_nav_focus_next_active_key = ui_key_zero(); - } - } - - - ProfEnd(); - return sig; -} - -//////////////////////////////// -//~ rjf: Stacks - -//- rjf: helpers - -internal Rng2F32 -ui_push_rect(Rng2F32 rect) -{ - Rng2F32 replaced = {0}; - Vec2F32 size = dim_2f32(rect); - replaced.x0 = ui_push_fixed_x(rect.x0); - replaced.y0 = ui_push_fixed_y(rect.y0); - replaced.x1 = replaced.x0 + ui_push_fixed_width(size.x); - replaced.y1 = replaced.y0 + ui_push_fixed_height(size.y); - return replaced; -} - -internal Rng2F32 -ui_pop_rect(void) -{ - Rng2F32 popped = {0}; - popped.x0 = ui_pop_fixed_x(); - popped.y0 = ui_pop_fixed_y(); - popped.x1 = popped.x0 + ui_pop_fixed_width(); - popped.y1 = popped.y0 + ui_pop_fixed_height(); - return popped; -} - -internal void -ui_set_next_rect(Rng2F32 rect) -{ - Vec2F32 size = dim_2f32(rect); - ui_set_next_fixed_x(rect.x0); - ui_set_next_fixed_y(rect.y0); - ui_set_next_fixed_width(size.x); - ui_set_next_fixed_height(size.y); -} - -internal UI_Size -ui_push_pref_size(Axis2 axis, UI_Size v) -{ - UI_Size result = zero_struct; - switch(axis) - { - default: break; - case Axis2_X: {result = ui_push_pref_width(v);}break; - case Axis2_Y: {result = ui_push_pref_height(v);}break; - } - return result; -} - -internal UI_Size -ui_pop_pref_size(Axis2 axis) -{ - UI_Size result = zero_struct; - switch(axis) - { - default: break; - case Axis2_X: {result = ui_pop_pref_width();}break; - case Axis2_Y: {result = ui_pop_pref_height();}break; - } - return result; -} - -internal UI_Size -ui_set_next_pref_size(Axis2 axis, UI_Size v) -{ - return (axis == Axis2_X ? ui_set_next_pref_width : ui_set_next_pref_height)(v); -} - -internal void -ui_push_corner_radius(F32 v) -{ - ui_push_corner_radius_00(v); - ui_push_corner_radius_01(v); - ui_push_corner_radius_10(v); - ui_push_corner_radius_11(v); -} - -internal void -ui_pop_corner_radius(void) -{ - ui_pop_corner_radius_00(); - ui_pop_corner_radius_01(); - ui_pop_corner_radius_10(); - ui_pop_corner_radius_11(); -} - -//////////////////////////////// -//~ rjf: Generated Code - -#define UI_StackTopImpl(state, name_upper, name_lower) \ -return state->name_lower##_stack.top->v; - -#define UI_StackBottomImpl(state, name_upper, name_lower) \ -return state->name_lower##_stack.bottom_val; - -#define UI_StackPushImpl(state, name_upper, name_lower, type, new_value) \ -UI_##name_upper##Node *node = state->name_lower##_stack.free;\ -if(node != 0) {SLLStackPop(state->name_lower##_stack.free);}\ -else {node = push_array(ui_build_arena(), UI_##name_upper##Node, 1);}\ -type old_value = state->name_lower##_stack.top->v;\ -node->v = new_value;\ -SLLStackPush(state->name_lower##_stack.top, node);\ -if(node->next == &state->name_lower##_nil_stack_top)\ -{\ -state->name_lower##_stack.bottom_val = (new_value);\ -}\ -state->name_lower##_stack.auto_pop = 0;\ -return old_value; - -#define UI_StackPopImpl(state, name_upper, name_lower) \ -UI_##name_upper##Node *popped = state->name_lower##_stack.top;\ -if(popped != &state->name_lower##_nil_stack_top)\ -{\ -SLLStackPop(state->name_lower##_stack.top);\ -SLLStackPush(state->name_lower##_stack.free, popped);\ -state->name_lower##_stack.auto_pop = 0;\ -}\ -return popped->v;\ - -#define UI_StackSetNextImpl(state, name_upper, name_lower, type, new_value) \ -UI_##name_upper##Node *node = state->name_lower##_stack.free;\ -if(node != 0) {SLLStackPop(state->name_lower##_stack.free);}\ -else {node = push_array(ui_build_arena(), UI_##name_upper##Node, 1);}\ -type old_value = state->name_lower##_stack.top->v;\ -node->v = new_value;\ -SLLStackPush(state->name_lower##_stack.top, node);\ -state->name_lower##_stack.auto_pop = 1;\ -return old_value; - -#include "generated/ui.meta.c" +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#undef RADDBG_LAYER_COLOR +#define RADDBG_LAYER_COLOR 0.80f, 0.40f, 0.35f + +//////////////////////////////// +//~ rjf: Globals + +thread_static UI_State *ui_state = 0; + +//////////////////////////////// +//~ rjf: Basic Type Functions + +internal U64 +ui_hash_from_string(U64 seed, String8 string) +{ + U64 result = seed; + for(U64 i = 0; i < string.size; i += 1) + { + result = ((result << 5) + result) + string.str[i]; + } + return result; +} + +internal String8 +ui_hash_part_from_key_string(String8 string) +{ + String8 result = string; + + // rjf: look for ### patterns, which can replace the entirety of the part of + // the string that is hashed. + U64 hash_replace_signifier_pos = str8_find_needle(string, 0, str8_lit("###"), 0); + if(hash_replace_signifier_pos < string.size) + { + result = str8_skip(string, hash_replace_signifier_pos); + } + + return result; +} + +internal String8 +ui_display_part_from_key_string(String8 string) +{ + U64 hash_pos = str8_find_needle(string, 0, str8_lit("##"), 0); + string.size = hash_pos; + return string; +} + +internal UI_Key +ui_key_zero(void) +{ + UI_Key result = {0}; + return result; +} + +internal UI_Key +ui_key_make(U64 v) +{ + UI_Key result = {v}; + return result; +} + +internal UI_Key +ui_key_from_string(UI_Key seed_key, String8 string) +{ + ProfBeginFunction(); + UI_Key result = {0}; + if(string.size != 0) + { + String8 hash_part = ui_hash_part_from_key_string(string); + result.u64[0] = ui_hash_from_string(seed_key.u64[0], hash_part); + } + ProfEnd(); + return result; +} + +internal UI_Key +ui_key_from_stringf(UI_Key seed_key, char *fmt, ...) +{ + Temp scratch = scratch_begin(0, 0); + va_list args; + va_start(args, fmt); + String8 string = push_str8fv(scratch.arena, fmt, args); + va_end(args); + UI_Key key = ui_key_from_string(seed_key, string); + scratch_end(scratch); + return key; +} + +internal B32 +ui_key_match(UI_Key a, UI_Key b) +{ + return a.u64[0] == b.u64[0]; +} + +//////////////////////////////// +//~ rjf: Event Type Functions + +internal UI_EventNode * +ui_event_list_push(Arena *arena, UI_EventList *list, UI_Event *v) +{ + UI_EventNode *n = push_array(arena, UI_EventNode, 1); + MemoryCopyStruct(&n->v, v); + n->v.string = push_str8_copy(arena, n->v.string); + DLLPushBack(list->first, list->last, n); + list->count += 1; + return n; +} + +internal void +ui_eat_event(UI_EventList *list, UI_EventNode *node) +{ + DLLRemove(list->first, list->last, node); + list->count -= 1; +} + +//////////////////////////////// +//~ rjf: Text Operation Functions + +internal B32 +ui_char_is_scan_boundary(U8 c) +{ + return (char_is_alpha(c) || char_is_digit(c, 10) || c == '_'); +} + +internal S64 +ui_scanned_column_from_column(String8 string, S64 start_column, Side side) +{ + S64 new_column = start_column; + S64 delta = (!!side)*2 - 1; + B32 found_text = 0; + B32 found_non_space = 0; + S64 start_off = delta < 0 ? delta : 0; + for(S64 col = start_column+start_off; 1 <= col && col <= string.size+1; col += delta) + { + U8 byte = (col <= string.size) ? string.str[col-1] : 0; + B32 is_non_space = !char_is_space(byte); + B32 is_name = ui_char_is_scan_boundary(byte); + if(((side == Side_Min) && (col == 1)) || + ((side == Side_Max) && (col == string.size+1)) || + (found_non_space && !is_non_space) || + (found_text && !is_name)) + { + new_column = col + (!side && col != 1); + break; + } + else if (!found_text && is_name) + { + found_text = 1; + } + else if (!found_non_space && is_non_space) + { + found_non_space = 1; + } + } + return new_column; +} + +internal UI_TxtOp +ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, TxtPt cursor, TxtPt mark) +{ + TxtPt next_cursor = cursor; + TxtPt next_mark = mark; + TxtRng range = {0}; + String8 replace = {0}; + String8 copy = {0}; + UI_TxtOpFlags flags = 0; + Vec2S32 delta = event->delta_2s32; + Vec2S32 original_delta = delta; + + //- rjf: resolve high-level delta into byte delta, based on unit + switch(event->delta_unit) + { + default:{}break; + case UI_EventDeltaUnit_Char: + { + // TODO(rjf): this should account for multi-byte characters in UTF-8... for now, just assume ASCII and + // no-op + }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; + }break; + case UI_EventDeltaUnit_Line: + case UI_EventDeltaUnit_Whole: + case UI_EventDeltaUnit_Page: + { + S64 first_nonwhitespace_column = 1; + for(U64 idx = 0; idx < string.size; idx += 1) + { + if(!char_is_space(string.str[idx])) + { + first_nonwhitespace_column = (S64)idx + 1; + 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); + }break; + } + + //- rjf: zero delta + if(!txt_pt_match(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)) + { + next_cursor.column += delta.x; + } + + //- rjf: cap at line + if(event->flags & UI_EventFlag_CapAtLine) + { + next_cursor.column = Clamp(1, next_cursor.column, (S64)(string.size+1)); + } + + //- rjf: in some cases, we want to pick a selection side based on the delta + if(!txt_pt_match(cursor, mark) && event->flags & UI_EventFlag_PickSelectSide) + { + if(original_delta.x < 0 || original_delta.y < 0) + { + next_cursor = next_mark = txt_pt_min(cursor, mark); + } + else if(original_delta.x > 0 || original_delta.y > 0) + { + next_cursor = next_mark = txt_pt_max(cursor, mark); + } + } + + //- rjf: copying + if(event->flags & UI_EventFlag_Copy) + { + if(cursor.line == mark.line) + { + copy = str8_substr(string, r1u64(cursor.column-1, mark.column-1)); + flags |= UI_TxtOpFlag_Copy; + } + else + { + flags |= UI_TxtOpFlag_Invalid; + } + } + + //- rjf: pasting + if(event->flags & UI_EventFlag_Paste) + { + range = txt_rng(cursor, mark); + replace = os_get_clipboard_text(arena); + next_cursor = next_mark = txt_pt(cursor.line, cursor.column+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(""); + next_cursor = next_mark = new_pos; + } + + //- rjf: stick mark to cursor, when we don't want to keep it in the same spot + if(!(event->flags & UI_EventFlag_KeepMark)) + { + next_mark = next_cursor; + } + + //- rjf: insertion + if(event->string.size != 0) + { + range = txt_rng(cursor, mark); + replace = push_str8_copy(arena, event->string); + next_cursor = next_mark = txt_pt(range.min.line, range.min.column + event->string.size); + } + + //- rjf: replace & commit -> replace entire range +#if 0 + if(event->flags & UI_EventFlag_ReplaceAndCommit) + { + range = txt_rng(txt_pt(cursor.line, 1), txt_pt(cursor.line, line.size+1)); + } +#endif + + //- rjf: determine if this event should be taken, based on bounds of cursor + { + if(next_cursor.column > string.size+1 || 1 > next_cursor.column || event->delta_2s32.y != 0) + { + flags |= UI_TxtOpFlag_Invalid; + } + next_cursor.column = Clamp(1, next_cursor.column, string.size+replace.size+1); + next_mark.column = Clamp(1, next_mark.column, string.size+replace.size+1); + } + + //- rjf: build+fill + UI_TxtOp op = {0}; + { + op.flags = flags; + op.replace = replace; + op.copy = copy; + op.range = range; + op.cursor = next_cursor; + op.mark = next_mark; + } + return op; +} + +internal String8 +ui_push_string_replace_range(Arena *arena, String8 string, Rng1S64 col_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) + { + range.min = 0; + } + if(range.max > string.size) + { + range.max = string.size; + } + + //- rjf: calculate new size + U64 old_size = string.size; + U64 new_size = old_size - (range.max - range.min) + replace.size; + + //- rjf: push+fill new string storage + U8 *push_base = push_array(arena, U8, new_size); + { + MemoryCopy(push_base+0, string.str, range.min); + MemoryCopy(push_base+range.min+replace.size, string.str+range.max, string.size-range.max); + if(replace.str != 0) + { + MemoryCopy(push_base+range.min, replace.str, replace.size); + } + } + + String8 result = str8(push_base, new_size); + return result; +} + +//////////////////////////////// +//~ rjf: Sizes + +internal UI_Size +ui_size(UI_SizeKind kind, F32 value, F32 strictness) +{ + UI_Size size = {kind, value, strictness}; + return size; +} + +//////////////////////////////// +//~ rjf: Scroll Point Type Functions + +internal UI_ScrollPt +ui_scroll_pt(S64 idx, F32 off) +{ + UI_ScrollPt pt = {idx, off}; + return pt; +} + +internal void +ui_scroll_pt_target_idx(UI_ScrollPt *v, S64 idx) +{ + v->off = mod_f32(v->off, 1.f) + (F32)(v->idx+(S64)v->off - idx); + v->idx = idx; +} + +internal void +ui_scroll_pt_clamp_idx(UI_ScrollPt *v, Rng1S64 range) +{ + if(v->idx < range.min || range.max < v->idx) + { + S64 clamped = range.min; + ui_scroll_pt_target_idx(v, clamped); + } +} + +//////////////////////////////// +//~ rjf: Boxes + +internal B32 +ui_box_is_nil(UI_Box *box) +{ + return box == 0 || box == &ui_g_nil_box; +} + +internal UI_BoxRec +ui_box_rec_df(UI_Box *box, UI_Box *root, U64 sib_member_off, U64 child_member_off) +{ + UI_BoxRec result = {0}; + result.next = &ui_g_nil_box; + if(!ui_box_is_nil(*MemberFromOffset(UI_Box **, box, child_member_off))) + { + result.next = *MemberFromOffset(UI_Box **, box, child_member_off); + result.push_count = 1; + } + else for(UI_Box *p = box; !ui_box_is_nil(p) && p != root; p = p->parent) + { + if(!ui_box_is_nil(*MemberFromOffset(UI_Box **, p, sib_member_off))) + { + result.next = *MemberFromOffset(UI_Box **, p, sib_member_off); + break; + } + result.pop_count += 1; + } + return result; +} + +internal void +ui_box_list_push(Arena *arena, UI_BoxList *list, UI_Box *box) +{ + UI_BoxNode *n = push_array(arena, UI_BoxNode, 1); + n->box = box; + SLLQueuePush(list->first, list->last, n); + list->count += 1; +} + +//////////////////////////////// +//~ rjf: State Building / Selecting + +internal UI_State * +ui_state_alloc(void) +{ + Arena *arena = arena_alloc(); + UI_State *ui = push_array(arena, UI_State, 1); + ui->arena = arena; + ui->build_arenas[0] = arena_alloc(); + ui->build_arenas[1] = arena_alloc(); + ui->drag_state_arena = arena_alloc(); + ui->string_hover_arena = arena_alloc(); + ui->box_table_size = 4096; + ui->box_table = push_array(arena, UI_BoxHashSlot, ui->box_table_size); + UI_InitStackNils(ui); + return ui; +} + +internal void +ui_state_release(UI_State *state) +{ + arena_release(state->string_hover_arena); + arena_release(state->drag_state_arena); + for(int i = 0; i < ArrayCount(state->build_arenas); i += 1) + { + arena_release(state->build_arenas[i]); + } + arena_release(state->arena); +} + +internal UI_Box * +ui_root_from_state(UI_State *state) +{ + return state->root; +} + +internal B32 +ui_animating_from_state(UI_State *state) +{ + return state->is_animating; +} + +internal void +ui_select_state(UI_State *state) +{ + ui_state = state; +} + +internal UI_State * +ui_get_selected_state(void) +{ + return ui_state; +} + +//////////////////////////////// +//~ rjf: Implicit State Accessors/Mutators + +//- rjf: per-frame info + +internal Arena * +ui_build_arena(void) +{ + Arena *result = ui_state->build_arenas[ui_state->build_index%ArrayCount(ui_state->build_arenas)]; + return result; +} + +internal OS_Handle +ui_window(void) +{ + return ui_state->window; +} + +internal UI_EventList * +ui_events(void) +{ + return ui_state->events; +} + +internal Vec2F32 +ui_mouse(void) +{ + return ui_state->mouse; +} + +internal F_Tag +ui_icon_font(void) +{ + return ui_state->icon_info.icon_font; +} + +internal String8 +ui_icon_string_from_kind(UI_IconKind icon_kind) +{ + return ui_state->icon_info.icon_kind_text_map[icon_kind]; +} + +internal F32 +ui_dt(void) +{ + return ui_state->animation_dt; +} + +//- rjf: event consumption helpers + +internal B32 +ui_key_press(OS_EventFlags mods, OS_Key key) +{ + UI_EventList *list = ui_events(); + B32 result = 0; + for(UI_EventNode *n = list->first; n != 0; n = n->next) + { + if(n->v.kind == UI_EventKind_Press && n->v.key == key && n->v.modifiers == mods) + { + result = 1; + ui_eat_event(list, n); + break; + } + } + return result; +} + +internal B32 +ui_key_release(OS_EventFlags mods, OS_Key key) +{ + UI_EventList *list = ui_events(); + B32 result = 0; + for(UI_EventNode *n = list->first; n != 0; n = n->next) + { + if(n->v.kind == UI_EventKind_Release && n->v.key == key && n->v.modifiers == mods) + { + result = 1; + ui_eat_event(list, n); + break; + } + } + return result; +} + +internal B32 +ui_text(U32 character) +{ + UI_EventList *list = ui_events(); + B32 result = 0; + Temp scratch = scratch_begin(0, 0); + String8 character_text = str8_from_32(scratch.arena, str32(&character, 1)); + for(UI_EventNode *n = list->first; n != 0; n = n->next) + { + if(n->v.kind == UI_EventKind_Text && str8_match(character_text, n->v.string, 0)) + { + result = 1; + ui_eat_event(list, n); + break; + } + } + scratch_end(scratch); + return result; +} + +internal B32 +ui_slot_press(UI_EventActionSlot slot) +{ + UI_EventList *list = ui_events(); + B32 result = 0; + for(UI_EventNode *n = list->first; n != 0; n = n->next) + { + if(n->v.kind == UI_EventKind_Press && n->v.slot == slot) + { + result = 1; + ui_eat_event(list, n); + break; + } + } + return result; +} + +//- rjf: drag data + +internal Vec2F32 +ui_drag_start_mouse(void) +{ + return ui_state->drag_start_mouse; +} + +internal Vec2F32 +ui_drag_delta(void) +{ + return sub_2f32(ui_mouse(), ui_state->drag_start_mouse); +} + +internal void +ui_store_drag_data(String8 string) +{ + arena_clear(ui_state->drag_state_arena); + ui_state->drag_state_data = push_str8_copy(ui_state->drag_state_arena, string); +} + +internal String8 +ui_get_drag_data(U64 min_required_size) +{ + if(ui_state->drag_state_data.size < min_required_size) + { + Temp scratch = scratch_begin(0, 0); + String8 str = {push_array(scratch.arena, U8, min_required_size), min_required_size}; + ui_store_drag_data(str); + scratch_end(scratch); + } + return ui_state->drag_state_data; +} + +//- rjf: hovered string info + +internal B32 +ui_string_hover_active(void) +{ + return (ui_state->build_index > 0 && ui_state->string_hover_build_index >= ui_state->build_index-1 && + os_now_microseconds() >= ui_state->string_hover_begin_us + 500000); +} + +internal U64 +ui_string_hover_begin_time_us(void) +{ + return ui_state->string_hover_begin_us; +} + +internal String8 +ui_string_hover_string(Arena *arena) +{ + String8 result = push_str8_copy(arena, ui_state->string_hover_string); + return result; +} + +internal D_FancyRunList +ui_string_hover_runs(Arena *arena) +{ + D_FancyRunList result = d_fancy_run_list_copy(arena, &ui_state->string_hover_fancy_runs); + return result; +} + +//- rjf: interaction keys + +internal UI_Key +ui_hot_key(void) +{ + return ui_state->hot_box_key; +} + +internal UI_Key +ui_active_key(UI_MouseButtonKind button_kind) +{ + return ui_state->active_box_key[button_kind]; +} + +internal UI_Key +ui_drop_hot_key(void) +{ + return ui_state->drop_hot_box_key; +} + +//- rjf: controls over interaction + +internal void +ui_kill_action(void) +{ + for(EachEnumVal(UI_MouseButtonKind, k)) + { + ui_state->active_box_key[k] = ui_key_zero(); + } +} + +//- rjf: box cache lookup + +internal UI_Box * +ui_box_from_key(UI_Key key) +{ + ProfBeginFunction(); + UI_Box *result = &ui_g_nil_box; + if(!ui_key_match(key, ui_key_zero())) + { + U64 slot = key.u64[0] % ui_state->box_table_size; + for(UI_Box *b = ui_state->box_table[slot].hash_first; !ui_box_is_nil(b); b = b->hash_next) + { + if(ui_key_match(b->key, key)) + { + result = b; + break; + } + } + } + ProfEnd(); + return result; +} + +//////////////////////////////// +//~ rjf: Top-Level Building API + +internal void +ui_begin_build(OS_Handle window, UI_EventList *events, UI_IconInfo *icon_info, UI_WidgetPaletteInfo *widget_palette_info, UI_AnimationInfo *animation_info, F32 real_dt, F32 animation_dt) +{ + //- rjf: reset per-build ui state + { + UI_InitStacks(ui_state); + ui_state->root = &ui_g_nil_box; + ui_state->ctx_menu_touched_this_frame = 0; + ui_state->is_animating = 0; + ui_state->clipboard_copy_key = ui_key_zero(); + ui_state->last_build_box_count = ui_state->build_box_count; + ui_state->build_box_count = 0; + ui_state->tooltip_open = 0; + ui_state->ctx_menu_changed = 0; + } + + //- rjf: detect mouse-moves + for(UI_EventNode *n = events->first; n != 0; n = n->next) + { + if(n->v.kind == UI_EventKind_MouseMove) + { + ui_state->last_time_mousemoved_us = os_now_microseconds(); + } + } + + //- rjf: fill build phase parameters + { + ui_state->events = events; + ui_state->window = window; + ui_state->mouse = (os_window_is_focused(window) || ui_state->last_time_mousemoved_us+500000 >= os_now_microseconds()) ? os_mouse_from_window(window) : v2f32(-100, -100); + ui_state->animation_dt = animation_dt; + MemoryZeroStruct(&ui_state->icon_info); + ui_state->icon_info.icon_font = icon_info->icon_font; + for(UI_IconKind icon_kind = UI_IconKind_Null; + icon_kind < UI_IconKind_COUNT; + icon_kind = (UI_IconKind)(icon_kind + 1)) + { + ui_state->icon_info.icon_kind_text_map[icon_kind] = push_str8_copy(ui_build_arena(), icon_info->icon_kind_text_map[icon_kind]); + } + MemoryCopyStruct(&ui_state->widget_palette_info, widget_palette_info); + MemoryCopyStruct(&ui_state->animation_info, animation_info); + } + + //- rjf: do default navigation + { + Temp scratch = scratch_begin(0, 0); + if(!ui_key_match(ui_state->default_nav_root_key, ui_key_zero())) + { + UI_Box *nav_root = ui_box_from_key(ui_state->default_nav_root_key); + if(!ui_box_is_nil(nav_root)) + { + //- rjf: no child has the active focus -> do navigation at this layer + if(ui_key_match(ui_key_zero(), nav_root->default_nav_focus_active_key)) + { + for(;;) + { + B32 moved = 0; + UI_Box *focus_box = ui_box_from_key(nav_root->default_nav_focus_next_hot_key); + UI_BoxList next_focus_box_candidates = {0}; + + // rjf: gather & consume events & nav actions + B32 nav_next = 0; + B32 nav_prev = 0; + Axis2 axis_lock = Axis2_Invalid; + if(ui_key_press(0, OS_Key_Tab)) + { + nav_next = 1; + } + if(ui_key_press(OS_EventFlag_Shift, OS_Key_Tab)) + { + nav_prev = 1; + } + for(UI_EventNode *node = events->first, *next = 0; node != 0; node = next) + { + next = node->next; + B32 taken = 0; + if(node->v.delta_2s32.x == 0 && node->v.delta_2s32.y == 0) + { + continue; + } + if(((node->v.delta_2s32.x > 0 && nav_root->flags & UI_BoxFlag_DefaultFocusNavX) || node->v.delta_2s32.x == 0) && + ((node->v.delta_2s32.y > 0 && nav_root->flags & UI_BoxFlag_DefaultFocusNavY) || node->v.delta_2s32.y == 0)) + { + taken = 1; + nav_next = 1; + } + if(((node->v.delta_2s32.x < 0 && nav_root->flags & UI_BoxFlag_DefaultFocusNavX) || node->v.delta_2s32.x == 0) && + ((node->v.delta_2s32.y < 0 && nav_root->flags & UI_BoxFlag_DefaultFocusNavY) || node->v.delta_2s32.y == 0)) + { + taken = 1; + nav_prev = 1; + } + if(node->v.flags & UI_EventFlag_ExplicitDirectional) + { + axis_lock = node->v.delta_2s32.x != 0 ? Axis2_X : Axis2_Y; + } + if(taken) + { + ui_eat_event(events, node); + } + } + + // rjf: [+] directional movement + if(nav_next) + { + UI_Box *search_start = ui_box_is_nil(focus_box) ? nav_root : focus_box; + U64 moved_in_axis[Axis2_COUNT] = {0}; + moved = 1; + for(UI_Box *box = search_start;;) + { + if(box != search_start && !(box->flags & UI_BoxFlag_FocusNavSkip) && (box->flags & UI_BoxFlag_Clickable || ui_box_is_nil(box)) && (axis_lock == Axis2_Invalid || moved_in_axis[axis_lock] > 0)) + { + ui_box_list_push(scratch.arena, &next_focus_box_candidates, box); + if(axis_lock == Axis2_Invalid || moved_in_axis[axis_lock] > 1) + { + break; + } + } + UI_Box *last_box = box; + if(!ui_box_is_nil(box->first)) + { + moved_in_axis[box->child_layout_axis] += 1; + box = box->first; + } + else for(UI_Box *p = box; !ui_box_is_nil(p) && p != nav_root; p = p->parent) + { + if(!ui_box_is_nil(p->next)) + { + moved_in_axis[p->parent->child_layout_axis] += 1; + box = p->next; + break; + } + } + if(last_box == box) + { + ui_box_list_push(scratch.arena, &next_focus_box_candidates, &ui_g_nil_box); + break; + } + } + } + + // rjf: [-] directional movement + if(nav_prev) + { + UI_Box *search_start = ui_box_is_nil(focus_box) ? nav_root : focus_box; + U64 moved_in_axis[Axis2_COUNT] = {0}; + moved = 1; + for(UI_Box *box = search_start;;) + { + if(box != search_start && !(box->flags & UI_BoxFlag_FocusNavSkip) && (box->flags & UI_BoxFlag_Clickable || ui_box_is_nil(box)) && (axis_lock == Axis2_Invalid || moved_in_axis[axis_lock] > 0)) + { + ui_box_list_push(scratch.arena, &next_focus_box_candidates, box); + if(axis_lock == Axis2_Invalid || moved_in_axis[axis_lock] > 1) + { + break; + } + } + UI_Box *last_box = box; + UI_Box *root_descendant = &ui_g_nil_box; + if(box == nav_root && box == search_start) + { + for(UI_Box *d = box->last; !ui_box_is_nil(d); d = d->last) + { + moved_in_axis[d->parent->child_layout_axis] += 1; + root_descendant = d; + } + } + UI_Box *prev_descendant = &ui_g_nil_box; + for(UI_Box *d = box->prev; !ui_box_is_nil(d); d = d->last) + { + moved_in_axis[d->parent->child_layout_axis] += 1; + prev_descendant = d; + } + if(!ui_box_is_nil(root_descendant)) + { + box = root_descendant; + } + else if(!ui_box_is_nil(prev_descendant)) + { + box = prev_descendant; + } + else if(box->parent != nav_root) + { + moved_in_axis[box->parent->child_layout_axis] += 1; + box = box->parent; + } + if(box == last_box) + { + ui_box_list_push(scratch.arena, &next_focus_box_candidates, &ui_g_nil_box); + break; + } + } + } + + // rjf: scan candidates and grab next focus box + UI_Box *next_focus_box = focus_box; + F32 best_distance_from_start = 1000000; + for(UI_BoxNode *n = next_focus_box_candidates.first; n != 0; n = n->next) + { + UI_Box *box = n->box; + F32 distance_from_start = 0; + if(axis_lock != Axis2_Invalid) + { + distance_from_start = abs_f32(center_2f32(box->rect).v[axis2_flip(axis_lock)] - center_2f32(focus_box->rect).v[axis2_flip(axis_lock)]); + } + if(distance_from_start < best_distance_from_start && box != focus_box) + { + next_focus_box = box; + best_distance_from_start = distance_from_start; + } + } + + // rjf: commit next focus box + nav_root->default_nav_focus_next_hot_key = next_focus_box->key; + + // rjf: no movement -> break + if(moved == 0) + { + break; + } + } + } + + //- rjf: some child has the active focus -> accept escape keys to pop from the active key stack + if(!ui_key_match(ui_key_zero(), nav_root->default_nav_focus_active_key)) + { + for(;ui_slot_press(UI_EventActionSlot_Cancel);) + { + UI_Box *prev_focus_root = nav_root; + for(UI_Box *focus_root = ui_box_from_key(nav_root->default_nav_focus_active_key); + !ui_box_is_nil(focus_root);) + { + UI_Box *next_focus_root = ui_box_from_key(focus_root->default_nav_focus_active_key); + if(ui_box_is_nil(next_focus_root)) + { + prev_focus_root->default_nav_focus_next_active_key = ui_key_zero(); + break; + } + else + { + prev_focus_root = focus_root; + focus_root = next_focus_root; + } + } + } + } + } + } + ui_state->default_nav_root_key = ui_key_zero(); + scratch_end(scratch); + } + + //- rjf: next-default-nav-focus keys -> current-default-nav-focus-keys + for(U64 slot_idx = 0; slot_idx < ui_state->box_table_size; slot_idx += 1) + { + for(UI_Box *box = ui_state->box_table[slot_idx].hash_first; + !ui_box_is_nil(box); + box = box->hash_next) + { + box->default_nav_focus_hot_key = box->default_nav_focus_next_hot_key; + box->default_nav_focus_active_key = box->default_nav_focus_next_active_key; + } + } + + //- rjf: build top-level root + { + Rng2F32 window_rect = os_client_rect_from_window(window); + Vec2F32 window_rect_size = dim_2f32(window_rect); + ui_set_next_fixed_width(window_rect_size.x); + ui_set_next_fixed_height(window_rect_size.y); + ui_set_next_child_layout_axis(Axis2_X); + UI_Box *root = ui_build_box_from_stringf(0, "###%I64x", window.u64[0]); + ui_push_parent(root); + ui_state->root = root; + } + + //- rjf: setup parent box for tooltip + UI_FixedX(ui_state->mouse.x+15.f) UI_FixedY(ui_state->mouse.y+15.f) UI_PrefWidth(ui_children_sum(1.f)) UI_PrefHeight(ui_children_sum(1.f)) + { + ui_set_next_child_layout_axis(Axis2_Y); + ui_state->tooltip_root = ui_build_box_from_stringf(0, "###tooltip_%I64x", window.u64[0]); + } + + //- rjf: setup parent box for context menu + ui_state->ctx_menu_open = ui_state->next_ctx_menu_open; + ui_state->ctx_menu_anchor_key = ui_state->next_ctx_menu_anchor_key; + { + UI_Box *anchor_box = ui_box_from_key(ui_state->ctx_menu_anchor_key); + if(!ui_box_is_nil(anchor_box)) + { + ui_state->ctx_menu_anchor_box_last_pos = anchor_box->rect.p0; + } + Vec2F32 anchor = add_2f32(ui_state->ctx_menu_anchor_box_last_pos, ui_state->ctx_menu_anchor_off); + UI_FixedX(anchor.x) UI_FixedY(anchor.y) UI_PrefWidth(ui_children_sum(1.f)) UI_PrefHeight(ui_children_sum(1.f)) + UI_Focus(UI_FocusKind_On) + UI_Squish(0.25f-ui_state->ctx_menu_open_t*0.25f) + UI_Transparency(1-ui_state->ctx_menu_open_t) + { + ui_set_next_child_layout_axis(Axis2_Y); + ui_state->ctx_menu_root = ui_build_box_from_stringf(UI_BoxFlag_Clickable|UI_BoxFlag_DrawDropShadow|(ui_state->ctx_menu_open*UI_BoxFlag_DefaultFocusNavY), "###ctx_menu_%I64x", window.u64[0]); + } + } + + //- rjf: reset hot if we don't have an active widget + { + B32 has_active = 0; + for(EachEnumVal(UI_MouseButtonKind, k)) + { + if(!ui_key_match(ui_state->active_box_key[k], ui_key_zero())) + { + has_active = 1; + } + } + if(!has_active) + { + ui_state->hot_box_key = ui_key_zero(); + } + } + + //- rjf: reset drop-hot key + { + ui_state->drop_hot_box_key = ui_key_zero(); + } + + //- rjf: reset active if our active box is disabled + for(EachEnumVal(UI_MouseButtonKind, k)) + { + if(!ui_key_match(ui_state->active_box_key[k], ui_key_zero())) + { + UI_Box *box = ui_box_from_key(ui_state->active_box_key[k]); + if(!ui_box_is_nil(box) && box->flags & UI_BoxFlag_Disabled) + { + ui_state->active_box_key[k] = ui_key_zero(); + } + } + } + + //- rjf: reset active keys if they have been pruned + for(EachEnumVal(UI_MouseButtonKind, k)) + { + UI_Box *box = ui_box_from_key(ui_state->active_box_key[k]); + if(ui_box_is_nil(box)) + { + ui_state->active_box_key[k] = ui_key_zero(); + } + } +} + +internal void +ui_end_build(void) +{ + ProfBeginFunction(); + + //- rjf: escape -> close context menu + if(ui_state->ctx_menu_open != 0 && ui_slot_press(UI_EventActionSlot_Cancel)) + { + ui_ctx_menu_close(); + } + + //- rjf: prune untouched or transient widgets in the cache + { + ProfBegin("ui prune unused widgets"); + for(U64 slot_idx = 0; slot_idx < ui_state->box_table_size; slot_idx += 1) + { + for(UI_Box *box = ui_state->box_table[slot_idx].hash_first, *next = 0; + !ui_box_is_nil(box); + box = next) + { + next = box->hash_next; + if(box->last_touched_build_index < ui_state->build_index || + ui_key_match(box->key, ui_key_zero())) + { + DLLRemove_NPZ(&ui_g_nil_box, ui_state->box_table[slot_idx].hash_first, ui_state->box_table[slot_idx].hash_last, box, hash_next, hash_prev); + SLLStackPush(ui_state->first_free_box, box); + } + } + } + ProfEnd(); + } + + //- rjf: layout box tree + { + ProfBegin("ui box tree layout"); + for(Axis2 axis = (Axis2)0; axis < Axis2_COUNT; axis = (Axis2)(axis + 1)) + { + ui_layout_root(ui_state->root, axis); + } + ProfEnd(); + } + + //- rjf: close ctx menu if untouched + if(!ui_state->ctx_menu_touched_this_frame) + { + ui_ctx_menu_close(); + } + + //- rjf: stick ctx menu to anchor + if(ui_state->ctx_menu_touched_this_frame && !ui_state->ctx_menu_changed) + { + UI_Box *anchor_box = ui_box_from_key(ui_state->ctx_menu_anchor_key); + if(!ui_box_is_nil(anchor_box)) + { + Rng2F32 root_rect = ui_state->ctx_menu_root->rect; + Vec2F32 pos = + { + anchor_box->rect.x0 + ui_state->ctx_menu_anchor_off.x, + anchor_box->rect.y0 + ui_state->ctx_menu_anchor_off.y, + }; + Vec2F32 shift = sub_2f32(pos, root_rect.p0); + Rng2F32 new_root_rect = shift_2f32(root_rect, shift); + ui_state->ctx_menu_root->fixed_position = new_root_rect.p0; + ui_state->ctx_menu_root->fixed_size = dim_2f32(new_root_rect); + ui_state->ctx_menu_root->rect = new_root_rect; + } + } + + //- rjf: ensure special floating roots are within screen bounds + UI_Box *floating_roots[] = {ui_state->tooltip_root, ui_state->ctx_menu_root}; + B32 force_contain[] = + { + (ui_key_match(ui_active_key(UI_MouseButtonKind_Left), ui_key_zero()) && + ui_key_match(ui_active_key(UI_MouseButtonKind_Right), ui_key_zero()) && + ui_key_match(ui_active_key(UI_MouseButtonKind_Middle), ui_key_zero())), + 1, + }; + for(U64 idx = 0; idx < ArrayCount(floating_roots); idx += 1) + { + UI_Box *root = floating_roots[idx]; + if(!ui_box_is_nil(root)) + { + Rng2F32 window_rect = os_client_rect_from_window(ui_window()); + Vec2F32 window_dim = dim_2f32(window_rect); + Rng2F32 root_rect = root->rect; + Vec2F32 shift = + { + -ClampBot(0, root_rect.x1 - window_rect.x1) * (force_contain[idx]), + -ClampBot(0, root_rect.y1 - window_rect.y1) * (force_contain[idx]), + }; + Rng2F32 new_root_rect = shift_2f32(root_rect, shift); + root->fixed_position = new_root_rect.p0; + root->fixed_size = dim_2f32(new_root_rect); + root->rect = new_root_rect; + for(Axis2 axis = (Axis2)0; axis < Axis2_COUNT; axis = (Axis2)(axis + 1)) + { + ui_calc_sizes_standalone__in_place_rec(root, axis); + ui_calc_sizes_upwards_dependent__in_place_rec(root, axis); + ui_calc_sizes_downwards_dependent__in_place_rec(root, axis); + ui_layout_enforce_constraints__in_place_rec(root, axis); + ui_layout_position__in_place_rec(root, axis); + } + } + } + + //- rjf: enforce child-rounding + { + for(U64 slot_idx = 0; slot_idx < ui_state->box_table_size; slot_idx += 1) + { + for(UI_Box *box = ui_state->box_table[slot_idx].hash_first; + !ui_box_is_nil(box); + box = box->hash_next) + { + if(box->flags & UI_BoxFlag_RoundChildrenByParent && + !ui_box_is_nil(box->first) && !ui_box_is_nil(box->last)) + { + box->first->corner_radii[Corner_00] = box->corner_radii[Corner_00]; + box->first->corner_radii[Corner_10] = box->corner_radii[Corner_10]; + box->last->corner_radii[Corner_01] = box->corner_radii[Corner_01]; + box->last->corner_radii[Corner_11] = box->corner_radii[Corner_11]; + } + } + } + } + + //- rjf: animate + { + ProfBegin("ui animate"); + F32 vast_rate = 1 - pow_f32(2, (-60.f * ui_state->animation_dt)); + F32 fast_rate = 1 - pow_f32(2, (-50.f * ui_state->animation_dt)); + F32 fish_rate = 1 - pow_f32(2, (-40.f * ui_state->animation_dt)); + F32 slow_rate = 1 - pow_f32(2, (-30.f * ui_state->animation_dt)); + F32 slug_rate = 1 - pow_f32(2, (-15.f * ui_state->animation_dt)); + F32 slaf_rate = 1 - pow_f32(2, (-8.f * ui_state->animation_dt)); + ui_state->ctx_menu_open_t += ((F32)!!ui_state->ctx_menu_open - ui_state->ctx_menu_open_t) * (ui_state->animation_info.flags & UI_AnimationInfoFlag_ContextMenuAnimations ? vast_rate : 1); + ui_state->is_animating = (ui_state->is_animating || abs_f32((F32)!!ui_state->ctx_menu_open - ui_state->ctx_menu_open_t) > 0.01f); + if(ui_state->ctx_menu_open_t >= 0.99f && ui_state->ctx_menu_open) + { + ui_state->ctx_menu_open_t = 1.f; + } + ui_state->tooltip_open_t += ((F32)!!ui_state->tooltip_open - ui_state->tooltip_open_t) * (ui_state->animation_info.flags & UI_AnimationInfoFlag_TooltipAnimations ? vast_rate : 1); + ui_state->is_animating = (ui_state->is_animating || abs_f32((F32)!!ui_state->tooltip_open - ui_state->tooltip_open_t) > 0.01f); + if(ui_state->tooltip_open_t >= 0.99f && ui_state->tooltip_open) + { + ui_state->tooltip_open_t = 1.f; + } + for(U64 slot_idx = 0; slot_idx < ui_state->box_table_size; slot_idx += 1) + { + for(UI_Box *box = ui_state->box_table[slot_idx].hash_first; + !ui_box_is_nil(box); + box = box->hash_next) + { + // rjf: grab states informing animation + B32 is_hot = ui_key_match(box->key, ui_state->hot_box_key); + B32 is_active = ui_key_match(box->key, ui_state->active_box_key[UI_MouseButtonKind_Left]); + B32 is_disabled = !!(box->flags & UI_BoxFlag_Disabled) && (box->first_disabled_build_index+10 < ui_state->build_index || + box->first_touched_build_index == box->first_disabled_build_index); + B32 is_focus_hot = !!(box->flags & UI_BoxFlag_FocusHot) && !(box->flags & UI_BoxFlag_FocusHotDisabled); + B32 is_focus_active = !!(box->flags & UI_BoxFlag_FocusActive) && !(box->flags & UI_BoxFlag_FocusActiveDisabled); + B32 is_focus_active_disabled = !!(box->flags & UI_BoxFlag_FocusActiveDisabled); + + // rjf: determine rates + F32 hot_rate = (ui_state->animation_info.flags & UI_AnimationInfoFlag_HotAnimations ? fast_rate : 1); + F32 active_rate = (ui_state->animation_info.flags & UI_AnimationInfoFlag_ActiveAnimations ? fast_rate : 1); + F32 disabled_rate = (ui_state->animation_info.flags & UI_AnimationInfoFlag_HotAnimations ? slow_rate : 1); + F32 focus_rate = (ui_state->animation_info.flags & UI_AnimationInfoFlag_FocusAnimations ? fast_rate : 1); + + // rjf: determine animating status + B32 box_is_animating = 0; + box_is_animating = (box_is_animating || abs_f32((F32)is_hot - box->hot_t) > 0.01f); + box_is_animating = (box_is_animating || abs_f32((F32)is_active - box->active_t) > 0.01f); + box_is_animating = (box_is_animating || abs_f32((F32)is_disabled - box->disabled_t) > 0.01f); + box_is_animating = (box_is_animating || abs_f32((F32)is_focus_hot - box->focus_hot_t) > 0.01f); + box_is_animating = (box_is_animating || abs_f32((F32)is_focus_active - box->focus_active_t) > 0.01f); + box_is_animating = (box_is_animating || abs_f32((F32)is_focus_active_disabled - box->focus_active_disabled_t) > 0.01f); + box_is_animating = (box_is_animating || abs_f32(box->view_off_target.x - box->view_off.x) > 0.5f); + box_is_animating = (box_is_animating || abs_f32(box->view_off_target.y - box->view_off.y) > 0.5f); + if(box->flags & UI_BoxFlag_AnimatePosX) + { + box_is_animating = (box_is_animating || abs_f32(box->fixed_position_animated.x - box->fixed_position.x) > 0.5f); + } + if(box->flags & UI_BoxFlag_AnimatePosY) + { + box_is_animating = (box_is_animating || abs_f32(box->fixed_position_animated.y - box->fixed_position.y) > 0.5f); + } + ui_state->is_animating = (ui_state->is_animating || box_is_animating); +#if 0 // NOTE(rjf): enable to debug animation-causing-frames (or not) + if(box_is_animating) + { + box->overlay_color = v4f32(1, 0, 0, 0.1f); + box->flags |= UI_BoxFlag_DrawOverlay; + } +#endif + + // rjf: animate interaction transition states + box->hot_t += hot_rate * ((F32)is_hot - box->hot_t); + box->active_t += active_rate * ((F32)is_active - box->active_t); + box->disabled_t += disabled_rate * ((F32)is_disabled - box->disabled_t); + box->focus_hot_t += focus_rate * ((F32)is_focus_hot - box->focus_hot_t); + box->focus_active_t += focus_rate * ((F32)is_focus_active - box->focus_active_t); + box->focus_active_disabled_t += focus_rate * ((F32)is_focus_active_disabled - box->focus_active_disabled_t); + + // rjf: animate positions + { + box->fixed_position_animated.x += fast_rate * (box->fixed_position.x - box->fixed_position_animated.x); + box->fixed_position_animated.y += fast_rate * (box->fixed_position.y - box->fixed_position_animated.y); + if(abs_f32(box->fixed_position.x - box->fixed_position_animated.x) < 1) + { + box->fixed_position_animated.x = box->fixed_position.x; + } + if(abs_f32(box->fixed_position.y - box->fixed_position_animated.y) < 1) + { + box->fixed_position_animated.y = box->fixed_position.y; + } + } + + // rjf: clamp view + if(box->flags & UI_BoxFlag_ViewClamp) + { + Vec2F32 max_view_off_target = + { + ClampBot(0, box->view_bounds.x - box->fixed_size.x), + ClampBot(0, box->view_bounds.y - box->fixed_size.y), + }; + if(box->flags & UI_BoxFlag_ViewClampX) { box->view_off_target.x = Clamp(0, box->view_off_target.x, max_view_off_target.x); } + if(box->flags & UI_BoxFlag_ViewClampY) { box->view_off_target.y = Clamp(0, box->view_off_target.y, max_view_off_target.y); } + } + + // rjf: animate view offset + { + box->view_off.x += fast_rate * (box->view_off_target.x - box->view_off.x); + box->view_off.y += fast_rate * (box->view_off_target.y - box->view_off.y); + if(abs_f32(box->view_off.x - box->view_off_target.x) < 2) + { + box->view_off.x = box->view_off_target.x; + } + if(abs_f32(box->view_off.y - box->view_off_target.y) < 2) + { + box->view_off.y = box->view_off_target.y; + } + } + } + } + ProfEnd(); + } + + //- rjf: animate context menu + if(ui_state->ctx_menu_open && !ui_box_is_nil(ui_state->ctx_menu_root) && !ui_state->ctx_menu_changed) + { + UI_Box *root = ui_state->ctx_menu_root; + Rng2F32 rect = root->rect; + root->rect.y1 = root->rect.y0 + dim_2f32(rect).y * ui_state->ctx_menu_open_t; + } + + //- rjf: fall-through interact with context menu + if(ui_state->ctx_menu_open) + { + ui_signal_from_box(ui_state->ctx_menu_root); + } + + //- rjf: close ctx menu if unconsumed clicks + { + UI_EventList *events = ui_events(); + for(UI_EventNode *n = events->first; n != 0; n = n->next) + { + UI_Event *event = &n->v; + if(event->kind == UI_EventKind_Press && + (event->key == OS_Key_LeftMouseButton || event->key == OS_Key_RightMouseButton)) + { + ui_ctx_menu_close(); + } + } + } + + //- rjf: hover cursor + { + UI_Box *hot = ui_box_from_key(ui_state->hot_box_key); + UI_Box *active = ui_box_from_key(ui_state->active_box_key[UI_MouseButtonKind_Left]); + UI_Box *box = ui_box_is_nil(active) ? hot : active; + OS_Cursor cursor = box->hover_cursor; + if(box->flags & UI_BoxFlag_Disabled && box->flags & UI_BoxFlag_Clickable) + { + cursor = OS_Cursor_Disabled; + } + if(os_window_is_focused(ui_state->window) || !ui_box_is_nil(active)) + { + os_set_cursor(cursor); + } + } + + //- rjf: clipboard commits + { + UI_Box *box = ui_box_from_key(ui_state->clipboard_copy_key); + if(!ui_box_is_nil(box)) + { + Temp scratch = scratch_begin(0, 0); + String8List strs = {0}; + UI_BoxRec rec = {0}; + for(UI_Box *b = box; !ui_box_is_nil(b); rec = ui_box_rec_df_pre(b, box), b = rec.next) + { + if(b->flags & UI_BoxFlag_DrawText && b->flags & UI_BoxFlag_HasDisplayString && !f_tag_match(b->font, ui_icon_font())) + { + String8 display_string = ui_box_display_string(b); + str8_list_push(scratch.arena, &strs, display_string); + } + } + if(strs.node_count != 0) + { + StringJoin join = {0}; + join.sep = str8_lit(" "); + String8 string = str8_list_join(scratch.arena, &strs, &join); + os_set_clipboard_text(string); + } + scratch_end(scratch); + } + } + + //- rjf: hovering possibly-truncated drawn text -> store text + { + B32 inactive = 1; + for(EachEnumVal(UI_MouseButtonKind, k)) + { + if(!ui_key_match(ui_key_zero(), ui_state->active_box_key[k])) + { + inactive = 0; + break; + } + } + if(inactive) + { + B32 found = 0; + for(UI_Box *box = ui_state->root, *next = 0; !ui_box_is_nil(box); box = next) + { + UI_BoxRec rec = ui_box_rec_df_pre(box, ui_state->root); + next = rec.next; + S32 pop_idx = 0; + for(UI_Box *b = box; !ui_box_is_nil(b) && pop_idx <= rec.pop_count; b = b->parent, pop_idx += 1) + { + if(b->flags & UI_BoxFlag_DrawText && !(b->flags & UI_BoxFlag_DisableTextTrunc)) + { + Rng2F32 rect = b->rect; + for(UI_Box *p = b->parent; !ui_box_is_nil(p); p = p->parent) + { + if(p->flags & UI_BoxFlag_Clip) + { + rect = intersect_2f32(rect, p->rect); + } + } + String8 box_display_string = ui_box_display_string(b); + Vec2F32 text_pos = ui_box_text_position(b); + Vec2F32 drawn_text_dim = b->display_string_runs.dim; + B32 text_is_truncated = (drawn_text_dim.x + text_pos.x > rect.x1); + B32 mouse_is_hovering = contains_2f32(r2f32p(text_pos.x, + rect.y0, + Min(text_pos.x+drawn_text_dim.x, rect.x1), + rect.y1), + ui_state->mouse); + if(text_is_truncated && mouse_is_hovering && !(b->flags & UI_BoxFlag_DisableTruncatedHover)) + { + if(!str8_match(box_display_string, ui_state->string_hover_string, 0)) + { + arena_clear(ui_state->string_hover_arena); + ui_state->string_hover_string = push_str8_copy(ui_state->string_hover_arena, box_display_string); + ui_state->string_hover_fancy_runs = d_fancy_run_list_copy(ui_state->string_hover_arena, &b->display_string_runs); + ui_state->string_hover_begin_us = os_now_microseconds(); + } + ui_state->string_hover_build_index = ui_state->build_index; + found = 1; + goto break_all_hover_string; + } + } + if(b != box && ui_key_match(b->key, ui_hot_key())) + { + goto break_all_hover_string; + } + if(b != box && contains_2f32(b->rect, ui_state->mouse) && b->flags & UI_BoxFlag_DrawText) + { + goto break_all_hover_string; + } + } + } + break_all_hover_string:; + if(!found) + { + arena_clear(ui_state->string_hover_arena); + ui_state->string_hover_build_index = 0; + MemoryZeroStruct(&ui_state->string_hover_string); + } + if(found && !ui_string_hover_active()) + { + ui_state->is_animating = 1; + } + } + } + + ui_state->build_index += 1; + arena_clear(ui_build_arena()); + ProfEnd(); +} + +internal void +ui_calc_sizes_standalone__in_place_rec(UI_Box *root, Axis2 axis) +{ + ProfBeginFunction(); + + switch(root->pref_size[axis].kind) + { + default:{}break; + case UI_SizeKind_Pixels: + { + root->fixed_size.v[axis] = root->pref_size[axis].value; + }break; + + case UI_SizeKind_TextContent: + { + F32 padding = root->pref_size[axis].value; + F32 text_size = root->display_string_runs.dim.x; + root->fixed_size.v[axis] = padding + text_size + root->text_padding*2; + }break; + } + + //- rjf: recurse + for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) + { + ui_calc_sizes_standalone__in_place_rec(child, axis); + } + + ProfEnd(); +} + +internal void +ui_calc_sizes_upwards_dependent__in_place_rec(UI_Box *root, Axis2 axis) +{ + ProfBeginFunction(); + + //- rjf: solve for all kinds that are upwards-dependent + switch(root->pref_size[axis].kind) + { + default: break; + + // rjf: if root has a parent percentage, figure out its size + case UI_SizeKind_ParentPct: + { + // rjf: find parent that has a fixed size + UI_Box *fixed_parent = &ui_g_nil_box; + for(UI_Box *p = root->parent; !ui_box_is_nil(p); p = p->parent) + { + if(p->flags & (UI_BoxFlag_FixedWidth<pref_size[axis].kind == UI_SizeKind_Pixels || + p->pref_size[axis].kind == UI_SizeKind_TextContent || + p->pref_size[axis].kind == UI_SizeKind_ParentPct) + { + fixed_parent = p; + break; + } + } + + // rjf: figure out root's size on this axis + F32 size = fixed_parent->fixed_size.v[axis] * root->pref_size[axis].value; + + // rjf: mutate root to have this size + root->fixed_size.v[axis] = size; + }break; + } + + //- rjf: recurse + for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) + { + ui_calc_sizes_upwards_dependent__in_place_rec(child, axis); + } + + ProfEnd(); +} + +internal void +ui_calc_sizes_downwards_dependent__in_place_rec(UI_Box *root, Axis2 axis) +{ + ProfBeginFunction(); + + //- rjf: recurse first. we may depend on children that have + // the same property + for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) + { + ui_calc_sizes_downwards_dependent__in_place_rec(child, axis); + } + + //- rjf: solve for all kinds that are downwards-dependent + switch(root->pref_size[axis].kind) + { + default: break; + + // rjf: sum children + case UI_SizeKind_ChildrenSum: + { + F32 sum = 0; + for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) + { + if(!(child->flags & (UI_BoxFlag_FloatingX<child_layout_axis) + { + sum += child->fixed_size.v[axis]; + } + else + { + sum = Max(sum, child->fixed_size.v[axis]); + } + } + } + + // rjf: figure out root's size on this axis + root->fixed_size.v[axis] = sum; + }break; + } + + ProfEnd(); +} + +internal void +ui_layout_enforce_constraints__in_place_rec(UI_Box *root, Axis2 axis) +{ + ProfBeginFunction(); + Temp scratch = scratch_begin(0, 0); + + // NOTE(rjf): The "layout axis" is the direction in which children + // of some node are intended to be laid out. + + //- rjf: fixup children sizes (if we're solving along the *non-layout* axis) + if(axis != root->child_layout_axis && !(root->flags & (UI_BoxFlag_AllowOverflowX << axis))) + { + F32 allowed_size = root->fixed_size.v[axis]; + for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) + { + if(!(child->flags & (UI_BoxFlag_FloatingX<fixed_size.v[axis]; + F32 violation = child_size - allowed_size; + F32 max_fixup = child_size; + F32 fixup = Clamp(0, violation, max_fixup); + if(fixup > 0) + { + child->fixed_size.v[axis] -= fixup; + } + } + } + + } + + //- rjf: fixup children sizes (in the direction of the layout axis) + if(axis == root->child_layout_axis && !(root->flags & (UI_BoxFlag_AllowOverflowX << axis))) + { + // rjf: figure out total allowed size & total size + F32 total_allowed_size = root->fixed_size.v[axis]; + F32 total_size = 0; + F32 total_weighted_size = 0; + for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) + { + if(!(child->flags & (UI_BoxFlag_FloatingX<fixed_size.v[axis]; + total_weighted_size += child->fixed_size.v[axis] * (1-child->pref_size[axis].strictness); + } + } + + // rjf: if we have a violation, we need to subtract some amount from all children + F32 violation = total_size - total_allowed_size; + if(violation > 0) + { + // rjf: figure out how much we can take in totality + F32 child_fixup_sum = 0; + F32 *child_fixups = push_array(scratch.arena, F32, root->child_count); + { + U64 child_idx = 0; + for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next, child_idx += 1) + { + if(!(child->flags & (UI_BoxFlag_FloatingX<fixed_size.v[axis] * (1-child->pref_size[axis].strictness); + fixup_size_this_child = ClampBot(0, fixup_size_this_child); + child_fixups[child_idx] = fixup_size_this_child; + child_fixup_sum += fixup_size_this_child; + } + } + } + + // rjf: fixup child sizes + { + U64 child_idx = 0; + for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next, child_idx += 1) + { + if(!(child->flags & (UI_BoxFlag_FloatingX<fixed_size.v[axis] -= child_fixups[child_idx] * fixup_pct; + } + } + } + } + + } + + //- rjf: fixup upwards-relative sizes + if(root->flags & (UI_BoxFlag_AllowOverflowX << axis)) + { + for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) + { + if(child->pref_size[axis].kind == UI_SizeKind_ParentPct) + { + child->fixed_size.v[axis] = root->fixed_size.v[axis] * child->pref_size[axis].value; + } + } + } + + //- rjf: recurse + for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) + { + ui_layout_enforce_constraints__in_place_rec(child, axis); + } + + scratch_end(scratch); + ProfEnd(); +} + +internal void +ui_layout_position__in_place_rec(UI_Box *root, Axis2 axis) +{ + ProfBeginFunction(); + F32 layout_position = 0; + + //- rjf: lay out children + F32 bounds = 0; + for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) + { + // rjf: grab original position + F32 original_position = Min(child->rect.p0.v[axis], child->rect.p1.v[axis]); + + // rjf: calculate fixed position & size + if(!(child->flags & (UI_BoxFlag_FloatingX<fixed_position.v[axis] = layout_position; + if(root->child_layout_axis == axis) + { + layout_position += child->fixed_size.v[axis]; + bounds += child->fixed_size.v[axis]; + } + else + { + bounds = Max(bounds, child->fixed_size.v[axis]); + } + } + + // rjf: determine final rect for child, given fixed_position & size + if(child->flags & (UI_BoxFlag_AnimatePosX<first_touched_build_index == child->last_touched_build_index) + { + child->fixed_position_animated = child->fixed_position; + } + child->rect.p0.v[axis] = root->rect.p0.v[axis] + child->fixed_position_animated.v[axis] - !(child->flags&(UI_BoxFlag_SkipViewOffX<view_off.v[axis]); + } + else + { + child->rect.p0.v[axis] = root->rect.p0.v[axis] + child->fixed_position.v[axis] - !(child->flags&(UI_BoxFlag_SkipViewOffX<view_off.v[axis]); + } + child->rect.p1.v[axis] = child->rect.p0.v[axis] + child->fixed_size.v[axis]; + child->rect.p0.x = floor_f32(child->rect.p0.x); + child->rect.p0.y = floor_f32(child->rect.p0.y); + child->rect.p1.x = floor_f32(child->rect.p1.x); + child->rect.p1.y = floor_f32(child->rect.p1.y); + + // rjf: grab new position + F32 new_position = Min(child->rect.p0.v[axis], child->rect.p1.v[axis]); + + // rjf: store position delta + child->position_delta.v[axis] = new_position - original_position; + } + + //- rjf: store view bounds + { + root->view_bounds.v[axis] = bounds; + } + + //- rjf: recurse + for(UI_Box *child = root->first; !ui_box_is_nil(child); child = child->next) + { + ui_layout_position__in_place_rec(child, axis); + } + + ProfEnd(); +} + +internal void +ui_layout_root(UI_Box *root, Axis2 axis) +{ + ProfBegin("ui layout pass (%s)", axis == Axis2_X ? "x" : "y"); + ui_calc_sizes_standalone__in_place_rec(root, axis); + ui_calc_sizes_upwards_dependent__in_place_rec(root, axis); + ui_calc_sizes_downwards_dependent__in_place_rec(root, axis); + ui_layout_enforce_constraints__in_place_rec(root, axis); + ui_layout_position__in_place_rec(root, axis); + ProfEnd(); +} + +//////////////////////////////// +//~ rjf: Box Building API + +//- rjf: spacers + +internal UI_Signal +ui_spacer(UI_Size size) +{ + UI_Box *parent = ui_top_parent(); + ui_set_next_pref_size(parent->child_layout_axis, size); + UI_Box *box = ui_build_box_from_key(0, ui_key_zero()); + UI_Signal interact = ui_signal_from_box(box); + return interact; +} + +//- rjf: tooltips + +internal void +ui_tooltip_begin_base(void) +{ + ui_state->tooltip_open = 1; + ui_push_parent(ui_root_from_state(ui_state)); + ui_push_parent(ui_state->tooltip_root); + ui_push_flags(0); + ui_push_text_raster_flags(ui_bottom_text_raster_flags()); + ui_push_palette(ui_bottom_palette()); +} + +internal void +ui_tooltip_end_base(void) +{ + ui_pop_palette(); + ui_pop_text_raster_flags(); + ui_pop_flags(); + ui_pop_parent(); + ui_pop_parent(); +} + +internal void +ui_tooltip_begin(void) +{ + ui_tooltip_begin_base(); + ui_push_palette(ui_state->widget_palette_info.tooltip_palette); + ui_set_next_squish(0.25f-ui_state->tooltip_open_t*0.25f); + ui_set_next_transparency(1-ui_state->tooltip_open_t); + UI_Flags(UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBackgroundBlur|UI_BoxFlag_DrawDropShadow) + UI_PrefWidth(ui_children_sum(1)) + UI_PrefHeight(ui_children_sum(1)) + UI_CornerRadius(ui_top_font_size()*0.25f) + ui_column_begin(); + UI_PrefWidth(ui_px(0, 1)) ui_spacer(ui_em(1.f, 1.f)); + UI_PrefWidth(ui_children_sum(1)) + UI_PrefHeight(ui_children_sum(1)) + ui_row_begin(); + UI_PrefHeight(ui_px(0, 1)) ui_spacer(ui_em(1.f, 1.f)); + UI_PrefWidth(ui_children_sum(1)) + UI_PrefHeight(ui_children_sum(1)) + ui_column_begin(); + ui_push_pref_width(ui_text_dim(10.f, 1.f)); + ui_push_pref_height(ui_em(2.f, 1.f)); + ui_push_text_alignment(UI_TextAlign_Center); +} + +internal void +ui_tooltip_end(void) +{ + ui_pop_text_alignment(); + ui_pop_pref_width(); + ui_pop_pref_height(); + ui_column_end(); + UI_PrefHeight(ui_px(0, 1)) ui_spacer(ui_em(1.f, 1.f)); + ui_row_end(); + UI_PrefWidth(ui_px(0, 1)) ui_spacer(ui_em(1.f, 1.f)); + ui_column_end(); + ui_pop_palette(); + ui_tooltip_end_base(); +} + +//- rjf: context menus + +internal void +ui_ctx_menu_open(UI_Key key, UI_Key anchor_box_key, Vec2F32 anchor_off) +{ + anchor_off.x = (F32)(int)anchor_off.x; + anchor_off.y = (F32)(int)anchor_off.y; + ui_state->next_ctx_menu_open = 1; + ui_state->ctx_menu_changed = 1; + ui_state->ctx_menu_open_t = 0; + ui_state->ctx_menu_key = key; + ui_state->next_ctx_menu_anchor_key = anchor_box_key; + ui_state->ctx_menu_anchor_off = anchor_off; + ui_state->ctx_menu_touched_this_frame = 1; + ui_state->ctx_menu_anchor_box_last_pos = v2f32(0, 0); + ui_state->ctx_menu_root->default_nav_focus_active_key = ui_key_zero(); + ui_state->ctx_menu_root->default_nav_focus_next_active_key = ui_key_zero(); +} + +internal void +ui_ctx_menu_close(void) +{ + ui_state->next_ctx_menu_open = 0; +} + +internal B32 +ui_begin_ctx_menu(UI_Key key) +{ + ui_push_parent(ui_root_from_state(ui_state)); + ui_push_parent(ui_state->ctx_menu_root); + ui_push_pref_width(ui_bottom_pref_width()); + ui_push_pref_height(ui_bottom_pref_height()); + ui_push_focus_hot(UI_FocusKind_Root); + ui_push_focus_active(UI_FocusKind_Root); + ui_push_palette(ui_state->widget_palette_info.ctx_menu_palette); + B32 is_open = ui_key_match(key, ui_state->ctx_menu_key) && ui_state->ctx_menu_open; + if(is_open != 0) + { + ui_state->ctx_menu_touched_this_frame = 1; + ui_state->ctx_menu_root->flags |= UI_BoxFlag_RoundChildrenByParent; + ui_state->ctx_menu_root->flags |= UI_BoxFlag_DrawBackgroundBlur; + ui_state->ctx_menu_root->flags |= UI_BoxFlag_DrawBackground; + ui_state->ctx_menu_root->flags |= UI_BoxFlag_DisableFocusOverlay; + ui_state->ctx_menu_root->flags |= UI_BoxFlag_DrawBorder; + ui_state->ctx_menu_root->flags |= UI_BoxFlag_Clip; + ui_state->ctx_menu_root->flags |= UI_BoxFlag_Clickable; + ui_state->ctx_menu_root->corner_radii[Corner_00] = ui_state->ctx_menu_root->corner_radii[Corner_01] = ui_state->ctx_menu_root->corner_radii[Corner_10] = ui_state->ctx_menu_root->corner_radii[Corner_11] = ui_top_font_size()*0.25f; + ui_state->ctx_menu_root->palette = ui_top_palette(); + ui_state->ctx_menu_root->blur_size = ui_top_blur_size(); + ui_spacer(ui_em(1.f, 1.f)); + } + ui_state->is_in_open_ctx_menu = is_open; + return is_open; +} + +internal void +ui_end_ctx_menu(void) +{ + if(ui_state->is_in_open_ctx_menu) + { + ui_state->is_in_open_ctx_menu = 0; + ui_spacer(ui_em(1.f, 1.f)); + } + ui_pop_palette(); + ui_pop_focus_active(); + ui_pop_focus_hot(); + ui_pop_pref_width(); + ui_pop_pref_height(); + ui_pop_parent(); + ui_pop_parent(); +} + +internal B32 +ui_ctx_menu_is_open(UI_Key key) +{ + return (ui_state->ctx_menu_open && ui_key_match(key, ui_state->ctx_menu_key)); +} + +internal B32 +ui_any_ctx_menu_is_open(void) +{ + return ui_state->ctx_menu_open; +} + +//- rjf: focus tree coloring + +internal B32 +ui_is_focus_hot(void) +{ + B32 result = (ui_state->focus_hot_stack.top->v == UI_FocusKind_On); + if(result) + { + for(UI_FocusHotNode *n = ui_state->focus_hot_stack.top; n != 0; n = n->next) + { + if(n->v == UI_FocusKind_Root) + { + break; + } + if(n->v == UI_FocusKind_Off) + { + result = 0; + break; + } + } + } + return result; +} + +internal B32 +ui_is_focus_active(void) +{ + B32 result = (ui_state->focus_active_stack.top->v == UI_FocusKind_On); + if(result) + { + for(UI_FocusActiveNode *n = ui_state->focus_active_stack.top; n != 0; n = n->next) + { + if(n->v == UI_FocusKind_Root) + { + break; + } + if(n->v == UI_FocusKind_Off) + { + result = 0; + break; + } + } + } + return result; +} + +//- rjf: implicit auto-managed tree-based focus state + +internal B32 +ui_is_key_auto_focus_active(UI_Key key) +{ + B32 result = 0; + if(!ui_key_match(ui_key_zero(), key)) + { + for(UI_Box *p = ui_top_parent(); !ui_box_is_nil(p); p = p->parent) + { + if(p->flags & UI_BoxFlag_FocusActive && ui_key_match(key, p->default_nav_focus_active_key)) + { + result = 1; + break; + } + } + } + return result; +} + +internal B32 +ui_is_key_auto_focus_hot(UI_Key key) +{ + B32 result = 0; + if(!ui_key_match(ui_key_zero(), key)) + { + for(UI_Box *p = ui_top_parent(); !ui_box_is_nil(p); p = p->parent) + { + if(p->flags & UI_BoxFlag_FocusHot && + ((!(p->flags & UI_BoxFlag_FocusHotDisabled) && + ui_key_match(key, p->default_nav_focus_hot_key)) || + ui_key_match(key, p->default_nav_focus_active_key))) + { + result = 1; + break; + } + } + } + return result; +} + +internal void +ui_set_auto_focus_active_key(UI_Key key) +{ + for(UI_Box *p = ui_top_parent(); !ui_box_is_nil(p); p = p->parent) + { + if(p->flags & UI_BoxFlag_DefaultFocusNav) + { + p->default_nav_focus_next_active_key = key; + break; + } + } +} + +internal void +ui_set_auto_focus_hot_key(UI_Key key) +{ + for(UI_Box *p = ui_top_parent(); !ui_box_is_nil(p); p = p->parent) + { + if(p->flags & UI_BoxFlag_DefaultFocusNav) + { + p->default_nav_focus_next_hot_key = key; + break; + } + } +} + +//- rjf: palette forming + +internal UI_Palette * +ui_build_palette_(UI_Palette *base, UI_Palette *overrides) +{ + UI_Palette *palette = push_array(ui_build_arena(), UI_Palette, 1); + if(base != 0) + { + MemoryCopyStruct(palette, base); + } + for(EachEnumVal(UI_ColorCode, code)) + { + if(overrides->colors[code].x != 0 || + overrides->colors[code].y != 0 || + overrides->colors[code].z != 0 || + overrides->colors[code].w != 0) + { + palette->colors[code] = overrides->colors[code]; + } + } + return palette; +} + +//- rjf: box node construction + +internal UI_Box * +ui_build_box_from_key(UI_BoxFlags flags, UI_Key key) +{ + ProfBeginFunction(); + ui_state->build_box_count += 1; + + //- rjf: grab active parent + UI_Box *parent = ui_top_parent(); + + //- rjf: try to get box + UI_BoxFlags last_flags = 0; + UI_Box *box = ui_box_from_key(key); + B32 box_first_frame = ui_box_is_nil(box); + last_flags = box->flags; + + //- rjf: zero key on duplicate + if(!box_first_frame && box->last_touched_build_index == ui_state->build_index) + { + box = &ui_g_nil_box; + key = ui_key_zero(); + box_first_frame = 1; + } + + //- rjf: gather info from box + B32 box_is_transient = ui_key_match(key, ui_key_zero()); + + //- rjf: allocate box if it doesn't yet exist + if(box_first_frame) + { + box = !box_is_transient ? ui_state->first_free_box : 0; + ui_state->is_animating = ui_state->is_animating || !box_is_transient; + if(!ui_box_is_nil(box)) + { + SLLStackPop(ui_state->first_free_box); + } + else + { + box = push_array_no_zero(box_is_transient ? ui_build_arena() : ui_state->arena, UI_Box, 1); + } + MemoryZeroStruct(box); + } + + //- rjf: zero out per-frame state + { + box->first = box->last = box->next = box->prev = box->parent = &ui_g_nil_box; + box->child_count = 0; + box->flags = 0; + box->hover_cursor = OS_Cursor_Pointer; + MemoryZeroArray(box->pref_size); + MemoryZeroStruct(&box->draw_bucket); + } + + //- rjf: hook into persistent state table + if(box_first_frame && !box_is_transient) + { + U64 slot = key.u64[0] % ui_state->box_table_size; + DLLInsert_NPZ(&ui_g_nil_box, ui_state->box_table[slot].hash_first, ui_state->box_table[slot].hash_last, ui_state->box_table[slot].hash_last, box, hash_next, hash_prev); + } + + //- rjf: hook into per-frame tree structure + if(!ui_box_is_nil(parent)) + { + DLLPushBack_NPZ(&ui_g_nil_box, parent->first, parent->last, box, next, prev); + parent->child_count += 1; + box->parent = parent; + } + + //- rjf: fill box + { + box->key = key; + box->flags = flags|ui_state->flags_stack.top->v; + box->fastpath_codepoint = ui_state->fastpath_codepoint_stack.top->v; + box->group_key = ui_state->group_key_stack.top->v; + + if(ui_is_focus_active() && (box->flags & UI_BoxFlag_DefaultFocusNav) && ui_key_match(ui_state->default_nav_root_key, ui_key_zero())) + { + ui_state->default_nav_root_key = box->key; + } + + if(box_first_frame) + { + box->first_touched_build_index = ui_state->build_index; + box->disabled_t = (F32)!!(box->flags & UI_BoxFlag_Disabled); + } + box->last_touched_build_index = ui_state->build_index; + + if(box->flags & UI_BoxFlag_Disabled && (!(last_flags & UI_BoxFlag_Disabled) || box_first_frame)) + { + box->first_disabled_build_index = ui_state->build_index; + } + + if(ui_state->fixed_x_stack.top != &ui_state->fixed_x_nil_stack_top) + { + box->flags |= UI_BoxFlag_FloatingX; + box->fixed_position.x = ui_state->fixed_x_stack.top->v; + } + if(ui_state->fixed_y_stack.top != &ui_state->fixed_y_nil_stack_top) + { + box->flags |= UI_BoxFlag_FloatingY; + box->fixed_position.y = ui_state->fixed_y_stack.top->v; + } + if(ui_state->fixed_width_stack.top != &ui_state->fixed_width_nil_stack_top) + { + box->flags |= UI_BoxFlag_FixedWidth; + box->fixed_size.x = ui_state->fixed_width_stack.top->v; + } + else + { + box->pref_size[Axis2_X] = ui_state->pref_width_stack.top->v; + } + if(ui_state->fixed_height_stack.top != &ui_state->fixed_height_nil_stack_top) + { + box->flags |= UI_BoxFlag_FixedHeight; + box->fixed_size.y = ui_state->fixed_height_stack.top->v; + } + else + { + box->pref_size[Axis2_Y] = ui_state->pref_height_stack.top->v; + } + + B32 is_auto_focus_active = ui_is_key_auto_focus_active(key); + B32 is_auto_focus_hot = ui_is_key_auto_focus_hot(key); + if(is_auto_focus_active) + { + ui_set_next_focus_active(UI_FocusKind_On); + } + if(is_auto_focus_hot) + { + ui_set_next_focus_hot(UI_FocusKind_On); + } + box->flags |= UI_BoxFlag_FocusHot * (ui_state->focus_hot_stack.top->v == UI_FocusKind_On); + box->flags |= UI_BoxFlag_FocusActive * (ui_state->focus_active_stack.top->v == UI_FocusKind_On); + if(box->flags & UI_BoxFlag_FocusHot && !ui_is_focus_hot()) + { + box->flags |= UI_BoxFlag_FocusHotDisabled; + } + if(box->flags & UI_BoxFlag_FocusActive && !ui_is_focus_active()) + { + box->flags |= UI_BoxFlag_FocusActiveDisabled; + } + + box->text_align = ui_state->text_alignment_stack.top->v; + box->child_layout_axis = ui_state->child_layout_axis_stack.top->v; + box->palette = ui_state->palette_stack.top->v; + box->font = ui_state->font_stack.top->v; + box->font_size = ui_state->font_size_stack.top->v; + box->tab_size = ui_state->tab_size_stack.top->v; + box->text_raster_flags = ui_state->text_raster_flags_stack.top->v; + box->corner_radii[Corner_00] = ui_state->corner_radius_00_stack.top->v; + box->corner_radii[Corner_01] = ui_state->corner_radius_01_stack.top->v; + box->corner_radii[Corner_10] = ui_state->corner_radius_10_stack.top->v; + box->corner_radii[Corner_11] = ui_state->corner_radius_11_stack.top->v; + box->blur_size = ui_state->blur_size_stack.top->v; + box->transparency = ui_state->transparency_stack.top->v; + box->squish = ui_state->squish_stack.top->v; + box->text_padding = ui_state->text_padding_stack.top->v; + box->hover_cursor = ui_state->hover_cursor_stack.top->v; + box->custom_draw = 0; + } + + //- rjf: auto-pop all stacks + { + UI_AutoPopStacks(ui_state); + } + + //- rjf: return + ProfEnd(); + return box; +} + +internal UI_Key +ui_active_seed_key(void) +{ + UI_Box *keyed_ancestor = &ui_g_nil_box; + { + for(UI_Box *p = ui_top_parent(); !ui_box_is_nil(p); p = p->parent) + { + if(!ui_key_match(ui_key_zero(), p->key)) + { + keyed_ancestor = p; + break; + } + } + } + return keyed_ancestor->key; +} + +internal UI_Box * +ui_build_box_from_string(UI_BoxFlags flags, String8 string) +{ + ProfBeginFunction(); + + //- rjf: grab active parent + UI_Box *parent = ui_top_parent(); + + //- rjf: figure out key + UI_Key key = ui_key_from_string(ui_active_seed_key(), string); + + //- rjf: build box from key, equip passed string + UI_Box *box = ui_build_box_from_key(flags, key); + if(flags & UI_BoxFlag_DrawText) + { + ui_box_equip_display_string(box, string); + } + + //- rjf: return + ProfEnd(); + return box; +} + +internal UI_Box * +ui_build_box_from_stringf(UI_BoxFlags flags, char *fmt, ...) +{ + Temp scratch = scratch_begin(0, 0); + va_list args; + va_start(args, fmt); + String8 string = push_str8fv(scratch.arena, fmt, args); + va_end(args); + UI_Box *box = ui_build_box_from_string(flags, string); + scratch_end(scratch); + return box; +} + +//- rjf: box node equipment + +internal void +ui_box_equip_display_string(UI_Box *box, String8 string) +{ + ProfBeginFunction(); + box->string = push_str8_copy(ui_build_arena(), string); + box->flags |= UI_BoxFlag_HasDisplayString; + UI_ColorCode text_color_code = (box->flags & UI_BoxFlag_DrawTextWeak ? UI_ColorCode_TextWeak : UI_ColorCode_Text); + if(box->flags & UI_BoxFlag_DrawText && (box->fastpath_codepoint == 0 || !(box->flags & UI_BoxFlag_DrawTextFastpathCodepoint))) + { + String8 display_string = ui_box_display_string(box); + D_FancyStringNode fancy_string_n = {0, {box->font, display_string, box->palette->colors[text_color_code], box->font_size, 0, 0}}; + D_FancyStringList fancy_strings = {&fancy_string_n, &fancy_string_n, 1}; + box->display_string_runs = d_fancy_run_list_from_fancy_string_list(ui_build_arena(), box->tab_size, box->text_raster_flags, &fancy_strings); + } + else if(box->flags & UI_BoxFlag_DrawText && box->flags & UI_BoxFlag_DrawTextFastpathCodepoint && box->fastpath_codepoint != 0) + { + Temp scratch = scratch_begin(0, 0); + String8 display_string = ui_box_display_string(box); + String32 fpcp32 = str32(&box->fastpath_codepoint, 1); + String8 fpcp = str8_from_32(scratch.arena, fpcp32); + U64 fpcp_pos = str8_find_needle(display_string, 0, fpcp, StringMatchFlag_CaseInsensitive); + if(fpcp_pos < display_string.size) + { + D_FancyStringNode pst_fancy_string_n = {0, {box->font, str8_skip(display_string, fpcp_pos+fpcp.size), box->palette->colors[text_color_code], box->font_size, 0, 0}}; + D_FancyStringNode cdp_fancy_string_n = {&pst_fancy_string_n, {box->font, str8_substr(display_string, r1u64(fpcp_pos, fpcp_pos+fpcp.size)), box->palette->colors[text_color_code], box->font_size, 3.f, 0}}; + D_FancyStringNode pre_fancy_string_n = {&cdp_fancy_string_n, {box->font, str8_prefix(display_string, fpcp_pos), box->palette->colors[text_color_code], box->font_size, 0, 0}}; + D_FancyStringList fancy_strings = {&pre_fancy_string_n, &pst_fancy_string_n, 3}; + box->display_string_runs = d_fancy_run_list_from_fancy_string_list(ui_build_arena(), box->tab_size, box->text_raster_flags, &fancy_strings); + } + else + { + D_FancyStringNode fancy_string_n = {0, {box->font, display_string, box->palette->colors[UI_ColorCode_Text], box->font_size, 0, 0}}; + D_FancyStringList fancy_strings = {&fancy_string_n, &fancy_string_n, 1}; + box->display_string_runs = d_fancy_run_list_from_fancy_string_list(ui_build_arena(), box->tab_size, box->text_raster_flags, &fancy_strings); + } + scratch_end(scratch); + } + ProfEnd(); +} + +internal void +ui_box_equip_display_fancy_strings(UI_Box *box, D_FancyStringList *strings) +{ + box->flags |= UI_BoxFlag_HasDisplayString; + box->string = d_string_from_fancy_string_list(ui_build_arena(), strings); + box->display_string_runs = d_fancy_run_list_from_fancy_string_list(ui_build_arena(), box->tab_size, box->text_raster_flags, strings); +} + +internal inline void +ui_box_equip_display_string_fancy_runs(UI_Box *box, String8 string, D_FancyRunList *runs) +{ + box->flags |= UI_BoxFlag_HasDisplayString; + box->string = push_str8_copy(ui_build_arena(), string); + box->display_string_runs = d_fancy_run_list_copy(ui_build_arena(), runs); +} + +internal inline void +ui_box_equip_fuzzy_match_ranges(UI_Box *box, FuzzyMatchRangeList *matches) +{ + box->flags |= UI_BoxFlag_HasFuzzyMatchRanges; + box->fuzzy_match_ranges = fuzzy_match_range_list_copy(ui_build_arena(), matches); +} + +internal void +ui_box_equip_draw_bucket(UI_Box *box, D_Bucket *bucket) +{ + box->flags |= UI_BoxFlag_DrawBucket; + if(box->draw_bucket != 0) + { + D_BucketScope(box->draw_bucket) d_sub_bucket(bucket); + } + else + { + box->draw_bucket = bucket; + } +} + +internal void +ui_box_equip_custom_draw(UI_Box *box, UI_BoxCustomDrawFunctionType *custom_draw, void *user_data) +{ + box->custom_draw = custom_draw; + box->custom_draw_user_data = user_data; +} + +//- rjf: box accessors / queries + +internal String8 +ui_box_display_string(UI_Box *box) +{ + String8 result = box->string; + if(!(box->flags & UI_BoxFlag_DisableIDString)) + { + result = ui_display_part_from_key_string(result); + } + return result; +} + +internal Vec2F32 +ui_box_text_position(UI_Box *box) +{ + Vec2F32 result = {0}; + F_Tag font = box->font; + F32 font_size = box->font_size; + F_Metrics font_metrics = f_metrics_from_tag_size(font, font_size); + result.y = floor_f32((box->rect.p0.y + box->rect.p1.y)/2.f) + font_metrics.capital_height/2.f; + if(!f_tag_match(font, ui_icon_font())) + { + result.y += font_metrics.descent/2; + } + switch(box->text_align) + { + default: + case UI_TextAlign_Left: + { + result.x = box->rect.p0.x + box->text_padding; + }break; + case UI_TextAlign_Center: + { + Vec2F32 text_dim = box->display_string_runs.dim; + result.x = floor_f32((box->rect.p0.x + box->rect.p1.x)/2 - text_dim.x/2); + result.x = ClampBot(result.x, box->rect.x0); + }break; + case UI_TextAlign_Right: + { + Vec2F32 text_dim = box->display_string_runs.dim; + result.x = round_f32((box->rect.p1.x) - text_dim.x - box->text_padding); + result.x = ClampBot(result.x, box->rect.x0); + }break; + } + result.x = floor_f32(result.x); + return result; +} + +internal U64 +ui_box_char_pos_from_xy(UI_Box *box, Vec2F32 xy) +{ + F_Tag font = box->font; + F32 font_size = box->font_size; + String8 line = ui_box_display_string(box); + U64 result = f_char_pos_from_tag_size_string_p(font, font_size, 0, box->tab_size, line, xy.x - ui_box_text_position(box).x); + return result; +} + +//////////////////////////////// +//~ rjf: Box Interaction + +internal UI_Signal +ui_signal_from_box(UI_Box *box) +{ + ProfBeginFunction(); + B32 is_focus_hot = box->flags & UI_BoxFlag_FocusHot && !(box->flags & UI_BoxFlag_FocusHotDisabled); + UI_Signal sig = {box}; + sig.event_flags |= os_get_event_flags(); + + ////////////////////////////// + //- rjf: calculate possibly-clipped box rectangle + // + Rng2F32 rect = box->rect; + for(UI_Box *b = box->parent; !ui_box_is_nil(b); b = b->parent) + { + if(b->flags & UI_BoxFlag_Clip) + { + rect = intersect_2f32(rect, b->rect); + } + } + + ////////////////////////////// + //- rjf: determine if we're under the context menu or not + // + B32 ctx_menu_is_ancestor = 0; + ProfScope("check context menu ancestor") + { + for(UI_Box *parent = box; !ui_box_is_nil(parent); parent = parent->parent) + { + if(parent == ui_state->ctx_menu_root) + { + ctx_menu_is_ancestor = 1; + break; + } + } + } + + ////////////////////////////// + //- rjf: calculate blacklist rectangles + // + Rng2F32 blacklist_rect = {0}; + if(!ctx_menu_is_ancestor && ui_state->ctx_menu_open) + { + blacklist_rect = ui_state->ctx_menu_root->rect; + } + + ////////////////////////////// + //- rjf: process events related to this box + // + B32 view_scrolled = 0; + for(UI_EventNode *n = ui_state->events->first, *next = 0; + n != 0; + n = next) + { + B32 taken = 0; + next = n->next; + UI_Event *evt = &n->v; + + //- rjf: unpack event + Vec2F32 evt_mouse = evt->pos; + B32 evt_mouse_in_bounds = !contains_2f32(blacklist_rect, evt_mouse) && contains_2f32(rect, evt_mouse); + UI_MouseButtonKind evt_mouse_button_kind = (evt->key == OS_Key_LeftMouseButton ? UI_MouseButtonKind_Left : + evt->key == OS_Key_MiddleMouseButton ? UI_MouseButtonKind_Middle : + evt->key == OS_Key_RightMouseButton ? UI_MouseButtonKind_Right : + UI_MouseButtonKind_Left); + B32 evt_key_is_mouse = (evt->key == OS_Key_LeftMouseButton || + evt->key == OS_Key_MiddleMouseButton || + evt->key == OS_Key_RightMouseButton); + sig.event_flags |= evt->modifiers; + + //- rjf: mouse presses in box -> set hot/active; mark signal accordingly + if(box->flags & UI_BoxFlag_MouseClickable && + evt->kind == UI_EventKind_Press && + evt_mouse_in_bounds && + evt_key_is_mouse) + { + ui_state->hot_box_key = box->key; + ui_state->active_box_key[evt_mouse_button_kind] = box->key; + sig.f |= (UI_SignalFlag_LeftPressed<drag_start_mouse = evt->pos; + if(ui_key_match(box->key, ui_state->press_key_history[evt_mouse_button_kind][0]) && + evt->timestamp_us-ui_state->press_timestamp_history_us[evt_mouse_button_kind][0] <= 1000000*os_get_gfx_info()->double_click_time) + { + sig.f |= (UI_SignalFlag_LeftDoubleClicked<key, ui_state->press_key_history[evt_mouse_button_kind][0]) && + ui_key_match(box->key, ui_state->press_key_history[evt_mouse_button_kind][1]) && + evt->timestamp_us-ui_state->press_timestamp_history_us[evt_mouse_button_kind][0] <= 1000000*os_get_gfx_info()->double_click_time && + ui_state->press_timestamp_history_us[evt_mouse_button_kind][0] - ui_state->press_timestamp_history_us[evt_mouse_button_kind][1] <= 1000000*os_get_gfx_info()->double_click_time) + { + sig.f |= (UI_SignalFlag_LeftTripleClicked<press_timestamp_history_us[evt_mouse_button_kind][1], &ui_state->press_timestamp_history_us[evt_mouse_button_kind][0], + sizeof(ui_state->press_timestamp_history_us[evt_mouse_button_kind][0]) * ArrayCount(ui_state->press_timestamp_history_us[evt_mouse_button_kind])-1); + MemoryCopy(&ui_state->press_key_history[evt_mouse_button_kind][1], &ui_state->press_key_history[evt_mouse_button_kind][0], + sizeof(ui_state->press_key_history[evt_mouse_button_kind][0]) * ArrayCount(ui_state->press_key_history[evt_mouse_button_kind])-1); + MemoryCopy(&ui_state->press_pos_history[evt_mouse_button_kind][1], &ui_state->press_pos_history[evt_mouse_button_kind][0], + sizeof(ui_state->press_pos_history[evt_mouse_button_kind][0]) * ArrayCount(ui_state->press_pos_history[evt_mouse_button_kind])-1); + ui_state->press_timestamp_history_us[evt_mouse_button_kind][0] = evt->timestamp_us; + ui_state->press_key_history[evt_mouse_button_kind][0] = box->key; + ui_state->press_pos_history[evt_mouse_button_kind][0] = evt_mouse; + taken = 1; + } + + //- rjf: mouse releases in active box -> unset active; mark signal accordingly + if(box->flags & UI_BoxFlag_MouseClickable && + evt->kind == UI_EventKind_Release && + ui_key_match(ui_state->active_box_key[evt_mouse_button_kind], box->key) && + evt_mouse_in_bounds && + evt_key_is_mouse) + { + ui_state->active_box_key[evt_mouse_button_kind] = ui_key_zero(); + sig.f |= (UI_SignalFlag_LeftReleased< unset hot/active + if(box->flags & UI_BoxFlag_MouseClickable && + evt->kind == UI_EventKind_Release && + ui_key_match(ui_state->active_box_key[evt_mouse_button_kind], box->key) && + !evt_mouse_in_bounds && + evt_key_is_mouse) + { + ui_state->hot_box_key = ui_key_zero(); + ui_state->active_box_key[evt_mouse_button_kind] = ui_key_zero(); + sig.f |= (UI_SignalFlag_LeftReleased< mark signal + if(box->flags & UI_BoxFlag_KeyboardClickable && + is_focus_hot && + evt->kind == UI_EventKind_Press && + evt->slot == UI_EventActionSlot_Accept) + { + sig.f |= UI_SignalFlag_KeyboardPressed; + taken = 1; + } + + //- rjf: focus is hot & copy event -> remember to copy this box tree's text content + if(is_focus_hot && + evt->flags & UI_EventFlag_Copy && + !ui_key_match(ui_key_zero(), box->key)) + { + ui_state->clipboard_copy_key = box->key; + taken = 1; + } + + //- rjf: ancestor is focused & fastpath codepoint pressed -> press + if(box->flags & UI_BoxFlag_Clickable && box->fastpath_codepoint != 0 && evt->string.size != 0) + { + B32 ancestor_is_focused = 0; + for(UI_Box *parent = box->parent; !ui_box_is_nil(parent); parent = parent->parent) + { + if(parent->flags & UI_BoxFlag_FocusActive) + { + ancestor_is_focused = 1; + if(parent->flags & UI_BoxFlag_FocusActiveDisabled || + !ui_key_match(parent->default_nav_focus_active_key, ui_key_zero())) + { + ancestor_is_focused = 0; + break; + } + } + } + if(ancestor_is_focused) + { + Temp scratch = scratch_begin(0, 0); + String32 insertion32 = str32_from_8(scratch.arena, evt->string); + if(insertion32.size == 1 && insertion32.str[0] == box->fastpath_codepoint) + { + taken = 1; + sig.f |= UI_SignalFlag_Clicked|UI_SignalFlag_Pressed; + } + scratch_end(scratch); + } + } + + //- rjf: scrolling + if(box->flags & UI_BoxFlag_Scroll && + evt->kind == UI_EventKind_Scroll && + evt->modifiers != OS_EventFlag_Ctrl && + evt_mouse_in_bounds) + { + Vec2F32 delta = evt->delta_2f32; + if(evt->modifiers & OS_EventFlag_Shift) + { + Swap(F32, delta.x, delta.y); + } + Vec2S16 delta16 = v2s16((S16)(delta.x/30.f), (S16)(delta.y/30.f)); + if(delta.x > 0 && delta16.x == 0) { delta16.x = +1; } + if(delta.x < 0 && delta16.x == 0) { delta16.x = -1; } + if(delta.y > 0 && delta16.y == 0) { delta16.y = +1; } + if(delta.y < 0 && delta16.y == 0) { delta16.y = -1; } + sig.scroll.x += delta16.x; + sig.scroll.y += delta16.y; + taken = 1; + } + + //- rjf: view scrolling + if(box->flags & UI_BoxFlag_ViewScroll && box->first_touched_build_index != box->last_touched_build_index && + evt->kind == UI_EventKind_Scroll && + evt->modifiers != OS_EventFlag_Ctrl && + evt_mouse_in_bounds) + { + Vec2F32 delta = evt->delta_2f32; + if(evt->modifiers & OS_EventFlag_Shift) + { + Swap(F32, delta.x, delta.y); + } + if(!(box->flags & UI_BoxFlag_ViewScrollX)) + { + if(delta.y == 0) + { + delta.y = delta.x; + } + delta.x = 0; + } + if(!(box->flags & UI_BoxFlag_ViewScrollY)) + { + if(delta.x == 0) + { + delta.x = delta.y; + } + delta.y = 0; + } + box->view_off_target.x += delta.x; + box->view_off_target.y += delta.y; + view_scrolled = 1; + taken = 1; + } + + //- rjf: taken -> eat event + if(taken) + { + ui_eat_event(ui_state->events, n); + } + } + + ////////////////////////////// + //- rjf: clamp view scrolling + // + if(view_scrolled && box->flags & UI_BoxFlag_ViewClamp) + { + Vec2F32 max_view_off_target = + { + ClampBot(0, box->view_bounds.x - box->fixed_size.x), + ClampBot(0, box->view_bounds.y - box->fixed_size.y), + }; + if(box->flags & UI_BoxFlag_ViewClampX) { box->view_off_target.x = Clamp(0, box->view_off_target.x, max_view_off_target.x); } + if(box->flags & UI_BoxFlag_ViewClampY) { box->view_off_target.y = Clamp(0, box->view_off_target.y, max_view_off_target.y); } + } + + ////////////////////////////// + //- rjf: active -> dragging + // + if(box->flags & UI_BoxFlag_MouseClickable) + { + for(EachEnumVal(UI_MouseButtonKind, k)) + { + if(ui_key_match(ui_state->active_box_key[k], box->key) || + sig.f & (UI_SignalFlag_LeftPressed< double-dragging + // + if(box->flags & UI_BoxFlag_MouseClickable) + { + for(EachEnumVal(UI_MouseButtonKind, k)) + { + if(sig.f & (UI_SignalFlag_LeftDragging<press_key_history[k][0], box->key) && + ui_key_match(ui_state->press_key_history[k][1], box->key) && + ui_state->press_timestamp_history_us[k][0] - ui_state->press_timestamp_history_us[k][1] <= 1000000*os_get_gfx_info()->double_click_time && + length_2f32(sub_2f32(ui_state->press_pos_history[k][0], ui_state->press_pos_history[k][1])) < 10.f) + { + sig.f |= (UI_SignalFlag_LeftDoubleDragging< triple-dragging + // + if(box->flags & UI_BoxFlag_MouseClickable) + { + for(EachEnumVal(UI_MouseButtonKind, k)) + { + if(sig.f & (UI_SignalFlag_LeftDragging<press_key_history[k][0], box->key) && + ui_key_match(ui_state->press_key_history[k][1], box->key) && + ui_key_match(ui_state->press_key_history[k][2], box->key) && + ui_state->press_timestamp_history_us[k][0] - ui_state->press_timestamp_history_us[k][1] <= 1000000*os_get_gfx_info()->double_click_time && + ui_state->press_timestamp_history_us[k][1] - ui_state->press_timestamp_history_us[k][2] <= 1000000*os_get_gfx_info()->double_click_time && + length_2f32(sub_2f32(ui_state->press_pos_history[k][0], ui_state->press_pos_history[k][1])) < 10.f && + length_2f32(sub_2f32(ui_state->press_pos_history[k][1], ui_state->press_pos_history[k][2])) < 10.f) + { + sig.f |= (UI_SignalFlag_LeftTripleDragging< always mark mouse-over + // + { + if(contains_2f32(rect, ui_state->mouse) && + !contains_2f32(blacklist_rect, ui_state->mouse)) + { + sig.f |= UI_SignalFlag_MouseOver; + } + } + + ////////////////////////////// + //- rjf: mouse is over this box's rect, no other hot key? -> set hot key, mark hovering + // + { + if(box->flags & UI_BoxFlag_MouseClickable && + contains_2f32(rect, ui_state->mouse) && + !contains_2f32(blacklist_rect, ui_state->mouse) && + (ui_key_match(ui_state->hot_box_key, ui_key_zero()) || ui_key_match(ui_state->hot_box_key, box->key)) && + (ui_key_match(ui_state->active_box_key[UI_MouseButtonKind_Left], ui_key_zero()) || ui_key_match(ui_state->active_box_key[UI_MouseButtonKind_Left], box->key)) && + (ui_key_match(ui_state->active_box_key[UI_MouseButtonKind_Middle], ui_key_zero()) || ui_key_match(ui_state->active_box_key[UI_MouseButtonKind_Middle], box->key)) && + (ui_key_match(ui_state->active_box_key[UI_MouseButtonKind_Right], ui_key_zero()) || ui_key_match(ui_state->active_box_key[UI_MouseButtonKind_Right], box->key))) + { + ui_state->hot_box_key = box->key; + sig.f |= UI_SignalFlag_Hovering; + } + } + + ////////////////////////////// + //- rjf: mouse is over this box's rect, currently-active-key has the same group key? -> set hot/active key + // + if(box->flags & UI_BoxFlag_MouseClickable && + contains_2f32(rect, ui_state->mouse) && + !contains_2f32(blacklist_rect, ui_state->mouse) && + !ui_key_match(ui_key_zero(), box->group_key)) + { + for(EachEnumVal(UI_MouseButtonKind, k)) + { + UI_Box *active_box = ui_box_from_key(ui_state->active_box_key[k]); + if(ui_key_match(box->group_key, active_box->group_key)) + { + ui_state->hot_box_key = box->key; + ui_state->active_box_key[k] = box->key; + sig.f |= UI_SignalFlag_Hovering|(UI_SignalFlag_Dragging< set drop hot key + // + { + if(box->flags & UI_BoxFlag_DropSite && + contains_2f32(rect, ui_state->mouse) && + !contains_2f32(blacklist_rect, ui_state->mouse) && + (ui_key_match(ui_state->drop_hot_box_key, ui_key_zero()) || ui_key_match(ui_state->drop_hot_box_key, box->key))) + { + ui_state->drop_hot_box_key = box->key; + } + } + + ////////////////////////////// + //- rjf: mouse is not over this box's rect, but this is the drop hot key? -> zero drop hot key + // + { + if(box->flags & UI_BoxFlag_DropSite && + (!contains_2f32(rect, ui_state->mouse) || + contains_2f32(blacklist_rect, ui_state->mouse)) && + ui_key_match(ui_state->drop_hot_box_key, box->key)) + { + ui_state->drop_hot_box_key = ui_key_zero(); + } + } + + ////////////////////////////// + //- rjf: clicking on something outside the context menu kills the context menu + // + if(!ctx_menu_is_ancestor && sig.f & (UI_SignalFlag_LeftPressed|UI_SignalFlag_RightPressed|UI_SignalFlag_MiddlePressed)) + { + ui_ctx_menu_close(); + } + + ////////////////////////////// + //- rjf: get default nav ancestor + // + UI_Box *default_nav_parent = &ui_g_nil_box; + for(UI_Box *p = ui_top_parent(); !ui_box_is_nil(p); p = p->parent) + { + if(p->flags & UI_BoxFlag_DefaultFocusNav) + { + default_nav_parent = p; + break; + } + } + + ////////////////////////////// + //- rjf: clicking in default nav -> set navigation state to this box + // + if(box->flags & UI_BoxFlag_ClickToFocus && sig.f&UI_SignalFlag_Pressed && !ui_box_is_nil(default_nav_parent)) + { + default_nav_parent->default_nav_focus_next_hot_key = box->key; + if(!ui_key_match(default_nav_parent->default_nav_focus_active_key, box->key)) + { + default_nav_parent->default_nav_focus_next_active_key = ui_key_zero(); + } + } + + + ProfEnd(); + return sig; +} + +//////////////////////////////// +//~ rjf: Stacks + +//- rjf: helpers + +internal Rng2F32 +ui_push_rect(Rng2F32 rect) +{ + Rng2F32 replaced = {0}; + Vec2F32 size = dim_2f32(rect); + replaced.x0 = ui_push_fixed_x(rect.x0); + replaced.y0 = ui_push_fixed_y(rect.y0); + replaced.x1 = replaced.x0 + ui_push_fixed_width(size.x); + replaced.y1 = replaced.y0 + ui_push_fixed_height(size.y); + return replaced; +} + +internal Rng2F32 +ui_pop_rect(void) +{ + Rng2F32 popped = {0}; + popped.x0 = ui_pop_fixed_x(); + popped.y0 = ui_pop_fixed_y(); + popped.x1 = popped.x0 + ui_pop_fixed_width(); + popped.y1 = popped.y0 + ui_pop_fixed_height(); + return popped; +} + +internal void +ui_set_next_rect(Rng2F32 rect) +{ + Vec2F32 size = dim_2f32(rect); + ui_set_next_fixed_x(rect.x0); + ui_set_next_fixed_y(rect.y0); + ui_set_next_fixed_width(size.x); + ui_set_next_fixed_height(size.y); +} + +internal UI_Size +ui_push_pref_size(Axis2 axis, UI_Size v) +{ + UI_Size result = zero_struct; + switch(axis) + { + default: break; + case Axis2_X: {result = ui_push_pref_width(v);}break; + case Axis2_Y: {result = ui_push_pref_height(v);}break; + } + return result; +} + +internal UI_Size +ui_pop_pref_size(Axis2 axis) +{ + UI_Size result = zero_struct; + switch(axis) + { + default: break; + case Axis2_X: {result = ui_pop_pref_width();}break; + case Axis2_Y: {result = ui_pop_pref_height();}break; + } + return result; +} + +internal UI_Size +ui_set_next_pref_size(Axis2 axis, UI_Size v) +{ + return (axis == Axis2_X ? ui_set_next_pref_width : ui_set_next_pref_height)(v); +} + +internal void +ui_push_corner_radius(F32 v) +{ + ui_push_corner_radius_00(v); + ui_push_corner_radius_01(v); + ui_push_corner_radius_10(v); + ui_push_corner_radius_11(v); +} + +internal void +ui_pop_corner_radius(void) +{ + ui_pop_corner_radius_00(); + ui_pop_corner_radius_01(); + ui_pop_corner_radius_10(); + ui_pop_corner_radius_11(); +} + +//////////////////////////////// +//~ rjf: Generated Code + +#define UI_StackTopImpl(state, name_upper, name_lower) \ +return state->name_lower##_stack.top->v; + +#define UI_StackBottomImpl(state, name_upper, name_lower) \ +return state->name_lower##_stack.bottom_val; + +#define UI_StackPushImpl(state, name_upper, name_lower, type, new_value) \ +UI_##name_upper##Node *node = state->name_lower##_stack.free;\ +if(node != 0) {SLLStackPop(state->name_lower##_stack.free);}\ +else {node = push_array(ui_build_arena(), UI_##name_upper##Node, 1);}\ +type old_value = state->name_lower##_stack.top->v;\ +node->v = new_value;\ +SLLStackPush(state->name_lower##_stack.top, node);\ +if(node->next == &state->name_lower##_nil_stack_top)\ +{\ +state->name_lower##_stack.bottom_val = (new_value);\ +}\ +state->name_lower##_stack.auto_pop = 0;\ +return old_value; + +#define UI_StackPopImpl(state, name_upper, name_lower) \ +UI_##name_upper##Node *popped = state->name_lower##_stack.top;\ +if(popped != &state->name_lower##_nil_stack_top)\ +{\ +SLLStackPop(state->name_lower##_stack.top);\ +SLLStackPush(state->name_lower##_stack.free, popped);\ +state->name_lower##_stack.auto_pop = 0;\ +}\ +return popped->v;\ + +#define UI_StackSetNextImpl(state, name_upper, name_lower, type, new_value) \ +UI_##name_upper##Node *node = state->name_lower##_stack.free;\ +if(node != 0) {SLLStackPop(state->name_lower##_stack.free);}\ +else {node = push_array(ui_build_arena(), UI_##name_upper##Node, 1);}\ +type old_value = state->name_lower##_stack.top->v;\ +node->v = new_value;\ +SLLStackPush(state->name_lower##_stack.top, node);\ +state->name_lower##_stack.auto_pop = 1;\ +return old_value; + +#include "generated/ui.meta.c"