考虑一些字节码的生成问题.
比如
```
long start = System.nanoTime();
long end = System.nanoTime();
Profiler.INSTANCE.collect(end-start);
```
按照SSA的风格话,大概会是
```
invokestatic
lstore 1
invokestatic
lstore 2
lload 2
lload 1
lsub
lstore n
getstatic
lloadn
invokestatic
```
这样的形式.
如果是手工生成的话大概会是
```
getstatic
invokestatic
lstore 1
invokestatic
lload 1
lsub
invokestatic
```
区别在于压栈的次数.
因为调整了Profiler.INSTANCE入栈顺序.
所以基本上按照最后collect参数规格维护stack的.
如果要自动地做这种重排的话呢?
本质上来说,就是针对一个函数调用入参约定的一个dependency resolution/backtrace.
就一个control flow的graph来说,可能做的还是一些prune/merge的事情.
不过如果再仔细考虑下上面的例子的话.
其实两个并不是等价的.
因为这里隐含的一个假定是INSTANCE是immutable的.
所以push的时机不影响最终的结果.
但如果INSTANCE是mutable的话,那么这种重排其实是有问题的.
因为后面的调用/指令可能会影响INSTANCE的值.
那么有没有immutable的情况呢.
理论上来说,应该是没有的.
因为如果用反射的话,对于final的field也是可以改变值的.
而且这里比较有趣的一个地方就是final本身的实现.
大概瞄了眼c2的相关代码.
主要是getfield和putfield的.
里面提到确实是会对static final和@Stable的field转成constant node.
这个容易理解.
但是putfield并没有针对final做什么过多特殊处理.
那么如果是严格按照bytecode执行的话,问题不大.
因为不管是get/set/static/field,还是反射的读写.
针对的都是同一个东西.
但是如果是JIT过的,那么在生成native code的时候依据的是转换后的constant node.
这样的话,就可能并不是很符合预期.
一个简单的例子就是一个boolean的if condition.
因为field access变成了一个constant,那么就有可能触发一个优化规则把整个if优化掉.
而当用反射把这个值置回true的话,貌似并不会触发deoptimize.
这样的话,即使从代码和逻辑层面上来说if分支应该被执行,但实际可能是并不会被执行到.
那么如果想办法让put指令里触发deoptimize的话呢?
比如动一下FieldAccessor的相关路径和生成的native code风格?
理论上来说应该是代价比较大的.
一个是如果在native code里对field access引入一个stub的话.
则在这个field被修改的时候需要知道哪些native code部分是需要做改动的.
这个如果不是stub的方式的话,基本上是不太现实的.
因为必须维护一个列表.
这样的话复杂度就不低了.
而如果用stub function的话,就没什么太大意义了.
本来就是一个最多二次寻址的问题,变成反而需要用一个更重的function call替代.
于是从这个角度看的话,set final某种程度上来说算是一种挺危险的行为.
因为这个导致的不确定行为可能还跟环境有关.
在触发JIT的情况下和在interpreter的场景还不太一样.
没有评论:
发表评论