优化屏障(Optimization barrier)第二讲

1. gcc编译的大致过程

可以看到,gcc优化主要分两大部分:Tree优化RTL(Register Transfer Language)优化

前文所说的指令调度(Instruction scheduling)即为RTL优化的一部分。

2. 从RTL指令调度出发,追寻Optimization barrier的踪迹

还是从实验出发,实验代码如下:

1
2
3
4
5
6
7
8
volatile int ready;
int message[100];
 
void cmb ( int i) {
     message[i/10] = 42;
     __asm__ __volatile__ ( "" ::: "memory" );
     ready = 1;
}

老的gcc可以-dr输出过程中的rtl文件,现在这个版本-dr好像不认识,所以只好先用-da输出全部信息,即gcc -da basic_cmb.c
执行后发现生成一大堆basic_cmb.c.XXXr.YYY文件,我们找到basic_cmb.c.128r.expand即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
;;
;; Full RTL generated for this function: // ;; 表注释
;;
(note 1 0 3 NOTE_INSN_DELETED)   // 调试和说明信息,不用关心
 
;; Start of basic block ( 0) -> 2
;; Pred edge  ENTRY (fallthru)
(note 3 1 2 2 [bb 2] NOTE_INSN_BASIC_BLOCK) // 调试和说明信息,不用关心
 
(note 2 3 4 2 NOTE_INSN_FUNCTION_BEG) // 调试和说明信息,不用关心
;; End of basic block 2 -> ( 3)
 
;; Succ edge  3 [100.0%]  (fallthru)
 
;; Start of basic block ( 2) -> 3
;; Pred edge  2 [100.0%]  (fallthru)
(note 4 2 5 3 [bb 3] NOTE_INSN_BASIC_BLOCK) // 调试和说明信息,不用关心
 
(insn 5 4 6 3 basic_cmb.c:5 (set (reg:SI 59) // virtual-incoming-args表示入参,即reg59=入参
         (mem/c/i:SI (reg/f:SI 53 virtual -incoming-args) [0 i+0 S4 A32])) -1 (nil))
 
(insn 6 5 7 3 basic_cmb.c:5 (set (reg:SI 61) // reg61=1717986919
         (const_int 1717986919 [0x66666667])) -1 (nil))
 
(insn 7 6 8 3 basic_cmb.c:5 (parallel [ // ((reg59 * reg61) << 32)取高32位,
             (set (reg:SI 60) // 再赋值给reg60
                (truncate:SI (lshiftrt:DI (mult:DI (sign_extend:DI (reg:SI 59))
                                                   (sign_extend:DI (reg:SI 61)))
                                                   (const_int 32 [0x20])))) // 即取reg59和reg61乘积的高32位,等效于imull指令             
                             (clobber (scratch:SI))            
                             (clobber (reg:CC 17 flags))]) -1 (nil))
 
(insn 8 7 9 3 basic_cmb.c:5 (parallel [    // reg62 = reg60 >> 2
             (set (reg:SI 62)
                 (ashiftrt:SI (reg:SI 60)
                     (const_int 2 [0x2])))
             (clobber (reg:CC 17 flags))
         ]) -1 (nil))
 
(insn 9 8 10 3 basic_cmb.c:5 (parallel [  // reg63 = reg59 >> 31
             (set (reg:SI 63)
                 (ashiftrt:SI (reg:SI 59)
                     (const_int 31 [0x1f])))
             (clobber (reg:CC 17 flags))
         ]) -1 (nil))
 
(insn 10 9 11 3 basic_cmb.c:5 (parallel [
             (set (reg:SI 58 [ D.1250 ])  // reg58 = reg62 - reg63
                 (minus:SI (reg:SI 62)
                     (reg:SI 63)))
             (clobber (reg:CC 17 flags))
         ]) -1 (expr_list:REG_EQUAL ( div :SI (reg:SI 59) // 表示reg59/10由上面的
             (const_int 10 [0xa]))        //reg58 = reg62 -reg63替代
         (nil)))  // 即它们是等效的,从这个点也可以看出编译器对除法做出的优化
 
(insn 11 10 12 3 basic_cmb.c:5 (set (mem/s/j:SI (plus:SI (mult:SI (reg:SI 58 [ D.1250 ]) // reg58 * 4 + message = 42
                     (const_int 4 [0x4]))
                 (symbol_ref:SI ( "message" ) )) [0 message S4 A32]) //其实这个已经和movl $42, message(,%eax,4)相对应了
         (const_int 42 [0x2a])) -1 (nil))
 
