Understanding How Graal Works - a Java JIT Compiler Written in Java に沿って、Graalの理解を深めてきました。前回はロック粗粒化、記事の最後の解説でした。
これからは少しづつ自分で調べていきます。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。
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ごとのサブクラスがあります。AMD64はorg.graalvm.compiler.core.amd64.AMD64LIRGenerator
です。CAS命令やアトミックな読み書き処理といったコードを生成しています。
*1:http://www.christianwimmer.at/Publications/Wimmer04a/Wimmer04a.pdf P.26 Figure 4.1