Offline Compilation

Maxine VM supports offline compilation of Java methods. Offline compilation essentially invokes Maxine VM’s compilers through the host VM. This process allows users to inspect the whole process of code generation without the need to build a Maxine VM image. Some of the use cases of offline compilation are listed below:

  1. Debugging when implementing the compiler back-ends for a new ISA.
  2. Debugging when implementing new JIT optimizations.
  3. Inspecting how code generation works for educational purposes.

Warning

The output is not guaranteed to be the same as the output for a compilation performed at run-time. The code produced by a compiler is sensitive to the compilation context such as what classes have been resolved etc.

To compile offline the main method of Main class issue the following command:

$ mx olc -cp ./ -c=C1X ^Main:main^
Initializing Java prototype... done
Main.main(String[])
.
1 passed

Note

On Mac OS you will need to install binutils (brew install binutils) and change your PATH (export PATH="/usr/local/opt/binutils/bin:$PATH").

  • mx olc invokes the offline compilation
  • -cp ./ adds the current path to the classpath. This is necessary for the online compilation to find the class we want to compile. Depending on your project structure you might need to pass a different directory than the current one.
  • -c=C1X defines the Maxine compiler to be used. This option supports T1X and C1X at the moment.

Note

Using T1X will also invoke C1X to generate the machine code for the T1X templates. The method however will be compiled using T1X

  • ^Main:main^ defines the methods to be compiled. This is essentially a pattern.

The above command will generate a folder olc with two files in it:

  • Main.main-C1X-amd64.bin contains the raw machine code generated by the compiler.
  • Main.main-C1X-amd64.S contains the assembly code obtained by disassembling Main.main-C1X-amd64.bin.

C1visualizer

To better understand C1X code generation and how the code gets translated from Java byte-code to machine code we use the c1visualizer. C1visualizer expects a cfg file as input. To generate this file we can run mx olc with the -C1X:+PrintCFGToFile.

Note

This will generate a single compilations*.cfg file for all the methods matching the pattern.

More options

Another option is to generate assembly code annotated with LIR instructions:

$ mx olc -cp ./ -c=C1X -C1X:+PrintLIRWithAssembly ^Main:main^
Initializing Java prototype... done
Main.main(String[])
B0 [0, 0] . B1   0 Label ?

2 XIR: prologue()

0x0:        e8 00 00 00 00          callq  0x5
0x5:        90                      nop
0x6:        90                      nop
0x7:        90                      nop
0x8:        48 83 ec 28             sub    $0x28,%rsp
0xc:        48 89 84 24 28 e0 ff    mov    %rax,-0x1fd8(%rsp)
0x13:       ff

B1 (SV)[0, 3] . B4 dom B0 pred: B0   8 Label ?

10 move rsi:i = const[1|0x1]:i

0x14:       be 01 00 00 00          mov    $0x1,%esi

12 move rax:i = const[1|0x1]:i

0x19:       b8 01 00 00 00          mov    $0x1,%eax

14 Branch TRUE [B4]

0x1e:       e9 00 00 00 00          jmpq   0x5

B2 (bV)[11, 18] . B4 dom B4 pred: B4  24 Label ?

0x23:       90                      nop
0x24:       90                      nop
0x25:       90                      nop
0x26:       90                      nop
0x27:       90                      nop

28 Mul rax:i = (rax:i, rsi:i)

0x28:       0f af c6                imul   %esi,%eax

32 Add rsi:i = (rsi:i, const[1|0x1]:i)

0x2b:       83 c6 01                add    $0x1,%esi

34 XIR: safepoint() temp=(r14:j) [bci:18, refmap(rdi:a)]

0x2e:       4d 8b 36                mov    (%r14),%r14

B4 (LHV)[4, 8] . B3 B2 dom B1 pred: B1 B2  16 Label ?

18 Cmp (rsi:i, const[1000|0x3e8]:i)

0x31:       81 fe e8 03 00 00       cmp    $0x3e8,%esi

20 Branch < [B2]

0x37:       7c ef                   jl     0xfffffff1

B3 (V)[21, 28] dom B4 pred: B4  42 Label ?

    move stack:1:a = rdi:a

0x39:       48 89 7c 24 08          mov    %rdi,0x8(%rsp)

44 move rsi:a = const[staticTuple-System]:a

0x3e:       48 8b 35 00 00 00 00    mov    0x0(%rip),%rsi        # 0x7

46 XIR: rcx:a = getfield<Object>(v5:a, const[24|0x18]:i) input=(rsi:a)

0x45:       48 8b 4e 18             mov    0x18(%rsi),%rcx

48 XIR: rdx:j = invokevirtual(v6:a, const[360|0x168]:i) method=java.io.PrintStream.println(int) input=(rcx:a) temp=(rsi:a, rdx:j) [bci:25, refmap(stack:1:a, rcx:a)]

0x49:       48 8b 31                mov    (%rcx),%rsi
0x4c:       48 8b 96 68 01 00 00    mov    0x168(%rsi),%rdx

50 move rdi:a = rcx:a

0x53:       48 8b f9                mov    %rcx,%rdi

52 move rsi:i = rax:i

0x56:       48 8b f0                mov    %rax,%rsi

    move stack:2:i = rax:i

0x59:       89 44 24 10             mov    %eax,0x10(%rsp)

54 IndirectCall rdx:j(rdi:a, rsi:i) [bci:25, refmap(stack:1:a)]

0x5d:       ff d2                   callq  *%rdx
0x5f:       90                      nop

56 XIR: epilogue() method=Main.main(String[]) [bci:28, refmap(stack:1:a)]

0x60:       48 83 c4 28             add    $0x28,%rsp

58 Return <illegal>

0x64:       c3                      retq

.
1 passed

Use mx olc -help to see what other options mx olc accepts.

Targeting different architectures

Offline compilation can also be used to target a different architecture than the host one. For instance, to target RISC-V we issue the following command:

mx --J @"-Dmax.platform=linux-riscv64" olc -cp ./ -c=C1X ^Main:main^

Maxine currently supports:

  • linux-amd64
  • linux-arm
  • linux-aarch64
  • linux-riscv64

Patterns

A pattern is a class name pattern followed by an optional method name pattern separated by a : further followed by an optional signature:

<class name>[:<method name>[:<signature>]]

For example, the list of patterns:

"Object:wait", "String", "Util:add:(int,float)"

will match all methods in a class whose name contains “Object” where the method name contains “wait”, all methods in a class whose name contains “String” and all methods in any class whose name contains “Util”, the method name contains “add” and the signature is (int, float).

The type of matching performed for a given class/method name is determined by the position of ^ in the pattern name as follows:

Position of ^ Match algorithm
start AND end Equality
start Prefix
end Suffix
absent Substring

For example, ^java.util:^toString^ matches all methods named “toString” in any class whose name starts with “java.util”.

The matching performed on a signature is always a substring test. Signatures can be specified either in Java source syntax (e.g. “int,String”) or JVM internal syntax (e.g. “IFLjava/lang/String;”). The latter must always use fully qualified type names where as the former must not.

Any pattern starting with ! is an exclusion specification. Any class or method whose name contains an exclusion string (the exclusion specification minus the leading !) is excluded.