Package ghidra.pcode.emu.jit

# Class JitCompiler

java.lang.Object
ghidra.pcode.emu.jit.JitCompiler

public class JitCompiler extends Object
The Just-in-Time (JIT) translation engine that powers the `JitPcodeEmulator`.

This is the translation engine from "any" machine language into JVM bytecode. The same caveats that apply to interpretation-based p-code emulation apply to JIT-accelerated emulation: Ghidra must have a Sleigh specification for the emulation target language, there must be userop libraries (built-in or user-provided) defining any userops encountered during the course of execution, all dependent code must be loaded or stubbed out, etc.

A passage is decoded at a desired entry point using the `JitPassageDecoder`. This compiler then translates the passage into bytecode. It will produce a classfile which is then loaded and returned to the emulator (or other client). The provided class will have three principal methods, not counting getters: 1) The class initializer, which initializes static fields; 2) The constructor, which takes a thread and initializes instance fields, and 3) The `run` method, which comprises the actual translation. A static field `ENTRIES` describes each entry point generated by the compiler. To execute the passage starting at a given entry point, the emulation thread must retrieve the index of the appropriate entry (i.e., address and contextreg value), instantiate the class, and then invoke the run method, passing it the entry index. The translated passage will read variables from the thread's `state` as needed, perform the equivalent operations as expressed in the source p-code, and then write the resulting variables back into the state. Memory variables are treated similarly, but without scope-based optimizations. In this manner, execution of the translated passage produces exactly the same effect on the emulation state as interpretation of the same p-code passage. The run method returns the next entry point to execute or `null` when the emulator must look up the next entry point.

Translation of a passage takes place in distinct phases. See each respective class for details of its design and implementation:

1. Control Flow Analysis: `JitControlFlowModel`
2. Data Flow Analysis: `JitDataFlowModel`
3. Variable Scope Analysis: `JitVarScopeModel`
4. Type Assignment: `JitTypeModel`
5. Variable Allocation: `JitAllocationModel`
6. Operation Elimination: `JitOpUseModel`
7. Code Generation: `JitCodeGenerator`

## Control Flow Analysis

Some rudimentary control flow analysis is performed during decode, but the output of decode is a passage, i.e., collection of strides, not basic blocks. The control flow analysis breaks each stride down into basic blocks at the p-code level. Note that a single instruction's pcode (as well as any user instrumentation on that instruction's address) may have complex control flow. Additionally, branches that leave an instruction preclude execution of its remaining p-code. Thus, p-code basic blocks do not coincide precisely with instruction-level basic blocks. See `JitControlFlowModel`.

## Data Flow Analysis

Most every following step consumes the control flow analysis. Data flow analysis interprets each basic block independently using an abstraction that produces a use-def graph. A varnode that is read before it is written produces a "missing" variable. Those missing variables are converted to phi nodes and later resolved during inter-block analysis. The graph is also able to consider aliasing, partial accesses, overlapping accesses, etc., by synthesizing operations to model those effects. See `JitDataFlowModel`.

## Variable Scope Analysis

Because accessing `PcodeExecutorState` is expensive (relative to accessing a JVM local variable), the translation seeks to minimize such accesses. This is generally not recommended for memory accesses, as there is no telling in multi-threaded applications whether a given memory variable is shared/volatile or not. However, for registers and uniques, we can allocate the variables as JVM locals. Then we only "birth" them (read them in) when they come into scope and "retire" them (write them out) when they leave scope. This analyzer determines which variables are in scope (alive) in which basic blocks. See `JitVarScopeModel`.

## Type Assignment

For those variables we allocate as JVM locals, we have to choose a type, because the JVM requires it. We have essentially 4 to choose from. (Though we could also choose a reference type, depending on the strategy we eventually choose for multi-precision arithmetic.) Those four are the JVM primitives: int, float, long, and double. For those more familiar with Java but not the JVM, the smaller integral primitives are all represented by JVM ints. The JVM does not permit type confusion, e.g., the application of float addition `FADD` to int variables. However, the emulation target may permit type confusion. (Those familiar with the constant 0x5f759df may appreciate intentional type confusion.) When this happens, we must explicitly convert by calling, e.g., `Float.floatToRawIntBits(float)`, which is essentially just a bit cast. Nevertheless, we seek to reduce the number of such calls we encode into the translation. See `JitTypeModel`.

## Variable Allocation

Once we've decided the type of each use-def variable node, we allocate JVM locals and assign their types accordingly. To keep things simple and fast, we just allocate variables by varnode. Partial/overlapping accesses are coalesced to the containing varnode and cause the type to be a JVM int (to facilitate shifting and masking). Otherwise, types are assigned according to the most common use of the varnode, i.e., by taking a vote among the use-def variable nodes sharing that varnode. See `JitAllocationModel`.

## Operation Elimination

