Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

JITコンパイルのコードにあるGCアルゴリズムの分岐

JJUG CCC 2017 Fallで一番楽しみにしていたセッション(そして素晴らしいセッションでした)"CPUから見たG1GC"で、初めて知ったことがありました。

www.slideshare.net

JITコンパイルのコードの中に、使っているGCアルゴリズムでの分岐があるということです。

void GraphKit::post_barrier(Node* ctl,
                            Node* store,
                            Node* obj,
                            Node* adr,
                            uint  adr_idx,
                            Node* val,
                            BasicType bt,
                            bool use_precise) {
  BarrierSet* bs = Universe::heap()->barrier_set();
  set_control(ctl);
  switch (bs->kind()) {
    case BarrierSet::G1SATBCTLogging:
      g1_write_barrier_post(store, obj, adr, adr_idx, val, bt, use_precise);
      break;

    case BarrierSet::CardTableForRS:
    case BarrierSet::CardTableExtension:
      write_barrier_post(store, obj, adr, adr_idx, val, use_precise);
      break;

    case BarrierSet::ModRef:
      break;

    default      :
      ShouldNotReachHere();

  }
}

src/hotspot/share/opto/graphKit.cpp です。caseでG1GCだったらという分岐があります。

Graalにもあるだろうと考え、調べてみました。"g1gc"で検索すると、org.graalvm.compiler.hotspot.phases.WriteBarrierAdditionPhaseWriteBarrierVerificationPhaseが見つかりました。

WriteBarrierVerificationPhaseのコードです。

    private void validateWrite(Node write) {
...
        NodeFlood frontier = write.graph().createNodeFlood();
        expandFrontier(frontier, write);
        Iterator<Node> iterator = frontier.iterator();
        while (iterator.hasNext()) {
            Node currentNode = iterator.next();
            if (isSafepoint(currentNode)) {
                throw new AssertionError("Write barrier must be present " + write.toString(Verbosity.All) + " / " + write.inputs());
            }
            if (useG1GC()) {
                if (!(currentNode instanceof G1PostWriteBarrier) || (!validateBarrier((FixedAccessNode) write, (ObjectWriteBarrier) currentNode))) {
                    expandFrontier(frontier, currentNode);
                }
            } else {
                if (!(currentNode instanceof SerialWriteBarrier) || (!validateBarrier((FixedAccessNode) write, (ObjectWriteBarrier) currentNode)) ||
                                ((currentNode instanceof SerialWriteBarrier) && !validateBarrier((FixedAccessNode) write, (ObjectWriteBarrier) currentNode))) {
                    expandFrontier(frontier, currentNode);
                }
            }
        }
    }

    private boolean useG1GC() {
        return config.useG1GC;
    }

WriteBarrierAdditionPhaseのコードです。

    private void addWriteNodeBarriers(WriteNode node, StructuredGraph graph) {
        BarrierType barrierType = node.getBarrierType();
        switch (barrierType) {
            case NONE:
                // nothing to do
                break;
            case IMPRECISE:
            case PRECISE:
                boolean precise = barrierType == BarrierType.PRECISE;
                if (config.useG1GC) {
                    if (!node.getLocationIdentity().isInit()) {
                        addG1PreWriteBarrier(node, node.getAddress(), null, true, node.getNullCheck(), graph);
                    }
                    addG1PostWriteBarrier(node, node.getAddress(), node.value(), precise, graph);
                } else {
                    addSerialPostWriteBarrier(node, node.getAddress(), node.value(), precise, graph);
                }
                break;
            default:
                throw new GraalError("unexpected barrier type: " + barrierType);
        }
    }

どちらもライトバリアに関する処理をするPhaseです。G1GCではポストライトバリアでリメンバーセットを更新します。

G1 GCでは、個別の記憶集合(RSet)を使用して、リージョンへの参照を追跡します。個別のRSetを使用すると、そのリージョンへの参照のスキャンが必要になるのはヒープ全体ではなくリージョンのRSetだけになるので、リージョンのコレクションを並列かつ個別に実行できるようになります。G1 GCでは、ポストライト・バリアを使用してヒープの変更を記録し、RSetを更新します。 https://docs.oracle.com/javase/jp/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html

RSet = リメンバーセットです。G1PostWriteBarrierクラスもValueNodeクラスのサブクラスであり、Graalのグラフに表現されるようです。で、なぜそうしているのか?、どういう処理をしているのか?について、僕はわかりません。

G1PostWriteBarrierオブジェクトを実際に処理しているのはorg.graalvm.compiler.hotspot.replacements.WriteBarrierSnippetsですが、

        public void lower(G1PostWriteBarrier writeBarrierPost, HotSpotRegistersProvider registers, LoweringTool tool) {
            StructuredGraph graph = writeBarrierPost.graph();
            if (writeBarrierPost.alwaysNull()) {
                graph.removeFixed(writeBarrierPost);
                return;
            }
            Arguments args = new Arguments(g1PostWriteBarrier, graph.getGuardsStage(), tool.getLoweringStage());
            AddressNode address = writeBarrierPost.getAddress();
            args.add("address", address);
            if (address instanceof OffsetAddressNode) {
                args.add("object", ((OffsetAddressNode) address).getBase());
            } else {
                assert writeBarrierPost.usePrecise() : "found imprecise barrier that's not an object access " + writeBarrierPost;
                args.add("object", null);
            }

            ValueNode value = writeBarrierPost.getValue();
            if (value.stamp() instanceof NarrowOopStamp) {
                assert oopEncoding != null;
                value = HotSpotCompressionNode.uncompress(value, oopEncoding);
            }
            args.add("value", value);

            args.addConst("usePrecise", writeBarrierPost.usePrecise());
            args.addConst("threadRegister", registers.getThreadRegister());
            args.addConst("trace", traceBarrier(writeBarrierPost.graph()));
            args.addConst("counters", counters);
            template(graph.getDebug(), args).instantiate(providers.getMetaAccess(), writeBarrierPost, DEFAULT_REPLACER, args);
        }

何をしているのかわかりませんでした。