Record Class Lbl<N extends Emitter.Next>
- Type Parameters:
N- the stack contents where the label is placed (or must be placed)- Record Components:
label- the wrapped ASM label
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 ClassesModifier and TypeClassDescriptionstatic final recordLbl.LblEm<LN extends Emitter.Next,N> A tuple providing both a (new) label and a resulting emitter -
Constructor Summary
ConstructorsConstructorDescriptionLbl(org.objectweb.asm.Label label) Creates an instance of aLblrecord class. -
Method Summary
Modifier and TypeMethodDescriptionstatic <N extends Emitter.Next>
Lbl<N> create()Create a fresh label with any expected stack contentsfinal booleanIndicates whether some other object is "equal to" this one.final inthashCode()Returns a hash code value for this object.org.objectweb.asm.Labellabel()Returns the value of thelabelrecord component.static <N extends Emitter.Next>
Lbl.LblEm<N, N> Generate a place a label where execution could already reachstatic <N extends Emitter.Next>
Emitter<N> Place the given label at a place where execution could already reachstatic <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 reachfinal StringtoString()Returns a string representation of this record class.
-
Constructor Details
-
Lbl
public Lbl(org.objectweb.asm.Label label) Creates an instance of aLblrecord class.- Parameters:
label- the value for thelabelrecord component
-
-
Method Details
-
create
Create a fresh label with any expected stack contentsUsing 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
Generate a place a label where execution could already reachThe 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
Place the given label at a place where execution could already reachThe 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 emitterlbl- the label to place- Returns:
- the same emitter
-
placeDead
Place the given label at a place where execution could not otherwise reachThe 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 codelbl- the label to place- Returns:
- the emitter, as reachable via the given label
-
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. -
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. -
equals
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 withObjects::equals(Object,Object). -
label
public org.objectweb.asm.Label label()Returns the value of thelabelrecord component.- Returns:
- the value of the
labelrecord component
-