(insn 12 11 13 3 basic_cmb.c:6 (parallel [  // __asm__ __volatile__ ("" ::: "memory");出现在这里
             (asm_operands/v ( "" ) ( "" ) 0 []
                  [] 736)
             (clobber (reg:QI 18 fpsr))
             (clobber (reg:QI 17 flags))
             (clobber (mem:BLK (scratch) [0 A8]))
         ]) -1 (nil))
 
(insn 13 12 18 3 basic_cmb.c:7 (set (mem/v/c/i:SI (symbol_ref:SI ( "ready" ) ) [0 ready+0 S4 A32]) // movl  $1, ready
         (const_int 1 [0x1])) -1 (nil))
;; End of basic block 3 -> ( 4)
 
;; Succ edge  4 (fallthru)
 
;; Start of basic block ( 3) -> 4
;; Pred edge  3 (fallthru)
(note 18 13 15 4 [bb 4] NOTE_INSN_BASIC_BLOCK) // 调试和说明信息,不用关心
 
(jump_insn 15 18 16 4 basic_cmb.c:8 (set (pc) // goto label 17
         (label_ref 17)) -1 (nil))
;; End of basic block 4 -> ( 6)
 
;; Succ edge  6
 
(barrier 16 15 14)    // 栅栏
 
;; Start of basic block () -> 5
(code_label 14 16 19 5 1 "" [0 uses])  // label 14
 
(note 19 14 17 5 [bb 5] NOTE_INSN_BASIC_BLOCK) // 调试和说明信息,不用关心
;; End of basic block 5 -> ( 6)
 
;; Succ edge  6 (fallthru)
 
;; Start of basic block ( 4 5) -> 6
;; Pred edge  4
;; Pred edge  5 (fallthru)
(code_label 17 19 20 6 2 "" [1 uses])  // label 17
 
(note 20 17 0 6 [bb 6] NOTE_INSN_BASIC_BLOCK) // 调试和说明信息,不用关心
;; End of basic block 6 -> ( 1)
 
;; Succ edge  EXIT [100.0%]  (fallthru)

有关insns的介绍在http://www.lingcc.com/gccint/Insns.html#Insns
rtl文件有多个insn组成,insns表示的灵感即来自于LISP,当然,肯定不是规范的LISP语法,但至少”;;”作为注释这一点还是相同的,这里大致介绍一下各个insns的作用:

  • note用于表示额外的调试和说明信息
    如:(note 1 0 3 NOTE_INSN_DELETED)
    1 0 3分别表示当前insns的id为1,上一条insns的id为0,下一条为3
    NOTE_INSN_DELETED这样的注解被完全忽略掉。编译器的一些过程会通过将insn修改成这种类型的注解,来删除insn。
    本文并不是为了讲解RTL的,所以这里只是简单提一下,既然note是调试相关的信息,我们就暂时不关心它了。
  • insn
    表达式代码insn用于不进行跳转和函数调用的指令。sequence表达式总是包含在表达式代码为insn的insn中,即使它们中的一个insn是跳转或者函数调用。
    如:(insn 13 12 18 3 basic_cmb.c:7 (set (mem/v/c/i:SI (symbol_ref:SI (“ready”) ) [0 ready+0 S4 A32]) (const_int 1 [0x1])) -1 (nil))
    13 12 18和note一样,分别表示当前id,前一条id和后一条id;后面的3则表示所属的基本块id;
    basic_cmb.c:7表示为当前insns对应basic_cmb.c文件中的第7行;后面一大串内容要解释起来也花上很大篇幅,可以参考http://www.lingcc.com/gccint/;不过应该还是比较容易看懂这句在干嘛:set ready const_int 1,自然对应于ready=1的语句。
  • barrier
    栅栏被放在指令流中,控制无法经过的地方。它们被放在无条件跳转指令的后面,表示跳转是无条件的,以及对volatile函数的调用之后,表示不会返回(例如,exit)。除了三个标准的域以外,不包含其它信息。
  • code_label即表示标签label
  • jump_insn
    表达式代码jump_insn用于可能执行跳转(或者,更一般的讲,指令中可能包含了label_ref表达式,并用其来设置pc)的指令。如果有一条从当前函数返回的指令,则其被记录为jump_insn。
    如:(jump_insn 15 18 16 4 basic_cmb.c:8 (set (pc) (label_ref 17)) -1 (nil));设置pc寄存器为17号label引用,即相当于goto label 17

从rtl中可以看到,每个insns通过当前id,前一条id和后一条id可以形成一条链,把所有insns链起来未免过长,由于我们的主要研究对象是Optimization barrier,所以就从insns11开始:

