OPTION CASEMAP:NONE GENERIC_READ equ 80000000h GENERIC_WRITE equ 40000000h FILE_SHARE_READ equ 00000001h FILE_SHARE_WRITE equ 00000002h CREATE_NEW equ 1 CREATE_ALWAYS equ 2 OPEN_EXISTING equ 3 OPEN_ALWAYS equ 4 TRUNCATE_EXISTING equ 5 FILE_ATTRIBUTE_NORMAL equ 00000080h INVALID_HANDLE_VALUE equ -1 ; --------------------------------------------------------------------------- ; Data section ; --------------------------------------------------------------------------- .data InputPath BYTE "D:/Sorting/Sorting_Input.txt", 0 OutputPath BYTE "D:/Sorting/Output_X64ASM.txt", 0 ProgramName BYTE "***HEAPSORT x64 ASM***", 13, 10, 0 LoadingMessage BYTE "LOADING INPUT FILE...", 0 SavingMessage BYTE "SAVING OUTPUT FILE...", 0 SortingMessage BYTE "SORTING ELEMENT...", 0 DoneMessage BYTE "DONE.", 13,10, 0 CompleteMessage BYTE "***RUN COMPLETE***", 13, 10, 0 TimeMessage BYTE "HEAPSORT TOOK ", 0 SecondsMessage BYTE " SECONDS.", 13, 10, 0 ; High resolution timer storage and scaling factor StartTime QWORD 0 ms_factor REAL8 1000.0 ; File read buffer and helper counters InputBuffer BYTE 32768 DUP(?) BytesReadTracker DWORD 0 .code PUBLIC HeapSort PUBLIC HeapSortMain PUBLIC LoadFile PUBLIC SaveFile PUBLIC WriteConsoleString PUBLIC LoadInput PUBLIC SaveOutput PUBLIC StartTimer PUBLIC StopTimerAndWriteElapsed EXTERN GetStdHandle:PROC EXTERN WriteConsoleA:PROC EXTERN CreateFileA:PROC EXTERN ReadFile:PROC EXTERN CloseHandle:PROC EXTERN WriteFile:PROC EXTERN QueryPerformanceCounter:PROC EXTERN QueryPerformanceFrequency:PROC ; --------------------------------------------------------------------------- ; SaveOutput PROC ; Purpose: ; Write an array of signed 32-bit integers, one per line, to a file. ; Each integer is formatted as an ASCII decimal string with CRLF termination. ; ; Parameters (incoming registers): ; rcx - pointer to first int32 element (int32*) ; rdx - number of elements to write (count) ; r8 - pointer to output filename (char*) ; ; Returns: ; None (no valuable register return). Preserves non-volatile registers. ; ; Registers used (and meaning within the proc): ; rbx - cursor into the integer array (kept non-volatile) ; r12 - remaining element count (kept non-volatile) ; r13..r15, rsi, rdi, r14 - scratch and stack buffer cursors for formatting ; rax, rcx, rdx, r8, r9, ecx, edx - caller-saved temporaries for API calls and division ; Stack layout used extensively for safe shadow-space and local string buffer. ; ; Notes: ; - Preserves non-volatile registers by storing them on the stack at entry ; - Uses a backward-building scratch buffer for number formatting to avoid heap allocations ; --------------------------------------------------------------------------- SaveOutput PROC ; Allocate 144 bytes total frame. 8 (ret addr) + 144 = 152 bytes total (Perfect 16-byte alignment match) SUB rsp, 144 ; Protect non-volatile registers completely above our string writing buffer space MOV QWORD PTR [rsp+80], rbx MOV QWORD PTR [rsp+88], rsi MOV QWORD PTR [rsp+96], rdi MOV QWORD PTR [rsp+104], r12 MOV QWORD PTR [rsp+112], r13 MOV QWORD PTR [rsp+120], r14 MOV QWORD PTR [rsp+128], r15 MOV rbx, rcx ; cursor into the value array MOV r12, rdx ; remaining element count ; --- Open file --- MOV rcx, r8 ; lpFileName for CreateFileA MOV rdx, GENERIC_WRITE MOV r8, FILE_SHARE_READ MOV r9, 0 ; lpSecurityAttributes (NULL) MOV QWORD PTR [rsp+32], CREATE_ALWAYS MOV QWORD PTR [rsp+40], FILE_ATTRIBUTE_NORMAL MOV QWORD PTR [rsp+48], 0 MOV QWORD PTR [rsp+56], 0 CALL CreateFileA ; CORRECTION: Cache file handle cleanly at [rsp+136], completely safe from API shadow spaces MOV QWORD PTR [rsp+136], rax CMP QWORD PTR [rsp+136], INVALID_HANDLE_VALUE JE SaveDone WriteLoop: TEST r12, r12 JE CloseOut LEA r14, [rsp+78] ; Start at the back end of our safe stack scratch space MOV byte PTR [r14], 0Ah ; Pre-fill LF DEC r14 MOV byte PTR [r14], 0Dh ; Pre-fill CR MOV r9d, 2 ; Clear length tracking counter immediately to 2 (CR + LF) MOV eax, dword PTR [rbx] ; current element value to format ; Check for zero TEST eax, eax JNE CheckNegative DEC r14 MOV byte PTR [r14], '0' INC r9d JMP FinishNum CheckNegative: ; Check if the value is negative XOR r15d, r15d ; r15d = 0 means positive CMP eax, 0 JGE Convert MOV r15d, 1 ; Mark as negative NEG eax ; Convert negative number to positive for unsigned DIV math Convert: MOV ecx, 10 ; divisor (base 10) ConvLoop: XOR edx, edx DIV ecx ; eax:edx / ecx -> eax = quotient, edx = remainder ADD dl, '0' ; Convert digit to ASCII DEC r14 ; Move text cursor backward MOV byte PTR [r14], dl INC r9d ; Increment string length count safely via register math TEST eax, eax JNE ConvLoop ; Append negative sign if original number was negative CMP r15d, 1 JNE FinishNum DEC r14 MOV byte PTR [r14], '-' ; Append minus sign to text sequence INC r9d FinishNum: MOV r8d, r9d ; 3rd arg: explicit character payload count limit MOV rcx, QWORD PTR [rsp+136] ; 1st arg: Load safe file handle from [rsp+136] MOV rdx, r14 ; 2nd arg: direct pointer to the start of our string data MOV r9, 0 ; 4th arg: lpNumberOfBytesWritten (NULL) MOV QWORD PTR [rsp+32], 0 ; 5th arg: lpOverlapped -> shadow-safe at [rsp+32] CALL WriteFile ADD rbx, 4 DEC r12 JMP WriteLoop CloseOut: MOV rcx, QWORD PTR [rsp+136] CALL CloseHandle SaveDone: ; Restore non-volatile tracking states cleanly out of structural boundaries MOV rbx, QWORD PTR [rsp+80] MOV rsi, QWORD PTR [rsp+88] MOV rdi, QWORD PTR [rsp+96] MOV r12, QWORD PTR [rsp+104] MOV r13, QWORD PTR [rsp+112] MOV r14, QWORD PTR [rsp+120] MOV r15, QWORD PTR [rsp+128] ADD rsp, 144 RET SaveOutput ENDP ; --------------------------------------------------------------------------- ; LoadInput PROC ; Purpose: ; Open the file specified at r8, read up to 32KB into `InputBuffer`, parse ; whitespace-separated decimal integers and store them into the array at rcx. ; Parsing honors an optional leading minus sign. ; ; Parameters: ; rcx - pointer to destination int32 array (int32*) ; rdx - maximum number of elements to parse (count) ; r8 - file path (char*) ; ; Returns: ; rax - number of integers parsed (0 on error or no data) ; ; Registers used: ; r13 - output array base (preserved across calls) ; rbx - max element count (preserved across calls) ; r10 - file handle (temporary, closed before parsing) ; r11 - pointer to InputBuffer ; r14 - read cursor pointer (pointer into InputBuffer) ; r15d- remaining bytes / temp counters ; r8d, r12d etc - temporary accumulators during parsing ; Non-volatile rbx, rsi, rdi, r12..r15 are saved/restored on entry/exit. ; Notes: ; - Protects non-volatile registers and uses a global InputBuffer so stack stays small. ; - Stops parsing when the element count limit is reached. ; --------------------------------------------------------------------------- LoadInput PROC SUB rsp, 112 ; Shifted tracking spills down by 8 bytes to match the new 112-byte layout MOV QWORD PTR [rsp+56], rbx MOV QWORD PTR [rsp+64], rsi MOV QWORD PTR [rsp+72], rdi MOV QWORD PTR [rsp+80], r12 MOV QWORD PTR [rsp+88], r13 MOV QWORD PTR [rsp+96], r14 MOV QWORD PTR [rsp+104], r15 SIZE_GUARD: CMP rcx, 0 JE LoadDone TEST rdx, rdx JZ LoadDone MOV r13, rcx ; r13 = output array base MOV rbx, rdx ; rbx = max element count ; --- Open file --- MOV rcx, r8 ; 1st arg: lpFileName MOV rdx, GENERIC_READ ; 2nd arg: dwDesiredAccess MOV r8, FILE_SHARE_READ ; 3rd arg: dwShareMode MOV r9, 0 ; 4th arg: lpSecurityAttributes (NULL) ; Safe out-of-bounds stack argument placement MOV QWORD PTR [rsp+32], OPEN_EXISTING MOV QWORD PTR [rsp+40], FILE_ATTRIBUTE_NORMAL MOV QWORD PTR [rsp+48], 0 MOV QWORD PTR [rsp+56], 0 CALL CreateFileA MOV r10, rax ; r10 = file handle CMP r10, INVALID_HANDLE_VALUE JE LoadErrorExit MOV DWORD PTR [BytesReadTracker], 0 ; --- Read into global buffer --- LEA r11, InputBuffer ; destination buffer MOV r12d, 32768 ; buffer capacity in bytes MOV rcx, r10 ; 1st arg: hFile MOV rdx, r11 ; 2nd arg: lpBuffer MOV r8d, r12d ; 3rd arg: bytes to read LEA r9, OFFSET BytesReadTracker ; RIP-relative pointer MOV QWORD PTR [rsp+32], 0 ; 5th arg: lpOverlapped (NULL) CALL ReadFile TEST eax, eax JZ SafeCloseExit MOV rcx, r10 CALL CloseHandle JMP StartParsing SafeCloseExit: MOV rcx, r10 CALL CloseHandle MOV DWORD PTR [BytesReadTracker], 0 StartParsing: ; --- Parse --- MOV rax, 0 ; rax = running count of parsed integers LEA r14, InputBuffer ; r14 = tracking cursor position pointer MOV r15d, DWORD PTR [BytesReadTracker] TEST r15d, r15d JZ LoadDone ParseLoop: CMP r15d, 0 JE LoadDone SkipWS: CMP byte PTR [r14], 0Dh JE NextChar CMP byte PTR [r14], 0Ah JE NextChar CMP byte PTR [r14], ' ' JE NextChar CMP byte PTR [r14], 9 JE NextChar JMP ParseNum NextChar: INC r14 DEC r15d JMP ParseLoop ParseNum: XOR r8d, r8d ; Clear the digit accumulator XOR r12d, r12d ; r12d = 0 means positive, 1 means negative ; Check if the very first character of the number is a minus sign CMP byte PTR [r14], '-' JNE DigitLoop ; If not, skip to parsing digits normally MOV r12d, 1 ; Mark the number as negative INC r14 ; Skip over the minus sign in the buffer DEC r15d ; Decrement remaining buffer bytes counter JZ LoadDone ; If file ends abruptly on a minus sign, exit DigitLoop: CMP r15d, 0 JE CheckSignAndStore MOV dl, byte PTR [r14] CMP dl, '0' JB CheckSignAndStore CMP dl, '9' JA CheckSignAndStore SUB dl, '0' IMUL r8d, r8d, 10 ADD r8d, edx INC r14 DEC r15d JMP DigitLoop CheckSignAndStore: ; Apply the negative sign if our sign flag was set CMP r12d, 1 JNE StoreNum NEG r8d ; Negate the accumulated 32-bit integer value StoreNum: MOV dword PTR [r13 + rax*4], r8d INC rax CMP rax, rbx ; Check if we hit the maximum element count limit JAE LoadDone ; If we are full, exit parsing completely ; Advance the buffer cursor past the trailing character we just hit INC r14 ; Move pointer forward by 1 character DEC r15d ; Decrease remaining byte counter by 1 JMP ParseLoop ; Go process the next integer freshly LoadErrorExit: XOR rax, rax LoadDone: ; Restore non-volatile registers from their updated slots MOV rbx, QWORD PTR [rsp+56] MOV rsi, QWORD PTR [rsp+64] MOV rdi, QWORD PTR [rsp+72] MOV r12, QWORD PTR [rsp+80] MOV r13, QWORD PTR [rsp+88] MOV r14, QWORD PTR [rsp+96] MOV r15, QWORD PTR [rsp+104] ADD rsp, 112 RET LoadInput ENDP ; --------------------------------------------------------------------------- ; LoadFile PROC ; Purpose: ; Wrapper that prints the loading message and calls LoadInput. ; ; Parameters: ; rcx - destination array pointer (forwarded to LoadInput) ; rdx - max element count (forwarded to LoadInput) ; ; Registers used: ; rbx, rsi - saved/restored around calls; used to cache incoming arguments ; --------------------------------------------------------------------------- LoadFile PROC SUB rsp, 48 MOV QWORD PTR [rsp+32], rbx ; Save rbx safely in shadow spill area MOV QWORD PTR [rsp+40], rsi ; Save rsi safely MOV rbx, rcx ; Cache array base MOV rsi, rdx ; Cache max count LEA rcx, LoadingMessage CALL WriteConsoleString MOV rcx, rbx MOV rdx, rsi MOV r8, OFFSET InputPath CALL LoadInput LEA rcx, DoneMessage CALL WriteConsoleString MOV rbx, QWORD PTR [rsp+32] ; Restore rbx MOV rsi, QWORD PTR [rsp+40] ; Restore rsi ADD rsp, 48 RET LoadFile ENDP ; --------------------------------------------------------------------------- ; SaveFile PROC ; Purpose: ; Wrapper that prints the saving message and calls SaveOutput. ; ; Parameters: ; rcx - pointer to array to save (int32*) ; rdx - number of elements to save ; ; Registers used: ; rbx, rsi - saved/restored and used to forward args to SaveOutput ; --------------------------------------------------------------------------- SaveFile PROC SUB rsp, 48 MOV QWORD PTR [rsp+32], rbx MOV QWORD PTR [rsp+40], rsi MOV rbx, rcx ; Cache array base MOV rsi, rdx ; Cache element count LEA rcx, SavingMessage CALL WriteConsoleString MOV rcx, rbx MOV rdx, rsi MOV r8, OFFSET OutputPath CALL SaveOutput LEA rcx, DoneMessage CALL WriteConsoleString MOV rbx, QWORD PTR [rsp+32] ; Restore rbx MOV rsi, QWORD PTR [rsp+40] ; Restore rsi ADD rsp, 48 RET SaveFile ENDP ; --------------------------------------------------------------------------- ; HeapSortMain PROC ; Purpose: ; Top-level entry that prints program header, loads input, sorts, saves output, ; and prints completion. This is the intended entry for external callers. ; ; Parameters: ; rcx - pointer to array buffer (int32*) ; rdx - maximum element count ; ; Stack/frame behaviour: ; Uses RBP as frame anchor and aligns RSP to 16 bytes. Incoming parameters ; are saved into stack home slots so they survive nested calls. ; --------------------------------------------------------------------------- HeapSortMain PROC ; Establish standard, pristine stack frame tracking using RBP PUSH rbp ; Save parent RBP frame anchor MOV rbp, rsp ; RBP now points to our untouchable entry baseline AND rsp, -16 ; Safely mask RSP down to 16-byte alignment SUB rsp, 48 ; Allocate 32-byte shadow space + 16-byte home spills ; Save incoming call parameters directly into our aligned home spill spaces MOV QWORD PTR [rsp+32], rcx ; Save array base parameter MOV QWORD PTR [rsp+40], rdx ; Save max element count parameter LEA rcx, ProgramName CALL WriteConsoleString MOV rcx, QWORD PTR [rsp+32] ; Restore array base parameter MOV rdx, QWORD PTR [rsp+40] ; Restore max element count parameter CALL LoadFile MOV rcx, QWORD PTR [rsp+32] MOV rdx, QWORD PTR [rsp+40] CALL HeapSort MOV rcx, QWORD PTR [rsp+32] MOV rdx, QWORD PTR [rsp+40] CALL SaveFile LEA rcx, CompleteMessage CALL WriteConsoleString ; Cleanup and Restore Sequence MOV rsp, rbp ; Snap RSP back to our un-clobberable entry state POP rbp ; Restore parent frame pointer RET HeapSortMain ENDP ; --------------------------------------------------------------------------- ; WriteConsoleString PROC ; Purpose: ; Writes a null-terminated ASCII string at rcx to the console (STDOUT). ; ; Parameters: ; rcx - pointer to NUL-terminated ASCII string ; ; Registers used: ; r8 - local copy of rcx (string pointer) ; r9 - console handle (returned by GetStdHandle) ; r10d- counted length during scanning ; rcx, rdx, r8d - used for WriteConsoleA arguments ; ; Notes: ; - Leaves non-volatile registers unchanged (uses caller-saved registers only). ; --------------------------------------------------------------------------- WriteConsoleString PROC SUB rsp, 40 ; Padded shadow space to keep stack 16-byte aligned MOV r8, rcx MOV rcx, -11 CALL GetStdHandle MOV r9, rax MOV rdx, r8 XOR r10d, r10d CountLoop: CMP byte PTR [rdx], 0 JE WriteIt INC rdx INC r10d JMP CountLoop WriteIt: MOV rcx, r9 MOV rdx, r8 MOV r8d, r10d XOR r9, r9 XOR r10, r10 CALL WriteConsoleA ADD rsp, 40 RET WriteConsoleString ENDP ; --------------------------------------------------------------------------- ; HeapSort PROC ; Purpose: ; In-place heap sort for an array of signed 32-bit integers. ; ; Parameters: ; rcx - pointer to array base (int32*) ; rdx - number of elements in the array (count) ; ; Registers used: ; rbx - cached array base (non-volatile) ; rdi - cached element count (non-volatile) ; rsi - working half/count index and loop counter ; rcx, rdx, r8, etc. - used when calling SiftDown ; Notes: ; - Preserves non-volatile registers and uses StartTimer/StopTimer helpers. ; --------------------------------------------------------------------------- HeapSort PROC PUSH rbx PUSH rsi PUSH rdi SUB rsp, 32 ; Allocate clean baseline shadow padding layer MOV rbx, rcx ; Cache array base MOV rdi, rdx ; Cache elements max bounds count MOV rsi, rdx LEA rcx, SortingMessage CALL WriteConsoleString CALL StartTimer MOV QWORD PTR [StartTime], rax SHR rsi, 1 JZ SortDone DEC rsi BuildHeap: MOV rcx, rbx MOV rdx, rsi MOV r8, rdi CALL SiftDown DEC rsi JNS BuildHeap MOV rsi, rdi DEC rsi SortHeap: TEST rsi, rsi JZ SortDone MOV eax, dword PTR [rbx] XCHG eax, dword PTR [rbx + rsi * 4] MOV dword PTR [rbx], eax MOV rcx, rbx MOV rdx, 0 MOV r8, rsi CALL SiftDown DEC rsi JMP SortHeap SortDone: LEA rcx, DoneMessage CALL WriteConsoleString LEA rcx, TimeMessage CALL WriteConsoleString MOV rcx, QWORD PTR [StartTime] CALL StopTimerAndWriteElapsed LEA rcx, SecondsMessage CALL WriteConsoleString ADD rsp, 32 POP rdi POP rsi POP rbx RET HeapSort ENDP ; --------------------------------------------------------------------------- ; SiftDown PROC ; Purpose: ; Restore the max-heap property at index rdx within a heap that is stored ; at rcx (base pointer). The heap covers indices [0 .. r8-1] (r8 is heap size). ; ; Parameters: ; rcx - array base (int32*) ; rdx - root index to sift down (0-based) ; r8 - heap size limit (index upper bound, typically element count) ; ; Registers used: ; r9, r10 - child index calculations (temporaries) ; eax/edx - element temporaries for comparisons and exchange ; Notes: ; - This routine expects rcx/r8/rdx to be valid and within bounds. ; --------------------------------------------------------------------------- SiftDown PROC LEA r9, [rdx * 2 + 1] ; left child index CMP r9, r8 JAE SiftDone FindChild: LEA r10, [r9 + 1] ; right child index CMP r10, r8 ; Verify right child stays inside valid heap array JAE CompareRoot MOV eax, dword PTR [rcx + r9 * 4] CMP eax, dword PTR [rcx + r10 * 4] JGE CompareRoot MOV r9, r10 ; right child is larger candidate CompareRoot: MOV eax, dword PTR [rcx + rdx * 4] CMP eax, dword PTR [rcx + r9 * 4] JGE SiftDone XCHG eax, dword PTR [rcx + r9 * 4] MOV dword PTR [rcx + rdx * 4], eax MOV rdx, r9 LEA r9, [rdx * 2 + 1] CMP r9, r8 JB FindChild SiftDone: RET SiftDown ENDP ; --------------------------------------------------------------------------- ; StartTimer PROC ; Purpose: ; Read the high-resolution performance counter and return its value in RAX. ; ; Parameters: ; none (call directly) ; ; Returns: ; rax - counter ticks at the moment of call ; ; Registers used: ; uses QueryPerformanceCounter which writes a 64-bit value to the memory pointer ; provided in rcx. This routine provides [rsp+32] as temporary storage. ; --------------------------------------------------------------------------- StartTimer PROC SUB rsp, 48 ; Safe 16-byte divisible shadow alignment LEA rcx, [rsp+32] ; Safe home spill address allocation CALL QueryPerformanceCounter MOV rax, QWORD PTR [rsp+32] ; Pull raw baseline ticks out cleanly ADD rsp, 48 RET StartTimer ENDP ; --------------------------------------------------------------------------- ; StopTimerAndWriteElapsed PROC ; Purpose: ; Compute elapsed time between the StartTime passed in RCX and the current ; high resolution counter, convert to fractional seconds (nanosecond precision ; scaled to seconds) and print the decimal string to console. ; ; Parameters: ; rcx - StartTime ticks value (64-bit) previously saved by StartTimer/HeapSort ; ; Behavior / Registers: ; rbx - used to save/restore a non-volatile register ; rax - used for intermediate tick arithmetic (elapsed) ; rsp scratch bytes used for QueryPerformanceCounter/Frequency outputs and string buffer ; r12, r13, r15 used as temporary pointers/counters for string building ; ; Notes: ; - Produces a decimal string with at least 9 fractional digits (nanosecond-like precision) ; - Restores non-volatile rbx prior to return. ; --------------------------------------------------------------------------- StopTimerAndWriteElapsed PROC SUB rsp, 96 MOV QWORD PTR [rsp+88], rbx MOV rbx, rcx ; rbx = StartTime ticks value passed from HeapSort LEA rcx, [rsp+64] CALL QueryPerformanceCounter MOV rax, QWORD PTR [rsp+64] ; rax = EndTime ticks value SUB rax, rbx ; rax = Raw Elapsed Clock Ticks ; --- ULTRA HIGH PRECISION INT MATH --- ; Multiply elapsed ticks by 1,000,000,000 to scale up to nanoseconds resolution IMUL rax, rax, 1000000000 ; Fetch the precise system hardware clock frequency LEA rcx, [rsp+72] CALL QueryPerformanceFrequency MOV rcx, QWORD PTR [rsp+72] ; rcx = Frequency ticks per second ; Divide (Ticks * 1,000,000,000) by Frequency to get Nanoseconds XOR rdx, rdx ; Clear RDX for 64-bit division DIV rcx ; rax = (Ticks * 1,000,000,000) / Frequency ; Setup safe forward scratch string mapping at [rsp+32] LEA r13, [rsp+32] MOV r12, r13 XOR r15d, r15d ; r15d = loop character digit counter ConvLoop: XOR edx, edx ; Clear EDX inside the loop to prevent overflows MOV ecx, 10 DIV ecx ; Divides edx:eax cleanly by 10 ADD dl, '0' MOV byte PTR [r12], dl ; Store character forward INC r12 INC r15d ; We force the loop to extract AT LEAST 9 fractional decimal digits for nanoseconds CMP r15d, 9 JB ConvLoop TEST eax, eax JNE ConvLoop ; Inject the manual decimal point and a leading '0' MOV byte PTR [r12], '.' INC r12 MOV byte PTR [r12], '0' INC r12 ; Symmetrical string reversal tracking block MOV byte PTR [r12], 0 ; Append true string null-terminator MOV rsi, r13 MOV rdi, r12 RevLoop: DEC rdi CMP rsi, rdi JAE PrintTime MOV al, byte PTR [rsi] MOV dl, byte PTR [rdi] MOV byte PTR [rsi], dl MOV byte PTR [rdi], al INC rsi JMP RevLoop PrintTime: MOV byte PTR [r12], 0 ; Force strict terminal termination MOV rcx, r13 ; Pass direct pointer to string data payload CALL WriteConsoleString MOV rbx, QWORD PTR [rsp+88] ; Restore loop anchors ADD rsp, 96 RET StopTimerAndWriteElapsed ENDP END