diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java index cb2776abd..6df6aeaf1 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java @@ -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()); } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/lambda/LambdaMetafactorySubstitutor.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/lambda/LambdaMetafactorySubstitutor.java new file mode 100644 index 000000000..9a28bc502 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/lambda/LambdaMetafactorySubstitutor.java @@ -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("", signature); + ctor.setLevel(AccessLevel.PUBLIC); + + ProgramEmitter pe = ProgramEmitter.create(ctor); + ValueEmitter thisVar = pe.newVar(); + thisVar.invokeSpecial(new MethodReference(implementor.getParent(), "", 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); + } +} diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java index c02e6f54c..3b7e205db 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java @@ -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 splitInstructions = block.getInstructions().subList(j + 1, block.getInstructions().size()); - splitBlock.getInstructions().addAll(splitInstructions); + List 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); } diff --git a/teavm-core/src/main/java/org/teavm/dependency/DynamicCallSite.java b/teavm-core/src/main/java/org/teavm/dependency/DynamicCallSite.java index 35b9f0113..5e1268ec4 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DynamicCallSite.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DynamicCallSite.java @@ -34,20 +34,15 @@ public class DynamicCallSite { private MethodHandle bootstrapMethod; private List bootstrapArguments; private DependencyAgent agent; - private DependencyNode instanceNode; - private List argumentNodes = new ArrayList<>(); DynamicCallSite(MethodDescriptor calledMethod, ValueEmitter instance, List arguments, - MethodHandle bootstrapMethod, List bootstrapArguments, DependencyAgent agent, - DependencyNode instanceNode, List argumentNodes) { + MethodHandle bootstrapMethod, List 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 getArgumentNodes() { - return argumentNodes; - } } diff --git a/teavm-core/src/main/java/org/teavm/model/ClassReaderSource.java b/teavm-core/src/main/java/org/teavm/model/ClassReaderSource.java index a98f6de9f..93d9089da 100644 --- a/teavm-core/src/main/java/org/teavm/model/ClassReaderSource.java +++ b/teavm-core/src/main/java/org/teavm/model/ClassReaderSource.java @@ -53,7 +53,7 @@ public interface ClassReaderSource { default Stream getAncestors(String name) { return StreamSupport.stream(((Iterable) () -> { return new Iterator() { - private Deque> state = new ArrayDeque<>(); + Deque> state = new ArrayDeque<>(); private Set 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; + } } diff --git a/teavm-core/src/main/java/org/teavm/model/emit/ValueEmitter.java b/teavm-core/src/main/java/org/teavm/model/emit/ValueEmitter.java index deee5501d..a7089c16a 100644 --- a/teavm-core/src/main/java/org/teavm/model/emit/ValueEmitter.java +++ b/teavm-core/src/main/java/org/teavm/model/emit/ValueEmitter.java @@ -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); diff --git a/teavm-core/src/main/java/org/teavm/parsing/ProgramParser.java b/teavm-core/src/main/java/org/teavm/parsing/ProgramParser.java index 19a4495ae..04978953c 100644 --- a/teavm-core/src/main/java/org/teavm/parsing/ProgramParser.java +++ b/teavm-core/src/main/java/org/teavm/parsing/ProgramParser.java @@ -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()); diff --git a/teavm-core/src/main/java/org/teavm/vm/TeaVM.java b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java index e60a8378f..1892901eb 100644 --- a/teavm-core/src/main/java/org/teavm/vm/TeaVM.java +++ b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java @@ -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); diff --git a/teavm-core/src/main/java/org/teavm/vm/spi/TeaVMHost.java b/teavm-core/src/main/java/org/teavm/vm/spi/TeaVMHost.java index d76709353..e4877202e 100644 --- a/teavm-core/src/main/java/org/teavm/vm/spi/TeaVMHost.java +++ b/teavm-core/src/main/java/org/teavm/vm/spi/TeaVMHost.java @@ -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); void registerService(Class type, T instance);