This is an old revision of the document!


Displaying floating point values

As the second scenario, we will add to our library a function for displaying floating-point values. This function will allow us to display the results of calculations we implement in further scenarios. According to x64 Windows ABI rules, floating-point values should be passed through XMM registers. We will display a single value, so we'll use the XMM0 register.

Displaying floating-point numbers is a much more complex task than displaying an integer. We will split it into a conversion of the fractional part and a conversion of the integer part. First, we'll store the argument in XMM0 into XMM1 to have the original value unchanged.

Let's start with a check to see if the value is positive or negative. Floating-point numbers ​​are stored as absolute values, with the sign bit in the most significant position. The encoding scheme for positive and negative numbers with the same absolute value differs only in the sign bit. To test whether a number is negative, we can use the movmskps instruction, which copies the sign bits from all elements of a vector into the destination register. As our argument is a scalar, the bit we're interested in is at the lowest position. Shifting the register one position to the right, we can execute a conditional jump. If the argument is negative, we'll change it into positive by clearing a sign bit. The andps instruction with clear_sign_bit variable clears one bit in the XMM1 register.

.data
clear_sign_bit dword 07FFFFFFFh, 0FFFFFFFFh, 0FFFFFFFFh, 0FFFFFFFFh
...
 
.code
...
; test if the number is positive or negative
    movq xmm1, xmm0
    movmskps rax, xmm1
    rcr rax, 1
    jnc float_positive
 
; change the sign of the scalar
    andps xmm1, xmmword ptr clear_sign_bit
 
; do not change the sign  
float_positive:

We will start the conversion from the least significant digit of the fractional part, limiting precision to thousandths. We obtain the fractional part by subtracting the integer part from the original argument. An integer is obtained with the cvttss2si instruction, which simply cuts out the fractional part of a number. We store the result in rcx for further use.

.data 
const1000 real4 1000.0
 
.code
...
; convert fractional part  
    cvttss2si rax, xmm1 ; convert float to int with truncation
    mov rcx, rax        ; store for conversion of an integer part
    cvtsi2ss xmm2, rax  ; convert back into float
    subss xmm1, xmm2    ; subtract integer part
 
    mulss xmm1, const1000 ; we want three fractional digits
    cvttss2si rax, xmm1
 
    mov rbx, 10
convert_fraction:
    dec rdi		; starting from the end of the text (least significant)
    xor rdx, rdx	; prepare to divide rdx:rax by rbx
 
    div rbx		; rax / 10 → remainder in rdx
    add dl, "0"		; convert remainder into ASCII
    mov [rdi], dl	; write character to buffer
    test rax, rax	; test if there is still a value for conversion
    jne convert_fraction

We separate the fractional and integer parts with a dot.

; add dot
    dec rdi
    mov byte ptr [rdi], '.'

The integer part is converted with the same algorithm as the fractional, but before we restore its value from rcx.

; convert integer part
    mov rax, rcx        ; restore integer part
 
convert_integer:
    dec rdi		; starting from the end of the text (least significant)
    xor rdx, rdx	; prepare to divide rdx:rax by rbx
 
    div rbx	        ; rax / 10 → remainder in rdx
    add dl, "0"		; convert remainder into ASCII
    mov [rdi], dl	; write character to buffer
    test rax, rax	; test if there is still a value for conversion
    jne convert_integer

After converting the integer part, we need to add “minus” for a negative value. We'll test it again with the same method as at the beginning. For this purpose, we kept the original argument in XMM0.

; test if the number is positive or negative
    movmskps rax, xmm0
    rcr rax, 1
    jnc end_float
 
; add minus if needed
    dec rdi             ; add minus character
    mov byte ptr [rdi], '-'
 
end_float:

The final part, calculating the string length, is the same as in the conversion of integers.

Implementation of calculation functions

In another scenario, we will create a library with functions performing the simple calculations on integers and floating-point numbers. We will write functions for adding six integers and six floating-point values. This example will present argument passing through the registers and also through the stack, showing the order and addresses of stack-allocated arguments. The simplest version of a function adds six integers without advanced stack manipulation. Let's present the code of a function first. It takes the first four arguments from registers, and the latter two from the stack. Please note that for each argument, there is an 8-byte space reserved on the stack.

.code
; ---------------------------------
; sum of six integer arguments
; this is a leaf function
; does not need to reserve shadow space
; arguments as passed by MSVC
    ; a = RCX
    ; b = RDX
    ; c = R8
    ; d = R9
    ; e = [RSP + 28h]
    ; f = [RSP + 30h]
; ---------------------------------
sum_6_int proc
 
    mov rax, rcx
    add rax, rdx   ; a + b
 
    mov rcx, r8 
    add rax, rcx   ; + c
 
    mov rcx, r9
    add rax, rcx   ; + d
 
    mov rcx, QWORD PTR [rsp + 28h]
    add rax, rcx   ; + e
 
    mov rcx, QWORD PTR [rsp + 30h]
    add rax, rcx   ; + f
    ret
sum_6_int endp

The stack from a function perspective looks like in a fig.

The caller passes arguments according to the Windows x64 ABI. The responsibility of the caller is also to reserve the shadow space for all arguments before the call.

en/multiasm/exercisebook/pc/sut/scenarios_standalone.1779278406.txt.gz · Last modified: by ktokarz
CC Attribution-Share Alike 4.0 International
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0