Each instruction typically produces several p-code ops, the outputs of which may not actually be used by any subsequent op. This analysis seeks to identify such p-code ops and remove them. Since many ISAs employ "flags," which are set by nearly every arithmetic instruction, such ops are incredibly common. Worse yet, their computation is very expensive, because the JVM does not have comparable flag registers, nor does it provide opcodes for producing comparable values. We have to emit the bit banging operations ourselves. Thus, performing this elimination stands to improve execution speed significantly. However, eliminating these operations may lead to confusing results if execution is interrupted and the state inspected by a user. The effects of the eliminated operations will be missing. Even though they do not (or should not) matter, the user may expect to see them. Thus, this step can be toggled by `JitConfiguration.removeUnusedOperations()`. See `JitOpUseModel`.

## Code Generation

For simplicity, we seek to generate JVM bytecode in the same order as the source p-code ops. There are several details given the optimizations informed by all the preceding analysis. For example, the transfer of control to the requested entry point, the placement of variable birth and retirement on control flow edges (including fall-through).... We take an object-oriented approach to the translation of each p-code op, the handling of each variable's allocation and access, the conversion of types, etc. This phase outputs the final classfile bytes, which are then loaded as a hidden class. See `JitCodeGenerator`.

Implementation Notes:
  There are static fields in this class for configuring diagnostics. They are meant to be modified only temporarily by developers seeking to debug issues in the translation engine.

- ## Nested Class Summary

  Nested Classes
  Modifier and Type
  Class
  Description
  `static enum`
  `JitCompiler.Diag`
  Diagnostic toggles

- ## Field Summary

  Fields
  Modifier and Type
  Field
  Description
  `static final EnumSet<JitCompiler.Diag>`
  `ENABLE_DIAGNOSTICS`
  The set of enabled diagnostic toggles.
  `static final long`
  `EXCLUDE_MAXS`
  Exclude a given address offset from ASM's `ClassWriter.COMPUTE_MAXS` and `ClassWriter.COMPUTE_FRAMES`.

- ## Constructor Summary

  Constructors
  Constructor
  Description
  `JitCompiler(JitConfiguration config)`
  Construct a p-code to bytecode translator.

- ## Method Summary

  Modifier and Type
  Method
  Description
  `JitCompiledPassageClass`
  `compilePassage(MethodHandles.Lookup lookup, JitPassage passage)`
  Translate a passage using the given lookup
  `JitConfiguration`
  `getConfiguration()`
  Get this compiler's configuration

  ### Methods inherited from class java.lang.Object

  `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait`

- ## Field Details

  - ### ENABLE_DIAGNOSTICS

    public static final EnumSet<JitCompiler.Diag> ENABLE_DIAGNOSTICS
    The set of enabled diagnostic toggles.

    In production, this should be empty.

  - ### EXCLUDE_MAXS

    public static final long EXCLUDE_MAXS
    Exclude a given address offset from ASM's `ClassWriter.COMPUTE_MAXS` and `ClassWriter.COMPUTE_FRAMES`.

    Unfortunately, when automatic computation of frames and maxes fails, the ASM library offers little in terms of diagnostics. It usually crashes with an NPE or an AIOOBE. Worse, when this happens, it fails to output any of the classfile trace. To help with this, a developer may identify the address of the passage seed that causes such a failure and set this variable to its offset. This will prevent ASM from attempting this computation so that it at least prints the trace and dumps out the classfile to disk (if those `JitCompiler.Diag`nostics are enabled).

    Once the trace/classfile is obtained, set this back to -1 and then apply debug prints in the crashing method. Since it's probably in the ASM library, you'll need to use your IDE / debugger to inject those prints. The way to do this in Eclipse is to set a "conditional breakpoint" then have the condition print the value and return false, so that execution continues. Sadly, this will still slow execution down considerably, so you'll want to set some other conditional breakpoint to catch when the troublesome passage is being translated. Probably the most helpful thing to print is the bytecode offset of each basic block ASM is processing as it computes the frames. Once it crashes, look at the last couple of bytecode offsets in the dumped classfile.

    See Also:
      - Constant Field Values

- ## Constructor Details

  - ### JitCompiler

    public JitCompiler(JitConfiguration config)
    Construct a p-code to bytecode translator.

    In general, this should only be used by the JIT emulator and its test suite.

    Parameters:
      `config` - the configuration

- ## Method Details

  - ### compilePassage

    public JitCompiledPassageClass compilePassage(MethodHandles.Lookup lookup, JitPassage passage)
    Translate a passage using the given lookup
    Parameters:
      `lookup` - a lookup that can access everything the passage may need, e.g., userop libraries. Likely, this should come from the emulator, which may be in a script. If you are unsure what to use here, use `MethodHandles.lookup()`. If you see errors about accessing stuff during the compilation, ensure everything the emulator needs is accessible from the method calling `MethodHandles.lookup()`.
      `passage` - the decoded passage to compile
    Returns:
      the compiled class, not instantiated for any particular thread

  - ### getConfiguration

    public JitConfiguration getConfiguration()
    Get this compiler's configuration
    Returns:
      the configuration