mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 16:14:10 -08:00
classlib: support SwitchBootstraps (#764)
Java 21 supports switch pattern matching (as well as few earlier Java version as an experimental feature). Javac produces code with INVOKEDYNAMIC that relies on bootstrap methods from `java.lang.runtime.SwitchBootstraps`. This commit is intended to support for new Java language feature
This commit is contained in:
parent
0ef08a01e7
commit
5dec78b590
|
@ -130,6 +130,22 @@ public class JCLPlugin implements TeaVMPlugin {
|
||||||
ValueType.object("java.lang.invoke.CallSite")),
|
ValueType.object("java.lang.invoke.CallSite")),
|
||||||
stringConcatSubstitutor);
|
stringConcatSubstitutor);
|
||||||
|
|
||||||
|
SwitchBootstrapSubstitutor switchBootstrapSubstitutor = new SwitchBootstrapSubstitutor();
|
||||||
|
host.add(new MethodReference("java.lang.runtime.SwitchBootstraps", "typeSwitch",
|
||||||
|
ValueType.object("java.lang.invoke.MethodHandles$Lookup"),
|
||||||
|
ValueType.object("java.lang.String"),
|
||||||
|
ValueType.object("java.lang.invoke.MethodType"),
|
||||||
|
ValueType.arrayOf(ValueType.object("java.lang.Object")),
|
||||||
|
ValueType.object("java.lang.invoke.CallSite")),
|
||||||
|
switchBootstrapSubstitutor);
|
||||||
|
host.add(new MethodReference("java.lang.runtime.SwitchBootstraps", "enumSwitch",
|
||||||
|
ValueType.object("java.lang.invoke.MethodHandles$Lookup"),
|
||||||
|
ValueType.object("java.lang.String"),
|
||||||
|
ValueType.object("java.lang.invoke.MethodType"),
|
||||||
|
ValueType.arrayOf(ValueType.object("java.lang.Object")),
|
||||||
|
ValueType.object("java.lang.invoke.CallSite")),
|
||||||
|
switchBootstrapSubstitutor);
|
||||||
|
|
||||||
if (!isBootstrap()) {
|
if (!isBootstrap()) {
|
||||||
host.add(new ScalaHacks());
|
host.add(new ScalaHacks());
|
||||||
host.add(new KotlinHacks());
|
host.add(new KotlinHacks());
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 ihromant.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.teavm.classlib.impl;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.teavm.dependency.BootstrapMethodSubstitutor;
|
||||||
|
import org.teavm.dependency.DynamicCallSite;
|
||||||
|
import org.teavm.model.BasicBlock;
|
||||||
|
import org.teavm.model.RuntimeConstant;
|
||||||
|
import org.teavm.model.ValueType;
|
||||||
|
import org.teavm.model.emit.PhiEmitter;
|
||||||
|
import org.teavm.model.emit.ProgramEmitter;
|
||||||
|
import org.teavm.model.emit.ValueEmitter;
|
||||||
|
import org.teavm.model.instructions.SwitchInstruction;
|
||||||
|
import org.teavm.model.instructions.SwitchTableEntry;
|
||||||
|
|
||||||
|
public class SwitchBootstrapSubstitutor implements BootstrapMethodSubstitutor {
|
||||||
|
@Override
|
||||||
|
public ValueEmitter substitute(DynamicCallSite callSite, ProgramEmitter pe) {
|
||||||
|
boolean enumSwitch = callSite.getBootstrapMethod().getName().equals("enumSwitch");
|
||||||
|
List<RuntimeConstant> labels = callSite.getBootstrapArguments();
|
||||||
|
ValueEmitter target = callSite.getArguments().get(0);
|
||||||
|
ValueEmitter restartIdx = callSite.getArguments().get(1);
|
||||||
|
BasicBlock joint = pe.prepareBlock();
|
||||||
|
PhiEmitter result = pe.phi(ValueType.INTEGER, joint);
|
||||||
|
pe.when(() -> target.isNull()).thenDo(() -> {
|
||||||
|
pe.constant(-1).propagateTo(result);
|
||||||
|
pe.jump(joint);
|
||||||
|
});
|
||||||
|
|
||||||
|
var switchInsn = new SwitchInstruction();
|
||||||
|
switchInsn.setCondition(restartIdx.getVariable());
|
||||||
|
pe.addInstruction(switchInsn);
|
||||||
|
|
||||||
|
var block = pe.prepareBlock();
|
||||||
|
pe.enter(block);
|
||||||
|
ValueType.Object enumType = enumSwitch ? labels.stream()
|
||||||
|
.filter(l -> l.getKind() == RuntimeConstant.TYPE)
|
||||||
|
.findAny().map(vt -> (ValueType.Object) vt.getValueType()).orElseThrow() : null;
|
||||||
|
if (enumType != null) {
|
||||||
|
pe.initClass(enumType.getClassName());
|
||||||
|
}
|
||||||
|
for (var i = 0; i < labels.size(); ++i) {
|
||||||
|
var entry = new SwitchTableEntry();
|
||||||
|
entry.setCondition(i);
|
||||||
|
entry.setTarget(block);
|
||||||
|
switchInsn.getEntries().add(entry);
|
||||||
|
|
||||||
|
var label = labels.get(i);
|
||||||
|
emitFragment(target, i, label, pe, result, joint, enumType);
|
||||||
|
|
||||||
|
block = pe.prepareBlock();
|
||||||
|
pe.jump(block);
|
||||||
|
pe.enter(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
switchInsn.setDefaultTarget(block);
|
||||||
|
|
||||||
|
pe.constant(callSite.getBootstrapArguments().size()).propagateTo(result);
|
||||||
|
pe.jump(joint);
|
||||||
|
pe.enter(joint);
|
||||||
|
return result.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void emitFragment(ValueEmitter target, int idx, RuntimeConstant label, ProgramEmitter pe,
|
||||||
|
PhiEmitter result, BasicBlock exit, ValueType.Object enumType) {
|
||||||
|
switch (label.getKind()) {
|
||||||
|
case RuntimeConstant.TYPE:
|
||||||
|
ValueType type = label.getValueType();
|
||||||
|
pe.when(() -> target.instanceOf(type).isTrue())
|
||||||
|
.thenDo(() -> {
|
||||||
|
pe.constant(idx).propagateTo(result);
|
||||||
|
pe.jump(exit);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case RuntimeConstant.INT:
|
||||||
|
int val = label.getInt();
|
||||||
|
pe.when(() -> target.instanceOf(ValueType.object("java.lang.Number")).isTrue()
|
||||||
|
.and(() -> target.cast(Number.class)
|
||||||
|
.invokeVirtual("intValue", int.class).isSame(pe.constant(val))))
|
||||||
|
.thenDo(() -> {
|
||||||
|
pe.constant(idx).propagateTo(result);
|
||||||
|
pe.jump(exit);
|
||||||
|
});
|
||||||
|
pe.when(() -> target.instanceOf(ValueType.object("java.lang.Character")).isTrue()
|
||||||
|
.and(() -> target.cast(Character.class)
|
||||||
|
.invokeSpecial("charValue", char.class).isSame(pe.constant(val))))
|
||||||
|
.thenDo(() -> {
|
||||||
|
pe.constant(idx).propagateTo(result);
|
||||||
|
pe.jump(exit);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case RuntimeConstant.STRING:
|
||||||
|
String str = label.getString();
|
||||||
|
pe.when(enumType != null
|
||||||
|
? () -> pe.getField(enumType.getClassName(), str, enumType).isSame(target)
|
||||||
|
: () -> pe.constant(str).isEqualTo(target))
|
||||||
|
.thenDo(() -> {
|
||||||
|
pe.constant(idx).propagateTo(result);
|
||||||
|
pe.jump(exit);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported constant type: " + label.getKind());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
203
tests/src/test/java/org/teavm/vm/SwitchTest.java
Normal file
203
tests/src/test/java/org/teavm/vm/SwitchTest.java
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 ihromant.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.teavm.vm;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.teavm.junit.TeaVMTestRunner;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
@RunWith(TeaVMTestRunner.class)
|
||||||
|
public class SwitchTest {
|
||||||
|
private static int switchWithLogic(Object o) {
|
||||||
|
return switch (o) {
|
||||||
|
case null -> -1;
|
||||||
|
case A a when (a.af & 31) == 5 -> 0;
|
||||||
|
case A a -> 1;
|
||||||
|
case B b -> {
|
||||||
|
b.bbf = 21;
|
||||||
|
yield b.f.length();
|
||||||
|
}
|
||||||
|
case C c -> c.cf.af;
|
||||||
|
case D(byte c, short d) when ((int) d & 31) == 31 -> c;
|
||||||
|
case D(byte c, short d) -> d;
|
||||||
|
case TestEnum te -> te.ordinal();
|
||||||
|
default -> throw new IllegalArgumentException();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void genericSwitch() {
|
||||||
|
assertEquals(-1, switchWithLogic(null));
|
||||||
|
A a = new A();
|
||||||
|
assertEquals(1, switchWithLogic(a));
|
||||||
|
a.af = 5;
|
||||||
|
assertEquals(0, switchWithLogic(a));
|
||||||
|
B b = new B();
|
||||||
|
b.f = "abc";
|
||||||
|
assertEquals(3, switchWithLogic(b));
|
||||||
|
assertEquals(21, b.bbf);
|
||||||
|
C c = new C();
|
||||||
|
c.cf = a;
|
||||||
|
assertEquals(5, switchWithLogic(c));
|
||||||
|
assertEquals(Byte.MIN_VALUE, switchWithLogic(new D(Byte.MIN_VALUE, Short.MAX_VALUE)));
|
||||||
|
assertEquals(Short.MIN_VALUE, switchWithLogic(new D(Byte.MIN_VALUE, Short.MIN_VALUE)));
|
||||||
|
assertEquals(4, switchWithLogic(TestEnum.E));
|
||||||
|
try {
|
||||||
|
switchWithLogic(new Object());
|
||||||
|
fail();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int enumSwitchWithLogic(TestEnum o) {
|
||||||
|
return switch (o) {
|
||||||
|
case A, B -> 1;
|
||||||
|
case TestEnum e when e.ordinal() % 3 == 0 -> 3;
|
||||||
|
case C, D, E, F -> 2;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void enumSwitch() {
|
||||||
|
assertEquals(1, enumSwitchWithLogic(TestEnum.A));
|
||||||
|
assertEquals(2, enumSwitchWithLogic(TestEnum.C));
|
||||||
|
assertEquals(3, enumSwitchWithLogic(TestEnum.D));
|
||||||
|
assertEquals(2, enumSwitchWithLogic(TestEnum.F));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int integerSwitchWithLogic(Integer o) {
|
||||||
|
return switch (o) {
|
||||||
|
case 23 -> 1;
|
||||||
|
case Integer i when i < 10 -> 2;
|
||||||
|
case 42 -> 3;
|
||||||
|
default -> 4;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void integerSwitch() {
|
||||||
|
assertEquals(1, integerSwitchWithLogic(23));
|
||||||
|
assertEquals(3, integerSwitchWithLogic(42));
|
||||||
|
assertEquals(4, integerSwitchWithLogic(11));
|
||||||
|
assertEquals(2, integerSwitchWithLogic(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int characterSwitchWithLogic(Character c) {
|
||||||
|
return switch (c) {
|
||||||
|
case Character ch when ch >= 'a' && ch <= 'z' -> 5;
|
||||||
|
case 'R' -> 1;
|
||||||
|
case 'T' -> 2;
|
||||||
|
default -> throw new IllegalArgumentException();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void characterSwitch() {
|
||||||
|
assertEquals(5, characterSwitchWithLogic('a'));
|
||||||
|
assertEquals(1, characterSwitchWithLogic('R'));
|
||||||
|
assertEquals(2, characterSwitchWithLogic('T'));
|
||||||
|
try {
|
||||||
|
characterSwitchWithLogic('A');
|
||||||
|
fail();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int stringSwitchWithLogic(String s) {
|
||||||
|
return switch (s) {
|
||||||
|
case String str when str.length() < 3 -> 0;
|
||||||
|
case "abc" -> 1;
|
||||||
|
default -> 2;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void stringSwitch() {
|
||||||
|
assertEquals(0, stringSwitchWithLogic(""));
|
||||||
|
assertEquals(1, stringSwitchWithLogic("abc"));
|
||||||
|
assertEquals(2, stringSwitchWithLogic("bcd"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int switchWithHierarchy(Object o) {
|
||||||
|
return switch (o) {
|
||||||
|
case Superclass s when s.x == 23 -> 1;
|
||||||
|
case SubclassA a -> 2;
|
||||||
|
case Superclass s when s.x == 24 -> 3;
|
||||||
|
case SubclassB b -> 4;
|
||||||
|
default -> 5;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hierarchySwitch() {
|
||||||
|
assertEquals(1, switchWithHierarchy(new SubclassA(23)));
|
||||||
|
assertEquals(2, switchWithHierarchy(new SubclassA(24)));
|
||||||
|
assertEquals(2, switchWithHierarchy(new SubclassA(1)));
|
||||||
|
assertEquals(1, switchWithHierarchy(new SubclassB(23)));
|
||||||
|
assertEquals(3, switchWithHierarchy(new SubclassB(24)));
|
||||||
|
assertEquals(4, switchWithHierarchy(new SubclassB(1)));
|
||||||
|
assertEquals(5, switchWithHierarchy("foo"));
|
||||||
|
assertEquals(5, switchWithHierarchy(new Superclass(1)));
|
||||||
|
assertEquals(1, switchWithHierarchy(new Superclass(23)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class A {
|
||||||
|
private int af;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class B {
|
||||||
|
private String f;
|
||||||
|
private int bbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class C {
|
||||||
|
private A cf;
|
||||||
|
private long ccf;
|
||||||
|
private boolean cccf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record D(byte a, short b) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TestEnum {
|
||||||
|
A, B, C, D, E, F
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Superclass {
|
||||||
|
final int x;
|
||||||
|
|
||||||
|
public Superclass(int x) {
|
||||||
|
this.x = x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SubclassA extends Superclass {
|
||||||
|
public SubclassA(int x) {
|
||||||
|
super(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SubclassB extends Superclass {
|
||||||
|
public SubclassB(int x) {
|
||||||
|
super(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user