从图中可以看到,在RTL这一层,我们的Optimization barrier还是存在的,所以可以大胆的猜测是在RTL优化的过程中它贡献了自己的力量。

RTL(Register Transfer Language)优化指令调度相关说明可以看到gcc实现部分相关代码:haifa-sched.c, sched-deps.c, sched-ebb.c, sched-rgn.c和sched-vis.c。

先不用追根溯源,gcc大庞大,反而会摸不着头脑的,Optimization barrier对应的insns为:

1
2
3
4
5
6
7
(insn 12 11 13 3 basic_cmb.c:6 (parallel [
             (asm_operands/v ( "" ) ( "" ) 0 []
                  [] 736)
             (clobber (reg:QI 18 fpsr))
             (clobber (reg:QI 17 flags))
             (clobber (mem:BLK (scratch) [0 A8]))
         ]) -1 (nil))

可以先参照http://www.lingcc.com/gccint/Side-Effects.html#Side-Effects
http://www.lingcc.com/gccint/Flags.html#Flags
不过,还是针对这条insn做一个较详细的解释吧:

  • asm_operands:表这是一条asm操作指令
  • /v:表含有volatile修饰符,所以asm_operands/v就和__asm__ __volatile__相对应
  • reg:表寄存器
  • QI:Quarter-Integer模式,表示一个一字节的整数
  • fpsr:float point state register,表浮点状态寄存器,所以reg:QI 18 fpsr表这是一个字节大小的浮点状态寄存器,引用是18
  • flags:通用状态寄存器,所以reg:QI 17 flags表这是一个字节大小的通用状态寄存器,引用是17
  • mem:表内存
  • BLK:“Block”模式,表示其它模式都不适用的聚合值
  • (clobber x)
    表示一个不可预期的存储或者可能的存储,将不可描述的值存储到x,其必须为一个reg,scratch, parallel 或者 mem表达式。
    可以用在字符串指令中,将标准的值存储到特定的硬件寄存器中。不需要去描述被存储的值,只用来告诉编译器寄存器被修改了,以免其尝试在字符串指令中保持数据。如果x为(mem:BLK (const_int 0))或者(mem:BLK (scratch)),则意味着所有的内存位置必须假设被破坏。

所以综合来讲,这条指令的意思是:volatile修饰的asm操作指令,并告知编译器这条指令会改变状态寄存器和内存,正好应对

1
__asm__ __volatile__ ( "" ::: "memory" )

针对这一点,我们还可以做一个实验:

1
2
3
4
5
6
7
8
9
10
11
#include
int main( void )
{
         int foo = 10, bar = 15;
         __asm__ __volatile__( "addl  %%ebx,%%eax"
                              : "=a" (foo)
                              : "a" (foo), "b" (bar)
                              );
         printf ( "foo+bar=%d\n" , foo);
         return 0;
}

asm语句对应的insn为(如对asm不熟悉,可以参照http://ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html):

1
2
3
4
5
6
7
8
9
10
11
12
13
(insn 9 8 10 asm.c:5 (parallel [
             (set (reg:SI 61)
                 (asm_operands/v:SI ( "addl  %%ebx,%%eax" ) ( "=a" ) 0 [
                         (reg:SI 62)
                         (reg:SI 63)
                     ]
                      [
                         (asm_input:SI ( "a" ) 0)
                         (asm_input:SI ( "b" ) 0)
                     ] 596358))
             (clobber (reg:QI 18 fpsr))
             (clobber (reg:QI 17 flags))
         ]) -1 (nil))

这条insn更为详细,因为未加”memory”所以,只默认告知编译器状态寄存器的改变。
我们再做一个实验来应证这一点:

1
2
3
4
5
6
7
8
volatile int ready;
int message[100];
 
void cmb ( int i) {
     message[i/10] = 42;
     __asm__ __volatile__ ( "" ::: "cc" );
     ready = 1;
}

将”memory”修改为”cc”后,发现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.globl cmb
     .type   cmb, @function
cmb:
     pushl   %ebp
     movl    %esp, %ebp
     movl    8(%ebp), %ecx
     movl    $1717986919, %edx
     movl    $1, ready
     movl    %ecx, %eax
     imull   %edx
     movl    %ecx, %eax
     sarl    $31, %eax
     sarl    $2, %edx
     movl    %edx, %ecx
     subl    %eax, %ecx
     movl    %ecx, %eax
     movl    $42, message(,%eax,4)
     popl    %ebp
     ret

这条语句是不起作用的,所以,是这个”memory”起到了关键作用,下面我们就重点关注”memory”产生的影响。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值