LambdaMetafactory support

This commit is contained in:
Alexey Andreev 2015-07-26 18:47:37 +03:00
parent d5f5e2633b
commit 13353d0bde
9 changed files with 374 additions and 34 deletions

View File

@ -15,7 +15,13 @@
*/
package org.teavm.classlib.impl;
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.ServiceLoader;
import org.teavm.classlib.impl.lambda.LambdaMetafactorySubstitutor;
import org.teavm.classlib.impl.unicode.CLDRReader;
import org.teavm.classlib.java.lang.reflect.AnnotationDependencyListener;
import org.teavm.model.MethodReference;
@ -41,5 +47,8 @@ public class JCLPlugin implements TeaVMPlugin {
host.registerService(CLDRReader.class, new CLDRReader(host.getProperties(), host.getClassLoader()));
host.add(new AnnotationDependencyListener());
host.add(new MethodReference(LambdaMetafactory.class, "metafactory", MethodHandles.Lookup.class,
String.class, MethodType.class, MethodType.class, MethodHandle.class, MethodType.class,
CallSite.class), new LambdaMetafactorySubstitutor());
}
}

View File

@ -0,0 +1,314 @@
package org.teavm.classlib.impl.lambda;
import java.util.Arrays;
import org.teavm.dependency.BootstrapMethodSubstitutor;
import org.teavm.dependency.DynamicCallSite;
import org.teavm.model.AccessLevel;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldHolder;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodHandle;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference;
import org.teavm.model.PrimitiveType;
import org.teavm.model.ValueType;
import org.teavm.model.emit.ProgramEmitter;
import org.teavm.model.emit.ValueEmitter;
import org.teavm.model.instructions.CastIntegerDirection;
import org.teavm.model.instructions.IntegerSubtype;
import org.teavm.model.instructions.NumericOperandType;
/**
*
* @author Alexey Andreev
*/
public class LambdaMetafactorySubstitutor implements BootstrapMethodSubstitutor {
private int lambdaIndex = 0;
@Override
public ValueEmitter substitute(DynamicCallSite callSite, ProgramEmitter callerPe) {
ValueType[] invokedType = callSite.getCalledMethod().getSignature();
ValueType[] samMethodType = callSite.getBootstrapArguments().get(0).getMethodType();
MethodHandle implMethod = callSite.getBootstrapArguments().get(1).getMethodHandle();
ValueType[] instantiatedMethodType = callSite.getBootstrapArguments().get(2).getMethodType();
String samName = ((ValueType.Object) callSite.getCalledMethod().getResultType()).getClassName();
ClassReaderSource classSource = callSite.getAgent().getClassSource();
ClassReader samClass = classSource.get(samName);
ClassHolder implementor = new ClassHolder("$$LAMBDA" + (lambdaIndex++) + "$$");
implementor.setLevel(AccessLevel.PUBLIC);
if (samClass != null && samClass.hasModifier(ElementModifier.INTERFACE)) {
implementor.setParent("java.lang.Object");
implementor.getInterfaces().add(samName);
} else {
implementor.setParent(samName);
}
int capturedVarCount = callSite.getCalledMethod().parameterCount();
MethodHolder ctor = createConstructor(implementor, Arrays.copyOfRange(invokedType, 0, capturedVarCount));
createBridge(implementor, callSite.getCalledMethod().getName(), instantiatedMethodType, samMethodType);
MethodHolder worker = new MethodHolder(callSite.getCalledMethod().getName(), instantiatedMethodType);
worker.setLevel(AccessLevel.PUBLIC);
ProgramEmitter pe = ProgramEmitter.create(worker);
ValueEmitter thisVar = pe.newVar();
ValueEmitter[] arguments = new ValueEmitter[instantiatedMethodType.length - 1];
for (int i = 0; i < arguments.length; ++i) {
arguments[i] = pe.newVar();
}
ValueType[] implementorSignature = getSignature(implMethod);
ValueEmitter[] passedArguments = new ValueEmitter[implementorSignature.length - 1];
for (int i = 0; i < capturedVarCount; ++i) {
passedArguments[i] = thisVar.getField(new FieldReference(implementor.getName(), "_" + i), invokedType[i]);
}
for (int i = 0; i < instantiatedMethodType.length - 1; ++i) {
passedArguments[i + capturedVarCount] = tryConvertArgument(arguments[i], instantiatedMethodType[i],
implementorSignature[i + capturedVarCount]);
}
ValueEmitter result = invoke(pe, implMethod, callSite.getInstance(), passedArguments);
if (result != null) {
ValueType actualResult = implementorSignature[implementorSignature.length - 1];
ValueType expectedResult = instantiatedMethodType[instantiatedMethodType.length - 1];
tryConvertArgument(result, actualResult, expectedResult).returnValue();
} else {
pe.exit();
}
implementor.addMethod(worker);
callSite.getAgent().submitClass(implementor);
return callerPe.construct(ctor.getReference(), callSite.getArguments().toArray(new ValueEmitter[0]));
}
private ValueEmitter invoke(ProgramEmitter pe, MethodHandle handle, ValueEmitter instance,
ValueEmitter[] arguments) {
switch (handle.getKind()) {
case GET_FIELD:
return instance.getField(new FieldReference(handle.getClassName(), handle.getName()),
handle.getValueType());
case GET_STATIC_FIELD:
return pe.getField(new FieldReference(handle.getClassName(), handle.getName()),
handle.getValueType());
case PUT_FIELD:
instance.setField(new FieldReference(handle.getClassName(), handle.getName()), handle.getValueType(),
arguments[0]);
return null;
case PUT_STATIC_FIELD:
pe.setField(new FieldReference(handle.getClassName(), handle.getName()), handle.getValueType(),
arguments[0]);
return null;
case INVOKE_VIRTUAL:
case INVOKE_INTERFACE:
case INVOKE_SPECIAL:
return instance.invokeVirtual(new MethodReference(handle.getClassName(), handle.getName(),
handle.signature()), arguments);
case INVOKE_STATIC:
return pe.invoke(new MethodReference(handle.getClassName(), handle.getName(),
handle.signature()), arguments);
case INVOKE_CONSTRUCTOR:
return pe.construct(new MethodReference(handle.getClassName(), handle.getName(),
handle.signature()), arguments);
default:
throw new IllegalArgumentException("Unexpected handle type: " + handle.getKind());
}
}
private ValueEmitter tryConvertArgument(ValueEmitter arg, ValueType from, ValueType to) {
if (from.equals(to)) {
return arg;
}
if (from instanceof ValueType.Primitive && to instanceof ValueType.Primitive) {
PrimitiveType fromType = ((ValueType.Primitive) from).getKind();
PrimitiveType toType = ((ValueType.Primitive) to).getKind();
IntegerSubtype fromSubtype = convert(fromType);
if (fromSubtype != null) {
arg = arg.cast(fromSubtype, CastIntegerDirection.TO_INTEGER);
}
arg = arg.cast(convertNumeric(fromType), convertNumeric(toType));
IntegerSubtype toSubtype = convert(toType);
if (toSubtype != null) {
arg = arg.cast(fromSubtype, CastIntegerDirection.FROM_INTEGER);
}
return arg;
} else if (from instanceof ValueType.Primitive && to instanceof ValueType.Object) {
String primitiveClass = ((ValueType.Object) to).getClassName();
PrimitiveType toType = getWrappedPrimitive(primitiveClass);
if (toType == null) {
return arg;
}
arg = tryConvertArgument(arg, from, ValueType.primitive(toType));
return arg.getProgramEmitter().invoke(new MethodReference(primitiveClass, "valueOf",
ValueType.primitive(toType), to), arg);
} else if (from instanceof ValueType.Object && to instanceof ValueType.Primitive) {
String primitiveClass = ((ValueType.Object) from).getClassName();
PrimitiveType fromType = getWrappedPrimitive(primitiveClass);
if (fromType == null) {
return arg;
}
arg = arg.invokeVirtual(new MethodReference(primitiveClass, primitiveName(fromType) + "Value",
ValueType.primitive(fromType)));
return tryConvertArgument(arg, ValueType.primitive(fromType), to);
} else {
return arg.cast(to);
}
}
private IntegerSubtype convert(PrimitiveType type) {
switch (type) {
case BOOLEAN:
case BYTE:
return IntegerSubtype.BYTE;
case SHORT:
return IntegerSubtype.SHORT;
case CHARACTER:
return IntegerSubtype.CHARACTER;
default:
return null;
}
}
private NumericOperandType convertNumeric(PrimitiveType type) {
switch (type) {
case BOOLEAN:
case BYTE:
case SHORT:
case CHARACTER:
case INTEGER:
return NumericOperandType.INT;
case LONG:
return NumericOperandType.LONG;
case FLOAT:
return NumericOperandType.FLOAT;
case DOUBLE:
return NumericOperandType.DOUBLE;
default:
throw new IllegalArgumentException("Unexpected type " + type);
}
}
private PrimitiveType getWrappedPrimitive(String name) {
switch (name) {
case "java.lang.Boolean":
return PrimitiveType.BOOLEAN;
case "java.lang.Byte":
return PrimitiveType.BYTE;
case "java.lang.Short":
return PrimitiveType.SHORT;
case "java.lang.Character":
return PrimitiveType.CHARACTER;
case "java.lang.Integer":
return PrimitiveType.INTEGER;
case "java.lang.Long":
return PrimitiveType.LONG;
case "java.lang.Float":
return PrimitiveType.FLOAT;
case "java.lang.Double":
return PrimitiveType.DOUBLE;
default:
return null;
}
}
private String primitiveName(PrimitiveType type) {
switch (type) {
case BOOLEAN:
return "boolean";
case BYTE:
return "byte";
case SHORT:
return "short";
case CHARACTER:
return "char";
case INTEGER:
return "int";
case LONG:
return "long";
case FLOAT:
return "float";
case DOUBLE:
return "double";
default:
throw new IllegalArgumentException("Unexpected primitive " + type);
}
}
private ValueType[] getSignature(MethodHandle handle) {
switch (handle.getKind()) {
case GET_FIELD:
case GET_STATIC_FIELD:
return new ValueType[] { handle.getValueType() };
case PUT_FIELD:
case PUT_STATIC_FIELD:
return new ValueType[] { handle.getValueType(), ValueType.VOID };
default:
return handle.signature();
}
}
private MethodHolder createConstructor(ClassHolder implementor, ValueType[] types) {
ValueType[] signature = Arrays.copyOf(types, types.length + 1);
signature[types.length] = ValueType.VOID;
MethodHolder ctor = new MethodHolder("<init>", signature);
ctor.setLevel(AccessLevel.PUBLIC);
ProgramEmitter pe = ProgramEmitter.create(ctor);
ValueEmitter thisVar = pe.newVar();
thisVar.invokeSpecial(new MethodReference(implementor.getParent(), "<init>", ValueType.VOID));
for (int i = 0; i < types.length; ++i) {
FieldHolder field = new FieldHolder("_" + i);
field.setLevel(AccessLevel.PRIVATE);
field.setType(types[i]);
implementor.addField(field);
thisVar.setField(field.getReference(), types[i], pe.newVar());
}
pe.exit();
implementor.addMethod(ctor);
return ctor;
}
private void createBridge(ClassHolder implementor, String name, ValueType[] types, ValueType[] bridgeTypes) {
if (Arrays.equals(types, bridgeTypes)) {
return;
}
MethodHolder bridge = new MethodHolder(name, bridgeTypes);
bridge.setLevel(AccessLevel.PUBLIC);
bridge.getModifiers().add(ElementModifier.BRIDGE);
ProgramEmitter pe = ProgramEmitter.create(bridge);
ValueEmitter thisVar = pe.newVar();
ValueEmitter[] arguments = new ValueEmitter[bridgeTypes.length - 1];
for (int i = 0; i < arguments.length; ++i) {
arguments[i] = pe.newVar();
}
for (int i = 0; i < bridgeTypes.length - 1; ++i) {
ValueType type = types[i];
ValueType bridgeType = types[i];
if (type.equals(bridgeType)) {
continue;
}
arguments[i] = arguments[i].cast(type);
}
ValueEmitter result = thisVar.invokeVirtual(new MethodReference(implementor.getName(), name, types),
arguments);
if (result != null) {
if (!types[types.length - 1].equals(bridgeTypes[bridgeTypes.length - 1])) {
result = result.cast(bridgeTypes[bridgeTypes.length - 1]);
}
result.returnValue();
} else {
pe.exit();
}
implementor.addMethod(bridge);
}
}

