diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java index d718e70cc..12ff437f9 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java @@ -15,6 +15,9 @@ */ package org.teavm.classlib.java.lang; +import org.teavm.javascript.ni.GeneratedBy; +import org.teavm.runtime.Async; + /** * * @author Alexey Andreev @@ -56,8 +59,9 @@ public class TThread extends TObject implements TRunnable { return name; } - public static void yield() { - } + @Async + @GeneratedBy(ThreadNativeGenerator.class) + public static native void yield(); public void interrupt() { } @@ -81,4 +85,12 @@ public class TThread extends TObject implements TRunnable { public static boolean holdsLock(@SuppressWarnings("unused") TObject obj) { return true; } + + public static void sleep(long millis) throws TInterruptedException { + sleep((double)millis); + } + + @Async + @GeneratedBy(ThreadNativeGenerator.class) + private static native void sleep(double millis) throws TInterruptedException; } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ThreadNativeGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ThreadNativeGenerator.java new file mode 100644 index 000000000..2560d7931 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ThreadNativeGenerator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015 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.classlib.java.lang; + +import java.io.IOException; +import org.teavm.codegen.SourceWriter; +import org.teavm.javascript.ni.Generator; +import org.teavm.javascript.ni.GeneratorContext; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class ThreadNativeGenerator implements Generator { + @Override + public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { + if (methodRef.getName().equals("sleep")) { + generateSleep(context, writer); + } else if (methodRef.getName().equals("yield")) { + generateYield(context, writer); + } + } + + private void generateSleep(GeneratorContext context, SourceWriter writer) throws IOException { + writer.append("setTimeout(function() {").indent().softNewLine(); + writer.append(context.getCompleteContinuation()).append("();").softNewLine(); + writer.outdent().append("},").ws().append(context.getParameterName(1)).append(");").softNewLine(); + } + + private void generateYield(GeneratorContext context, SourceWriter writer) throws IOException { + writer.append("setTimeout(function() {").indent().softNewLine(); + writer.append(context.getCompleteContinuation()).append("();").softNewLine(); + writer.outdent().append("},").ws().append("0);").softNewLine(); + } +} diff --git a/teavm-core/src/main/java/org/teavm/codegen/DefaultNamingStrategy.java b/teavm-core/src/main/java/org/teavm/codegen/DefaultNamingStrategy.java index 1fd8cea18..f4cbf3a1e 100644 --- a/teavm-core/src/main/java/org/teavm/codegen/DefaultNamingStrategy.java +++ b/teavm-core/src/main/java/org/teavm/codegen/DefaultNamingStrategy.java @@ -57,6 +57,15 @@ public class DefaultNamingStrategy implements NamingStrategy { @Override public String getNameFor(MethodReference method) { + return getNameFor(method, false); + } + + @Override + public String getNameForAsync(MethodReference method) throws NamingException { + return getNameFor(method, true); + } + + private String getNameFor(MethodReference method, boolean async) { MethodReference origMethod = method; method = getRealMethod(method); if (method == null) { @@ -67,7 +76,7 @@ public class DefaultNamingStrategy implements NamingStrategy { if (methodHolder.hasModifier(ElementModifier.STATIC) || method.getDescriptor().getName().equals("") || methodHolder.getLevel() == AccessLevel.PRIVATE) { - String key = method.toString(); + String key = (async ? "A" : "S") + method.toString(); String alias = privateAliases.get(key); if (alias == null) { alias = aliasProvider.getAlias(method); @@ -75,7 +84,7 @@ public class DefaultNamingStrategy implements NamingStrategy { } return alias; } else { - String key = method.getDescriptor().toString(); + String key = (async ? "A" : "S") + method.getDescriptor().toString(); String alias = aliases.get(key); if (alias == null) { alias = aliasProvider.getAlias(method); diff --git a/teavm-core/src/main/java/org/teavm/codegen/NamingStrategy.java b/teavm-core/src/main/java/org/teavm/codegen/NamingStrategy.java index e8f3c289e..9ac73ecfe 100644 --- a/teavm-core/src/main/java/org/teavm/codegen/NamingStrategy.java +++ b/teavm-core/src/main/java/org/teavm/codegen/NamingStrategy.java @@ -27,6 +27,8 @@ public interface NamingStrategy { String getNameFor(MethodReference method) throws NamingException; + String getNameForAsync(MethodReference method) throws NamingException; + String getFullNameFor(MethodReference method) throws NamingException; String getNameFor(FieldReference field) throws NamingException; diff --git a/teavm-core/src/main/java/org/teavm/javascript/AsyncInvocationType.java b/teavm-core/src/main/java/org/teavm/javascript/AsyncInvocationType.java new file mode 100644 index 000000000..8e2c55563 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/AsyncInvocationType.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015 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.javascript; + +/** + * + * @author Alexey Andreev + */ +public enum AsyncInvocationType { + COMPLETE, + ERROR +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java index ef30581e2..c5f4ca06a 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java @@ -23,6 +23,7 @@ import org.teavm.javascript.ni.Generator; import org.teavm.javascript.ni.InjectedBy; import org.teavm.javascript.ni.PreserveOriginalName; import org.teavm.model.*; +import org.teavm.model.util.AsyncProgramSplitter; import org.teavm.model.util.ProgramUtils; /** @@ -45,10 +46,12 @@ public class Decompiler { private Map generators = new HashMap<>(); private Set methodsToPass = new HashSet<>(); private RegularMethodNodeCache regularMethodCache; + private Set asyncMethods; - public Decompiler(ClassHolderSource classSource, ClassLoader classLoader) { + public Decompiler(ClassHolderSource classSource, ClassLoader classLoader, Set asyncMethods) { this.classSource = classSource; this.classLoader = classLoader; + this.asyncMethods = asyncMethods; } public RegularMethodNodeCache getRegularMethodCache() { @@ -154,7 +157,7 @@ public class Decompiler { public MethodNode decompile(MethodHolder method) { return method.getModifiers().contains(ElementModifier.NATIVE) ? decompileNative(method) : - decompileRegular(method); + !asyncMethods.contains(method.getReference()) ? decompileRegular(method) : decompileAsync(method); } public NativeMethodNode decompileNative(MethodHolder method) { @@ -179,6 +182,7 @@ public class Decompiler { method.getDescriptor())); methodNode.getModifiers().addAll(mapModifiers(method.getModifiers())); methodNode.setGenerator(generator); + methodNode.setAsync(asyncMethods.contains(method.getReference())); return methodNode; } @@ -194,14 +198,58 @@ public class Decompiler { return node; } + public AsyncMethodNode decompileAsync(MethodHolder method) { + AsyncMethodNode node = new AsyncMethodNode(method.getReference()); + AsyncProgramSplitter splitter = new AsyncProgramSplitter(asyncMethods); + splitter.split(method.getProgram()); + for (int i = 0; i < splitter.size(); ++i) { + AsyncMethodPart part = getRegularMethodStatement(splitter.getProgram(i), splitter.getBlockSuccessors(i)); + part.setInputVariable(splitter.getInput(i)); + node.getBody().add(part); + } + Program program = method.getProgram(); + for (int i = 0; i < program.variableCount(); ++i) { + node.getVariables().add(program.variableAt(i).getRegister()); + } + Optimizer optimizer = new Optimizer(); + optimizer.optimize(node, method.getProgram()); + node.getModifiers().addAll(mapModifiers(method.getModifiers())); + int paramCount = Math.min(method.getSignature().length, program.variableCount()); + for (int i = 0; i < paramCount; ++i) { + Variable var = program.variableAt(i); + node.getParameterDebugNames().add(new HashSet<>(var.getDebugNames())); + } + return node; + } + public RegularMethodNode decompileRegularCacheMiss(MethodHolder method) { + RegularMethodNode methodNode = new RegularMethodNode(method.getReference()); + Program program = method.getProgram(); + int[] targetBlocks = new int[program.basicBlockCount()]; + Arrays.fill(targetBlocks, -1); + methodNode.setBody(getRegularMethodStatement(program, targetBlocks).getStatement()); + for (int i = 0; i < program.variableCount(); ++i) { + methodNode.getVariables().add(program.variableAt(i).getRegister()); + } + Optimizer optimizer = new Optimizer(); + optimizer.optimize(methodNode, method.getProgram()); + methodNode.getModifiers().addAll(mapModifiers(method.getModifiers())); + int paramCount = Math.min(method.getSignature().length, program.variableCount()); + for (int i = 0; i < paramCount; ++i) { + Variable var = program.variableAt(i); + methodNode.getParameterDebugNames().add(new HashSet<>(var.getDebugNames())); + } + return methodNode; + } + + private AsyncMethodPart getRegularMethodStatement(Program program, int[] targetBlocks) { + AsyncMethodPart result = new AsyncMethodPart(); lastBlockId = 1; - graph = ProgramUtils.buildControlFlowGraph(method.getProgram()); + graph = ProgramUtils.buildControlFlowGraph(program); indexer = new GraphIndexer(graph); graph = indexer.getGraph(); loopGraph = new LoopGraph(this.graph); unflatCode(); - Program program = method.getProgram(); blockMap = new Block[program.basicBlockCount() * 2 + 1]; Deque stack = new ArrayDeque<>(); BlockStatement rootStmt = new BlockStatement(); @@ -247,9 +295,13 @@ public class Decompiler { int tmp = indexer.nodeAt(next); generator.nextBlock = tmp >= 0 && next < indexer.size() ? program.basicBlockAt(tmp) : null; generator.statements.clear(); + generator.asyncTarget = null; InstructionLocation lastLocation = null; NodeLocation nodeLocation = null; - for (Instruction insn : generator.currentBlock.getInstructions()) { + List instructions = generator.currentBlock.getInstructions(); + boolean asyncInvocation = false; + for (int j = 0; j < instructions.size(); ++j) { + Instruction insn = generator.currentBlock.getInstructions().get(j); if (insn.getLocation() != null && lastLocation != insn.getLocation()) { lastLocation = insn.getLocation(); nodeLocation = new NodeLocation(lastLocation.getFileName(), lastLocation.getLine()); @@ -257,41 +309,51 @@ public class Decompiler { if (insn.getLocation() != null) { generator.setCurrentLocation(nodeLocation); } + if (targetBlocks[node] >= 0 && j == instructions.size() - 1) { + generator.asyncTarget = targetBlocks[node]; + asyncInvocation = true; + } insn.acceptVisitor(generator); } + boolean hasAsyncCatch = false; for (TryCatchBlock tryCatch : generator.currentBlock.getTryCatchBlocks()) { - TryCatchStatement tryCatchStmt = new TryCatchStatement(); - tryCatchStmt.setExceptionType(tryCatch.getExceptionType()); - tryCatchStmt.setExceptionVariable(tryCatch.getExceptionVariable().getIndex()); - tryCatchStmt.getProtectedBody().addAll(generator.statements); - generator.statements.clear(); - generator.statements.add(tryCatchStmt); - Statement handlerStmt = generator.generateJumpStatement(tryCatch.getHandler()); - if (handlerStmt != null) { - tryCatchStmt.getHandler().add(handlerStmt); + if (asyncInvocation) { + TryCatchStatement tryCatchStmt = new TryCatchStatement(); + tryCatchStmt.setExceptionType(tryCatch.getExceptionType()); + tryCatchStmt.setExceptionVariable(tryCatch.getExceptionVariable().getIndex()); + tryCatchStmt.getProtectedBody().addAll(generator.statements); + generator.statements.clear(); + generator.statements.add(tryCatchStmt); + Statement handlerStmt = generator.generateJumpStatement(tryCatch.getHandler()); + if (handlerStmt != null) { + tryCatchStmt.getHandler().add(handlerStmt); + } + } else { + AsyncMethodCatch asyncCatch = new AsyncMethodCatch(); + asyncCatch.setExceptionType(tryCatch.getExceptionType()); + asyncCatch.setExceptionVariable(tryCatch.getExceptionVariable().getIndex()); + Statement handlerStmt = generator.generateJumpStatement(tryCatch.getHandler()); + if (handlerStmt != null) { + asyncCatch.getHandler().add(handlerStmt); + } + result.getCatches().add(asyncCatch); + hasAsyncCatch = true; } } + if (hasAsyncCatch) { + TryCatchStatement guardTryCatch = new TryCatchStatement(); + guardTryCatch.setAsync(true); + guardTryCatch.getProtectedBody().addAll(generator.statements); + generator.statements.clear(); + generator.statements.add(guardTryCatch); + } block.body.addAll(generator.statements); } } - SequentialStatement result = new SequentialStatement(); - result.getSequence().addAll(rootStmt.getBody()); - MethodReference reference = new MethodReference(method.getOwnerName(), method.getDescriptor()); - RegularMethodNode methodNode = new RegularMethodNode(reference); - methodNode.getModifiers().addAll(mapModifiers(method.getModifiers())); - methodNode.setBody(result); - for (int i = 0; i < program.variableCount(); ++i) { - methodNode.getVariables().add(program.variableAt(i).getRegister()); - } - Optimizer optimizer = new Optimizer(); - optimizer.optimize(methodNode, method.getProgram()); - methodNode.getModifiers().addAll(mapModifiers(method.getModifiers())); - int paramCount = Math.min(method.getSignature().length, program.variableCount()); - for (int i = 0; i < paramCount; ++i) { - Variable var = program.variableAt(i); - methodNode.getParameterDebugNames().add(new HashSet<>(var.getDebugNames())); - } - return methodNode; + SequentialStatement resultBody = new SequentialStatement(); + resultBody.getSequence().addAll(rootStmt.getBody()); + result.setStatement(resultBody); + return result; } private Set mapModifiers(Set modifiers) { diff --git a/teavm-core/src/main/java/org/teavm/javascript/Optimizer.java b/teavm-core/src/main/java/org/teavm/javascript/Optimizer.java index c5174b0ea..5396e0964 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Optimizer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Optimizer.java @@ -15,6 +15,8 @@ */ package org.teavm.javascript; +import org.teavm.javascript.ast.AsyncMethodNode; +import org.teavm.javascript.ast.AsyncMethodPart; import org.teavm.javascript.ast.RegularMethodNode; import org.teavm.model.Program; @@ -40,4 +42,27 @@ public class Optimizer { method.getVariables().set(i, i); } } + + public void optimize(AsyncMethodNode method, Program program) { + ReadWriteStatsBuilder stats = new ReadWriteStatsBuilder(method.getVariables().size()); + stats.analyze(program); + OptimizingVisitor optimizer = new OptimizingVisitor(stats); + for (AsyncMethodPart part : method.getBody()) { + part.getStatement().acceptVisitor(optimizer); + part.setStatement(optimizer.resultStmt); + } + int paramCount = method.getReference().parameterCount(); + UnusedVariableEliminator unusedEliminator = new UnusedVariableEliminator(paramCount, method.getVariables()); + for (AsyncMethodPart part : method.getBody()) { + part.getStatement().acceptVisitor(unusedEliminator); + } + method.getVariables().subList(unusedEliminator.lastIndex, method.getVariables().size()).clear(); + RedundantLabelEliminator labelEliminator = new RedundantLabelEliminator(); + for (AsyncMethodPart part : method.getBody()) { + part.getStatement().acceptVisitor(labelEliminator); + } + for (int i = 0; i < method.getVariables().size(); ++i) { + method.getVariables().set(i, i); + } + } } diff --git a/teavm-core/src/main/java/org/teavm/javascript/OptimizingVisitor.java b/teavm-core/src/main/java/org/teavm/javascript/OptimizingVisitor.java index 61fd9345b..2d8dd3528 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/OptimizingVisitor.java +++ b/teavm-core/src/main/java/org/teavm/javascript/OptimizingVisitor.java @@ -180,7 +180,7 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { } private boolean tryApplyConstructor(InvocationExpr expr) { - if (!expr.getMethod().getName().equals("")) { + if (expr.getAsyncTarget() != null || !expr.getMethod().getName().equals("")) { return false; } if (resultSequence == null || resultSequence.isEmpty()) { @@ -211,7 +211,7 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { } Expr[] args = expr.getArguments().toArray(new Expr[0]); args = Arrays.copyOfRange(args, 1, args.length); - Expr constructrExpr = Expr.constructObject(expr.getMethod(), args); + InvocationExpr constructrExpr = Expr.constructObject(expr.getMethod(), args); constructrExpr.setLocation(expr.getLocation()); assignment.setRightValue(constructrExpr); stats.reads[var.getIndex()]--; diff --git a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java index b13f3a929..e2ae3b11b 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java @@ -53,6 +53,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext private Deque locationStack = new ArrayDeque<>(); private DeferredCallSite lastCallSite; private DeferredCallSite prevCallSite; + private boolean async; private static class InjectorHolder { public final Injector injector; @@ -469,6 +470,12 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } writer.append(");").ws().append("}"); debugEmitter.emitMethod(null); + + if (!method.isAsync()) { + writer.append(",").newLine(); + writer.append("\"").append(naming.getNameForAsync(ref)).append("\",").ws(); + writer.append("$rt_asyncAdapter(").appendMethodBody(ref).append(')'); + } } writer.append(");").newLine().outdent(); } @@ -518,6 +525,12 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } writer.append(variableName(i)); } + if (method.isAsync()) { + if (startParam < ref.parameterCount() + 1) { + writer.append(',').ws(); + } + writer.append("$return,").ws().append("$throw"); + } writer.append(")").ws().append("{").softNewLine().indent(); method.acceptVisitor(new MethodBodyRenderer()); writer.outdent().append("}").newLine(); @@ -525,9 +538,13 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } private class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext { + private boolean async; + @Override public void visit(NativeMethodNode methodNode) { try { + this.async = methodNode.isAsync(); + Renderer.this.async = methodNode.isAsync(); methodNode.getGenerator().generate(this, writer, methodNode.getReference()); } catch (IOException e) { throw new RenderingException("IO error occured", e); @@ -537,6 +554,8 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(RegularMethodNode method) { try { + Renderer.this.async = false; + this.async = false; MethodReference ref = method.getReference(); for (int i = 0; i < method.getParameterDebugNames().size(); ++i) { debugEmitter.emitVariable(method.getParameterDebugNames().get(i).toArray(new String[0]), @@ -572,6 +591,51 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } } + @Override + public void visit(AsyncMethodNode methodNode) { + try { + Renderer.this.async = true; + this.async = true; + MethodReference ref = methodNode.getReference(); + for (int i = 0; i < methodNode.getParameterDebugNames().size(); ++i) { + debugEmitter.emitVariable(methodNode.getParameterDebugNames().get(i).toArray(new String[0]), + variableName(i)); + } + int variableCount = 0; + for (int var : methodNode.getVariables()) { + variableCount = Math.max(variableCount, var + 1); + } + List variableNames = new ArrayList<>(); + for (int i = ref.parameterCount() + 1; i < variableCount; ++i) { + variableNames.add(variableName(i)); + } + if (!variableNames.isEmpty()) { + writer.append("var "); + for (int i = 0; i < variableNames.size(); ++i) { + if (i > 0) { + writer.append(",").ws(); + } + writer.append(variableNames.get(i)); + } + writer.append(";").softNewLine(); + } + for (int i = 0; i < methodNode.getBody().size(); ++i) { + writer.append("function $part_").append(i).append("($input)").ws().append('{') + .indent().softNewLine(); + AsyncMethodPart part = methodNode.getBody().get(i); + if (part.getInputVariable() != null) { + writer.append(variableName(part.getInputVariable())).ws().append('=').ws().append("$input;") + .softNewLine(); + } + part.getStatement().acceptVisitor(Renderer.this); + writer.outdent().append('}').softNewLine(); + } + writer.append("return $part_0();").softNewLine(); + } catch (IOException e) { + throw new RenderingException("IO error occured", e); + } + } + @Override public String getParameterName(int index) { return variableName(index); @@ -596,6 +660,21 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext public T getService(Class type) { return services.getService(type); } + + @Override + public boolean isAsync() { + return async; + } + + @Override + public String getErrorContinuation() { + return "$throw"; + } + + @Override + public String getCompleteContinuation() { + return "$return"; + } } private void pushLocation(NodeLocation location) { @@ -832,12 +911,18 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext pushLocation(statement.getLocation()); } writer.append("return"); + if (async) { + writer.append(" $return("); + } if (statement.getResult() != null) { writer.append(' '); prevCallSite = debugEmitter.emitCallSite(); statement.getResult().acceptVisitor(this); debugEmitter.emitCallSite(); } + if (async) { + writer.append(')'); + } writer.append(";").softNewLine(); if (statement.getLocation() != null) { popLocation(); @@ -1336,11 +1421,15 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (injector != null) { injector.generate(new InjectorContextImpl(expr.getArguments()), expr.getMethod()); } else { + if (expr.getAsyncTarget() != null) { + writer.append("return "); + } if (expr.getType() == InvocationType.DYNAMIC) { expr.getArguments().get(0).acceptVisitor(this); } String className = naming.getNameFor(expr.getMethod().getClassName()); - String name = naming.getNameFor(expr.getMethod()); + String name = expr.getAsyncTarget() == null ? naming.getNameFor(expr.getMethod()) : + naming.getNameForAsync(expr.getMethod()); String fullName = naming.getFullNameFor(expr.getMethod()); DeferredCallSite callSite = prevCallSite; boolean shouldEraseCallSite = lastCallSite == null; @@ -1348,6 +1437,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext lastCallSite = callSite; } boolean virtual = false; + boolean hasParams = false; switch (expr.getType()) { case STATIC: writer.append(fullName).append("("); @@ -1357,18 +1447,18 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append(",").ws(); } expr.getArguments().get(i).acceptVisitor(this); + hasParams = true; } - writer.append(')'); break; case SPECIAL: writer.append(fullName).append("("); prevCallSite = debugEmitter.emitCallSite(); expr.getArguments().get(0).acceptVisitor(this); + hasParams = true; for (int i = 1; i < expr.getArguments().size(); ++i) { writer.append(",").ws(); expr.getArguments().get(i).acceptVisitor(this); } - writer.append(")"); break; case DYNAMIC: writer.append(".").append(name).append("("); @@ -1377,9 +1467,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (i > 1) { writer.append(",").ws(); } + hasParams = true; expr.getArguments().get(i).acceptVisitor(this); } - writer.append(')'); virtual = true; break; case CONSTRUCTOR: @@ -1389,11 +1479,18 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (i > 0) { writer.append(",").ws(); } + hasParams = true; expr.getArguments().get(i).acceptVisitor(this); } - writer.append(')'); break; } + if (expr.getAsyncTarget() != null) { + if (hasParams) { + writer.append(',').ws(); + } + writer.append("$rt_continue($part_").append(expr.getAsyncTarget()).append(')'); + } + writer.append(')'); if (lastCallSite != null) { if (virtual) { lastCallSite.setVirtualMethod(expr.getMethod()); diff --git a/teavm-core/src/main/java/org/teavm/javascript/StatementGenerator.java b/teavm-core/src/main/java/org/teavm/javascript/StatementGenerator.java index 4b69c3e52..9c3388ff6 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/StatementGenerator.java +++ b/teavm-core/src/main/java/org/teavm/javascript/StatementGenerator.java @@ -37,6 +37,7 @@ class StatementGenerator implements InstructionVisitor { Program program; ClassHolderSource classSource; private NodeLocation currentLocation; + Integer asyncTarget; public void setCurrentLocation(NodeLocation currentLocation) { this.currentLocation = currentLocation; @@ -546,7 +547,7 @@ class StatementGenerator implements InstructionVisitor { for (int i = 0; i < insn.getArguments().size(); ++i) { exprArgs[i] = Expr.var(insn.getArguments().get(i).getIndex()); } - Expr invocationExpr; + InvocationExpr invocationExpr; if (insn.getInstance() != null) { if (insn.getType() == InvocationType.VIRTUAL) { invocationExpr = Expr.invoke(insn.getMethod(), Expr.var(insn.getInstance().getIndex()), exprArgs); @@ -557,8 +558,15 @@ class StatementGenerator implements InstructionVisitor { } else { invocationExpr = Expr.invokeStatic(insn.getMethod(), exprArgs); } - if (insn.getReceiver() != null) { - assign(invocationExpr, insn.getReceiver()); + invocationExpr.setAsyncTarget(asyncTarget); + if (asyncTarget == null) { + if (insn.getReceiver() != null) { + assign(invocationExpr, insn.getReceiver()); + } else { + AssignmentStatement stmt = Statement.assign(null, invocationExpr); + stmt.setLocation(currentLocation); + statements.add(stmt); + } } else { AssignmentStatement stmt = Statement.assign(null, invocationExpr); stmt.setLocation(currentLocation); diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodCatch.java b/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodCatch.java new file mode 100644 index 000000000..09d79fa25 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodCatch.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015 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.javascript.ast; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Alexey Andreev + */ +public class AsyncMethodCatch { + private List handler = new ArrayList<>(); + private String exceptionType; + private Integer exceptionVariable; + + public List getHandler() { + return handler; + } + + public String getExceptionType() { + return exceptionType; + } + + public void setExceptionType(String exceptionType) { + this.exceptionType = exceptionType; + } + + public Integer getExceptionVariable() { + return exceptionVariable; + } + + public void setExceptionVariable(Integer exceptionVariable) { + this.exceptionVariable = exceptionVariable; + } +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodNode.java b/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodNode.java new file mode 100644 index 000000000..bdeb269e3 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodNode.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015 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.javascript.ast; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class AsyncMethodNode extends MethodNode { + private List body = new ArrayList<>(); + private List variables = new ArrayList<>(); + private List> parameterDebugNames = new ArrayList<>(); + + public AsyncMethodNode(MethodReference reference) { + super(reference); + } + + public List getBody() { + return body; + } + + public List getVariables() { + return variables; + } + + public List> getParameterDebugNames() { + return parameterDebugNames; + } + + @Override + public void acceptVisitor(MethodNodeVisitor visitor) { + visitor.visit(this); + } + + @Override + public boolean isAsync() { + return true; + } +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodPart.java b/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodPart.java new file mode 100644 index 000000000..85f216951 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodPart.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015 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.javascript.ast; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Alexey Andreev + */ +public class AsyncMethodPart { + private Statement statement; + private Integer inputVariable; + private List catches = new ArrayList<>(); + + public Statement getStatement() { + return statement; + } + + public void setStatement(Statement statement) { + this.statement = statement; + } + + public Integer getInputVariable() { + return inputVariable; + } + + public void setInputVariable(Integer inputVariable) { + this.inputVariable = inputVariable; + } + + public List getCatches() { + return catches; + } +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/Expr.java b/teavm-core/src/main/java/org/teavm/javascript/ast/Expr.java index 9be6e91d1..0c8f13f2e 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/Expr.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/Expr.java @@ -104,7 +104,7 @@ public abstract class Expr implements Cloneable { return expr; } - public static Expr constructObject(MethodReference method, Expr[] arguments) { + public static InvocationExpr constructObject(MethodReference method, Expr[] arguments) { InvocationExpr expr = new InvocationExpr(); expr.setMethod(method); expr.setType(InvocationType.CONSTRUCTOR); @@ -119,7 +119,7 @@ public abstract class Expr implements Cloneable { return expr; } - public static Expr invoke(MethodReference method, Expr target, Expr[] arguments) { + public static InvocationExpr invoke(MethodReference method, Expr target, Expr[] arguments) { InvocationExpr expr = new InvocationExpr(); expr.setMethod(method); expr.setType(InvocationType.DYNAMIC); @@ -128,7 +128,7 @@ public abstract class Expr implements Cloneable { return expr; } - public static Expr invokeSpecial(MethodReference method, Expr target, Expr[] arguments) { + public static InvocationExpr invokeSpecial(MethodReference method, Expr target, Expr[] arguments) { InvocationExpr expr = new InvocationExpr(); expr.setMethod(method); expr.setType(InvocationType.SPECIAL); @@ -137,7 +137,7 @@ public abstract class Expr implements Cloneable { return expr; } - public static Expr invokeStatic(MethodReference method, Expr[] arguments) { + public static InvocationExpr invokeStatic(MethodReference method, Expr[] arguments) { InvocationExpr expr = new InvocationExpr(); expr.setMethod(method); expr.setType(InvocationType.STATIC); diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/InvocationExpr.java b/teavm-core/src/main/java/org/teavm/javascript/ast/InvocationExpr.java index 2d462bb85..8ee249842 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/InvocationExpr.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/InvocationExpr.java @@ -28,6 +28,7 @@ public class InvocationExpr extends Expr { private MethodReference method; private InvocationType type; private List arguments = new ArrayList<>(); + private Integer asyncTarget; public MethodReference getMethod() { return method; @@ -49,6 +50,14 @@ public class InvocationExpr extends Expr { return arguments; } + public Integer getAsyncTarget() { + return asyncTarget; + } + + public void setAsyncTarget(Integer asyncTarget) { + this.asyncTarget = asyncTarget; + } + @Override public void acceptVisitor(ExprVisitor visitor) { visitor.visit(this); @@ -63,6 +72,7 @@ public class InvocationExpr extends Expr { InvocationExpr copy = new InvocationExpr(); cache.put(this, copy); copy.setMethod(method); + copy.setAsyncTarget(asyncTarget); for (Expr arg : arguments) { copy.getArguments().add(arg.clone(cache)); } diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/MethodNode.java b/teavm-core/src/main/java/org/teavm/javascript/ast/MethodNode.java index ba1e8653b..952a1d154 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/MethodNode.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/MethodNode.java @@ -50,4 +50,6 @@ public abstract class MethodNode { } public abstract void acceptVisitor(MethodNodeVisitor visitor); + + public abstract boolean isAsync(); } diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/MethodNodeVisitor.java b/teavm-core/src/main/java/org/teavm/javascript/ast/MethodNodeVisitor.java index 086dd212d..08573d697 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/MethodNodeVisitor.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/MethodNodeVisitor.java @@ -22,5 +22,7 @@ package org.teavm.javascript.ast; public interface MethodNodeVisitor { void visit(RegularMethodNode methodNode); + void visit(AsyncMethodNode methodNode); + void visit(NativeMethodNode methodNode); } diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/NativeMethodNode.java b/teavm-core/src/main/java/org/teavm/javascript/ast/NativeMethodNode.java index e08e83a83..a58ea263e 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/NativeMethodNode.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/NativeMethodNode.java @@ -24,6 +24,7 @@ import org.teavm.model.MethodReference; */ public class NativeMethodNode extends MethodNode { private Generator generator; + private boolean async; public NativeMethodNode(MethodReference reference) { super(reference); @@ -37,6 +38,15 @@ public class NativeMethodNode extends MethodNode { this.generator = generator; } + @Override + public boolean isAsync() { + return async; + } + + public void setAsync(boolean async) { + this.async = async; + } + @Override public void acceptVisitor(MethodNodeVisitor visitor) { visitor.visit(this); diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/RegularMethodNode.java b/teavm-core/src/main/java/org/teavm/javascript/ast/RegularMethodNode.java index 442d6a963..054a050b1 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/RegularMethodNode.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/RegularMethodNode.java @@ -53,4 +53,9 @@ public class RegularMethodNode extends MethodNode { public void acceptVisitor(MethodNodeVisitor visitor) { visitor.visit(this); } + + @Override + public boolean isAsync() { + return false; + } } diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/TryCatchStatement.java b/teavm-core/src/main/java/org/teavm/javascript/ast/TryCatchStatement.java index a5d11e66a..863cea302 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/TryCatchStatement.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/TryCatchStatement.java @@ -27,6 +27,7 @@ public class TryCatchStatement extends Statement { private List handler = new ArrayList<>(); private String exceptionType; private Integer exceptionVariable; + private boolean async; public List getProtectedBody() { return protectedBody; @@ -52,6 +53,14 @@ public class TryCatchStatement extends Statement { this.exceptionVariable = exceptionVariable; } + public boolean isAsync() { + return async; + } + + public void setAsync(boolean async) { + this.async = async; + } + @Override public void acceptVisitor(StatementVisitor visitor) { visitor.visit(this); diff --git a/teavm-core/src/main/java/org/teavm/javascript/ni/GeneratorContext.java b/teavm-core/src/main/java/org/teavm/javascript/ni/GeneratorContext.java index 985727a90..c28756a55 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ni/GeneratorContext.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ni/GeneratorContext.java @@ -31,4 +31,10 @@ public interface GeneratorContext extends ServiceRepository { ClassLoader getClassLoader(); Properties getProperties(); + + boolean isAsync(); + + String getErrorContinuation(); + + String getCompleteContinuation(); } diff --git a/teavm-core/src/main/java/org/teavm/model/AsyncInformation.java b/teavm-core/src/main/java/org/teavm/model/AsyncInformation.java new file mode 100644 index 000000000..f1e73b4b9 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/model/AsyncInformation.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 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.model; + +import java.util.HashSet; +import java.util.Set; + +/** + * + * @author Alexey Andreev + */ +public class AsyncInformation { + private Set syncMethods = new HashSet<>(); + private Set asyncMethods = new HashSet<>(); + + public Set getSyncMethods() { + return syncMethods; + } + + public Set getAsyncMethods() { + return asyncMethods; + } +} diff --git a/teavm-core/src/main/java/org/teavm/model/instructions/InvokeInstruction.java b/teavm-core/src/main/java/org/teavm/model/instructions/InvokeInstruction.java index 59ff4d2bb..8bf04206e 100644 --- a/teavm-core/src/main/java/org/teavm/model/instructions/InvokeInstruction.java +++ b/teavm-core/src/main/java/org/teavm/model/instructions/InvokeInstruction.java @@ -17,7 +17,9 @@ package org.teavm.model.instructions; import java.util.ArrayList; import java.util.List; -import org.teavm.model.*; +import org.teavm.model.Instruction; +import org.teavm.model.MethodReference; +import org.teavm.model.Variable; /** * diff --git a/teavm-core/src/main/java/org/teavm/model/util/AsyncMethodFinder.java b/teavm-core/src/main/java/org/teavm/model/util/AsyncMethodFinder.java new file mode 100644 index 000000000..4c07b28bd --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/model/util/AsyncMethodFinder.java @@ -0,0 +1,89 @@ +/* + * Copyright 2015 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.model.util; + +import java.util.HashSet; +import java.util.Set; +import org.teavm.callgraph.CallGraph; +import org.teavm.callgraph.CallGraphNode; +import org.teavm.callgraph.CallSite; +import org.teavm.diagnostics.Diagnostics; +import org.teavm.javascript.ni.InjectedBy; +import org.teavm.model.*; +import org.teavm.runtime.Async; +import org.teavm.runtime.Sync; + +/** + * + * @author Alexey Andreev + */ +public class AsyncMethodFinder { + private Set asyncMethods = new HashSet<>(); + private CallGraph callGraph; + private Diagnostics diagnostics; + private ListableClassReaderSource classSource; + + public AsyncMethodFinder(CallGraph callGraph, Diagnostics diagnostics) { + this.callGraph = callGraph; + this.diagnostics = diagnostics; + } + + public Set getAsyncMethods() { + return asyncMethods; + } + + public void find(ListableClassReaderSource classSource) { + this.classSource = classSource; + for (String clsName : classSource.getClassNames()) { + ClassReader cls = classSource.get(clsName); + for (MethodReader method : cls.getMethods()) { + if (asyncMethods.contains(method.getReference())) { + continue; + } + if (method.getAnnotations().get(Async.class.getName()) != null) { + add(method.getReference()); + } + } + } + } + + private void add(MethodReference methodRef) { + if (!asyncMethods.add(methodRef)) { + return; + } + CallGraphNode node = callGraph.getNode(methodRef); + if (node == null) { + return; + } + ClassReader cls = classSource.get(methodRef.getClassName()); + if (cls == null) { + return; + } + MethodReader method = cls.getMethod(methodRef.getDescriptor()); + if (method == null) { + return; + } + if (method.getAnnotations().get(Sync.class.getName()) != null || + method.getAnnotations().get(InjectedBy.class.getName()) != null) { + diagnostics.error(new CallLocation(methodRef), "Method {{m0}} is claimed to be synchronous, " + + "but it is has invocations of asynchronous methods", methodRef); + return; + } + for (CallSite callSite : node.getCallerCallSites()) { + add(callSite.getCaller().getMethod()); + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java b/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java new file mode 100644 index 000000000..602a58c15 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java @@ -0,0 +1,178 @@ +/* + * Copyright 2015 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.model.util; + +import java.util.*; +import org.teavm.model.*; +import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.JumpInstruction; + +/** + * + * @author Alexey Andreev + */ +public class AsyncProgramSplitter { + private List parts = new ArrayList<>(); + private Map partMap = new HashMap<>(); + private Set asyncMethods = new HashSet<>(); + + public AsyncProgramSplitter(Set asyncMethods) { + this.asyncMethods = asyncMethods; + } + + public void split(Program program) { + parts.clear(); + Program initialProgram = createStubCopy(program); + Part initialPart = new Part(); + initialPart.program = initialProgram; + initialPart.blockSuccessors = new int[program.basicBlockCount()]; + Arrays.fill(initialPart.blockSuccessors, -1); + parts.add(initialPart); + partMap.put(0L, 0); + Step initialStep = new Step(); + initialStep.source = 0; + initialStep.targetPart = initialPart; + Queue queue = new ArrayDeque<>(); + queue.add(initialStep); + + taskLoop: while (!queue.isEmpty()) { + Step step = queue.remove(); + BasicBlock targetBlock = step.targetPart.program.basicBlockAt(step.source); + if (targetBlock.instructionCount() > 0) { + continue; + } + BasicBlock sourceBlock = program.basicBlockAt(step.source); + int last = 0; + for (int i = 0; i < sourceBlock.getInstructions().size(); ++i) { + Instruction insn = sourceBlock.getInstructions().get(i); + if (insn instanceof InvokeInstruction) { + InvokeInstruction invoke = (InvokeInstruction)insn; + if (!asyncMethods.contains(invoke.getMethod())) { + continue; + } + + // If we met asynchronous invocation... + // Copy portion of current block from last occurence (or from start) to i'th instruction. + targetBlock.getInstructions().addAll(ProgramUtils.copyInstructions(sourceBlock, + last, i + 1, targetBlock.getProgram())); + ProgramUtils.copyTryCatches(sourceBlock, targetBlock.getProgram()); + for (TryCatchBlock tryCatch : targetBlock.getTryCatchBlocks()) { + if (tryCatch.getHandler() != null) { + Step next = new Step(); + next.source = tryCatch.getHandler().getIndex(); + next.targetPart = step.targetPart; + queue.add(next); + } + } + last = i + 1; + + // If this instruction already separates program, end with current block and refer to the + // existing part + long key = ((long)step.source << 32) | i; + if (partMap.containsKey(key)) { + step.targetPart.blockSuccessors[targetBlock.getIndex()] = partMap.get(key); + continue taskLoop; + } + + // Create a new part + Program nextProgram = createStubCopy(program); + Part part = new Part(); + part.input = invoke.getReceiver() != null ? invoke.getReceiver().getIndex() : null; + part.program = nextProgram; + int partId = parts.size(); + parts.add(part); + part.blockSuccessors = new int[program.basicBlockCount() + 1]; + Arrays.fill(part.blockSuccessors, -1); + + // Mark current instruction as a separator and remember which part is in charge. + partMap.put(key, partId); + step.targetPart.blockSuccessors[targetBlock.getIndex()] = partId; + + // Continue with a new block in the new part + targetBlock = nextProgram.createBasicBlock(); + if (step.source > 0) { + JumpInstruction jumpToNextBlock = new JumpInstruction(); + jumpToNextBlock.setTarget(targetBlock); + nextProgram.basicBlockAt(0).getInstructions().add(jumpToNextBlock); + } + step.targetPart = part; + } + } + targetBlock.getInstructions().addAll(ProgramUtils.copyInstructions(sourceBlock, + last, sourceBlock.getInstructions().size(), targetBlock.getProgram())); + for (TryCatchBlock tryCatch : targetBlock.getTryCatchBlocks()) { + if (tryCatch.getHandler() != null) { + Step next = new Step(); + next.source = tryCatch.getHandler().getIndex(); + next.targetPart = step.targetPart; + queue.add(next); + } + } + InstructionTransitionExtractor successorExtractor = new InstructionTransitionExtractor(); + sourceBlock.getLastInstruction().acceptVisitor(successorExtractor); + for (BasicBlock successor : successorExtractor.getTargets()) { + BasicBlock targetSuccessor = targetBlock.getProgram().basicBlockAt(successor.getIndex()); + if (targetSuccessor.instructionCount() == 0) { + Step next = new Step(); + next.source = successor.getIndex(); + next.targetPart = step.targetPart; + queue.add(next); + } + } + } + + partMap.clear(); + } + + private Program createStubCopy(Program program) { + Program copy = new Program(); + for (int i = 0; i < program.basicBlockCount(); ++i) { + copy.createBasicBlock(); + } + for (int i = 0; i < program.variableCount(); ++i) { + copy.createVariable(); + } + return copy; + } + + public int size() { + return parts.size(); + } + + public Program getProgram(int index) { + return parts.get(index).program; + } + + public Integer getInput(int index) { + return parts.get(index).input; + } + + public int[] getBlockSuccessors(int index) { + int[] result = parts.get(index).blockSuccessors; + return Arrays.copyOf(result, result.length); + } + + private static class Part { + Program program; + Integer input; + int[] blockSuccessors; + } + + private static class Step { + Part targetPart; + int source; + } +} diff --git a/teavm-core/src/main/java/org/teavm/model/util/ProgramUtils.java b/teavm-core/src/main/java/org/teavm/model/util/ProgramUtils.java index e3ca78d8e..a570efe8b 100644 --- a/teavm-core/src/main/java/org/teavm/model/util/ProgramUtils.java +++ b/teavm-core/src/main/java/org/teavm/model/util/ProgramUtils.java @@ -92,21 +92,8 @@ public final class ProgramUtils { for (int i = 0; i < program.basicBlockCount(); ++i) { BasicBlockReader block = program.basicBlockAt(i); BasicBlock blockCopy = copy.basicBlockAt(i); - for (int j = 0; j < block.instructionCount(); ++j) { - block.readInstruction(j, insnCopier); - blockCopy.getInstructions().add(insnCopier.copy); - } - for (PhiReader phi : block.readPhis()) { - Phi phiCopy = new Phi(); - phiCopy.setReceiver(copy.variableAt(phi.getReceiver().getIndex())); - for (IncomingReader incoming : phi.readIncomings()) { - Incoming incomingCopy = new Incoming(); - incomingCopy.setSource(copy.basicBlockAt(incoming.getSource().getIndex())); - incomingCopy.setValue(copy.variableAt(incoming.getValue().getIndex())); - phiCopy.getIncomings().add(incomingCopy); - } - blockCopy.getPhis().add(phiCopy); - } + blockCopy.getInstructions().addAll(copyInstructions(block, 0, block.instructionCount(), copy)); + blockCopy.getPhis().addAll(copyPhis(block, copy)); for (TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) { TryCatchBlock tryCatchCopy = new TryCatchBlock(); tryCatchCopy.setExceptionType(tryCatch.getExceptionType()); @@ -118,6 +105,46 @@ public final class ProgramUtils { return copy; } + public static List copyInstructions(BasicBlockReader block, int from, int to, Program target) { + List result = new ArrayList<>(); + InstructionCopyReader copyReader = new InstructionCopyReader(); + copyReader.programCopy = target; + for (int i = from; i < to; ++i) { + block.readInstruction(i, copyReader); + copyReader.copy.setLocation(copyReader.location); + result.add(copyReader.copy); + } + return result; + } + + public static List copyPhis(BasicBlockReader block, Program target) { + List result = new ArrayList<>(); + for (PhiReader phi : block.readPhis()) { + Phi phiCopy = new Phi(); + phiCopy.setReceiver(target.variableAt(phi.getReceiver().getIndex())); + for (IncomingReader incoming : phi.readIncomings()) { + Incoming incomingCopy = new Incoming(); + incomingCopy.setSource(target.basicBlockAt(incoming.getSource().getIndex())); + incomingCopy.setValue(target.variableAt(incoming.getValue().getIndex())); + phiCopy.getIncomings().add(incomingCopy); + } + result.add(phiCopy); + } + return result; + } + + public static List copyTryCatches(BasicBlockReader block, Program target) { + List result = new ArrayList<>(); + for (TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) { + TryCatchBlock tryCatchCopy = new TryCatchBlock(); + tryCatchCopy.setExceptionType(tryCatch.getExceptionType()); + tryCatchCopy.setExceptionVariable(target.variableAt(tryCatch.getExceptionVariable().getIndex())); + tryCatchCopy.setHandler(target.basicBlockAt(tryCatch.getHandler().getIndex())); + result.add(tryCatchCopy); + } + return result; + } + private static class InstructionCopyReader implements InstructionReader { Instruction copy; Program programCopy; diff --git a/teavm-core/src/main/java/org/teavm/runtime/Async.java b/teavm-core/src/main/java/org/teavm/runtime/Async.java new file mode 100644 index 000000000..46047cd78 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/runtime/Async.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015 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.runtime; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author Alexey Andreev + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD }) +public @interface Async { +} diff --git a/teavm-core/src/main/java/org/teavm/runtime/AsyncCallback.java b/teavm-core/src/main/java/org/teavm/runtime/AsyncCallback.java new file mode 100644 index 000000000..8579eab71 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/runtime/AsyncCallback.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015 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.runtime; + +/** + * + * @author Alexey Andreev + */ +public interface AsyncCallback { + void complete(T value); + + void error(Exception e); +} diff --git a/teavm-core/src/main/java/org/teavm/runtime/Sync.java b/teavm-core/src/main/java/org/teavm/runtime/Sync.java new file mode 100644 index 000000000..8b5a68c2a --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/runtime/Sync.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015 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.runtime; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author Alexey Andreev + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Sync { +} 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 27d261cd0..7a03aa30b 100644 --- a/teavm-core/src/main/java/org/teavm/vm/TeaVM.java +++ b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java @@ -436,7 +436,8 @@ public class TeaVM implements TeaVMHost, ServiceRepository { renderer.renderStringPool(); for (Map.Entry entry : entryPoints.entrySet()) { sourceWriter.append("var ").append(entry.getKey()).ws().append("=").ws() - .appendMethodBody(entry.getValue().reference).append(";").softNewLine(); + .append("$rt_rootInvocationAdapter(") + .appendMethodBody(entry.getValue().reference).append(");").softNewLine(); } for (Map.Entry entry : exportedClasses.entrySet()) { sourceWriter.append("var ").append(entry.getKey()).ws().append("=").ws() @@ -526,8 +527,11 @@ public class TeaVM implements TeaVMHost, ServiceRepository { } private List modelToAst(ListableClassHolderSource classes) { + AsyncMethodFinder asyncFinder = new AsyncMethodFinder(dependencyChecker.getCallGraph(), diagnostics); + asyncFinder.find(classes); + progressListener.phaseStarted(TeaVMPhase.DECOMPILATION, classes.getClassNames().size()); - Decompiler decompiler = new Decompiler(classes, classLoader); + Decompiler decompiler = new Decompiler(classes, classLoader, asyncFinder.getAsyncMethods()); decompiler.setRegularMethodCache(incremental ? astCache : null); for (Map.Entry entry : methodGenerators.entrySet()) { diff --git a/teavm-core/src/main/resources/org/teavm/javascript/runtime.js b/teavm-core/src/main/resources/org/teavm/javascript/runtime.js index 5845769ec..e71694365 100644 --- a/teavm-core/src/main/resources/org/teavm/javascript/runtime.js +++ b/teavm-core/src/main/resources/org/teavm/javascript/runtime.js @@ -392,6 +392,28 @@ function $rt_virtualMethods(cls) { } } } +function $rt_asyncAdapter(f) { + return function() { + var e; + var args = Array.prototype.slice.apply(arguments); + var $throw = args.pop(); + var $return = args.pop(); + try { + var result = f.apply(this, args); + } catch (e) { + return $throw(e); + } + return $return(result); + } +} +function $rt_rootInvocationAdapter(f) { + return function() { + var args = Array.prototype.slice.apply(arguments); + args.push(function() {}); + args.push(function() {}); + return f.apply(this, args); + } +} var $rt_stringPool_instance; function $rt_stringPool(strings) { $rt_stringPool_instance = new Array(strings.length); @@ -402,6 +424,21 @@ function $rt_stringPool(strings) { function $rt_s(index) { return $rt_stringPool_instance[index]; } +var $rt_continueCounter = 0; +function $rt_continue(f) { + if ($rt_continueCounter++ == 10) { + $rt_continueCounter = 0; + return function() { + var self = this; + var args = arguments; + setTimeout(function() { + f.apply(self, args); + }, 0); + }; + } else { + return f; + } +} function $dbg_repr(obj) { return obj.toString ? obj.toString() : ""; diff --git a/teavm-samples/pom.xml b/teavm-samples/pom.xml index bcc56b08c..6513d89c4 100644 --- a/teavm-samples/pom.xml +++ b/teavm-samples/pom.xml @@ -34,5 +34,6 @@ teavm-samples-benchmark teavm-samples-storage teavm-samples-video + teavm-samples-async \ No newline at end of file diff --git a/teavm-samples/teavm-samples-async/.gitignore b/teavm-samples/teavm-samples-async/.gitignore new file mode 100644 index 000000000..c708c363d --- /dev/null +++ b/teavm-samples/teavm-samples-async/.gitignore @@ -0,0 +1,4 @@ +/target +/.settings +/.classpath +/.project diff --git a/teavm-samples/teavm-samples-async/pom.xml b/teavm-samples/teavm-samples-async/pom.xml new file mode 100644 index 000000000..b04494c1b --- /dev/null +++ b/teavm-samples/teavm-samples-async/pom.xml @@ -0,0 +1,93 @@ + + + 4.0.0 + + org.teavm + teavm-samples + 0.3.0-SNAPSHOT + + teavm-samples-async + + war + + TeaVM CPS demo + TeaVM application that demonstrates continuation-passing style generator + + + + org.teavm + teavm-classlib + ${project.version} + + + + + + + maven-war-plugin + 2.4 + + + + ${project.build.directory}/generated/js + + + + + + org.teavm + teavm-maven-plugin + ${project.version} + + + web-client + prepare-package + + build-javascript + + + ${project.build.directory}/generated/js/teavm + org.teavm.samples.async.AsyncProgram + SEPARATE + false + true + true + true + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + ../../checkstyle.xml + config_loc=${basedir}/../.. + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + \ No newline at end of file diff --git a/teavm-samples/teavm-samples-async/src/main/java/org/teavm/samples/async/AsyncProgram.java b/teavm-samples/teavm-samples-async/src/main/java/org/teavm/samples/async/AsyncProgram.java new file mode 100644 index 000000000..fca37cc4a --- /dev/null +++ b/teavm-samples/teavm-samples-async/src/main/java/org/teavm/samples/async/AsyncProgram.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015 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.samples.async; + +/** + * + * @author Alexey Andreev + */ +public final class AsyncProgram { + private AsyncProgram() { + } + + public static void main(String[] args) throws InterruptedException { + withoutAsync(); + System.out.println(); + withAsync(); + } + + private static void withoutAsync() { + System.out.println("Start sync"); + for (int i = 0; i < 20; ++i) { + for (int j = 0; j <= i; ++j) { + System.out.print(j); + System.out.print(' '); + } + System.out.println(); + } + System.out.println("Complete sync"); + } + + private static void withAsync() throws InterruptedException { + System.out.println("Start async"); + for (int i = 0; i < 20; ++i) { + for (int j = 0; j <= i; ++j) { + System.out.print(j); + System.out.print(' '); + } + System.out.println(); + if (i % 3 == 0) { + System.out.println("Suspend for a second"); + Thread.sleep(1000); + } + } + System.out.println("Complete async"); + } +} diff --git a/teavm-samples/teavm-samples-async/src/main/webapp/WEB-INF/web.xml b/teavm-samples/teavm-samples-async/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..6471cd77a --- /dev/null +++ b/teavm-samples/teavm-samples-async/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + + \ No newline at end of file diff --git a/teavm-samples/teavm-samples-async/src/main/webapp/index.html b/teavm-samples/teavm-samples-async/src/main/webapp/index.html new file mode 100644 index 000000000..96817c4a0 --- /dev/null +++ b/teavm-samples/teavm-samples-async/src/main/webapp/index.html @@ -0,0 +1,27 @@ + + + + + Continuation-passing style demo + + + + + +

Please, open developer's console to view program's output

+ + \ No newline at end of file