Record Class Lbl<N extends Emitter.Next>

java.lang.Object
java.lang.Record
ghidra.pcode.emu.jit.gen.util.Lbl<N>
Type Parameters:
N - the stack contents where the label is placed (or must be placed)
Record Components:
label - the wrapped ASM label

public record Lbl<N extends Emitter.Next>(org.objectweb.asm.Label label) extends Record
Utility for defining and placing labels.

These are used as control-flow targets, to specify the scope of local variables, and to specify the bounds of try-catch blocks.

Labels, and the possibility of control flow, necessitate some care when trying to validate stack contents in generated code. Again, our goal is to find an acceptable syntax that also provides as much flexibility as possible for later maintenance and refactoring. The requirements:

  • Where code can jump, the stack at the jump target must agree with the resulting stack at the jump site.
  • If code is only reachable by a jump, then the stack there must be the resulting stack at the jump site.
  • If code is reachable by multiple jumps and/or fall-through, then the stack must agree along all those paths.

To enforce these requirements, we encode thhe stack contents at a label's position in the same manner as we encode the emitter's current stack contents. Now, when a label is placed, there are two possibilities: 1) The code is reachable, in which case the label's and the emitter's stacks must agree. 2) The code is unreachable, in which case the emitter's incoming stack does not matter. Its resulting stack is the label's stack, and the code at this point is now presumed reachable. Because we would have collisions due to type erasure, these two cases are implemented in non-overloaded methods place(Emitter, Lbl) and placeDead(Emitter, Lbl).

As an example, we show an if-else construct:

 var lblLess = em
                .emit(Op::iload, params.a)
                .emit(Op::ldc__i, 20)
                .emit(Op::if_icmple);
 var lblDone = lblLess.em()
                .emit(Op::ldc__i, 0xcafe)
                .emit(Op::goto_);
 return lblDone.em()
                .emit(Lbl::placeDead, lblLess.lbl())
                .emit(Op::ldc__i, 0xbabe)
                .emit(Lbl::place, lblDone.lbl())
                .emit(Op::ireturn, retReq);
 

This would be equivalent to

 int myFunc(int a) {
        if (a <= 20) {
                return 0xbabe;
        }
        else {
                return 0xcafe;
        }
 }
 

Note that we allow the Java compiler to infer the type of the label targeted by the Op.if_icmple(Emitter). That form of the operator generates a label for us, and the inferred type of lblLess is an Lbl.LblEm<Bot>, representing the empty stack, because the conditional jump consumes both ints without pushing anything. The emitter in that returned tuple has the same stack contents, representing the fall-through case, and so we emit code into the false branch. To avoid falling through into the true branch, we emit an unconditional jump, i.e., Op.goto_(Emitter), again taking advantage of Java's type inference to automatically derive the stack contents. Similar to the previous jump instruction, this returns a tuple, but this time, while the label still expects an empty stack, the emitter now has <N>:=Emitter.Dead, because any code emitted after this point would be unreachable. It is worth noting that none of the methods in Op accept a dead emitter. The only way (don't you dare cast it!) to resurrect the emitter is to place a label using placeDead(Emitter, Lbl). This is fitting, since we need to emit the true branch, so we place lblLess and emit the appropriate code. There is no need to jump after the true branch. We just allow both branches to flow into the same Op.ireturn(Emitter, RetReq). Thus, we place lblDone, which is checked by the Java compiler to have matching stack contents, and finally emit the return.

