diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java index 8e082c21b..17712be79 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java @@ -363,16 +363,7 @@ public class Renderer implements RenderingManager { } if (needsClinit) { - writer.append("function ").appendClass(cls.getName()).append("_$callClinit()").ws() - .append("{").softNewLine().indent(); - writer.appendClass(cls.getName()).append("_$callClinit").ws().append("=").ws() - .append("function(){};").newLine(); - for (MethodNode method : clinitMethods) { - renderBody(method, true); - } - writer.appendMethodBody(new MethodReference(cls.getName(), clinit.getDescriptor())) - .append("();").softNewLine(); - writer.outdent().append("}").newLine(); + renderCallClinit(clinit, cls, clinitMethods); } if (!cls.getModifiers().contains(ElementModifier.INTERFACE)) { for (MethodNode method : cls.getMethods()) { @@ -396,6 +387,71 @@ public class Renderer implements RenderingManager { debugEmitter.emitClass(null); } + private void renderCallClinit(MethodReader clinit, ClassNode cls, List clinitMethods) + throws IOException { + boolean isAsync = asyncMethods.contains(clinit.getReference()); + + if (isAsync) { + writer.append("var ").appendClass(cls.getName()).append("_$clinitCalled").ws().append("=").ws() + .append("false;").softNewLine(); + } + + writer.append("function ").appendClass(cls.getName()).append("_$callClinit()").ws() + .append("{").softNewLine().indent(); + + if (isAsync) { + writer.append("var ").append(context.pointerName()).ws().append("=").ws() + .append("0").append(";").softNewLine(); + writer.append("if").ws().append("(").appendFunction("$rt_resuming").append("())").ws().append("{") + .indent().softNewLine(); + writer.append(context.pointerName()).ws().append("=").ws().appendFunction("$rt_nativeThread") + .append("().pop();").softNewLine(); + writer.outdent().append("}").ws(); + writer.append("else if").ws().append("(").appendClass(cls.getName()).append("_$clinitCalled)").ws() + .append("{").indent().softNewLine(); + writer.append("return;").softNewLine(); + writer.outdent().append("}").softNewLine(); + + renderAsyncPrologue(); + + writer.append("case 0:").indent().softNewLine(); + writer.appendClass(cls.getName()).append("_$clinitCalled").ws().append('=').ws().append("true;") + .softNewLine(); + } else { + renderEraseClinit(cls); + for (MethodNode method : clinitMethods) { + renderBody(method, true); + } + } + + if (isAsync) { + writer.append(context.pointerName()).ws().append("=").ws().append("1;").softNewLine(); + writer.outdent().append("case 1:").indent().softNewLine(); + } + + writer.appendMethodBody(new MethodReference(cls.getName(), clinit.getDescriptor())) + .append("();").softNewLine(); + + if (isAsync) { + writer.append("if").ws().append("(").appendFunction("$rt_suspending").append("())").ws().append("{") + .indent().softNewLine(); + writer.append("break " + context.mainLoopName() + ";").softNewLine(); + writer.outdent().append("}").softNewLine(); + renderEraseClinit(cls); + writer.append("return;").softNewLine().outdent(); + + renderAsyncEpilogue(); + writer.appendFunction("$rt_nativeThread").append("().push(" + context.pointerName() + ");").softNewLine(); + } + + writer.outdent().append("}").newLine(); + } + + private void renderEraseClinit(ClassNode cls) throws IOException { + writer.appendClass(cls.getName()).append("_$callClinit").ws().append("=").ws() + .append("function(){};").newLine(); + } + private void renderClassMetadata(List classes) { try { writer.append("$rt_metadata(["); @@ -598,6 +654,18 @@ public class Renderer implements RenderingManager { debugEmitter.emitMethod(null); } + private void renderAsyncPrologue() throws IOException { + writer.append(context.mainLoopName()).append(":").ws().append("while").ws().append("(true)") + .ws().append("{").ws(); + writer.append("switch").ws().append("(").append(context.pointerName()).append(")").ws() + .append('{').softNewLine(); + } + + private void renderAsyncEpilogue() throws IOException { + writer.append("default:").ws().appendFunction("$rt_invalidPointer").append("();").softNewLine(); + writer.append("}}").softNewLine(); + } + private class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext { private boolean async; private StatementRenderer statementRenderer; @@ -726,10 +794,8 @@ public class Renderer implements RenderingManager { if (methodNode.getModifiers().contains(ElementModifier.SYNCHRONIZED)) { writer.append("try").ws().append('{').indent().softNewLine(); } - writer.append(context.mainLoopName()).append(":").ws().append("while").ws().append("(true)") - .ws().append("{").ws(); - writer.append("switch").ws().append("(").append(context.pointerName()).append(")").ws() - .append('{').softNewLine(); + + renderAsyncPrologue(); for (int i = 0; i < methodNode.getBody().size(); ++i) { writer.append("case ").append(i).append(":").indent().softNewLine(); if (i == 0 && methodNode.getModifiers().contains(ElementModifier.SYNCHRONIZED)) { @@ -746,8 +812,7 @@ public class Renderer implements RenderingManager { part.getStatement().acceptVisitor(statementRenderer); writer.outdent(); } - writer.append("default:").ws().appendFunction("$rt_invalidPointer").append("();").softNewLine(); - writer.append("}}").softNewLine(); + renderAsyncEpilogue(); if (methodNode.getModifiers().contains(ElementModifier.SYNCHRONIZED)) { writer.outdent().append("}").ws().append("finally").ws().append('{').indent().softNewLine(); diff --git a/core/src/main/resources/org/teavm/backend/javascript/runtime.js b/core/src/main/resources/org/teavm/backend/javascript/runtime.js index 4288b39eb..e9e4eb65e 100644 --- a/core/src/main/resources/org/teavm/backend/javascript/runtime.js +++ b/core/src/main/resources/org/teavm/backend/javascript/runtime.js @@ -406,10 +406,25 @@ function $rt_metadata(data) { for (var j = 0; j < names.length; j = (j + 1) | 0) { window[names[j]] = (function(cls, name) { return function() { - var clinit = cls.$clinit; - cls.$clinit = function() {}; - clinit(); - return window[name].apply(window, arguments); + var ptr = 0; + if ($rt_resuming()) { + ptr = $rt_nativeThread().pop(); + } + main: while (true) { + switch (ptr) { + case 0: + clinit = cls.$clinit; + clinit(); + ptr = 1; + case 1: + var result = window[name].apply(window, arguments); + if ($rt_suspend()) { + break; + } + return result; + } + } + $rt_nativeThread().push(ptr); } })(cls, names[j]); } diff --git a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java index b3d77e44c..c79de90d0 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java +++ b/platform/src/main/java/org/teavm/platform/plugin/AsyncMethodProcessor.java @@ -19,7 +19,6 @@ import org.teavm.backend.javascript.spi.GeneratedBy; import org.teavm.dependency.PluggableDependency; import org.teavm.diagnostics.Diagnostics; import org.teavm.interop.Async; -import org.teavm.interop.Sync; import org.teavm.model.AnnotationHolder; import org.teavm.model.AnnotationValue; import org.teavm.model.CallLocation; @@ -32,10 +31,6 @@ import org.teavm.model.MethodHolder; import org.teavm.model.ValueType; import org.teavm.platform.async.AsyncCallback; -/** - * - * @author Alexey Andreev - */ public class AsyncMethodProcessor implements ClassHolderTransformer { @Override public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { @@ -65,11 +60,6 @@ public class AsyncMethodProcessor implements ClassHolderTransformer { annot.getValues().put("value", new AnnotationValue(ValueType.parse(AsyncMethodGenerator.class))); method.getAnnotations().add(annot); } - } else if (method.getName().equals("")) { - if (method.getAnnotations().get(Sync.class.getName()) == null) { - AnnotationHolder annot = new AnnotationHolder(Sync.class.getName()); - method.getAnnotations().add(annot); - } } } } diff --git a/tests/src/test/java/org/teavm/vm/VMTest.java b/tests/src/test/java/org/teavm/vm/VMTest.java index b5dfc8169..be749aca3 100644 --- a/tests/src/test/java/org/teavm/vm/VMTest.java +++ b/tests/src/test/java/org/teavm/vm/VMTest.java @@ -116,6 +116,41 @@ public class VMTest { assertEquals(x, id(23)); } + @Test + public void asyncClinit() { + assertEquals(0, initCount); + assertEquals("foo", AsyncClinitClass.foo()); + assertEquals(1, initCount); + assertEquals(AsyncClinitClass.state, "ok"); + assertEquals("bar", AsyncClinitClass.bar()); + assertEquals(1, initCount); + assertEquals(AsyncClinitClass.state, "ok"); + } + + static int initCount = 0; + + private static class AsyncClinitClass { + static String state = ""; + + static { + initCount++; + try { + Thread.sleep(1); + state += "ok"; + } catch (InterruptedException e) { + state += "error"; + } + } + + public static String foo() { + return "foo"; + } + + public static String bar() { + return "bar"; + } + } + private void throwException() { throw new RuntimeException(); }