10

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?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
poundifdef
  • 18,726
  • 23
  • 95
  • 134
  • Ask the compiler to put `150.0` in `st(0)` for you, and tell it you'll leave the result in `st(0)`: see https://stackoverflow.com/questions/37509596/using-fpu-with-c-inline-assembly. Leave the `fld` / `fst` to the compiler, so your asm only includes `"fsqrt"` + some operand constraints. – Peter Cordes Oct 26 '17 at 06:18
  • 1
    Agreed with Peter. If you really want to use inline assembly for this one could simply get _C_ to create the floating point literal and pass it into the template. You can also use the floating point constraints to put things on the top of the FPU stack and return them there. For example `double y = 150.0; __asm__ ( "fsqrt" : "+t"(y) );` – Michael Petch Oct 26 '17 at 07:26

5 Answers5

9

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.

wallyk
  • 56,922
  • 16
  • 83
  • 148
  • 2
    I think the x87 supports loading 1 and 0, if you count those as immediates ;) – tc. Jun 29 '11 at 00:39
  • 1
    @tc. There are dedicated instructions to load 1 and 0 on x87; I wouldn't call them immediates, exactly. @wallyk: ARM actually *does* have floating-point immediates in the VFP ISA (not all floating-point values are representable, however, since the immediate field is only 8 bits wide). – Stephen Canon Jun 29 '11 at 00:58
  • I would avoid this method because it's not position-independent. Instead, see my answer. – R.. GitHub STOP HELPING ICE Jun 29 '11 at 01:19
  • Hahaha, by "artificially complicated" I think you mean "learning and curiosity" on my own part! is that okay? :P (also, interestingly, the `gcc -S` output when I use math.h's `sqrt()` function both invokes `sqrt()` and calls the `sqrt` instruction, surrounded by some conditionals and jmps. But that is a separate question. – poundifdef Jun 29 '11 at 01:50
  • "I do not know of an assembly language which supports literal floating point constants for immediate use." GNU AS aarch64 does it: https://stackoverflow.com/questions/6514537/how-do-i-specify-immediate-floating-point-numbers-with-inline-assembly/52906126#52906126 and NASM has `__float32__(1.5)` minimal example: https://github.com/cirosantilli/x86-assembly-cheat/blob/9bf86360bcd26fcb7da77ce87a874446aec5f697/fadd_text_literal.asm see also: https://stackoverflow.com/questions/29925432/nasm-why-must-float32-1-5-be-used-for-floating-point-literals-instead-of-j :-) – Ciro Santilli Oct 20 '18 at 13:28
  • 1
    @CiroSantilli: That does seem to be such an ability. But it is *not* treating it as a floating point constant: As far as the instruction is concerned, it is moving a 32-bit integer. – wallyk Oct 20 '18 at 17:15
5

Try something like this

push $0x????????
push $0x????????
fldl (%esp)
addl $8,%esp

Where the ????????'s are replaced by the IEEE representation of the double constant. This method has the advantage that it works equally well in normal and position-independent (PIC, i.e. shared library) code.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • x86-64 has position-independent access to static data with `fldl myconst(%rip)`. Position-independence in 32-bit code is expensive; generally avoid it and let the runtime loader do fixups at program load time. (Of course, with x87 if your constant can be exactly represented as a 32-bit `float`, you can save space with only one `push` and a `flds` single-precision load. Either way it's converted to the internal 80-bit representation.) – Peter Cordes Oct 22 '18 at 00:51
2

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.

Community
  • 1
  • 1
Ciro Santilli
  • 3,693
  • 1
  • 18
  • 44
1

The only valid operands for the fld instruction are memory or a floating-point stack register.

(Also, you have specified y as an input operand for the asm block, whereas it should be an output. Probably safer to constrain that to being memory ("m", rather than "g") as well.)

If you really want to do this with inline assembly:

#include <stdio.h>

int main(void)
{
   double y;
   const double k = 150.0;

   __asm__ ("fldl %1;"
            "fsqrt;"
            "fstl %0;" : "=m" (y) : "m" (k) );

   printf("%f\n", y);

   return 0;
}
Matthew Slattery
  • 45,290
  • 8
  • 103
  • 119
  • Seems kinda pointless to ask for the constant in memory instead of asking for it on the top of the FP stack and letting the compiler emit `fld`. Any time the first and/or last instructions in your asm template are a mov or other load/store, you're usually doing it wrong. See https://stackoverflow.com/questions/39728398/how-to-specify-clobbered-bottom-of-the-x87-fpu-stack-with-extended-gcc-assembly and especially [this, wrapping `fadds` with x87 register output, input from st(0) and memory](https://stackoverflow.com/questions/37509596/using-fpu-with-c-inline-assembly). – Peter Cordes Oct 26 '17 at 06:16
-3

You can bypass many assemblers refusing to support float literals by having PHP preprocess the literal for you. (rawSingleHex taken from here). In a perfect world C preprocessor would be enough, but that's not true at the moment.

<?php
function rawSingleHex($num) {
    return '0x' . strrev(unpack('h*', pack('f', $num))[1]);
}
?>

#include <stdio.h>

int main(int argc, char **argv) {
   float y = 0;

   __asm__ ("pushl $<?php echo rawSingleHex(150);?>\n"
            "fsqrt\n"
            "fstl %0\n" : : "g" (y));

   printf("%f\n", y);


   return 0;
}

run php to generate the c file, and run c compiler to compile your program :P

Dmytro
  • 5,068
  • 4
  • 39
  • 50
  • 2
    My eyes, they hurt! :-) – Ciro Santilli Oct 20 '18 at 13:37
  • This doesn't even work; `pushl` pushes onto the call-stack (memory pointed-to by %ESP), not the x87 register stack. Also, the output operand is listed as an input, so with optimization a 32-bit compiler will do constant-propagation and use an integer push to push a zero arg for printf, regardless of the `asm` statement corrupting the memory (or register) value of `y`. – Peter Cordes Jun 22 '19 at 16:49