Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
836 views
in Technique[技术] by (71.8m points)

c - Assembly code fsqrt and fmul instructions

I'm trying to compute 1.34 *sqrt(lght) in this function using inline assembly but I'm getting errors like:

'_asm' undeclared (first use in this function) each undeclared identifier is reported only once for each func tion it appears in expected ';' before '{' token


I have been researching how to solve this problem but can't find much information. Can someone suggest a way to get this to work?

My code is:

   double hullSpeed(double lgth) {
       _asm {
           global _start
           fld lght; //load lght
           fld st(0); //duplicate lght on Top of stack
           fsqrt;
           square root of lght
           fld st(0); //load square result on top of stack
           fld 1.34; //load 1.34 on top of stack
           fld st(i);
           duplicate 1.34 on TOS
           fmulp st(0), st(i); //multiply them 
           fst z;
           save result in z
       }
       return z; // return result of [ 1.34 *sqrt(lght) ]
   }

Im using Ubuntu so GCC, 32 bits

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

It looks like you are trying to do something similar to this:

#include <stdio.h>

double hullSpeed(double lgth)
{
    double result;

    __asm__(
            "fldl %1
" //st(0)=>st(1), st(0)=lgth . FLDL means load double float
            "fsqrt
"   //st(0) = square root st(0)
            "fmulp
"   //Multiplies st(0) and st(1) (1.34). Result in st(0)
            : "=&t" (result) : "m" (lgth), "0" (1.34));

    return result;
}

int main()
{
    printf ("%f
", hullSpeed(64.0));
}

The template I used can be simplified, but for demonstration purposes it will suffice. We use "=&t" constraint since we are returning the result at the top of the floating point stack in st(0), and we use ampersand to denote early clobber (we'll be using the top of the floating point stack to pass in 1.34). We pass the address of lgth with a memory reference via the constraint "m" (lgth), and the "0"(1.34) constraint says we will pass in 1.34 in the same register as parameter 0, which in this case is the top of the floating point stack. These are registers(or memory) that our assembler will overwrite but don't appear as an input or output constraint.

Learning assembly language with inline assembler is a very difficult way to learn. The machine constraints specific to x86 can be found here under x86 family. Information on the constraint modifiers can be found here, and information on GCC extended assembler templates can be found here.

I'm only giving you a starting point, as GCC's inline assembler usage can be rather complex and any answer may be too broad for a Stackoverflow answer. The fact you are using inline assembler with x87 floating point makes it that much more complex.


Once you have a handle on constraints and modifiers another mechanism that would yield better assembler code by the compiler would be:

__asm__(
        "fsqrt
"   // st(0) = square root st(0)
        "fmulp
"   // Multiplies st(0) and st(1) (1.34). Result in st(0)
        : "=t"(result) : "0"(lgth), "u" (1.34) : "st(1)");

Hint: Constraint "u" places a value in x87 floating point register st(1). The assembler template constraints effectively place lgth in st(0) and 1.34 in st(1). st(1) is invalid after the inline assembly is complete so we list it as a clobber. We use the constraints to place our values on the floating point stack for us. This has the effect of reducing the work we have to do inside the assembler code itself.


If you are developing 64-bit applications I highly recommend using SSE/SSE2 at a minimum for basic floating point calculations. The code above should work on 32-bit and 64-bit. In 64-bit code the x87 floating point instructions are generally not as efficient as SSE/SSE2, but they will work.


Rounding with Inline Assembly and x87

If you are attempting to round based on one of the 4 rounding modes on the x87 you can utilize code like this:

#include <stdint.h>
#include <stdio.h>

#define RND_CTL_BIT_SHIFT   10

typedef enum {
    ROUND_NEAREST_EVEN =    0 << RND_CTL_BIT_SHIFT,
    ROUND_MINUS_INF =       1 << RND_CTL_BIT_SHIFT,
    ROUND_PLUS_INF =        2 << RND_CTL_BIT_SHIFT,
    ROUND_TOWARD_ZERO =     3 << RND_CTL_BIT_SHIFT
} RoundingMode;

double roundd (double n, RoundingMode mode)
{
    uint16_t cw;        /* Storage for the current x87 control register */
    uint16_t newcw;     /* Storage for the new value of the control register */
    uint16_t dummyreg;  /* Temporary dummy register used in the template */

    __asm__ __volatile__ (
            "fstcw %w[cw]          
" /* Read current x87 control register into cw*/
            "fwait                 
" /* Do an fwait after an fstcw instruction */
            "mov %w[cw],%w[treg]   
" /* ax = value in cw variable*/
            "and $0xf3ff,%w[treg]  
" /* Set rounding mode bits 10 and 11 of control
                                            register to zero*/
            "or %w[rmode],%w[treg] 
" /* Set the rounding mode bits */
            "mov %w[treg],%w[newcw]
" /* newcw = value for new control reg value*/
            "fldcw %w[newcw]       
" /* Set control register to newcw */
            "frndint               
" /* st(0) = round(st(0)) */
            "fldcw %w[cw]          
" /* restore control reg to orig value in cw*/
            : [cw]"=m"(cw),
              [newcw]"=m"(newcw),
              [treg]"=&r"(dummyreg),  /* Register constraint with dummy variable
                                         allows compiler to choose available register */
              [n]"+t"(n)              /* +t constraint passes `n` through 
                                         top of FPU stack (st0) for both input&output*/
            : [rmode]"rmi"((uint16_t)mode)); /* "g" constraint same as "rmi" */

    return n;
}

double hullSpeed(double lgth)
{
    double result;

    __asm__(
            "fsqrt
"   // st(0) = square root st(0)
            "fmulp
"   // Multiplies st(0) and st(1) (1.34). Result in st(0)
            : "=t"(result) : "0"(lgth), "u" (1.34) : "st(1)");
    
    return result;
}

int main()
{
    double dbHullSpeed = hullSpeed(64.0);
    printf ("%f, %f
", dbHullSpeed, roundd(dbHullSpeed, ROUND_NEAREST_EVEN));
    printf ("%f, %f
", dbHullSpeed, roundd(dbHullSpeed, ROUND_MINUS_INF));
    printf ("%f, %f
", dbHullSpeed, roundd(dbHullSpeed, ROUND_PLUS_INF));
    printf ("%f, %f
", dbHullSpeed, roundd(dbHullSpeed, ROUND_TOWARD_ZERO));
    return 0;
}

As you pointed out in the comments, there was equivalent code in this Stackoverflow answer but it used multiple __asm__ statements and you were curious how a single __asm__ statement could be coded.

The rounding modes (0,1,2,3) can be found in the Intel Architecture Document:

Rounding Mode RC Field

00B Rounded result is the closest to the infinitely precise result. If two values are equally close, the result is the even value (that is, the one with the least-significant bit of zero). Default Round down (toward ?∞)

01B Rounded result is closest to but no greater than the infinitely precise result. Round up (toward +∞)

10B Rounded result is closest to but no less than the infinitely precise result.Round toward zero (Truncate)

11B Rounded result is closest to but no greater in absolute value than the infinitely precise result.

In section 8.1.5 (rounding mode specifically described in section 8.1.5.3) there is a description of the fields. The 4 rounding modes are defined in figure 4-8 under section 4.8.4.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...