Skip to content

Commit 23d3124

Browse files
PaulSandozasotona
authored andcommitted
Ifthenelse (openjdk#28)
* Enhance ifThenElse. * Expose block builder with break label. * Docs and tests. * Doc. * Review feedback. * Unused import.
1 parent 0c448a1 commit 23d3124

File tree

6 files changed

+224
-55
lines changed

6 files changed

+224
-55
lines changed

src/java.base/share/classes/jdk/classfile/CodeBuilder.java

Lines changed: 105 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,8 @@
3131
import java.lang.constant.DynamicCallSiteDesc;
3232
import java.lang.constant.MethodTypeDesc;
3333
import java.util.ArrayList;
34-
import java.util.HashSet;
3534
import java.util.List;
36-
import java.util.Objects;
3735
import java.util.Optional;
38-
import java.util.Set;
3936
import java.util.function.Consumer;
4037

4138
import jdk.classfile.constantpool.ClassEntry;
@@ -49,7 +46,7 @@
4946
import jdk.classfile.constantpool.NameAndTypeEntry;
5047
import jdk.classfile.constantpool.Utf8Entry;
5148
import jdk.classfile.impl.AbstractInstruction;
52-
import jdk.classfile.impl.BlockCodeBuilder;
49+
import jdk.classfile.impl.BlockCodeBuilderImpl;
5350
import jdk.classfile.impl.BytecodeHelpers;
5451
import jdk.classfile.impl.CatchBuilderImpl;
5552
import jdk.classfile.impl.ChainedCodeBuilder;
@@ -99,7 +96,7 @@
9996
*/
10097
public sealed interface CodeBuilder
10198
extends ClassfileBuilder<CodeElement, CodeBuilder>
102-
permits BlockCodeBuilder, ChainedCodeBuilder, TerminalCodeBuilder, NonterminalCodeBuilder {
99+
permits CodeBuilder.BlockCodeBuilder, ChainedCodeBuilder, TerminalCodeBuilder, NonterminalCodeBuilder {
103100

104101
/**
105102
* {@return the {@link CodeModel} representing the method body being transformed,
@@ -159,58 +156,140 @@ public sealed interface CodeBuilder
159156
int allocateLocal(TypeKind typeKind);
160157

161158
/**
162-
* Add a lexical block to the method being built. Within this block, the
163-
* {@link #startLabel()} and {@link #endLabel()} correspond to the start
164-
* and end of the block.
159+
* A builder for blocks of code.
165160
*/
166-
default CodeBuilder block(Consumer<CodeBuilder> handler) {
167-
BlockCodeBuilder child = new BlockCodeBuilder(this);
161+
sealed interface BlockCodeBuilder extends CodeBuilder
162+
permits BlockCodeBuilderImpl {
163+
/**
164+
* {@return the label locating where control is passed back to the parent block.}
165+
* A branch to this label "break"'s out of the current block.
166+
* <p>
167+
* If an instruction occurring immediately after the built block's last instruction would
168+
* be reachable from that last instruction, then a {@linkplain #goto_ goto} instruction
169+
* targeting the "break" label is appended to the built block.
170+
*/
171+
Label breakLabel();
172+
}
173+
174+
/**
175+
* Add a lexical block to the method being built.
176+
* <p>
177+
* Within this block, the {@link #startLabel()} and {@link #endLabel()} correspond
178+
* to the start and end of the block, and the {@link BlockCodeBuilder#breakLabel()}
179+
* also corresponds to the end of the block.
180+
*
181+
* @param handler handler that receives a {@linkplain BlockCodeBuilder} to
182+
* generate the body of the lexical block.
183+
*/
184+
default CodeBuilder block(Consumer<BlockCodeBuilder> handler) {
185+
Label breakLabel = newLabel();
186+
BlockCodeBuilderImpl child = new BlockCodeBuilderImpl(this, breakLabel);
168187
child.start();
169188
handler.accept(child);
170189
child.end();
190+
labelBinding(breakLabel);
171191
return this;
172192
}
173193

174194
/**
175195
* Add an "if-then" block that is conditional on the boolean value
176196
* on top of the operand stack.
197+
* <p>
198+
* The {@link BlockCodeBuilder#breakLabel()} for the "then" block corresponds to the
199+
* end of that block.
177200
*
178-
* @param thenHandler handler that receives a {@linkplain CodeBuilder} to
201+
* @param thenHandler handler that receives a {@linkplain BlockCodeBuilder} to
179202
* generate the body of the {@code if}
180203
* @return this builder
181204
*/
182-
default CodeBuilder ifThen(Consumer<CodeBuilder> thenHandler) {
183-
BlockCodeBuilder thenBlock = new BlockCodeBuilder(this);
184-
branchInstruction(Opcode.IFEQ, thenBlock.endLabel());
205+
default CodeBuilder ifThen(Consumer<BlockCodeBuilder> thenHandler) {
206+
return ifThen(Opcode.IFNE, thenHandler);
207+
}
208+
209+
/**
210+
* Add an "if-then" block that is conditional on the value(s) on top of the operand stack
211+
* in accordance with the given opcode.
212+
* <p>
213+
* The {@link BlockCodeBuilder#breakLabel()} for the "then" block corresponds to the
214+
* end of that block.
215+
*
216+
* @param opcode the operation code for a branch instructions that accepts one or two operands on the stack
217+
* @param thenHandler handler that receives a {@linkplain BlockCodeBuilder} to
218+
* generate the body of the {@code if}
219+
* @return this builder
220+
* @throws java.lang.IllegalArgumentException if the operation code is not for a branch instruction that accepts
221+
* one or two operands
222+
*/
223+
default CodeBuilder ifThen(Opcode opcode,
224+
Consumer<BlockCodeBuilder> thenHandler) {
225+
if (opcode.kind() != CodeElement.Kind.BRANCH || opcode.primaryTypeKind() == TypeKind.VoidType) {
226+
throw new IllegalArgumentException("Illegal branch opcode: " + opcode);
227+
}
228+
229+
Label breakLabel = newLabel();
230+
BlockCodeBuilderImpl thenBlock = new BlockCodeBuilderImpl(this, breakLabel);
231+
branchInstruction(BytecodeHelpers.reverseBranchOpcode(opcode), thenBlock.endLabel());
185232
thenBlock.start();
186233
thenHandler.accept(thenBlock);
187234
thenBlock.end();
235+
labelBinding(breakLabel);
188236
return this;
189237
}
190238

191239
/**
192240
* Add an "if-then-else" block that is conditional on the boolean value
193241
* on top of the operand stack.
242+
* <p>
243+
* The {@link BlockCodeBuilder#breakLabel()} for each block corresponds to the
244+
* end of the "else" block.
194245
*
195-
* @param thenHandler handler that receives a {@linkplain CodeBuilder} to
246+
* @param thenHandler handler that receives a {@linkplain BlockCodeBuilder} to
196247
* generate the body of the {@code if}
197-
* @param elseHandler handler that receives a {@linkplain CodeBuilder} to
248+
* @param elseHandler handler that receives a {@linkplain BlockCodeBuilder} to
198249
* generate the body of the {@code else}
199250
* @return this builder
200251
*/
201-
default CodeBuilder ifThenElse(Consumer<CodeBuilder> thenHandler,
202-
Consumer<CodeBuilder> elseHandler) {
203-
BlockCodeBuilder thenBlock = new BlockCodeBuilder(this);
204-
BlockCodeBuilder elseBlock = new BlockCodeBuilder(this);
205-
branchInstruction(Opcode.IFEQ, elseBlock.startLabel());
252+
default CodeBuilder ifThenElse(Consumer<BlockCodeBuilder> thenHandler,
253+
Consumer<BlockCodeBuilder> elseHandler) {
254+
return ifThenElse(Opcode.IFNE, thenHandler, elseHandler);
255+
}
256+
257+
/**
258+
* Add an "if-then-else" block that is conditional on the value(s) on top of the operand stack
259+
* in accordance with the given opcode.
260+
* <p>
261+
* The {@link BlockCodeBuilder#breakLabel()} for each block corresponds to the
262+
* end of the "else" block.
263+
*
264+
* @param opcode the operation code for a branch instructions that accepts one or two operands on the stack
265+
* @param thenHandler handler that receives a {@linkplain BlockCodeBuilder} to
266+
* generate the body of the {@code if}
267+
* @param elseHandler handler that receives a {@linkplain BlockCodeBuilder} to
268+
* generate the body of the {@code else}
269+
* @return this builder
270+
* @throws java.lang.IllegalArgumentException if the operation code is not for a branch instruction that accepts
271+
* one or two operands
272+
*/
273+
default CodeBuilder ifThenElse(Opcode opcode,
274+
Consumer<BlockCodeBuilder> thenHandler,
275+
Consumer<BlockCodeBuilder> elseHandler) {
276+
if (opcode.kind() != CodeElement.Kind.BRANCH || opcode.primaryTypeKind() == TypeKind.VoidType) {
277+
throw new IllegalArgumentException("Illegal branch opcode: " + opcode);
278+
}
279+
280+
Label breakLabel = newLabel();
281+
BlockCodeBuilderImpl thenBlock = new BlockCodeBuilderImpl(this, breakLabel);
282+
BlockCodeBuilderImpl elseBlock = new BlockCodeBuilderImpl(this, breakLabel);
283+
branchInstruction(BytecodeHelpers.reverseBranchOpcode(opcode), elseBlock.startLabel());
206284
thenBlock.start();
207285
thenHandler.accept(thenBlock);
208286
if (thenBlock.reachable())
209-
thenBlock.branchInstruction(Opcode.GOTO, elseBlock.endLabel());
287+
thenBlock.branchInstruction(Opcode.GOTO, thenBlock.breakLabel());
210288
thenBlock.end();
211289
elseBlock.start();
212290
elseHandler.accept(elseBlock);
213291
elseBlock.end();
292+
labelBinding(breakLabel);
214293
return this;
215294
}
216295

@@ -234,7 +313,7 @@ sealed interface CatchBuilder permits CatchBuilderImpl {
234313
* @throws java.lang.IllegalArgumentException if an existing catch block catches an exception of the given type.
235314
* @see #catchingAll
236315
*/
237-
CatchBuilder catching(ClassDesc exceptionType, Consumer<CodeBuilder> catchHandler);
316+
CatchBuilder catching(ClassDesc exceptionType, Consumer<BlockCodeBuilder> catchHandler);
238317

239318
/**
240319
* Adds a "catch" block that catches all exceptions.
@@ -246,7 +325,7 @@ sealed interface CatchBuilder permits CatchBuilderImpl {
246325
* @throws java.lang.IllegalArgumentException if an existing catch block catches all exceptions.
247326
* @see #catching
248327
*/
249-
void catchingAll(Consumer<CodeBuilder> catchAllHandler);
328+
void catchingAll(Consumer<BlockCodeBuilder> catchAllHandler);
250329
}
251330

252331
/**
@@ -260,12 +339,12 @@ sealed interface CatchBuilder permits CatchBuilderImpl {
260339
* @return this builder
261340
* @see CatchBuilder
262341
*/
263-
default CodeBuilder trying(Consumer<CodeBuilder> tryHandler,
342+
default CodeBuilder trying(Consumer<BlockCodeBuilder> tryHandler,
264343
Consumer<CatchBuilder> catchesHandler) {
265344
Label tryCatchEnd = newLabel();
266345

267346
// @@@ the tryHandler does not have access to tryCatchEnd
268-
BlockCodeBuilder tryBlock = new BlockCodeBuilder(this);
347+
BlockCodeBuilderImpl tryBlock = new BlockCodeBuilderImpl(this, tryCatchEnd);
269348
tryBlock.start();
270349
tryHandler.accept(tryBlock);
271350
tryBlock.end();

src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilder.java renamed to src/java.base/share/classes/jdk/classfile/impl/BlockCodeBuilderImpl.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,31 +26,32 @@
2626

2727
import jdk.classfile.CodeBuilder;
2828
import jdk.classfile.CodeElement;
29-
import jdk.classfile.Instruction;
3029
import jdk.classfile.Label;
3130
import jdk.classfile.Opcode;
32-
import jdk.classfile.PseudoInstruction;
3331
import jdk.classfile.TypeKind;
3432
import jdk.classfile.instruction.LabelTarget;
3533

34+
import java.util.Objects;
35+
3636
/**
3737
* BlockCodeBuilder
3838
*/
39-
public final class BlockCodeBuilder
39+
public final class BlockCodeBuilderImpl
4040
extends NonterminalCodeBuilder
41-
implements CodeBuilder {
41+
implements CodeBuilder.BlockCodeBuilder {
4242
private final CodeBuilder parent;
43-
private final Label startLabel, endLabel;
43+
private final Label startLabel, endLabel, breakLabel;
4444
private boolean reachable = true;
4545
private boolean hasInstructions = false;
4646
private int topLocal;
4747
private int terminalMaxLocals;
4848

49-
public BlockCodeBuilder(CodeBuilder parent) {
49+
public BlockCodeBuilderImpl(CodeBuilder parent, Label breakLabel) {
5050
super(parent);
5151
this.parent = parent;
52-
startLabel = terminal.newLabel();
53-
endLabel = terminal.newLabel();
52+
this.startLabel = parent.newLabel();
53+
this.endLabel = parent.newLabel();
54+
this.breakLabel = Objects.requireNonNull(breakLabel);
5455
}
5556

5657
public void start() {
@@ -61,8 +62,9 @@ public void start() {
6162

6263
public void end() {
6364
terminal.with((LabelTarget) endLabel);
64-
if (terminalMaxLocals != topLocal(terminal))
65+
if (terminalMaxLocals != topLocal(terminal)) {
6566
throw new IllegalStateException("Interference in local variable slot management");
67+
}
6668
}
6769

6870
public boolean reachable() {
@@ -75,7 +77,7 @@ public boolean isEmpty() {
7577

7678
private int topLocal(CodeBuilder parent) {
7779
return switch (parent) {
78-
case BlockCodeBuilder b -> b.topLocal;
80+
case BlockCodeBuilderImpl b -> b.topLocal;
7981
case ChainedCodeBuilder b -> topLocal(b.terminal);
8082
case DirectCodeBuilder b -> b.curTopLocal();
8183
case BufferedCodeBuilder b -> b.curTopLocal();
@@ -115,4 +117,9 @@ public int allocateLocal(TypeKind typeKind) {
115117
topLocal += typeKind.slotSize();
116118
return retVal;
117119
}
120+
121+
@Override
122+
public Label breakLabel() {
123+
return breakLabel;
124+
}
118125
}

src/java.base/share/classes/jdk/classfile/impl/BytecodeHelpers.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ public static Opcode arrayStoreOpcode(TypeKind tk) {
193193
};
194194
}
195195

196-
static Opcode reverseBranchOpcode(Opcode op) {
196+
public static Opcode reverseBranchOpcode(Opcode op) {
197197
return switch (op) {
198198
case IFEQ -> IFNE;
199199
case IFNE -> IFEQ;

src/java.base/share/classes/jdk/classfile/impl/CatchBuilderImpl.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,20 @@
3737

3838
public final class CatchBuilderImpl implements CodeBuilder.CatchBuilder {
3939
final CodeBuilder b;
40-
final BlockCodeBuilder tryBlock;
40+
final BlockCodeBuilderImpl tryBlock;
4141
final Label tryCatchEnd;
4242
final Set<ConstantDesc> catchTypes;
43-
BlockCodeBuilder catchBlock;
43+
BlockCodeBuilderImpl catchBlock;
4444

45-
public CatchBuilderImpl(CodeBuilder b, BlockCodeBuilder tryBlock, Label tryCatchEnd) {
45+
public CatchBuilderImpl(CodeBuilder b, BlockCodeBuilderImpl tryBlock, Label tryCatchEnd) {
4646
this.b = b;
4747
this.tryBlock = tryBlock;
4848
this.tryCatchEnd = tryCatchEnd;
4949
this.catchTypes = new HashSet<>();
5050
}
5151

5252
@Override
53-
public CodeBuilder.CatchBuilder catching(ClassDesc exceptionType, Consumer<CodeBuilder> catchHandler) {
53+
public CodeBuilder.CatchBuilder catching(ClassDesc exceptionType, Consumer<CodeBuilder.BlockCodeBuilder> catchHandler) {
5454
Objects.requireNonNull(catchHandler);
5555

5656
if (catchBlock == null) {
@@ -71,7 +71,7 @@ public CodeBuilder.CatchBuilder catching(ClassDesc exceptionType, Consumer<CodeB
7171
}
7272
}
7373

74-
catchBlock = new BlockCodeBuilder(b);
74+
catchBlock = new BlockCodeBuilderImpl(b, tryCatchEnd);
7575
Label tryStart = tryBlock.startLabel();
7676
Label tryEnd = tryBlock.endLabel();
7777
catchBlock.start();
@@ -87,14 +87,14 @@ public CodeBuilder.CatchBuilder catching(ClassDesc exceptionType, Consumer<CodeB
8787
}
8888

8989
@Override
90-
public void catchingAll(Consumer<CodeBuilder> catchAllHandler) {
90+
public void catchingAll(Consumer<CodeBuilder.BlockCodeBuilder> catchAllHandler) {
9191
catching(null, catchAllHandler);
9292
}
9393

9494
public void finish() {
9595
if (catchBlock != null) {
9696
catchBlock.end();
97-
b.labelBinding(tryCatchEnd);
9897
}
98+
b.labelBinding(tryCatchEnd);
9999
}
100100
}

src/java.base/share/classes/jdk/classfile/impl/NonterminalCodeBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@
3535
* NonterminalCodeBuilder
3636
*/
3737
public abstract sealed class NonterminalCodeBuilder implements CodeBuilder
38-
permits ChainedCodeBuilder, BlockCodeBuilder {
38+
permits ChainedCodeBuilder, BlockCodeBuilderImpl {
3939
protected final TerminalCodeBuilder terminal;
4040

4141
public NonterminalCodeBuilder(CodeBuilder downstream) {
4242
this.terminal = switch (downstream) {
4343
case ChainedCodeBuilder cb -> cb.terminal;
44-
case BlockCodeBuilder cb -> cb.terminal;
44+
case BlockCodeBuilderImpl cb -> cb.terminal;
4545
case TerminalCodeBuilder cb -> cb;
4646
};
4747
}

0 commit comments

Comments
 (0)