When I try to compile this code:
#include <stdio.h>
main(int argc, char *argv[]) {
double y = 0;
__asm__ ("fldl $150;"
"fsqrt;"
"fstl %0;" : : "g" (y) );
printf("%f\n", y);
return 0;
}
I get this error:
sqrt.c: Assembler messages:
sqrt.c:6: Error: suffix or operands invalid for `fld'
Why doesn't this work? Why can't I push the number "150" onto the stack for floating point operations?
In assembly language, there are at least two standard formats for floating-point numbers: short and long. Short floating-point (32 bits): The first bit is the sign bit: 0 for positive and 1 for negative. The next 7 bits are the exponent: -64 to +63, stored as 0 to 127.
The __asm keyword invokes the inline assembler and can appear wherever a C or C++ statement is legal. It cannot appear by itself. It must be followed by an assembly instruction, a group of instructions enclosed in braces, or, at the very least, an empty pair of braces.
asm volatile ("" ::: "memory") AFAIK is the same as the previous. The volatile keyword tells the compiler that it's not allowed to move this assembly block. For example, it may be hoisted out of a loop if the compiler decides that the input values are the same in every invocation.
t
constraint
According to the GCC docs https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html#Machine-Constraints
t
Top of 80387 floating-point stack (%st(0)).
So we can do:
#include <assert.h>
int main(void) {
double io = 4.0;
__asm__ (
"fsqrt"
: "+t" (io)
:
:
);
assert(io == 2.0);
return 0;
}
GitHub upstream.
Refresher: +
means that io
will be used both as input and output.
Tested in Ubuntu 19.04.
GNU GAS ARM assembly supports it
E.g. in ARMv8:
main.c
#include <assert.h>
int main(void) {
float my_float = 1.5;
__asm__ (
"fmov s0, 1.0;"
"fadd %s[my_float], %s[my_float], s0;"
: [my_float] "+w" (my_float)
:
: "s0"
);
assert(my_float == 2.5);
}
GitHub upstream.
Compile and run:
aarch64-linux-gnu-gcc -o main.out -static -std=gnu99 main.c
qemu-aarch64 ./main.out
The %s
modifier is mentioned at: ARMv8 floating point output inline assembly
It also works on ARMv7.
However, for some reason, it only works for floating point instructions such as fmov
, e.g. the following ARMv7 attempt fails to assemble:
mov r0, 1.5
with error:
Error: garbage following instruction -- `mov r0,1.5'
presumably because it uses the mov
instruction, which acts on general purpose registers instead of floating point ones.
However perhaps this doesn't matter much, as for the most part you just want to do your floating operations on your floating point registers, and then do an fcmp
followed by vmrs
as in:
vmov s0, 1.5
vmov s1, 2.5
fadds s2, s0, s1
vmov s3, 4.0
/* Compare two floating point registers. Stores results in fpscr:
* (floating point status and control register).
*/
vcmp.f32 s2, s3
/* Move the nzcv bits from fpscr to apsr */
vmrs apsr_nzcv, fpscr
/* This branch uses the Z bit of apsr, which was set accordingly. */
beq theyre_equal
GitHub upstream.
It never ceases to amuse me how GNU GAS has subtly different syntax for every arch!
I could not however find a hex float literal syntax: How to use hexadecimal floating point literals in GNU GAS?
Tested on Ubuntu 18.04.
I do not know of an assembly language which supports literal floating point constants for immediate use. The usual means is to declare initialized storage containing the floating point constant and referencing it:
const1: dq 1.2345
...
fldl const1
For the example you give, it is possible to do this more directly:
printf ("%f\n", sqrt (150));
Otherwise, this must be an artificially complicated project, perhaps homework.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With