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