Fight the Future

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

プレゼン、ボランティアコーチします!

勉強会で話したいけど、プレゼンが初めて、苦手という方に無償でコーチします!

  • スライドのレビュー
  • 録画リハへのアドバイス

Twitter@jyukutyoまでメンションでもDMでも。 デブサミやJJUG CCCなど200人規模で登壇しました。海外での登壇も短いながらあり。デブサミ2017では公募スピーカー1位でした!

GraalでのHIRとLIR

Understanding How Graal Works - a Java JIT Compiler Written in Java に沿って、Graalの理解を深めてきました。前回はロック粗粒化、記事の最後の解説でした。

jyukutyo.hatenablog.com

これからは少しづつ自分で調べていきます。Graalがというかコンパイラの基本的なことを理解していないと、進まない気もしてきています。なので、HotSpotのJITコンパイラについて調べて、それがGraalでどうなっているかを見るというのをやってみます。

HotSpotでのClient(C1)コンパイラは、2つの部分に分かれています。ハードウェアから独立しているフロントエンドと、ある程度ハードウェア依存があるバックエンドです。

フロントエンドでは、JavaバイトコードからHIR(high-level intermediate representation: 高水準中間表現)を生成します。バックエンドではHIRからLIR(low-level intermediate representation: 高水準中間表現)を生成します。最終的にこのLIRからマシンコードを生成します。

Christian WimmerさんのLinear Scan Register Allocation for the Java HotSpot™ Client Compilerにすばらしい図がありましたので、引用します*1

f:id:jyukutyo:20171130122646p:plain

Graalではorg.graalvm.compiler.core.GraalCompilerでその部分が読み取れます。

    /**
     * Services a given compilation request.
     *
     * @return the result of the compilation
     */
    @SuppressWarnings("try")
    public static <T extends CompilationResult> T compile(Request<T> r) {
        DebugContext debug = r.graph.getDebug();
        try (CompilationAlarm alarm = CompilationAlarm.trackCompilationPeriod(r.graph.getOptions())) {
            assert !r.graph.isFrozen();
            try (DebugContext.Scope s0 = debug.scope("GraalCompiler", r.graph, r.providers.getCodeCache()); DebugCloseable a = CompilerTimer.start(debug)) {
                emitFrontEnd(r.providers, r.backend, r.graph, r.graphBuilderSuite, r.optimisticOpts, r.profilingInfo, r.suites);
                emitBackEnd(r.graph, null, r.installedCodeOwner, r.backend, r.compilationResult, r.factory, null, r.lirSuites);
            } catch (Throwable e) {
                throw debug.handle(e);
            }
            checkForRequestedCrash(r.graph);
            return r.compilationResult;
        }
    }

emitFrontEnd()、emitBackEnd()はまさにそうだと思います。

HIR

emitFrontEnd()では、さらにHighTier、MidTier、LowTierという3層に分かれています。IGVで見たGraalのグラフは、初期状態と各層の処理後に出力されます。IGVの各画像名と、debug.dump()に渡している文字列は同じです。"Final HIR schedule"という文字列も見て取れますので、HIRとはGraalではグラフと考えてよさそうです。

    public static void emitFrontEnd(Providers providers, TargetProvider target, StructuredGraph graph, PhaseSuite<HighTierContext> graphBuilderSuite, OptimisticOptimizations optimisticOpts,
                    ProfilingInfo profilingInfo, Suites suites) {
        DebugContext debug = graph.getDebug();
        try (DebugContext.Scope s = debug.scope("FrontEnd"); DebugCloseable a = FrontEnd.start(debug)) {
            HighTierContext highTierContext = new HighTierContext(providers, graphBuilderSuite, optimisticOpts);
            if (graph.start().next() == null) {
                graphBuilderSuite.apply(graph, highTierContext);
                new DeadCodeEliminationPhase(DeadCodeEliminationPhase.Optionality.Optional).apply(graph);
                debug.dump(DebugContext.BASIC_LEVEL, graph, "After parsing");
            } else {
                debug.dump(DebugContext.INFO_LEVEL, graph, "initial state");
            }

            suites.getHighTier().apply(graph, highTierContext);
            graph.maybeCompress();
            debug.dump(DebugContext.BASIC_LEVEL, graph, "After high tier");

            MidTierContext midTierContext = new MidTierContext(providers, target, optimisticOpts, profilingInfo);
            suites.getMidTier().apply(graph, midTierContext);
            graph.maybeCompress();
            debug.dump(DebugContext.BASIC_LEVEL, graph, "After mid tier");

            LowTierContext lowTierContext = new LowTierContext(providers, target);
            suites.getLowTier().apply(graph, lowTierContext);
            debug.dump(DebugContext.BASIC_LEVEL, graph, "After low tier");

            debug.dump(DebugContext.BASIC_LEVEL, graph.getLastSchedule(), "Final HIR schedule");
        } catch (Throwable e) {
            throw debug.handle(e);
        } finally {
            graph.checkCancellation();
        }
    }

LIR

emitBackEnd()内で、最終的にorg.graalvm.compiler.lir.gen.LIRGeneratorの処理が呼び出されます。LIRはハードウェアに依存しますので、各CPUごとのサブクラスがあります。AMD64org.graalvm.compiler.core.amd64.AMD64LIRGeneratorです。CAS命令やアトミックな読み書き処理といったコードを生成しています。