There is some manual bookkeeping here to ensure we use each previous emitter, but this is not too much worse than the manual bookkeeping needed to track label placement. In our experience, when we get that wrong, the compiler reports it as inconsistent, anyway. One drawback to using type inference is that the label's name does not appear in the jump instruction that targets it. We do not currently have a solution to that complaint.

  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static final record 
    A tuple providing both a (new) label and a resulting emitter
  • Constructor Summary

    Constructors
    Constructor
    Description
    Lbl(org.objectweb.asm.Label label)
    Creates an instance of a Lbl record class.
  • Method Summary

    Modifier and Type
    Method
    Description
    static <N extends Emitter.Next>
    Lbl<N>
    Create a fresh label with any expected stack contents
    final boolean
    Indicates whether some other object is "equal to" this one.
    final int
    Returns a hash code value for this object.
    org.objectweb.asm.Label
    Returns the value of the label record component.
    static <N extends Emitter.Next>
    Lbl.LblEm<N,N>
    place(Emitter<N> em)
    Generate a place a label where execution could already reach
    static <N extends Emitter.Next>
    Emitter<N>
    place(Emitter<N> em, Lbl<N> lbl)
    Place the given label at a place where execution could already reach
    static <N extends Emitter.Next>
    Emitter<N>
    Place the given label at a place where execution could not otherwise reach
    final String
    Returns a string representation of this record class.

    Methods inherited from class java.lang.Object

    clone, finalize, getClass, notify, notifyAll, wait, wait, wait
  • Constructor Details

    • Lbl

      public Lbl(org.objectweb.asm.Label label)
      Creates an instance of a Lbl record class.
      Parameters:
      label - the value for the label record component
  • Method Details

    • create

      public static <N extends Emitter.Next> Lbl<N> create()
      Create a fresh label with any expected stack contents

      Using this to forward declare labels requires the user to explicate the expected stack, which may not be ideal, as it may require updating during refactoring. Consider using place(Emitter) instead, which facilitates inference of the stack contents.

      Type Parameters:
      N - the expected stack contents
      Returns:
      the label
    • place

      public static <N extends Emitter.Next> Lbl.LblEm<N,N> place(Emitter<N> em)
      Generate a place a label where execution could already reach

      The returned label's stack will match this emitter's stack, since the code could be reached by multiple paths, likely fall-through and a jump to the returned label.

      Type Parameters:
      N - the emitter's and the label's stack, i.e., as where the returned label is referenced
      Parameters:
      em - the emitter
      Returns:
      the label and emitter
    • place

      public static <N extends Emitter.Next> Emitter<N> place(Emitter<N> em, Lbl<N> lbl)
      Place the given label at a place where execution could already reach

      The emitter's stack and the label's stack must agree, since the code is reachable by multiple paths, likely fallthrough and a jump to the given label.

      Type Parameters:
      N - the emitter's and the label's stack, i.e., as where the given label is referenced
      Parameters:
      em - the emitter
      lbl - the label to place
      Returns:
      the same emitter
    • placeDead

      public static <N extends Emitter.Next> Emitter<N> placeDead(Emitter<Emitter.Dead> em, Lbl<N> lbl)
      Place the given label at a place where execution could not otherwise reach

      The emitter must be dead, i.e., if it were to emit code, that code would be unreachable. By placing a referenced label at this place, the code following becomes reachable, and so the given emitter becomes alive again, having the stack that results from the referenced code. If the label has not yet been referenced, it must be forward declared with the expected stack. There is no equivalent of place(Emitter) for a dead emitter, because there is no way to know the resulting stack.

      Type Parameters:
      N - the stack where the given label is referenced
      Parameters:
      em - the emitter for otherwise-unreachable code
      lbl - the label to place
      Returns:
      the emitter, as reachable via the given label
    • toString

      public final String toString()
      Returns a string representation of this record class. The representation contains the name of the class, followed by the name and value of each of the record components.
      Specified by:
      toString in class Record
      Returns:
      a string representation of this object
    • hashCode

      public final int hashCode()
      Returns a hash code value for this object. The value is derived from the hash code of each of the record components.
      Specified by:
      hashCode in class Record
      Returns:
      a hash code value for this object
    • equals

      public final boolean equals(Object o)
      Indicates whether some other object is "equal to" this one. The objects are equal if the other object is of the same class and if all the record components are equal. All components in this record class are compared with Objects::equals(Object,Object).
      Specified by:
      equals in class Record
      Parameters:
      o - the object with which to compare
      Returns:
      true if this object is the same as the o argument; false otherwise.
    • label

      public org.objectweb.asm.Label label()
      Returns the value of the label record component.
      Returns:
      the value of the label record component