mirror of
https://github.com/konsoletyper/teavm.git
synced 2024-12-15 02:10:30 +08:00
parent
5684c09690
commit
772dd9eded
@ -23,6 +23,7 @@ description = "implementation of JSO"
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":core"))
|
||||
compileOnly(project(":platform"))
|
||||
|
||||
implementation(libs.rhino)
|
||||
implementation(project(":jso:core"))
|
||||
|
@ -19,10 +19,13 @@ import org.teavm.backend.javascript.TeaVMJavaScriptHost;
|
||||
import org.teavm.jso.JSExceptions;
|
||||
import org.teavm.jso.JSObject;
|
||||
import org.teavm.model.MethodReference;
|
||||
import org.teavm.platform.plugin.PlatformPlugin;
|
||||
import org.teavm.vm.TeaVMPluginUtil;
|
||||
import org.teavm.vm.spi.After;
|
||||
import org.teavm.vm.spi.TeaVMHost;
|
||||
import org.teavm.vm.spi.TeaVMPlugin;
|
||||
|
||||
@After(PlatformPlugin.class)
|
||||
public class JSOPlugin implements TeaVMPlugin {
|
||||
@Override
|
||||
public void install(TeaVMHost host) {
|
||||
|
@ -15,6 +15,6 @@
|
||||
*/
|
||||
package org.teavm.platform.plugin;
|
||||
|
||||
@interface AsyncCallClass {
|
||||
@interface AsyncCaller {
|
||||
String value();
|
||||
}
|
@ -18,12 +18,12 @@ package org.teavm.platform.plugin;
|
||||
import org.teavm.dependency.AbstractDependencyListener;
|
||||
import org.teavm.dependency.DependencyAgent;
|
||||
import org.teavm.dependency.MethodDependency;
|
||||
import org.teavm.interop.Async;
|
||||
|
||||
public class AsyncDependencyListener extends AbstractDependencyListener {
|
||||
@Override
|
||||
public void methodReached(DependencyAgent agent, MethodDependency method) {
|
||||
if (method.getMethod() != null && method.getMethod().getAnnotations().get(Async.class.getName()) != null) {
|
||||
if (method.getMethod() != null && method.getMethod().getAnnotations()
|
||||
.get(AsyncCaller.class.getName()) != null) {
|
||||
new AsyncMethodGenerator().methodReached(agent, method);
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ public class AsyncLowLevelDependencyListener extends AbstractDependencyListener
|
||||
}
|
||||
|
||||
private ClassHolder generateClassDecl(MethodReader method) {
|
||||
AnnotationReader annot = method.getAnnotations().get(AsyncCallClass.class.getName());
|
||||
AnnotationReader annot = method.getAnnotations().get(AsyncCaller.class.getName());
|
||||
String className = annot.getValue("value").getString();
|
||||
if (!generatedClassNames.add(className)) {
|
||||
return null;
|
||||
|
@ -26,6 +26,7 @@ import org.teavm.dependency.DependencyPlugin;
|
||||
import org.teavm.dependency.MethodDependency;
|
||||
import org.teavm.interop.AsyncCallback;
|
||||
import org.teavm.model.ClassReader;
|
||||
import org.teavm.model.ClassReaderSource;
|
||||
import org.teavm.model.ElementModifier;
|
||||
import org.teavm.model.MethodDescriptor;
|
||||
import org.teavm.model.MethodReader;
|
||||
@ -38,7 +39,7 @@ public class AsyncMethodGenerator implements Generator, DependencyPlugin, Virtua
|
||||
|
||||
@Override
|
||||
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
|
||||
MethodReference asyncRef = getAsyncReference(methodRef);
|
||||
MethodReference asyncRef = getAsyncReference(context.getClassSource(), methodRef);
|
||||
writer.append("var thread").ws().append('=').ws().append("$rt_nativeThread();").softNewLine();
|
||||
writer.append("var javaThread").ws().append('=').ws().append("$rt_getThread();").softNewLine();
|
||||
writer.append("if").ws().append("(thread.isResuming())").ws().append("{").indent().softNewLine();
|
||||
@ -65,7 +66,7 @@ public class AsyncMethodGenerator implements Generator, DependencyPlugin, Virtua
|
||||
writer.outdent().append("};").softNewLine();
|
||||
writer.append("callback").ws().append("=").ws().appendMethodBody(AsyncCallbackWrapper.class, "create",
|
||||
AsyncCallback.class, AsyncCallbackWrapper.class).append("(callback);").softNewLine();
|
||||
writer.append("return thread.suspend(function()").ws().append("{").indent().softNewLine();
|
||||
writer.append("thread.suspend(function()").ws().append("{").indent().softNewLine();
|
||||
writer.append("try").ws().append("{").indent().softNewLine();
|
||||
writer.appendMethodBody(asyncRef).append('(');
|
||||
ClassReader cls = context.getClassSource().get(methodRef.getClassName());
|
||||
@ -81,22 +82,19 @@ public class AsyncMethodGenerator implements Generator, DependencyPlugin, Virtua
|
||||
.softNewLine();
|
||||
writer.outdent().append("}").softNewLine();
|
||||
writer.outdent().append("});").softNewLine();
|
||||
writer.append("return null;").softNewLine();
|
||||
}
|
||||
|
||||
private MethodReference getAsyncReference(MethodReference methodRef) {
|
||||
ValueType[] signature = new ValueType[methodRef.parameterCount() + 2];
|
||||
for (int i = 0; i < methodRef.parameterCount(); ++i) {
|
||||
signature[i] = methodRef.getDescriptor().parameterType(i);
|
||||
}
|
||||
signature[methodRef.parameterCount()] = ValueType.parse(AsyncCallback.class);
|
||||
signature[methodRef.parameterCount() + 1] = ValueType.VOID;
|
||||
return new MethodReference(methodRef.getClassName(), methodRef.getName(), signature);
|
||||
private MethodReference getAsyncReference(ClassReaderSource classSource, MethodReference methodRef) {
|
||||
var method = classSource.resolve(methodRef);
|
||||
var callerAnnot = method.getAnnotations().get(AsyncCaller.class.getName());
|
||||
return MethodReference.parse(callerAnnot.getValue("value").getString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void methodReached(DependencyAgent agent, MethodDependency method) {
|
||||
MethodReference ref = method.getReference();
|
||||
MethodReference asyncRef = getAsyncReference(ref);
|
||||
MethodReference asyncRef = getAsyncReference(agent.getClassSource(), ref);
|
||||
MethodDependency asyncMethod = agent.linkMethod(asyncRef);
|
||||
method.addLocationListener(asyncMethod::addLocation);
|
||||
int paramCount = ref.parameterCount();
|
||||
|
@ -31,6 +31,7 @@ import org.teavm.model.ElementModifier;
|
||||
import org.teavm.model.MethodDescriptor;
|
||||
import org.teavm.model.MethodHolder;
|
||||
import org.teavm.model.MethodReference;
|
||||
import org.teavm.model.PrimitiveType;
|
||||
import org.teavm.model.Program;
|
||||
import org.teavm.model.ValueType;
|
||||
import org.teavm.model.Variable;
|
||||
@ -52,7 +53,7 @@ public class AsyncMethodProcessor implements ClassHolderTransformer {
|
||||
@Override
|
||||
public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) {
|
||||
int suffix = 0;
|
||||
for (MethodHolder method : cls.getMethods()) {
|
||||
for (var method : List.copyOf(cls.getMethods())) {
|
||||
if (method.hasModifier(ElementModifier.NATIVE)
|
||||
&& method.getAnnotations().get(Async.class.getName()) != null
|
||||
&& method.getAnnotations().get(GeneratedBy.class.getName()) == null) {
|
||||
@ -75,6 +76,8 @@ public class AsyncMethodProcessor implements ClassHolderTransformer {
|
||||
|
||||
if (lowLevel) {
|
||||
generateLowLevelCall(method, suffix++);
|
||||
} else {
|
||||
generateCallerMethod(cls, method);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -82,7 +85,7 @@ public class AsyncMethodProcessor implements ClassHolderTransformer {
|
||||
|
||||
private void generateLowLevelCall(MethodHolder method, int suffix) {
|
||||
String className = method.getOwnerName() + "$" + method.getName() + "$" + suffix;
|
||||
AnnotationHolder classNameAnnot = new AnnotationHolder(AsyncCallClass.class.getName());
|
||||
AnnotationHolder classNameAnnot = new AnnotationHolder(AsyncCaller.class.getName());
|
||||
classNameAnnot.getValues().put("value", new AnnotationValue(className));
|
||||
method.getAnnotations().add(classNameAnnot);
|
||||
|
||||
@ -186,4 +189,110 @@ public class AsyncMethodProcessor implements ClassHolderTransformer {
|
||||
block.add(invoke);
|
||||
return invoke.getReceiver();
|
||||
}
|
||||
|
||||
private void generateCallerMethod(ClassHolder cls, MethodHolder method) {
|
||||
method.getAnnotations().remove(Async.class.getName());
|
||||
|
||||
var mappedSignature = method.getSignature();
|
||||
mappedSignature[mappedSignature.length - 1] = ValueType.object("java.lang.Object");
|
||||
var callerMethod = new MethodHolder(method.getName() + "$_asyncCall_$", mappedSignature);
|
||||
var annot = new AnnotationHolder(AsyncCaller.class.getName());
|
||||
annot.getValues().put("value", new AnnotationValue(getAsyncReference(method.getReference()).toString()));
|
||||
callerMethod.getAnnotations().add(annot);
|
||||
callerMethod.getAnnotations().add(new AnnotationHolder(Async.class.getName()));
|
||||
callerMethod.getModifiers().add(ElementModifier.NATIVE);
|
||||
cls.addMethod(callerMethod);
|
||||
|
||||
method.getModifiers().remove(ElementModifier.NATIVE);
|
||||
var program = new Program();
|
||||
var block = program.createBasicBlock();
|
||||
var thisVar = program.createVariable();
|
||||
var call = new InvokeInstruction();
|
||||
call.setMethod(callerMethod.getReference());
|
||||
call.setType(InvocationType.SPECIAL);
|
||||
if (!method.hasModifier(ElementModifier.STATIC)) {
|
||||
call.setInstance(thisVar);
|
||||
} else {
|
||||
callerMethod.getModifiers().add(ElementModifier.STATIC);
|
||||
}
|
||||
var args = new Variable[method.parameterCount()];
|
||||
for (var i = 0; i < method.parameterCount(); ++i) {
|
||||
args[i] = program.createVariable();
|
||||
}
|
||||
call.setArguments(args);
|
||||
block.add(call);
|
||||
|
||||
var exit = new ExitInstruction();
|
||||
var returnType = method.getResultType();
|
||||
if (returnType instanceof ValueType.Primitive) {
|
||||
call.setReceiver(program.createVariable());
|
||||
exit.setValueToReturn(unbox(call.getReceiver(), ((ValueType.Primitive) returnType).getKind(),
|
||||
block, program));
|
||||
} else if (!(returnType instanceof ValueType.Void)) {
|
||||
call.setReceiver(program.createVariable());
|
||||
var cast = new CastInstruction();
|
||||
cast.setValue(call.getReceiver());
|
||||
cast.setTargetType(returnType);
|
||||
cast.setReceiver(program.createVariable());
|
||||
block.add(cast);
|
||||
exit.setValueToReturn(cast.getReceiver());
|
||||
}
|
||||
|
||||
block.add(exit);
|
||||
|
||||
method.setProgram(program);
|
||||
}
|
||||
|
||||
private MethodReference getAsyncReference(MethodReference methodRef) {
|
||||
var signature = new ValueType[methodRef.parameterCount() + 2];
|
||||
for (int i = 0; i < methodRef.parameterCount(); ++i) {
|
||||
signature[i] = methodRef.getDescriptor().parameterType(i);
|
||||
}
|
||||
signature[methodRef.parameterCount()] = ValueType.parse(AsyncCallback.class);
|
||||
signature[methodRef.parameterCount() + 1] = ValueType.VOID;
|
||||
return new MethodReference(methodRef.getClassName(), methodRef.getName(), signature);
|
||||
}
|
||||
|
||||
private Variable unbox(Variable value, PrimitiveType type, BasicBlock block, Program program) {
|
||||
var cast = new CastInstruction();
|
||||
cast.setValue(value);
|
||||
cast.setReceiver(program.createVariable());
|
||||
block.add(cast);
|
||||
|
||||
var call = new InvokeInstruction();
|
||||
call.setInstance(cast.getReceiver());
|
||||
call.setReceiver(program.createVariable());
|
||||
call.setType(InvocationType.VIRTUAL);
|
||||
block.add(call);
|
||||
|
||||
switch (type) {
|
||||
case BOOLEAN:
|
||||
call.setMethod(new MethodReference(Boolean.class, "booleanValue", boolean.class));
|
||||
break;
|
||||
case BYTE:
|
||||
call.setMethod(new MethodReference(Byte.class, "byteValue", boolean.class));
|
||||
break;
|
||||
case SHORT:
|
||||
call.setMethod(new MethodReference(Short.class, "shortValue", short.class));
|
||||
break;
|
||||
case CHARACTER:
|
||||
call.setMethod(new MethodReference(Character.class, "charValue", char.class));
|
||||
break;
|
||||
case INTEGER:
|
||||
call.setMethod(new MethodReference(Integer.class, "intValue", int.class));
|
||||
break;
|
||||
case LONG:
|
||||
call.setMethod(new MethodReference(Long.class, "longValue", int.class));
|
||||
break;
|
||||
case FLOAT:
|
||||
call.setMethod(new MethodReference(Float.class, "floatValue", int.class));
|
||||
break;
|
||||
case DOUBLE:
|
||||
call.setMethod(new MethodReference(Double.class, "doubleValue", int.class));
|
||||
break;
|
||||
}
|
||||
|
||||
cast.setTargetType(ValueType.object(call.getMethod().getClassName()));
|
||||
return call.getReceiver();
|
||||
}
|
||||
}
|
||||
|
61
tests/src/test/java/org/teavm/vm/AsyncTest.java
Normal file
61
tests/src/test/java/org/teavm/vm/AsyncTest.java
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2023 Alexey Andreev.
|
||||
*
|
||||
* 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 org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.teavm.interop.Async;
|
||||
import org.teavm.interop.AsyncCallback;
|
||||
import org.teavm.jso.browser.Window;
|
||||
import org.teavm.jso.core.JSString;
|
||||
import org.teavm.junit.EachTestCompiledSeparately;
|
||||
import org.teavm.junit.OnlyPlatform;
|
||||
import org.teavm.junit.SkipJVM;
|
||||
import org.teavm.junit.TeaVMTestRunner;
|
||||
import org.teavm.junit.TestPlatform;
|
||||
|
||||
@RunWith(TeaVMTestRunner.class)
|
||||
@EachTestCompiledSeparately
|
||||
@OnlyPlatform(TestPlatform.JAVASCRIPT)
|
||||
@SkipJVM
|
||||
public class AsyncTest {
|
||||
@Test
|
||||
public void primitives() {
|
||||
assertEquals(23, getPrimitive());
|
||||
}
|
||||
|
||||
@Async
|
||||
private native int getPrimitive();
|
||||
|
||||
private void getPrimitive(AsyncCallback<Integer> callback) {
|
||||
Window.setTimeout(() -> callback.complete(23), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jsObjects() {
|
||||
var str = getJsString();
|
||||
assertEquals(3, str.getLength());
|
||||
assertEquals("foo", str.stringValue());
|
||||
}
|
||||
|
||||
@Async
|
||||
private native JSString getJsString();
|
||||
|
||||
private void getJsString(AsyncCallback<JSString> callback) {
|
||||
Window.setTimeout(() -> callback.complete(JSString.valueOf("foo")), 0);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user