Compare commits
31 Commits
5ae2cc3a56
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a9c9ae6371 | |||
| 1b21b7c533 | |||
| 037e6e8d45 | |||
| 4b1e8720e3 | |||
| 65efb397ba | |||
| 8083a7c782 | |||
| d01cf53ff8 | |||
| 5817060a8b | |||
| bc2da4df4c | |||
| 3e9d6980df | |||
| b91869a49e | |||
| 662f5afd9b | |||
| 1f9a9bdc21 | |||
| f8ac671e3a | |||
| 7dfd1b1927 | |||
| a4658b1951 | |||
| 9db292f616 | |||
| 56cc90c57b | |||
| fb286c7f11 | |||
| 16b8483312 | |||
| d888968ce0 | |||
| dd83866f2b | |||
| 35fe0b899a | |||
| 39ec4213d6 | |||
| bd3dc5f2bc | |||
| 00ad047d9a | |||
| 6f0715b0ca | |||
| 20be5d0892 | |||
| 2a5b3461e3 | |||
| 9c9a505464 | |||
| b5fd6cb4a8 |
+3
-1
@@ -1 +1,3 @@
|
|||||||
*/build
|
Build
|
||||||
|
part2/*.f64
|
||||||
|
part2/*.json
|
||||||
|
|||||||
@@ -0,0 +1,350 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
REM Setup //////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set script_dir_backslash=%~dp0
|
||||||
|
set script_dir=%script_dir_backslash:~0,-1%
|
||||||
|
set build_dir=%script_dir%\Build
|
||||||
|
|
||||||
|
set part1_dir=%script_dir%\part1
|
||||||
|
set part1_build_dir=%build_dir%\part1
|
||||||
|
if not exist %part1_build_dir% mkdir %part1_build_dir%
|
||||||
|
|
||||||
|
set part2_dir=%script_dir%\part2
|
||||||
|
set part2_build_dir=%build_dir%\part2
|
||||||
|
if not exist %part2_build_dir% mkdir %part2_build_dir%
|
||||||
|
|
||||||
|
set part3_dir=%script_dir%\part3
|
||||||
|
set part3_build_dir=%build_dir%\part3
|
||||||
|
if not exist %part3_build_dir% mkdir %part3_build_dir%
|
||||||
|
goto :part3
|
||||||
|
|
||||||
|
REM Part 1 /////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
REM Build //////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
pushd %part1_build_dir%
|
||||||
|
cl %part1_dir%\sim8086.c /W4 /WX /Z7 /nologo || exit /b 1
|
||||||
|
popd
|
||||||
|
|
||||||
|
REM Tests //////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0037=listing_0037_single_register_mov
|
||||||
|
set part1_build_dir_listing_0037=%part1_build_dir%\%listing_0037%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0037% %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0037% > %part1_build_dir_listing_0037%_disassembled.asm
|
||||||
|
nasm %part1_build_dir_listing_0037%_disassembled.asm
|
||||||
|
fc /B %part1_build_dir_listing_0037% %part1_build_dir_listing_0037%_disassembled || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0038=listing_0038_many_register_mov
|
||||||
|
set part1_build_dir_listing_0038=%part1_build_dir%\%listing_0038%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0038% %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0038% > %part1_build_dir_listing_0038%_disassembled.asm
|
||||||
|
nasm %part1_build_dir_listing_0038%_disassembled.asm
|
||||||
|
fc /B %part1_build_dir_listing_0038% %part1_build_dir_listing_0038%_disassembled || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0039=listing_0039_more_movs
|
||||||
|
set part1_build_dir_listing_0039=%part1_build_dir%\%listing_0039%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0039% %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0039% > %part1_build_dir_listing_0039%_disassembled.asm
|
||||||
|
nasm %part1_build_dir_listing_0039%_disassembled.asm
|
||||||
|
fc /B %part1_build_dir_listing_0039% %part1_build_dir_listing_0039%_disassembled || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0040=listing_0040_challenge_movs
|
||||||
|
set part1_build_dir_listing_0040=%part1_build_dir%\%listing_0040%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0040% %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0040% > %part1_build_dir_listing_0040%_disassembled.asm
|
||||||
|
nasm %part1_build_dir_listing_0040%_disassembled.asm
|
||||||
|
fc /B %part1_build_dir_listing_0040% %part1_build_dir_listing_0040%_disassembled || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0041=listing_0041_add_sub_cmp_jnz
|
||||||
|
set part1_build_dir_listing_0041=%part1_build_dir%\%listing_0041%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0041% %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0041% > %part1_build_dir_listing_0041%_disassembled.asm
|
||||||
|
nasm %part1_build_dir_listing_0041%_disassembled.asm
|
||||||
|
fc /B %part1_build_dir_listing_0041% %part1_build_dir_listing_0041%_disassembled || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0042=listing_0042_completionist_decode
|
||||||
|
set part1_build_dir_listing_0042=%part1_build_dir%\%listing_0042%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0042% %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0042% > %part1_build_dir_listing_0042%_disassembled.asm
|
||||||
|
nasm %part1_build_dir_listing_0042%_disassembled.asm
|
||||||
|
fc /B %part1_build_dir_listing_0042% %part1_build_dir_listing_0042%_disassembled || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0043=listing_0043_immediate_movs
|
||||||
|
set part1_build_dir_listing_0043=%part1_build_dir%\%listing_0043%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0043% %part1_build_dir% 1>NUL
|
||||||
|
copy /Y %part1_dir%\%listing_0043%.txt %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0043% > %part1_build_dir_listing_0043%_disassembled.asm
|
||||||
|
%part1_build_dir%\sim8086.exe --exec %part1_build_dir_listing_0043% > %part1_build_dir_listing_0043%_disassembled.txt
|
||||||
|
|
||||||
|
nasm %part1_build_dir_listing_0043%_disassembled.asm
|
||||||
|
|
||||||
|
fc /B %part1_build_dir_listing_0043% %part1_build_dir_listing_0043%_disassembled || exit /b 1
|
||||||
|
fc /N %part1_build_dir_listing_0043%.txt %part1_build_dir_listing_0043%_disassembled.txt || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0044=listing_0044_register_movs
|
||||||
|
set part1_build_dir_listing_0044=%part1_build_dir%\%listing_0044%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0044% %part1_build_dir% 1>NUL
|
||||||
|
copy /Y %part1_dir%\%listing_0044%.txt %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe --exec %part1_build_dir_listing_0044% > %part1_build_dir_listing_0044%_disassembled.txt
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0044% > %part1_build_dir_listing_0044%_disassembled.asm
|
||||||
|
|
||||||
|
nasm %part1_build_dir_listing_0044%_disassembled.asm
|
||||||
|
|
||||||
|
fc /B %part1_build_dir_listing_0044% %part1_build_dir_listing_0044%_disassembled || exit /b 1
|
||||||
|
fc /N %part1_build_dir_listing_0044%.txt %part1_build_dir_listing_0044%_disassembled.txt || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0045=listing_0045_challenge_register_movs
|
||||||
|
set part1_build_dir_listing_0045=%part1_build_dir%\%listing_0045%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0045% %part1_build_dir% 1>NUL
|
||||||
|
copy /Y %part1_dir%\%listing_0045%.txt %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe --exec %part1_build_dir_listing_0045% > %part1_build_dir_listing_0045%_disassembled.txt
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0045% > %part1_build_dir_listing_0045%_disassembled.asm
|
||||||
|
|
||||||
|
nasm %part1_build_dir_listing_0045%_disassembled.asm
|
||||||
|
|
||||||
|
fc /B %part1_build_dir_listing_0045% %part1_build_dir_listing_0045%_disassembled || exit /b 1
|
||||||
|
fc /N %part1_build_dir_listing_0045%.txt %part1_build_dir_listing_0045%_disassembled.txt || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0046=listing_0046_add_sub_cmp
|
||||||
|
set part1_build_dir_listing_0046=%part1_build_dir%\%listing_0046%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0046% %part1_build_dir% 1>NUL
|
||||||
|
copy /Y %part1_dir%\%listing_0046%.txt %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe --exec %part1_build_dir_listing_0046% > %part1_build_dir_listing_0046%_disassembled.txt
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0046% > %part1_build_dir_listing_0046%_disassembled.asm
|
||||||
|
|
||||||
|
nasm %part1_build_dir_listing_0046%_disassembled.asm
|
||||||
|
|
||||||
|
fc /B %part1_build_dir_listing_0046% %part1_build_dir_listing_0046%_disassembled || exit /b 1
|
||||||
|
fc /N %part1_build_dir_listing_0046%.txt %part1_build_dir_listing_0046%_disassembled.txt || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0047=listing_0047_challenge_flags
|
||||||
|
set part1_build_dir_listing_0047=%part1_build_dir%\%listing_0047%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0047% %part1_build_dir% 1>NUL
|
||||||
|
copy /Y %part1_dir%\%listing_0047%.txt %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe --exec %part1_build_dir_listing_0047% > %part1_build_dir_listing_0047%_disassembled.txt
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0047% > %part1_build_dir_listing_0047%_disassembled.asm
|
||||||
|
|
||||||
|
nasm %part1_build_dir_listing_0047%_disassembled.asm
|
||||||
|
|
||||||
|
fc /B %part1_build_dir_listing_0047% %part1_build_dir_listing_0047%_disassembled || exit /b 1
|
||||||
|
fc /N %part1_build_dir_listing_0047%.txt %part1_build_dir_listing_0047%_disassembled.txt || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0048=listing_0048_ip_register
|
||||||
|
set part1_build_dir_listing_0048=%part1_build_dir%\%listing_0048%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0048% %part1_build_dir% 1>NUL
|
||||||
|
copy /Y %part1_dir%\%listing_0048%.txt %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr %part1_build_dir_listing_0048% > %part1_build_dir_listing_0048%_disassembled.txt
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0048% > %part1_build_dir_listing_0048%_disassembled.asm
|
||||||
|
|
||||||
|
nasm %part1_build_dir_listing_0048%_disassembled.asm
|
||||||
|
|
||||||
|
fc /B %part1_build_dir_listing_0048% %part1_build_dir_listing_0048%_disassembled || exit /b 1
|
||||||
|
fc /N %part1_build_dir_listing_0048%.txt %part1_build_dir_listing_0048%_disassembled.txt || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0049=listing_0049_conditional_jumps
|
||||||
|
set part1_build_dir_listing_0049=%part1_build_dir%\%listing_0049%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0049% %part1_build_dir% 1>NUL
|
||||||
|
copy /Y %part1_dir%\%listing_0049%.txt %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr %part1_build_dir_listing_0049% > %part1_build_dir_listing_0049%_disassembled.txt
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0049% > %part1_build_dir_listing_0049%_disassembled.asm
|
||||||
|
|
||||||
|
nasm %part1_build_dir_listing_0049%_disassembled.asm
|
||||||
|
|
||||||
|
fc /B %part1_build_dir_listing_0049% %part1_build_dir_listing_0049%_disassembled || exit /b 1
|
||||||
|
fc /N %part1_build_dir_listing_0049%.txt %part1_build_dir_listing_0049%_disassembled.txt || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0050=listing_0050_challenge_jumps
|
||||||
|
set part1_build_dir_listing_0050=%part1_build_dir%\%listing_0050%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0050% %part1_build_dir% 1>NUL
|
||||||
|
copy /Y %part1_dir%\%listing_0050%.txt %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr %part1_build_dir_listing_0050% > %part1_build_dir_listing_0050%_disassembled.txt
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0050% > %part1_build_dir_listing_0050%_disassembled.asm
|
||||||
|
|
||||||
|
nasm %part1_build_dir_listing_0050%_disassembled.asm
|
||||||
|
|
||||||
|
fc /B %part1_build_dir_listing_0050% %part1_build_dir_listing_0050%_disassembled || exit /b 1
|
||||||
|
fc /N %part1_build_dir_listing_0050%.txt %part1_build_dir_listing_0050%_disassembled.txt || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0051=listing_0051_memory_mov
|
||||||
|
set part1_build_dir_listing_0051=%part1_build_dir%\%listing_0051%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0051% %part1_build_dir% 1>NUL
|
||||||
|
copy /Y %part1_dir%\%listing_0051%.txt %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr %part1_build_dir_listing_0051% > %part1_build_dir_listing_0051%_disassembled.txt
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0051% > %part1_build_dir_listing_0051%_disassembled.asm
|
||||||
|
|
||||||
|
nasm %part1_build_dir_listing_0051%_disassembled.asm
|
||||||
|
|
||||||
|
fc /B %part1_build_dir_listing_0051% %part1_build_dir_listing_0051%_disassembled || exit /b 1
|
||||||
|
fc /N %part1_build_dir_listing_0051%.txt %part1_build_dir_listing_0051%_disassembled.txt || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0052=listing_0052_memory_add_loop
|
||||||
|
set part1_build_dir_listing_0052=%part1_build_dir%\%listing_0052%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0052% %part1_build_dir% 1>NUL
|
||||||
|
copy /Y %part1_dir%\%listing_0052%.txt %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr %part1_build_dir_listing_0052% > %part1_build_dir_listing_0052%_disassembled.txt
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0052% > %part1_build_dir_listing_0052%_disassembled.asm
|
||||||
|
|
||||||
|
nasm %part1_build_dir_listing_0052%_disassembled.asm
|
||||||
|
|
||||||
|
fc /B %part1_build_dir_listing_0052% %part1_build_dir_listing_0052%_disassembled || exit /b 1
|
||||||
|
fc /N %part1_build_dir_listing_0052%.txt %part1_build_dir_listing_0052%_disassembled.txt || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0053=listing_0053_add_loop_challenge
|
||||||
|
set part1_build_dir_listing_0053=%part1_build_dir%\%listing_0053%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0053% %part1_build_dir% 1>NUL
|
||||||
|
copy /Y %part1_dir%\%listing_0053%.txt %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr %part1_build_dir_listing_0053% > %part1_build_dir_listing_0053%_disassembled.txt
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0053% > %part1_build_dir_listing_0053%_disassembled.asm
|
||||||
|
|
||||||
|
nasm %part1_build_dir_listing_0053%_disassembled.asm
|
||||||
|
|
||||||
|
fc /B %part1_build_dir_listing_0053% %part1_build_dir_listing_0053%_disassembled || exit /b 1
|
||||||
|
fc /N %part1_build_dir_listing_0053%.txt %part1_build_dir_listing_0053%_disassembled.txt || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0054=listing_0054_draw_rectangle
|
||||||
|
set part1_build_dir_listing_0054=%part1_build_dir%\%listing_0054%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0054% %part1_build_dir% 1>NUL
|
||||||
|
copy /Y %part1_dir%\%listing_0054%.txt %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
pushd %part1_build_dir%
|
||||||
|
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr --dump %part1_build_dir_listing_0054% > %part1_build_dir_listing_0054%_disassembled.txt
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0054% > %part1_build_dir_listing_0054%_disassembled.asm
|
||||||
|
popd
|
||||||
|
|
||||||
|
nasm %part1_build_dir_listing_0054%_disassembled.asm
|
||||||
|
|
||||||
|
fc /B %part1_build_dir_listing_0054% %part1_build_dir_listing_0054%_disassembled || exit /b 1
|
||||||
|
fc /N %part1_build_dir_listing_0054%.txt %part1_build_dir_listing_0054%_disassembled.txt || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0055=listing_0055_challenge_rectangle
|
||||||
|
set part1_build_dir_listing_0055=%part1_build_dir%\%listing_0055%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0055% %part1_build_dir% 1>NUL
|
||||||
|
copy /Y %part1_dir%\%listing_0055%.txt %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
pushd %part1_build_dir%
|
||||||
|
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr --dump %part1_build_dir_listing_0055% > %part1_build_dir_listing_0055%_disassembled.txt
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0055% > %part1_build_dir_listing_0055%_disassembled.asm
|
||||||
|
popd
|
||||||
|
|
||||||
|
nasm %part1_build_dir_listing_0055%_disassembled.asm
|
||||||
|
|
||||||
|
fc /B %part1_build_dir_listing_0055% %part1_build_dir_listing_0055%_disassembled || exit /b 1
|
||||||
|
fc /N %part1_build_dir_listing_0055%.txt %part1_build_dir_listing_0055%_disassembled.txt || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0056=listing_0056_estimating_cycles
|
||||||
|
set part1_build_dir_listing_0056=%part1_build_dir%\%listing_0056%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0056% %part1_build_dir% 1>NUL
|
||||||
|
copy /Y %part1_dir%\%listing_0056%.txt %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
pushd %part1_build_dir%
|
||||||
|
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr --log-cycle-counts 8086 %part1_build_dir_listing_0056% > %part1_build_dir_listing_0056%_disassembled.txt
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0056% > %part1_build_dir_listing_0056%_disassembled.asm
|
||||||
|
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr --log-cycle-counts 8088 %part1_build_dir_listing_0056% >> %part1_build_dir_listing_0056%_disassembled.txt
|
||||||
|
popd
|
||||||
|
|
||||||
|
nasm %part1_build_dir_listing_0056%_disassembled.asm
|
||||||
|
|
||||||
|
fc /B %part1_build_dir_listing_0056% %part1_build_dir_listing_0056%_disassembled || exit /b 1
|
||||||
|
fc /N %part1_build_dir_listing_0056%.txt %part1_build_dir_listing_0056%_disassembled.txt || exit /b 1
|
||||||
|
|
||||||
|
REM ////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
set listing_0057=listing_0057_challenge_cycles
|
||||||
|
set part1_build_dir_listing_0057=%part1_build_dir%\%listing_0057%
|
||||||
|
|
||||||
|
copy /Y %part1_dir%\%listing_0057% %part1_build_dir% 1>NUL
|
||||||
|
copy /Y %part1_dir%\%listing_0057%.txt %part1_build_dir% 1>NUL
|
||||||
|
|
||||||
|
pushd %part1_build_dir%
|
||||||
|
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr --log-cycle-counts 8086 %part1_build_dir_listing_0057% > %part1_build_dir_listing_0057%_disassembled.txt
|
||||||
|
%part1_build_dir%\sim8086.exe %part1_build_dir_listing_0057% > %part1_build_dir_listing_0057%_disassembled.asm
|
||||||
|
%part1_build_dir%\sim8086.exe --exec --log-instruction-ptr --log-cycle-counts 8088 %part1_build_dir_listing_0057% >> %part1_build_dir_listing_0057%_disassembled.txt
|
||||||
|
popd
|
||||||
|
|
||||||
|
nasm %part1_build_dir_listing_0057%_disassembled.asm
|
||||||
|
|
||||||
|
fc /B %part1_build_dir_listing_0057% %part1_build_dir_listing_0057%_disassembled || exit /b 1
|
||||||
|
fc /N %part1_build_dir_listing_0057%.txt %part1_build_dir_listing_0057%_disassembled.txt || exit /b 1
|
||||||
|
|
||||||
|
REM Part 2 /////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
REM Build //////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
:part2
|
||||||
|
pushd %part2_build_dir%
|
||||||
|
cl %part2_dir%\haversine_generator.c /W4 /WX /Z7 /nologo /Fe:haversine_generator_debug || exit /b 1
|
||||||
|
cl %part2_dir%\haversine_generator.c /W4 /WX /Z7 /nologo /O2 /Fe:haversine_generator_release || exit /b 1
|
||||||
|
|
||||||
|
cl %part2_dir%\haversine_generator.c /DHAV_PROFILER /W4 /WX /Z7 /nologo /Fe:haversine_generator_profiled_debug || exit /b 1
|
||||||
|
cl %part2_dir%\haversine_generator.c /DHAV_PROFILER /W4 /WX /Z7 /nologo /O2 /Fe:haversine_generator_profiled_release || exit /b 1
|
||||||
|
|
||||||
|
cl %part2_dir%\haversine.c /W4 /WX /Z7 /nologo /Fe:haversine_debug || exit /b 1
|
||||||
|
cl %part2_dir%\haversine.c /W4 /WX /Z7 /nologo /O2 /Fe:haversine_release || exit /b 1
|
||||||
|
|
||||||
|
cl %part2_dir%\haversine.c /DHAV_PROFILER /W4 /WX /Z7 /nologo /Fe:haversine_profiled_debug || exit /b 1
|
||||||
|
cl %part2_dir%\haversine.c /DHAV_PROFILER /W4 /WX /Z7 /nologo /O2 /Fe:haversine_profiled_release || exit /b 1
|
||||||
|
|
||||||
|
cl %part2_dir%\listing_0071_os_timer_main.cpp /W4 /WX /Z7 /O2 /nologo /Fe:listing_0071_os_timer_main_release || exit /b 1
|
||||||
|
cl %part2_dir%\listing_0072_cpu_timer_main.cpp /W4 /WX /Z7 /O2 /nologo /Fe:listing_0072_cpu_timer_main_release || exit /b 1
|
||||||
|
cl %part2_dir%\listing_0073_cpu_timer_guessfreq_main.cpp /W4 /WX /Z7 /O2 /nologo /Fe:listing_0073_cpu_timer_guessfreq_release || exit /b 1
|
||||||
|
popd
|
||||||
|
|
||||||
|
REM Part 3 /////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
REM Build //////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
:part3
|
||||||
|
pushd %part3_build_dir%
|
||||||
|
cl %part3_dir%\repetition_tester.c /W4 /WX /Z7 /nologo /Fe:repetition_tester_debug || exit /b 1
|
||||||
|
cl %part3_dir%\repetition_tester.c /W4 /WX /Z7 /nologo /O2 /Fe:repetition_tester_release || exit /b 1
|
||||||
|
popd
|
||||||
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
@echo off
|
|
||||||
|
|
||||||
REM Setup
|
|
||||||
REM ===========================================================================
|
|
||||||
set script_dir_backslash=%~dp0
|
|
||||||
set script_dir=%script_dir_backslash:~0,-1%
|
|
||||||
set build_dir=%script_dir%\build
|
|
||||||
if not exist %build_dir% mkdir %build_dir%
|
|
||||||
|
|
||||||
REM Build
|
|
||||||
REM ===========================================================================
|
|
||||||
pushd %build_dir%
|
|
||||||
cl %script_dir%\sim8086.c /W4 /WX /Z7 /nologo || exit /b 1
|
|
||||||
popd
|
|
||||||
|
|
||||||
REM Tests
|
|
||||||
REM ===========================================================================
|
|
||||||
set listing_0037=listing_0037_single_register_mov
|
|
||||||
copy /Y %script_dir%\%listing_0037% %build_dir% 1>NUL
|
|
||||||
set build_dir_listing_0037=%build_dir%\%listing_0037%
|
|
||||||
%build_dir%\sim8086.exe %build_dir_listing_0037% > %build_dir_listing_0037%_disassembled.asm
|
|
||||||
nasm %build_dir_listing_0037%_disassembled.asm
|
|
||||||
fc /B %build_dir_listing_0037% %build_dir_listing_0037%_disassembled || exit /b 1
|
|
||||||
|
|
||||||
set listing_0038=listing_0038_many_register_mov
|
|
||||||
copy /Y %script_dir%\%listing_0038% %build_dir% 1>NUL
|
|
||||||
set build_dir_listing_0038=%build_dir%\%listing_0038%
|
|
||||||
%build_dir%\sim8086.exe %build_dir_listing_0038% > %build_dir_listing_0038%_disassembled.asm
|
|
||||||
nasm %build_dir_listing_0038%_disassembled.asm
|
|
||||||
fc /B %build_dir_listing_0038% %build_dir_listing_0038%_disassembled || exit /b 1
|
|
||||||
|
|
||||||
set listing_0039=listing_0039_more_movs
|
|
||||||
copy /Y %script_dir%\%listing_0039% %build_dir% 1>NUL
|
|
||||||
set build_dir_listing_0039=%build_dir%\%listing_0039%
|
|
||||||
%build_dir%\sim8086.exe %build_dir_listing_0039% > %build_dir_listing_0039%_disassembled.asm
|
|
||||||
nasm %build_dir_listing_0039%_disassembled.asm
|
|
||||||
fc /B %build_dir_listing_0039% %build_dir_listing_0039%_disassembled || exit /b 1
|
|
||||||
|
|
||||||
set listing_0040=listing_0040_challenge_movs
|
|
||||||
copy /Y %script_dir%\%listing_0040% %build_dir% 1>NUL
|
|
||||||
set build_dir_listing_0040=%build_dir%\%listing_0040%
|
|
||||||
%build_dir%\sim8086.exe %build_dir_listing_0040% > %build_dir_listing_0040%_disassembled.asm
|
|
||||||
nasm %build_dir_listing_0040%_disassembled.asm
|
|
||||||
fc /B %build_dir_listing_0040% %build_dir_listing_0040%_disassembled || exit /b 1
|
|
||||||
|
|
||||||
set listing_0041=listing_0041_add_sub_cmp_jnz
|
|
||||||
copy /Y %script_dir%\%listing_0041% %build_dir% 1>NUL
|
|
||||||
set build_dir_listing_0041=%build_dir%\%listing_0041%
|
|
||||||
%build_dir%\sim8086.exe %build_dir_listing_0041% > %build_dir_listing_0041%_disassembled.asm
|
|
||||||
nasm %build_dir_listing_0041%_disassembled.asm
|
|
||||||
fc /B %build_dir_listing_0041% %build_dir_listing_0041%_disassembled || exit /b 1
|
|
||||||
|
|
||||||
set listing_0042=listing_0042_completionist_decode
|
|
||||||
copy /Y %script_dir%\%listing_0042% %build_dir% 1>NUL
|
|
||||||
set build_dir_listing_0042=%build_dir%\%listing_0042%
|
|
||||||
%build_dir%\sim8086.exe %build_dir_listing_0042% > %build_dir_listing_0042%_disassembled.asm
|
|
||||||
nasm %build_dir_listing_0042%_disassembled.asm
|
|
||||||
fc /B %build_dir_listing_0042% %build_dir_listing_0042%_disassembled || exit /b 1
|
|
||||||
|
|
||||||
set listing_0043=listing_0043_immediate_movs
|
|
||||||
copy /Y %script_dir%\%listing_0043% %build_dir% 1>NUL
|
|
||||||
copy /Y %script_dir%\%listing_0043%.txt %build_dir% 1>NUL
|
|
||||||
set build_dir_listing_0043=%build_dir%\%listing_0043%
|
|
||||||
%build_dir%\sim8086.exe --exec %build_dir_listing_0043% > %build_dir_listing_0043%_disassembled.txt
|
|
||||||
fc /N %build_dir_listing_0043%.txt %build_dir_listing_0043%_disassembled.txt || exit /b 1
|
|
||||||
|
|
||||||
set listing_0044=listing_0044_register_movs
|
|
||||||
copy /Y %script_dir%\%listing_0044% %build_dir% 1>NUL
|
|
||||||
copy /Y %script_dir%\%listing_0044%.txt %build_dir% 1>NUL
|
|
||||||
set build_dir_listing_0044=%build_dir%\%listing_0044%
|
|
||||||
%build_dir%\sim8086.exe --exec %build_dir_listing_0044% > %build_dir_listing_0044%_disassembled.txt
|
|
||||||
fc /N %build_dir_listing_0044%.txt %build_dir_listing_0044%_disassembled.txt || exit /b 1
|
|
||||||
|
|
||||||
set listing_0045=listing_0045_challenge_register_movs
|
|
||||||
copy /Y %script_dir%\%listing_0045% %build_dir% 1>NUL
|
|
||||||
copy /Y %script_dir%\%listing_0045%.txt %build_dir% 1>NUL
|
|
||||||
set build_dir_listing_0045=%build_dir%\%listing_0045%
|
|
||||||
%build_dir%\sim8086.exe --exec %build_dir_listing_0045% > %build_dir_listing_0045%_disassembled.txt
|
|
||||||
fc /N %build_dir_listing_0045%.txt %build_dir_listing_0045%_disassembled.txt || exit /b 1
|
|
||||||
set listing_0045=listing_0045_challenge_register_movs
|
|
||||||
|
|
||||||
set listing_0046=listing_0046_add_sub_cmp
|
|
||||||
copy /Y %script_dir%\%listing_0046% %build_dir% 1>NUL
|
|
||||||
copy /Y %script_dir%\%listing_0046%.txt %build_dir% 1>NUL
|
|
||||||
set build_dir_listing_0046=%build_dir%\%listing_0046%
|
|
||||||
%build_dir%\sim8086.exe --exec %build_dir_listing_0046% > %build_dir_listing_0046%_disassembled.txt
|
|
||||||
fc /N %build_dir_listing_0046%.txt %build_dir_listing_0046%_disassembled.txt || exit /b 1
|
|
||||||
Binary file not shown.
@@ -0,0 +1,36 @@
|
|||||||
|
; ========================================================================
|
||||||
|
;
|
||||||
|
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
;
|
||||||
|
; This software is provided 'as-is', without any express or implied
|
||||||
|
; warranty. In no event will the authors be held liable for any damages
|
||||||
|
; arising from the use of this software.
|
||||||
|
;
|
||||||
|
; Please see https://computerenhance.com for further information
|
||||||
|
;
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
; ========================================================================
|
||||||
|
; LISTING 47
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
bits 16
|
||||||
|
|
||||||
|
add bx, 30000
|
||||||
|
add bx, 10000
|
||||||
|
sub bx, 5000
|
||||||
|
sub bx, 5000
|
||||||
|
|
||||||
|
mov bx, 1
|
||||||
|
mov cx, 100
|
||||||
|
add bx, cx
|
||||||
|
|
||||||
|
mov dx, 10
|
||||||
|
sub cx, dx
|
||||||
|
|
||||||
|
add bx, 40000
|
||||||
|
add cx, -90
|
||||||
|
|
||||||
|
mov sp, 99
|
||||||
|
mov bp, 98
|
||||||
|
cmp bp, sp
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
--- test\listing_0047_challenge_flags execution ---
|
||||||
|
add bx, 30000 ; bx:0x0->0x7530 flags:->P
|
||||||
|
add bx, 10000 ; bx:0x7530->0x9c40 flags:P->SO
|
||||||
|
sub bx, 5000 ; bx:0x9c40->0x88b8 flags:SO->PAS
|
||||||
|
sub bx, 5000 ; bx:0x88b8->0x7530 flags:PAS->PO
|
||||||
|
mov bx, 1 ; bx:0x7530->0x1
|
||||||
|
mov cx, 100 ; cx:0x0->0x64
|
||||||
|
add bx, cx ; bx:0x1->0x65 flags:PO->P
|
||||||
|
mov dx, 10 ; dx:0x0->0xa
|
||||||
|
sub cx, dx ; cx:0x64->0x5a flags:P->PA
|
||||||
|
add bx, 40000 ; bx:0x65->0x9ca5 flags:PA->PS
|
||||||
|
add cx, -90 ; cx:0x5a->0x0 flags:PS->CPAZ
|
||||||
|
mov sp, 99 ; sp:0x0->0x63
|
||||||
|
mov bp, 98 ; bp:0x0->0x62
|
||||||
|
cmp bp, sp ; flags:CPAZ->CPAS
|
||||||
|
|
||||||
|
Final registers:
|
||||||
|
bx: 0x9ca5 (40101)
|
||||||
|
dx: 0x000a (10)
|
||||||
|
sp: 0x0063 (99)
|
||||||
|
bp: 0x0062 (98)
|
||||||
|
flags: CPAS
|
||||||
|
|
||||||
Binary file not shown.
@@ -0,0 +1,23 @@
|
|||||||
|
; ========================================================================
|
||||||
|
;
|
||||||
|
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
;
|
||||||
|
; This software is provided 'as-is', without any express or implied
|
||||||
|
; warranty. In no event will the authors be held liable for any damages
|
||||||
|
; arising from the use of this software.
|
||||||
|
;
|
||||||
|
; Please see https://computerenhance.com for further information
|
||||||
|
;
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
; ========================================================================
|
||||||
|
; LISTING 48
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
bits 16
|
||||||
|
|
||||||
|
mov cx, 200
|
||||||
|
mov bx, cx
|
||||||
|
add cx, 1000
|
||||||
|
mov bx, 2000
|
||||||
|
sub cx, bx
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
--- test\listing_0048_ip_register execution ---
|
||||||
|
mov cx, 200 ; cx:0x0->0xc8 ip:0x0->0x3
|
||||||
|
mov bx, cx ; bx:0x0->0xc8 ip:0x3->0x5
|
||||||
|
add cx, 1000 ; cx:0xc8->0x4b0 ip:0x5->0x9 flags:->A
|
||||||
|
mov bx, 2000 ; bx:0xc8->0x7d0 ip:0x9->0xc
|
||||||
|
sub cx, bx ; cx:0x4b0->0xfce0 ip:0xc->0xe flags:A->CS
|
||||||
|
|
||||||
|
Final registers:
|
||||||
|
bx: 0x07d0 (2000)
|
||||||
|
cx: 0xfce0 (64736)
|
||||||
|
ip: 0x000e (14)
|
||||||
|
flags: CS
|
||||||
|
|
||||||
Binary file not shown.
@@ -0,0 +1,24 @@
|
|||||||
|
; ========================================================================
|
||||||
|
;
|
||||||
|
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
;
|
||||||
|
; This software is provided 'as-is', without any express or implied
|
||||||
|
; warranty. In no event will the authors be held liable for any damages
|
||||||
|
; arising from the use of this software.
|
||||||
|
;
|
||||||
|
; Please see https://computerenhance.com for further information
|
||||||
|
;
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
; ========================================================================
|
||||||
|
; LISTING 49
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
bits 16
|
||||||
|
|
||||||
|
mov cx, 3
|
||||||
|
mov bx, 1000
|
||||||
|
loop_start:
|
||||||
|
add bx, 10
|
||||||
|
sub cx, 1
|
||||||
|
jnz loop_start
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
--- test\listing_0049_conditional_jumps execution ---
|
||||||
|
mov cx, 3 ; cx:0x0->0x3 ip:0x0->0x3
|
||||||
|
mov bx, 1000 ; bx:0x0->0x3e8 ip:0x3->0x6
|
||||||
|
add bx, 10 ; bx:0x3e8->0x3f2 ip:0x6->0x9 flags:->A
|
||||||
|
sub cx, 1 ; cx:0x3->0x2 ip:0x9->0xc flags:A->
|
||||||
|
jne $-6 ; ip:0xc->0x6
|
||||||
|
add bx, 10 ; bx:0x3f2->0x3fc ip:0x6->0x9 flags:->P
|
||||||
|
sub cx, 1 ; cx:0x2->0x1 ip:0x9->0xc flags:P->
|
||||||
|
jne $-6 ; ip:0xc->0x6
|
||||||
|
add bx, 10 ; bx:0x3fc->0x406 ip:0x6->0x9 flags:->PA
|
||||||
|
sub cx, 1 ; cx:0x1->0x0 ip:0x9->0xc flags:PA->PZ
|
||||||
|
jne $-6 ; ip:0xc->0xe
|
||||||
|
|
||||||
|
Final registers:
|
||||||
|
bx: 0x0406 (1030)
|
||||||
|
ip: 0x000e (14)
|
||||||
|
flags: PZ
|
||||||
|
|
||||||
Binary file not shown.
@@ -0,0 +1,38 @@
|
|||||||
|
; ========================================================================
|
||||||
|
;
|
||||||
|
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
;
|
||||||
|
; This software is provided 'as-is', without any express or implied
|
||||||
|
; warranty. In no event will the authors be held liable for any damages
|
||||||
|
; arising from the use of this software.
|
||||||
|
;
|
||||||
|
; Please see https://computerenhance.com for further information
|
||||||
|
;
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
; ========================================================================
|
||||||
|
; LISTING 50
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
bits 16
|
||||||
|
|
||||||
|
mov ax, 10
|
||||||
|
mov bx, 10
|
||||||
|
mov cx, 10
|
||||||
|
|
||||||
|
label_0:
|
||||||
|
cmp bx, cx
|
||||||
|
je label_1
|
||||||
|
|
||||||
|
add ax, 1
|
||||||
|
jp label_2
|
||||||
|
|
||||||
|
label_1:
|
||||||
|
sub bx, 5
|
||||||
|
jb label_3
|
||||||
|
|
||||||
|
label_2:
|
||||||
|
sub cx, 2
|
||||||
|
|
||||||
|
label_3:
|
||||||
|
loopnz label_0
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
--- test\listing_0050_challenge_jumps execution ---
|
||||||
|
mov ax, 10 ; ax:0x0->0xa ip:0x0->0x3
|
||||||
|
mov bx, 10 ; bx:0x0->0xa ip:0x3->0x6
|
||||||
|
mov cx, 10 ; cx:0x0->0xa ip:0x6->0x9
|
||||||
|
cmp bx, cx ; ip:0x9->0xb flags:->PZ
|
||||||
|
je $+7 ; ip:0xb->0x12
|
||||||
|
sub bx, 5 ; bx:0xa->0x5 ip:0x12->0x15 flags:PZ->P
|
||||||
|
jb $+5 ; ip:0x15->0x17
|
||||||
|
sub cx, 2 ; cx:0xa->0x8 ip:0x17->0x1a flags:P->
|
||||||
|
loopnz $-17 ; cx:0x8->0x7 ip:0x1a->0x9
|
||||||
|
cmp bx, cx ; ip:0x9->0xb flags:->CAS
|
||||||
|
je $+7 ; ip:0xb->0xd
|
||||||
|
add ax, 1 ; ax:0xa->0xb ip:0xd->0x10 flags:CAS->
|
||||||
|
jp $+7 ; ip:0x10->0x12
|
||||||
|
sub bx, 5 ; bx:0x5->0x0 ip:0x12->0x15 flags:->PZ
|
||||||
|
jb $+5 ; ip:0x15->0x17
|
||||||
|
sub cx, 2 ; cx:0x7->0x5 ip:0x17->0x1a flags:PZ->P
|
||||||
|
loopnz $-17 ; cx:0x5->0x4 ip:0x1a->0x9
|
||||||
|
cmp bx, cx ; ip:0x9->0xb flags:P->CPAS
|
||||||
|
je $+7 ; ip:0xb->0xd
|
||||||
|
add ax, 1 ; ax:0xb->0xc ip:0xd->0x10 flags:CPAS->P
|
||||||
|
jp $+7 ; ip:0x10->0x17
|
||||||
|
sub cx, 2 ; cx:0x4->0x2 ip:0x17->0x1a flags:P->
|
||||||
|
loopnz $-17 ; cx:0x2->0x1 ip:0x1a->0x9
|
||||||
|
cmp bx, cx ; ip:0x9->0xb flags:->CPAS
|
||||||
|
je $+7 ; ip:0xb->0xd
|
||||||
|
add ax, 1 ; ax:0xc->0xd ip:0xd->0x10 flags:CPAS->
|
||||||
|
jp $+7 ; ip:0x10->0x12
|
||||||
|
sub bx, 5 ; bx:0x0->0xfffb ip:0x12->0x15 flags:->CAS
|
||||||
|
jb $+5 ; ip:0x15->0x1a
|
||||||
|
loopnz $-17 ; cx:0x1->0x0 ip:0x1a->0x1c
|
||||||
|
|
||||||
|
Final registers:
|
||||||
|
ax: 0x000d (13)
|
||||||
|
bx: 0xfffb (65531)
|
||||||
|
ip: 0x001c (28)
|
||||||
|
flags: CAS
|
||||||
|
|
||||||
Binary file not shown.
@@ -0,0 +1,30 @@
|
|||||||
|
; ========================================================================
|
||||||
|
;
|
||||||
|
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
;
|
||||||
|
; This software is provided 'as-is', without any express or implied
|
||||||
|
; warranty. In no event will the authors be held liable for any damages
|
||||||
|
; arising from the use of this software.
|
||||||
|
;
|
||||||
|
; Please see https://computerenhance.com for further information
|
||||||
|
;
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
; ========================================================================
|
||||||
|
; LISTING 51
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
bits 16
|
||||||
|
|
||||||
|
mov word [1000], 1
|
||||||
|
mov word [1002], 2
|
||||||
|
mov word [1004], 3
|
||||||
|
mov word [1006], 4
|
||||||
|
|
||||||
|
mov bx, 1000
|
||||||
|
mov word [bx + 4], 10
|
||||||
|
|
||||||
|
mov bx, word [1000]
|
||||||
|
mov cx, word [1002]
|
||||||
|
mov dx, word [1004]
|
||||||
|
mov bp, word [1006]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
--- test\listing_0051_memory_mov execution ---
|
||||||
|
mov word [+1000], 1 ; ip:0x0->0x6
|
||||||
|
mov word [+1002], 2 ; ip:0x6->0xc
|
||||||
|
mov word [+1004], 3 ; ip:0xc->0x12
|
||||||
|
mov word [+1006], 4 ; ip:0x12->0x18
|
||||||
|
mov bx, 1000 ; bx:0x0->0x3e8 ip:0x18->0x1b
|
||||||
|
mov word [bx+4], 10 ; ip:0x1b->0x20
|
||||||
|
mov bx, [+1000] ; bx:0x3e8->0x1 ip:0x20->0x24
|
||||||
|
mov cx, [+1002] ; cx:0x0->0x2 ip:0x24->0x28
|
||||||
|
mov dx, [+1004] ; dx:0x0->0xa ip:0x28->0x2c
|
||||||
|
mov bp, [+1006] ; bp:0x0->0x4 ip:0x2c->0x30
|
||||||
|
|
||||||
|
Final registers:
|
||||||
|
bx: 0x0001 (1)
|
||||||
|
cx: 0x0002 (2)
|
||||||
|
dx: 0x000a (10)
|
||||||
|
bp: 0x0004 (4)
|
||||||
|
ip: 0x0030 (48)
|
||||||
|
|
||||||
Binary file not shown.
@@ -0,0 +1,36 @@
|
|||||||
|
; ========================================================================
|
||||||
|
;
|
||||||
|
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
;
|
||||||
|
; This software is provided 'as-is', without any express or implied
|
||||||
|
; warranty. In no event will the authors be held liable for any damages
|
||||||
|
; arising from the use of this software.
|
||||||
|
;
|
||||||
|
; Please see https://computerenhance.com for further information
|
||||||
|
;
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
; ========================================================================
|
||||||
|
; LISTING 52
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
bits 16
|
||||||
|
|
||||||
|
mov dx, 6
|
||||||
|
mov bp, 1000
|
||||||
|
|
||||||
|
mov si, 0
|
||||||
|
init_loop_start:
|
||||||
|
mov word [bp + si], si
|
||||||
|
add si, 2
|
||||||
|
cmp si, dx
|
||||||
|
jnz init_loop_start
|
||||||
|
|
||||||
|
mov bx, 0
|
||||||
|
mov si, 0
|
||||||
|
add_loop_start:
|
||||||
|
mov cx, word [bp + si]
|
||||||
|
add bx, cx
|
||||||
|
add si, 2
|
||||||
|
cmp si, dx
|
||||||
|
jnz add_loop_start
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
--- test\listing_0052_memory_add_loop execution ---
|
||||||
|
mov dx, 6 ; dx:0x0->0x6 ip:0x0->0x3
|
||||||
|
mov bp, 1000 ; bp:0x0->0x3e8 ip:0x3->0x6
|
||||||
|
mov si, 0 ; ip:0x6->0x9
|
||||||
|
mov word [bp+si], si ; ip:0x9->0xb
|
||||||
|
add si, 2 ; si:0x0->0x2 ip:0xb->0xe
|
||||||
|
cmp si, dx ; ip:0xe->0x10 flags:->CPAS
|
||||||
|
jne $-7 ; ip:0x10->0x9
|
||||||
|
mov word [bp+si], si ; ip:0x9->0xb
|
||||||
|
add si, 2 ; si:0x2->0x4 ip:0xb->0xe flags:CPAS->
|
||||||
|
cmp si, dx ; ip:0xe->0x10 flags:->CAS
|
||||||
|
jne $-7 ; ip:0x10->0x9
|
||||||
|
mov word [bp+si], si ; ip:0x9->0xb
|
||||||
|
add si, 2 ; si:0x4->0x6 ip:0xb->0xe flags:CAS->P
|
||||||
|
cmp si, dx ; ip:0xe->0x10 flags:P->PZ
|
||||||
|
jne $-7 ; ip:0x10->0x12
|
||||||
|
mov bx, 0 ; ip:0x12->0x15
|
||||||
|
mov si, 0 ; si:0x6->0x0 ip:0x15->0x18
|
||||||
|
mov cx, [bp+si] ; ip:0x18->0x1a
|
||||||
|
add bx, cx ; ip:0x1a->0x1c
|
||||||
|
add si, 2 ; si:0x0->0x2 ip:0x1c->0x1f flags:PZ->
|
||||||
|
cmp si, dx ; ip:0x1f->0x21 flags:->CPAS
|
||||||
|
jne $-9 ; ip:0x21->0x18
|
||||||
|
mov cx, [bp+si] ; cx:0x0->0x2 ip:0x18->0x1a
|
||||||
|
add bx, cx ; bx:0x0->0x2 ip:0x1a->0x1c flags:CPAS->
|
||||||
|
add si, 2 ; si:0x2->0x4 ip:0x1c->0x1f
|
||||||
|
cmp si, dx ; ip:0x1f->0x21 flags:->CAS
|
||||||
|
jne $-9 ; ip:0x21->0x18
|
||||||
|
mov cx, [bp+si] ; cx:0x2->0x4 ip:0x18->0x1a
|
||||||
|
add bx, cx ; bx:0x2->0x6 ip:0x1a->0x1c flags:CAS->P
|
||||||
|
add si, 2 ; si:0x4->0x6 ip:0x1c->0x1f
|
||||||
|
cmp si, dx ; ip:0x1f->0x21 flags:P->PZ
|
||||||
|
jne $-9 ; ip:0x21->0x23
|
||||||
|
|
||||||
|
Final registers:
|
||||||
|
bx: 0x0006 (6)
|
||||||
|
cx: 0x0004 (4)
|
||||||
|
dx: 0x0006 (6)
|
||||||
|
bp: 0x03e8 (1000)
|
||||||
|
si: 0x0006 (6)
|
||||||
|
ip: 0x0023 (35)
|
||||||
|
flags: PZ
|
||||||
|
|
||||||
Binary file not shown.
@@ -0,0 +1,35 @@
|
|||||||
|
; ========================================================================
|
||||||
|
;
|
||||||
|
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
;
|
||||||
|
; This software is provided 'as-is', without any express or implied
|
||||||
|
; warranty. In no event will the authors be held liable for any damages
|
||||||
|
; arising from the use of this software.
|
||||||
|
;
|
||||||
|
; Please see https://computerenhance.com for further information
|
||||||
|
;
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
; ========================================================================
|
||||||
|
; LISTING 53
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
bits 16
|
||||||
|
|
||||||
|
mov dx, 6
|
||||||
|
mov bp, 1000
|
||||||
|
|
||||||
|
mov si, 0
|
||||||
|
init_loop_start:
|
||||||
|
mov word [bp + si], si
|
||||||
|
add si, 2
|
||||||
|
cmp si, dx
|
||||||
|
jnz init_loop_start
|
||||||
|
|
||||||
|
mov bx, 0
|
||||||
|
mov si, dx
|
||||||
|
sub bp, 2
|
||||||
|
add_loop_start:
|
||||||
|
add bx, word [bp + si]
|
||||||
|
sub si, 2
|
||||||
|
jnz add_loop_start
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
--- test\listing_0053_add_loop_challenge execution ---
|
||||||
|
mov dx, 6 ; dx:0x0->0x6 ip:0x0->0x3
|
||||||
|
mov bp, 1000 ; bp:0x0->0x3e8 ip:0x3->0x6
|
||||||
|
mov si, 0 ; ip:0x6->0x9
|
||||||
|
mov word [bp+si], si ; ip:0x9->0xb
|
||||||
|
add si, 2 ; si:0x0->0x2 ip:0xb->0xe
|
||||||
|
cmp si, dx ; ip:0xe->0x10 flags:->CPAS
|
||||||
|
jne $-7 ; ip:0x10->0x9
|
||||||
|
mov word [bp+si], si ; ip:0x9->0xb
|
||||||
|
add si, 2 ; si:0x2->0x4 ip:0xb->0xe flags:CPAS->
|
||||||
|
cmp si, dx ; ip:0xe->0x10 flags:->CAS
|
||||||
|
jne $-7 ; ip:0x10->0x9
|
||||||
|
mov word [bp+si], si ; ip:0x9->0xb
|
||||||
|
add si, 2 ; si:0x4->0x6 ip:0xb->0xe flags:CAS->P
|
||||||
|
cmp si, dx ; ip:0xe->0x10 flags:P->PZ
|
||||||
|
jne $-7 ; ip:0x10->0x12
|
||||||
|
mov bx, 0 ; ip:0x12->0x15
|
||||||
|
mov si, dx ; ip:0x15->0x17
|
||||||
|
sub bp, 2 ; bp:0x3e8->0x3e6 ip:0x17->0x1a flags:PZ->
|
||||||
|
add bx, [bp+si] ; bx:0x0->0x4 ip:0x1a->0x1c
|
||||||
|
sub si, 2 ; si:0x6->0x4 ip:0x1c->0x1f
|
||||||
|
jne $-5 ; ip:0x1f->0x1a
|
||||||
|
add bx, [bp+si] ; bx:0x4->0x6 ip:0x1a->0x1c flags:->P
|
||||||
|
sub si, 2 ; si:0x4->0x2 ip:0x1c->0x1f flags:P->
|
||||||
|
jne $-5 ; ip:0x1f->0x1a
|
||||||
|
add bx, [bp+si] ; ip:0x1a->0x1c flags:->P
|
||||||
|
sub si, 2 ; si:0x2->0x0 ip:0x1c->0x1f flags:P->PZ
|
||||||
|
jne $-5 ; ip:0x1f->0x21
|
||||||
|
|
||||||
|
Final registers:
|
||||||
|
bx: 0x0006 (6)
|
||||||
|
dx: 0x0006 (6)
|
||||||
|
bp: 0x03e6 (998)
|
||||||
|
ip: 0x0021 (33)
|
||||||
|
flags: PZ
|
||||||
|
|
||||||
Binary file not shown.
@@ -0,0 +1,43 @@
|
|||||||
|
; ========================================================================
|
||||||
|
;
|
||||||
|
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
;
|
||||||
|
; This software is provided 'as-is', without any express or implied
|
||||||
|
; warranty. In no event will the authors be held liable for any damages
|
||||||
|
; arising from the use of this software.
|
||||||
|
;
|
||||||
|
; Please see https://computerenhance.com for further information
|
||||||
|
;
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
; ========================================================================
|
||||||
|
; LISTING 54
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
bits 16
|
||||||
|
|
||||||
|
; Start image after one row, to avoid overwriting our code!
|
||||||
|
mov bp, 64*4
|
||||||
|
|
||||||
|
mov dx, 0
|
||||||
|
y_loop_start:
|
||||||
|
|
||||||
|
mov cx, 0
|
||||||
|
x_loop_start:
|
||||||
|
; Fill pixel
|
||||||
|
mov word [bp + 0], cx ; Red
|
||||||
|
mov word [bp + 2], dx ; Blue
|
||||||
|
mov byte [bp + 3], 255 ; Alpha
|
||||||
|
|
||||||
|
; Advance pixel location
|
||||||
|
add bp, 4
|
||||||
|
|
||||||
|
; Advance X coordinate and loop
|
||||||
|
add cx, 1
|
||||||
|
cmp cx, 64
|
||||||
|
jnz x_loop_start
|
||||||
|
|
||||||
|
; Advance Y coordinate and loop
|
||||||
|
add dx, 1
|
||||||
|
cmp dx, 64
|
||||||
|
jnz y_loop_start
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,53 @@
|
|||||||
|
; ========================================================================
|
||||||
|
;
|
||||||
|
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
;
|
||||||
|
; This software is provided 'as-is', without any express or implied
|
||||||
|
; warranty. In no event will the authors be held liable for any damages
|
||||||
|
; arising from the use of this software.
|
||||||
|
;
|
||||||
|
; Please see https://computerenhance.com for further information
|
||||||
|
;
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
; ========================================================================
|
||||||
|
; LISTING 55
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
bits 16
|
||||||
|
|
||||||
|
; Start image after one row, to avoid overwriting our code!
|
||||||
|
mov bp, 64*4
|
||||||
|
|
||||||
|
; Draw the solid rectangle red/blue/alpha
|
||||||
|
mov dx, 64
|
||||||
|
y_loop_start:
|
||||||
|
|
||||||
|
mov cx, 64
|
||||||
|
x_loop_start:
|
||||||
|
mov byte [bp + 0], cl ; Red
|
||||||
|
mov byte [bp + 1], 0 ; Green
|
||||||
|
mov byte [bp + 2], dl ; Blue
|
||||||
|
mov byte [bp + 3], 255 ; Alpha
|
||||||
|
add bp, 4
|
||||||
|
|
||||||
|
loop x_loop_start
|
||||||
|
|
||||||
|
sub dx, 1
|
||||||
|
jnz y_loop_start
|
||||||
|
|
||||||
|
; Add the line rectangle green
|
||||||
|
mov bp, 64*4 + 4*64 + 4
|
||||||
|
mov bx, bp
|
||||||
|
mov cx, 62
|
||||||
|
outline_loop_start:
|
||||||
|
|
||||||
|
mov byte [bp + 1], 255 ; Top line
|
||||||
|
mov byte [bp + 61*64*4 + 1], 255 ; Bottom line
|
||||||
|
mov byte [bx + 1], 255 ; Left line
|
||||||
|
mov byte [bx + 61*4 + 1], 255 ; Right line
|
||||||
|
|
||||||
|
add bp, 4
|
||||||
|
add bx, 4*64
|
||||||
|
|
||||||
|
loop outline_loop_start
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,41 @@
|
|||||||
|
; ========================================================================
|
||||||
|
;
|
||||||
|
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
;
|
||||||
|
; This software is provided 'as-is', without any express or implied
|
||||||
|
; warranty. In no event will the authors be held liable for any damages
|
||||||
|
; arising from the use of this software.
|
||||||
|
;
|
||||||
|
; Please see https://computerenhance.com for further information
|
||||||
|
;
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
; ========================================================================
|
||||||
|
; LISTING 56
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
bits 16
|
||||||
|
|
||||||
|
mov bx, 1000
|
||||||
|
mov bp, 2000
|
||||||
|
mov si, 3000
|
||||||
|
mov di, 4000
|
||||||
|
|
||||||
|
mov cx, bx
|
||||||
|
mov dx, 12
|
||||||
|
|
||||||
|
mov dx, [1000]
|
||||||
|
|
||||||
|
mov cx, [bx]
|
||||||
|
mov cx, [bp]
|
||||||
|
mov [si], cx
|
||||||
|
mov [di], cx
|
||||||
|
|
||||||
|
mov cx, [bx + 1000]
|
||||||
|
mov cx, [bp + 1000]
|
||||||
|
mov [si + 1000], cx
|
||||||
|
mov [di + 1000], cx
|
||||||
|
|
||||||
|
add cx, dx
|
||||||
|
add [di + 1000], cx
|
||||||
|
add dx, 50
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
**************
|
||||||
|
**** 8086 ****
|
||||||
|
**************
|
||||||
|
|
||||||
|
WARNING: Clocks reported by this utility are strictly from the 8086 manual.
|
||||||
|
They will be inaccurate, both because the manual clocks are estimates, and because
|
||||||
|
some of the entries in the manual look highly suspicious and are probably typos.
|
||||||
|
|
||||||
|
--- test\listing_0056_estimating_cycles execution ---
|
||||||
|
mov bx, 1000 ; Clocks: +4 = 4 | bx:0x0->0x3e8 ip:0x0->0x3
|
||||||
|
mov bp, 2000 ; Clocks: +4 = 8 | bp:0x0->0x7d0 ip:0x3->0x6
|
||||||
|
mov si, 3000 ; Clocks: +4 = 12 | si:0x0->0xbb8 ip:0x6->0x9
|
||||||
|
mov di, 4000 ; Clocks: +4 = 16 | di:0x0->0xfa0 ip:0x9->0xc
|
||||||
|
mov cx, bx ; Clocks: +2 = 18 | cx:0x0->0x3e8 ip:0xc->0xe
|
||||||
|
mov dx, 12 ; Clocks: +4 = 22 | dx:0x0->0xc ip:0xe->0x11
|
||||||
|
mov dx, [+1000] ; Clocks: +14 = 36 (8 + 6ea) | dx:0xc->0x0 ip:0x11->0x15
|
||||||
|
mov cx, [bx] ; Clocks: +13 = 49 (8 + 5ea) | cx:0x3e8->0x0 ip:0x15->0x17
|
||||||
|
mov cx, [bp] ; Clocks: +13 = 62 (8 + 5ea) | ip:0x17->0x1a
|
||||||
|
mov word [si], cx ; Clocks: +14 = 76 (9 + 5ea) | ip:0x1a->0x1c
|
||||||
|
mov word [di], cx ; Clocks: +14 = 90 (9 + 5ea) | ip:0x1c->0x1e
|
||||||
|
mov cx, [bx+1000] ; Clocks: +17 = 107 (8 + 9ea) | ip:0x1e->0x22
|
||||||
|
mov cx, [bp+1000] ; Clocks: +17 = 124 (8 + 9ea) | ip:0x22->0x26
|
||||||
|
mov word [si+1000], cx ; Clocks: +18 = 142 (9 + 9ea) | ip:0x26->0x2a
|
||||||
|
mov word [di+1000], cx ; Clocks: +18 = 160 (9 + 9ea) | ip:0x2a->0x2e
|
||||||
|
add cx, dx ; Clocks: +3 = 163 | ip:0x2e->0x30 flags:->PZ
|
||||||
|
add word [di+1000], cx ; Clocks: +25 = 188 (16 + 9ea) | ip:0x30->0x34
|
||||||
|
add dx, 50 ; Clocks: +4 = 192 | dx:0x0->0x32 ip:0x34->0x37 flags:PZ->
|
||||||
|
|
||||||
|
Final registers:
|
||||||
|
bx: 0x03e8 (1000)
|
||||||
|
dx: 0x0032 (50)
|
||||||
|
bp: 0x07d0 (2000)
|
||||||
|
si: 0x0bb8 (3000)
|
||||||
|
di: 0x0fa0 (4000)
|
||||||
|
ip: 0x0037 (55)
|
||||||
|
|
||||||
|
**************
|
||||||
|
**** 8088 ****
|
||||||
|
**************
|
||||||
|
|
||||||
|
WARNING: Clocks reported by this utility are strictly from the 8086 manual.
|
||||||
|
They will be inaccurate, both because the manual clocks are estimates, and because
|
||||||
|
some of the entries in the manual look highly suspicious and are probably typos.
|
||||||
|
|
||||||
|
--- test\listing_0056_estimating_cycles execution ---
|
||||||
|
mov bx, 1000 ; Clocks: +4 = 4 | bx:0x0->0x3e8 ip:0x0->0x3
|
||||||
|
mov bp, 2000 ; Clocks: +4 = 8 | bp:0x0->0x7d0 ip:0x3->0x6
|
||||||
|
mov si, 3000 ; Clocks: +4 = 12 | si:0x0->0xbb8 ip:0x6->0x9
|
||||||
|
mov di, 4000 ; Clocks: +4 = 16 | di:0x0->0xfa0 ip:0x9->0xc
|
||||||
|
mov cx, bx ; Clocks: +2 = 18 | cx:0x0->0x3e8 ip:0xc->0xe
|
||||||
|
mov dx, 12 ; Clocks: +4 = 22 | dx:0x0->0xc ip:0xe->0x11
|
||||||
|
mov dx, [+1000] ; Clocks: +18 = 40 (8 + 6ea + 4p) | dx:0xc->0x0 ip:0x11->0x15
|
||||||
|
mov cx, [bx] ; Clocks: +17 = 57 (8 + 5ea + 4p) | cx:0x3e8->0x0 ip:0x15->0x17
|
||||||
|
mov cx, [bp] ; Clocks: +17 = 74 (8 + 5ea + 4p) | ip:0x17->0x1a
|
||||||
|
mov word [si], cx ; Clocks: +18 = 92 (9 + 5ea + 4p) | ip:0x1a->0x1c
|
||||||
|
mov word [di], cx ; Clocks: +18 = 110 (9 + 5ea + 4p) | ip:0x1c->0x1e
|
||||||
|
mov cx, [bx+1000] ; Clocks: +21 = 131 (8 + 9ea + 4p) | ip:0x1e->0x22
|
||||||
|
mov cx, [bp+1000] ; Clocks: +21 = 152 (8 + 9ea + 4p) | ip:0x22->0x26
|
||||||
|
mov word [si+1000], cx ; Clocks: +22 = 174 (9 + 9ea + 4p) | ip:0x26->0x2a
|
||||||
|
mov word [di+1000], cx ; Clocks: +22 = 196 (9 + 9ea + 4p) | ip:0x2a->0x2e
|
||||||
|
add cx, dx ; Clocks: +3 = 199 | ip:0x2e->0x30 flags:->PZ
|
||||||
|
add word [di+1000], cx ; Clocks: +33 = 232 (16 + 9ea + 8p) | ip:0x30->0x34
|
||||||
|
add dx, 50 ; Clocks: +4 = 236 | dx:0x0->0x32 ip:0x34->0x37 flags:PZ->
|
||||||
|
|
||||||
|
Final registers:
|
||||||
|
bx: 0x03e8 (1000)
|
||||||
|
dx: 0x0032 (50)
|
||||||
|
bp: 0x07d0 (2000)
|
||||||
|
si: 0x0bb8 (3000)
|
||||||
|
di: 0x0fa0 (4000)
|
||||||
|
ip: 0x0037 (55)
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
»иЅРѕёї ‹‰‹
|
||||||
|
‰ ‹‹и‰€и‹Љи‰‰и’иѓL’й•зѓK
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
; ========================================================================
|
||||||
|
;
|
||||||
|
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
;
|
||||||
|
; This software is provided 'as-is', without any express or implied
|
||||||
|
; warranty. In no event will the authors be held liable for any damages
|
||||||
|
; arising from the use of this software.
|
||||||
|
;
|
||||||
|
; Please see https://computerenhance.com for further information
|
||||||
|
;
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
; ========================================================================
|
||||||
|
; LISTING 57
|
||||||
|
; ========================================================================
|
||||||
|
|
||||||
|
bits 16
|
||||||
|
|
||||||
|
mov bx, 1000
|
||||||
|
mov bp, 2000
|
||||||
|
mov si, 3000
|
||||||
|
mov di, 4000
|
||||||
|
|
||||||
|
mov cx, [bp + di]
|
||||||
|
mov [bx + si], cx
|
||||||
|
|
||||||
|
mov cx, [bp + si]
|
||||||
|
mov [bx + di], cx
|
||||||
|
|
||||||
|
mov cx, [bp + di + 1000]
|
||||||
|
mov [bx + si + 1000], cx
|
||||||
|
|
||||||
|
mov cx, [bp + si + 1000]
|
||||||
|
mov [bx + di + 1000], cx
|
||||||
|
|
||||||
|
add dx, [bp + si + 1000]
|
||||||
|
|
||||||
|
add word [bp + si], 76
|
||||||
|
|
||||||
|
add dx, [bp + si + 1001]
|
||||||
|
add [di + 999], dx
|
||||||
|
add word [bp + si], 75
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
**************
|
||||||
|
**** 8086 ****
|
||||||
|
**************
|
||||||
|
|
||||||
|
WARNING: Clocks reported by this utility are strictly from the 8086 manual.
|
||||||
|
They will be inaccurate, both because the manual clocks are estimates, and because
|
||||||
|
some of the entries in the manual look highly suspicious and are probably typos.
|
||||||
|
|
||||||
|
--- test\listing_0057_challenge_cycles execution ---
|
||||||
|
mov bx, 1000 ; Clocks: +4 = 4 | bx:0x0->0x3e8 ip:0x0->0x3
|
||||||
|
mov bp, 2000 ; Clocks: +4 = 8 | bp:0x0->0x7d0 ip:0x3->0x6
|
||||||
|
mov si, 3000 ; Clocks: +4 = 12 | si:0x0->0xbb8 ip:0x6->0x9
|
||||||
|
mov di, 4000 ; Clocks: +4 = 16 | di:0x0->0xfa0 ip:0x9->0xc
|
||||||
|
mov cx, [bp+di] ; Clocks: +15 = 31 (8 + 7ea) | ip:0xc->0xe
|
||||||
|
mov word [bx+si], cx ; Clocks: +16 = 47 (9 + 7ea) | ip:0xe->0x10
|
||||||
|
mov cx, [bp+si] ; Clocks: +16 = 63 (8 + 8ea) | ip:0x10->0x12
|
||||||
|
mov word [bx+di], cx ; Clocks: +17 = 80 (9 + 8ea) | ip:0x12->0x14
|
||||||
|
mov cx, [bp+di+1000] ; Clocks: +19 = 99 (8 + 11ea) | ip:0x14->0x18
|
||||||
|
mov word [bx+si+1000], cx ; Clocks: +20 = 119 (9 + 11ea) | ip:0x18->0x1c
|
||||||
|
mov cx, [bp+si+1000] ; Clocks: +20 = 139 (8 + 12ea) | ip:0x1c->0x20
|
||||||
|
mov word [bx+di+1000], cx ; Clocks: +21 = 160 (9 + 12ea) | ip:0x20->0x24
|
||||||
|
add dx, [bp+si+1000] ; Clocks: +21 = 181 (9 + 12ea) | ip:0x24->0x28 flags:->PZ
|
||||||
|
add word [bp+si], 76 ; Clocks: +25 = 206 (17 + 8ea) | ip:0x28->0x2b flags:PZ->
|
||||||
|
add dx, [bp+si+1001] ; Clocks: +25 = 231 (9 + 12ea + 4p) | ip:0x2b->0x2f flags:->PZ
|
||||||
|
add word [di+999], dx ; Clocks: +33 = 264 (16 + 9ea + 8p) | ip:0x2f->0x33 flags:PZ->P
|
||||||
|
add word [bp+si], 75 ; Clocks: +25 = 289 (17 + 8ea) | ip:0x33->0x36 flags:P->A
|
||||||
|
|
||||||
|
Final registers:
|
||||||
|
bx: 0x03e8 (1000)
|
||||||
|
bp: 0x07d0 (2000)
|
||||||
|
si: 0x0bb8 (3000)
|
||||||
|
di: 0x0fa0 (4000)
|
||||||
|
ip: 0x0036 (54)
|
||||||
|
flags: A
|
||||||
|
|
||||||
|
**************
|
||||||
|
**** 8088 ****
|
||||||
|
**************
|
||||||
|
|
||||||
|
WARNING: Clocks reported by this utility are strictly from the 8086 manual.
|
||||||
|
They will be inaccurate, both because the manual clocks are estimates, and because
|
||||||
|
some of the entries in the manual look highly suspicious and are probably typos.
|
||||||
|
|
||||||
|
--- test\listing_0057_challenge_cycles execution ---
|
||||||
|
mov bx, 1000 ; Clocks: +4 = 4 | bx:0x0->0x3e8 ip:0x0->0x3
|
||||||
|
mov bp, 2000 ; Clocks: +4 = 8 | bp:0x0->0x7d0 ip:0x3->0x6
|
||||||
|
mov si, 3000 ; Clocks: +4 = 12 | si:0x0->0xbb8 ip:0x6->0x9
|
||||||
|
mov di, 4000 ; Clocks: +4 = 16 | di:0x0->0xfa0 ip:0x9->0xc
|
||||||
|
mov cx, [bp+di] ; Clocks: +19 = 35 (8 + 7ea + 4p) | ip:0xc->0xe
|
||||||
|
mov word [bx+si], cx ; Clocks: +20 = 55 (9 + 7ea + 4p) | ip:0xe->0x10
|
||||||
|
mov cx, [bp+si] ; Clocks: +20 = 75 (8 + 8ea + 4p) | ip:0x10->0x12
|
||||||
|
mov word [bx+di], cx ; Clocks: +21 = 96 (9 + 8ea + 4p) | ip:0x12->0x14
|
||||||
|
mov cx, [bp+di+1000] ; Clocks: +23 = 119 (8 + 11ea + 4p) | ip:0x14->0x18
|
||||||
|
mov word [bx+si+1000], cx ; Clocks: +24 = 143 (9 + 11ea + 4p) | ip:0x18->0x1c
|
||||||
|
mov cx, [bp+si+1000] ; Clocks: +24 = 167 (8 + 12ea + 4p) | ip:0x1c->0x20
|
||||||
|
mov word [bx+di+1000], cx ; Clocks: +25 = 192 (9 + 12ea + 4p) | ip:0x20->0x24
|
||||||
|
add dx, [bp+si+1000] ; Clocks: +25 = 217 (9 + 12ea + 4p) | ip:0x24->0x28 flags:->PZ
|
||||||
|
add word [bp+si], 76 ; Clocks: +33 = 250 (17 + 8ea + 8p) | ip:0x28->0x2b flags:PZ->
|
||||||
|
add dx, [bp+si+1001] ; Clocks: +25 = 275 (9 + 12ea + 4p) | ip:0x2b->0x2f flags:->PZ
|
||||||
|
add word [di+999], dx ; Clocks: +33 = 308 (16 + 9ea + 8p) | ip:0x2f->0x33 flags:PZ->P
|
||||||
|
add word [bp+si], 75 ; Clocks: +33 = 341 (17 + 8ea + 8p) | ip:0x33->0x36 flags:P->A
|
||||||
|
|
||||||
|
Final registers:
|
||||||
|
bx: 0x03e8 (1000)
|
||||||
|
bp: 0x07d0 (2000)
|
||||||
|
si: 0x0bb8 (3000)
|
||||||
|
di: 0x0fa0 (4000)
|
||||||
|
ip: 0x0036 (54)
|
||||||
|
flags: A
|
||||||
|
|
||||||
+680
-265
File diff suppressed because it is too large
Load Diff
+83
-9
@@ -1,5 +1,4 @@
|
|||||||
// NOTE: Sim8086
|
// NOTE: Sim8086 ///////////////////////////////////////////////////////////////////////////////////
|
||||||
// ============================================================================
|
|
||||||
typedef enum S86_OpDecodeType {
|
typedef enum S86_OpDecodeType {
|
||||||
S86_OpDecodeType_MOVRegOrMemToOrFromReg,
|
S86_OpDecodeType_MOVRegOrMemToOrFromReg,
|
||||||
S86_OpDecodeType_MOVImmediateToRegOrMem,
|
S86_OpDecodeType_MOVImmediateToRegOrMem,
|
||||||
@@ -307,6 +306,7 @@ typedef enum S86_MnemonicOp {
|
|||||||
|
|
||||||
S86_MnemonicOp_DirectInterSegment,
|
S86_MnemonicOp_DirectInterSegment,
|
||||||
S86_MnemonicOp_Jump,
|
S86_MnemonicOp_Jump,
|
||||||
|
S86_MnemonicOp_Count,
|
||||||
} S86_MnemonicOp;
|
} S86_MnemonicOp;
|
||||||
|
|
||||||
typedef enum S86_EffectiveAddress {
|
typedef enum S86_EffectiveAddress {
|
||||||
@@ -315,31 +315,105 @@ typedef enum S86_EffectiveAddress {
|
|||||||
S86_EffectiveAddress_Dest,
|
S86_EffectiveAddress_Dest,
|
||||||
} S86_EffectiveAddress;
|
} S86_EffectiveAddress;
|
||||||
|
|
||||||
typedef enum S86_WidePrefix {
|
typedef enum S86_WordBytePrefix {
|
||||||
S86_WidePrefix_None,
|
S86_WordBytePrefix_None,
|
||||||
S86_WidePrefix_Src,
|
S86_WordBytePrefix_Byte,
|
||||||
S86_WidePrefix_Dest,
|
S86_WordBytePrefix_Word,
|
||||||
} S86_WidePrefix;
|
} S86_WordBytePrefix;
|
||||||
|
|
||||||
typedef struct S86_Opcode {
|
typedef struct S86_Opcode {
|
||||||
|
S86_OpDecodeType type;
|
||||||
|
uint8_t byte_size; ///< Number of bytes used to encode this opcode
|
||||||
S86_Mnemonic mnemonic; ///< Mnemonic type
|
S86_Mnemonic mnemonic; ///< Mnemonic type
|
||||||
S86_EffectiveAddress effective_addr; ///< Src/dest op is an effective address calculation
|
S86_EffectiveAddress effective_addr; ///< Src/dest op is an effective address calculation
|
||||||
bool effective_addr_loads_mem; ///< Effective address uses '[]' notation to load address memory
|
bool effective_addr_loads_mem; ///< Effective address uses '[]' notation to load address memory
|
||||||
bool lock_prefix; ///< Prefix the opcode with "lock" instruction
|
bool lock_prefix; ///< Prefix the opcode with "lock" instruction
|
||||||
bool rep_prefix; ///< Prefix the opcode with "rep" instruction
|
bool rep_prefix; ///< Prefix the opcode with "rep" instruction
|
||||||
bool wide; ///< Opcode has the 'w' flag set
|
bool wide; ///< Opcode has the 'w' flag set
|
||||||
S86_WidePrefix wide_prefix; ///< Mnemonic src/dest op requires a 'word' or 'byte' prefix (e.g. ambiguous immediate size)
|
S86_WordBytePrefix word_byte_prefix; ///< Opcode has the 'word' or 'byte' prefix
|
||||||
S86_MnemonicOp src; ///< Source op for the mnemonic
|
S86_MnemonicOp src; ///< Source op for the mnemonic
|
||||||
S86_MnemonicOp dest; ///< Destination op for the mnemonic
|
S86_MnemonicOp dest; ///< Destination op for the mnemonic
|
||||||
int32_t displacement; ///< Opcode has displacement/data/offset
|
int32_t displacement; ///< Opcode has displacement/data/offset
|
||||||
int32_t immediate; ///< Immediate value when src/dest op is an immediate
|
int32_t immediate; ///< Immediate value when src/dest op is an immediate
|
||||||
|
bool immediate_is_8bit; ///< Immediate was 8bit and sign extended
|
||||||
S86_MnemonicOp seg_reg_prefix; ///< Segment register that should prefix the upcoming instruction
|
S86_MnemonicOp seg_reg_prefix; ///< Segment register that should prefix the upcoming instruction
|
||||||
|
uint32_t base_clocks; ///< Number of cycles required to complete this operation
|
||||||
|
uint32_t effective_address_clocks; ///< Number of cycles required to complete this operation
|
||||||
|
uint32_t transfer_penalty_clocks; ///< Number of extra cycles required to complete a word transfer
|
||||||
} S86_Opcode;
|
} S86_Opcode;
|
||||||
|
|
||||||
|
typedef enum S86_RegisterByte {
|
||||||
|
S86_RegisterByte_Lo,
|
||||||
|
S86_RegisterByte_Hi,
|
||||||
|
S86_RegisterByte_Count,
|
||||||
|
S86_RegisterByte_Nil,
|
||||||
|
} S86_RegisterByte;
|
||||||
|
|
||||||
|
typedef union S86_Register16 {
|
||||||
|
uint16_t word;
|
||||||
|
uint8_t bytes[S86_RegisterByte_Count];
|
||||||
|
} S86_Register16;
|
||||||
|
|
||||||
|
typedef struct S86_RegisterFileFlags {
|
||||||
|
bool carry;
|
||||||
|
bool zero;
|
||||||
|
bool sign;
|
||||||
|
bool overflow;
|
||||||
|
bool parity;
|
||||||
|
bool auxiliary_carry;
|
||||||
|
} S86_RegisterFileFlags;
|
||||||
|
|
||||||
|
typedef enum S86_RegisterFileRegArray {
|
||||||
|
S86_RegisterFileRegArray_AX,
|
||||||
|
S86_RegisterFileRegArray_BX,
|
||||||
|
S86_RegisterFileRegArray_CX,
|
||||||
|
S86_RegisterFileRegArray_DX,
|
||||||
|
|
||||||
|
S86_RegisterFileRegArray_SP,
|
||||||
|
S86_RegisterFileRegArray_BP,
|
||||||
|
S86_RegisterFileRegArray_SI,
|
||||||
|
S86_RegisterFileRegArray_DI,
|
||||||
|
|
||||||
|
S86_RegisterFileRegArray_ES,
|
||||||
|
S86_RegisterFileRegArray_CS,
|
||||||
|
S86_RegisterFileRegArray_SS,
|
||||||
|
S86_RegisterFileRegArray_DS,
|
||||||
|
S86_RegisterFileRegArray_Count,
|
||||||
|
} S86_RegisterFileRegArray;
|
||||||
|
|
||||||
|
typedef struct S86_RegisterFile {
|
||||||
|
S86_RegisterFileFlags flags;
|
||||||
|
uint16_t instruction_ptr;
|
||||||
|
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
S86_Register16 ax;
|
||||||
|
S86_Register16 bx;
|
||||||
|
S86_Register16 cx;
|
||||||
|
S86_Register16 dx;
|
||||||
|
|
||||||
|
S86_Register16 sp;
|
||||||
|
S86_Register16 bp;
|
||||||
|
S86_Register16 si;
|
||||||
|
S86_Register16 di;
|
||||||
|
|
||||||
|
S86_Register16 es;
|
||||||
|
S86_Register16 cs;
|
||||||
|
S86_Register16 ss;
|
||||||
|
S86_Register16 ds;
|
||||||
|
} file;
|
||||||
|
S86_Register16 array[S86_RegisterFileRegArray_Count];
|
||||||
|
} reg;
|
||||||
|
} S86_RegisterFile;
|
||||||
|
|
||||||
|
bool S86_RegisterFileFlagsEq (S86_RegisterFileFlags lhs, S86_RegisterFileFlags rhs);
|
||||||
S86_Str8 S86_MnemonicStr8 (S86_Mnemonic type);
|
S86_Str8 S86_MnemonicStr8 (S86_Mnemonic type);
|
||||||
S86_MnemonicOp S86_MnemonicOpFromWReg (bool w, uint8_t reg);
|
S86_MnemonicOp S86_MnemonicOpFromWReg (bool w, uint8_t reg);
|
||||||
S86_MnemonicOp S86_MnemonicOpFromSR (uint8_t sr);
|
S86_MnemonicOp S86_MnemonicOpFromSR (uint8_t sr);
|
||||||
S86_Str8 S86_MnemonicOpStr8 (S86_MnemonicOp type);
|
S86_Str8 S86_MnemonicOpStr8 (S86_MnemonicOp type);
|
||||||
void S86_PrintOpcodeMnemonicOp(S86_Opcode opcode, bool src);
|
bool S86_MnemonicOpIsAccumulator (S86_MnemonicOp type);
|
||||||
|
bool S86_MnemonicOpIsRegister (S86_MnemonicOp type);
|
||||||
|
S86_Str8 S86_RegisterFileRegArrayStr8(S86_RegisterFileRegArray type);
|
||||||
|
void S86_PrintOpcodeMnemonicOp (S86_Opcode opcode, bool src);
|
||||||
void S86_PrintOpcode (S86_Opcode opcode);
|
void S86_PrintOpcode (S86_Opcode opcode);
|
||||||
void S86_DecodeEffectiveAddr (S86_Opcode *opcode, S86_BufferIterator *it, uint8_t rm, uint8_t mod, uint8_t w);
|
void S86_DecodeEffectiveAddr (S86_Opcode *opcode, S86_BufferIterator *it, uint8_t rm, uint8_t mod, uint8_t w);
|
||||||
|
|||||||
@@ -111,6 +111,42 @@ void S86_FileFree(S86_Buffer buffer)
|
|||||||
VirtualFree(buffer.data, 0, MEM_RELEASE);
|
VirtualFree(buffer.data, 0, MEM_RELEASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool S86_FileWrite(char const *file_path, void const *buffer, size_t buffer_size)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
// NOTE: Open file
|
||||||
|
// =========================================================================
|
||||||
|
HANDLE file_handle = CreateFile(
|
||||||
|
/*LPCSTR lpFileName*/ file_path,
|
||||||
|
/*DWORD dwDesiredAccess*/ GENERIC_WRITE,
|
||||||
|
/*DWORD dwShareMode*/ 0,
|
||||||
|
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ NULL,
|
||||||
|
/*DWORD dwCreationDisposition*/ CREATE_ALWAYS,
|
||||||
|
/*DWORD dwFlagsAndAttributes*/ 0,
|
||||||
|
/*HANDLE hTemplateFile*/ NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (file_handle == INVALID_HANDLE_VALUE)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// NOTE: Write file to disk
|
||||||
|
// =========================================================================
|
||||||
|
DWORD bytes_written = 0;
|
||||||
|
BOOL write_file_result = WriteFile(
|
||||||
|
/*HANDLE hFile*/ file_handle,
|
||||||
|
/*LPVOID lpBuffer*/ buffer,
|
||||||
|
/*DWORD nNumberOfBytesToWrite*/ S86_CAST(DWORD)buffer_size,
|
||||||
|
/*LPDWORD lpNumberOfBytesWrite*/ &bytes_written,
|
||||||
|
/*LPOVERLAPPED lpOverlapped*/ NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
S86_ASSERT(bytes_written == buffer_size);
|
||||||
|
result = write_file_result && bytes_written == buffer_size;
|
||||||
|
CloseHandle(file_handle);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
void S86_Print(S86_Str8 string)
|
void S86_Print(S86_Str8 string)
|
||||||
{
|
{
|
||||||
if (s86_globals.stdout_handle == NULL) {
|
if (s86_globals.stdout_handle == NULL) {
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
#define WIN32_MEAN_AND_LEAN
|
|
||||||
#define NOMINMAX
|
|
||||||
#include <Windows.h>
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
// NOTE: Macros
|
// NOTE: Macros
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
#define S86_STRINGIFY2(token) #token
|
#define S86_STRINGIFY2(token) #token
|
||||||
@@ -62,6 +53,7 @@ uint8_t S86_BufferIteratorNextByte(S86_BufferIterator *it);
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
S86_Buffer S86_FileRead(char const *file_path);
|
S86_Buffer S86_FileRead(char const *file_path);
|
||||||
void S86_FileFree(S86_Buffer buffer);
|
void S86_FileFree(S86_Buffer buffer);
|
||||||
|
bool S86_FileWrite(char const *file_path, void const *buffer, size_t buffer_size);
|
||||||
|
|
||||||
// NOTE: Print
|
// NOTE: Print
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "haversine_stdlib.h"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0074_platform_metrics.cpp"
|
||||||
|
#include "haversine_stdlib.c"
|
||||||
|
|
||||||
|
typedef struct Str8FindResult {
|
||||||
|
bool found;
|
||||||
|
HAV_Str8 match;
|
||||||
|
HAV_Str8 match_to_end_of_buffer;
|
||||||
|
} Str8FindResult;
|
||||||
|
|
||||||
|
static Str8FindResult FindFirstCharThatLooksLikeANumber(HAV_Str8 buffer)
|
||||||
|
{
|
||||||
|
Str8FindResult result = {0};
|
||||||
|
for (size_t index = 0; !result.found && index < buffer.size; index++) {
|
||||||
|
if (HAV_CharIsDigit(buffer.data[index]) || buffer.data[index] == '+' || buffer.data[index] == '-') {
|
||||||
|
result.found = true;
|
||||||
|
result.match = (HAV_Str8){.data = buffer.data + index, .size = 1};
|
||||||
|
result.match_to_end_of_buffer = (HAV_Str8){.data = result.match.data, .size = buffer.size - index};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 StringToF64(HAV_Str8 value)
|
||||||
|
{
|
||||||
|
f64 result = 0.f;
|
||||||
|
Str8FindResult find_result = FindFirstCharThatLooksLikeANumber(value);
|
||||||
|
if (!find_result.found)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
bool negative = false;
|
||||||
|
HAV_Str8 real_number_string = find_result.match_to_end_of_buffer;
|
||||||
|
if (find_result.match.data[0] == '+' || find_result.match.data[0] == '-') {
|
||||||
|
negative = find_result.match.data[0] == '-';
|
||||||
|
real_number_string.data++;
|
||||||
|
real_number_string.size--;
|
||||||
|
}
|
||||||
|
|
||||||
|
HAV_Str8BinarySplitResult number_split = HAV_Str8_BinarySplit(real_number_string, HAV_STR8("."));
|
||||||
|
HAV_Str8 integer_part = number_split.lhs;
|
||||||
|
HAV_Str8 decimal_part = number_split.rhs;
|
||||||
|
|
||||||
|
uint64_t integer_part_as_u64 = 0;
|
||||||
|
for (size_t index = 0; index < integer_part.size; index++) {
|
||||||
|
integer_part_as_u64 *= 10;
|
||||||
|
integer_part_as_u64 += integer_part.data[index] - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t decimal_part_as_u64 = 0;
|
||||||
|
uint64_t decimal_magnitude = 1;
|
||||||
|
for (size_t index = 0; index < decimal_part.size; index++) {
|
||||||
|
decimal_part_as_u64 *= 10;
|
||||||
|
decimal_part_as_u64 += decimal_part.data[index] - '0';
|
||||||
|
decimal_magnitude *= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef union FU64 {
|
||||||
|
f64 f64_value;
|
||||||
|
uint64_t u64_value;
|
||||||
|
} FU64;
|
||||||
|
|
||||||
|
FU64 fu64 = {0};
|
||||||
|
fu64.f64_value = HAV_CAST(f64)integer_part_as_u64 + (HAV_CAST(f64)decimal_part_as_u64 / decimal_magnitude);
|
||||||
|
if (negative) {
|
||||||
|
fu64.u64_value |= (1ULL << 63);
|
||||||
|
}
|
||||||
|
return fu64.f64_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define PRINT_USAGE HAV_PrintLnFmt("Usage: %s [haversine_input.json] [answers.f64]", argv[0])
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
// NOTE: Arg Parsing
|
||||||
|
// =========================================================================
|
||||||
|
if (argc != 2 && argc != 3) {
|
||||||
|
PRINT_USAGE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_profiler.begin_tsc = ReadCPUTimer();
|
||||||
|
HAV_Str8 arg_json = {argv[1], strlen(argv[1])};
|
||||||
|
HAV_Str8 arg_answers = {0};
|
||||||
|
if (argc == 3)
|
||||||
|
arg_answers = (HAV_Str8){.data = argv[2], .size = strlen(argv[2])};
|
||||||
|
|
||||||
|
HAV_Buffer json_buffer = HAV_FileRead(arg_json.data);
|
||||||
|
if (!HAV_BufferIsValid(json_buffer))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
HAV_ProfilerZone prof_parse_and_sum_zone = HAV_Profiler_BeginZone("Parse&Hav Sum");
|
||||||
|
f64 haversine_sum = 0;
|
||||||
|
size_t pair_count = 0;
|
||||||
|
HAV_Str8 json_it = (HAV_Str8){.data = json_buffer.data, .size = json_buffer.size};
|
||||||
|
for (;; pair_count++) {
|
||||||
|
f64 x0 = 0.f, y0 = 0.f, x1 = 0.f, y1 = 0.f;
|
||||||
|
HAV_ProfilerZone prof_json_parse_zone = HAV_Profiler_BeginZoneBandwidth("Parse", json_it.size);
|
||||||
|
HAV_Str8BinarySplitResult x0_key = HAV_Str8_BinarySplit(json_it, HAV_STR8("x0"));
|
||||||
|
if (x0_key.rhs.size) {
|
||||||
|
Str8FindResult x0_find_value = FindFirstCharThatLooksLikeANumber(x0_key.rhs);
|
||||||
|
HAV_Str8BinarySplitResult x0_value = HAV_Str8_BinarySplit(x0_find_value.match_to_end_of_buffer, HAV_STR8(","));
|
||||||
|
|
||||||
|
HAV_Str8BinarySplitResult y0_key = HAV_Str8_BinarySplit(x0_value.rhs, HAV_STR8("y0"));
|
||||||
|
Str8FindResult y0_find_value = FindFirstCharThatLooksLikeANumber(y0_key.rhs);
|
||||||
|
HAV_Str8BinarySplitResult y0_value = HAV_Str8_BinarySplit(y0_find_value.match_to_end_of_buffer, HAV_STR8(","));
|
||||||
|
|
||||||
|
HAV_Str8BinarySplitResult x1_key = HAV_Str8_BinarySplit(y0_value.rhs, HAV_STR8("x1"));
|
||||||
|
Str8FindResult x1_find_value = FindFirstCharThatLooksLikeANumber(x1_key.rhs);
|
||||||
|
HAV_Str8BinarySplitResult x1_value = HAV_Str8_BinarySplit(x1_find_value.match_to_end_of_buffer, HAV_STR8(","));
|
||||||
|
|
||||||
|
HAV_Str8BinarySplitResult y1_key = HAV_Str8_BinarySplit(x1_value.rhs, HAV_STR8("y1"));
|
||||||
|
Str8FindResult y1_find_value = FindFirstCharThatLooksLikeANumber(y1_key.rhs);
|
||||||
|
HAV_Str8BinarySplitResult y1_value = HAV_Str8_BinarySplit(y1_find_value.match_to_end_of_buffer, HAV_STR8("}"));
|
||||||
|
|
||||||
|
x0 = StringToF64(x0_value.lhs);
|
||||||
|
y0 = StringToF64(y0_value.lhs);
|
||||||
|
x1 = StringToF64(x1_value.lhs);
|
||||||
|
y1 = StringToF64(y1_value.lhs);
|
||||||
|
|
||||||
|
json_it = y1_value.rhs;
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
|
HAV_PrintLnFmt("{x0: %.*s (%f), y0: %.*s (%f), x1 %.*s (%f), y1: %.*s (%f)}",
|
||||||
|
HAV_STR8_FMT(x0_value.lhs), x0,
|
||||||
|
HAV_STR8_FMT(y0_value.lhs), y0,
|
||||||
|
HAV_STR8_FMT(x1_value.lhs), x1,
|
||||||
|
HAV_STR8_FMT(y1_value.lhs), y1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
HAV_Profiler_EndZone(prof_json_parse_zone);
|
||||||
|
if (!x0_key.rhs.size)
|
||||||
|
break;
|
||||||
|
|
||||||
|
HAV_ProfilerZone prof_haversine_sum_zone = HAV_Profiler_BeginZoneBandwidth("Hav Sum", sizeof(x0) + sizeof(y0) + sizeof(x1) + sizeof(y1));
|
||||||
|
f64 haversine_dist = ReferenceHaversine(x0, y0, x1, y1, /*EarthRadius*/ 6372.8);
|
||||||
|
haversine_sum += haversine_dist;
|
||||||
|
HAV_Profiler_EndZone(prof_haversine_sum_zone);
|
||||||
|
}
|
||||||
|
HAV_Profiler_EndZone(prof_parse_and_sum_zone);
|
||||||
|
|
||||||
|
haversine_sum /= pair_count;
|
||||||
|
size_t input_size = json_buffer.size;
|
||||||
|
HAV_PrintLnFmt("Input size: %zu", input_size);
|
||||||
|
HAV_PrintLnFmt("Pair count: %zu", pair_count);
|
||||||
|
HAV_PrintLnFmt("Haversine sum: %f", haversine_sum);
|
||||||
|
|
||||||
|
if (arg_answers.size) {
|
||||||
|
HAV_Buffer answers_buffer = HAV_FileRead(arg_answers.data);
|
||||||
|
if (HAV_BufferIsValid(answers_buffer)) {
|
||||||
|
HAV_ASSERT(answers_buffer.size == (pair_count * sizeof(f64)) + /*Reference Sum*/ sizeof(f64));
|
||||||
|
|
||||||
|
f64 reference_haversine_sum = 0;
|
||||||
|
memcpy(&reference_haversine_sum, answers_buffer.data + (pair_count * sizeof(f64)), sizeof(reference_haversine_sum));
|
||||||
|
|
||||||
|
f64 difference = reference_haversine_sum - haversine_sum;
|
||||||
|
HAV_PrintLn(HAV_STR8("\nValidation: "));
|
||||||
|
HAV_PrintLnFmt("Reference sum: %f", reference_haversine_sum);
|
||||||
|
HAV_PrintLnFmt("Difference: %f", difference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_profiler.end_tsc = ReadCPUTimer();
|
||||||
|
HAV_Profiler_Dump();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "haversine_stdlib.h"
|
||||||
|
#include "listing_0074_platform_metrics.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "haversine_stdlib.c"
|
||||||
|
|
||||||
|
#define PRINT_USAGE HAV_PrintLnFmt("Usage: %s [uniform/cluster] [random seed] [number of coordinate pairs to generate]", argv[0])
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
// NOTE: Unit Tests ////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
{
|
||||||
|
HAV_Str8ToU64Result result = HAV_Str8_ToU64(HAV_STR8("00"));
|
||||||
|
HAV_ASSERT(result.success);
|
||||||
|
HAV_ASSERT(result.value == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
HAV_Str8ToU64Result result = HAV_Str8_ToU64(HAV_STR8("+100"));
|
||||||
|
HAV_ASSERT(!result.success);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
HAV_Str8ToU64Result result = HAV_Str8_ToU64(HAV_STR8("100,0"));
|
||||||
|
HAV_ASSERT(!result.success);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
HAV_Str8ToU64Result result = HAV_Str8_ToU64(HAV_STR8("100a"));
|
||||||
|
HAV_ASSERT(!result.success);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
HAV_Str8ToU64Result result = HAV_Str8_ToU64(HAV_STR8("3147"));
|
||||||
|
HAV_ASSERT(result.success);
|
||||||
|
HAV_ASSERT(result.value == 3147);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Arg Parsing ///////////////////////////////////////////////////////////////////////////
|
||||||
|
if (argc != 4) {
|
||||||
|
PRINT_USAGE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
HAV_Str8 arg_uniform_cluster = {argv[1], strlen(argv[1])};
|
||||||
|
HAV_Str8 arg_random_seed = {argv[2], strlen(argv[2])};
|
||||||
|
HAV_Str8 arg_number_of_coordinate_pairs_to_generate = {argv[3], strlen(argv[3])};
|
||||||
|
|
||||||
|
typedef enum PointGenerator {
|
||||||
|
PointGenerator_Invalid,
|
||||||
|
PointGenerator_Uniform,
|
||||||
|
PointGenerator_Cluster,
|
||||||
|
} PointGenerator;
|
||||||
|
|
||||||
|
HAV_Str8ToU64Result random_seed_u64_result = HAV_Str8_ToU64(arg_random_seed);
|
||||||
|
HAV_Str8ToU64Result number_of_coordinate_pairs_to_generate_u64_result = HAV_Str8_ToU64(arg_number_of_coordinate_pairs_to_generate);
|
||||||
|
PointGenerator point_generator = PointGenerator_Invalid;
|
||||||
|
|
||||||
|
if (HAV_Str8_Equals(arg_uniform_cluster, HAV_STR8("uniform"))) {
|
||||||
|
point_generator = PointGenerator_Uniform;
|
||||||
|
} else if (HAV_Str8_Equals(arg_uniform_cluster, HAV_STR8("cluster"))) {
|
||||||
|
point_generator = PointGenerator_Cluster;
|
||||||
|
} else {
|
||||||
|
PRINT_USAGE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!random_seed_u64_result.success) {
|
||||||
|
HAV_PrintLnFmt("Random seed was not a valid U64 value [seed=%.*s]", HAV_STR8_FMT(arg_random_seed));
|
||||||
|
PRINT_USAGE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!number_of_coordinate_pairs_to_generate_u64_result.success) {
|
||||||
|
HAV_PrintLnFmt("Number of coordinate pairs to generate was not a valid U64 value [seed=%.*s]", HAV_STR8_FMT(arg_number_of_coordinate_pairs_to_generate));
|
||||||
|
PRINT_USAGE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t const MAX_COORD_PAIRS = 100'000'000;
|
||||||
|
if (number_of_coordinate_pairs_to_generate_u64_result.value > MAX_COORD_PAIRS) {
|
||||||
|
HAV_PrintLnFmt("Maximum number of coordinate pairs exceeded, exiting to avoid accidental large files [requested=%zu, max=%zu]",
|
||||||
|
number_of_coordinate_pairs_to_generate_u64_result.value,
|
||||||
|
MAX_COORD_PAIRS);
|
||||||
|
PRINT_USAGE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Generator /////////////////////////////////////////////////////////////////////////////
|
||||||
|
uint64_t point_count = number_of_coordinate_pairs_to_generate_u64_result.value;
|
||||||
|
uint64_t random_seed = random_seed_u64_result.value;
|
||||||
|
uint64_t rng_state = random_seed;
|
||||||
|
|
||||||
|
char json_file_name[1024];
|
||||||
|
char answers_file_name[1024];
|
||||||
|
snprintf(json_file_name, sizeof(json_file_name), "haversine_%zu_%s_%zu.json", point_count, point_generator == PointGenerator_Cluster ? "cluster" : "uniform", random_seed);
|
||||||
|
snprintf(answers_file_name, sizeof(json_file_name), "haversine_%zu_%s_%zu.f64", point_count, point_generator == PointGenerator_Cluster ? "cluster" : "uniform", random_seed);
|
||||||
|
|
||||||
|
HANDLE haversine_json_file_handle = CreateFile(
|
||||||
|
/*LPCSTR lpFileName*/ json_file_name,
|
||||||
|
/*DWORD dwDesiredAccess*/ GENERIC_WRITE,
|
||||||
|
/*DWORD dwShareMode*/ 0,
|
||||||
|
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ NULL,
|
||||||
|
/*DWORD dwCreationDisposition*/ CREATE_ALWAYS,
|
||||||
|
/*DWORD dwFlagsAndAttributes*/ 0,
|
||||||
|
/*HANDLE hTemplateFile*/ NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
HANDLE haversine_f64_file_handle = CreateFile(
|
||||||
|
/*LPCSTR lpFileName*/ answers_file_name,
|
||||||
|
/*DWORD dwDesiredAccess*/ GENERIC_WRITE,
|
||||||
|
/*DWORD dwShareMode*/ 0,
|
||||||
|
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ NULL,
|
||||||
|
/*DWORD dwCreationDisposition*/ CREATE_ALWAYS,
|
||||||
|
/*DWORD dwFlagsAndAttributes*/ 0,
|
||||||
|
/*HANDLE hTemplateFile*/ NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
f64 const LAT_LON_MIN = -180.0;
|
||||||
|
f64 const LAT_LON_MAX = 180.0;
|
||||||
|
|
||||||
|
uint64_t const MAX_CLUSTERS = 64;
|
||||||
|
uint64_t const points_per_cluster = point_count / MAX_CLUSTERS;
|
||||||
|
|
||||||
|
char tmp_buffer[128];
|
||||||
|
f64 expected_sum = 0;
|
||||||
|
HAV_PrintHandle(haversine_json_file_handle, HAV_STR8("{\"pairs\":[\n"));
|
||||||
|
|
||||||
|
f64 const sum_coefficient = 1.0 / point_count;
|
||||||
|
f64 point_centre = 0;
|
||||||
|
f64 point_min_offset = LAT_LON_MIN;
|
||||||
|
f64 point_max_offset = LAT_LON_MAX;
|
||||||
|
for (int index = 0; index < point_count; index++) {
|
||||||
|
if (point_generator == PointGenerator_Cluster && (index % points_per_cluster) == 0) {
|
||||||
|
point_centre = HAV_PCG32_PieF64(&rng_state, LAT_LON_MIN, LAT_LON_MAX);
|
||||||
|
point_min_offset = -HAV_PCG32_PieF64(&rng_state, 0, 45);
|
||||||
|
point_max_offset = -point_min_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 x0 = point_centre + HAV_PCG32_PieF64(&rng_state, point_min_offset, point_max_offset);
|
||||||
|
f64 y0 = point_centre + HAV_PCG32_PieF64(&rng_state, point_min_offset, point_max_offset);
|
||||||
|
f64 x1 = point_centre + HAV_PCG32_PieF64(&rng_state, point_min_offset, point_max_offset);
|
||||||
|
f64 y1 = point_centre + HAV_PCG32_PieF64(&rng_state, point_min_offset, point_max_offset);
|
||||||
|
|
||||||
|
f64 haversine_dist = ReferenceHaversine(x0, y0, x1, y1, /*EarthRadius*/ 6372.8);
|
||||||
|
HAV_PrintHandle(haversine_f64_file_handle, (HAV_Str8){.data = (char *)&haversine_dist, .size = sizeof(haversine_dist)});
|
||||||
|
expected_sum += (sum_coefficient * haversine_dist);
|
||||||
|
|
||||||
|
size_t json_line_size = snprintf(tmp_buffer, sizeof(tmp_buffer), " {\"x0\": %f, \"y0\": %f, \"x1\": %f, \"y1\": %f}%s\n", x0, y0, x1, y1, (index == (point_count - 1) ? "" : ","));
|
||||||
|
HAV_ASSERT(json_line_size < sizeof(tmp_buffer));
|
||||||
|
HAV_PrintHandle(haversine_json_file_handle, (HAV_Str8){.data = tmp_buffer, .size = json_line_size});
|
||||||
|
}
|
||||||
|
HAV_PrintHandle(haversine_json_file_handle, HAV_STR8("]}\n"));
|
||||||
|
HAV_PrintHandle(haversine_f64_file_handle, (HAV_Str8){.data = (char *)&expected_sum, .size = sizeof(expected_sum)});
|
||||||
|
|
||||||
|
CloseHandle(haversine_json_file_handle);
|
||||||
|
CloseHandle(haversine_f64_file_handle);
|
||||||
|
|
||||||
|
HAV_PrintLnFmt("Method: %s", (point_generator == PointGenerator_Uniform ? "uniform" : "cluster"));
|
||||||
|
HAV_PrintLnFmt("Seed: %zu", random_seed);
|
||||||
|
HAV_PrintLnFmt("Pair Count: %zu", point_count);
|
||||||
|
HAV_PrintLnFmt("Expected Sum: %f", expected_sum);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,353 @@
|
|||||||
|
// NOTE: Implementation ////////////////////////////////////////////////////////////////////////////
|
||||||
|
bool HAV_Str8_Equals(HAV_Str8 lhs, HAV_Str8 rhs)
|
||||||
|
{
|
||||||
|
bool result = lhs.size == rhs.size && memcmp(lhs.data, rhs.data, lhs.size) == 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
HAV_Str8ToU64Result HAV_Str8_ToU64(HAV_Str8 string)
|
||||||
|
{
|
||||||
|
HAV_Str8ToU64Result result = {0};
|
||||||
|
|
||||||
|
size_t ch_index = 0;
|
||||||
|
while (ch_index < string.size && HAV_CharIsWhiteSpace(string.data[ch_index]))
|
||||||
|
ch_index++;
|
||||||
|
|
||||||
|
for (; ch_index < string.size; ch_index++) {
|
||||||
|
char ch = string.data[ch_index];
|
||||||
|
if (ch >= '0' && ch <= '9') {
|
||||||
|
result.value = (result.value * 10) + (ch - '0');
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.success = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
HAV_Str8BinarySplitResult HAV_Str8_BinarySplit(HAV_Str8 buffer, HAV_Str8 find)
|
||||||
|
{
|
||||||
|
HAV_Str8BinarySplitResult result = {0};
|
||||||
|
result.lhs = buffer;
|
||||||
|
for (size_t index = 0; (index + find.size) <= buffer.size; index++) {
|
||||||
|
HAV_Str8 check = {buffer.data + index, find.size};
|
||||||
|
if (HAV_Str8_Equals(find, check)) {
|
||||||
|
result.lhs.size = index;
|
||||||
|
result.rhs.data = check.data + find.size;
|
||||||
|
result.rhs.size = buffer.size - (index + find.size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HAV_CharIsWhiteSpace(char ch)
|
||||||
|
{
|
||||||
|
bool result = ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HAV_CharIsDigit(char ch)
|
||||||
|
{
|
||||||
|
bool result = ch >= '0' && ch <= '9';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HAV_Profiler_Dump()
|
||||||
|
{
|
||||||
|
u64 total_elapsed_tsc = g_profiler.end_tsc - g_profiler.begin_tsc;
|
||||||
|
u64 cpu_frequency = EstimateCPUTimerFreq();
|
||||||
|
if (cpu_frequency)
|
||||||
|
printf("\nTotal time: %0.4fms (CPU freq %llu)\n", 1000.0 * (f64)total_elapsed_tsc / (f64)cpu_frequency, cpu_frequency);
|
||||||
|
|
||||||
|
for (uint32_t index = 1; index < HAV_ARRAY_UCOUNT(g_profiler.anchors); index++) {
|
||||||
|
HAV_ProfilerAnchor const *anchor = g_profiler.anchors + index;
|
||||||
|
if (!anchor->elapsed_tsc_inclusive)
|
||||||
|
break;
|
||||||
|
|
||||||
|
f64 percent = total_elapsed_tsc ? (f64)anchor->elapsed_tsc_exclusive / (f64)total_elapsed_tsc * 100.0 : 100.0;
|
||||||
|
printf(" %.*s[%zu]: %llu (%.2f%%", HAV_STR8_FMT(anchor->label), anchor->hits, anchor->elapsed_tsc_exclusive, percent);
|
||||||
|
if (anchor->elapsed_tsc_inclusive != anchor->elapsed_tsc_exclusive) {
|
||||||
|
f64 percent_w_children = total_elapsed_tsc ? ((f64)anchor->elapsed_tsc_inclusive / (f64)total_elapsed_tsc * 100.0) : 100.0;
|
||||||
|
printf(", %.2f%% w/children", percent_w_children);
|
||||||
|
}
|
||||||
|
printf(")");
|
||||||
|
|
||||||
|
if (anchor->byte_count) {
|
||||||
|
f64 megabytes_processed = anchor->byte_count / (1024.f * 1024.f);
|
||||||
|
f64 elapsed_s = anchor->elapsed_tsc_inclusive / HAV_CAST(f64)cpu_frequency;
|
||||||
|
f64 bytes_per_s = anchor->byte_count / elapsed_s;
|
||||||
|
f64 gigabytes_bandwidth = bytes_per_s / (1024.f * 1024.f * 1024.f);
|
||||||
|
printf(" %.3fmb at %.2fgb/s", megabytes_processed, gigabytes_bandwidth);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HAV_ProfilerZone HAV_Profiler_BeginZone_(HAV_Str8 label, uint32_t index, u64 byte_count)
|
||||||
|
{
|
||||||
|
HAV_ProfilerZone result = {0};
|
||||||
|
#if defined(HAV_PROFILER)
|
||||||
|
result.index = index;
|
||||||
|
result.label = label;
|
||||||
|
result.tsc = ReadCPUTimer();
|
||||||
|
result.elapsed_tsc_inclusive = g_profiler.anchors[index].elapsed_tsc_inclusive;
|
||||||
|
result.byte_count = byte_count;
|
||||||
|
result.parent_index = g_profiler.parent_index;
|
||||||
|
g_profiler.parent_index = index;
|
||||||
|
#else
|
||||||
|
(void)label; (void)index; (void)byte_count;
|
||||||
|
#endif
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HAV_Profiler_EndZone(HAV_ProfilerZone zone)
|
||||||
|
{
|
||||||
|
#if defined(HAV_PROFILER)
|
||||||
|
u64 elapsed_tsc = ReadCPUTimer() - zone.tsc;
|
||||||
|
HAV_ProfilerAnchor* anchor = g_profiler.anchors + zone.index;
|
||||||
|
HAV_ProfilerAnchor* parent = g_profiler.anchors + zone.parent_index;
|
||||||
|
|
||||||
|
anchor->elapsed_tsc_exclusive += elapsed_tsc;
|
||||||
|
anchor->elapsed_tsc_inclusive = zone.elapsed_tsc_inclusive + elapsed_tsc;
|
||||||
|
anchor->label = zone.label;
|
||||||
|
anchor->byte_count += zone.byte_count;
|
||||||
|
anchor->hits++;
|
||||||
|
parent->elapsed_tsc_exclusive -= elapsed_tsc;
|
||||||
|
g_profiler.parent_index = zone.parent_index;
|
||||||
|
#else
|
||||||
|
(void)zone;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable: 4146) // warning C4146: unary minus operator applied to unsigned type, result still unsigned
|
||||||
|
uint32_t HAV_PCG32_Pie (uint64_t *state)
|
||||||
|
{
|
||||||
|
uint64_t old = *state ^ 0xc90fdaa2adf85459ULL;
|
||||||
|
*state = *state * 6364136223846793005ULL + 0xc90fdaa2adf85459ULL;
|
||||||
|
uint32_t xorshifted = (uint32_t)(((old >> 18u) ^ old) >> 27u);
|
||||||
|
uint32_t rot = old >> 59u;
|
||||||
|
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
|
||||||
|
}
|
||||||
|
#pragma warning(pop)
|
||||||
|
|
||||||
|
f64 HAV_PCG32_PieF64(uint64_t *state, f64 min, f64 max)
|
||||||
|
{
|
||||||
|
uint32_t u32_value = HAV_PCG32_Pie(state);
|
||||||
|
f64 t01 = HAV_CAST(f64)u32_value / HAV_CAST(f64)HAV_CAST(uint32_t)-1;
|
||||||
|
f64 result = min + (max - min) * t01;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HAV_BufferIsValid(HAV_Buffer buffer)
|
||||||
|
{
|
||||||
|
bool result = buffer.data && buffer.size;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
HAV_BufferIterator HAV_BufferIteratorInit(HAV_Buffer buffer)
|
||||||
|
{
|
||||||
|
HAV_BufferIterator result = {0};
|
||||||
|
result.buffer = buffer;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HAV_BufferIteratorHasMoreBytes(HAV_BufferIterator it)
|
||||||
|
{
|
||||||
|
bool result = HAV_BufferIsValid(it.buffer) && it.index < it.buffer.size;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t HAV_BufferIteratorPeekByte(HAV_BufferIterator *it)
|
||||||
|
{
|
||||||
|
HAV_ASSERT(it);
|
||||||
|
HAV_ASSERT(HAV_BufferIsValid(it->buffer));
|
||||||
|
HAV_ASSERT(it->index < it->buffer.size);
|
||||||
|
uint8_t result = it->buffer.data[it->index];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t HAV_BufferIteratorNextByte(HAV_BufferIterator *it)
|
||||||
|
{
|
||||||
|
uint8_t result = HAV_BufferIteratorPeekByte(it);
|
||||||
|
it->index++;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
HAV_Buffer HAV_FileRead(char const *file_path)
|
||||||
|
{
|
||||||
|
HAV_Buffer result = {0};
|
||||||
|
|
||||||
|
// NOTE: Determine file size ///////////////////////////////////////////////////////////////////
|
||||||
|
WIN32_FILE_ATTRIBUTE_DATA file_attrib_data = {0};
|
||||||
|
if (GetFileAttributesEx(file_path, GetFileExInfoStandard, &file_attrib_data) == 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// NOTE: Open file /////////////////////////////////////////////////////////////////////////////
|
||||||
|
HANDLE file_handle = CreateFile(
|
||||||
|
/*LPCSTR lpFileName*/ file_path,
|
||||||
|
/*DWORD dwDesiredAccess*/ GENERIC_READ,
|
||||||
|
/*DWORD dwShareMode*/ 0,
|
||||||
|
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ NULL,
|
||||||
|
/*DWORD dwCreationDisposition*/ OPEN_EXISTING,
|
||||||
|
/*DWORD dwFlagsAndAttributes*/ 0,
|
||||||
|
/*HANDLE hTemplateFile*/ NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (file_handle == INVALID_HANDLE_VALUE)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// NOTE: Allocate buffer ///////////////////////////////////////////////////////////////////////
|
||||||
|
uint64_t file_size = (uint64_t)file_attrib_data.nFileSizeHigh << 32 | (uint64_t)file_attrib_data.nFileSizeLow << 0;
|
||||||
|
HAV_ASSERT(file_size < (DWORD)-1);
|
||||||
|
char *buffer = VirtualAlloc(
|
||||||
|
/*LPVOID lpAddress*/ NULL,
|
||||||
|
/*SIZE_T dwSize*/ file_size,
|
||||||
|
/*DWORD flAllocationType*/ MEM_COMMIT | MEM_RESERVE,
|
||||||
|
/*DWORD flProtect*/ PAGE_READWRITE
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!buffer)
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
// NOTE: Read file to buffer ///////////////////////////////////////////////////////////////////
|
||||||
|
DWORD bytes_read = 0;
|
||||||
|
HAV_ProfilerZone prof_file_read_zone = HAV_Profiler_BeginZoneBandwidth("File Read", file_size);
|
||||||
|
BOOL read_file_result = ReadFile(
|
||||||
|
/*HANDLE hFile*/ file_handle,
|
||||||
|
/*LPVOID lpBuffer*/ buffer,
|
||||||
|
/*DWORD nNumberOfBytesToRead*/ HAV_CAST(DWORD)file_size,
|
||||||
|
/*LPDWORD lpNumberOfBytesRead*/ &bytes_read,
|
||||||
|
/*LPOVERLAPPED lpOverlapped*/ NULL
|
||||||
|
);
|
||||||
|
HAV_Profiler_EndZone(prof_file_read_zone);
|
||||||
|
|
||||||
|
// NOTE: Handle read result ////////////////////////////////////////////////////////////////////
|
||||||
|
if (read_file_result == 0) {
|
||||||
|
VirtualFree(buffer, 0, MEM_RELEASE);
|
||||||
|
} else {
|
||||||
|
result.data = buffer;
|
||||||
|
result.size = file_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
CloseHandle(file_handle);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
void HAV_FileFree(HAV_Buffer buffer)
|
||||||
|
{
|
||||||
|
if (HAV_BufferIsValid(buffer))
|
||||||
|
VirtualFree(buffer.data, 0, MEM_RELEASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HAV_FileWrite(char const *file_path, void const *buffer, size_t buffer_size)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
// NOTE: Open file /////////////////////////////////////////////////////////////////////////////
|
||||||
|
HANDLE file_handle = CreateFile(
|
||||||
|
/*LPCSTR lpFileName*/ file_path,
|
||||||
|
/*DWORD dwDesiredAccess*/ GENERIC_WRITE,
|
||||||
|
/*DWORD dwShareMode*/ 0,
|
||||||
|
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ NULL,
|
||||||
|
/*DWORD dwCreationDisposition*/ CREATE_ALWAYS,
|
||||||
|
/*DWORD dwFlagsAndAttributes*/ 0,
|
||||||
|
/*HANDLE hTemplateFile*/ NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (file_handle == INVALID_HANDLE_VALUE)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// NOTE: Write file to disk ////////////////////////////////////////////////////////////////////
|
||||||
|
DWORD bytes_written = 0;
|
||||||
|
BOOL write_file_result = WriteFile(
|
||||||
|
/*HANDLE hFile*/ file_handle,
|
||||||
|
/*LPVOID lpBuffer*/ buffer,
|
||||||
|
/*DWORD nNumberOfBytesToWrite*/ HAV_CAST(DWORD)buffer_size,
|
||||||
|
/*LPDWORD lpNumberOfBytesWrite*/ &bytes_written,
|
||||||
|
/*LPOVERLAPPED lpOverlapped*/ NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
HAV_ASSERT(bytes_written == buffer_size);
|
||||||
|
result = write_file_result && bytes_written == buffer_size;
|
||||||
|
CloseHandle(file_handle);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
void HAV_PrintHandle(void *handle, HAV_Str8 string)
|
||||||
|
{
|
||||||
|
DWORD bytes_written = 0;
|
||||||
|
WriteFile(handle, string.data, HAV_CAST(DWORD)string.size, &bytes_written, NULL);
|
||||||
|
(void)bytes_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HAV_Print(HAV_Str8 string)
|
||||||
|
{
|
||||||
|
if (pap_globals.stdout_handle == NULL) {
|
||||||
|
pap_globals.stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
DWORD mode = 0;
|
||||||
|
BOOL get_console_mode_result = GetConsoleMode(
|
||||||
|
/*HANDLE hConsoleHandle*/ pap_globals.stdout_handle,
|
||||||
|
/*LPDWORD lpMode*/ &mode
|
||||||
|
);
|
||||||
|
pap_globals.write_to_console = get_console_mode_result != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HAV_ASSERT(string.size < HAV_CAST(DWORD)-1);
|
||||||
|
if (pap_globals.write_to_console) {
|
||||||
|
DWORD chars_written = 0;
|
||||||
|
WriteConsoleA(pap_globals.stdout_handle, string.data, (DWORD)string.size, &chars_written, NULL);
|
||||||
|
} else {
|
||||||
|
HAV_PrintHandle(pap_globals.stdout_handle, string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HAV_PrintFmt(char const *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list args, args_copy;
|
||||||
|
va_start(args, fmt);
|
||||||
|
|
||||||
|
va_copy(args_copy, args);
|
||||||
|
int string_size = vsnprintf(NULL, 0, fmt, args_copy);
|
||||||
|
va_end(args_copy);
|
||||||
|
|
||||||
|
char buffer[8192];
|
||||||
|
HAV_ASSERT(string_size >= 0 && string_size < HAV_ARRAY_UCOUNT(buffer));
|
||||||
|
if (string_size) {
|
||||||
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||||
|
HAV_Str8 string = {.data = buffer, .size = string_size};
|
||||||
|
HAV_Print(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HAV_PrintLn(HAV_Str8 string)
|
||||||
|
{
|
||||||
|
HAV_Print(string);
|
||||||
|
HAV_Print(HAV_STR8("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HAV_PrintLnFmt(char const *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list args, args_copy;
|
||||||
|
va_start(args, fmt);
|
||||||
|
|
||||||
|
va_copy(args_copy, args);
|
||||||
|
int string_size = vsnprintf(NULL, 0, fmt, args_copy);
|
||||||
|
va_end(args_copy);
|
||||||
|
|
||||||
|
char buffer[8192];
|
||||||
|
HAV_ASSERT(string_size >= 0 && string_size < HAV_ARRAY_UCOUNT(buffer));
|
||||||
|
if (string_size) {
|
||||||
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||||
|
HAV_Str8 string = {.data = buffer, .size = string_size};
|
||||||
|
HAV_PrintLn(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// NOTE: Macros ////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
#define HAV_STRINGIFY2(token) #token
|
||||||
|
#define HAV_STRINGIFY(token) HAV_STRINGIFY2(token)
|
||||||
|
|
||||||
|
#if defined(NDEBUG)
|
||||||
|
#define HAV_ASSERT(expr)
|
||||||
|
#else
|
||||||
|
#define HAV_ASSERT(expr) \
|
||||||
|
if (!(expr)) { \
|
||||||
|
HAV_PrintLnFmt("Assertion triggered [file=\"" __FILE__ ":" HAV_STRINGIFY(__LINE__) "\", expr=\"" #expr "\"]"); \
|
||||||
|
__debugbreak(); \
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define HAV_ARRAY_UCOUNT(array) sizeof((array)) / sizeof((array)[0])
|
||||||
|
#define HAV_CAST(Type) (Type)
|
||||||
|
#define HAV_MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||||
|
#define HAV_MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
// NOTE: Globals ///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
typedef struct HAV_Globals {
|
||||||
|
HANDLE stdout_handle;
|
||||||
|
bool write_to_console;
|
||||||
|
} HAV_Globals;
|
||||||
|
|
||||||
|
HAV_Globals pap_globals;
|
||||||
|
|
||||||
|
// NOTE: Strings ///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
typedef struct HAV_Str8 {
|
||||||
|
char *data;
|
||||||
|
size_t size;
|
||||||
|
} HAV_Str8;
|
||||||
|
|
||||||
|
typedef struct HAV_Str8ToU64Result {
|
||||||
|
bool success;
|
||||||
|
uint64_t value;
|
||||||
|
} HAV_Str8ToU64Result;
|
||||||
|
|
||||||
|
typedef struct HAV_Str8BinarySplitResult {
|
||||||
|
HAV_Str8 lhs;
|
||||||
|
HAV_Str8 rhs;
|
||||||
|
} HAV_Str8BinarySplitResult;
|
||||||
|
|
||||||
|
#define HAV_STR8(string) (HAV_Str8){.data = (string), .size = HAV_ARRAY_UCOUNT(string) - 1 }
|
||||||
|
#define HAV_STR8_FMT(string) (int)((string).size), (string).data
|
||||||
|
|
||||||
|
bool HAV_Str8_Equals(HAV_Str8 lhs, HAV_Str8 rhs);
|
||||||
|
HAV_Str8ToU64Result HAV_Str8_ToU64(HAV_Str8 string);
|
||||||
|
HAV_Str8BinarySplitResult HAV_Str8_BinarySplit(HAV_Str8 buffer, HAV_Str8 find);
|
||||||
|
|
||||||
|
bool HAV_CharIsWhiteSpace(char ch);
|
||||||
|
bool HAV_CharIsDigit(char ch);
|
||||||
|
|
||||||
|
// NOTE: Profiler //////////////////////////////////////////////////////////////////////////////////
|
||||||
|
typedef struct HAV_ProfilerAnchor {
|
||||||
|
HAV_Str8 label;
|
||||||
|
u64 elapsed_tsc_exclusive; // Does not include children
|
||||||
|
u64 elapsed_tsc_inclusive; // Includes children
|
||||||
|
u64 byte_count;
|
||||||
|
u64 hits;
|
||||||
|
} HAV_ProfilerAnchor;
|
||||||
|
|
||||||
|
typedef struct HAV_Profiler {
|
||||||
|
HAV_ProfilerAnchor anchors[4096];
|
||||||
|
u64 begin_tsc;
|
||||||
|
u64 end_tsc;
|
||||||
|
u64 parent_index;
|
||||||
|
} HAV_Profiler;
|
||||||
|
|
||||||
|
typedef struct HAV_ProfilerZone {
|
||||||
|
u64 parent_index;
|
||||||
|
uint32_t index;
|
||||||
|
HAV_Str8 label;
|
||||||
|
u64 elapsed_tsc_inclusive;
|
||||||
|
u64 tsc;
|
||||||
|
u64 byte_count;
|
||||||
|
} HAV_ProfilerZone;
|
||||||
|
|
||||||
|
static HAV_Profiler g_profiler;
|
||||||
|
|
||||||
|
#define HAV_Profiler_BeginZone(label) HAV_Profiler_BeginZone_(HAV_STR8(label), __COUNTER__ + 1, 0)
|
||||||
|
#define HAV_Profiler_BeginZoneBandwidth(label, byte_count) HAV_Profiler_BeginZone_(HAV_STR8(label), __COUNTER__ + 1, byte_count)
|
||||||
|
|
||||||
|
static void HAV_Profiler_Dump ();
|
||||||
|
static HAV_ProfilerZone HAV_Profiler_BeginZone_(HAV_Str8 label, uint32_t index, u64 byte_count);
|
||||||
|
static void HAV_Profiler_EndZone (HAV_ProfilerZone zone);
|
||||||
|
|
||||||
|
// NOTE: PCG32 /////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// NOTE: PCG RNG from Demetri Spanos: https://github.com/demetri/scribbles
|
||||||
|
// pcg32_pie, based on the minimal C version from O'Neill at pcg-random.org;
|
||||||
|
// I've made a few (subjective) UX improvements for beginner use
|
||||||
|
//
|
||||||
|
// I'm not allowing the user to pick the stream/increment constant at all,
|
||||||
|
// since there is almost never a reason for doing this in common applications.
|
||||||
|
// This means that the prng state is reduced to a single uint64_t which also
|
||||||
|
// means we can avoid having a state struct at all. The (fixed) stream constant
|
||||||
|
// uses the leading hex digits of pi and e multipled by 2^30 (c90fdaa2 and
|
||||||
|
// adf85459).
|
||||||
|
//
|
||||||
|
// I have also added an XOR with the same digits on the output path prior
|
||||||
|
// to xorshift mixing. This prevents the "surprising" result that the
|
||||||
|
// first "random 32-bit number" from a (very common) 0 seed is 0.
|
||||||
|
//
|
||||||
|
// use:
|
||||||
|
// uint64_t state = 12345; // whatever you like can go here
|
||||||
|
// uint32_t some_random_32_bits = pcg32_pie(&state);
|
||||||
|
// uint32_t more_random_32_bits = pcg32_pie(&state);
|
||||||
|
uint32_t HAV_PCG32_Pie (uint64_t *state);
|
||||||
|
f64 HAV_PCG32_PieF64(uint64_t *state, f64 min, f64 max);
|
||||||
|
|
||||||
|
// NOTE: Buffer ////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
typedef struct HAV_Buffer {
|
||||||
|
char *data;
|
||||||
|
size_t size;
|
||||||
|
} HAV_Buffer;
|
||||||
|
|
||||||
|
typedef struct HAV_BufferIterator {
|
||||||
|
HAV_Buffer buffer;
|
||||||
|
size_t index;
|
||||||
|
} HAV_BufferIterator;
|
||||||
|
|
||||||
|
bool HAV_BufferIsValid (HAV_Buffer buffer);
|
||||||
|
HAV_BufferIterator HAV_BufferIteratorInit (HAV_Buffer buffer);
|
||||||
|
bool HAV_BufferIteratorHasMoreBytes(HAV_BufferIterator it);
|
||||||
|
uint8_t HAV_BufferIteratorNextByte (HAV_BufferIterator *it);
|
||||||
|
|
||||||
|
// NOTE: File //////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
HAV_Buffer HAV_FileRead (char const *file_path);
|
||||||
|
void HAV_FileFree (HAV_Buffer buffer);
|
||||||
|
bool HAV_FileWrite(char const *file_path, void const *buffer, size_t buffer_size);
|
||||||
|
|
||||||
|
// NOTE: Print /////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void HAV_PrintHandle(void *handle, HAV_Str8 string);
|
||||||
|
void HAV_PrintLn (HAV_Str8 string);
|
||||||
|
void HAV_PrintLnFmt (char const *fmt, ...);
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 65
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
static f64 Square(f64 A)
|
||||||
|
{
|
||||||
|
f64 Result = (A*A);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 RadiansFromDegrees(f64 Degrees)
|
||||||
|
{
|
||||||
|
f64 Result = 0.01745329251994329577 * Degrees;
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): EarthRadius is generally expected to be 6372.8
|
||||||
|
static f64 ReferenceHaversine(f64 X0, f64 Y0, f64 X1, f64 Y1, f64 EarthRadius)
|
||||||
|
{
|
||||||
|
/* NOTE(casey): This is not meant to be a "good" way to calculate the Haversine distance.
|
||||||
|
Instead, it attempts to follow, as closely as possible, the formula used in the real-world
|
||||||
|
question on which these homework exercises are loosely based.
|
||||||
|
*/
|
||||||
|
|
||||||
|
f64 lat1 = Y0;
|
||||||
|
f64 lat2 = Y1;
|
||||||
|
f64 lon1 = X0;
|
||||||
|
f64 lon2 = X1;
|
||||||
|
|
||||||
|
f64 dLat = RadiansFromDegrees(lat2 - lat1);
|
||||||
|
f64 dLon = RadiansFromDegrees(lon2 - lon1);
|
||||||
|
lat1 = RadiansFromDegrees(lat1);
|
||||||
|
lat2 = RadiansFromDegrees(lat2);
|
||||||
|
|
||||||
|
f64 a = Square(sin(dLat/2.0)) + cos(lat1)*cos(lat2)*Square(sin(dLon/2));
|
||||||
|
f64 c = 2.0*asin(sqrt(a));
|
||||||
|
|
||||||
|
f64 Result = EarthRadius * c;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 66
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
typedef double f64;
|
||||||
|
#define U64Max UINT64_MAX
|
||||||
|
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
|
||||||
|
struct random_series
|
||||||
|
{
|
||||||
|
u64 A, B, C, D;
|
||||||
|
};
|
||||||
|
|
||||||
|
static u64 RotateLeft(u64 V, int Shift)
|
||||||
|
{
|
||||||
|
u64 Result = ((V << Shift) | (V >> (64-Shift)));
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 RandomU64(random_series *Series)
|
||||||
|
{
|
||||||
|
u64 A = Series->A;
|
||||||
|
u64 B = Series->B;
|
||||||
|
u64 C = Series->C;
|
||||||
|
u64 D = Series->D;
|
||||||
|
|
||||||
|
u64 E = A - RotateLeft(B, 27);
|
||||||
|
|
||||||
|
A = (B ^ RotateLeft(C, 17));
|
||||||
|
B = (C + D);
|
||||||
|
C = (D + E);
|
||||||
|
D = (E + A);
|
||||||
|
|
||||||
|
Series->A = A;
|
||||||
|
Series->B = B;
|
||||||
|
Series->C = C;
|
||||||
|
Series->D = D;
|
||||||
|
|
||||||
|
return D;
|
||||||
|
}
|
||||||
|
|
||||||
|
static random_series Seed(u64 Value)
|
||||||
|
{
|
||||||
|
random_series Series = {};
|
||||||
|
|
||||||
|
// NOTE(casey): This is the seed pattern for JSF generators, as per the original post
|
||||||
|
Series.A = 0xf1ea5eed;
|
||||||
|
Series.B = Value;
|
||||||
|
Series.C = Value;
|
||||||
|
Series.D = Value;
|
||||||
|
|
||||||
|
u32 Count = 20;
|
||||||
|
while(Count--)
|
||||||
|
{
|
||||||
|
RandomU64(&Series);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Series;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 RandomInRange(random_series *Series, f64 Min, f64 Max)
|
||||||
|
{
|
||||||
|
f64 t = (f64)RandomU64(Series) / (f64)U64Max;
|
||||||
|
f64 Result = (1.0 - t)*Min + t*Max;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FILE *Open(long long unsigned PairCount, char *Label, char *Extension)
|
||||||
|
{
|
||||||
|
char Temp[256];
|
||||||
|
sprintf(Temp, "data_%llu_%s.%s", PairCount, Label, Extension);
|
||||||
|
FILE *Result = fopen(Temp, "wb");
|
||||||
|
if(!Result)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Unable to open \"%s\" for writing.\n", Temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 RandomDegree(random_series *Series, f64 Center, f64 Radius, f64 MaxAllowed)
|
||||||
|
{
|
||||||
|
f64 MinVal = Center - Radius;
|
||||||
|
if(MinVal < -MaxAllowed)
|
||||||
|
{
|
||||||
|
MinVal = -MaxAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 MaxVal = Center + Radius;
|
||||||
|
if(MaxVal > MaxAllowed)
|
||||||
|
{
|
||||||
|
MaxVal = MaxAllowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 Result = RandomInRange(Series, MinVal, MaxVal);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
if(ArgCount == 4)
|
||||||
|
{
|
||||||
|
u64 ClusterCountLeft = U64Max;
|
||||||
|
f64 MaxAllowedX = 180;
|
||||||
|
f64 MaxAllowedY = 90;
|
||||||
|
|
||||||
|
f64 XCenter = 0;
|
||||||
|
f64 YCenter = 0;
|
||||||
|
f64 XRadius = MaxAllowedX;
|
||||||
|
f64 YRadius = MaxAllowedY;
|
||||||
|
|
||||||
|
char *MethodName = Args[1];
|
||||||
|
if(strcmp(MethodName, "cluster") == 0)
|
||||||
|
{
|
||||||
|
ClusterCountLeft = 0;
|
||||||
|
}
|
||||||
|
else if(strcmp(MethodName, "uniform") != 0)
|
||||||
|
{
|
||||||
|
MethodName = "uniform";
|
||||||
|
fprintf(stderr, "WARNING: Unrecognized method name. Using 'uniform'.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 SeedValue = atoll(Args[2]);
|
||||||
|
random_series Series = Seed(SeedValue);
|
||||||
|
|
||||||
|
u64 MaxPairCount = (1ULL << 34);
|
||||||
|
u64 PairCount = atoll(Args[3]);
|
||||||
|
if(PairCount < MaxPairCount)
|
||||||
|
{
|
||||||
|
u64 ClusterCountMax = 1 + (PairCount / 64);
|
||||||
|
|
||||||
|
FILE *FlexJSON = Open(PairCount, "flex", "json");
|
||||||
|
FILE *HaverAnswers = Open(PairCount, "haveranswer", "f64");
|
||||||
|
if(FlexJSON && HaverAnswers)
|
||||||
|
{
|
||||||
|
fprintf(FlexJSON, "{\"pairs\":[\n");
|
||||||
|
f64 Sum = 0;
|
||||||
|
f64 SumCoef = 1.0 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
if(ClusterCountLeft-- == 0)
|
||||||
|
{
|
||||||
|
ClusterCountLeft = ClusterCountMax;
|
||||||
|
XCenter = RandomInRange(&Series, -MaxAllowedX, MaxAllowedX);
|
||||||
|
YCenter = RandomInRange(&Series, -MaxAllowedY, MaxAllowedY);
|
||||||
|
XRadius = RandomInRange(&Series, 0, MaxAllowedX);
|
||||||
|
YRadius = RandomInRange(&Series, 0, MaxAllowedY);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 X0 = RandomDegree(&Series, XCenter, XRadius, MaxAllowedX);
|
||||||
|
f64 Y0 = RandomDegree(&Series, YCenter, YRadius, MaxAllowedY);
|
||||||
|
f64 X1 = RandomDegree(&Series, XCenter, XRadius, MaxAllowedX);
|
||||||
|
f64 Y1 = RandomDegree(&Series, YCenter, YRadius, MaxAllowedY);
|
||||||
|
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 HaversineDistance = ReferenceHaversine(X0, Y0, X1, Y1, EarthRadius);
|
||||||
|
|
||||||
|
Sum += SumCoef*HaversineDistance;
|
||||||
|
|
||||||
|
char *JSONSep = (PairIndex == (PairCount - 1)) ? "\n" : ",\n";
|
||||||
|
fprintf(FlexJSON, " {\"x0\":%.16f, \"y0\":%.16f, \"x1\":%.16f, \"y1\":%.16f}%s", X0, Y0, X1, Y1, JSONSep);
|
||||||
|
|
||||||
|
fwrite(&HaversineDistance, sizeof(HaversineDistance), 1, HaverAnswers);
|
||||||
|
}
|
||||||
|
fprintf(FlexJSON, "]}\n");
|
||||||
|
fwrite(&Sum, sizeof(Sum), 1, HaverAnswers);
|
||||||
|
|
||||||
|
fprintf(stdout, "Method: %s\n", MethodName);
|
||||||
|
fprintf(stdout, "Random seed: %llu\n", SeedValue);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Expected sum: %.16f\n", Sum);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(FlexJSON) fclose(FlexJSON);
|
||||||
|
if(HaverAnswers) fclose(HaverAnswers);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "To avoid accidentally generating massive files, number of pairs must be less than %llu.\n", MaxPairCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [uniform/cluster] [random seed] [number of coordinate pairs to generate]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 67
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0069_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 68
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
struct buffer
|
||||||
|
{
|
||||||
|
size_t Count;
|
||||||
|
u8 *Data;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CONSTANT_STRING(String) {sizeof(String) - 1, (u8 *)(String)}
|
||||||
|
|
||||||
|
static b32 IsInBounds(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
b32 Result = (At < Source.Count);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 AreEqual(buffer A, buffer B)
|
||||||
|
{
|
||||||
|
if(A.Count != B.Count)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(u64 Index = 0; Index < A.Count; ++Index)
|
||||||
|
{
|
||||||
|
if(A.Data[Index] != B.Data[Index])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static buffer AllocateBuffer(size_t Count)
|
||||||
|
{
|
||||||
|
buffer Result = {};
|
||||||
|
Result.Data = (u8 *)malloc(Count);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
Result.Count = Count;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to allocate %llu bytes.\n", Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FreeBuffer(buffer *Buffer)
|
||||||
|
{
|
||||||
|
if(Buffer->Data)
|
||||||
|
{
|
||||||
|
free(Buffer->Data);
|
||||||
|
}
|
||||||
|
*Buffer = {};
|
||||||
|
}
|
||||||
@@ -0,0 +1,508 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 69
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
enum json_token_type
|
||||||
|
{
|
||||||
|
Token_end_of_stream,
|
||||||
|
Token_error,
|
||||||
|
|
||||||
|
Token_open_brace,
|
||||||
|
Token_open_bracket,
|
||||||
|
Token_close_brace,
|
||||||
|
Token_close_bracket,
|
||||||
|
Token_comma,
|
||||||
|
Token_colon,
|
||||||
|
Token_semi_colon,
|
||||||
|
Token_string_literal,
|
||||||
|
Token_number,
|
||||||
|
Token_true,
|
||||||
|
Token_false,
|
||||||
|
Token_null,
|
||||||
|
|
||||||
|
Token_count,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_token
|
||||||
|
{
|
||||||
|
json_token_type Type;
|
||||||
|
buffer Value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_element
|
||||||
|
{
|
||||||
|
buffer Label;
|
||||||
|
buffer Value;
|
||||||
|
json_element *FirstSubElement;
|
||||||
|
|
||||||
|
json_element *NextSibling;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_parser
|
||||||
|
{
|
||||||
|
buffer Source;
|
||||||
|
u64 At;
|
||||||
|
b32 HadError;
|
||||||
|
};
|
||||||
|
|
||||||
|
static b32 IsJSONDigit(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
b32 Result = false;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Val = Source.Data[At];
|
||||||
|
Result = ((Val >= '0') && (Val <= '9'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsJSONWhitespace(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
b32 Result = false;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Val = Source.Data[At];
|
||||||
|
Result = ((Val == ' ') || (Val == '\t') || (Val == '\n') || (Val == '\r'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsParsing(json_parser *Parser)
|
||||||
|
{
|
||||||
|
b32 Result = !Parser->HadError && IsInBounds(Parser->Source, Parser->At);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Error(json_parser *Parser, json_token Token, char const *Message)
|
||||||
|
{
|
||||||
|
Parser->HadError = true;
|
||||||
|
fprintf(stderr, "ERROR: \"%.*s\" - %s\n", (u32)Token.Value.Count, (char *)Token.Value.Data, Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ParseKeyword(buffer Source, u64 *At, buffer KeywordRemaining, json_token_type Type, json_token *Result)
|
||||||
|
{
|
||||||
|
if((Source.Count - *At) >= KeywordRemaining.Count)
|
||||||
|
{
|
||||||
|
buffer Check = Source;
|
||||||
|
Check.Data += *At;
|
||||||
|
Check.Count = KeywordRemaining.Count;
|
||||||
|
if(AreEqual(Check, KeywordRemaining))
|
||||||
|
{
|
||||||
|
Result->Type = Type;
|
||||||
|
Result->Value.Count += KeywordRemaining.Count;
|
||||||
|
*At += KeywordRemaining.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_token GetJSONToken(json_parser *Parser)
|
||||||
|
{
|
||||||
|
json_token Result = {};
|
||||||
|
|
||||||
|
buffer Source = Parser->Source;
|
||||||
|
u64 At = Parser->At;
|
||||||
|
|
||||||
|
while(IsJSONWhitespace(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
Result.Type = Token_error;
|
||||||
|
Result.Value.Count = 1;
|
||||||
|
Result.Value.Data = Source.Data + At;
|
||||||
|
u8 Val = Source.Data[At++];
|
||||||
|
switch(Val)
|
||||||
|
{
|
||||||
|
case '{': {Result.Type = Token_open_brace;} break;
|
||||||
|
case '[': {Result.Type = Token_open_bracket;} break;
|
||||||
|
case '}': {Result.Type = Token_close_brace;} break;
|
||||||
|
case ']': {Result.Type = Token_close_bracket;} break;
|
||||||
|
case ',': {Result.Type = Token_comma;} break;
|
||||||
|
case ':': {Result.Type = Token_colon;} break;
|
||||||
|
case ';': {Result.Type = Token_semi_colon;} break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("alse"), Token_false, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("ull"), Token_null, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("rue"), Token_true, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
{
|
||||||
|
Result.Type = Token_string_literal;
|
||||||
|
|
||||||
|
u64 StringStart = At;
|
||||||
|
|
||||||
|
while(IsInBounds(Source, At) && (Source.Data[At] != '"'))
|
||||||
|
{
|
||||||
|
if(IsInBounds(Source, (At + 1)) &&
|
||||||
|
(Source.Data[At] == '\\') &&
|
||||||
|
(Source.Data[At + 1] == '"'))
|
||||||
|
{
|
||||||
|
// NOTE(casey): Skip escaped quotation marks
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Value.Data = Source.Data + StringStart;
|
||||||
|
Result.Value.Count = At - StringStart;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
{
|
||||||
|
u64 Start = At - 1;
|
||||||
|
Result.Type = Token_number;
|
||||||
|
|
||||||
|
// NOTE(casey): Move past a leading negative sign if one exists
|
||||||
|
if((Val == '-') && IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
Val = Source.Data[At++];
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If the leading digit wasn't 0, parse any digits before the decimal point
|
||||||
|
if(Val != '0')
|
||||||
|
{
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If there is a decimal point, parse any digits after the decimal point
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If it's in scientific notation, parse any digits after the "e"
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == '+') || (Source.Data[At] == '-')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Value.Count = At - Start;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser->At = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSONList(json_parser *Parser, json_token StartingToken, json_token_type EndType, b32 HasLabels);
|
||||||
|
static json_element *ParseJSONElement(json_parser *Parser, buffer Label, json_token Value)
|
||||||
|
{
|
||||||
|
b32 Valid = true;
|
||||||
|
|
||||||
|
json_element *SubElement = 0;
|
||||||
|
if(Value.Type == Token_open_bracket)
|
||||||
|
{
|
||||||
|
SubElement = ParseJSONList(Parser, Value, Token_close_bracket, false);
|
||||||
|
}
|
||||||
|
else if(Value.Type == Token_open_brace)
|
||||||
|
{
|
||||||
|
SubElement = ParseJSONList(Parser, Value, Token_close_brace, true);
|
||||||
|
}
|
||||||
|
else if((Value.Type == Token_string_literal) ||
|
||||||
|
(Value.Type == Token_true) ||
|
||||||
|
(Value.Type == Token_false) ||
|
||||||
|
(Value.Type == Token_null) ||
|
||||||
|
(Value.Type == Token_number))
|
||||||
|
{
|
||||||
|
// NOTE(casey): Nothing to do here, since there is no additional data
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_element *Result = 0;
|
||||||
|
|
||||||
|
if(Valid)
|
||||||
|
{
|
||||||
|
Result = (json_element *)malloc(sizeof(json_element));
|
||||||
|
Result->Label = Label;
|
||||||
|
Result->Value = Value.Value;
|
||||||
|
Result->FirstSubElement = SubElement;
|
||||||
|
Result->NextSibling = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSONList(json_parser *Parser, json_token StartingToken, json_token_type EndType, b32 HasLabels)
|
||||||
|
{
|
||||||
|
json_element *FirstElement = {};
|
||||||
|
json_element *LastElement = {};
|
||||||
|
|
||||||
|
while(IsParsing(Parser))
|
||||||
|
{
|
||||||
|
buffer Label = {};
|
||||||
|
json_token Value = GetJSONToken(Parser);
|
||||||
|
if(HasLabels)
|
||||||
|
{
|
||||||
|
if(Value.Type == Token_string_literal)
|
||||||
|
{
|
||||||
|
Label = Value.Value;
|
||||||
|
|
||||||
|
json_token Colon = GetJSONToken(Parser);
|
||||||
|
if(Colon.Type == Token_colon)
|
||||||
|
{
|
||||||
|
Value = GetJSONToken(Parser);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Parser, Colon, "Expected colon after field name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(Value.Type != EndType)
|
||||||
|
{
|
||||||
|
Error(Parser, Value, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_element *Element = ParseJSONElement(Parser, Label, Value);
|
||||||
|
if(Element)
|
||||||
|
{
|
||||||
|
LastElement = (LastElement ? LastElement->NextSibling : FirstElement) = Element;
|
||||||
|
}
|
||||||
|
else if(Value.Type == EndType)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Parser, Value, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
json_token Comma = GetJSONToken(Parser);
|
||||||
|
if(Comma.Type == EndType)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if(Comma.Type != Token_comma)
|
||||||
|
{
|
||||||
|
Error(Parser, Comma, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FirstElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSON(buffer InputJSON)
|
||||||
|
{
|
||||||
|
json_parser Parser = {};
|
||||||
|
Parser.Source = InputJSON;
|
||||||
|
|
||||||
|
json_element *Result = ParseJSONElement(&Parser, {}, GetJSONToken(&Parser));
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FreeJSON(json_element *Element)
|
||||||
|
{
|
||||||
|
while(Element)
|
||||||
|
{
|
||||||
|
json_element *FreeElement = Element;
|
||||||
|
Element = Element->NextSibling;
|
||||||
|
|
||||||
|
FreeJSON(FreeElement->FirstSubElement);
|
||||||
|
free(FreeElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *LookupElement(json_element *Object, buffer ElementName)
|
||||||
|
{
|
||||||
|
json_element *Result = 0;
|
||||||
|
|
||||||
|
if(Object)
|
||||||
|
{
|
||||||
|
for(json_element *Search = Object->FirstSubElement; Search; Search = Search->NextSibling)
|
||||||
|
{
|
||||||
|
if(AreEqual(Search->Label, ElementName))
|
||||||
|
{
|
||||||
|
Result = Search;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertJSONSign(buffer Source, u64 *AtResult)
|
||||||
|
{
|
||||||
|
u64 At = *AtResult;
|
||||||
|
|
||||||
|
f64 Result = 1.0;
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '-'))
|
||||||
|
{
|
||||||
|
Result = -1.0;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
*AtResult = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertJSONNumber(buffer Source, u64 *AtResult)
|
||||||
|
{
|
||||||
|
u64 At = *AtResult;
|
||||||
|
|
||||||
|
f64 Result = 0.0;
|
||||||
|
while(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Char = Source.Data[At] - (u8)'0';
|
||||||
|
if(Char < 10)
|
||||||
|
{
|
||||||
|
Result = 10.0*Result + (f64)Char;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*AtResult = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertElementToF64(json_element *Object, buffer ElementName)
|
||||||
|
{
|
||||||
|
f64 Result = 0.0;
|
||||||
|
|
||||||
|
json_element *Element = LookupElement(Object, ElementName);
|
||||||
|
if(Element)
|
||||||
|
{
|
||||||
|
buffer Source = Element->Value;
|
||||||
|
u64 At = 0;
|
||||||
|
|
||||||
|
f64 Sign = ConvertJSONSign(Source, &At);
|
||||||
|
f64 Number = ConvertJSONNumber(Source, &At);
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
f64 C = 1.0 / 10.0;
|
||||||
|
while(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Char = Source.Data[At] - (u8)'0';
|
||||||
|
if(Char < 10)
|
||||||
|
{
|
||||||
|
Number = Number + C*(f64)Char;
|
||||||
|
C *= 1.0 / 10.0;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '+'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 ExponentSign = ConvertJSONSign(Source, &At);
|
||||||
|
f64 Exponent = ExponentSign*ConvertJSONNumber(Source, &At);
|
||||||
|
Number *= pow(10.0, Exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result = Sign*Number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ParseHaversinePairs(buffer InputJSON, u64 MaxPairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
u64 PairCount = 0;
|
||||||
|
|
||||||
|
json_element *JSON = ParseJSON(InputJSON);
|
||||||
|
json_element *PairsArray = LookupElement(JSON, CONSTANT_STRING("pairs"));
|
||||||
|
if(PairsArray)
|
||||||
|
{
|
||||||
|
for(json_element *Element = PairsArray->FirstSubElement;
|
||||||
|
Element && (PairCount < MaxPairCount);
|
||||||
|
Element = Element->NextSibling)
|
||||||
|
{
|
||||||
|
haversine_pair *Pair = Pairs + PairCount++;
|
||||||
|
|
||||||
|
Pair->X0 = ConvertElementToF64(Element, CONSTANT_STRING("x0"));
|
||||||
|
Pair->Y0 = ConvertElementToF64(Element, CONSTANT_STRING("y0"));
|
||||||
|
Pair->X1 = ConvertElementToF64(Element, CONSTANT_STRING("x1"));
|
||||||
|
Pair->Y1 = ConvertElementToF64(Element, CONSTANT_STRING("y1"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeJSON(JSON);
|
||||||
|
|
||||||
|
return PairCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 70
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
|
||||||
|
#include <intrin.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
static u64 GetOSTimerFreq(void)
|
||||||
|
{
|
||||||
|
LARGE_INTEGER Freq;
|
||||||
|
QueryPerformanceFrequency(&Freq);
|
||||||
|
return Freq.QuadPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ReadOSTimer(void)
|
||||||
|
{
|
||||||
|
LARGE_INTEGER Value;
|
||||||
|
QueryPerformanceCounter(&Value);
|
||||||
|
return Value.QuadPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <x86intrin.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
static u64 GetOSTimerFreq(void)
|
||||||
|
{
|
||||||
|
return 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ReadOSTimer(void)
|
||||||
|
{
|
||||||
|
// NOTE(casey): The "struct" keyword is not necessary here when compiling in C++,
|
||||||
|
// but just in case anyone is using this file from C, I include it.
|
||||||
|
struct timeval Value;
|
||||||
|
gettimeofday(&Value, 0);
|
||||||
|
|
||||||
|
u64 Result = GetOSTimerFreq()*(u64)Value.tv_sec + (u64)Value.tv_usec;
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* NOTE(casey): This does not need to be "inline", it could just be "static"
|
||||||
|
because compilers will inline it anyway. But compilers will warn about
|
||||||
|
static functions that aren't used. So "inline" is just the simplest way
|
||||||
|
to tell them to stop complaining about that. */
|
||||||
|
inline u64 ReadCPUTimer(void)
|
||||||
|
{
|
||||||
|
// NOTE(casey): If you were on ARM, you would need to replace __rdtsc
|
||||||
|
// with one of their performance counter read instructions, depending
|
||||||
|
// on which ones are available on your platform.
|
||||||
|
|
||||||
|
return __rdtsc();
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 71
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
typedef uint64_t u64;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#include "listing_0070_platform_metrics.cpp"
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
u64 OSFreq = GetOSTimerFreq();
|
||||||
|
printf(" OS Freq: %llu\n", OSFreq);
|
||||||
|
|
||||||
|
u64 OSStart = ReadOSTimer();
|
||||||
|
u64 OSEnd = 0;
|
||||||
|
u64 OSElapsed = 0;
|
||||||
|
while(OSElapsed < OSFreq)
|
||||||
|
{
|
||||||
|
OSEnd = ReadOSTimer();
|
||||||
|
OSElapsed = OSEnd - OSStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(" OS Timer: %llu -> %llu = %llu elapsed\n", OSStart, OSEnd, OSElapsed);
|
||||||
|
printf(" OS Seconds: %.4f\n", (f64)OSElapsed/(f64)OSFreq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 72
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
typedef uint64_t u64;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#include "listing_0070_platform_metrics.cpp"
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
u64 OSFreq = GetOSTimerFreq();
|
||||||
|
printf(" OS Freq: %llu\n", OSFreq);
|
||||||
|
|
||||||
|
u64 CPUStart = ReadCPUTimer();
|
||||||
|
u64 OSStart = ReadOSTimer();
|
||||||
|
u64 OSEnd = 0;
|
||||||
|
u64 OSElapsed = 0;
|
||||||
|
while(OSElapsed < OSFreq)
|
||||||
|
{
|
||||||
|
OSEnd = ReadOSTimer();
|
||||||
|
OSElapsed = OSEnd - OSStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 CPUEnd = ReadCPUTimer();
|
||||||
|
u64 CPUElapsed = CPUEnd - CPUStart;
|
||||||
|
|
||||||
|
printf(" OS Timer: %llu -> %llu = %llu elapsed\n", OSStart, OSEnd, OSElapsed);
|
||||||
|
printf(" OS Seconds: %.4f\n", (f64)OSElapsed/(f64)OSFreq);
|
||||||
|
|
||||||
|
printf(" CPU Timer: %llu -> %llu = %llu elapsed\n", CPUStart, CPUEnd, CPUElapsed);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 73
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
typedef uint64_t u64;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#include "listing_0070_platform_metrics.cpp"
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
u64 MillisecondsToWait = 1000;
|
||||||
|
if(ArgCount == 2)
|
||||||
|
{
|
||||||
|
MillisecondsToWait = atol(Args[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 OSFreq = GetOSTimerFreq();
|
||||||
|
printf(" OS Freq: %llu (reported)\n", OSFreq);
|
||||||
|
|
||||||
|
u64 CPUStart = ReadCPUTimer();
|
||||||
|
u64 OSStart = ReadOSTimer();
|
||||||
|
u64 OSEnd = 0;
|
||||||
|
u64 OSElapsed = 0;
|
||||||
|
u64 OSWaitTime = OSFreq * MillisecondsToWait / 1000;
|
||||||
|
while(OSElapsed < OSWaitTime)
|
||||||
|
{
|
||||||
|
OSEnd = ReadOSTimer();
|
||||||
|
OSElapsed = OSEnd - OSStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 CPUEnd = ReadCPUTimer();
|
||||||
|
u64 CPUElapsed = CPUEnd - CPUStart;
|
||||||
|
u64 CPUFreq = 0;
|
||||||
|
if(OSElapsed)
|
||||||
|
{
|
||||||
|
CPUFreq = OSFreq * CPUElapsed / OSElapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(" OS Timer: %llu -> %llu = %llu elapsed\n", OSStart, OSEnd, OSElapsed);
|
||||||
|
printf(" OS Seconds: %.4f\n", (f64)OSElapsed/(f64)OSFreq);
|
||||||
|
|
||||||
|
printf(" CPU Timer: %llu -> %llu = %llu elapsed\n", CPUStart, CPUEnd, CPUElapsed);
|
||||||
|
printf(" CPU Freq: %llu (guessed)\n", CPUFreq);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 74
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
|
||||||
|
#include <intrin.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
static u64 GetOSTimerFreq(void)
|
||||||
|
{
|
||||||
|
LARGE_INTEGER Freq;
|
||||||
|
QueryPerformanceFrequency(&Freq);
|
||||||
|
return Freq.QuadPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ReadOSTimer(void)
|
||||||
|
{
|
||||||
|
LARGE_INTEGER Value;
|
||||||
|
QueryPerformanceCounter(&Value);
|
||||||
|
return Value.QuadPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <x86intrin.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
static u64 GetOSTimerFreq(void)
|
||||||
|
{
|
||||||
|
return 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ReadOSTimer(void)
|
||||||
|
{
|
||||||
|
// NOTE(casey): The "struct" keyword is not necessary here when compiling in C++,
|
||||||
|
// but just in case anyone is using this file from C, I include it.
|
||||||
|
struct timeval Value;
|
||||||
|
gettimeofday(&Value, 0);
|
||||||
|
|
||||||
|
u64 Result = GetOSTimerFreq()*(u64)Value.tv_sec + (u64)Value.tv_usec;
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* NOTE(casey): This does not need to be "inline", it could just be "static"
|
||||||
|
because compilers will inline it anyway. But compilers will warn about
|
||||||
|
static functions that aren't used. So "inline" is just the simplest way
|
||||||
|
to tell them to stop complaining about that. */
|
||||||
|
inline u64 ReadCPUTimer(void)
|
||||||
|
{
|
||||||
|
// NOTE(casey): If you were on ARM, you would need to replace __rdtsc
|
||||||
|
// with one of their performance counter read instructions, depending
|
||||||
|
// on which ones are available on your platform.
|
||||||
|
|
||||||
|
return __rdtsc();
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 EstimateCPUTimerFreq(void)
|
||||||
|
{
|
||||||
|
u64 MillisecondsToWait = 100;
|
||||||
|
u64 OSFreq = GetOSTimerFreq();
|
||||||
|
|
||||||
|
u64 CPUStart = ReadCPUTimer();
|
||||||
|
u64 OSStart = ReadOSTimer();
|
||||||
|
u64 OSEnd = 0;
|
||||||
|
u64 OSElapsed = 0;
|
||||||
|
u64 OSWaitTime = OSFreq * MillisecondsToWait / 1000;
|
||||||
|
while(OSElapsed < OSWaitTime)
|
||||||
|
{
|
||||||
|
OSEnd = ReadOSTimer();
|
||||||
|
OSElapsed = OSEnd - OSStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 CPUEnd = ReadCPUTimer();
|
||||||
|
u64 CPUElapsed = CPUEnd - CPUStart;
|
||||||
|
|
||||||
|
u64 CPUFreq = 0;
|
||||||
|
if(OSElapsed)
|
||||||
|
{
|
||||||
|
CPUFreq = OSFreq * CPUElapsed / OSElapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CPUFreq;
|
||||||
|
}
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 75
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "listing_0074_platform_metrics.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0069_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintTimeElapsed(char const *Label, u64 TotalTSCElapsed, u64 Begin, u64 End)
|
||||||
|
{
|
||||||
|
u64 Elapsed = End - Begin;
|
||||||
|
f64 Percent = 100.0 * ((f64)Elapsed / (f64)TotalTSCElapsed);
|
||||||
|
printf(" %s: %llu (%.2f%%)\n", Label, Elapsed, Percent);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
u64 Prof_Begin = 0;
|
||||||
|
u64 Prof_Read = 0;
|
||||||
|
u64 Prof_MiscSetup = 0;
|
||||||
|
u64 Prof_Parse = 0;
|
||||||
|
u64 Prof_Sum = 0;
|
||||||
|
u64 Prof_MiscOutput = 0;
|
||||||
|
u64 Prof_End = 0;
|
||||||
|
|
||||||
|
Prof_Begin = ReadCPUTimer();
|
||||||
|
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
Prof_Read = ReadCPUTimer();
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
Prof_MiscSetup = ReadCPUTimer();
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
|
||||||
|
Prof_Parse = ReadCPUTimer();
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
Prof_Sum = ReadCPUTimer();
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
Prof_MiscOutput = ReadCPUTimer();
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Prof_End = ReadCPUTimer();
|
||||||
|
|
||||||
|
if(Result == 0)
|
||||||
|
{
|
||||||
|
u64 TotalCPUElapsed = Prof_End - Prof_Begin;
|
||||||
|
|
||||||
|
u64 CPUFreq = EstimateCPUTimerFreq();
|
||||||
|
if(CPUFreq)
|
||||||
|
{
|
||||||
|
printf("\nTotal time: %0.4fms (CPU freq %llu)\n", 1000.0 * (f64)TotalCPUElapsed / (f64)CPUFreq, CPUFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintTimeElapsed("Startup", TotalCPUElapsed, Prof_Begin, Prof_Read);
|
||||||
|
PrintTimeElapsed("Read", TotalCPUElapsed, Prof_Read, Prof_MiscSetup);
|
||||||
|
PrintTimeElapsed("MiscSetup", TotalCPUElapsed, Prof_MiscSetup, Prof_Parse);
|
||||||
|
PrintTimeElapsed("Parse", TotalCPUElapsed, Prof_Parse, Prof_Sum);
|
||||||
|
PrintTimeElapsed("Sum", TotalCPUElapsed, Prof_Sum, Prof_MiscOutput);
|
||||||
|
PrintTimeElapsed("MiscOutput", TotalCPUElapsed, Prof_MiscOutput, Prof_End);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 76
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#include "listing_0074_platform_metrics.cpp"
|
||||||
|
|
||||||
|
struct profile_anchor
|
||||||
|
{
|
||||||
|
u64 TSCElapsed;
|
||||||
|
u64 HitCount;
|
||||||
|
char const *Label;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct profiler
|
||||||
|
{
|
||||||
|
profile_anchor Anchors[4096];
|
||||||
|
|
||||||
|
u64 StartTSC;
|
||||||
|
u64 EndTSC;
|
||||||
|
};
|
||||||
|
static profiler GlobalProfiler;
|
||||||
|
|
||||||
|
struct profile_block
|
||||||
|
{
|
||||||
|
profile_block(char const *Label_, u32 AnchorIndex_)
|
||||||
|
{
|
||||||
|
AnchorIndex = AnchorIndex_;
|
||||||
|
Label = Label_;
|
||||||
|
StartTSC = ReadCPUTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
~profile_block(void)
|
||||||
|
{
|
||||||
|
u64 Elapsed = ReadCPUTimer() - StartTSC;
|
||||||
|
|
||||||
|
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
|
||||||
|
Anchor->TSCElapsed += Elapsed;
|
||||||
|
++Anchor->HitCount;
|
||||||
|
|
||||||
|
/* NOTE(casey): This write happens every time solely because there is no
|
||||||
|
straightforward way in C++ to have the same ease-of-use. In a better programming
|
||||||
|
language, it would be simple to have the anchor points gathered and labeled at compile
|
||||||
|
time, and this repetative write would be eliminated. */
|
||||||
|
Anchor->Label = Label;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *Label;
|
||||||
|
u64 StartTSC;
|
||||||
|
u32 AnchorIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NameConcat2(A, B) A##B
|
||||||
|
#define NameConcat(A, B) NameConcat2(A, B)
|
||||||
|
#define TimeBlock(Name) profile_block NameConcat(Block, __LINE__)(Name, __COUNTER__ + 1);
|
||||||
|
#define TimeFunction TimeBlock(__func__)
|
||||||
|
|
||||||
|
static void PrintTimeElapsed(u64 TotalTSCElapsed, profile_anchor *Anchor)
|
||||||
|
{
|
||||||
|
u64 Elapsed = Anchor->TSCElapsed;
|
||||||
|
f64 Percent = 100.0 * ((f64)Elapsed / (f64)TotalTSCElapsed);
|
||||||
|
printf(" %s[%llu]: %llu (%.2f%%)\n", Anchor->Label, Anchor->HitCount, Elapsed, Percent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BeginProfile(void)
|
||||||
|
{
|
||||||
|
GlobalProfiler.StartTSC = ReadCPUTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EndAndPrintProfile()
|
||||||
|
{
|
||||||
|
GlobalProfiler.EndTSC = ReadCPUTimer();
|
||||||
|
u64 CPUFreq = EstimateCPUTimerFreq();
|
||||||
|
|
||||||
|
u64 TotalCPUElapsed = GlobalProfiler.EndTSC - GlobalProfiler.StartTSC;
|
||||||
|
|
||||||
|
if(CPUFreq)
|
||||||
|
{
|
||||||
|
printf("\nTotal time: %0.4fms (CPU freq %llu)\n", 1000.0 * (f64)TotalCPUElapsed / (f64)CPUFreq, CPUFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfiler.Anchors); ++AnchorIndex)
|
||||||
|
{
|
||||||
|
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
|
||||||
|
if(Anchor->TSCElapsed)
|
||||||
|
{
|
||||||
|
PrintTimeElapsed(TotalCPUElapsed, Anchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,509 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 77
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
enum json_token_type
|
||||||
|
{
|
||||||
|
Token_end_of_stream,
|
||||||
|
Token_error,
|
||||||
|
|
||||||
|
Token_open_brace,
|
||||||
|
Token_open_bracket,
|
||||||
|
Token_close_brace,
|
||||||
|
Token_close_bracket,
|
||||||
|
Token_comma,
|
||||||
|
Token_colon,
|
||||||
|
Token_string_literal,
|
||||||
|
Token_number,
|
||||||
|
Token_true,
|
||||||
|
Token_false,
|
||||||
|
Token_null,
|
||||||
|
|
||||||
|
Token_count,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_token
|
||||||
|
{
|
||||||
|
json_token_type Type;
|
||||||
|
buffer Value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_element
|
||||||
|
{
|
||||||
|
buffer Label;
|
||||||
|
buffer Value;
|
||||||
|
json_element *FirstSubElement;
|
||||||
|
|
||||||
|
json_element *NextSibling;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_parser
|
||||||
|
{
|
||||||
|
buffer Source;
|
||||||
|
u64 At;
|
||||||
|
b32 HadError;
|
||||||
|
};
|
||||||
|
|
||||||
|
static b32 IsJSONDigit(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
b32 Result = false;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Val = Source.Data[At];
|
||||||
|
Result = ((Val >= '0') && (Val <= '9'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsJSONWhitespace(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
b32 Result = false;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Val = Source.Data[At];
|
||||||
|
Result = ((Val == ' ') || (Val == '\t') || (Val == '\n') || (Val == '\r'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsParsing(json_parser *Parser)
|
||||||
|
{
|
||||||
|
b32 Result = !Parser->HadError && IsInBounds(Parser->Source, Parser->At);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Error(json_parser *Parser, json_token Token, char const *Message)
|
||||||
|
{
|
||||||
|
Parser->HadError = true;
|
||||||
|
fprintf(stderr, "ERROR: \"%.*s\" - %s\n", (u32)Token.Value.Count, (char *)Token.Value.Data, Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ParseKeyword(buffer Source, u64 *At, buffer KeywordRemaining, json_token_type Type, json_token *Result)
|
||||||
|
{
|
||||||
|
if((Source.Count - *At) >= KeywordRemaining.Count)
|
||||||
|
{
|
||||||
|
buffer Check = Source;
|
||||||
|
Check.Data += *At;
|
||||||
|
Check.Count = KeywordRemaining.Count;
|
||||||
|
if(AreEqual(Check, KeywordRemaining))
|
||||||
|
{
|
||||||
|
Result->Type = Type;
|
||||||
|
Result->Value.Count += KeywordRemaining.Count;
|
||||||
|
*At += KeywordRemaining.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_token GetJSONToken(json_parser *Parser)
|
||||||
|
{
|
||||||
|
json_token Result = {};
|
||||||
|
|
||||||
|
buffer Source = Parser->Source;
|
||||||
|
u64 At = Parser->At;
|
||||||
|
|
||||||
|
while(IsJSONWhitespace(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
Result.Type = Token_error;
|
||||||
|
Result.Value.Count = 1;
|
||||||
|
Result.Value.Data = Source.Data + At;
|
||||||
|
u8 Val = Source.Data[At++];
|
||||||
|
switch(Val)
|
||||||
|
{
|
||||||
|
case '{': {Result.Type = Token_open_brace;} break;
|
||||||
|
case '[': {Result.Type = Token_open_bracket;} break;
|
||||||
|
case '}': {Result.Type = Token_close_brace;} break;
|
||||||
|
case ']': {Result.Type = Token_close_bracket;} break;
|
||||||
|
case ',': {Result.Type = Token_comma;} break;
|
||||||
|
case ':': {Result.Type = Token_colon;} break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("alse"), Token_false, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("ull"), Token_null, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("rue"), Token_true, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
{
|
||||||
|
Result.Type = Token_string_literal;
|
||||||
|
|
||||||
|
u64 StringStart = At;
|
||||||
|
|
||||||
|
while(IsInBounds(Source, At) && (Source.Data[At] != '"'))
|
||||||
|
{
|
||||||
|
if(IsInBounds(Source, (At + 1)) &&
|
||||||
|
(Source.Data[At] == '\\') &&
|
||||||
|
(Source.Data[At + 1] == '"'))
|
||||||
|
{
|
||||||
|
// NOTE(casey): Skip escaped quotation marks
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Value.Data = Source.Data + StringStart;
|
||||||
|
Result.Value.Count = At - StringStart;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
{
|
||||||
|
u64 Start = At - 1;
|
||||||
|
Result.Type = Token_number;
|
||||||
|
|
||||||
|
// NOTE(casey): Move past a leading negative sign if one exists
|
||||||
|
if((Val == '-') && IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
Val = Source.Data[At++];
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If the leading digit wasn't 0, parse any digits before the decimal point
|
||||||
|
if(Val != '0')
|
||||||
|
{
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If there is a decimal point, parse any digits after the decimal point
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If it's in scientific notation, parse any digits after the "e"
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == '+') || (Source.Data[At] == '-')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Value.Count = At - Start;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser->At = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels);
|
||||||
|
static json_element *ParseJSONElement(json_parser *Parser, buffer Label, json_token Value)
|
||||||
|
{
|
||||||
|
b32 Valid = true;
|
||||||
|
|
||||||
|
json_element *SubElement = 0;
|
||||||
|
if(Value.Type == Token_open_bracket)
|
||||||
|
{
|
||||||
|
SubElement = ParseJSONList(Parser, Token_close_bracket, false);
|
||||||
|
}
|
||||||
|
else if(Value.Type == Token_open_brace)
|
||||||
|
{
|
||||||
|
SubElement = ParseJSONList(Parser, Token_close_brace, true);
|
||||||
|
}
|
||||||
|
else if((Value.Type == Token_string_literal) ||
|
||||||
|
(Value.Type == Token_true) ||
|
||||||
|
(Value.Type == Token_false) ||
|
||||||
|
(Value.Type == Token_null) ||
|
||||||
|
(Value.Type == Token_number))
|
||||||
|
{
|
||||||
|
// NOTE(casey): Nothing to do here, since there is no additional data
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_element *Result = 0;
|
||||||
|
|
||||||
|
if(Valid)
|
||||||
|
{
|
||||||
|
Result = (json_element *)malloc(sizeof(json_element));
|
||||||
|
Result->Label = Label;
|
||||||
|
Result->Value = Value.Value;
|
||||||
|
Result->FirstSubElement = SubElement;
|
||||||
|
Result->NextSibling = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels)
|
||||||
|
{
|
||||||
|
json_element *FirstElement = {};
|
||||||
|
json_element *LastElement = {};
|
||||||
|
|
||||||
|
while(IsParsing(Parser))
|
||||||
|
{
|
||||||
|
buffer Label = {};
|
||||||
|
json_token Value = GetJSONToken(Parser);
|
||||||
|
if(HasLabels)
|
||||||
|
{
|
||||||
|
if(Value.Type == Token_string_literal)
|
||||||
|
{
|
||||||
|
Label = Value.Value;
|
||||||
|
|
||||||
|
json_token Colon = GetJSONToken(Parser);
|
||||||
|
if(Colon.Type == Token_colon)
|
||||||
|
{
|
||||||
|
Value = GetJSONToken(Parser);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Parser, Colon, "Expected colon after field name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(Value.Type != EndType)
|
||||||
|
{
|
||||||
|
Error(Parser, Value, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_element *Element = ParseJSONElement(Parser, Label, Value);
|
||||||
|
if(Element)
|
||||||
|
{
|
||||||
|
LastElement = (LastElement ? LastElement->NextSibling : FirstElement) = Element;
|
||||||
|
}
|
||||||
|
else if(Value.Type == EndType)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Parser, Value, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
json_token Comma = GetJSONToken(Parser);
|
||||||
|
if(Comma.Type == EndType)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if(Comma.Type != Token_comma)
|
||||||
|
{
|
||||||
|
Error(Parser, Comma, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FirstElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSON(buffer InputJSON)
|
||||||
|
{
|
||||||
|
json_parser Parser = {};
|
||||||
|
Parser.Source = InputJSON;
|
||||||
|
|
||||||
|
json_element *Result = ParseJSONElement(&Parser, {}, GetJSONToken(&Parser));
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FreeJSON(json_element *Element)
|
||||||
|
{
|
||||||
|
while(Element)
|
||||||
|
{
|
||||||
|
json_element *FreeElement = Element;
|
||||||
|
Element = Element->NextSibling;
|
||||||
|
|
||||||
|
FreeJSON(FreeElement->FirstSubElement);
|
||||||
|
free(FreeElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *LookupElement(json_element *Object, buffer ElementName)
|
||||||
|
{
|
||||||
|
json_element *Result = 0;
|
||||||
|
|
||||||
|
if(Object)
|
||||||
|
{
|
||||||
|
for(json_element *Search = Object->FirstSubElement; Search; Search = Search->NextSibling)
|
||||||
|
{
|
||||||
|
if(AreEqual(Search->Label, ElementName))
|
||||||
|
{
|
||||||
|
Result = Search;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertJSONSign(buffer Source, u64 *AtResult)
|
||||||
|
{
|
||||||
|
u64 At = *AtResult;
|
||||||
|
|
||||||
|
f64 Result = 1.0;
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '-'))
|
||||||
|
{
|
||||||
|
Result = -1.0;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
*AtResult = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertJSONNumber(buffer Source, u64 *AtResult)
|
||||||
|
{
|
||||||
|
u64 At = *AtResult;
|
||||||
|
|
||||||
|
f64 Result = 0.0;
|
||||||
|
while(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Char = Source.Data[At] - (u8)'0';
|
||||||
|
if(Char < 10)
|
||||||
|
{
|
||||||
|
Result = 10.0*Result + (f64)Char;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*AtResult = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertElementToF64(json_element *Object, buffer ElementName)
|
||||||
|
{
|
||||||
|
f64 Result = 0.0;
|
||||||
|
|
||||||
|
json_element *Element = LookupElement(Object, ElementName);
|
||||||
|
if(Element)
|
||||||
|
{
|
||||||
|
buffer Source = Element->Value;
|
||||||
|
u64 At = 0;
|
||||||
|
|
||||||
|
f64 Sign = ConvertJSONSign(Source, &At);
|
||||||
|
f64 Number = ConvertJSONNumber(Source, &At);
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
f64 C = 1.0 / 10.0;
|
||||||
|
while(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Char = Source.Data[At] - (u8)'0';
|
||||||
|
if(Char < 10)
|
||||||
|
{
|
||||||
|
Number = Number + C*(f64)Char;
|
||||||
|
C *= 1.0 / 10.0;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '+'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 ExponentSign = ConvertJSONSign(Source, &At);
|
||||||
|
f64 Exponent = ExponentSign*ConvertJSONNumber(Source, &At);
|
||||||
|
Number *= pow(10.0, Exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result = Sign*Number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ParseHaversinePairs(buffer InputJSON, u64 MaxPairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
u64 PairCount = 0;
|
||||||
|
|
||||||
|
json_element *JSON = ParseJSON(InputJSON);
|
||||||
|
|
||||||
|
json_element *PairsArray = LookupElement(JSON, CONSTANT_STRING("pairs"));
|
||||||
|
if(PairsArray)
|
||||||
|
{
|
||||||
|
for(json_element *Element = PairsArray->FirstSubElement;
|
||||||
|
Element && (PairCount < MaxPairCount);
|
||||||
|
Element = Element->NextSibling)
|
||||||
|
{
|
||||||
|
haversine_pair *Pair = Pairs + PairCount++;
|
||||||
|
|
||||||
|
Pair->X0 = ConvertElementToF64(Element, CONSTANT_STRING("x0"));
|
||||||
|
Pair->Y0 = ConvertElementToF64(Element, CONSTANT_STRING("y0"));
|
||||||
|
Pair->X1 = ConvertElementToF64(Element, CONSTANT_STRING("x1"));
|
||||||
|
Pair->Y1 = ConvertElementToF64(Element, CONSTANT_STRING("y1"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeJSON(JSON);
|
||||||
|
|
||||||
|
return PairCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 78
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "listing_0076_simple_profiler.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0077_profiled_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
BeginProfile();
|
||||||
|
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Result == 0)
|
||||||
|
{
|
||||||
|
EndAndPrintProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(__COUNTER__ < ArrayCount(profiler::Anchors), "Number of profile points exceeds size of profiler::Anchors array");
|
||||||
@@ -0,0 +1,510 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 79
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
enum json_token_type
|
||||||
|
{
|
||||||
|
Token_end_of_stream,
|
||||||
|
Token_error,
|
||||||
|
|
||||||
|
Token_open_brace,
|
||||||
|
Token_open_bracket,
|
||||||
|
Token_close_brace,
|
||||||
|
Token_close_bracket,
|
||||||
|
Token_comma,
|
||||||
|
Token_colon,
|
||||||
|
Token_string_literal,
|
||||||
|
Token_number,
|
||||||
|
Token_true,
|
||||||
|
Token_false,
|
||||||
|
Token_null,
|
||||||
|
|
||||||
|
Token_count,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_token
|
||||||
|
{
|
||||||
|
json_token_type Type;
|
||||||
|
buffer Value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_element
|
||||||
|
{
|
||||||
|
buffer Label;
|
||||||
|
buffer Value;
|
||||||
|
json_element *FirstSubElement;
|
||||||
|
|
||||||
|
json_element *NextSibling;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_parser
|
||||||
|
{
|
||||||
|
buffer Source;
|
||||||
|
u64 At;
|
||||||
|
b32 HadError;
|
||||||
|
};
|
||||||
|
|
||||||
|
static b32 IsJSONDigit(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
b32 Result = false;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Val = Source.Data[At];
|
||||||
|
Result = ((Val >= '0') && (Val <= '9'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsJSONWhitespace(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
b32 Result = false;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Val = Source.Data[At];
|
||||||
|
Result = ((Val == ' ') || (Val == '\t') || (Val == '\n') || (Val == '\r'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsParsing(json_parser *Parser)
|
||||||
|
{
|
||||||
|
b32 Result = !Parser->HadError && IsInBounds(Parser->Source, Parser->At);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Error(json_parser *Parser, json_token Token, char const *Message)
|
||||||
|
{
|
||||||
|
Parser->HadError = true;
|
||||||
|
fprintf(stderr, "ERROR: \"%.*s\" - %s\n", (u32)Token.Value.Count, (char *)Token.Value.Data, Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ParseKeyword(buffer Source, u64 *At, buffer KeywordRemaining, json_token_type Type, json_token *Result)
|
||||||
|
{
|
||||||
|
if((Source.Count - *At) >= KeywordRemaining.Count)
|
||||||
|
{
|
||||||
|
buffer Check = Source;
|
||||||
|
Check.Data += *At;
|
||||||
|
Check.Count = KeywordRemaining.Count;
|
||||||
|
if(AreEqual(Check, KeywordRemaining))
|
||||||
|
{
|
||||||
|
Result->Type = Type;
|
||||||
|
Result->Value.Count += KeywordRemaining.Count;
|
||||||
|
*At += KeywordRemaining.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_token GetJSONToken(json_parser *Parser)
|
||||||
|
{
|
||||||
|
json_token Result = {};
|
||||||
|
|
||||||
|
buffer Source = Parser->Source;
|
||||||
|
u64 At = Parser->At;
|
||||||
|
|
||||||
|
while(IsJSONWhitespace(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
Result.Type = Token_error;
|
||||||
|
Result.Value.Count = 1;
|
||||||
|
Result.Value.Data = Source.Data + At;
|
||||||
|
u8 Val = Source.Data[At++];
|
||||||
|
switch(Val)
|
||||||
|
{
|
||||||
|
case '{': {Result.Type = Token_open_brace;} break;
|
||||||
|
case '[': {Result.Type = Token_open_bracket;} break;
|
||||||
|
case '}': {Result.Type = Token_close_brace;} break;
|
||||||
|
case ']': {Result.Type = Token_close_bracket;} break;
|
||||||
|
case ',': {Result.Type = Token_comma;} break;
|
||||||
|
case ':': {Result.Type = Token_colon;} break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("alse"), Token_false, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("ull"), Token_null, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("rue"), Token_true, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
{
|
||||||
|
Result.Type = Token_string_literal;
|
||||||
|
|
||||||
|
u64 StringStart = At;
|
||||||
|
|
||||||
|
while(IsInBounds(Source, At) && (Source.Data[At] != '"'))
|
||||||
|
{
|
||||||
|
if(IsInBounds(Source, (At + 1)) &&
|
||||||
|
(Source.Data[At] == '\\') &&
|
||||||
|
(Source.Data[At + 1] == '"'))
|
||||||
|
{
|
||||||
|
// NOTE(casey): Skip escaped quotation marks
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Value.Data = Source.Data + StringStart;
|
||||||
|
Result.Value.Count = At - StringStart;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
{
|
||||||
|
u64 Start = At - 1;
|
||||||
|
Result.Type = Token_number;
|
||||||
|
|
||||||
|
// NOTE(casey): Move past a leading negative sign if one exists
|
||||||
|
if((Val == '-') && IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
Val = Source.Data[At++];
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If the leading digit wasn't 0, parse any digits before the decimal point
|
||||||
|
if(Val != '0')
|
||||||
|
{
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If there is a decimal point, parse any digits after the decimal point
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If it's in scientific notation, parse any digits after the "e"
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == '+') || (Source.Data[At] == '-')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Value.Count = At - Start;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser->At = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels);
|
||||||
|
static json_element *ParseJSONElement(json_parser *Parser, buffer Label, json_token Value)
|
||||||
|
{
|
||||||
|
b32 Valid = true;
|
||||||
|
|
||||||
|
json_element *SubElement = 0;
|
||||||
|
if(Value.Type == Token_open_bracket)
|
||||||
|
{
|
||||||
|
SubElement = ParseJSONList(Parser, Token_close_bracket, false);
|
||||||
|
}
|
||||||
|
else if(Value.Type == Token_open_brace)
|
||||||
|
{
|
||||||
|
SubElement = ParseJSONList(Parser, Token_close_brace, true);
|
||||||
|
}
|
||||||
|
else if((Value.Type == Token_string_literal) ||
|
||||||
|
(Value.Type == Token_true) ||
|
||||||
|
(Value.Type == Token_false) ||
|
||||||
|
(Value.Type == Token_null) ||
|
||||||
|
(Value.Type == Token_number))
|
||||||
|
{
|
||||||
|
// NOTE(casey): Nothing to do here, since there is no additional data
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_element *Result = 0;
|
||||||
|
|
||||||
|
if(Valid)
|
||||||
|
{
|
||||||
|
Result = (json_element *)malloc(sizeof(json_element));
|
||||||
|
Result->Label = Label;
|
||||||
|
Result->Value = Value.Value;
|
||||||
|
Result->FirstSubElement = SubElement;
|
||||||
|
Result->NextSibling = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels)
|
||||||
|
{
|
||||||
|
json_element *FirstElement = {};
|
||||||
|
json_element *LastElement = {};
|
||||||
|
|
||||||
|
while(IsParsing(Parser))
|
||||||
|
{
|
||||||
|
buffer Label = {};
|
||||||
|
json_token Value = GetJSONToken(Parser);
|
||||||
|
if(HasLabels)
|
||||||
|
{
|
||||||
|
if(Value.Type == Token_string_literal)
|
||||||
|
{
|
||||||
|
Label = Value.Value;
|
||||||
|
|
||||||
|
json_token Colon = GetJSONToken(Parser);
|
||||||
|
if(Colon.Type == Token_colon)
|
||||||
|
{
|
||||||
|
Value = GetJSONToken(Parser);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Parser, Colon, "Expected colon after field name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(Value.Type != EndType)
|
||||||
|
{
|
||||||
|
Error(Parser, Value, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_element *Element = ParseJSONElement(Parser, Label, Value);
|
||||||
|
if(Element)
|
||||||
|
{
|
||||||
|
LastElement = (LastElement ? LastElement->NextSibling : FirstElement) = Element;
|
||||||
|
}
|
||||||
|
else if(Value.Type == EndType)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Parser, Value, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
json_token Comma = GetJSONToken(Parser);
|
||||||
|
if(Comma.Type == EndType)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if(Comma.Type != Token_comma)
|
||||||
|
{
|
||||||
|
Error(Parser, Comma, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FirstElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSON(buffer InputJSON)
|
||||||
|
{
|
||||||
|
json_parser Parser = {};
|
||||||
|
Parser.Source = InputJSON;
|
||||||
|
|
||||||
|
json_element *Result = ParseJSONElement(&Parser, {}, GetJSONToken(&Parser));
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FreeJSON(json_element *Element)
|
||||||
|
{
|
||||||
|
while(Element)
|
||||||
|
{
|
||||||
|
json_element *FreeElement = Element;
|
||||||
|
Element = Element->NextSibling;
|
||||||
|
|
||||||
|
FreeJSON(FreeElement->FirstSubElement);
|
||||||
|
free(FreeElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *LookupElement(json_element *Object, buffer ElementName)
|
||||||
|
{
|
||||||
|
json_element *Result = 0;
|
||||||
|
|
||||||
|
if(Object)
|
||||||
|
{
|
||||||
|
for(json_element *Search = Object->FirstSubElement; Search; Search = Search->NextSibling)
|
||||||
|
{
|
||||||
|
if(AreEqual(Search->Label, ElementName))
|
||||||
|
{
|
||||||
|
Result = Search;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertJSONSign(buffer Source, u64 *AtResult)
|
||||||
|
{
|
||||||
|
u64 At = *AtResult;
|
||||||
|
|
||||||
|
f64 Result = 1.0;
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '-'))
|
||||||
|
{
|
||||||
|
Result = -1.0;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
*AtResult = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertJSONNumber(buffer Source, u64 *AtResult)
|
||||||
|
{
|
||||||
|
u64 At = *AtResult;
|
||||||
|
|
||||||
|
f64 Result = 0.0;
|
||||||
|
while(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Char = Source.Data[At] - (u8)'0';
|
||||||
|
if(Char < 10)
|
||||||
|
{
|
||||||
|
Result = 10.0*Result + (f64)Char;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*AtResult = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertElementToF64(json_element *Object, buffer ElementName)
|
||||||
|
{
|
||||||
|
f64 Result = 0.0;
|
||||||
|
|
||||||
|
json_element *Element = LookupElement(Object, ElementName);
|
||||||
|
if(Element)
|
||||||
|
{
|
||||||
|
buffer Source = Element->Value;
|
||||||
|
u64 At = 0;
|
||||||
|
|
||||||
|
f64 Sign = ConvertJSONSign(Source, &At);
|
||||||
|
f64 Number = ConvertJSONNumber(Source, &At);
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
f64 C = 1.0 / 10.0;
|
||||||
|
while(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Char = Source.Data[At] - (u8)'0';
|
||||||
|
if(Char < 10)
|
||||||
|
{
|
||||||
|
Number = Number + C*(f64)Char;
|
||||||
|
C *= 1.0 / 10.0;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '+'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 ExponentSign = ConvertJSONSign(Source, &At);
|
||||||
|
f64 Exponent = ExponentSign*ConvertJSONNumber(Source, &At);
|
||||||
|
Number *= pow(10.0, Exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result = Sign*Number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ParseHaversinePairs(buffer InputJSON, u64 MaxPairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
u64 PairCount = 0;
|
||||||
|
|
||||||
|
json_element *JSON = ParseJSON(InputJSON);
|
||||||
|
|
||||||
|
json_element *PairsArray = LookupElement(JSON, CONSTANT_STRING("pairs"));
|
||||||
|
if(PairsArray)
|
||||||
|
{
|
||||||
|
TimeBlock("Lookup and Convert");
|
||||||
|
for(json_element *Element = PairsArray->FirstSubElement;
|
||||||
|
Element && (PairCount < MaxPairCount);
|
||||||
|
Element = Element->NextSibling)
|
||||||
|
{
|
||||||
|
haversine_pair *Pair = Pairs + PairCount++;
|
||||||
|
|
||||||
|
Pair->X0 = ConvertElementToF64(Element, CONSTANT_STRING("x0"));
|
||||||
|
Pair->Y0 = ConvertElementToF64(Element, CONSTANT_STRING("y0"));
|
||||||
|
Pair->X1 = ConvertElementToF64(Element, CONSTANT_STRING("x1"));
|
||||||
|
Pair->Y1 = ConvertElementToF64(Element, CONSTANT_STRING("y1"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeJSON(JSON);
|
||||||
|
|
||||||
|
return PairCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 80
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "listing_0076_simple_profiler.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0079_timedblock_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
BeginProfile();
|
||||||
|
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Result == 0)
|
||||||
|
{
|
||||||
|
EndAndPrintProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(__COUNTER__ < ArrayCount(profiler::Anchors), "Number of profile points exceeds size of profiler::Anchors array");
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 81
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#include "listing_0074_platform_metrics.cpp"
|
||||||
|
|
||||||
|
struct profile_anchor
|
||||||
|
{
|
||||||
|
u64 TSCElapsed;
|
||||||
|
u64 TSCElapsedChildren;
|
||||||
|
u64 HitCount;
|
||||||
|
char const *Label;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct profiler
|
||||||
|
{
|
||||||
|
profile_anchor Anchors[4096];
|
||||||
|
|
||||||
|
u64 StartTSC;
|
||||||
|
u64 EndTSC;
|
||||||
|
};
|
||||||
|
static profiler GlobalProfiler;
|
||||||
|
static u32 GlobalProfilerParent;
|
||||||
|
|
||||||
|
struct profile_block
|
||||||
|
{
|
||||||
|
profile_block(char const *Label_, u32 AnchorIndex_)
|
||||||
|
{
|
||||||
|
ParentIndex = GlobalProfilerParent;
|
||||||
|
|
||||||
|
AnchorIndex = AnchorIndex_;
|
||||||
|
Label = Label_;
|
||||||
|
|
||||||
|
GlobalProfilerParent = AnchorIndex;
|
||||||
|
StartTSC = ReadCPUTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
~profile_block(void)
|
||||||
|
{
|
||||||
|
u64 Elapsed = ReadCPUTimer() - StartTSC;
|
||||||
|
GlobalProfilerParent = ParentIndex;
|
||||||
|
|
||||||
|
profile_anchor *Parent = GlobalProfiler.Anchors + ParentIndex;
|
||||||
|
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
|
||||||
|
|
||||||
|
Parent->TSCElapsedChildren += Elapsed;
|
||||||
|
Anchor->TSCElapsed += Elapsed;
|
||||||
|
++Anchor->HitCount;
|
||||||
|
|
||||||
|
/* NOTE(casey): This write happens every time solely because there is no
|
||||||
|
straightforward way in C++ to have the same ease-of-use. In a better programming
|
||||||
|
language, it would be simple to have the anchor points gathered and labeled at compile
|
||||||
|
time, and this repetative write would be eliminated. */
|
||||||
|
Anchor->Label = Label;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *Label;
|
||||||
|
u64 StartTSC;
|
||||||
|
u32 ParentIndex;
|
||||||
|
u32 AnchorIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NameConcat2(A, B) A##B
|
||||||
|
#define NameConcat(A, B) NameConcat2(A, B)
|
||||||
|
#define TimeBlock(Name) profile_block NameConcat(Block, __LINE__)(Name, __COUNTER__ + 1);
|
||||||
|
#define TimeFunction TimeBlock(__func__)
|
||||||
|
|
||||||
|
static void PrintTimeElapsed(u64 TotalTSCElapsed, profile_anchor *Anchor)
|
||||||
|
{
|
||||||
|
u64 Elapsed = Anchor->TSCElapsed - Anchor->TSCElapsedChildren;
|
||||||
|
f64 Percent = 100.0 * ((f64)Elapsed / (f64)TotalTSCElapsed);
|
||||||
|
printf(" %s[%llu]: %llu (%.2f%%", Anchor->Label, Anchor->HitCount, Elapsed, Percent);
|
||||||
|
if(Anchor->TSCElapsedChildren)
|
||||||
|
{
|
||||||
|
f64 PercentWithChildren = 100.0 * ((f64)Anchor->TSCElapsed / (f64)TotalTSCElapsed);
|
||||||
|
printf(", %.2f%% w/children", PercentWithChildren);
|
||||||
|
}
|
||||||
|
printf(")\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BeginProfile(void)
|
||||||
|
{
|
||||||
|
GlobalProfiler.StartTSC = ReadCPUTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EndAndPrintProfile()
|
||||||
|
{
|
||||||
|
GlobalProfiler.EndTSC = ReadCPUTimer();
|
||||||
|
u64 CPUFreq = EstimateCPUTimerFreq();
|
||||||
|
|
||||||
|
u64 TotalCPUElapsed = GlobalProfiler.EndTSC - GlobalProfiler.StartTSC;
|
||||||
|
|
||||||
|
if(CPUFreq)
|
||||||
|
{
|
||||||
|
printf("\nTotal time: %0.4fms (CPU freq %llu)\n", 1000.0 * (f64)TotalCPUElapsed / (f64)CPUFreq, CPUFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfiler.Anchors); ++AnchorIndex)
|
||||||
|
{
|
||||||
|
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
|
||||||
|
if(Anchor->TSCElapsed)
|
||||||
|
{
|
||||||
|
PrintTimeElapsed(TotalCPUElapsed, Anchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 82
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "listing_0081_nesting_profiler.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0079_timedblock_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
BeginProfile();
|
||||||
|
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Result == 0)
|
||||||
|
{
|
||||||
|
EndAndPrintProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(__COUNTER__ < ArrayCount(profiler::Anchors), "Number of profile points exceeds size of profiler::Anchors array");
|
||||||
@@ -0,0 +1,512 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 83
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
enum json_token_type
|
||||||
|
{
|
||||||
|
Token_end_of_stream,
|
||||||
|
Token_error,
|
||||||
|
|
||||||
|
Token_open_brace,
|
||||||
|
Token_open_bracket,
|
||||||
|
Token_close_brace,
|
||||||
|
Token_close_bracket,
|
||||||
|
Token_comma,
|
||||||
|
Token_colon,
|
||||||
|
Token_string_literal,
|
||||||
|
Token_number,
|
||||||
|
Token_true,
|
||||||
|
Token_false,
|
||||||
|
Token_null,
|
||||||
|
|
||||||
|
Token_count,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_token
|
||||||
|
{
|
||||||
|
json_token_type Type;
|
||||||
|
buffer Value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_element
|
||||||
|
{
|
||||||
|
buffer Label;
|
||||||
|
buffer Value;
|
||||||
|
json_element *FirstSubElement;
|
||||||
|
|
||||||
|
json_element *NextSibling;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_parser
|
||||||
|
{
|
||||||
|
buffer Source;
|
||||||
|
u64 At;
|
||||||
|
b32 HadError;
|
||||||
|
};
|
||||||
|
|
||||||
|
static b32 IsJSONDigit(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
b32 Result = false;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Val = Source.Data[At];
|
||||||
|
Result = ((Val >= '0') && (Val <= '9'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsJSONWhitespace(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
b32 Result = false;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Val = Source.Data[At];
|
||||||
|
Result = ((Val == ' ') || (Val == '\t') || (Val == '\n') || (Val == '\r'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsParsing(json_parser *Parser)
|
||||||
|
{
|
||||||
|
b32 Result = !Parser->HadError && IsInBounds(Parser->Source, Parser->At);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Error(json_parser *Parser, json_token Token, char const *Message)
|
||||||
|
{
|
||||||
|
Parser->HadError = true;
|
||||||
|
fprintf(stderr, "ERROR: \"%.*s\" - %s\n", (u32)Token.Value.Count, (char *)Token.Value.Data, Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ParseKeyword(buffer Source, u64 *At, buffer KeywordRemaining, json_token_type Type, json_token *Result)
|
||||||
|
{
|
||||||
|
if((Source.Count - *At) >= KeywordRemaining.Count)
|
||||||
|
{
|
||||||
|
buffer Check = Source;
|
||||||
|
Check.Data += *At;
|
||||||
|
Check.Count = KeywordRemaining.Count;
|
||||||
|
if(AreEqual(Check, KeywordRemaining))
|
||||||
|
{
|
||||||
|
Result->Type = Type;
|
||||||
|
Result->Value.Count += KeywordRemaining.Count;
|
||||||
|
*At += KeywordRemaining.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_token GetJSONToken(json_parser *Parser)
|
||||||
|
{
|
||||||
|
json_token Result = {};
|
||||||
|
|
||||||
|
buffer Source = Parser->Source;
|
||||||
|
u64 At = Parser->At;
|
||||||
|
|
||||||
|
while(IsJSONWhitespace(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
Result.Type = Token_error;
|
||||||
|
Result.Value.Count = 1;
|
||||||
|
Result.Value.Data = Source.Data + At;
|
||||||
|
u8 Val = Source.Data[At++];
|
||||||
|
switch(Val)
|
||||||
|
{
|
||||||
|
case '{': {Result.Type = Token_open_brace;} break;
|
||||||
|
case '[': {Result.Type = Token_open_bracket;} break;
|
||||||
|
case '}': {Result.Type = Token_close_brace;} break;
|
||||||
|
case ']': {Result.Type = Token_close_bracket;} break;
|
||||||
|
case ',': {Result.Type = Token_comma;} break;
|
||||||
|
case ':': {Result.Type = Token_colon;} break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("alse"), Token_false, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("ull"), Token_null, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("rue"), Token_true, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
{
|
||||||
|
Result.Type = Token_string_literal;
|
||||||
|
|
||||||
|
u64 StringStart = At;
|
||||||
|
|
||||||
|
while(IsInBounds(Source, At) && (Source.Data[At] != '"'))
|
||||||
|
{
|
||||||
|
if(IsInBounds(Source, (At + 1)) &&
|
||||||
|
(Source.Data[At] == '\\') &&
|
||||||
|
(Source.Data[At + 1] == '"'))
|
||||||
|
{
|
||||||
|
// NOTE(casey): Skip escaped quotation marks
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Value.Data = Source.Data + StringStart;
|
||||||
|
Result.Value.Count = At - StringStart;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
{
|
||||||
|
u64 Start = At - 1;
|
||||||
|
Result.Type = Token_number;
|
||||||
|
|
||||||
|
// NOTE(casey): Move past a leading negative sign if one exists
|
||||||
|
if((Val == '-') && IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
Val = Source.Data[At++];
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If the leading digit wasn't 0, parse any digits before the decimal point
|
||||||
|
if(Val != '0')
|
||||||
|
{
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If there is a decimal point, parse any digits after the decimal point
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If it's in scientific notation, parse any digits after the "e"
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == '+') || (Source.Data[At] == '-')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Value.Count = At - Start;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser->At = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels);
|
||||||
|
static json_element *ParseJSONElement(json_parser *Parser, buffer Label, json_token Value)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
b32 Valid = true;
|
||||||
|
|
||||||
|
json_element *SubElement = 0;
|
||||||
|
if(Value.Type == Token_open_bracket)
|
||||||
|
{
|
||||||
|
SubElement = ParseJSONList(Parser, Token_close_bracket, false);
|
||||||
|
}
|
||||||
|
else if(Value.Type == Token_open_brace)
|
||||||
|
{
|
||||||
|
SubElement = ParseJSONList(Parser, Token_close_brace, true);
|
||||||
|
}
|
||||||
|
else if((Value.Type == Token_string_literal) ||
|
||||||
|
(Value.Type == Token_true) ||
|
||||||
|
(Value.Type == Token_false) ||
|
||||||
|
(Value.Type == Token_null) ||
|
||||||
|
(Value.Type == Token_number))
|
||||||
|
{
|
||||||
|
// NOTE(casey): Nothing to do here, since there is no additional data
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_element *Result = 0;
|
||||||
|
|
||||||
|
if(Valid)
|
||||||
|
{
|
||||||
|
Result = (json_element *)malloc(sizeof(json_element));
|
||||||
|
Result->Label = Label;
|
||||||
|
Result->Value = Value.Value;
|
||||||
|
Result->FirstSubElement = SubElement;
|
||||||
|
Result->NextSibling = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels)
|
||||||
|
{
|
||||||
|
json_element *FirstElement = {};
|
||||||
|
json_element *LastElement = {};
|
||||||
|
|
||||||
|
while(IsParsing(Parser))
|
||||||
|
{
|
||||||
|
buffer Label = {};
|
||||||
|
json_token Value = GetJSONToken(Parser);
|
||||||
|
if(HasLabels)
|
||||||
|
{
|
||||||
|
if(Value.Type == Token_string_literal)
|
||||||
|
{
|
||||||
|
Label = Value.Value;
|
||||||
|
|
||||||
|
json_token Colon = GetJSONToken(Parser);
|
||||||
|
if(Colon.Type == Token_colon)
|
||||||
|
{
|
||||||
|
Value = GetJSONToken(Parser);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Parser, Colon, "Expected colon after field name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(Value.Type != EndType)
|
||||||
|
{
|
||||||
|
Error(Parser, Value, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_element *Element = ParseJSONElement(Parser, Label, Value);
|
||||||
|
if(Element)
|
||||||
|
{
|
||||||
|
LastElement = (LastElement ? LastElement->NextSibling : FirstElement) = Element;
|
||||||
|
}
|
||||||
|
else if(Value.Type == EndType)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Parser, Value, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
json_token Comma = GetJSONToken(Parser);
|
||||||
|
if(Comma.Type == EndType)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if(Comma.Type != Token_comma)
|
||||||
|
{
|
||||||
|
Error(Parser, Comma, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FirstElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSON(buffer InputJSON)
|
||||||
|
{
|
||||||
|
json_parser Parser = {};
|
||||||
|
Parser.Source = InputJSON;
|
||||||
|
|
||||||
|
json_element *Result = ParseJSONElement(&Parser, {}, GetJSONToken(&Parser));
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FreeJSON(json_element *Element)
|
||||||
|
{
|
||||||
|
while(Element)
|
||||||
|
{
|
||||||
|
json_element *FreeElement = Element;
|
||||||
|
Element = Element->NextSibling;
|
||||||
|
|
||||||
|
FreeJSON(FreeElement->FirstSubElement);
|
||||||
|
free(FreeElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *LookupElement(json_element *Object, buffer ElementName)
|
||||||
|
{
|
||||||
|
json_element *Result = 0;
|
||||||
|
|
||||||
|
if(Object)
|
||||||
|
{
|
||||||
|
for(json_element *Search = Object->FirstSubElement; Search; Search = Search->NextSibling)
|
||||||
|
{
|
||||||
|
if(AreEqual(Search->Label, ElementName))
|
||||||
|
{
|
||||||
|
Result = Search;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertJSONSign(buffer Source, u64 *AtResult)
|
||||||
|
{
|
||||||
|
u64 At = *AtResult;
|
||||||
|
|
||||||
|
f64 Result = 1.0;
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '-'))
|
||||||
|
{
|
||||||
|
Result = -1.0;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
*AtResult = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertJSONNumber(buffer Source, u64 *AtResult)
|
||||||
|
{
|
||||||
|
u64 At = *AtResult;
|
||||||
|
|
||||||
|
f64 Result = 0.0;
|
||||||
|
while(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Char = Source.Data[At] - (u8)'0';
|
||||||
|
if(Char < 10)
|
||||||
|
{
|
||||||
|
Result = 10.0*Result + (f64)Char;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*AtResult = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertElementToF64(json_element *Object, buffer ElementName)
|
||||||
|
{
|
||||||
|
f64 Result = 0.0;
|
||||||
|
|
||||||
|
json_element *Element = LookupElement(Object, ElementName);
|
||||||
|
if(Element)
|
||||||
|
{
|
||||||
|
buffer Source = Element->Value;
|
||||||
|
u64 At = 0;
|
||||||
|
|
||||||
|
f64 Sign = ConvertJSONSign(Source, &At);
|
||||||
|
f64 Number = ConvertJSONNumber(Source, &At);
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
f64 C = 1.0 / 10.0;
|
||||||
|
while(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Char = Source.Data[At] - (u8)'0';
|
||||||
|
if(Char < 10)
|
||||||
|
{
|
||||||
|
Number = Number + C*(f64)Char;
|
||||||
|
C *= 1.0 / 10.0;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '+'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 ExponentSign = ConvertJSONSign(Source, &At);
|
||||||
|
f64 Exponent = ExponentSign*ConvertJSONNumber(Source, &At);
|
||||||
|
Number *= pow(10.0, Exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result = Sign*Number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ParseHaversinePairs(buffer InputJSON, u64 MaxPairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
u64 PairCount = 0;
|
||||||
|
|
||||||
|
json_element *JSON = ParseJSON(InputJSON);
|
||||||
|
|
||||||
|
json_element *PairsArray = LookupElement(JSON, CONSTANT_STRING("pairs"));
|
||||||
|
if(PairsArray)
|
||||||
|
{
|
||||||
|
TimeBlock("Lookup and Convert");
|
||||||
|
for(json_element *Element = PairsArray->FirstSubElement;
|
||||||
|
Element && (PairCount < MaxPairCount);
|
||||||
|
Element = Element->NextSibling)
|
||||||
|
{
|
||||||
|
haversine_pair *Pair = Pairs + PairCount++;
|
||||||
|
|
||||||
|
Pair->X0 = ConvertElementToF64(Element, CONSTANT_STRING("x0"));
|
||||||
|
Pair->Y0 = ConvertElementToF64(Element, CONSTANT_STRING("y0"));
|
||||||
|
Pair->X1 = ConvertElementToF64(Element, CONSTANT_STRING("x1"));
|
||||||
|
Pair->Y1 = ConvertElementToF64(Element, CONSTANT_STRING("y1"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeJSON(JSON);
|
||||||
|
|
||||||
|
return PairCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 84
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "listing_0081_nesting_profiler.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0083_recursive_timed_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
BeginProfile();
|
||||||
|
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Result == 0)
|
||||||
|
{
|
||||||
|
EndAndPrintProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(__COUNTER__ < ArrayCount(profiler::Anchors), "Number of profile points exceeds size of profiler::Anchors array");
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 85
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#include "listing_0074_platform_metrics.cpp"
|
||||||
|
|
||||||
|
struct profile_anchor
|
||||||
|
{
|
||||||
|
u64 TSCElapsed;
|
||||||
|
u64 TSCElapsedChildren;
|
||||||
|
u64 TSCElapsedAtRoot;
|
||||||
|
u64 HitCount;
|
||||||
|
char const *Label;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct profiler
|
||||||
|
{
|
||||||
|
profile_anchor Anchors[4096];
|
||||||
|
|
||||||
|
u64 StartTSC;
|
||||||
|
u64 EndTSC;
|
||||||
|
};
|
||||||
|
static profiler GlobalProfiler;
|
||||||
|
static u32 GlobalProfilerParent;
|
||||||
|
|
||||||
|
struct profile_block
|
||||||
|
{
|
||||||
|
profile_block(char const *Label_, u32 AnchorIndex_)
|
||||||
|
{
|
||||||
|
ParentIndex = GlobalProfilerParent;
|
||||||
|
|
||||||
|
AnchorIndex = AnchorIndex_;
|
||||||
|
Label = Label_;
|
||||||
|
|
||||||
|
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
|
||||||
|
OldTSCElapsedAtRoot = Anchor->TSCElapsedAtRoot;
|
||||||
|
|
||||||
|
GlobalProfilerParent = AnchorIndex;
|
||||||
|
StartTSC = ReadCPUTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
~profile_block(void)
|
||||||
|
{
|
||||||
|
u64 Elapsed = ReadCPUTimer() - StartTSC;
|
||||||
|
GlobalProfilerParent = ParentIndex;
|
||||||
|
|
||||||
|
profile_anchor *Parent = GlobalProfiler.Anchors + ParentIndex;
|
||||||
|
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
|
||||||
|
|
||||||
|
Parent->TSCElapsedChildren += Elapsed;
|
||||||
|
Anchor->TSCElapsedAtRoot = OldTSCElapsedAtRoot + Elapsed;
|
||||||
|
Anchor->TSCElapsed += Elapsed;
|
||||||
|
++Anchor->HitCount;
|
||||||
|
|
||||||
|
/* NOTE(casey): This write happens every time solely because there is no
|
||||||
|
straightforward way in C++ to have the same ease-of-use. In a better programming
|
||||||
|
language, it would be simple to have the anchor points gathered and labeled at compile
|
||||||
|
time, and this repetative write would be eliminated. */
|
||||||
|
Anchor->Label = Label;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *Label;
|
||||||
|
u64 OldTSCElapsedAtRoot;
|
||||||
|
u64 StartTSC;
|
||||||
|
u32 ParentIndex;
|
||||||
|
u32 AnchorIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NameConcat2(A, B) A##B
|
||||||
|
#define NameConcat(A, B) NameConcat2(A, B)
|
||||||
|
#define TimeBlock(Name) profile_block NameConcat(Block, __LINE__)(Name, __COUNTER__ + 1);
|
||||||
|
#define TimeFunction TimeBlock(__func__)
|
||||||
|
|
||||||
|
static void PrintTimeElapsed(u64 TotalTSCElapsed, profile_anchor *Anchor)
|
||||||
|
{
|
||||||
|
u64 TSCElapsedSelf = Anchor->TSCElapsed - Anchor->TSCElapsedChildren;
|
||||||
|
f64 Percent = 100.0 * ((f64)TSCElapsedSelf / (f64)TotalTSCElapsed);
|
||||||
|
printf(" %s[%llu]: %llu (%.2f%%", Anchor->Label, Anchor->HitCount, TSCElapsedSelf, Percent);
|
||||||
|
if(Anchor->TSCElapsedAtRoot != TSCElapsedSelf)
|
||||||
|
{
|
||||||
|
f64 PercentWithChildren = 100.0 * ((f64)Anchor->TSCElapsedAtRoot / (f64)TotalTSCElapsed);
|
||||||
|
printf(", %.2f%% w/children", PercentWithChildren);
|
||||||
|
}
|
||||||
|
printf(")\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BeginProfile(void)
|
||||||
|
{
|
||||||
|
GlobalProfiler.StartTSC = ReadCPUTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EndAndPrintProfile()
|
||||||
|
{
|
||||||
|
GlobalProfiler.EndTSC = ReadCPUTimer();
|
||||||
|
u64 CPUFreq = EstimateCPUTimerFreq();
|
||||||
|
|
||||||
|
u64 TotalCPUElapsed = GlobalProfiler.EndTSC - GlobalProfiler.StartTSC;
|
||||||
|
|
||||||
|
if(CPUFreq)
|
||||||
|
{
|
||||||
|
printf("\nTotal time: %0.4fms (CPU freq %llu)\n", 1000.0 * (f64)TotalCPUElapsed / (f64)CPUFreq, CPUFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfiler.Anchors); ++AnchorIndex)
|
||||||
|
{
|
||||||
|
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
|
||||||
|
if(Anchor->TSCElapsed)
|
||||||
|
{
|
||||||
|
PrintTimeElapsed(TotalCPUElapsed, Anchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 86
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "listing_0085_recursive_profiler.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0083_recursive_timed_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
BeginProfile();
|
||||||
|
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Result == 0)
|
||||||
|
{
|
||||||
|
EndAndPrintProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(__COUNTER__ < ArrayCount(profiler::Anchors), "Number of profile points exceeds size of profiler::Anchors array");
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 87
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#include "listing_0074_platform_metrics.cpp"
|
||||||
|
|
||||||
|
struct profile_anchor
|
||||||
|
{
|
||||||
|
u64 TSCElapsedExclusive; // NOTE(casey): Does NOT include children
|
||||||
|
u64 TSCElapsedInclusive; // NOTE(casey): DOES include children
|
||||||
|
u64 HitCount;
|
||||||
|
char const *Label;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct profiler
|
||||||
|
{
|
||||||
|
profile_anchor Anchors[4096];
|
||||||
|
|
||||||
|
u64 StartTSC;
|
||||||
|
u64 EndTSC;
|
||||||
|
};
|
||||||
|
static profiler GlobalProfiler;
|
||||||
|
static u32 GlobalProfilerParent;
|
||||||
|
|
||||||
|
struct profile_block
|
||||||
|
{
|
||||||
|
profile_block(char const *Label_, u32 AnchorIndex_)
|
||||||
|
{
|
||||||
|
ParentIndex = GlobalProfilerParent;
|
||||||
|
|
||||||
|
AnchorIndex = AnchorIndex_;
|
||||||
|
Label = Label_;
|
||||||
|
|
||||||
|
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
|
||||||
|
OldTSCElapsedInclusive = Anchor->TSCElapsedInclusive;
|
||||||
|
|
||||||
|
GlobalProfilerParent = AnchorIndex;
|
||||||
|
StartTSC = ReadCPUTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
~profile_block(void)
|
||||||
|
{
|
||||||
|
u64 Elapsed = ReadCPUTimer() - StartTSC;
|
||||||
|
GlobalProfilerParent = ParentIndex;
|
||||||
|
|
||||||
|
profile_anchor *Parent = GlobalProfiler.Anchors + ParentIndex;
|
||||||
|
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
|
||||||
|
|
||||||
|
Parent->TSCElapsedExclusive -= Elapsed;
|
||||||
|
Anchor->TSCElapsedExclusive += Elapsed;
|
||||||
|
Anchor->TSCElapsedInclusive = OldTSCElapsedInclusive + Elapsed;
|
||||||
|
++Anchor->HitCount;
|
||||||
|
|
||||||
|
/* NOTE(casey): This write happens every time solely because there is no
|
||||||
|
straightforward way in C++ to have the same ease-of-use. In a better programming
|
||||||
|
language, it would be simple to have the anchor points gathered and labeled at compile
|
||||||
|
time, and this repetative write would be eliminated. */
|
||||||
|
Anchor->Label = Label;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *Label;
|
||||||
|
u64 OldTSCElapsedInclusive;
|
||||||
|
u64 StartTSC;
|
||||||
|
u32 ParentIndex;
|
||||||
|
u32 AnchorIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NameConcat2(A, B) A##B
|
||||||
|
#define NameConcat(A, B) NameConcat2(A, B)
|
||||||
|
#define TimeBlock(Name) profile_block NameConcat(Block, __LINE__)(Name, __COUNTER__ + 1);
|
||||||
|
#define TimeFunction TimeBlock(__func__)
|
||||||
|
|
||||||
|
static void PrintTimeElapsed(u64 TotalTSCElapsed, profile_anchor *Anchor)
|
||||||
|
{
|
||||||
|
f64 Percent = 100.0 * ((f64)Anchor->TSCElapsedExclusive / (f64)TotalTSCElapsed);
|
||||||
|
printf(" %s[%llu]: %llu (%.2f%%", Anchor->Label, Anchor->HitCount, Anchor->TSCElapsedExclusive, Percent);
|
||||||
|
if(Anchor->TSCElapsedInclusive != Anchor->TSCElapsedExclusive)
|
||||||
|
{
|
||||||
|
f64 PercentWithChildren = 100.0 * ((f64)Anchor->TSCElapsedInclusive / (f64)TotalTSCElapsed);
|
||||||
|
printf(", %.2f%% w/children", PercentWithChildren);
|
||||||
|
}
|
||||||
|
printf(")\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BeginProfile(void)
|
||||||
|
{
|
||||||
|
GlobalProfiler.StartTSC = ReadCPUTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EndAndPrintProfile()
|
||||||
|
{
|
||||||
|
GlobalProfiler.EndTSC = ReadCPUTimer();
|
||||||
|
u64 CPUFreq = EstimateCPUTimerFreq();
|
||||||
|
|
||||||
|
u64 TotalCPUElapsed = GlobalProfiler.EndTSC - GlobalProfiler.StartTSC;
|
||||||
|
|
||||||
|
if(CPUFreq)
|
||||||
|
{
|
||||||
|
printf("\nTotal time: %0.4fms (CPU freq %llu)\n", 1000.0 * (f64)TotalCPUElapsed / (f64)CPUFreq, CPUFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfiler.Anchors); ++AnchorIndex)
|
||||||
|
{
|
||||||
|
profile_anchor *Anchor = GlobalProfiler.Anchors + AnchorIndex;
|
||||||
|
if(Anchor->TSCElapsedInclusive)
|
||||||
|
{
|
||||||
|
PrintTimeElapsed(TotalCPUElapsed, Anchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 88
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "listing_0087_simplified_profiler.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0083_recursive_timed_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
BeginProfile();
|
||||||
|
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Result == 0)
|
||||||
|
{
|
||||||
|
EndAndPrintProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(__COUNTER__ < ArrayCount(profiler::Anchors), "Number of profile points exceeds size of profiler::Anchors array");
|
||||||
@@ -0,0 +1,537 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 89
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
enum json_token_type
|
||||||
|
{
|
||||||
|
Token_end_of_stream,
|
||||||
|
Token_error,
|
||||||
|
|
||||||
|
Token_open_brace,
|
||||||
|
Token_open_bracket,
|
||||||
|
Token_close_brace,
|
||||||
|
Token_close_bracket,
|
||||||
|
Token_comma,
|
||||||
|
Token_colon,
|
||||||
|
Token_string_literal,
|
||||||
|
Token_number,
|
||||||
|
Token_true,
|
||||||
|
Token_false,
|
||||||
|
Token_null,
|
||||||
|
|
||||||
|
Token_count,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_token
|
||||||
|
{
|
||||||
|
json_token_type Type;
|
||||||
|
buffer Value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_element
|
||||||
|
{
|
||||||
|
buffer Label;
|
||||||
|
buffer Value;
|
||||||
|
json_element *FirstSubElement;
|
||||||
|
|
||||||
|
json_element *NextSibling;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_parser
|
||||||
|
{
|
||||||
|
buffer Source;
|
||||||
|
u64 At;
|
||||||
|
b32 HadError;
|
||||||
|
};
|
||||||
|
|
||||||
|
static b32 IsJSONDigit(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
b32 Result = false;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Val = Source.Data[At];
|
||||||
|
Result = ((Val >= '0') && (Val <= '9'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsJSONWhitespace(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
b32 Result = false;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Val = Source.Data[At];
|
||||||
|
Result = ((Val == ' ') || (Val == '\t') || (Val == '\n') || (Val == '\r'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsParsing(json_parser *Parser)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
b32 Result = !Parser->HadError && IsInBounds(Parser->Source, Parser->At);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Error(json_parser *Parser, json_token Token, char const *Message)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
Parser->HadError = true;
|
||||||
|
fprintf(stderr, "ERROR: \"%.*s\" - %s\n", (u32)Token.Value.Count, (char *)Token.Value.Data, Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ParseKeyword(buffer Source, u64 *At, buffer KeywordRemaining, json_token_type Type, json_token *Result)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
if((Source.Count - *At) >= KeywordRemaining.Count)
|
||||||
|
{
|
||||||
|
buffer Check = Source;
|
||||||
|
Check.Data += *At;
|
||||||
|
Check.Count = KeywordRemaining.Count;
|
||||||
|
if(AreEqual(Check, KeywordRemaining))
|
||||||
|
{
|
||||||
|
Result->Type = Type;
|
||||||
|
Result->Value.Count += KeywordRemaining.Count;
|
||||||
|
*At += KeywordRemaining.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_token GetJSONToken(json_parser *Parser)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
json_token Result = {};
|
||||||
|
|
||||||
|
buffer Source = Parser->Source;
|
||||||
|
u64 At = Parser->At;
|
||||||
|
|
||||||
|
while(IsJSONWhitespace(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
Result.Type = Token_error;
|
||||||
|
Result.Value.Count = 1;
|
||||||
|
Result.Value.Data = Source.Data + At;
|
||||||
|
u8 Val = Source.Data[At++];
|
||||||
|
switch(Val)
|
||||||
|
{
|
||||||
|
case '{': {Result.Type = Token_open_brace;} break;
|
||||||
|
case '[': {Result.Type = Token_open_bracket;} break;
|
||||||
|
case '}': {Result.Type = Token_close_brace;} break;
|
||||||
|
case ']': {Result.Type = Token_close_bracket;} break;
|
||||||
|
case ',': {Result.Type = Token_comma;} break;
|
||||||
|
case ':': {Result.Type = Token_colon;} break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("alse"), Token_false, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("ull"), Token_null, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("rue"), Token_true, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
{
|
||||||
|
Result.Type = Token_string_literal;
|
||||||
|
|
||||||
|
u64 StringStart = At;
|
||||||
|
|
||||||
|
while(IsInBounds(Source, At) && (Source.Data[At] != '"'))
|
||||||
|
{
|
||||||
|
if(IsInBounds(Source, (At + 1)) &&
|
||||||
|
(Source.Data[At] == '\\') &&
|
||||||
|
(Source.Data[At + 1] == '"'))
|
||||||
|
{
|
||||||
|
// NOTE(casey): Skip escaped quotation marks
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Value.Data = Source.Data + StringStart;
|
||||||
|
Result.Value.Count = At - StringStart;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
{
|
||||||
|
u64 Start = At - 1;
|
||||||
|
Result.Type = Token_number;
|
||||||
|
|
||||||
|
// NOTE(casey): Move past a leading negative sign if one exists
|
||||||
|
if((Val == '-') && IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
Val = Source.Data[At++];
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If the leading digit wasn't 0, parse any digits before the decimal point
|
||||||
|
if(Val != '0')
|
||||||
|
{
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If there is a decimal point, parse any digits after the decimal point
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If it's in scientific notation, parse any digits after the "e"
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == '+') || (Source.Data[At] == '-')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Value.Count = At - Start;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser->At = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels);
|
||||||
|
static json_element *ParseJSONElement(json_parser *Parser, buffer Label, json_token Value)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
b32 Valid = true;
|
||||||
|
|
||||||
|
json_element *SubElement = 0;
|
||||||
|
if(Value.Type == Token_open_bracket)
|
||||||
|
{
|
||||||
|
SubElement = ParseJSONList(Parser, Token_close_bracket, false);
|
||||||
|
}
|
||||||
|
else if(Value.Type == Token_open_brace)
|
||||||
|
{
|
||||||
|
SubElement = ParseJSONList(Parser, Token_close_brace, true);
|
||||||
|
}
|
||||||
|
else if((Value.Type == Token_string_literal) ||
|
||||||
|
(Value.Type == Token_true) ||
|
||||||
|
(Value.Type == Token_false) ||
|
||||||
|
(Value.Type == Token_null) ||
|
||||||
|
(Value.Type == Token_number))
|
||||||
|
{
|
||||||
|
// NOTE(casey): Nothing to do here, since there is no additional data
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_element *Result = 0;
|
||||||
|
|
||||||
|
if(Valid)
|
||||||
|
{
|
||||||
|
Result = (json_element *)malloc(sizeof(json_element));
|
||||||
|
Result->Label = Label;
|
||||||
|
Result->Value = Value.Value;
|
||||||
|
Result->FirstSubElement = SubElement;
|
||||||
|
Result->NextSibling = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
json_element *FirstElement = {};
|
||||||
|
json_element *LastElement = {};
|
||||||
|
|
||||||
|
while(IsParsing(Parser))
|
||||||
|
{
|
||||||
|
buffer Label = {};
|
||||||
|
json_token Value = GetJSONToken(Parser);
|
||||||
|
if(HasLabels)
|
||||||
|
{
|
||||||
|
if(Value.Type == Token_string_literal)
|
||||||
|
{
|
||||||
|
Label = Value.Value;
|
||||||
|
|
||||||
|
json_token Colon = GetJSONToken(Parser);
|
||||||
|
if(Colon.Type == Token_colon)
|
||||||
|
{
|
||||||
|
Value = GetJSONToken(Parser);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Parser, Colon, "Expected colon after field name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(Value.Type != EndType)
|
||||||
|
{
|
||||||
|
Error(Parser, Value, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_element *Element = ParseJSONElement(Parser, Label, Value);
|
||||||
|
if(Element)
|
||||||
|
{
|
||||||
|
LastElement = (LastElement ? LastElement->NextSibling : FirstElement) = Element;
|
||||||
|
}
|
||||||
|
else if(Value.Type == EndType)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Parser, Value, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
json_token Comma = GetJSONToken(Parser);
|
||||||
|
if(Comma.Type == EndType)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if(Comma.Type != Token_comma)
|
||||||
|
{
|
||||||
|
Error(Parser, Comma, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FirstElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSON(buffer InputJSON)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
json_parser Parser = {};
|
||||||
|
Parser.Source = InputJSON;
|
||||||
|
|
||||||
|
json_element *Result = ParseJSONElement(&Parser, {}, GetJSONToken(&Parser));
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FreeJSON(json_element *Element)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
while(Element)
|
||||||
|
{
|
||||||
|
json_element *FreeElement = Element;
|
||||||
|
Element = Element->NextSibling;
|
||||||
|
|
||||||
|
FreeJSON(FreeElement->FirstSubElement);
|
||||||
|
free(FreeElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *LookupElement(json_element *Object, buffer ElementName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
json_element *Result = 0;
|
||||||
|
|
||||||
|
if(Object)
|
||||||
|
{
|
||||||
|
for(json_element *Search = Object->FirstSubElement; Search; Search = Search->NextSibling)
|
||||||
|
{
|
||||||
|
if(AreEqual(Search->Label, ElementName))
|
||||||
|
{
|
||||||
|
Result = Search;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertJSONSign(buffer Source, u64 *AtResult)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
u64 At = *AtResult;
|
||||||
|
|
||||||
|
f64 Result = 1.0;
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '-'))
|
||||||
|
{
|
||||||
|
Result = -1.0;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
*AtResult = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertJSONNumber(buffer Source, u64 *AtResult)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
u64 At = *AtResult;
|
||||||
|
|
||||||
|
f64 Result = 0.0;
|
||||||
|
while(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Char = Source.Data[At] - (u8)'0';
|
||||||
|
if(Char < 10)
|
||||||
|
{
|
||||||
|
Result = 10.0*Result + (f64)Char;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*AtResult = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertElementToF64(json_element *Object, buffer ElementName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
f64 Result = 0.0;
|
||||||
|
|
||||||
|
json_element *Element = LookupElement(Object, ElementName);
|
||||||
|
if(Element)
|
||||||
|
{
|
||||||
|
buffer Source = Element->Value;
|
||||||
|
u64 At = 0;
|
||||||
|
|
||||||
|
f64 Sign = ConvertJSONSign(Source, &At);
|
||||||
|
f64 Number = ConvertJSONNumber(Source, &At);
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
f64 C = 1.0 / 10.0;
|
||||||
|
while(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Char = Source.Data[At] - (u8)'0';
|
||||||
|
if(Char < 10)
|
||||||
|
{
|
||||||
|
Number = Number + C*(f64)Char;
|
||||||
|
C *= 1.0 / 10.0;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '+'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 ExponentSign = ConvertJSONSign(Source, &At);
|
||||||
|
f64 Exponent = ExponentSign*ConvertJSONNumber(Source, &At);
|
||||||
|
Number *= pow(10.0, Exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result = Sign*Number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ParseHaversinePairs(buffer InputJSON, u64 MaxPairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
u64 PairCount = 0;
|
||||||
|
|
||||||
|
json_element *JSON = ParseJSON(InputJSON);
|
||||||
|
|
||||||
|
json_element *PairsArray = LookupElement(JSON, CONSTANT_STRING("pairs"));
|
||||||
|
if(PairsArray)
|
||||||
|
{
|
||||||
|
for(json_element *Element = PairsArray->FirstSubElement;
|
||||||
|
Element && (PairCount < MaxPairCount);
|
||||||
|
Element = Element->NextSibling)
|
||||||
|
{
|
||||||
|
haversine_pair *Pair = Pairs + PairCount++;
|
||||||
|
|
||||||
|
Pair->X0 = ConvertElementToF64(Element, CONSTANT_STRING("x0"));
|
||||||
|
Pair->Y0 = ConvertElementToF64(Element, CONSTANT_STRING("y0"));
|
||||||
|
Pair->X1 = ConvertElementToF64(Element, CONSTANT_STRING("x1"));
|
||||||
|
Pair->Y1 = ConvertElementToF64(Element, CONSTANT_STRING("y1"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeJSON(JSON);
|
||||||
|
|
||||||
|
return PairCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 90
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "listing_0087_simplified_profiler.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0089_allfuncs_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(File);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
BeginProfile();
|
||||||
|
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Result == 0)
|
||||||
|
{
|
||||||
|
EndAndPrintProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(__COUNTER__ < ArrayCount(profiler::Anchors), "Number of profile points exceeds size of profiler::Anchors array");
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 91
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#include "listing_0074_platform_metrics.cpp"
|
||||||
|
|
||||||
|
#ifndef PROFILER
|
||||||
|
#define PROFILER 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PROFILER
|
||||||
|
|
||||||
|
struct profile_anchor
|
||||||
|
{
|
||||||
|
u64 TSCElapsedExclusive; // NOTE(casey): Does NOT include children
|
||||||
|
u64 TSCElapsedInclusive; // NOTE(casey): DOES include children
|
||||||
|
u64 HitCount;
|
||||||
|
char const *Label;
|
||||||
|
};
|
||||||
|
static profile_anchor GlobalProfilerAnchors[4096];
|
||||||
|
static u32 GlobalProfilerParent;
|
||||||
|
|
||||||
|
struct profile_block
|
||||||
|
{
|
||||||
|
profile_block(char const *Label_, u32 AnchorIndex_)
|
||||||
|
{
|
||||||
|
ParentIndex = GlobalProfilerParent;
|
||||||
|
|
||||||
|
AnchorIndex = AnchorIndex_;
|
||||||
|
Label = Label_;
|
||||||
|
|
||||||
|
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
|
||||||
|
OldTSCElapsedInclusive = Anchor->TSCElapsedInclusive;
|
||||||
|
|
||||||
|
GlobalProfilerParent = AnchorIndex;
|
||||||
|
StartTSC = ReadCPUTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
~profile_block(void)
|
||||||
|
{
|
||||||
|
u64 Elapsed = ReadCPUTimer() - StartTSC;
|
||||||
|
GlobalProfilerParent = ParentIndex;
|
||||||
|
|
||||||
|
profile_anchor *Parent = GlobalProfilerAnchors + ParentIndex;
|
||||||
|
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
|
||||||
|
|
||||||
|
Parent->TSCElapsedExclusive -= Elapsed;
|
||||||
|
Anchor->TSCElapsedExclusive += Elapsed;
|
||||||
|
Anchor->TSCElapsedInclusive = OldTSCElapsedInclusive + Elapsed;
|
||||||
|
++Anchor->HitCount;
|
||||||
|
|
||||||
|
/* NOTE(casey): This write happens every time solely because there is no
|
||||||
|
straightforward way in C++ to have the same ease-of-use. In a better programming
|
||||||
|
language, it would be simple to have the anchor points gathered and labeled at compile
|
||||||
|
time, and this repetative write would be eliminated. */
|
||||||
|
Anchor->Label = Label;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *Label;
|
||||||
|
u64 OldTSCElapsedInclusive;
|
||||||
|
u64 StartTSC;
|
||||||
|
u32 ParentIndex;
|
||||||
|
u32 AnchorIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NameConcat2(A, B) A##B
|
||||||
|
#define NameConcat(A, B) NameConcat2(A, B)
|
||||||
|
#define TimeBlock(Name) profile_block NameConcat(Block, __LINE__)(Name, __COUNTER__ + 1);
|
||||||
|
#define ProfilerEndOfCompilationUnit static_assert(__COUNTER__ < ArrayCount(GlobalProfilerAnchors), "Number of profile points exceeds size of profiler::Anchors array")
|
||||||
|
|
||||||
|
static void PrintTimeElapsed(u64 TotalTSCElapsed, profile_anchor *Anchor)
|
||||||
|
{
|
||||||
|
f64 Percent = 100.0 * ((f64)Anchor->TSCElapsedExclusive / (f64)TotalTSCElapsed);
|
||||||
|
printf(" %s[%llu]: %llu (%.2f%%", Anchor->Label, Anchor->HitCount, Anchor->TSCElapsedExclusive, Percent);
|
||||||
|
if(Anchor->TSCElapsedInclusive != Anchor->TSCElapsedExclusive)
|
||||||
|
{
|
||||||
|
f64 PercentWithChildren = 100.0 * ((f64)Anchor->TSCElapsedInclusive / (f64)TotalTSCElapsed);
|
||||||
|
printf(", %.2f%% w/children", PercentWithChildren);
|
||||||
|
}
|
||||||
|
printf(")\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintAnchorData(u64 TotalCPUElapsed)
|
||||||
|
{
|
||||||
|
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfilerAnchors); ++AnchorIndex)
|
||||||
|
{
|
||||||
|
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
|
||||||
|
if(Anchor->TSCElapsedInclusive)
|
||||||
|
{
|
||||||
|
PrintTimeElapsed(TotalCPUElapsed, Anchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define TimeBlock(...)
|
||||||
|
#define PrintAnchorData(...)
|
||||||
|
#define ProfilerEndOfCompilationUnit
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct profiler
|
||||||
|
{
|
||||||
|
u64 StartTSC;
|
||||||
|
u64 EndTSC;
|
||||||
|
};
|
||||||
|
static profiler GlobalProfiler;
|
||||||
|
|
||||||
|
#define TimeFunction TimeBlock(__func__)
|
||||||
|
|
||||||
|
static void BeginProfile(void)
|
||||||
|
{
|
||||||
|
GlobalProfiler.StartTSC = ReadCPUTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EndAndPrintProfile()
|
||||||
|
{
|
||||||
|
GlobalProfiler.EndTSC = ReadCPUTimer();
|
||||||
|
u64 CPUFreq = EstimateCPUTimerFreq();
|
||||||
|
|
||||||
|
u64 TotalCPUElapsed = GlobalProfiler.EndTSC - GlobalProfiler.StartTSC;
|
||||||
|
|
||||||
|
if(CPUFreq)
|
||||||
|
{
|
||||||
|
printf("\nTotal time: %0.4fms (CPU freq %llu)\n", 1000.0 * (f64)TotalCPUElapsed / (f64)CPUFreq, CPUFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintAnchorData(TotalCPUElapsed);
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 92
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PROFILER 1
|
||||||
|
#include "listing_0091_switchable_profiler.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0089_allfuncs_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(File);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
BeginProfile();
|
||||||
|
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Result == 0)
|
||||||
|
{
|
||||||
|
EndAndPrintProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilerEndOfCompilationUnit;
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 93
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "listing_0091_switchable_profiler.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0089_allfuncs_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(File);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
BeginProfile();
|
||||||
|
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Result == 0)
|
||||||
|
{
|
||||||
|
EndAndPrintProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilerEndOfCompilationUnit;
|
||||||
@@ -0,0 +1,516 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 94
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
enum json_token_type
|
||||||
|
{
|
||||||
|
Token_end_of_stream,
|
||||||
|
Token_error,
|
||||||
|
|
||||||
|
Token_open_brace,
|
||||||
|
Token_open_bracket,
|
||||||
|
Token_close_brace,
|
||||||
|
Token_close_bracket,
|
||||||
|
Token_comma,
|
||||||
|
Token_colon,
|
||||||
|
Token_string_literal,
|
||||||
|
Token_number,
|
||||||
|
Token_true,
|
||||||
|
Token_false,
|
||||||
|
Token_null,
|
||||||
|
|
||||||
|
Token_count,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_token
|
||||||
|
{
|
||||||
|
json_token_type Type;
|
||||||
|
buffer Value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_element
|
||||||
|
{
|
||||||
|
buffer Label;
|
||||||
|
buffer Value;
|
||||||
|
json_element *FirstSubElement;
|
||||||
|
|
||||||
|
json_element *NextSibling;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_parser
|
||||||
|
{
|
||||||
|
buffer Source;
|
||||||
|
u64 At;
|
||||||
|
b32 HadError;
|
||||||
|
};
|
||||||
|
|
||||||
|
static b32 IsJSONDigit(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
b32 Result = false;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Val = Source.Data[At];
|
||||||
|
Result = ((Val >= '0') && (Val <= '9'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsJSONWhitespace(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
b32 Result = false;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Val = Source.Data[At];
|
||||||
|
Result = ((Val == ' ') || (Val == '\t') || (Val == '\n') || (Val == '\r'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsParsing(json_parser *Parser)
|
||||||
|
{
|
||||||
|
b32 Result = !Parser->HadError && IsInBounds(Parser->Source, Parser->At);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Error(json_parser *Parser, json_token Token, char const *Message)
|
||||||
|
{
|
||||||
|
Parser->HadError = true;
|
||||||
|
fprintf(stderr, "ERROR: \"%.*s\" - %s\n", (u32)Token.Value.Count, (char *)Token.Value.Data, Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ParseKeyword(buffer Source, u64 *At, buffer KeywordRemaining, json_token_type Type, json_token *Result)
|
||||||
|
{
|
||||||
|
if((Source.Count - *At) >= KeywordRemaining.Count)
|
||||||
|
{
|
||||||
|
buffer Check = Source;
|
||||||
|
Check.Data += *At;
|
||||||
|
Check.Count = KeywordRemaining.Count;
|
||||||
|
if(AreEqual(Check, KeywordRemaining))
|
||||||
|
{
|
||||||
|
Result->Type = Type;
|
||||||
|
Result->Value.Count += KeywordRemaining.Count;
|
||||||
|
*At += KeywordRemaining.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_token GetJSONToken(json_parser *Parser)
|
||||||
|
{
|
||||||
|
json_token Result = {};
|
||||||
|
|
||||||
|
buffer Source = Parser->Source;
|
||||||
|
u64 At = Parser->At;
|
||||||
|
|
||||||
|
while(IsJSONWhitespace(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
Result.Type = Token_error;
|
||||||
|
Result.Value.Count = 1;
|
||||||
|
Result.Value.Data = Source.Data + At;
|
||||||
|
u8 Val = Source.Data[At++];
|
||||||
|
switch(Val)
|
||||||
|
{
|
||||||
|
case '{': {Result.Type = Token_open_brace;} break;
|
||||||
|
case '[': {Result.Type = Token_open_bracket;} break;
|
||||||
|
case '}': {Result.Type = Token_close_brace;} break;
|
||||||
|
case ']': {Result.Type = Token_close_bracket;} break;
|
||||||
|
case ',': {Result.Type = Token_comma;} break;
|
||||||
|
case ':': {Result.Type = Token_colon;} break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("alse"), Token_false, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("ull"), Token_null, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("rue"), Token_true, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
{
|
||||||
|
Result.Type = Token_string_literal;
|
||||||
|
|
||||||
|
u64 StringStart = At;
|
||||||
|
|
||||||
|
while(IsInBounds(Source, At) && (Source.Data[At] != '"'))
|
||||||
|
{
|
||||||
|
if(IsInBounds(Source, (At + 1)) &&
|
||||||
|
(Source.Data[At] == '\\') &&
|
||||||
|
(Source.Data[At + 1] == '"'))
|
||||||
|
{
|
||||||
|
// NOTE(casey): Skip escaped quotation marks
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Value.Data = Source.Data + StringStart;
|
||||||
|
Result.Value.Count = At - StringStart;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
{
|
||||||
|
u64 Start = At - 1;
|
||||||
|
Result.Type = Token_number;
|
||||||
|
|
||||||
|
// NOTE(casey): Move past a leading negative sign if one exists
|
||||||
|
if((Val == '-') && IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
Val = Source.Data[At++];
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If the leading digit wasn't 0, parse any digits before the decimal point
|
||||||
|
if(Val != '0')
|
||||||
|
{
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If there is a decimal point, parse any digits after the decimal point
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If it's in scientific notation, parse any digits after the "e"
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == '+') || (Source.Data[At] == '-')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Value.Count = At - Start;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser->At = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels);
|
||||||
|
static json_element *ParseJSONElement(json_parser *Parser, buffer Label, json_token Value)
|
||||||
|
{
|
||||||
|
b32 Valid = true;
|
||||||
|
|
||||||
|
json_element *SubElement = 0;
|
||||||
|
if(Value.Type == Token_open_bracket)
|
||||||
|
{
|
||||||
|
SubElement = ParseJSONList(Parser, Token_close_bracket, false);
|
||||||
|
}
|
||||||
|
else if(Value.Type == Token_open_brace)
|
||||||
|
{
|
||||||
|
SubElement = ParseJSONList(Parser, Token_close_brace, true);
|
||||||
|
}
|
||||||
|
else if((Value.Type == Token_string_literal) ||
|
||||||
|
(Value.Type == Token_true) ||
|
||||||
|
(Value.Type == Token_false) ||
|
||||||
|
(Value.Type == Token_null) ||
|
||||||
|
(Value.Type == Token_number))
|
||||||
|
{
|
||||||
|
// NOTE(casey): Nothing to do here, since there is no additional data
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_element *Result = 0;
|
||||||
|
|
||||||
|
if(Valid)
|
||||||
|
{
|
||||||
|
Result = (json_element *)malloc(sizeof(json_element));
|
||||||
|
Result->Label = Label;
|
||||||
|
Result->Value = Value.Value;
|
||||||
|
Result->FirstSubElement = SubElement;
|
||||||
|
Result->NextSibling = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels)
|
||||||
|
{
|
||||||
|
json_element *FirstElement = {};
|
||||||
|
json_element *LastElement = {};
|
||||||
|
|
||||||
|
while(IsParsing(Parser))
|
||||||
|
{
|
||||||
|
buffer Label = {};
|
||||||
|
json_token Value = GetJSONToken(Parser);
|
||||||
|
if(HasLabels)
|
||||||
|
{
|
||||||
|
if(Value.Type == Token_string_literal)
|
||||||
|
{
|
||||||
|
Label = Value.Value;
|
||||||
|
|
||||||
|
json_token Colon = GetJSONToken(Parser);
|
||||||
|
if(Colon.Type == Token_colon)
|
||||||
|
{
|
||||||
|
Value = GetJSONToken(Parser);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Parser, Colon, "Expected colon after field name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(Value.Type != EndType)
|
||||||
|
{
|
||||||
|
Error(Parser, Value, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_element *Element = ParseJSONElement(Parser, Label, Value);
|
||||||
|
if(Element)
|
||||||
|
{
|
||||||
|
LastElement = (LastElement ? LastElement->NextSibling : FirstElement) = Element;
|
||||||
|
}
|
||||||
|
else if(Value.Type == EndType)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Parser, Value, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
json_token Comma = GetJSONToken(Parser);
|
||||||
|
if(Comma.Type == EndType)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if(Comma.Type != Token_comma)
|
||||||
|
{
|
||||||
|
Error(Parser, Comma, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FirstElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSON(buffer InputJSON)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
json_parser Parser = {};
|
||||||
|
Parser.Source = InputJSON;
|
||||||
|
|
||||||
|
json_element *Result = ParseJSONElement(&Parser, {}, GetJSONToken(&Parser));
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FreeJSON(json_element *Element)
|
||||||
|
{
|
||||||
|
while(Element)
|
||||||
|
{
|
||||||
|
json_element *FreeElement = Element;
|
||||||
|
Element = Element->NextSibling;
|
||||||
|
|
||||||
|
FreeJSON(FreeElement->FirstSubElement);
|
||||||
|
free(FreeElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *LookupElement(json_element *Object, buffer ElementName)
|
||||||
|
{
|
||||||
|
json_element *Result = 0;
|
||||||
|
|
||||||
|
if(Object)
|
||||||
|
{
|
||||||
|
for(json_element *Search = Object->FirstSubElement; Search; Search = Search->NextSibling)
|
||||||
|
{
|
||||||
|
if(AreEqual(Search->Label, ElementName))
|
||||||
|
{
|
||||||
|
Result = Search;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertJSONSign(buffer Source, u64 *AtResult)
|
||||||
|
{
|
||||||
|
u64 At = *AtResult;
|
||||||
|
|
||||||
|
f64 Result = 1.0;
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '-'))
|
||||||
|
{
|
||||||
|
Result = -1.0;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
*AtResult = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertJSONNumber(buffer Source, u64 *AtResult)
|
||||||
|
{
|
||||||
|
u64 At = *AtResult;
|
||||||
|
|
||||||
|
f64 Result = 0.0;
|
||||||
|
while(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Char = Source.Data[At] - (u8)'0';
|
||||||
|
if(Char < 10)
|
||||||
|
{
|
||||||
|
Result = 10.0*Result + (f64)Char;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*AtResult = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertElementToF64(json_element *Object, buffer ElementName)
|
||||||
|
{
|
||||||
|
f64 Result = 0.0;
|
||||||
|
|
||||||
|
json_element *Element = LookupElement(Object, ElementName);
|
||||||
|
if(Element)
|
||||||
|
{
|
||||||
|
buffer Source = Element->Value;
|
||||||
|
u64 At = 0;
|
||||||
|
|
||||||
|
f64 Sign = ConvertJSONSign(Source, &At);
|
||||||
|
f64 Number = ConvertJSONNumber(Source, &At);
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
f64 C = 1.0 / 10.0;
|
||||||
|
while(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Char = Source.Data[At] - (u8)'0';
|
||||||
|
if(Char < 10)
|
||||||
|
{
|
||||||
|
Number = Number + C*(f64)Char;
|
||||||
|
C *= 1.0 / 10.0;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '+'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 ExponentSign = ConvertJSONSign(Source, &At);
|
||||||
|
f64 Exponent = ExponentSign*ConvertJSONNumber(Source, &At);
|
||||||
|
Number *= pow(10.0, Exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result = Sign*Number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ParseHaversinePairs(buffer InputJSON, u64 MaxPairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
u64 PairCount = 0;
|
||||||
|
|
||||||
|
json_element *JSON = ParseJSON(InputJSON);
|
||||||
|
|
||||||
|
json_element *PairsArray = LookupElement(JSON, CONSTANT_STRING("pairs"));
|
||||||
|
if(PairsArray)
|
||||||
|
{
|
||||||
|
TimeBlock("Lookup and Convert");
|
||||||
|
|
||||||
|
for(json_element *Element = PairsArray->FirstSubElement;
|
||||||
|
Element && (PairCount < MaxPairCount);
|
||||||
|
Element = Element->NextSibling)
|
||||||
|
{
|
||||||
|
haversine_pair *Pair = Pairs + PairCount++;
|
||||||
|
|
||||||
|
Pair->X0 = ConvertElementToF64(Element, CONSTANT_STRING("x0"));
|
||||||
|
Pair->Y0 = ConvertElementToF64(Element, CONSTANT_STRING("y0"));
|
||||||
|
Pair->X1 = ConvertElementToF64(Element, CONSTANT_STRING("x1"));
|
||||||
|
Pair->Y1 = ConvertElementToF64(Element, CONSTANT_STRING("y1"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
TimeBlock("FreeJSON");
|
||||||
|
FreeJSON(JSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PairCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 95
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PROFILER 1
|
||||||
|
#include "listing_0091_switchable_profiler.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0094_profiled_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(File);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
BeginProfile();
|
||||||
|
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Result == 0)
|
||||||
|
{
|
||||||
|
EndAndPrintProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilerEndOfCompilationUnit;
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 96
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#include "listing_0074_platform_metrics.cpp"
|
||||||
|
|
||||||
|
#ifndef PROFILER
|
||||||
|
#define PROFILER 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef READ_BLOCK_TIMER
|
||||||
|
#define READ_BLOCK_TIMER ReadCPUTimer
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PROFILER
|
||||||
|
|
||||||
|
struct profile_anchor
|
||||||
|
{
|
||||||
|
u64 TSCElapsedExclusive; // NOTE(casey): Does NOT include children
|
||||||
|
u64 TSCElapsedInclusive; // NOTE(casey): DOES include children
|
||||||
|
u64 HitCount;
|
||||||
|
char const *Label;
|
||||||
|
};
|
||||||
|
static profile_anchor GlobalProfilerAnchors[4096];
|
||||||
|
static u32 GlobalProfilerParent;
|
||||||
|
|
||||||
|
struct profile_block
|
||||||
|
{
|
||||||
|
profile_block(char const *Label_, u32 AnchorIndex_)
|
||||||
|
{
|
||||||
|
ParentIndex = GlobalProfilerParent;
|
||||||
|
|
||||||
|
AnchorIndex = AnchorIndex_;
|
||||||
|
Label = Label_;
|
||||||
|
|
||||||
|
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
|
||||||
|
OldTSCElapsedInclusive = Anchor->TSCElapsedInclusive;
|
||||||
|
|
||||||
|
GlobalProfilerParent = AnchorIndex;
|
||||||
|
StartTSC = READ_BLOCK_TIMER();
|
||||||
|
}
|
||||||
|
|
||||||
|
~profile_block(void)
|
||||||
|
{
|
||||||
|
u64 Elapsed = READ_BLOCK_TIMER() - StartTSC;
|
||||||
|
GlobalProfilerParent = ParentIndex;
|
||||||
|
|
||||||
|
profile_anchor *Parent = GlobalProfilerAnchors + ParentIndex;
|
||||||
|
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
|
||||||
|
|
||||||
|
Parent->TSCElapsedExclusive -= Elapsed;
|
||||||
|
Anchor->TSCElapsedExclusive += Elapsed;
|
||||||
|
Anchor->TSCElapsedInclusive = OldTSCElapsedInclusive + Elapsed;
|
||||||
|
++Anchor->HitCount;
|
||||||
|
|
||||||
|
/* NOTE(casey): This write happens every time solely because there is no
|
||||||
|
straightforward way in C++ to have the same ease-of-use. In a better programming
|
||||||
|
language, it would be simple to have the anchor points gathered and labeled at compile
|
||||||
|
time, and this repetative write would be eliminated. */
|
||||||
|
Anchor->Label = Label;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *Label;
|
||||||
|
u64 OldTSCElapsedInclusive;
|
||||||
|
u64 StartTSC;
|
||||||
|
u32 ParentIndex;
|
||||||
|
u32 AnchorIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NameConcat2(A, B) A##B
|
||||||
|
#define NameConcat(A, B) NameConcat2(A, B)
|
||||||
|
#define TimeBlock(Name) profile_block NameConcat(Block, __LINE__)(Name, __COUNTER__ + 1);
|
||||||
|
#define ProfilerEndOfCompilationUnit static_assert(__COUNTER__ < ArrayCount(GlobalProfilerAnchors), "Number of profile points exceeds size of profiler::Anchors array")
|
||||||
|
|
||||||
|
static void PrintTimeElapsed(u64 TotalTSCElapsed, profile_anchor *Anchor)
|
||||||
|
{
|
||||||
|
f64 Percent = 100.0 * ((f64)Anchor->TSCElapsedExclusive / (f64)TotalTSCElapsed);
|
||||||
|
printf(" %s[%llu]: %llu (%.2f%%", Anchor->Label, Anchor->HitCount, Anchor->TSCElapsedExclusive, Percent);
|
||||||
|
if(Anchor->TSCElapsedInclusive != Anchor->TSCElapsedExclusive)
|
||||||
|
{
|
||||||
|
f64 PercentWithChildren = 100.0 * ((f64)Anchor->TSCElapsedInclusive / (f64)TotalTSCElapsed);
|
||||||
|
printf(", %.2f%% w/children", PercentWithChildren);
|
||||||
|
}
|
||||||
|
printf(")\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintAnchorData(u64 TotalCPUElapsed)
|
||||||
|
{
|
||||||
|
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfilerAnchors); ++AnchorIndex)
|
||||||
|
{
|
||||||
|
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
|
||||||
|
if(Anchor->TSCElapsedInclusive)
|
||||||
|
{
|
||||||
|
PrintTimeElapsed(TotalCPUElapsed, Anchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define TimeBlock(...)
|
||||||
|
#define PrintAnchorData(...)
|
||||||
|
#define ProfilerEndOfCompilationUnit
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct profiler
|
||||||
|
{
|
||||||
|
u64 StartTSC;
|
||||||
|
u64 EndTSC;
|
||||||
|
};
|
||||||
|
static profiler GlobalProfiler;
|
||||||
|
|
||||||
|
#define TimeFunction TimeBlock(__func__)
|
||||||
|
|
||||||
|
static u64 EstimateBlockTimerFreq(void)
|
||||||
|
{
|
||||||
|
(void)&EstimateCPUTimerFreq; // NOTE(casey): This has to be voided here to prevent compilers from warning us that it is not used
|
||||||
|
|
||||||
|
u64 MillisecondsToWait = 100;
|
||||||
|
u64 OSFreq = GetOSTimerFreq();
|
||||||
|
|
||||||
|
u64 BlockStart = READ_BLOCK_TIMER();
|
||||||
|
u64 OSStart = ReadOSTimer();
|
||||||
|
u64 OSEnd = 0;
|
||||||
|
u64 OSElapsed = 0;
|
||||||
|
u64 OSWaitTime = OSFreq * MillisecondsToWait / 1000;
|
||||||
|
while(OSElapsed < OSWaitTime)
|
||||||
|
{
|
||||||
|
OSEnd = ReadOSTimer();
|
||||||
|
OSElapsed = OSEnd - OSStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 BlockEnd = READ_BLOCK_TIMER();
|
||||||
|
u64 BlockElapsed = BlockEnd - BlockStart;
|
||||||
|
|
||||||
|
u64 BlockFreq = 0;
|
||||||
|
if(OSElapsed)
|
||||||
|
{
|
||||||
|
BlockFreq = OSFreq * BlockElapsed / OSElapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BlockFreq;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BeginProfile(void)
|
||||||
|
{
|
||||||
|
GlobalProfiler.StartTSC = READ_BLOCK_TIMER();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EndAndPrintProfile()
|
||||||
|
{
|
||||||
|
GlobalProfiler.EndTSC = READ_BLOCK_TIMER();
|
||||||
|
u64 TimerFreq = EstimateBlockTimerFreq();
|
||||||
|
|
||||||
|
u64 TotalTSCElapsed = GlobalProfiler.EndTSC - GlobalProfiler.StartTSC;
|
||||||
|
|
||||||
|
if(TimerFreq)
|
||||||
|
{
|
||||||
|
printf("\nTotal time: %0.4fms (timer freq %llu)\n", 1000.0 * (f64)TotalTSCElapsed / (f64)TimerFreq, TimerFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintAnchorData(TotalTSCElapsed);
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 97
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PROFILER 1
|
||||||
|
#include "listing_0096_selectable_profiler.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0083_recursive_timed_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(File);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
BeginProfile();
|
||||||
|
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Result == 0)
|
||||||
|
{
|
||||||
|
EndAndPrintProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilerEndOfCompilationUnit;
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 98
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PROFILER 1
|
||||||
|
#define READ_BLOCK_TIMER ReadOSTimer
|
||||||
|
#include "listing_0096_selectable_profiler.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0083_recursive_timed_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(File);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
BeginProfile();
|
||||||
|
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Result == 0)
|
||||||
|
{
|
||||||
|
EndAndPrintProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilerEndOfCompilationUnit;
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 99
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PROFILER 1
|
||||||
|
#define READ_BLOCK_TIMER ReadOSTimer
|
||||||
|
#include "listing_0091_switchable_profiler.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0094_profiled_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(File);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
BeginProfile();
|
||||||
|
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Result == 0)
|
||||||
|
{
|
||||||
|
EndAndPrintProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilerEndOfCompilationUnit;
|
||||||
+353
@@ -0,0 +1,353 @@
|
|||||||
|
// NOTE: Implementation ////////////////////////////////////////////////////////////////////////////
|
||||||
|
bool Str8_Equals(Str8 lhs, Str8 rhs)
|
||||||
|
{
|
||||||
|
bool result = lhs.size == rhs.size && memcmp(lhs.data, rhs.data, lhs.size) == 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Str8ToU64Result Str8_ToU64(Str8 string)
|
||||||
|
{
|
||||||
|
Str8ToU64Result result = {0};
|
||||||
|
|
||||||
|
size_t ch_index = 0;
|
||||||
|
while (ch_index < string.size && CharIsWhiteSpace(string.data[ch_index]))
|
||||||
|
ch_index++;
|
||||||
|
|
||||||
|
for (; ch_index < string.size; ch_index++) {
|
||||||
|
char ch = string.data[ch_index];
|
||||||
|
if (ch >= '0' && ch <= '9') {
|
||||||
|
result.value = (result.value * 10) + (ch - '0');
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.success = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Str8BinarySplitResult Str8_BinarySplit(Str8 buffer, Str8 find)
|
||||||
|
{
|
||||||
|
Str8BinarySplitResult result = {0};
|
||||||
|
result.lhs = buffer;
|
||||||
|
for (size_t index = 0; (index + find.size) <= buffer.size; index++) {
|
||||||
|
Str8 check = {buffer.data + index, find.size};
|
||||||
|
if (Str8_Equals(find, check)) {
|
||||||
|
result.lhs.size = index;
|
||||||
|
result.rhs.data = check.data + find.size;
|
||||||
|
result.rhs.size = buffer.size - (index + find.size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CharIsWhiteSpace(char ch)
|
||||||
|
{
|
||||||
|
bool result = ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CharIsDigit(char ch)
|
||||||
|
{
|
||||||
|
bool result = ch >= '0' && ch <= '9';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Profiler_Dump()
|
||||||
|
{
|
||||||
|
u64 total_elapsed_tsc = g_profiler.end_tsc - g_profiler.begin_tsc;
|
||||||
|
u64 cpu_frequency = EstimateCPUTimerFreq();
|
||||||
|
if (cpu_frequency)
|
||||||
|
printf("\nTotal time: %0.4fms (CPU freq %llu)\n", 1000.0 * (f64)total_elapsed_tsc / (f64)cpu_frequency, cpu_frequency);
|
||||||
|
|
||||||
|
for (uint32_t index = 1; index < ARRAY_UCOUNT(g_profiler.anchors); index++) {
|
||||||
|
ProfilerAnchor const *anchor = g_profiler.anchors + index;
|
||||||
|
if (!anchor->elapsed_tsc_inclusive)
|
||||||
|
break;
|
||||||
|
|
||||||
|
f64 percent = total_elapsed_tsc ? (f64)anchor->elapsed_tsc_exclusive / (f64)total_elapsed_tsc * 100.0 : 100.0;
|
||||||
|
printf(" %.*s[%zu]: %llu (%.2f%%", STR8_FMT(anchor->label), anchor->hits, anchor->elapsed_tsc_exclusive, percent);
|
||||||
|
if (anchor->elapsed_tsc_inclusive != anchor->elapsed_tsc_exclusive) {
|
||||||
|
f64 percent_w_children = total_elapsed_tsc ? ((f64)anchor->elapsed_tsc_inclusive / (f64)total_elapsed_tsc * 100.0) : 100.0;
|
||||||
|
printf(", %.2f%% w/children", percent_w_children);
|
||||||
|
}
|
||||||
|
printf(")");
|
||||||
|
|
||||||
|
if (anchor->byte_count) {
|
||||||
|
f64 megabytes_processed = anchor->byte_count / (1024.f * 1024.f);
|
||||||
|
f64 elapsed_s = anchor->elapsed_tsc_inclusive / CAST(f64)cpu_frequency;
|
||||||
|
f64 bytes_per_s = anchor->byte_count / elapsed_s;
|
||||||
|
f64 gigabytes_bandwidth = bytes_per_s / (1024.f * 1024.f * 1024.f);
|
||||||
|
printf(" %.3fmb at %.2fgb/s", megabytes_processed, gigabytes_bandwidth);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilerZone Profiler_BeginZone_(Str8 label, uint32_t index, u64 byte_count)
|
||||||
|
{
|
||||||
|
ProfilerZone result = {0};
|
||||||
|
#if defined(PROFILER)
|
||||||
|
result.index = index;
|
||||||
|
result.label = label;
|
||||||
|
result.tsc = ReadCPUTimer();
|
||||||
|
result.elapsed_tsc_inclusive = g_profiler.anchors[index].elapsed_tsc_inclusive;
|
||||||
|
result.byte_count = byte_count;
|
||||||
|
result.parent_index = g_profiler.parent_index;
|
||||||
|
g_profiler.parent_index = index;
|
||||||
|
#else
|
||||||
|
(void)label; (void)index; (void)byte_count;
|
||||||
|
#endif
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Profiler_EndZone(ProfilerZone zone)
|
||||||
|
{
|
||||||
|
#if defined(PROFILER)
|
||||||
|
u64 elapsed_tsc = ReadCPUTimer() - zone.tsc;
|
||||||
|
ProfilerAnchor* anchor = g_profiler.anchors + zone.index;
|
||||||
|
ProfilerAnchor* parent = g_profiler.anchors + zone.parent_index;
|
||||||
|
|
||||||
|
anchor->elapsed_tsc_exclusive += elapsed_tsc;
|
||||||
|
anchor->elapsed_tsc_inclusive = zone.elapsed_tsc_inclusive + elapsed_tsc;
|
||||||
|
anchor->label = zone.label;
|
||||||
|
anchor->byte_count += zone.byte_count;
|
||||||
|
anchor->hits++;
|
||||||
|
parent->elapsed_tsc_exclusive -= elapsed_tsc;
|
||||||
|
g_profiler.parent_index = zone.parent_index;
|
||||||
|
#else
|
||||||
|
(void)zone;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable: 4146) // warning C4146: unary minus operator applied to unsigned type, result still unsigned
|
||||||
|
uint32_t PCG32_Pie (uint64_t *state)
|
||||||
|
{
|
||||||
|
uint64_t old = *state ^ 0xc90fdaa2adf85459ULL;
|
||||||
|
*state = *state * 6364136223846793005ULL + 0xc90fdaa2adf85459ULL;
|
||||||
|
uint32_t xorshifted = (uint32_t)(((old >> 18u) ^ old) >> 27u);
|
||||||
|
uint32_t rot = old >> 59u;
|
||||||
|
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
|
||||||
|
}
|
||||||
|
#pragma warning(pop)
|
||||||
|
|
||||||
|
f64 PCG32_PieF64(uint64_t *state, f64 min, f64 max)
|
||||||
|
{
|
||||||
|
uint32_t u32_value = PCG32_Pie(state);
|
||||||
|
f64 t01 = CAST(f64)u32_value / CAST(f64)CAST(uint32_t)-1;
|
||||||
|
f64 result = min + (max - min) * t01;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BufferIsValid(Buffer buffer)
|
||||||
|
{
|
||||||
|
bool result = buffer.data && buffer.size;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferIterator BufferIteratorInit(Buffer buffer)
|
||||||
|
{
|
||||||
|
BufferIterator result = {0};
|
||||||
|
result.buffer = buffer;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BufferIteratorHasMoreBytes(BufferIterator it)
|
||||||
|
{
|
||||||
|
bool result = BufferIsValid(it.buffer) && it.index < it.buffer.size;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t BufferIteratorPeekByte(BufferIterator *it)
|
||||||
|
{
|
||||||
|
ASSERT(it);
|
||||||
|
ASSERT(BufferIsValid(it->buffer));
|
||||||
|
ASSERT(it->index < it->buffer.size);
|
||||||
|
uint8_t result = it->buffer.data[it->index];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t BufferIteratorNextByte(BufferIterator *it)
|
||||||
|
{
|
||||||
|
uint8_t result = BufferIteratorPeekByte(it);
|
||||||
|
it->index++;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer FileRead(char const *file_path)
|
||||||
|
{
|
||||||
|
Buffer result = {0};
|
||||||
|
|
||||||
|
// NOTE: Determine file size ///////////////////////////////////////////////////////////////////
|
||||||
|
WIN32_FILE_ATTRIBUTE_DATA file_attrib_data = {0};
|
||||||
|
if (GetFileAttributesEx(file_path, GetFileExInfoStandard, &file_attrib_data) == 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// NOTE: Open file /////////////////////////////////////////////////////////////////////////////
|
||||||
|
HANDLE file_handle = CreateFile(
|
||||||
|
/*LPCSTR lpFileName*/ file_path,
|
||||||
|
/*DWORD dwDesiredAccess*/ GENERIC_READ,
|
||||||
|
/*DWORD dwShareMode*/ 0,
|
||||||
|
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ NULL,
|
||||||
|
/*DWORD dwCreationDisposition*/ OPEN_EXISTING,
|
||||||
|
/*DWORD dwFlagsAndAttributes*/ 0,
|
||||||
|
/*HANDLE hTemplateFile*/ NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (file_handle == INVALID_HANDLE_VALUE)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// NOTE: Allocate buffer ///////////////////////////////////////////////////////////////////////
|
||||||
|
uint64_t file_size = (uint64_t)file_attrib_data.nFileSizeHigh << 32 | (uint64_t)file_attrib_data.nFileSizeLow << 0;
|
||||||
|
ASSERT(file_size < (DWORD)-1);
|
||||||
|
char *buffer = VirtualAlloc(
|
||||||
|
/*LPVOID lpAddress*/ NULL,
|
||||||
|
/*SIZE_T dwSize*/ file_size,
|
||||||
|
/*DWORD flAllocationType*/ MEM_COMMIT | MEM_RESERVE,
|
||||||
|
/*DWORD flProtect*/ PAGE_READWRITE
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!buffer)
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
// NOTE: Read file to buffer ///////////////////////////////////////////////////////////////////
|
||||||
|
DWORD bytes_read = 0;
|
||||||
|
ProfilerZone prof_file_read_zone = Profiler_BeginZoneBandwidth("File Read", file_size);
|
||||||
|
BOOL read_file_result = ReadFile(
|
||||||
|
/*HANDLE hFile*/ file_handle,
|
||||||
|
/*LPVOID lpBuffer*/ buffer,
|
||||||
|
/*DWORD nNumberOfBytesToRead*/ CAST(DWORD)file_size,
|
||||||
|
/*LPDWORD lpNumberOfBytesRead*/ &bytes_read,
|
||||||
|
/*LPOVERLAPPED lpOverlapped*/ NULL
|
||||||
|
);
|
||||||
|
Profiler_EndZone(prof_file_read_zone);
|
||||||
|
|
||||||
|
// NOTE: Handle read result ////////////////////////////////////////////////////////////////////
|
||||||
|
if (read_file_result == 0) {
|
||||||
|
VirtualFree(buffer, 0, MEM_RELEASE);
|
||||||
|
} else {
|
||||||
|
result.data = buffer;
|
||||||
|
result.size = file_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
CloseHandle(file_handle);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
void FileFree(Buffer buffer)
|
||||||
|
{
|
||||||
|
if (BufferIsValid(buffer))
|
||||||
|
VirtualFree(buffer.data, 0, MEM_RELEASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileWrite(char const *file_path, void const *buffer, size_t buffer_size)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
// NOTE: Open file /////////////////////////////////////////////////////////////////////////////
|
||||||
|
HANDLE file_handle = CreateFile(
|
||||||
|
/*LPCSTR lpFileName*/ file_path,
|
||||||
|
/*DWORD dwDesiredAccess*/ GENERIC_WRITE,
|
||||||
|
/*DWORD dwShareMode*/ 0,
|
||||||
|
/*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ NULL,
|
||||||
|
/*DWORD dwCreationDisposition*/ CREATE_ALWAYS,
|
||||||
|
/*DWORD dwFlagsAndAttributes*/ 0,
|
||||||
|
/*HANDLE hTemplateFile*/ NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
if (file_handle == INVALID_HANDLE_VALUE)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// NOTE: Write file to disk ////////////////////////////////////////////////////////////////////
|
||||||
|
DWORD bytes_written = 0;
|
||||||
|
BOOL write_file_result = WriteFile(
|
||||||
|
/*HANDLE hFile*/ file_handle,
|
||||||
|
/*LPVOID lpBuffer*/ buffer,
|
||||||
|
/*DWORD nNumberOfBytesToWrite*/ CAST(DWORD)buffer_size,
|
||||||
|
/*LPDWORD lpNumberOfBytesWrite*/ &bytes_written,
|
||||||
|
/*LPOVERLAPPED lpOverlapped*/ NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
ASSERT(bytes_written == buffer_size);
|
||||||
|
result = write_file_result && bytes_written == buffer_size;
|
||||||
|
CloseHandle(file_handle);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
void PrintHandle(void *handle, Str8 string)
|
||||||
|
{
|
||||||
|
DWORD bytes_written = 0;
|
||||||
|
WriteFile(handle, string.data, CAST(DWORD)string.size, &bytes_written, NULL);
|
||||||
|
(void)bytes_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Print(Str8 string)
|
||||||
|
{
|
||||||
|
if (pap_globals.stdout_handle == NULL) {
|
||||||
|
pap_globals.stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
DWORD mode = 0;
|
||||||
|
BOOL get_console_mode_result = GetConsoleMode(
|
||||||
|
/*HANDLE hConsoleHandle*/ pap_globals.stdout_handle,
|
||||||
|
/*LPDWORD lpMode*/ &mode
|
||||||
|
);
|
||||||
|
pap_globals.write_to_console = get_console_mode_result != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ASSERT(string.size < CAST(DWORD)-1);
|
||||||
|
if (pap_globals.write_to_console) {
|
||||||
|
DWORD chars_written = 0;
|
||||||
|
WriteConsoleA(pap_globals.stdout_handle, string.data, (DWORD)string.size, &chars_written, NULL);
|
||||||
|
} else {
|
||||||
|
PrintHandle(pap_globals.stdout_handle, string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintFmt(char const *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list args, args_copy;
|
||||||
|
va_start(args, fmt);
|
||||||
|
|
||||||
|
va_copy(args_copy, args);
|
||||||
|
int string_size = vsnprintf(NULL, 0, fmt, args_copy);
|
||||||
|
va_end(args_copy);
|
||||||
|
|
||||||
|
char buffer[8192];
|
||||||
|
ASSERT(string_size >= 0 && string_size < ARRAY_UCOUNT(buffer));
|
||||||
|
if (string_size) {
|
||||||
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||||
|
Str8 string = {.data = buffer, .size = string_size};
|
||||||
|
Print(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintLn(Str8 string)
|
||||||
|
{
|
||||||
|
Print(string);
|
||||||
|
Print(STR8("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintLnFmt(char const *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list args, args_copy;
|
||||||
|
va_start(args, fmt);
|
||||||
|
|
||||||
|
va_copy(args_copy, args);
|
||||||
|
int string_size = vsnprintf(NULL, 0, fmt, args_copy);
|
||||||
|
va_end(args_copy);
|
||||||
|
|
||||||
|
char buffer[8192];
|
||||||
|
ASSERT(string_size >= 0 && string_size < ARRAY_UCOUNT(buffer));
|
||||||
|
if (string_size) {
|
||||||
|
vsnprintf(buffer, sizeof(buffer), fmt, args);
|
||||||
|
Str8 string = {.data = buffer, .size = string_size};
|
||||||
|
PrintLn(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// NOTE: Macros ////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
#define STRINGIFY2(token) #token
|
||||||
|
#define STRINGIFY(token) STRINGIFY2(token)
|
||||||
|
|
||||||
|
#if defined(NDEBUG)
|
||||||
|
#define ASSERT(expr)
|
||||||
|
#else
|
||||||
|
#define ASSERT(expr) \
|
||||||
|
if (!(expr)) { \
|
||||||
|
PrintLnFmt("Assertion triggered [file=\"" __FILE__ ":" STRINGIFY(__LINE__) "\", expr=\"" #expr "\"]"); \
|
||||||
|
__debugbreak(); \
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ARRAY_UCOUNT(array) sizeof((array)) / sizeof((array)[0])
|
||||||
|
#define CAST(Type) (Type)
|
||||||
|
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||||
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
typedef bool b32;
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint16_t u16;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
// NOTE: Globals ///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
typedef struct Globals {
|
||||||
|
HANDLE stdout_handle;
|
||||||
|
bool write_to_console;
|
||||||
|
} Globals;
|
||||||
|
|
||||||
|
Globals pap_globals;
|
||||||
|
|
||||||
|
// NOTE: Strings ///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
typedef struct Str8 {
|
||||||
|
char *data;
|
||||||
|
size_t size;
|
||||||
|
} Str8;
|
||||||
|
|
||||||
|
typedef struct Str8ToU64Result {
|
||||||
|
bool success;
|
||||||
|
uint64_t value;
|
||||||
|
} Str8ToU64Result;
|
||||||
|
|
||||||
|
typedef struct Str8BinarySplitResult {
|
||||||
|
Str8 lhs;
|
||||||
|
Str8 rhs;
|
||||||
|
} Str8BinarySplitResult;
|
||||||
|
|
||||||
|
#define STR8(string) (Str8){.data = (char *)(string), .size = ARRAY_UCOUNT(string) - 1 }
|
||||||
|
#define STR8_FMT(string) (int)((string).size), (string).data
|
||||||
|
|
||||||
|
bool Str8_Equals(Str8 lhs, Str8 rhs);
|
||||||
|
Str8ToU64Result Str8_ToU64(Str8 string);
|
||||||
|
Str8BinarySplitResult Str8_BinarySplit(Str8 buffer, Str8 find);
|
||||||
|
|
||||||
|
bool CharIsWhiteSpace(char ch);
|
||||||
|
bool CharIsDigit(char ch);
|
||||||
|
|
||||||
|
// NOTE: Profiler //////////////////////////////////////////////////////////////////////////////////
|
||||||
|
typedef struct ProfilerAnchor {
|
||||||
|
Str8 label;
|
||||||
|
u64 elapsed_tsc_exclusive; // Does not include children
|
||||||
|
u64 elapsed_tsc_inclusive; // Includes children
|
||||||
|
u64 byte_count;
|
||||||
|
u64 hits;
|
||||||
|
} ProfilerAnchor;
|
||||||
|
|
||||||
|
typedef struct Profiler {
|
||||||
|
ProfilerAnchor anchors[4096];
|
||||||
|
u64 begin_tsc;
|
||||||
|
u64 end_tsc;
|
||||||
|
u64 parent_index;
|
||||||
|
} Profiler;
|
||||||
|
|
||||||
|
typedef struct ProfilerZone {
|
||||||
|
u64 parent_index;
|
||||||
|
uint32_t index;
|
||||||
|
Str8 label;
|
||||||
|
u64 elapsed_tsc_inclusive;
|
||||||
|
u64 tsc;
|
||||||
|
u64 byte_count;
|
||||||
|
} ProfilerZone;
|
||||||
|
|
||||||
|
static Profiler g_profiler;
|
||||||
|
|
||||||
|
#define Profiler_BeginZone(label) Profiler_BeginZone_(STR8(label), __COUNTER__ + 1, 0)
|
||||||
|
#define Profiler_BeginZoneBandwidth(label, byte_count) Profiler_BeginZone_(STR8(label), __COUNTER__ + 1, byte_count)
|
||||||
|
|
||||||
|
static void Profiler_Dump ();
|
||||||
|
static ProfilerZone Profiler_BeginZone_(Str8 label, uint32_t index, u64 byte_count);
|
||||||
|
static void Profiler_EndZone (ProfilerZone zone);
|
||||||
|
|
||||||
|
// NOTE: PCG32 /////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// NOTE: PCG RNG from Demetri Spanos: https://github.com/demetri/scribbles
|
||||||
|
// pcg32_pie, based on the minimal C version from O'Neill at pcg-random.org;
|
||||||
|
// I've made a few (subjective) UX improvements for beginner use
|
||||||
|
//
|
||||||
|
// I'm not allowing the user to pick the stream/increment constant at all,
|
||||||
|
// since there is almost never a reason for doing this in common applications.
|
||||||
|
// This means that the prng state is reduced to a single uint64_t which also
|
||||||
|
// means we can avoid having a state struct at all. The (fixed) stream constant
|
||||||
|
// uses the leading hex digits of pi and e multipled by 2^30 (c90fdaa2 and
|
||||||
|
// adf85459).
|
||||||
|
//
|
||||||
|
// I have also added an XOR with the same digits on the output path prior
|
||||||
|
// to xorshift mixing. This prevents the "surprising" result that the
|
||||||
|
// first "random 32-bit number" from a (very common) 0 seed is 0.
|
||||||
|
//
|
||||||
|
// use:
|
||||||
|
// uint64_t state = 12345; // whatever you like can go here
|
||||||
|
// uint32_t some_random_32_bits = pcg32_pie(&state);
|
||||||
|
// uint32_t more_random_32_bits = pcg32_pie(&state);
|
||||||
|
uint32_t PCG32_Pie (uint64_t *state);
|
||||||
|
f64 PCG32_PieF64(uint64_t *state, f64 min, f64 max);
|
||||||
|
|
||||||
|
// NOTE: Buffer ////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
typedef struct Buffer {
|
||||||
|
char *data;
|
||||||
|
size_t size;
|
||||||
|
} Buffer;
|
||||||
|
|
||||||
|
typedef struct BufferIterator {
|
||||||
|
Buffer buffer;
|
||||||
|
size_t index;
|
||||||
|
} BufferIterator;
|
||||||
|
|
||||||
|
bool BufferIsValid (Buffer buffer);
|
||||||
|
BufferIterator BufferIteratorInit (Buffer buffer);
|
||||||
|
bool BufferIteratorHasMoreBytes(BufferIterator it);
|
||||||
|
uint8_t BufferIteratorNextByte (BufferIterator *it);
|
||||||
|
|
||||||
|
// NOTE: File //////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Buffer FileRead (char const *file_path);
|
||||||
|
void FileFree (Buffer buffer);
|
||||||
|
bool FileWrite(char const *file_path, void const *buffer, size_t buffer_size);
|
||||||
|
|
||||||
|
// NOTE: Print /////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void PrintHandle(void *handle, Str8 string);
|
||||||
|
void PrintLn (Str8 string);
|
||||||
|
void PrintLnFmt (char const *fmt, ...);
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 65
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
static f64 Square(f64 A)
|
||||||
|
{
|
||||||
|
f64 Result = (A*A);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 RadiansFromDegrees(f64 Degrees)
|
||||||
|
{
|
||||||
|
f64 Result = 0.01745329251994329577 * Degrees;
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): EarthRadius is generally expected to be 6372.8
|
||||||
|
static f64 ReferenceHaversine(f64 X0, f64 Y0, f64 X1, f64 Y1, f64 EarthRadius)
|
||||||
|
{
|
||||||
|
/* NOTE(casey): This is not meant to be a "good" way to calculate the Haversine distance.
|
||||||
|
Instead, it attempts to follow, as closely as possible, the formula used in the real-world
|
||||||
|
question on which these homework exercises are loosely based.
|
||||||
|
*/
|
||||||
|
|
||||||
|
f64 lat1 = Y0;
|
||||||
|
f64 lat2 = Y1;
|
||||||
|
f64 lon1 = X0;
|
||||||
|
f64 lon2 = X1;
|
||||||
|
|
||||||
|
f64 dLat = RadiansFromDegrees(lat2 - lat1);
|
||||||
|
f64 dLon = RadiansFromDegrees(lon2 - lon1);
|
||||||
|
lat1 = RadiansFromDegrees(lat1);
|
||||||
|
lat2 = RadiansFromDegrees(lat2);
|
||||||
|
|
||||||
|
f64 a = Square(sin(dLat/2.0)) + cos(lat1)*cos(lat2)*Square(sin(dLon/2));
|
||||||
|
f64 c = 2.0*asin(sqrt(a));
|
||||||
|
|
||||||
|
f64 Result = EarthRadius * c;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 68
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
struct buffer
|
||||||
|
{
|
||||||
|
size_t Count;
|
||||||
|
u8 *Data;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CONSTANT_STRING(String) {sizeof(String) - 1, (u8 *)(String)}
|
||||||
|
|
||||||
|
static b32 IsInBounds(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
b32 Result = (At < Source.Count);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 AreEqual(buffer A, buffer B)
|
||||||
|
{
|
||||||
|
if(A.Count != B.Count)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(u64 Index = 0; Index < A.Count; ++Index)
|
||||||
|
{
|
||||||
|
if(A.Data[Index] != B.Data[Index])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static buffer AllocateBuffer(size_t Count)
|
||||||
|
{
|
||||||
|
buffer Result = {};
|
||||||
|
Result.Data = (u8 *)malloc(Count);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
Result.Count = Count;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to allocate %llu bytes.\n", Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FreeBuffer(buffer *Buffer)
|
||||||
|
{
|
||||||
|
if(Buffer->Data)
|
||||||
|
{
|
||||||
|
free(Buffer->Data);
|
||||||
|
}
|
||||||
|
*Buffer = {};
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 74
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
|
||||||
|
#include <intrin.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
static u64 GetOSTimerFreq(void)
|
||||||
|
{
|
||||||
|
LARGE_INTEGER Freq;
|
||||||
|
QueryPerformanceFrequency(&Freq);
|
||||||
|
return Freq.QuadPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ReadOSTimer(void)
|
||||||
|
{
|
||||||
|
LARGE_INTEGER Value;
|
||||||
|
QueryPerformanceCounter(&Value);
|
||||||
|
return Value.QuadPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <x86intrin.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
static u64 GetOSTimerFreq(void)
|
||||||
|
{
|
||||||
|
return 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ReadOSTimer(void)
|
||||||
|
{
|
||||||
|
// NOTE(casey): The "struct" keyword is not necessary here when compiling in C++,
|
||||||
|
// but just in case anyone is using this file from C, I include it.
|
||||||
|
struct timeval Value;
|
||||||
|
gettimeofday(&Value, 0);
|
||||||
|
|
||||||
|
u64 Result = GetOSTimerFreq()*(u64)Value.tv_sec + (u64)Value.tv_usec;
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* NOTE(casey): This does not need to be "inline", it could just be "static"
|
||||||
|
because compilers will inline it anyway. But compilers will warn about
|
||||||
|
static functions that aren't used. So "inline" is just the simplest way
|
||||||
|
to tell them to stop complaining about that. */
|
||||||
|
inline u64 ReadCPUTimer(void)
|
||||||
|
{
|
||||||
|
// NOTE(casey): If you were on ARM, you would need to replace __rdtsc
|
||||||
|
// with one of their performance counter read instructions, depending
|
||||||
|
// on which ones are available on your platform.
|
||||||
|
|
||||||
|
return __rdtsc();
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 EstimateCPUTimerFreq(void)
|
||||||
|
{
|
||||||
|
u64 MillisecondsToWait = 100;
|
||||||
|
u64 OSFreq = GetOSTimerFreq();
|
||||||
|
|
||||||
|
u64 CPUStart = ReadCPUTimer();
|
||||||
|
u64 OSStart = ReadOSTimer();
|
||||||
|
u64 OSEnd = 0;
|
||||||
|
u64 OSElapsed = 0;
|
||||||
|
u64 OSWaitTime = OSFreq * MillisecondsToWait / 1000;
|
||||||
|
while(OSElapsed < OSWaitTime)
|
||||||
|
{
|
||||||
|
OSEnd = ReadOSTimer();
|
||||||
|
OSElapsed = OSEnd - OSStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 CPUEnd = ReadCPUTimer();
|
||||||
|
u64 CPUElapsed = CPUEnd - CPUStart;
|
||||||
|
|
||||||
|
u64 CPUFreq = 0;
|
||||||
|
if(OSElapsed)
|
||||||
|
{
|
||||||
|
CPUFreq = OSFreq * CPUElapsed / OSElapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CPUFreq;
|
||||||
|
}
|
||||||
@@ -0,0 +1,516 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 94
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
enum json_token_type
|
||||||
|
{
|
||||||
|
Token_end_of_stream,
|
||||||
|
Token_error,
|
||||||
|
|
||||||
|
Token_open_brace,
|
||||||
|
Token_open_bracket,
|
||||||
|
Token_close_brace,
|
||||||
|
Token_close_bracket,
|
||||||
|
Token_comma,
|
||||||
|
Token_colon,
|
||||||
|
Token_string_literal,
|
||||||
|
Token_number,
|
||||||
|
Token_true,
|
||||||
|
Token_false,
|
||||||
|
Token_null,
|
||||||
|
|
||||||
|
Token_count,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_token
|
||||||
|
{
|
||||||
|
json_token_type Type;
|
||||||
|
buffer Value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_element
|
||||||
|
{
|
||||||
|
buffer Label;
|
||||||
|
buffer Value;
|
||||||
|
json_element *FirstSubElement;
|
||||||
|
|
||||||
|
json_element *NextSibling;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct json_parser
|
||||||
|
{
|
||||||
|
buffer Source;
|
||||||
|
u64 At;
|
||||||
|
b32 HadError;
|
||||||
|
};
|
||||||
|
|
||||||
|
static b32 IsJSONDigit(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
b32 Result = false;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Val = Source.Data[At];
|
||||||
|
Result = ((Val >= '0') && (Val <= '9'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsJSONWhitespace(buffer Source, u64 At)
|
||||||
|
{
|
||||||
|
b32 Result = false;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Val = Source.Data[At];
|
||||||
|
Result = ((Val == ' ') || (Val == '\t') || (Val == '\n') || (Val == '\r'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsParsing(json_parser *Parser)
|
||||||
|
{
|
||||||
|
b32 Result = !Parser->HadError && IsInBounds(Parser->Source, Parser->At);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Error(json_parser *Parser, json_token Token, char const *Message)
|
||||||
|
{
|
||||||
|
Parser->HadError = true;
|
||||||
|
fprintf(stderr, "ERROR: \"%.*s\" - %s\n", (u32)Token.Value.Count, (char *)Token.Value.Data, Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ParseKeyword(buffer Source, u64 *At, buffer KeywordRemaining, json_token_type Type, json_token *Result)
|
||||||
|
{
|
||||||
|
if((Source.Count - *At) >= KeywordRemaining.Count)
|
||||||
|
{
|
||||||
|
buffer Check = Source;
|
||||||
|
Check.Data += *At;
|
||||||
|
Check.Count = KeywordRemaining.Count;
|
||||||
|
if(AreEqual(Check, KeywordRemaining))
|
||||||
|
{
|
||||||
|
Result->Type = Type;
|
||||||
|
Result->Value.Count += KeywordRemaining.Count;
|
||||||
|
*At += KeywordRemaining.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_token GetJSONToken(json_parser *Parser)
|
||||||
|
{
|
||||||
|
json_token Result = {};
|
||||||
|
|
||||||
|
buffer Source = Parser->Source;
|
||||||
|
u64 At = Parser->At;
|
||||||
|
|
||||||
|
while(IsJSONWhitespace(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
Result.Type = Token_error;
|
||||||
|
Result.Value.Count = 1;
|
||||||
|
Result.Value.Data = Source.Data + At;
|
||||||
|
u8 Val = Source.Data[At++];
|
||||||
|
switch(Val)
|
||||||
|
{
|
||||||
|
case '{': {Result.Type = Token_open_brace;} break;
|
||||||
|
case '[': {Result.Type = Token_open_bracket;} break;
|
||||||
|
case '}': {Result.Type = Token_close_brace;} break;
|
||||||
|
case ']': {Result.Type = Token_close_bracket;} break;
|
||||||
|
case ',': {Result.Type = Token_comma;} break;
|
||||||
|
case ':': {Result.Type = Token_colon;} break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("alse"), Token_false, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("ull"), Token_null, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
{
|
||||||
|
ParseKeyword(Source, &At, CONSTANT_STRING("rue"), Token_true, &Result);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
{
|
||||||
|
Result.Type = Token_string_literal;
|
||||||
|
|
||||||
|
u64 StringStart = At;
|
||||||
|
|
||||||
|
while(IsInBounds(Source, At) && (Source.Data[At] != '"'))
|
||||||
|
{
|
||||||
|
if(IsInBounds(Source, (At + 1)) &&
|
||||||
|
(Source.Data[At] == '\\') &&
|
||||||
|
(Source.Data[At + 1] == '"'))
|
||||||
|
{
|
||||||
|
// NOTE(casey): Skip escaped quotation marks
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Value.Data = Source.Data + StringStart;
|
||||||
|
Result.Value.Count = At - StringStart;
|
||||||
|
if(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
{
|
||||||
|
u64 Start = At - 1;
|
||||||
|
Result.Type = Token_number;
|
||||||
|
|
||||||
|
// NOTE(casey): Move past a leading negative sign if one exists
|
||||||
|
if((Val == '-') && IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
Val = Source.Data[At++];
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If the leading digit wasn't 0, parse any digits before the decimal point
|
||||||
|
if(Val != '0')
|
||||||
|
{
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If there is a decimal point, parse any digits after the decimal point
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): If it's in scientific notation, parse any digits after the "e"
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == '+') || (Source.Data[At] == '-')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(IsJSONDigit(Source, At))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.Value.Count = At - Start;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser->At = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels);
|
||||||
|
static json_element *ParseJSONElement(json_parser *Parser, buffer Label, json_token Value)
|
||||||
|
{
|
||||||
|
b32 Valid = true;
|
||||||
|
|
||||||
|
json_element *SubElement = 0;
|
||||||
|
if(Value.Type == Token_open_bracket)
|
||||||
|
{
|
||||||
|
SubElement = ParseJSONList(Parser, Token_close_bracket, false);
|
||||||
|
}
|
||||||
|
else if(Value.Type == Token_open_brace)
|
||||||
|
{
|
||||||
|
SubElement = ParseJSONList(Parser, Token_close_brace, true);
|
||||||
|
}
|
||||||
|
else if((Value.Type == Token_string_literal) ||
|
||||||
|
(Value.Type == Token_true) ||
|
||||||
|
(Value.Type == Token_false) ||
|
||||||
|
(Value.Type == Token_null) ||
|
||||||
|
(Value.Type == Token_number))
|
||||||
|
{
|
||||||
|
// NOTE(casey): Nothing to do here, since there is no additional data
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_element *Result = 0;
|
||||||
|
|
||||||
|
if(Valid)
|
||||||
|
{
|
||||||
|
Result = (json_element *)malloc(sizeof(json_element));
|
||||||
|
Result->Label = Label;
|
||||||
|
Result->Value = Value.Value;
|
||||||
|
Result->FirstSubElement = SubElement;
|
||||||
|
Result->NextSibling = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSONList(json_parser *Parser, json_token_type EndType, b32 HasLabels)
|
||||||
|
{
|
||||||
|
json_element *FirstElement = {};
|
||||||
|
json_element *LastElement = {};
|
||||||
|
|
||||||
|
while(IsParsing(Parser))
|
||||||
|
{
|
||||||
|
buffer Label = {};
|
||||||
|
json_token Value = GetJSONToken(Parser);
|
||||||
|
if(HasLabels)
|
||||||
|
{
|
||||||
|
if(Value.Type == Token_string_literal)
|
||||||
|
{
|
||||||
|
Label = Value.Value;
|
||||||
|
|
||||||
|
json_token Colon = GetJSONToken(Parser);
|
||||||
|
if(Colon.Type == Token_colon)
|
||||||
|
{
|
||||||
|
Value = GetJSONToken(Parser);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Parser, Colon, "Expected colon after field name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(Value.Type != EndType)
|
||||||
|
{
|
||||||
|
Error(Parser, Value, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json_element *Element = ParseJSONElement(Parser, Label, Value);
|
||||||
|
if(Element)
|
||||||
|
{
|
||||||
|
LastElement = (LastElement ? LastElement->NextSibling : FirstElement) = Element;
|
||||||
|
}
|
||||||
|
else if(Value.Type == EndType)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Parser, Value, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
json_token Comma = GetJSONToken(Parser);
|
||||||
|
if(Comma.Type == EndType)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if(Comma.Type != Token_comma)
|
||||||
|
{
|
||||||
|
Error(Parser, Comma, "Unexpected token in JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FirstElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *ParseJSON(buffer InputJSON)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
json_parser Parser = {};
|
||||||
|
Parser.Source = InputJSON;
|
||||||
|
|
||||||
|
json_element *Result = ParseJSONElement(&Parser, {}, GetJSONToken(&Parser));
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FreeJSON(json_element *Element)
|
||||||
|
{
|
||||||
|
while(Element)
|
||||||
|
{
|
||||||
|
json_element *FreeElement = Element;
|
||||||
|
Element = Element->NextSibling;
|
||||||
|
|
||||||
|
FreeJSON(FreeElement->FirstSubElement);
|
||||||
|
free(FreeElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json_element *LookupElement(json_element *Object, buffer ElementName)
|
||||||
|
{
|
||||||
|
json_element *Result = 0;
|
||||||
|
|
||||||
|
if(Object)
|
||||||
|
{
|
||||||
|
for(json_element *Search = Object->FirstSubElement; Search; Search = Search->NextSibling)
|
||||||
|
{
|
||||||
|
if(AreEqual(Search->Label, ElementName))
|
||||||
|
{
|
||||||
|
Result = Search;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertJSONSign(buffer Source, u64 *AtResult)
|
||||||
|
{
|
||||||
|
u64 At = *AtResult;
|
||||||
|
|
||||||
|
f64 Result = 1.0;
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '-'))
|
||||||
|
{
|
||||||
|
Result = -1.0;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
*AtResult = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertJSONNumber(buffer Source, u64 *AtResult)
|
||||||
|
{
|
||||||
|
u64 At = *AtResult;
|
||||||
|
|
||||||
|
f64 Result = 0.0;
|
||||||
|
while(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Char = Source.Data[At] - (u8)'0';
|
||||||
|
if(Char < 10)
|
||||||
|
{
|
||||||
|
Result = 10.0*Result + (f64)Char;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*AtResult = At;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 ConvertElementToF64(json_element *Object, buffer ElementName)
|
||||||
|
{
|
||||||
|
f64 Result = 0.0;
|
||||||
|
|
||||||
|
json_element *Element = LookupElement(Object, ElementName);
|
||||||
|
if(Element)
|
||||||
|
{
|
||||||
|
buffer Source = Element->Value;
|
||||||
|
u64 At = 0;
|
||||||
|
|
||||||
|
f64 Sign = ConvertJSONSign(Source, &At);
|
||||||
|
f64 Number = ConvertJSONNumber(Source, &At);
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '.'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
f64 C = 1.0 / 10.0;
|
||||||
|
while(IsInBounds(Source, At))
|
||||||
|
{
|
||||||
|
u8 Char = Source.Data[At] - (u8)'0';
|
||||||
|
if(Char < 10)
|
||||||
|
{
|
||||||
|
Number = Number + C*(f64)Char;
|
||||||
|
C *= 1.0 / 10.0;
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsInBounds(Source, At) && ((Source.Data[At] == 'e') || (Source.Data[At] == 'E')))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
if(IsInBounds(Source, At) && (Source.Data[At] == '+'))
|
||||||
|
{
|
||||||
|
++At;
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 ExponentSign = ConvertJSONSign(Source, &At);
|
||||||
|
f64 Exponent = ExponentSign*ConvertJSONNumber(Source, &At);
|
||||||
|
Number *= pow(10.0, Exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result = Sign*Number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ParseHaversinePairs(buffer InputJSON, u64 MaxPairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
u64 PairCount = 0;
|
||||||
|
|
||||||
|
json_element *JSON = ParseJSON(InputJSON);
|
||||||
|
|
||||||
|
json_element *PairsArray = LookupElement(JSON, CONSTANT_STRING("pairs"));
|
||||||
|
if(PairsArray)
|
||||||
|
{
|
||||||
|
TimeBlock("Lookup and Convert");
|
||||||
|
|
||||||
|
for(json_element *Element = PairsArray->FirstSubElement;
|
||||||
|
Element && (PairCount < MaxPairCount);
|
||||||
|
Element = Element->NextSibling)
|
||||||
|
{
|
||||||
|
haversine_pair *Pair = Pairs + PairCount++;
|
||||||
|
|
||||||
|
Pair->X0 = ConvertElementToF64(Element, CONSTANT_STRING("x0"));
|
||||||
|
Pair->Y0 = ConvertElementToF64(Element, CONSTANT_STRING("y0"));
|
||||||
|
Pair->X1 = ConvertElementToF64(Element, CONSTANT_STRING("x1"));
|
||||||
|
Pair->Y1 = ConvertElementToF64(Element, CONSTANT_STRING("y1"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
TimeBlock("FreeJSON");
|
||||||
|
FreeJSON(JSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PairCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 100
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#include "listing_0074_platform_metrics.cpp"
|
||||||
|
|
||||||
|
#ifndef PROFILER
|
||||||
|
#define PROFILER 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef READ_BLOCK_TIMER
|
||||||
|
#define READ_BLOCK_TIMER ReadCPUTimer
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PROFILER
|
||||||
|
|
||||||
|
struct profile_anchor
|
||||||
|
{
|
||||||
|
u64 TSCElapsedExclusive; // NOTE(casey): Does NOT include children
|
||||||
|
u64 TSCElapsedInclusive; // NOTE(casey): DOES include children
|
||||||
|
u64 HitCount;
|
||||||
|
u64 ProcessedByteCount;
|
||||||
|
char const *Label;
|
||||||
|
};
|
||||||
|
static profile_anchor GlobalProfilerAnchors[4096];
|
||||||
|
static u32 GlobalProfilerParent;
|
||||||
|
|
||||||
|
struct profile_block
|
||||||
|
{
|
||||||
|
profile_block(char const *Label_, u32 AnchorIndex_, u64 ByteCount)
|
||||||
|
{
|
||||||
|
ParentIndex = GlobalProfilerParent;
|
||||||
|
|
||||||
|
AnchorIndex = AnchorIndex_;
|
||||||
|
Label = Label_;
|
||||||
|
|
||||||
|
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
|
||||||
|
OldTSCElapsedInclusive = Anchor->TSCElapsedInclusive;
|
||||||
|
Anchor->ProcessedByteCount += ByteCount;
|
||||||
|
|
||||||
|
GlobalProfilerParent = AnchorIndex;
|
||||||
|
StartTSC = READ_BLOCK_TIMER();
|
||||||
|
}
|
||||||
|
|
||||||
|
~profile_block(void)
|
||||||
|
{
|
||||||
|
u64 Elapsed = READ_BLOCK_TIMER() - StartTSC;
|
||||||
|
GlobalProfilerParent = ParentIndex;
|
||||||
|
|
||||||
|
profile_anchor *Parent = GlobalProfilerAnchors + ParentIndex;
|
||||||
|
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
|
||||||
|
|
||||||
|
Parent->TSCElapsedExclusive -= Elapsed;
|
||||||
|
Anchor->TSCElapsedExclusive += Elapsed;
|
||||||
|
Anchor->TSCElapsedInclusive = OldTSCElapsedInclusive + Elapsed;
|
||||||
|
++Anchor->HitCount;
|
||||||
|
|
||||||
|
/* NOTE(casey): This write happens every time solely because there is no
|
||||||
|
straightforward way in C++ to have the same ease-of-use. In a better programming
|
||||||
|
language, it would be simple to have the anchor points gathered and labeled at compile
|
||||||
|
time, and this repetative write would be eliminated. */
|
||||||
|
Anchor->Label = Label;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *Label;
|
||||||
|
u64 OldTSCElapsedInclusive;
|
||||||
|
u64 StartTSC;
|
||||||
|
u32 ParentIndex;
|
||||||
|
u32 AnchorIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NameConcat2(A, B) A##B
|
||||||
|
#define NameConcat(A, B) NameConcat2(A, B)
|
||||||
|
#define TimeBandwidth(Name, ByteCount) profile_block NameConcat(Block, __LINE__)(Name, __COUNTER__ + 1, ByteCount)
|
||||||
|
#define ProfilerEndOfCompilationUnit static_assert(__COUNTER__ < ArrayCount(GlobalProfilerAnchors), "Number of profile points exceeds size of profiler::Anchors array")
|
||||||
|
|
||||||
|
static void PrintTimeElapsed(u64 TotalTSCElapsed, u64 TimerFreq, profile_anchor *Anchor)
|
||||||
|
{
|
||||||
|
f64 Percent = 100.0 * ((f64)Anchor->TSCElapsedExclusive / (f64)TotalTSCElapsed);
|
||||||
|
printf(" %s[%llu]: %llu (%.2f%%", Anchor->Label, Anchor->HitCount, Anchor->TSCElapsedExclusive, Percent);
|
||||||
|
if(Anchor->TSCElapsedInclusive != Anchor->TSCElapsedExclusive)
|
||||||
|
{
|
||||||
|
f64 PercentWithChildren = 100.0 * ((f64)Anchor->TSCElapsedInclusive / (f64)TotalTSCElapsed);
|
||||||
|
printf(", %.2f%% w/children", PercentWithChildren);
|
||||||
|
}
|
||||||
|
printf(")");
|
||||||
|
|
||||||
|
if(Anchor->ProcessedByteCount)
|
||||||
|
{
|
||||||
|
f64 Megabyte = 1024.0f*1024.0f;
|
||||||
|
f64 Gigabyte = Megabyte*1024.0f;
|
||||||
|
|
||||||
|
f64 Seconds = (f64)Anchor->TSCElapsedInclusive / (f64)TimerFreq;
|
||||||
|
f64 BytesPerSecond = (f64)Anchor->ProcessedByteCount / Seconds;
|
||||||
|
f64 Megabytes = (f64)Anchor->ProcessedByteCount / (f64)Megabyte;
|
||||||
|
f64 GigabytesPerSecond = BytesPerSecond / Gigabyte;
|
||||||
|
|
||||||
|
printf(" %.3fmb at %.2fgb/s", Megabytes, GigabytesPerSecond);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintAnchorData(u64 TotalCPUElapsed, u64 TimerFreq)
|
||||||
|
{
|
||||||
|
for(u32 AnchorIndex = 0; AnchorIndex < ArrayCount(GlobalProfilerAnchors); ++AnchorIndex)
|
||||||
|
{
|
||||||
|
profile_anchor *Anchor = GlobalProfilerAnchors + AnchorIndex;
|
||||||
|
if(Anchor->TSCElapsedInclusive)
|
||||||
|
{
|
||||||
|
PrintTimeElapsed(TotalCPUElapsed, TimerFreq, Anchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define TimeBandwidth(...)
|
||||||
|
#define PrintAnchorData(...)
|
||||||
|
#define ProfilerEndOfCompilationUnit
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct profiler
|
||||||
|
{
|
||||||
|
u64 StartTSC;
|
||||||
|
u64 EndTSC;
|
||||||
|
};
|
||||||
|
static profiler GlobalProfiler;
|
||||||
|
|
||||||
|
#define TimeBlock(Name) TimeBandwidth(Name, 0)
|
||||||
|
#define TimeFunction TimeBlock(__func__)
|
||||||
|
|
||||||
|
static u64 EstimateBlockTimerFreq(void)
|
||||||
|
{
|
||||||
|
(void)&EstimateCPUTimerFreq; // NOTE(casey): This has to be voided here to prevent compilers from warning us that it is not used
|
||||||
|
|
||||||
|
u64 MillisecondsToWait = 100;
|
||||||
|
u64 OSFreq = GetOSTimerFreq();
|
||||||
|
|
||||||
|
u64 BlockStart = READ_BLOCK_TIMER();
|
||||||
|
u64 OSStart = ReadOSTimer();
|
||||||
|
u64 OSEnd = 0;
|
||||||
|
u64 OSElapsed = 0;
|
||||||
|
u64 OSWaitTime = OSFreq * MillisecondsToWait / 1000;
|
||||||
|
while(OSElapsed < OSWaitTime)
|
||||||
|
{
|
||||||
|
OSEnd = ReadOSTimer();
|
||||||
|
OSElapsed = OSEnd - OSStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 BlockEnd = READ_BLOCK_TIMER();
|
||||||
|
u64 BlockElapsed = BlockEnd - BlockStart;
|
||||||
|
|
||||||
|
u64 BlockFreq = 0;
|
||||||
|
if(OSElapsed)
|
||||||
|
{
|
||||||
|
BlockFreq = OSFreq * BlockElapsed / OSElapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BlockFreq;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BeginProfile(void)
|
||||||
|
{
|
||||||
|
GlobalProfiler.StartTSC = READ_BLOCK_TIMER();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EndAndPrintProfile()
|
||||||
|
{
|
||||||
|
GlobalProfiler.EndTSC = READ_BLOCK_TIMER();
|
||||||
|
u64 TimerFreq = EstimateBlockTimerFreq();
|
||||||
|
|
||||||
|
u64 TotalTSCElapsed = GlobalProfiler.EndTSC - GlobalProfiler.StartTSC;
|
||||||
|
|
||||||
|
if(TimerFreq)
|
||||||
|
{
|
||||||
|
printf("\nTotal time: %0.4fms (timer freq %llu)\n", 1000.0 * (f64)TotalTSCElapsed / (f64)TimerFreq, TimerFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintAnchorData(TotalTSCElapsed, TimerFreq);
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 101
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
struct haversine_pair
|
||||||
|
{
|
||||||
|
f64 X0, Y0;
|
||||||
|
f64 X1, Y1;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PROFILER 1
|
||||||
|
#include "listing_0100_bandwidth_profiler.cpp"
|
||||||
|
#include "listing_0065_haversine_formula.cpp"
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0094_profiled_lookup_json_parser.cpp"
|
||||||
|
|
||||||
|
static buffer ReadEntireFile(char *FileName)
|
||||||
|
{
|
||||||
|
TimeFunction;
|
||||||
|
|
||||||
|
buffer Result = {};
|
||||||
|
|
||||||
|
FILE *File = fopen(FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Result = AllocateBuffer(Stat.st_size);
|
||||||
|
if(Result.Data)
|
||||||
|
{
|
||||||
|
TimeBandwidth("fread", Result.Count);
|
||||||
|
if(fread(Result.Data, Result.Count, 1, File) != 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to read \"%s\".\n", FileName);
|
||||||
|
FreeBuffer(&Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(File);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to open \"%s\".\n", FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f64 SumHaversineDistances(u64 PairCount, haversine_pair *Pairs)
|
||||||
|
{
|
||||||
|
TimeBandwidth(__func__, PairCount*sizeof(haversine_pair));
|
||||||
|
|
||||||
|
f64 Sum = 0;
|
||||||
|
|
||||||
|
f64 SumCoef = 1 / (f64)PairCount;
|
||||||
|
for(u64 PairIndex = 0; PairIndex < PairCount; ++PairIndex)
|
||||||
|
{
|
||||||
|
haversine_pair Pair = Pairs[PairIndex];
|
||||||
|
f64 EarthRadius = 6372.8;
|
||||||
|
f64 Dist = ReferenceHaversine(Pair.X0, Pair.Y0, Pair.X1, Pair.Y1, EarthRadius);
|
||||||
|
Sum += SumCoef*Dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
BeginProfile();
|
||||||
|
|
||||||
|
int Result = 1;
|
||||||
|
|
||||||
|
if((ArgCount == 2) || (ArgCount == 3))
|
||||||
|
{
|
||||||
|
buffer InputJSON = ReadEntireFile(Args[1]);
|
||||||
|
|
||||||
|
u32 MinimumJSONPairEncoding = 6*4;
|
||||||
|
u64 MaxPairCount = InputJSON.Count / MinimumJSONPairEncoding;
|
||||||
|
if(MaxPairCount)
|
||||||
|
{
|
||||||
|
buffer ParsedValues = AllocateBuffer(MaxPairCount * sizeof(haversine_pair));
|
||||||
|
if(ParsedValues.Count)
|
||||||
|
{
|
||||||
|
haversine_pair *Pairs = (haversine_pair *)ParsedValues.Data;
|
||||||
|
|
||||||
|
u64 PairCount = ParseHaversinePairs(InputJSON, MaxPairCount, Pairs);
|
||||||
|
f64 Sum = SumHaversineDistances(PairCount, Pairs);
|
||||||
|
|
||||||
|
Result = 0;
|
||||||
|
|
||||||
|
fprintf(stdout, "Input size: %llu\n", InputJSON.Count);
|
||||||
|
fprintf(stdout, "Pair count: %llu\n", PairCount);
|
||||||
|
fprintf(stdout, "Haversine sum: %.16f\n", Sum);
|
||||||
|
|
||||||
|
if(ArgCount == 3)
|
||||||
|
{
|
||||||
|
buffer AnswersF64 = ReadEntireFile(Args[2]);
|
||||||
|
if(AnswersF64.Count >= sizeof(f64))
|
||||||
|
{
|
||||||
|
f64 *AnswerValues = (f64 *)AnswersF64.Data;
|
||||||
|
|
||||||
|
fprintf(stdout, "\nValidation:\n");
|
||||||
|
|
||||||
|
u64 RefAnswerCount = (AnswersF64.Count - sizeof(f64)) / sizeof(f64);
|
||||||
|
if(PairCount != RefAnswerCount)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "FAILED - pair count doesn't match %llu.\n", RefAnswerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 RefSum = AnswerValues[RefAnswerCount];
|
||||||
|
fprintf(stdout, "Reference sum: %.16f\n", RefSum);
|
||||||
|
fprintf(stdout, "Difference: %.16f\n", Sum - RefSum);
|
||||||
|
|
||||||
|
fprintf(stdout, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&ParsedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Malformed input JSON\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeBuffer(&InputJSON);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [haversine_input.json]\n", Args[0]);
|
||||||
|
fprintf(stderr, " %s [haversine_input.json] [answers.f64]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Result == 0)
|
||||||
|
{
|
||||||
|
EndAndPrintProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilerEndOfCompilationUnit;
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 102
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <io.h>
|
||||||
|
|
||||||
|
struct read_parameters
|
||||||
|
{
|
||||||
|
buffer Dest;
|
||||||
|
char const *FileName;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void read_overhead_test_func(repetition_tester *Tester, read_parameters *Params);
|
||||||
|
|
||||||
|
static void ReadViaFRead(repetition_tester *Tester, read_parameters *Params)
|
||||||
|
{
|
||||||
|
while(IsTesting(Tester))
|
||||||
|
{
|
||||||
|
FILE *File = fopen(Params->FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
buffer DestBuffer = Params->Dest;
|
||||||
|
|
||||||
|
BeginTime(Tester);
|
||||||
|
size_t Result = fread(DestBuffer.Data, DestBuffer.Count, 1, File);
|
||||||
|
EndTime(Tester);
|
||||||
|
|
||||||
|
if(Result == 1)
|
||||||
|
{
|
||||||
|
CountBytes(Tester, DestBuffer.Count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Tester, "fread failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(File);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Tester, "fopen failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ReadViaRead(repetition_tester *Tester, read_parameters *Params)
|
||||||
|
{
|
||||||
|
while(IsTesting(Tester))
|
||||||
|
{
|
||||||
|
int File = _open(Params->FileName, _O_BINARY|_O_RDONLY);
|
||||||
|
if(File != -1)
|
||||||
|
{
|
||||||
|
buffer DestBuffer = Params->Dest;
|
||||||
|
|
||||||
|
u8 *Dest = DestBuffer.Data;
|
||||||
|
u64 SizeRemaining = DestBuffer.Count;
|
||||||
|
while(SizeRemaining)
|
||||||
|
{
|
||||||
|
u32 ReadSize = INT_MAX;
|
||||||
|
if((u64)ReadSize > SizeRemaining)
|
||||||
|
{
|
||||||
|
ReadSize = (u32)SizeRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginTime(Tester);
|
||||||
|
int Result = _read(File, Dest, ReadSize);
|
||||||
|
EndTime(Tester);
|
||||||
|
|
||||||
|
if(Result == (int)ReadSize)
|
||||||
|
{
|
||||||
|
CountBytes(Tester, ReadSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Tester, "_read failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeRemaining -= ReadSize;
|
||||||
|
Dest += ReadSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
_close(File);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Tester, "_open failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ReadViaReadFile(repetition_tester *Tester, read_parameters *Params)
|
||||||
|
{
|
||||||
|
while(IsTesting(Tester))
|
||||||
|
{
|
||||||
|
HANDLE File = CreateFileA(Params->FileName, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, 0,
|
||||||
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
|
||||||
|
if(File != INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
buffer DestBuffer = Params->Dest;
|
||||||
|
|
||||||
|
u64 SizeRemaining = Params->Dest.Count;
|
||||||
|
u8 *Dest = (u8 *)DestBuffer.Data;
|
||||||
|
while(SizeRemaining)
|
||||||
|
{
|
||||||
|
u32 ReadSize = (u32)-1;
|
||||||
|
if((u64)ReadSize > SizeRemaining)
|
||||||
|
{
|
||||||
|
ReadSize = (u32)SizeRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD BytesRead = 0;
|
||||||
|
BeginTime(Tester);
|
||||||
|
BOOL Result = ReadFile(File, Dest, ReadSize, &BytesRead, 0);
|
||||||
|
EndTime(Tester);
|
||||||
|
|
||||||
|
if(Result && (BytesRead == ReadSize))
|
||||||
|
{
|
||||||
|
CountBytes(Tester, ReadSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Tester, "ReadFile failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeRemaining -= ReadSize;
|
||||||
|
Dest += ReadSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(File);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Tester, "CreateFileA failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 103
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
enum test_mode : u32
|
||||||
|
{
|
||||||
|
TestMode_Uninitialized,
|
||||||
|
TestMode_Testing,
|
||||||
|
TestMode_Completed,
|
||||||
|
TestMode_Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct repetition_test_results
|
||||||
|
{
|
||||||
|
u64 TestCount;
|
||||||
|
u64 TotalTime;
|
||||||
|
u64 MaxTime;
|
||||||
|
u64 MinTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct repetition_tester
|
||||||
|
{
|
||||||
|
u64 TargetProcessedByteCount;
|
||||||
|
u64 CPUTimerFreq;
|
||||||
|
u64 TryForTime;
|
||||||
|
u64 TestsStartedAt;
|
||||||
|
|
||||||
|
test_mode Mode;
|
||||||
|
b32 PrintNewMinimums;
|
||||||
|
u32 OpenBlockCount;
|
||||||
|
u32 CloseBlockCount;
|
||||||
|
u64 TimeAccumulatedOnThisTest;
|
||||||
|
u64 BytesAccumulatedOnThisTest;
|
||||||
|
|
||||||
|
repetition_test_results Results;
|
||||||
|
};
|
||||||
|
|
||||||
|
static f64 SecondsFromCPUTime(f64 CPUTime, u64 CPUTimerFreq)
|
||||||
|
{
|
||||||
|
f64 Result = 0.0;
|
||||||
|
if(CPUTimerFreq)
|
||||||
|
{
|
||||||
|
Result = (CPUTime / (f64)CPUTimerFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintTime(char const *Label, f64 CPUTime, u64 CPUTimerFreq, u64 ByteCount)
|
||||||
|
{
|
||||||
|
printf("%s: %.0f", Label, CPUTime);
|
||||||
|
if(CPUTimerFreq)
|
||||||
|
{
|
||||||
|
f64 Seconds = SecondsFromCPUTime(CPUTime, CPUTimerFreq);
|
||||||
|
printf(" (%fms)", 1000.0f*Seconds);
|
||||||
|
|
||||||
|
if(ByteCount)
|
||||||
|
{
|
||||||
|
f64 Gigabyte = (1024.0f * 1024.0f * 1024.0f);
|
||||||
|
f64 BestBandwidth = ByteCount / (Gigabyte * Seconds);
|
||||||
|
printf(" %fgb/s", BestBandwidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintTime(char const *Label, u64 CPUTime, u64 CPUTimerFreq, u64 ByteCount)
|
||||||
|
{
|
||||||
|
PrintTime(Label, (f64)CPUTime, CPUTimerFreq, ByteCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintResults(repetition_test_results Results, u64 CPUTimerFreq, u64 ByteCount)
|
||||||
|
{
|
||||||
|
PrintTime("Min", (f64)Results.MinTime, CPUTimerFreq, ByteCount);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
PrintTime("Max", (f64)Results.MaxTime, CPUTimerFreq, ByteCount);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
if(Results.TestCount)
|
||||||
|
{
|
||||||
|
PrintTime("Avg", (f64)Results.TotalTime / (f64)Results.TestCount, CPUTimerFreq, ByteCount);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Error(repetition_tester *Tester, char const *Message)
|
||||||
|
{
|
||||||
|
Tester->Mode = TestMode_Error;
|
||||||
|
fprintf(stderr, "ERROR: %s\n", Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void NewTestWave(repetition_tester *Tester, u64 TargetProcessedByteCount, u64 CPUTimerFreq, u32 SecondsToTry = 10)
|
||||||
|
{
|
||||||
|
if(Tester->Mode == TestMode_Uninitialized)
|
||||||
|
{
|
||||||
|
Tester->Mode = TestMode_Testing;
|
||||||
|
Tester->TargetProcessedByteCount = TargetProcessedByteCount;
|
||||||
|
Tester->CPUTimerFreq = CPUTimerFreq;
|
||||||
|
Tester->PrintNewMinimums = true;
|
||||||
|
Tester->Results.MinTime = (u64)-1;
|
||||||
|
}
|
||||||
|
else if(Tester->Mode == TestMode_Completed)
|
||||||
|
{
|
||||||
|
Tester->Mode = TestMode_Testing;
|
||||||
|
|
||||||
|
if(Tester->TargetProcessedByteCount != TargetProcessedByteCount)
|
||||||
|
{
|
||||||
|
Error(Tester, "TargetProcessedByteCount changed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Tester->CPUTimerFreq != CPUTimerFreq)
|
||||||
|
{
|
||||||
|
Error(Tester, "CPU frequency changed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tester->TryForTime = SecondsToTry*CPUTimerFreq;
|
||||||
|
Tester->TestsStartedAt = ReadCPUTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BeginTime(repetition_tester *Tester)
|
||||||
|
{
|
||||||
|
++Tester->OpenBlockCount;
|
||||||
|
Tester->TimeAccumulatedOnThisTest -= ReadCPUTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EndTime(repetition_tester *Tester)
|
||||||
|
{
|
||||||
|
++Tester->CloseBlockCount;
|
||||||
|
Tester->TimeAccumulatedOnThisTest += ReadCPUTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CountBytes(repetition_tester *Tester, u64 ByteCount)
|
||||||
|
{
|
||||||
|
Tester->BytesAccumulatedOnThisTest += ByteCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsTesting(repetition_tester *Tester)
|
||||||
|
{
|
||||||
|
if(Tester->Mode == TestMode_Testing)
|
||||||
|
{
|
||||||
|
u64 CurrentTime = ReadCPUTimer();
|
||||||
|
|
||||||
|
if(Tester->OpenBlockCount) // NOTE(casey): We don't count tests that had no timing blocks - we assume they took some other path
|
||||||
|
{
|
||||||
|
if(Tester->OpenBlockCount != Tester->CloseBlockCount)
|
||||||
|
{
|
||||||
|
Error(Tester, "Unbalanced BeginTime/EndTime");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Tester->BytesAccumulatedOnThisTest != Tester->TargetProcessedByteCount)
|
||||||
|
{
|
||||||
|
Error(Tester, "Processed byte count mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Tester->Mode == TestMode_Testing)
|
||||||
|
{
|
||||||
|
repetition_test_results *Results = &Tester->Results;
|
||||||
|
u64 ElapsedTime = Tester->TimeAccumulatedOnThisTest;
|
||||||
|
Results->TestCount += 1;
|
||||||
|
Results->TotalTime += ElapsedTime;
|
||||||
|
if(Results->MaxTime < ElapsedTime)
|
||||||
|
{
|
||||||
|
Results->MaxTime = ElapsedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Results->MinTime > ElapsedTime)
|
||||||
|
{
|
||||||
|
Results->MinTime = ElapsedTime;
|
||||||
|
|
||||||
|
// NOTE(casey): Whenever we get a new minimum time, we reset the clock to the full trial time
|
||||||
|
Tester->TestsStartedAt = CurrentTime;
|
||||||
|
|
||||||
|
if(Tester->PrintNewMinimums)
|
||||||
|
{
|
||||||
|
PrintTime("Min", Results->MinTime, Tester->CPUTimerFreq, Tester->BytesAccumulatedOnThisTest);
|
||||||
|
printf(" \r");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tester->OpenBlockCount = 0;
|
||||||
|
Tester->CloseBlockCount = 0;
|
||||||
|
Tester->TimeAccumulatedOnThisTest = 0;
|
||||||
|
Tester->BytesAccumulatedOnThisTest = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if((CurrentTime - Tester->TestsStartedAt) > Tester->TryForTime)
|
||||||
|
{
|
||||||
|
Tester->Mode = TestMode_Completed;
|
||||||
|
|
||||||
|
printf(" \r");
|
||||||
|
PrintResults(Tester->Results, Tester->CPUTimerFreq, Tester->TargetProcessedByteCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b32 Result = (Tester->Mode == TestMode_Testing);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 104
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0074_platform_metrics.cpp"
|
||||||
|
#include "listing_0103_repetition_tester.cpp"
|
||||||
|
#include "listing_0102_read_overhead_test.cpp"
|
||||||
|
|
||||||
|
struct test_function
|
||||||
|
{
|
||||||
|
char const *Name;
|
||||||
|
read_overhead_test_func *Func;
|
||||||
|
};
|
||||||
|
test_function TestFunctions[] =
|
||||||
|
{
|
||||||
|
{"fread", ReadViaFRead},
|
||||||
|
{"_read", ReadViaRead},
|
||||||
|
{"ReadFile", ReadViaReadFile},
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
|
||||||
|
// here to prevent the compiler from complaining about "unused functions".
|
||||||
|
(void)&IsInBounds;
|
||||||
|
(void)&AreEqual;
|
||||||
|
|
||||||
|
u64 CPUTimerFreq = EstimateCPUTimerFreq();
|
||||||
|
|
||||||
|
if(ArgCount == 2)
|
||||||
|
{
|
||||||
|
char *FileName = Args[1];
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
read_parameters Params = {};
|
||||||
|
Params.Dest = AllocateBuffer(Stat.st_size);
|
||||||
|
Params.FileName = FileName;
|
||||||
|
|
||||||
|
if(Params.Dest.Count > 0)
|
||||||
|
{
|
||||||
|
repetition_tester Testers[ArrayCount(TestFunctions)] = {};
|
||||||
|
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
|
||||||
|
{
|
||||||
|
repetition_tester *Tester = Testers + FuncIndex;
|
||||||
|
test_function TestFunc = TestFunctions[FuncIndex];
|
||||||
|
|
||||||
|
printf("\n--- %s ---\n", TestFunc.Name);
|
||||||
|
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
|
||||||
|
TestFunc.Func(Tester, &Params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
|
||||||
|
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
|
||||||
|
(void)&FreeBuffer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 105
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0074_platform_metrics.cpp"
|
||||||
|
#include "listing_0103_repetition_tester.cpp"
|
||||||
|
#include "listing_0102_read_overhead_test.cpp"
|
||||||
|
|
||||||
|
struct test_function
|
||||||
|
{
|
||||||
|
char const *Name;
|
||||||
|
read_overhead_test_func *Func;
|
||||||
|
};
|
||||||
|
test_function TestFunctions[] =
|
||||||
|
{
|
||||||
|
{"ReadFile", ReadViaReadFile},
|
||||||
|
{"_read", ReadViaRead},
|
||||||
|
{"fread", ReadViaFRead},
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
|
||||||
|
// here to prevent the compiler from complaining about "unused functions".
|
||||||
|
(void)&IsInBounds;
|
||||||
|
(void)&AreEqual;
|
||||||
|
|
||||||
|
u64 CPUTimerFreq = EstimateCPUTimerFreq();
|
||||||
|
|
||||||
|
if(ArgCount == 2)
|
||||||
|
{
|
||||||
|
char *FileName = Args[1];
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
read_parameters Params = {};
|
||||||
|
Params.Dest = AllocateBuffer(Stat.st_size);
|
||||||
|
Params.FileName = FileName;
|
||||||
|
|
||||||
|
if(Params.Dest.Count > 0)
|
||||||
|
{
|
||||||
|
repetition_tester Testers[ArrayCount(TestFunctions)] = {};
|
||||||
|
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
|
||||||
|
{
|
||||||
|
repetition_tester *Tester = Testers + FuncIndex;
|
||||||
|
test_function TestFunc = TestFunctions[FuncIndex];
|
||||||
|
|
||||||
|
printf("\n--- %s ---\n", TestFunc.Name);
|
||||||
|
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
|
||||||
|
TestFunc.Func(Tester, &Params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
|
||||||
|
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
|
||||||
|
(void)&FreeBuffer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 106
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <io.h>
|
||||||
|
|
||||||
|
enum allocation_type
|
||||||
|
{
|
||||||
|
AllocType_none,
|
||||||
|
AllocType_malloc,
|
||||||
|
|
||||||
|
AllocType_Count,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct read_parameters
|
||||||
|
{
|
||||||
|
allocation_type AllocType;
|
||||||
|
buffer Dest;
|
||||||
|
char const *FileName;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void read_overhead_test_func(repetition_tester *Tester, read_parameters *Params);
|
||||||
|
|
||||||
|
static char const *DescribeAllocationType(allocation_type AllocType)
|
||||||
|
{
|
||||||
|
char const *Result;
|
||||||
|
switch(AllocType)
|
||||||
|
{
|
||||||
|
case AllocType_none: {Result = "";} break;
|
||||||
|
case AllocType_malloc: {Result = "malloc";} break;
|
||||||
|
default : {Result = "UNKNOWN";} break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HandleAllocation(read_parameters *Params, buffer *Buffer)
|
||||||
|
{
|
||||||
|
switch(Params->AllocType)
|
||||||
|
{
|
||||||
|
case AllocType_none:
|
||||||
|
{
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case AllocType_malloc:
|
||||||
|
{
|
||||||
|
*Buffer = AllocateBuffer(Params->Dest.Count);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unrecognized allocation type");
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HandleDeallocation(read_parameters *Params, buffer *Buffer)
|
||||||
|
{
|
||||||
|
switch(Params->AllocType)
|
||||||
|
{
|
||||||
|
case AllocType_none:
|
||||||
|
{
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case AllocType_malloc:
|
||||||
|
{
|
||||||
|
FreeBuffer(Buffer);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unrecognized allocation type");
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ReadViaFRead(repetition_tester *Tester, read_parameters *Params)
|
||||||
|
{
|
||||||
|
while(IsTesting(Tester))
|
||||||
|
{
|
||||||
|
FILE *File = fopen(Params->FileName, "rb");
|
||||||
|
if(File)
|
||||||
|
{
|
||||||
|
buffer DestBuffer = Params->Dest;
|
||||||
|
HandleAllocation(Params, &DestBuffer);
|
||||||
|
|
||||||
|
BeginTime(Tester);
|
||||||
|
size_t Result = fread(DestBuffer.Data, DestBuffer.Count, 1, File);
|
||||||
|
EndTime(Tester);
|
||||||
|
|
||||||
|
if(Result == 1)
|
||||||
|
{
|
||||||
|
CountBytes(Tester, DestBuffer.Count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Tester, "fread failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleDeallocation(Params, &DestBuffer);
|
||||||
|
fclose(File);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Tester, "fopen failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ReadViaRead(repetition_tester *Tester, read_parameters *Params)
|
||||||
|
{
|
||||||
|
while(IsTesting(Tester))
|
||||||
|
{
|
||||||
|
int File = _open(Params->FileName, _O_BINARY|_O_RDONLY);
|
||||||
|
if(File != -1)
|
||||||
|
{
|
||||||
|
buffer DestBuffer = Params->Dest;
|
||||||
|
HandleAllocation(Params, &DestBuffer);
|
||||||
|
|
||||||
|
u8 *Dest = DestBuffer.Data;
|
||||||
|
u64 SizeRemaining = DestBuffer.Count;
|
||||||
|
while(SizeRemaining)
|
||||||
|
{
|
||||||
|
u32 ReadSize = INT_MAX;
|
||||||
|
if((u64)ReadSize > SizeRemaining)
|
||||||
|
{
|
||||||
|
ReadSize = (u32)SizeRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginTime(Tester);
|
||||||
|
int Result = _read(File, Dest, ReadSize);
|
||||||
|
EndTime(Tester);
|
||||||
|
|
||||||
|
if(Result == (int)ReadSize)
|
||||||
|
{
|
||||||
|
CountBytes(Tester, ReadSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Tester, "_read failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeRemaining -= ReadSize;
|
||||||
|
Dest += ReadSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleDeallocation(Params, &DestBuffer);
|
||||||
|
_close(File);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Tester, "_open failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ReadViaReadFile(repetition_tester *Tester, read_parameters *Params)
|
||||||
|
{
|
||||||
|
while(IsTesting(Tester))
|
||||||
|
{
|
||||||
|
HANDLE File = CreateFileA(Params->FileName, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, 0,
|
||||||
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
|
||||||
|
if(File != INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
buffer DestBuffer = Params->Dest;
|
||||||
|
HandleAllocation(Params, &DestBuffer);
|
||||||
|
|
||||||
|
u64 SizeRemaining = Params->Dest.Count;
|
||||||
|
u8 *Dest = (u8 *)DestBuffer.Data;
|
||||||
|
while(SizeRemaining)
|
||||||
|
{
|
||||||
|
u32 ReadSize = (u32)-1;
|
||||||
|
if((u64)ReadSize > SizeRemaining)
|
||||||
|
{
|
||||||
|
ReadSize = (u32)SizeRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD BytesRead = 0;
|
||||||
|
BeginTime(Tester);
|
||||||
|
BOOL Result = ReadFile(File, Dest, ReadSize, &BytesRead, 0);
|
||||||
|
EndTime(Tester);
|
||||||
|
|
||||||
|
if(Result && (BytesRead == ReadSize))
|
||||||
|
{
|
||||||
|
CountBytes(Tester, ReadSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Tester, "ReadFile failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeRemaining -= ReadSize;
|
||||||
|
Dest += ReadSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleDeallocation(Params, &DestBuffer);
|
||||||
|
CloseHandle(File);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Error(Tester, "CreateFileA failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 107
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0074_platform_metrics.cpp"
|
||||||
|
#include "listing_0103_repetition_tester.cpp"
|
||||||
|
#include "listing_0106_mallocread_overhead_test.cpp"
|
||||||
|
|
||||||
|
struct test_function
|
||||||
|
{
|
||||||
|
char const *Name;
|
||||||
|
read_overhead_test_func *Func;
|
||||||
|
};
|
||||||
|
test_function TestFunctions[] =
|
||||||
|
{
|
||||||
|
{"fread", ReadViaFRead},
|
||||||
|
{"_read", ReadViaRead},
|
||||||
|
{"ReadFile", ReadViaReadFile},
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
|
||||||
|
// here to prevent the compiler from complaining about "unused functions".
|
||||||
|
(void)&IsInBounds;
|
||||||
|
(void)&AreEqual;
|
||||||
|
|
||||||
|
u64 CPUTimerFreq = EstimateCPUTimerFreq();
|
||||||
|
|
||||||
|
if(ArgCount == 2)
|
||||||
|
{
|
||||||
|
char *FileName = Args[1];
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
read_parameters Params = {};
|
||||||
|
Params.Dest = AllocateBuffer(Stat.st_size);
|
||||||
|
Params.FileName = FileName;
|
||||||
|
|
||||||
|
if(Params.Dest.Count > 0)
|
||||||
|
{
|
||||||
|
repetition_tester Testers[ArrayCount(TestFunctions)][AllocType_Count] = {};
|
||||||
|
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
|
||||||
|
{
|
||||||
|
for(u32 AllocType = 0; AllocType < AllocType_Count; ++AllocType)
|
||||||
|
{
|
||||||
|
Params.AllocType = (allocation_type)AllocType;
|
||||||
|
|
||||||
|
repetition_tester *Tester = &Testers[FuncIndex][AllocType];
|
||||||
|
test_function TestFunc = TestFunctions[FuncIndex];
|
||||||
|
|
||||||
|
printf("\n--- %s%s%s ---\n",
|
||||||
|
DescribeAllocationType(Params.AllocType),
|
||||||
|
Params.AllocType ? " + " : "",
|
||||||
|
TestFunc.Name);
|
||||||
|
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
|
||||||
|
TestFunc.Func(Tester, &Params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
|
||||||
|
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
|
||||||
|
(void)&FreeBuffer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 108
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
|
||||||
|
#include <intrin.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <psapi.h>
|
||||||
|
|
||||||
|
typedef struct os_metrics
|
||||||
|
{
|
||||||
|
b32 Initialized;
|
||||||
|
HANDLE ProcessHandle;
|
||||||
|
} os_metrics;
|
||||||
|
static os_metrics GlobalMetrics;
|
||||||
|
|
||||||
|
static u64 GetOSTimerFreq(void)
|
||||||
|
{
|
||||||
|
LARGE_INTEGER Freq;
|
||||||
|
QueryPerformanceFrequency(&Freq);
|
||||||
|
return Freq.QuadPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ReadOSTimer(void)
|
||||||
|
{
|
||||||
|
LARGE_INTEGER Value;
|
||||||
|
QueryPerformanceCounter(&Value);
|
||||||
|
return Value.QuadPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ReadOSPageFaultCount(void)
|
||||||
|
{
|
||||||
|
PROCESS_MEMORY_COUNTERS_EX MemoryCounters = {};
|
||||||
|
MemoryCounters.cb = sizeof(MemoryCounters);
|
||||||
|
GetProcessMemoryInfo(GlobalMetrics.ProcessHandle, (PROCESS_MEMORY_COUNTERS *)&MemoryCounters, sizeof(MemoryCounters));
|
||||||
|
|
||||||
|
u64 Result = MemoryCounters.PageFaultCount;
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void InitializeOSMetrics(void)
|
||||||
|
{
|
||||||
|
if(!GlobalMetrics.Initialized)
|
||||||
|
{
|
||||||
|
GlobalMetrics.Initialized = true;
|
||||||
|
GlobalMetrics.ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, GetCurrentProcessId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <x86intrin.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
static u64 GetOSTimerFreq(void)
|
||||||
|
{
|
||||||
|
return 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ReadOSTimer(void)
|
||||||
|
{
|
||||||
|
// NOTE(casey): The "struct" keyword is not necessary here when compiling in C++,
|
||||||
|
// but just in case anyone is using this file from C, I include it.
|
||||||
|
struct timeval Value;
|
||||||
|
gettimeofday(&Value, 0);
|
||||||
|
|
||||||
|
u64 Result = GetOSTimerFreq()*(u64)Value.tv_sec + (u64)Value.tv_usec;
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 ReadOSPageFaultCount(void)
|
||||||
|
{
|
||||||
|
// NOTE(casey): The course materials are not tested on MacOS/Linux.
|
||||||
|
// This code was contributed to the public github. It may or may not work
|
||||||
|
// for your system.
|
||||||
|
|
||||||
|
struct rusage Usage = {};
|
||||||
|
getrusage(RUSAGE_SELF, &Usage);
|
||||||
|
|
||||||
|
// ru_minflt the number of page faults serviced without any I/O activity.
|
||||||
|
// ru_majflt the number of page faults serviced that required I/O activity.
|
||||||
|
u64 Result = Usage.ru_minflt + Usage.ru_majflt;
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void InitializeOSMetrics(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* NOTE(casey): This does not need to be "inline", it could just be "static"
|
||||||
|
because compilers will inline it anyway. But compilers will warn about
|
||||||
|
static functions that aren't used. So "inline" is just the simplest way
|
||||||
|
to tell them to stop complaining about that. */
|
||||||
|
inline u64 ReadCPUTimer(void)
|
||||||
|
{
|
||||||
|
// NOTE(casey): If you were on ARM, you would need to replace __rdtsc
|
||||||
|
// with one of their performance counter read instructions, depending
|
||||||
|
// on which ones are available on your platform.
|
||||||
|
|
||||||
|
return __rdtsc();
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 EstimateCPUTimerFreq(void)
|
||||||
|
{
|
||||||
|
u64 MillisecondsToWait = 100;
|
||||||
|
u64 OSFreq = GetOSTimerFreq();
|
||||||
|
|
||||||
|
u64 CPUStart = ReadCPUTimer();
|
||||||
|
u64 OSStart = ReadOSTimer();
|
||||||
|
u64 OSEnd = 0;
|
||||||
|
u64 OSElapsed = 0;
|
||||||
|
u64 OSWaitTime = OSFreq * MillisecondsToWait / 1000;
|
||||||
|
while(OSElapsed < OSWaitTime)
|
||||||
|
{
|
||||||
|
OSEnd = ReadOSTimer();
|
||||||
|
OSElapsed = OSEnd - OSStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 CPUEnd = ReadCPUTimer();
|
||||||
|
u64 CPUElapsed = CPUEnd - CPUStart;
|
||||||
|
|
||||||
|
u64 CPUFreq = 0;
|
||||||
|
if(OSElapsed)
|
||||||
|
{
|
||||||
|
CPUFreq = OSFreq * CPUElapsed / OSElapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CPUFreq;
|
||||||
|
}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 109
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
enum test_mode : u32
|
||||||
|
{
|
||||||
|
TestMode_Uninitialized,
|
||||||
|
TestMode_Testing,
|
||||||
|
TestMode_Completed,
|
||||||
|
TestMode_Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum repetition_value_type
|
||||||
|
{
|
||||||
|
RepValue_TestCount,
|
||||||
|
|
||||||
|
RepValue_CPUTimer,
|
||||||
|
RepValue_MemPageFaults,
|
||||||
|
RepValue_ByteCount,
|
||||||
|
|
||||||
|
RepValue_Count,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct repetition_value
|
||||||
|
{
|
||||||
|
u64 E[RepValue_Count];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct repetition_test_results
|
||||||
|
{
|
||||||
|
repetition_value Total;
|
||||||
|
repetition_value Min;
|
||||||
|
repetition_value Max;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct repetition_tester
|
||||||
|
{
|
||||||
|
u64 TargetProcessedByteCount;
|
||||||
|
u64 CPUTimerFreq;
|
||||||
|
u64 TryForTime;
|
||||||
|
u64 TestsStartedAt;
|
||||||
|
|
||||||
|
test_mode Mode;
|
||||||
|
b32 PrintNewMinimums;
|
||||||
|
u32 OpenBlockCount;
|
||||||
|
u32 CloseBlockCount;
|
||||||
|
|
||||||
|
repetition_value AccumulatedOnThisTest;
|
||||||
|
repetition_test_results Results;
|
||||||
|
};
|
||||||
|
|
||||||
|
static f64 SecondsFromCPUTime(f64 CPUTime, u64 CPUTimerFreq)
|
||||||
|
{
|
||||||
|
f64 Result = 0.0;
|
||||||
|
if(CPUTimerFreq)
|
||||||
|
{
|
||||||
|
Result = (CPUTime / (f64)CPUTimerFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintValue(char const *Label, repetition_value Value, u64 CPUTimerFreq)
|
||||||
|
{
|
||||||
|
u64 TestCount = Value.E[RepValue_TestCount];
|
||||||
|
f64 Divisor = TestCount ? (f64)TestCount : 1;
|
||||||
|
|
||||||
|
f64 E[RepValue_Count];
|
||||||
|
for(u32 EIndex = 0; EIndex < ArrayCount(E); ++EIndex)
|
||||||
|
{
|
||||||
|
E[EIndex] = (f64)Value.E[EIndex] / Divisor;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%s: %.0f", Label, E[RepValue_CPUTimer]);
|
||||||
|
if(CPUTimerFreq)
|
||||||
|
{
|
||||||
|
f64 Seconds = SecondsFromCPUTime(E[RepValue_CPUTimer], CPUTimerFreq);
|
||||||
|
printf(" (%fms)", 1000.0f*Seconds);
|
||||||
|
|
||||||
|
if(E[RepValue_ByteCount] > 0)
|
||||||
|
{
|
||||||
|
f64 Gigabyte = (1024.0f * 1024.0f * 1024.0f);
|
||||||
|
f64 Bandwidth = E[RepValue_ByteCount] / (Gigabyte * Seconds);
|
||||||
|
printf(" %fgb/s", Bandwidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(E[RepValue_MemPageFaults] > 0)
|
||||||
|
{
|
||||||
|
printf(" PF: %0.4f (%0.4fk/fault)", E[RepValue_MemPageFaults], E[RepValue_ByteCount] / (E[RepValue_MemPageFaults] * 1024.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintResults(repetition_test_results Results, u64 CPUTimerFreq)
|
||||||
|
{
|
||||||
|
PrintValue("Min", Results.Min, CPUTimerFreq);
|
||||||
|
printf("\n");
|
||||||
|
PrintValue("Max", Results.Max, CPUTimerFreq);
|
||||||
|
printf("\n");
|
||||||
|
PrintValue("Avg", Results.Total, CPUTimerFreq);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Error(repetition_tester *Tester, char const *Message)
|
||||||
|
{
|
||||||
|
Tester->Mode = TestMode_Error;
|
||||||
|
fprintf(stderr, "ERROR: %s\n", Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void NewTestWave(repetition_tester *Tester, u64 TargetProcessedByteCount, u64 CPUTimerFreq, u32 SecondsToTry = 10)
|
||||||
|
{
|
||||||
|
if(Tester->Mode == TestMode_Uninitialized)
|
||||||
|
{
|
||||||
|
Tester->Mode = TestMode_Testing;
|
||||||
|
Tester->TargetProcessedByteCount = TargetProcessedByteCount;
|
||||||
|
Tester->CPUTimerFreq = CPUTimerFreq;
|
||||||
|
Tester->PrintNewMinimums = true;
|
||||||
|
Tester->Results.Min.E[RepValue_CPUTimer] = (u64)-1;
|
||||||
|
}
|
||||||
|
else if(Tester->Mode == TestMode_Completed)
|
||||||
|
{
|
||||||
|
Tester->Mode = TestMode_Testing;
|
||||||
|
|
||||||
|
if(Tester->TargetProcessedByteCount != TargetProcessedByteCount)
|
||||||
|
{
|
||||||
|
Error(Tester, "TargetProcessedByteCount changed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Tester->CPUTimerFreq != CPUTimerFreq)
|
||||||
|
{
|
||||||
|
Error(Tester, "CPU frequency changed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tester->TryForTime = SecondsToTry*CPUTimerFreq;
|
||||||
|
Tester->TestsStartedAt = ReadCPUTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BeginTime(repetition_tester *Tester)
|
||||||
|
{
|
||||||
|
++Tester->OpenBlockCount;
|
||||||
|
|
||||||
|
repetition_value *Accum = &Tester->AccumulatedOnThisTest;
|
||||||
|
Accum->E[RepValue_MemPageFaults] -= ReadOSPageFaultCount();
|
||||||
|
Accum->E[RepValue_CPUTimer] -= ReadCPUTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void EndTime(repetition_tester *Tester)
|
||||||
|
{
|
||||||
|
repetition_value *Accum = &Tester->AccumulatedOnThisTest;
|
||||||
|
Accum->E[RepValue_CPUTimer] += ReadCPUTimer();
|
||||||
|
Accum->E[RepValue_MemPageFaults] += ReadOSPageFaultCount();
|
||||||
|
|
||||||
|
++Tester->CloseBlockCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CountBytes(repetition_tester *Tester, u64 ByteCount)
|
||||||
|
{
|
||||||
|
repetition_value *Accum = &Tester->AccumulatedOnThisTest;
|
||||||
|
Accum->E[RepValue_ByteCount] += ByteCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 IsTesting(repetition_tester *Tester)
|
||||||
|
{
|
||||||
|
if(Tester->Mode == TestMode_Testing)
|
||||||
|
{
|
||||||
|
repetition_value Accum = Tester->AccumulatedOnThisTest;
|
||||||
|
u64 CurrentTime = ReadCPUTimer();
|
||||||
|
|
||||||
|
if(Tester->OpenBlockCount) // NOTE(casey): We don't count tests that had no timing blocks - we assume they took some other path
|
||||||
|
{
|
||||||
|
if(Tester->OpenBlockCount != Tester->CloseBlockCount)
|
||||||
|
{
|
||||||
|
Error(Tester, "Unbalanced BeginTime/EndTime");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Accum.E[RepValue_ByteCount] != Tester->TargetProcessedByteCount)
|
||||||
|
{
|
||||||
|
Error(Tester, "Processed byte count mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Tester->Mode == TestMode_Testing)
|
||||||
|
{
|
||||||
|
repetition_test_results *Results = &Tester->Results;
|
||||||
|
|
||||||
|
Accum.E[RepValue_TestCount] = 1;
|
||||||
|
for(u32 EIndex = 0; EIndex < ArrayCount(Accum.E); ++EIndex)
|
||||||
|
{
|
||||||
|
Results->Total.E[EIndex] += Accum.E[EIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Results->Max.E[RepValue_CPUTimer] < Accum.E[RepValue_CPUTimer])
|
||||||
|
{
|
||||||
|
Results->Max = Accum;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Results->Min.E[RepValue_CPUTimer] > Accum.E[RepValue_CPUTimer])
|
||||||
|
{
|
||||||
|
Results->Min = Accum;
|
||||||
|
|
||||||
|
// NOTE(casey): Whenever we get a new minimum time, we reset the clock to the full trial time
|
||||||
|
Tester->TestsStartedAt = CurrentTime;
|
||||||
|
|
||||||
|
if(Tester->PrintNewMinimums)
|
||||||
|
{
|
||||||
|
PrintValue("Min", Results->Min, Tester->CPUTimerFreq);
|
||||||
|
printf(" \r");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tester->OpenBlockCount = 0;
|
||||||
|
Tester->CloseBlockCount = 0;
|
||||||
|
Tester->AccumulatedOnThisTest = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if((CurrentTime - Tester->TestsStartedAt) > Tester->TryForTime)
|
||||||
|
{
|
||||||
|
Tester->Mode = TestMode_Completed;
|
||||||
|
|
||||||
|
printf(" \r");
|
||||||
|
PrintResults(Tester->Results, Tester->CPUTimerFreq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b32 Result = (Tester->Mode == TestMode_Testing);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 110
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
static void WriteToAllBytes(repetition_tester *Tester, read_parameters *Params)
|
||||||
|
{
|
||||||
|
while(IsTesting(Tester))
|
||||||
|
{
|
||||||
|
buffer DestBuffer = Params->Dest;
|
||||||
|
HandleAllocation(Params, &DestBuffer);
|
||||||
|
|
||||||
|
BeginTime(Tester);
|
||||||
|
for(u64 Index = 0; Index < DestBuffer.Count; ++Index)
|
||||||
|
{
|
||||||
|
DestBuffer.Data[Index] = (u8)Index;
|
||||||
|
}
|
||||||
|
EndTime(Tester);
|
||||||
|
|
||||||
|
CountBytes(Tester, DestBuffer.Count);
|
||||||
|
|
||||||
|
HandleDeallocation(Params, &DestBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 111
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
#include "listing_0068_buffer.cpp"
|
||||||
|
#include "listing_0108_platform_metrics.cpp"
|
||||||
|
#include "listing_0109_pagefault_repetition_tester.cpp"
|
||||||
|
#include "listing_0106_mallocread_overhead_test.cpp"
|
||||||
|
#include "listing_0110_pagefault_overhead_test.cpp"
|
||||||
|
|
||||||
|
struct test_function
|
||||||
|
{
|
||||||
|
char const *Name;
|
||||||
|
read_overhead_test_func *Func;
|
||||||
|
};
|
||||||
|
test_function TestFunctions[] =
|
||||||
|
{
|
||||||
|
{"WriteToAllBytes", WriteToAllBytes},
|
||||||
|
{"fread", ReadViaFRead},
|
||||||
|
{"_read", ReadViaRead},
|
||||||
|
{"ReadFile", ReadViaReadFile},
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
|
||||||
|
// here to prevent the compiler from complaining about "unused functions".
|
||||||
|
(void)&IsInBounds;
|
||||||
|
(void)&AreEqual;
|
||||||
|
|
||||||
|
InitializeOSMetrics();
|
||||||
|
u64 CPUTimerFreq = EstimateCPUTimerFreq();
|
||||||
|
|
||||||
|
if(ArgCount == 2)
|
||||||
|
{
|
||||||
|
char *FileName = Args[1];
|
||||||
|
#if _WIN32
|
||||||
|
struct __stat64 Stat;
|
||||||
|
_stat64(FileName, &Stat);
|
||||||
|
#else
|
||||||
|
struct stat Stat;
|
||||||
|
stat(FileName, &Stat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
read_parameters Params = {};
|
||||||
|
Params.Dest = AllocateBuffer(Stat.st_size);
|
||||||
|
Params.FileName = FileName;
|
||||||
|
|
||||||
|
if(Params.Dest.Count > 0)
|
||||||
|
{
|
||||||
|
repetition_tester Testers[ArrayCount(TestFunctions)][AllocType_Count] = {};
|
||||||
|
|
||||||
|
for(;;)
|
||||||
|
{
|
||||||
|
for(u32 FuncIndex = 0; FuncIndex < ArrayCount(TestFunctions); ++FuncIndex)
|
||||||
|
{
|
||||||
|
for(u32 AllocType = 0; AllocType < AllocType_Count; ++AllocType)
|
||||||
|
{
|
||||||
|
Params.AllocType = (allocation_type)AllocType;
|
||||||
|
|
||||||
|
repetition_tester *Tester = &Testers[FuncIndex][AllocType];
|
||||||
|
test_function TestFunc = TestFunctions[FuncIndex];
|
||||||
|
|
||||||
|
printf("\n--- %s%s%s ---\n",
|
||||||
|
DescribeAllocationType(Params.AllocType),
|
||||||
|
Params.AllocType ? " + " : "",
|
||||||
|
TestFunc.Name);
|
||||||
|
NewTestWave(Tester, Params.Dest.Count, CPUTimerFreq);
|
||||||
|
TestFunc.Func(Tester, &Params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(casey): We would normally call this here, but we can't because the compiler will complain about "unreachable code".
|
||||||
|
// So instead we just reference the pointer to prevent the compiler complaining about unused function :(
|
||||||
|
(void)&FreeBuffer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Test data size must be non-zero\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [existing filename]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 112
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
#include "listing_0108_platform_metrics.cpp"
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
|
||||||
|
// here to prevent the compiler from complaining about "unused functions".
|
||||||
|
(void)&EstimateCPUTimerFreq;
|
||||||
|
|
||||||
|
InitializeOSMetrics();
|
||||||
|
|
||||||
|
if(ArgCount == 2)
|
||||||
|
{
|
||||||
|
u64 PageSize = 4096; // NOTE(casey): This may not be the OS page size! It is merely our testing page size.
|
||||||
|
u64 PageCount = atol(Args[1]);
|
||||||
|
u64 TotalSize = PageSize*PageCount;
|
||||||
|
|
||||||
|
printf("Page Count, Touch Count, Fault Count, Extra Faults\n");
|
||||||
|
|
||||||
|
for(u64 TouchCount = 0; TouchCount <= PageCount; ++TouchCount)
|
||||||
|
{
|
||||||
|
u64 TouchSize = PageSize*TouchCount;
|
||||||
|
u8 *Data = (u8 *)VirtualAlloc(0, TotalSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
if(Data)
|
||||||
|
{
|
||||||
|
u64 StartFaultCount = ReadOSPageFaultCount();
|
||||||
|
for(u64 Index = 0; Index < TouchSize; ++Index)
|
||||||
|
{
|
||||||
|
Data[Index] = (u8)Index;
|
||||||
|
}
|
||||||
|
u64 EndFaultCount = ReadOSPageFaultCount();
|
||||||
|
|
||||||
|
u64 FaultCount = EndFaultCount - StartFaultCount;
|
||||||
|
|
||||||
|
printf("%llu, %llu, %llu, %lld\n", PageCount, TouchCount, FaultCount, (FaultCount - TouchCount));
|
||||||
|
|
||||||
|
VirtualFree(Data, 0, MEM_RELEASE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to allocate memory\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [# of 4k pages to allocate]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/* ========================================================================
|
||||||
|
|
||||||
|
(C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Please see https://computerenhance.com for more information
|
||||||
|
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* ========================================================================
|
||||||
|
LISTING 113
|
||||||
|
======================================================================== */
|
||||||
|
|
||||||
|
/* NOTE(casey): _CRT_SECURE_NO_WARNINGS is here because otherwise we cannot
|
||||||
|
call fopen(). If we replace fopen() with fopen_s() to avoid the warning,
|
||||||
|
then the code doesn't compile on Linux anymore, since fopen_s() does not
|
||||||
|
exist there.
|
||||||
|
|
||||||
|
What exactly the CRT maintainers were thinking when they made this choice,
|
||||||
|
I have no idea. */
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef uint8_t u8;
|
||||||
|
typedef uint32_t u32;
|
||||||
|
typedef uint64_t u64;
|
||||||
|
|
||||||
|
typedef int32_t b32;
|
||||||
|
|
||||||
|
typedef float f32;
|
||||||
|
typedef double f64;
|
||||||
|
|
||||||
|
#define ArrayCount(Array) (sizeof(Array)/sizeof((Array)[0]))
|
||||||
|
|
||||||
|
#include "listing_0108_platform_metrics.cpp"
|
||||||
|
|
||||||
|
int main(int ArgCount, char **Args)
|
||||||
|
{
|
||||||
|
// NOTE(casey): Since we do not use these functions in this particular build, we reference their pointers
|
||||||
|
// here to prevent the compiler from complaining about "unused functions".
|
||||||
|
(void)&EstimateCPUTimerFreq;
|
||||||
|
|
||||||
|
InitializeOSMetrics();
|
||||||
|
|
||||||
|
if(ArgCount == 2)
|
||||||
|
{
|
||||||
|
u64 PageSize = 4096; // NOTE(casey): This may not be the OS page size! It is merely our testing page size.
|
||||||
|
u64 PageCount = atol(Args[1]);
|
||||||
|
u64 TotalSize = PageSize*PageCount;
|
||||||
|
|
||||||
|
printf("Page Count, Touch Count, Fault Count, Extra Faults\n");
|
||||||
|
|
||||||
|
for(u64 TouchCount = 0; TouchCount <= PageCount; ++TouchCount)
|
||||||
|
{
|
||||||
|
u64 TouchSize = PageSize*TouchCount;
|
||||||
|
u8 *Data = (u8 *)VirtualAlloc(0, TotalSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
if(Data)
|
||||||
|
{
|
||||||
|
u64 StartFaultCount = ReadOSPageFaultCount();
|
||||||
|
for(u64 Index = 0; Index < TouchSize; ++Index)
|
||||||
|
{
|
||||||
|
Data[TotalSize - 1 - Index] = (u8)Index;
|
||||||
|
}
|
||||||
|
u64 EndFaultCount = ReadOSPageFaultCount();
|
||||||
|
|
||||||
|
u64 FaultCount = EndFaultCount - StartFaultCount;
|
||||||
|
|
||||||
|
printf("%llu, %llu, %llu, %lld\n", PageCount, TouchCount, FaultCount, (FaultCount - TouchCount));
|
||||||
|
|
||||||
|
VirtualFree(Data, 0, MEM_RELEASE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Unable to allocate memory\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s [# of 4k pages to allocate]\n", Args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user