View File

@ -188,7 +188,6 @@ class DependencyGraphBuilder {
if (program == null) {
return;
}
System.out.println(new ListingBuilder().buildListing(program, ""));
ProgramEmitter pe = ProgramEmitter.create(program);
for (int i = 0; i < program.basicBlockCount(); ++i) {
BasicBlock block = program.basicBlockAt(i);
@ -207,17 +206,18 @@ class DependencyGraphBuilder {
nullInsn.setReceiver(indy.getReceiver());
nullInsn.setLocation(indy.getLocation());
block.getInstructions().set(j, nullInsn);
CallLocation location = new CallLocation(bootstrapMethod, indy.getLocation());
dependencyChecker.getDiagnostics().error(location, "Substitutor for this dependency method "
+ "was not found");
CallLocation location = new CallLocation(caller.getMethod(), currentLocation);
dependencyChecker.getDiagnostics().error(location, "Substitutor for bootstrap "
+ "method {{m0}} was not found", bootstrapMethod);
continue;
}
BasicBlock splitBlock = program.createBasicBlock();
List<Instruction> splitInstructions = block.getInstructions().subList(j + 1,
block.getInstructions().size());
splitBlock.getInstructions().addAll(splitInstructions);
List<Instruction> splitInstructionsBackup = new ArrayList<>(splitInstructions);
splitInstructions.clear();
splitBlock.getInstructions().addAll(splitInstructionsBackup);
for (int k = 0; k < program.basicBlockCount() - 1; ++k) {
BasicBlock replaceBlock = program.basicBlockAt(k);
@ -240,15 +240,13 @@ class DependencyGraphBuilder {
indy.getArguments().stream().map(arg -> pe.var(arg)).collect(Collectors.toList()),
indy.getBootstrapMethod(),
indy.getBootstrapArguments(),
dependencyChecker.getAgent(),
indy.getInstance() != null ? nodes[indy.getInstance().getIndex()] : null,
indy.getArguments().stream().map(arg -> nodes[arg.getIndex()]).collect(Collectors.toList()));
dependencyChecker.getAgent());
ValueEmitter result = substitutor.substitute(callSite, pe);
if (result.getVariable() != null && result.getVariable() != indy.getReceiver()) {
AssignInstruction assign = new AssignInstruction();
assign.setAssignee(result.getVariable());
assign.setReceiver(indy.getReceiver());
pe.addInstruction(insn);
pe.addInstruction(assign);
}
pe.jump(splitBlock);
}

View File

@ -34,20 +34,15 @@ public class DynamicCallSite {
private MethodHandle bootstrapMethod;
private List<RuntimeConstant> bootstrapArguments;
private DependencyAgent agent;
private DependencyNode instanceNode;
private List<DependencyNode> argumentNodes = new ArrayList<>();
DynamicCallSite(MethodDescriptor calledMethod, ValueEmitter instance, List<ValueEmitter> arguments,
MethodHandle bootstrapMethod, List<RuntimeConstant> bootstrapArguments, DependencyAgent agent,
DependencyNode instanceNode, List<DependencyNode> argumentNodes) {
MethodHandle bootstrapMethod, List<RuntimeConstant> bootstrapArguments, DependencyAgent agent) {
this.calledMethod = calledMethod;
this.instance = instance;
this.arguments = Collections.unmodifiableList(new ArrayList<>(arguments));
this.bootstrapMethod = bootstrapMethod;
this.bootstrapArguments = Collections.unmodifiableList(new ArrayList<>(bootstrapArguments));
this.agent = agent;
this.instanceNode = instanceNode;
this.argumentNodes = Collections.unmodifiableList(new ArrayList<>(argumentNodes));
}
public MethodDescriptor getCalledMethod() {
@ -73,12 +68,4 @@ public class DynamicCallSite {
public ValueEmitter getInstance() {
return instance;
}
public DependencyNode getInstanceNode() {
return instanceNode;
}
public List<DependencyNode> getArgumentNodes() {
return argumentNodes;
}
}

View File

@ -53,7 +53,7 @@ public interface ClassReaderSource {
default Stream<ClassReader> getAncestors(String name) {
return StreamSupport.stream(((Iterable<ClassReader>) () -> {
return new Iterator<ClassReader>() {
private Deque<Deque<ClassReader>> state = new ArrayDeque<>();
Deque<Deque<ClassReader>> state = new ArrayDeque<>();
private Set<ClassReader> visited = new HashSet<>();
{
state.push(new ArrayDeque<>());
@ -72,7 +72,7 @@ public interface ClassReaderSource {
return null;
}
@Override public boolean hasNext() {
return !state.isEmpty();
return !this.state.stream().allMatch(e -> e.isEmpty());
}
private void follow(ClassReader cls) {
state.push(new ArrayDeque<>());
@ -112,4 +112,25 @@ public interface ClassReaderSource {
.map(cls -> cls.getMethod(method.getDescriptor()))
.filter(candidate -> candidate != null);
}
default boolean isSuperType(String superType, String subType) {
if (superType.equals(subType)) {
return true;
}
ClassReader cls = get(subType);
if (subType == null) {
return false;
}
if (cls.getParent() != null && !cls.getParent().equals(cls.getName())) {
if (isSuperType(superType, cls.getParent())) {
return true;
}
}
for (String iface : cls.getInterfaces()) {
if (isSuperType(superType, iface)) {
return true;
}
}
return false;
}
}

View File

@ -228,6 +228,9 @@ public class ValueEmitter {
}
public ValueEmitter cast(NumericOperandType from, NumericOperandType to) {
if (from == to) {
return this;
}
Variable result = pe.getProgram().createVariable();
CastNumberInstruction insn = new CastNumberInstruction(from, to);
insn.setValue(variable);

View File

@ -1733,31 +1733,31 @@ public class ProgramParser implements VariableDebugInformation {
static MethodHandle parseHandle(Handle handle) {
switch (handle.getTag()) {
case Opcodes.H_GETFIELD:
return MethodHandle.fieldGetter(handle.getOwner(), handle.getName(),
return MethodHandle.fieldGetter(handle.getOwner().replace('/', '.'), handle.getName(),
ValueType.parse(handle.getDesc()));
case Opcodes.H_GETSTATIC:
return MethodHandle.staticFieldGetter(handle.getOwner(), handle.getName(),
return MethodHandle.staticFieldGetter(handle.getOwner().replace('/', '.'), handle.getName(),
ValueType.parse(handle.getDesc()));
case Opcodes.H_PUTFIELD:
return MethodHandle.fieldSetter(handle.getOwner(), handle.getName(),
return MethodHandle.fieldSetter(handle.getOwner().replace('/', '.'), handle.getName(),
ValueType.parse(handle.getDesc()));
case Opcodes.H_PUTSTATIC:
return MethodHandle.staticFieldSetter(handle.getOwner(), handle.getName(),
return MethodHandle.staticFieldSetter(handle.getOwner().replace('/', '.'), handle.getName(),
ValueType.parse(handle.getDesc()));
case Opcodes.H_INVOKEVIRTUAL:
return MethodHandle.virtualCaller(handle.getOwner(), handle.getName(),
return MethodHandle.virtualCaller(handle.getOwner().replace('/', '.'), handle.getName(),
MethodDescriptor.parseSignature(handle.getDesc()));
case Opcodes.H_INVOKESTATIC:
return MethodHandle.staticCaller(handle.getOwner(), handle.getName(),
return MethodHandle.staticCaller(handle.getOwner().replace('/', '.'), handle.getName(),
MethodDescriptor.parseSignature(handle.getDesc()));
case Opcodes.H_INVOKESPECIAL:
return MethodHandle.specialCaller(handle.getOwner(), handle.getName(),
return MethodHandle.specialCaller(handle.getOwner().replace('/', '.'), handle.getName(),
MethodDescriptor.parseSignature(handle.getDesc()));
case Opcodes.H_NEWINVOKESPECIAL:
return MethodHandle.constructorCaller(handle.getOwner(), handle.getName(),
return MethodHandle.constructorCaller(handle.getOwner().replace('/', '.'), handle.getName(),
MethodDescriptor.parseSignature(handle.getDesc()));
case Opcodes.H_INVOKEINTERFACE:
return MethodHandle.interfaceCaller(handle.getOwner(), handle.getName(),
return MethodHandle.interfaceCaller(handle.getOwner().replace('/', '.'), handle.getName(),
MethodDescriptor.parseSignature(handle.getDesc()));
default:
throw new IllegalArgumentException("Unknown handle tag: " + handle.getTag());

View File

@ -127,6 +127,11 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
methodInjectors.put(methodRef, injector);
}
@Override
public void add(MethodReference methodRef, BootstrapMethodSubstitutor substitutor) {
dependencyChecker.addBootstrapMethodSubstitutor(methodRef, substitutor);
}
@Override
public void add(RendererListener listener) {
rendererListeners.add(listener);

View File

@ -16,6 +16,7 @@
package org.teavm.vm.spi;
import java.util.Properties;
import org.teavm.dependency.BootstrapMethodSubstitutor;
import org.teavm.dependency.DependencyListener;
import org.teavm.javascript.spi.Generator;
import org.teavm.javascript.spi.Injector;
@ -39,6 +40,8 @@ public interface TeaVMHost {
void add(MethodReference methodRef, Injector injector);
void add(MethodReference methodRef, BootstrapMethodSubstitutor substitutor);
void add(RendererListener listener);
<T> void registerService(Class<T> type, T instance);