之前遇到了一个hotspot的bug:
https://bugs.openjdk.java.net/browse/JDK-8240576bugs.openjdk.java.net这个BUG的测试用例如下:
// java -XX:-TieredCompilation -XX:CompileCommand=compileonly,Test::testMethod
例子比较简单,主要就是包含了三重循环。如果入口是从函数的第一行(或者说是第一个字节码)开始,那么显然,它的CFG长成以下样子:
![57218c40e75afb365e9de7f60fa6d611.png](https://img-blog.csdnimg.cn/img_convert/57218c40e75afb365e9de7f60fa6d611.png)
但是因为存在OSR机制[1],导致建图的时候并不是从第一个字节码开始的,而是从第二层循环处开始。这就导致了C2 IR的图变成以下形式:
![7d8da0a52fe48e74a308514dc31ffb6a.png](https://img-blog.csdnimg.cn/img_convert/7d8da0a52fe48e74a308514dc31ffb6a.png)
从图中可以看到,那个绿色的循环,它的Entry边有两条,一条是蓝色的边,一条是红色的边。那么这个循环就变成了不可归约(irreducible)循环了。
为了对循环进行优化,C2借助了一种额外的数据结构来表达循环结构,但是C2对不可归约的循环的处理是相当糟糕的。看一下这一段代码,显然,对于不可归约循环,有一些优化没办法做的。
// "share/vm/opto/loopnode.cpp" : 1538
"merge_many_backedges"会对第一重循环头的回边进行合并,但是在irreducible的情况,回边的判断是错的,然后导致catch的回边和某一条Entry合并了。
然后,因为mI = new Integer这条装箱语句的优化,会导致catch边被删除,最后的结果就是删除了错误的入边,导致后面的crash。
修复的方案就是当发现循环是不可归约时就不要再做合并回边的优化了。也就是1541行也像1550行那样加上"!_irreducible"的条件。
这个问题修好并且提交社区以后,过了一段时间,我们又收到一个BUG,在新版本的Jetty上会遇到这个问题:
https://bugs.openjdk.java.net/browse/JDK-8244407bugs.openjdk.java.net几乎是必现的。也就是说,只要你“及时”升级了JDK版本和Jetty版本,就必然遇到这个crash。
其实,这个问题还是上一个问题的延续,关键就在于那个1545行的那个result变量,当它为true时,c2就不会更新IdealLoopTree信息,但是,在8244407这个bug里,split_fall_in 方法会修改Graph的结构,但它却依赖于merge_many_backedges处的那个result赋值。当merge动作不发生的时候,IdealLoopTree的信息就不再更新,split对图的修改就没有正确地同步到IdealLoopTree里,然后就出现了后面循环信息对不上的情况。所以上一次修复没修完全,从而暴露了一个更大的问题。这个问题影响非常广泛。好在OpenJDK社区还没有发布新的版本,不然真的就是一个巨坑了。
简单地说,关键还是在于在不可归约循环的情况下,backedge没能被正确地标识,这样就导致了fallin边的计算其实也是错的。这里比较好的修改可能是不可归约的情况下,发生split fallin时,应该同步更新IdealLoopTree的信息。但为了最大限度地与原来的逻辑保持一致,保守的做法就成了patch中的那个做法了。
对于irreducible,是否还存在split的必要,这一点其实还是存疑的。感觉C2的代码里对于不可归约循环基本上都是尽量不处理。这一点还是需要进一步的深入分析的。
以上分析和修改都是由 @周勇 完成的。关于循环优化的进一步分析,后面会尽量形成一系列文章呈现给大家。
参考:
[1] OSR,On Stack Replacement, https://www.zhihu.com/question/45910849/answer/100636125