mirror of
https://github.com/pkivolowitz/asm_book.git
synced 2026-06-21 03:56:47 +08:00
added section on float and double literals
This commit is contained in:
parent
499f9ea7f7
commit
4b0bad2e90
3 changed files with 189 additions and 0 deletions
135
section_1/float/literals.md
Normal file
135
section_1/float/literals.md
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
# Section 1 / Floating Point Literals
|
||||
|
||||
Recall that all AARCH64 instructions are 4 bytes long. Recall also that
|
||||
this means that there are constraints on what can be specified as a
|
||||
literal since the literal must be encoded into the 4 byte instruction.
|
||||
If the literal is too large, an assembler error will result.
|
||||
|
||||
Given that floating point values are always at least 4 bytes long
|
||||
themselves, using floating point literals is extremely constrained.
|
||||
For example:
|
||||
|
||||
```asm
|
||||
fmov d0, 1 // 1
|
||||
fmov d0, 1.1 // 2
|
||||
```
|
||||
|
||||
`Line 1` will pass muster but `Line 2` will cause an error.
|
||||
|
||||
To load a `float`, you could translate the value to binary and do
|
||||
as the following:
|
||||
|
||||
```asm
|
||||
.text // 1
|
||||
.global main // 2
|
||||
.align 2 // 3
|
||||
// 4
|
||||
main: str x30, [sp, -16]! // 5
|
||||
ldr s0, =0x3fc00000 // 6
|
||||
fcvt d0, s0 // 7
|
||||
ldr x0, =fmt // 8
|
||||
bl printf // 9
|
||||
ldr x30, [sp], 16 // 10
|
||||
mov w0, wzr // 11
|
||||
ret // 12
|
||||
// 13
|
||||
.data // 14
|
||||
fmt: .asciz "%f\n" // 15
|
||||
.end // 16
|
||||
```
|
||||
|
||||
The above code is found [here](./t.s).
|
||||
|
||||
`Line 6` puts the translated value of 1.5 into `s0` (since the value
|
||||
is a `float` it goes in an `s` register). The assembler performs the
|
||||
magic of treating the literal as if it were an address and puts it into
|
||||
`s0`.
|
||||
|
||||
`Line 7` converts the single precision number into a double precision
|
||||
number for printing.
|
||||
|
||||
*`printf()` only knows how to print double precision values. When you
|
||||
specify a `float`, it will convert it to a `double` before emitting it.*
|
||||
|
||||
Translating `floats` and `doubles` by hand isn't a common practice for
|
||||
humans, though compilers are happy to do so.
|
||||
|
||||
Instead for us humans, the assembler directives `.float` and `.double`
|
||||
are used more frequently to specify `float` and `double` values putting
|
||||
them into RAM.
|
||||
|
||||
The following example prints an array of floats and doubles:
|
||||
|
||||
```asm
|
||||
.global main // 1
|
||||
.text // 2
|
||||
.align 2 // 3
|
||||
// 4
|
||||
counter .req x20 // 5
|
||||
dptr .req x21 // 6
|
||||
fptr .req x22 // 7
|
||||
.equ max, 4 // 8
|
||||
// 9
|
||||
main: stp counter, x30, [sp, -16]! // 10
|
||||
stp dptr, fptr, [sp, -16]! // 11
|
||||
ldr dptr, =d // 12
|
||||
ldr fptr, =f // 13
|
||||
mov counter, xzr // 14
|
||||
// 15
|
||||
1: cmp counter, max // 16
|
||||
beq 2f // 17
|
||||
// 18
|
||||
ldr d0, [dptr, counter, lsl 3] // 19
|
||||
ldr s1, [fptr, counter, lsl 2] // 20
|
||||
fcvt d1, s1 // 21
|
||||
ldr x0, =fmt // 22
|
||||
add counter, counter, 1 // 23
|
||||
mov x1, counter // 24
|
||||
bl printf // 25
|
||||
b 1b // 26
|
||||
// 27
|
||||
2: ldp dptr, fptr, [sp], 16 // 28
|
||||
ldp counter, x30, [sp], 16 // 29
|
||||
mov w0, wzr // 30
|
||||
ret // 31
|
||||
// 32
|
||||
.data // 33
|
||||
fmt: .asciz "%d %f %f\n" // 34
|
||||
d: .double 1.111111, 2.222222, 3.333333, 4.444444 // 35
|
||||
f: .float 1.111111, 2.222222, 3.333333, 4.444444 // 36
|
||||
// 37
|
||||
.end // 38
|
||||
```
|
||||
|
||||
The above code is found [here](./literals.s).
|
||||
|
||||
A number of interesting things in this source code:
|
||||
|
||||
* We use `.req` to give symbolic names to various registers. This can
|
||||
help you in remembering which register is being used for what purpose.
|
||||
|
||||
* We use `.equ` to encode a small integer literal value to give it a
|
||||
symbolic name, eliminating the use of a "magic number."
|
||||
|
||||
* `Lines 19` and `20` use address arithmetic to march through an
|
||||
array of doubles (8 bytes each) and an array of floats (4 bytes each).
|
||||
|
||||
`Line 19` is equivalent to:
|
||||
|
||||
```c++
|
||||
// ldr d0, [dptr, counter, lsl 3]
|
||||
d0 = dptr[counter];
|
||||
```
|
||||
|
||||
`counter` is multiplied by 8 then added to `dptr`.
|
||||
|
||||
`Line 20` is equivalent to:
|
||||
|
||||
`counter` is multiplied byb 4 then added to `fptr`.
|
||||
|
||||
```c++
|
||||
// ldr s1, [fptr, counter, lsl 2]
|
||||
s1 = fptr[counter];
|
||||
```
|
||||
|
||||
Cool huh?
|
||||
38
section_1/float/literals.s
Normal file
38
section_1/float/literals.s
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
.global main
|
||||
.text
|
||||
.align 2
|
||||
|
||||
counter .req x20
|
||||
dptr .req x21
|
||||
fptr .req x22
|
||||
.equ max, 4
|
||||
|
||||
main: stp counter, x30, [sp, -16]!
|
||||
stp dptr, fptr, [sp, -16]!
|
||||
ldr dptr, =d
|
||||
ldr fptr, =f
|
||||
mov counter, xzr
|
||||
|
||||
1: cmp counter, max
|
||||
beq 2f
|
||||
|
||||
ldr d0, [dptr, counter, lsl 3]
|
||||
ldr s1, [fptr, counter, lsl 2]
|
||||
fcvt d1, s1
|
||||
ldr x0, =fmt
|
||||
add counter, counter, 1
|
||||
mov x1, counter
|
||||
bl printf
|
||||
b 1b
|
||||
|
||||
2: ldp dptr, fptr, [sp], 16
|
||||
ldp counter, x30, [sp], 16
|
||||
mov w0, wzr
|
||||
ret
|
||||
|
||||
.data
|
||||
fmt: .asciz "%d %f %f\n"
|
||||
d: .double 1.111111, 2.222222, 3.333333, 4.444444
|
||||
f: .float 1.111111, 2.222222, 3.333333, 4.444444
|
||||
|
||||
.end
|
||||
16
section_1/float/t.s
Normal file
16
section_1/float/t.s
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
.text
|
||||
.global main
|
||||
.align 2
|
||||
|
||||
main: str x30, [sp, -16]!
|
||||
ldr s0, =0x3fc00000
|
||||
fcvt d0, s0
|
||||
ldr x0, =fmt
|
||||
bl printf
|
||||
ldr x30, [sp], 16
|
||||
mov w0, wzr
|
||||
ret
|
||||
|
||||
.data
|
||||
fmt: .asciz "%f\n"
|
||||
.end
|
||||
Loading…
Reference in a new issue