-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #143 from mkouba/string-switch
Add the StringSwitch and EnumSwitch constructs
- Loading branch information
Showing
7 changed files
with
905 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package io.quarkus.gizmo; | ||
|
||
import java.util.Objects; | ||
import java.util.function.Consumer; | ||
|
||
abstract class AbstractSwitch<T> extends BytecodeCreatorImpl implements Switch<T> { | ||
|
||
protected static final Consumer<BytecodeCreator> EMPTY_BLOCK = bc -> { | ||
}; | ||
|
||
protected boolean fallThrough; | ||
protected Consumer<BytecodeCreator> defaultBlockConsumer; | ||
|
||
AbstractSwitch(BytecodeCreatorImpl enclosing) { | ||
super(enclosing); | ||
} | ||
|
||
@Override | ||
public void fallThrough() { | ||
fallThrough = true; | ||
} | ||
|
||
@Override | ||
public void defaultCase(Consumer<BytecodeCreator> defatultBlockConsumer) { | ||
Objects.requireNonNull(defatultBlockConsumer); | ||
this.defaultBlockConsumer = defatultBlockConsumer; | ||
} | ||
|
||
@Override | ||
public void doBreak(BytecodeCreator creator) { | ||
creator.breakScope(this); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
package io.quarkus.gizmo; | ||
|
||
import static org.objectweb.asm.Opcodes.ACC_PRIVATE; | ||
import static org.objectweb.asm.Opcodes.ACC_STATIC; | ||
|
||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.Iterator; | ||
import java.util.LinkedHashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Map.Entry; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
import java.util.function.Consumer; | ||
|
||
import org.objectweb.asm.Label; | ||
import org.objectweb.asm.MethodVisitor; | ||
|
||
class EnumSwitchImpl<E extends Enum<E>> extends AbstractSwitch<E> implements Switch.EnumSwitch<E> { | ||
|
||
private final Map<Integer, Consumer<BytecodeCreator>> ordinalToCaseBlocks; | ||
|
||
public EnumSwitchImpl(ResultHandle value, Class<E> enumClass, BytecodeCreatorImpl enclosing) { | ||
super(enclosing); | ||
this.ordinalToCaseBlocks = new LinkedHashMap<>(); | ||
|
||
MethodDescriptor enumOrdinal = MethodDescriptor.ofMethod(enumClass, "ordinal", int.class); | ||
ResultHandle ordinal = invokeVirtualMethod(enumOrdinal, value); | ||
|
||
// Generate the int[] switch table needed for binary compatibility | ||
ResultHandle switchTable; | ||
MethodCreatorImpl methodCreator = findMethodCreator(enclosing); | ||
if (methodCreator != null) { | ||
// Generate a static method that returns the switch table | ||
char sep = '$'; | ||
ClassCreator classCreator = methodCreator.getClassCreator(); | ||
// $GIZMO_SWITCH_TABLE$org$acme$MyEnum() | ||
StringBuilder methodName = new StringBuilder(); | ||
methodName.append(sep).append("GIZMO_SWITCH_TABLE"); | ||
for (String part : enumClass.getName().split("\\.")) { | ||
methodName.append(sep).append(part); | ||
} | ||
MethodDescriptor gizmoSwitchTableDescriptor = MethodDescriptor.ofMethod(classCreator.getClassName(), | ||
methodName.toString(), int[].class); | ||
if (!classCreator.getExistingMethods() | ||
.contains(gizmoSwitchTableDescriptor)) { | ||
MethodCreator gizmoSwitchTable = classCreator.getMethodCreator(gizmoSwitchTableDescriptor) | ||
.setModifiers(ACC_PRIVATE | ACC_STATIC); | ||
gizmoSwitchTable.returnValue(generateSwitchTable(enumClass, gizmoSwitchTable, enumOrdinal)); | ||
} | ||
switchTable = invokeStaticMethod(gizmoSwitchTableDescriptor); | ||
} else { | ||
// This is suboptimal - the switch table is generated for each switch construct | ||
switchTable = generateSwitchTable(enumClass, methodCreator, enumOrdinal); | ||
} | ||
ResultHandle effectiveOrdinal = readArrayValue(switchTable, ordinal); | ||
|
||
Set<ResultHandle> inputHandles = new HashSet<>(); | ||
inputHandles.add(effectiveOrdinal); | ||
|
||
operations.add(new Operation() { | ||
|
||
@Override | ||
void writeBytecode(MethodVisitor methodVisitor) { | ||
E[] constants = enumClass.getEnumConstants(); | ||
Map<Integer, Label> ordinalToLabel = new HashMap<>(); | ||
List<BytecodeCreatorImpl> caseBlocks = new ArrayList<>(); | ||
|
||
BytecodeCreatorImpl defaultBlock = new BytecodeCreatorImpl(EnumSwitchImpl.this); | ||
if (defaultBlockConsumer != null) { | ||
defaultBlockConsumer.accept(defaultBlock); | ||
} | ||
|
||
// Initialize the case blocks | ||
for (Entry<Integer, Consumer<BytecodeCreator>> caseEntry : ordinalToCaseBlocks.entrySet()) { | ||
BytecodeCreatorImpl caseBlock = new BytecodeCreatorImpl(EnumSwitchImpl.this); | ||
Consumer<BytecodeCreator> blockConsumer = caseEntry.getValue(); | ||
blockConsumer.accept(caseBlock); | ||
if (blockConsumer != EMPTY_BLOCK && !fallThrough) { | ||
caseBlock.breakScope(EnumSwitchImpl.this); | ||
} | ||
caseBlock.findActiveResultHandles(inputHandles); | ||
caseBlocks.add(caseBlock); | ||
ordinalToLabel.put(caseEntry.getKey(), caseBlock.getTop()); | ||
} | ||
|
||
int min = ordinalToLabel.keySet().stream().mapToInt(Integer::intValue).min().orElse(0); | ||
int max = ordinalToLabel.keySet().stream().mapToInt(Integer::intValue).max().orElse(0); | ||
|
||
// Add empty blocks for missing ordinals | ||
// This would be suboptimal for cases if there is a large number of missing ordinals | ||
for (int i = 0; i < constants.length; i++) { | ||
if (i >= min && i <= max) { | ||
if (ordinalToLabel.get(i) == null) { | ||
BytecodeCreatorImpl emptyCaseBlock = new BytecodeCreatorImpl(EnumSwitchImpl.this); | ||
caseBlocks.add(emptyCaseBlock); | ||
ordinalToLabel.put(i, emptyCaseBlock.getTop()); | ||
} | ||
} | ||
} | ||
|
||
// Load the ordinal of the tested value | ||
loadResultHandle(methodVisitor, effectiveOrdinal, EnumSwitchImpl.this, "I"); | ||
|
||
int[] ordinals = ordinalToLabel.keySet().stream().mapToInt(Integer::intValue).sorted().toArray(); | ||
Label[] labels = new Label[ordinals.length]; | ||
for (int i = 0; i < ordinals.length; i++) { | ||
labels[i] = ordinalToLabel.get(ordinals[i]); | ||
} | ||
methodVisitor.visitTableSwitchInsn(min, max, defaultBlock.getTop(), labels); | ||
|
||
// Write the case blocks | ||
for (BytecodeCreatorImpl caseBlock : caseBlocks) { | ||
caseBlock.writeOperations(methodVisitor); | ||
} | ||
|
||
// Write the default block | ||
defaultBlock.writeOperations(methodVisitor); | ||
} | ||
|
||
@Override | ||
ResultHandle getTopResultHandle() { | ||
return null; | ||
} | ||
|
||
@Override | ||
ResultHandle getOutgoingResultHandle() { | ||
return null; | ||
} | ||
|
||
@Override | ||
Set<ResultHandle> getInputResultHandles() { | ||
return inputHandles; | ||
} | ||
|
||
}); | ||
} | ||
|
||
@Override | ||
public void caseOf(E value, Consumer<BytecodeCreator> caseBlockConsumer) { | ||
Objects.requireNonNull(value); | ||
Objects.requireNonNull(caseBlockConsumer); | ||
addCaseBlock(value, caseBlockConsumer); | ||
} | ||
|
||
@Override | ||
public void caseOf(List<E> values, Consumer<BytecodeCreator> caseBlockConsumer) { | ||
Objects.requireNonNull(values); | ||
Objects.requireNonNull(caseBlockConsumer); | ||
for (Iterator<E> it = values.iterator(); it.hasNext();) { | ||
E e = it.next(); | ||
if (it.hasNext()) { | ||
addCaseBlock(e, EMPTY_BLOCK); | ||
} else { | ||
addCaseBlock(e, caseBlockConsumer); | ||
} | ||
} | ||
} | ||
|
||
private void addCaseBlock(E value, Consumer<BytecodeCreator> caseBlockConsumer) { | ||
int ordinal = value.ordinal(); | ||
if (ordinalToCaseBlocks.containsKey(ordinal)) { | ||
throw new IllegalArgumentException("A case block for the enum value " + value + " already exists"); | ||
} | ||
ordinalToCaseBlocks.put(ordinal, caseBlockConsumer); | ||
} | ||
|
||
private MethodCreatorImpl findMethodCreator(BytecodeCreatorImpl enclosing) { | ||
if (enclosing instanceof MethodCreatorImpl) { | ||
return (MethodCreatorImpl) enclosing; | ||
} | ||
if (enclosing.getOwner() != null) { | ||
return findMethodCreator(enclosing.getOwner()); | ||
} | ||
return null; | ||
} | ||
|
||
private ResultHandle generateSwitchTable(Class<E> enumClass, BytecodeCreator bytecodeCreator, | ||
MethodDescriptor enumOrdinal) { | ||
E[] constants = enumClass.getEnumConstants(); | ||
ResultHandle switchTable = bytecodeCreator.newArray(int.class, constants.length); | ||
for (int i = 0; i < constants.length; i++) { | ||
ResultHandle currentConstant = bytecodeCreator | ||
.readStaticField(FieldDescriptor.of(enumClass, constants[i].name(), enumClass)); | ||
ResultHandle currentOrdinal = bytecodeCreator.invokeVirtualMethod(enumOrdinal, currentConstant); | ||
bytecodeCreator.writeArrayValue(switchTable, i, currentOrdinal); | ||
} | ||
return switchTable; | ||
} | ||
|
||
} |
Oops, something went wrong.