C2 - Anti-dependences

写点C2中memory subgraph中涉及的反依赖问题。没有提纲,心意所至,笔向所指。

有这样一段java代码...

 static class Foo {
     public int a = 2;
     public int b = 4;
 }

Foo test(int x) {
    Foo f1 = new Foo();
    Foo f2 = new Foo();
    f1.a = 42; // LINE9
    f2.a = f1.a;  // LINE10
    f1.a = 24; // LILNE11
    return f1;
}

它对应的ir局部如下,这里我们只关心line9-11:

C2 - Anti-dependences

C2稍微做了些优化,将line10变成了f2.a=42,三条赋值分配对应188#StoreI,192#StoreI和195#StoreI,它们依次执行,和代码顺序一致,这里有个问题?C2如何构造IR的时候如何知道192#StoreI的in(Memory)是188#StoreI,答案就是通过alias type。

简单来说,alias type用来描述memory slice信息,它可以不同粒度的描述memory slice。比如下面的例子,f.a和f2.a访问的memory slice的alias type一样(Reduced\(Foo+12),f.b和f2.b同理alias type一样(Reduced\)Foo+16)。

int test() {
    // @ <2>     +any in BotPTR *+bot
    // @ <3>     +0   in rawptr:BotPTR
    // @ <4>     +0   in java/lang/Object *
    // @ <5>  RO +8   in java/lang/Object+8 * [narrowklass]
    // @ <6>     +12  in Reduced$Foo+12 *
    // @ <7>     +16  in Reduced$Foo+16 *
    // @ <8>  RO +172 in klass java/lang/Object: 0x00007fff54006cb8+172 *
    Foo f = new Foo();
    Foo f2 = new Foo();
    f.a  =12;
    f.b = 23;
    f2.a = 6;
    f2.b = 7;
    return f.a+f2.b;
}

回到例子中,由于三个赋值访问的都是Foo#a字段,所以它们相关。甚至,我们可以把上面的赋值改成:
f1.a=42; f2.a=25;
生成的StoreI也会关联起来

C2 - Anti-dependences

上面的情况中,赋值语句的rhs都是常量,但是很多情况下rhs也可以是一个读内存值,这个时候我们需要一个LoadX节点,它的输入是一个内存地址,得到的是内存地址里面的值,行为和解引用一样。下面的java代码:

static int i=2,j=3,k=9;
int test(int x) {
    j = i;     // 30#StoreI
    i = k;     // 35#StoreI
    return i ; // at this point, i==9,j==2
}

35#StoreI相当于i=k,30#StoreI相当于j=i,具体点27#LoadI是读i的值,33#LoadI是读k的值。执行流程如下:

  1. 执行27#LoadI,读取i的值,相当于tmp=i 。此时tmp==2
  2. 执行35#StoreI,读取k的值,然后赋值给i所在内存。此时i == 9
  3. 执行30#StoreI,将27#LoadI读取到的i的值赋值给j,相当于j=tmp。此时j==2
  4. 最终 i==9,j==2

C2 - Anti-dependences

上面的IR有个比较严重的问题:27#LoadI(读i值)和35#StoreI(给i赋值)访问的memory slice的alias type是相同的,但是两者没有依赖关系的。想象一下,假如先执行35#StoreI,后执行27#LoadI会发生什么?

  1. 执行35#StoreI,读取k的值,然后赋值给i所在内存。此时i == 9
  2. 执行27#LoadI,读取i的值,相当于tmp=i 。此时tmp==9
  3. 执行30#StoreI,将27#LoadI读取到的i的值赋值给j,相当于j=tmp。此时j==9
  4. 最终i==9,j==9

35#StoreI和27#LoadI没有依赖关系,不同的执行顺序可能导致不同的结果。

要解决这个问题,我们需要插入反依赖(PhaseCFG::insert_anti_dependences),这一步是在GCM阶段完成,GCM之后的IR如下图

C2 - Anti-dependences

可以看到8#storeI多了一条12#loadI的输入。当执行8#storeI的时候强制要求读取k和i的值,保证了12#lloadI先于8#storeI执行。这条从12#loadI流出到8#storeI的表就叫做反依赖,注意反依赖说的是load依赖store,主体是load。C2在处理反依赖问题的时候,除了插入反依赖的边之外,还有一种解决方案,它也可能将load节点所在的LCA block强制上移,使得load节点可以放置的early block-LCA block均处于store节点之上,用这样的方式来保证store不会先于load执行。

上一篇:F2前端页面分析


下一篇:2021SC@SDUSC F2移动端数据可视化源码分析(三)