regmove pass with x87 sincos() instruction

Uros Bizjak uros@kss-loka.si
Fri Apr 2 07:13:00 GMT 2004


Hello!

I'm working on a patch, which combines sin() and cos() instruction with 
the same argument into sincos() instruction. The patch somehow works, 
and it is able to generate quite optimized code. For example, from this 
testcase:

double sincos_test(double a) {
        double x,y;

        x = sin(a);
        x /= 2.0;
        y = cos(a);
        y *= y;

        return  x+y;
}

when compiled with gcc-3.5 -O2 -ffast-math -fnew-ra -fomit-frame-pointer 
-S, this asm is produced:
sincos_test:
        fldl    4(%esp)
        fsincos
        flds    .LC0
        fmulp   %st, %st(2)
        fmul    %st(0), %st
        faddp   %st, %st(1)
        ret

To achieve this, x87 sincos instruction has to be defined as this RTL:
(define_insn "sincosdf3"
  [(set (match_operand:DF 0 "register_operand" "=f")
    (unspec:DF [(match_operand:DF 2 "register_operand" "0")]
           UNSPEC_SINCOS_COS))
   (set (match_operand:DF 1 "register_operand" "=u")
        (unspec:DF [(match_dup 2)] UNSPEC_SINCOS_SIN))]
  "! TARGET_NO_FANCY_MATH_387 && TARGET_80387
   && flag_unsafe_math_optimizations"
  "fsincos"
  [(set_attr "type" "fpspc")
   (set_attr "mode" "DF")])

Sin() and cos() builtins  instantiate fsincos() builtin, with one of its 
output registers unused (as it is a case with divmod instructions). 
After this, cse pass detects that two instructions can be combined into 
one instruction, and produces:
(insn 10 4 11 0 (parallel [
            (set (reg:DF 62)
                (unspec:DF [
                        (reg/v:DF 59 [ a ])
                    ] 80))
            (set (reg:DF 63)
                (unspec:DF [
                        (reg/v:DF 59 [ a ])
                    ] 81))
        ]) 421 {sincosdf3} (nil)
    (nil))

With this, stack pass can produce expected fsincos instruction and 
generated asm code is OK.
(insn:TI 10 3 48 0 (parallel [
            (set (reg:DF 8 st)
                (unspec:DF [
                        (reg:DF 8 st)
                    ] 80))
            (set (reg:DF 9 st(1))
                (unspec:DF [
                        (reg:DF 8 st)
                    ] 81))
        ]) 421 {sincosdf3} (insn_list 3 (nil))
    (nil))

When sin() and cos() can't be combined, then life analysis detects that 
one of the output registers remains unused:
(insn 10 4 19 0 (parallel [
            (set (reg:DF 62)
                (unspec:DF [
                        (reg/v:DF 59 [ x ])
                    ] 80))
            (set (reg:DF 61)
                (unspec:DF [
                        (reg/v:DF 59 [ x ])
                    ] 81))
        ]) 421 {sincosdf3} (insn_list 3 (nil))
    (expr_list:REG_DEAD (reg/v:DF 59 [ x ])
        (expr_list:REG_UNUSED (reg:DF 62)
            (nil))))

This case is detected in peephole2 pass. To convert sincos RTL back to 
sin and cos RTL, two peephole2 definitions are defined:
(define_peephole2
  [(parallel [(set (match_operand:DF 0 "register_operand" "")
           (unspec:DF [(match_operand:DF 2 "register_operand" "")]
                  UNSPEC_SINCOS_COS))
          (set (match_operand:DF 1 "register_operand" "")
           (unspec:DF [(match_dup 2)] UNSPEC_SINCOS_SIN))])]
  "find_regno_note (insn, REG_UNUSED, REGNO (operands[0]))"
  [(set (match_operand:DF 1 "register_operand" "")
    (unspec:DF [(match_operand:DF 2 "register_operand" "")] UNSPEC_SIN))]
  "")

(define_peephole2
  [(parallel [(set (match_operand:DF 0 "register_operand" "")
           (unspec:DF [(match_operand:DF 2 "register_operand" "")]
                  UNSPEC_SINCOS_COS))
          (set (match_operand:DF 1 "register_operand" "")
           (unspec:DF [(match_dup 2)] UNSPEC_SINCOS_SIN))])]
  "find_regno_note (insn, REG_UNUSED, REGNO (operands[1]))"
  [(set (match_operand:DF 0 "register_operand" "")
    (unspec:DF [(match_operand:DF 2 "register_operand" "")] UNSPEC_COS))]
  "")

Conversion from sincos RTL into cos RTL works OK, because input and 
output arguments are both the same register (on top of stack). The 
problem is in sincos RTL to sin RTL conversion. Peephole2 pass produces 
this RTL, which does not satisfy sin RTL constraints:
(insn 38 4 19 0 (set (reg:DF 9 st(1) [61])
        (unspec:DF [
                (reg/v:DF 8 st [orig:59 x ] [59])
            ] 22)) -1 (nil)
    (expr_list:REG_DEAD (reg/v:DF 8 st [orig:59 x ] [59])
        (nil)))

sin RTL wants its input and output argument to be the same register, but 
here input and output arguments are in different registers.
The problem is in regmove pass. This pass finds correct register for cos 
case:
(insn:HI 10 4 19 0 (parallel [
            (set (reg/v:DF 59 [ x ])
                (unspec:DF [
                        (reg/v:DF 59 [ x ])
                    ] 80))
            (set (reg:DF 62)
                (unspec:DF [
                        (reg/v:DF 59 [ x ])
                    ] 81))
        ]) 421 {sincosdf3} (insn_list 3 (nil))
    (expr_list:REG_UNUSED (reg:DF 62)
        (nil)))

but for sin case, extra move is inserted:
(insn:HI 10 4 19 0 (parallel [
            (set (reg:DF 62)
                (unspec:DF [
                        (reg/v:DF 59 [ x ])
                    ] 80))
            (set (reg:DF 61)
                (unspec:DF [
                        (reg/v:DF 59 [ x ])
                    ] 81))
        ]) 421 {sincosdf3} (insn_list 3 (nil))
    (expr_list:REG_DEAD (reg/v:DF 59 [ x ])
        (expr_list:REG_UNUSED (reg:DF 62)
            (nil))))

(note:HI 19 10 22 0 NOTE_INSN_FUNCTION_END)

(insn:HI 22 19 25 0 (set (reg/i:DF 8 st [ <result> ])
        (reg:DF 61)) 65 {*movdf_nointeger} (insn_list 10 (nil))
    (expr_list:REG_DEAD (reg:DF 61)
        (nil)))

I would like to ask somebody with more knowledge in this area, if there 
is a way to make some kind of peephole2-like optimization _before_ 
regmove pass, which would detect unused register and in this case 
convert sincos() instruction back to sin() instruction before regmove 
starts to insert move instructions.

If anybody is interested, I can send necessary infrastructure patches 
for optabs.c to expand optab with one input and two outputs, patch for 
reg-stack.c to handle sincos RTL, patch for builtins.c to expand sin and 
cos optab as sincos optab and patch to i386.md. They are currently WIP.

Thanks,
    Uros.



More information about the Gcc mailing list