# Guessing game in NASM Win64 Assembly

Still learning Assembly. This program is a simple guessing game to test most of the new things I've learned! Any advice and all topical comments on code optimization and conventions is appreciated!

Compiled as follows using VS2017 x64 Native Tools Command Prompt:

> nasm -g -fwin64 guess.asm

> cl /Zi guess.obj msvcrt.lib legacy_stdio_definitions.lib


guess.asm

;; bits 64
default rel

extern time, srand, rand
extern printf, scanf

%macro str 2
%2: db %1, 0
%endmacro

; use DQ to define a quad-word, DD to define a double-word, DW to define a word, and DB to define a byte.
section .rdata          ; immutable predefined variables
str "Enter your guess (1-100): ", prompt
str "%u", scan_fmt
str "ERROR! Input was not a number!", scan_fail
str {"Too high!", 10}, too_high
str {"Too low!", 10}, too_low
str {"You guessed it!", 13, 10}, congrats

; use RESQ to reserve a number of quad-words, RESD to reserve a number of double-words, RESW to reserve a number of words, and RESB to reserve a number of bytes.
section .bss            ; mutable undefined variables
target: resb 4      ; 4 bytes of storage (enough for a 32bit number)

section .text
global main
main:
stack_reserve: equ 40      ; 32 + 8
sub     rsp, stack_reserve ; shadow space for callees + 8 bytes for stack alignment

;; calculate the random number
xor     rax, rax
mov     rcx, rax        ; clear rax and rcx
call    time
mov     rcx, rax
call    srand
call    rand

;; rand = (rand % 100) + 1
xor     rdx, rdx        ; clear rdx
mov     rcx, 100
div     rcx
inc     rdx
mov     [target], rdx
;mov     rcx, scan_fmt
;call    printf
.check:
lea     rcx, [prompt]
call    printf

; memory from rsp+0..31 has potentially been stepped on by printf
; leave RSP where it is, ready for another call

;;; scanf into that 8-byte block of stack space above the shadow space, or into our *own* shadow space
lea     rdx, [rsp+32]        ; stack addresses are normally 64-bit, can't get away with edx
lea     rcx, [scan_fmt]
mov     dword [rdx], 0       ; instead of error check, set n = 0 in case of I/O error
call    scanf
cmp     eax, 1               ; success = exactly 1 conversion
jnz     .scanf_fail          ; error check

mov     r8d, [rsp+32]        ; r8d: 32-bit unsigned number to be checked

cmp     r8d, [target]
jg      .too_high
jl      .too_low

lea     rcx, [congrats]
call    printf
.end:
;xor     eax,eax              ; return 0
movzx   eax, byte [rsp+32]   ; return EXIT_SUCCESS(0) or EXIT_FAILURE(1)
ret
.too_high:
lea     rcx, [too_high]
call    printf
jmp     .check
.too_low:
lea     rcx, [too_low]
call    printf
jmp     .check
.scanf_fail:
lea     rcx, [scan_fail]
call    printf
jmp     .end


The usage of target is not consistent:

target: resb 4      ; 4 bytes of storage (enough for a 32bit number)
...
mov     [target], rdx ; store 8 bytes


That's easy to improve, just use edx.

Using rand like this is not very random, it's some function of the time in seconds. Seconds are long, you could easily start the program twice in the same second. Maybe that's fine for a simple number guessing game. On not-too-old processors there is a simpler and more random way to get a random number though:

.rand_retry:
rdrand eax
jnc .rand_retry


But that doesn't work on some older processors (Intel processors older than Ivy Bridge, for example Nehalem or Core2, and AMD processors older than Excavator), and can actually be slower than calling rand. Seeding the random number generator with the TSC value is more portable and less predictable than using time:

rdtsc ; read TSC into edx:eax
mov ecx, eax
call srand
call rand


Since you asked for performance tips (this program is obviously limited by waiting for user input but you can still learn useful things from this):

This remainder calculation is inefficient:

mov     rcx, 100
div     rcx     ; 64bit div, quite slow
inc     rdx


There are other options. For example, for a 232 input range (which rand normally does not deliver but rdrand does), a very similar-looking fixed-point scale can be used:

mov     ecx, 100
mul     ecx
inc     edx


The mul, combined with using the upper half of the result, effectively multiplies the input by 100 / 232 so 0xFFFFFFFF maps to 99 and so on. This method can be adjusted for smaller ranges such as up to RAND_MAX by multiplying by a higher constant, namely 100 * 232 / (RAND_MAX + 1)

The too low/too high cases could be less branchy and can share some code - admittedly they call an IO function anyway, but still. For example:

    cmp     r8d, [target]
jne     .not_equal
...
.not_equal:
lea     rcx, [too_high]
lea     rdx, [too_low]
;; if "less than", use the too_low string
cmovl   rcx, rdx
call    printf
jmp     .check

• Or I could just make target a 64-bit number. Would you mind clarifying what you mean by "older processors," ie the cutoff generation? Also, if you could give a short implementation using rdrand and one using the Time Stamp Counter I'd appreciate it! – T145 Oct 9 '18 at 16:09
• Your 32-bit code for the TSC leaves the answer at 100 every time. I do it like so: pastebin.com/DjzXPiX8 – T145 Oct 9 '18 at 20:22
• I changed the register that sets target's value from ecx to edx, but now the answer is always one. – T145 Oct 9 '18 at 20:34
• @Mr.Vix the rand and the mul trick with 100 as the scale do not combine, as noted. The scale would need to be different. mov [target], ecx is not correct anyway, from the inc edx it should be clear that the result is in edx. – harold Oct 9 '18 at 20:48
• I noted my mistake in the paste over edx, and thanks for the clarification! I decided to go w/ the seeding improvement, since that lessens the programs overhead. – T145 Oct 9 '18 at 20:50