From 5593aa074e8b41793b64694b8da2170bd64d9ca6 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 30 Jan 2015 19:15:12 +0400 Subject: [PATCH 1/8] Start implementing async JS generator --- .../java/org/teavm/javascript/Decompiler.java | 69 +++++++++++++------ .../java/org/teavm/javascript/Renderer.java | 69 +++++++++++++++++-- .../teavm/javascript/ast/AsyncMethodNode.java | 52 ++++++++++++++ .../teavm/javascript/ast/AsyncMethodPart.java | 41 +++++++++++ .../teavm/javascript/ast/InvocationExpr.java | 10 +++ .../javascript/ast/MethodNodeVisitor.java | 2 + .../javascript/ast/NativeMethodNode.java | 9 +++ .../org/teavm/model/AsyncInformation.java | 36 ++++++++++ .../model/instructions/InvokeInstruction.java | 20 +++++- .../model/util/AsyncProgramSplitter.java | 54 +++++++++++++++ .../main/java/org/teavm/runtime/Async.java | 30 ++++++++ .../java/org/teavm/runtime/AsyncCallback.java | 26 +++++++ 12 files changed, 391 insertions(+), 27 deletions(-) create mode 100644 teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodNode.java create mode 100644 teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodPart.java create mode 100644 teavm-core/src/main/java/org/teavm/model/AsyncInformation.java create mode 100644 teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java create mode 100644 teavm-core/src/main/java/org/teavm/runtime/Async.java create mode 100644 teavm-core/src/main/java/org/teavm/runtime/AsyncCallback.java 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..42a220077 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java @@ -23,7 +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.ProgramUtils; +import org.teavm.model.util.AsyncProgramSplitter; /** * @@ -153,11 +153,16 @@ public class Decompiler { } public MethodNode decompile(MethodHolder method) { - return method.getModifiers().contains(ElementModifier.NATIVE) ? decompileNative(method) : + return method.getModifiers().contains(ElementModifier.NATIVE) ? decompileNative(method, false) : decompileRegular(method); } - public NativeMethodNode decompileNative(MethodHolder method) { + public MethodNode decompileAsync(MethodHolder method) { + return method.getModifiers().contains(ElementModifier.NATIVE) ? decompileNative(method, true) : + decompileAsync(method); + } + + public NativeMethodNode decompileNative(MethodHolder method, boolean async) { Generator generator = generators.get(method.getReference()); if (generator == null) { AnnotationHolder annotHolder = method.getAnnotations().get(GeneratedBy.class.getName()); @@ -179,10 +184,12 @@ public class Decompiler { method.getDescriptor())); methodNode.getModifiers().addAll(mapModifiers(method.getModifiers())); methodNode.setGenerator(generator); + methodNode.setAsync(async); return methodNode; } public RegularMethodNode decompileRegular(MethodHolder method) { + // TODO: add caching in case of incremental build if (regularMethodCache == null) { return decompileRegularCacheMiss(method); } @@ -191,17 +198,52 @@ public class Decompiler { node = decompileRegularCacheMiss(method); regularMethodCache.store(method.getReference(), node); } + // TODO: add optimization + node.getModifiers().addAll(mapModifiers(method.getModifiers())); + int paramCount = Math.min(method.getSignature().length, method.getProgram().variableCount()); + for (int i = 0; i < paramCount; ++i) { + Variable var = method.getProgram().variableAt(i); + node.getParameterDebugNames().add(new HashSet<>(var.getDebugNames())); + } + return node; + } + + public AsyncMethodNode decompileRegularAsync(MethodHolder method) { + AsyncMethodNode node = new AsyncMethodNode(method.getReference()); + AsyncProgramSplitter splitter = new AsyncProgramSplitter(); + splitter.split(method.getProgram()); + for (int i = 0; i < splitter.size(); ++i) { + AsyncMethodPart part = new AsyncMethodPart(); + part.setInputVariable(splitter.getInput(i)); + part.setStatement(getRegularMethodStatement(splitter.getProgram(i))); + } return node; } public RegularMethodNode decompileRegularCacheMiss(MethodHolder method) { + RegularMethodNode methodNode = new RegularMethodNode(method.getReference()); + Program program = method.getProgram(); + methodNode.setBody(getRegularMethodStatement(program)); + 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 Statement getRegularMethodStatement(Program program) { lastBlockId = 1; - graph = ProgramUtils.buildControlFlowGraph(method.getProgram()); 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(); @@ -276,22 +318,7 @@ public class Decompiler { } 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; + return result; } private Set mapModifiers(Set modifiers) { 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..88528eaaf 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; @@ -537,6 +538,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(RegularMethodNode method) { try { + Renderer.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 +574,50 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } } + @Override + public void visit(AsyncMethodNode methodNode) { + try { + Renderer.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("$return,").ws() + .append("$throw)").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); @@ -832,12 +878,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(); @@ -854,7 +906,11 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (statement.getLocation() != null) { pushLocation(statement.getLocation()); } - writer.append("$rt_throw("); + if (!async) { + writer.append("$rt_throw("); + } else { + writer.append("return $throw("); + } prevCallSite = debugEmitter.emitCallSite(); statement.getException().acceptVisitor(this); writer.append(");").softNewLine(); @@ -1336,6 +1392,9 @@ 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); } @@ -1358,7 +1417,6 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } expr.getArguments().get(i).acceptVisitor(this); } - writer.append(')'); break; case SPECIAL: writer.append(fullName).append("("); @@ -1368,7 +1426,6 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append(",").ws(); expr.getArguments().get(i).acceptVisitor(this); } - writer.append(")"); break; case DYNAMIC: writer.append(".").append(name).append("("); @@ -1379,7 +1436,6 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } expr.getArguments().get(i).acceptVisitor(this); } - writer.append(')'); virtual = true; break; case CONSTRUCTOR: @@ -1391,9 +1447,12 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } expr.getArguments().get(i).acceptVisitor(this); } - writer.append(')'); break; } + if (expr.getAsyncTarget() != null) { + writer.append(',').ws().append("$part_").append(expr.getAsyncTarget()); + } + writer.append(')'); if (lastCallSite != null) { if (virtual) { lastCallSite.setVirtualMethod(expr.getMethod()); 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..9c017e342 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodNode.java @@ -0,0 +1,52 @@ +/* + * 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); + } +} 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..aed946ea0 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodPart.java @@ -0,0 +1,41 @@ +/* + * 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; + +/** + * + * @author Alexey Andreev + */ +public class AsyncMethodPart { + private Statement statement; + private Integer inputVariable; + + 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; + } +} 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/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..0849a4cfa 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,14 @@ public class NativeMethodNode extends MethodNode { this.generator = generator; } + 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/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..982b4d792 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 @@ -16,8 +16,12 @@ package org.teavm.model.instructions; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; -import org.teavm.model.*; +import java.util.Set; +import org.teavm.model.Instruction; +import org.teavm.model.MethodReference; +import org.teavm.model.Variable; /** * @@ -28,6 +32,8 @@ public class InvokeInstruction extends Instruction { private MethodReference method; private Variable instance; private List arguments = new ArrayList<>(); + private Set implemenetations = new HashSet(); + private boolean resolved; private Variable receiver; public InvocationType getType() { @@ -66,6 +72,18 @@ public class InvokeInstruction extends Instruction { this.receiver = receiver; } + public Set getImplemenetations() { + return implemenetations; + } + + public boolean isResolved() { + return resolved; + } + + public void setResolved(boolean resolved) { + this.resolved = resolved; + } + @Override public void acceptVisitor(InstructionVisitor visitor) { visitor.visit(this); 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..0ac79b9a7 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java @@ -0,0 +1,54 @@ +/* + * 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.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.teavm.model.Program; + +/** + * + * @author Alexey Andreev + */ +public class AsyncProgramSplitter { + private List parts = new ArrayList<>(); + private Map partMap = new HashMap<>(); + + public void split(Program program) { + parts.clear(); + // TODO: implement splitting algorithm + partMap.clear(); + } + + 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; + } + + private static class Part { + Program program; + Integer input; + } +} 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); +} From de855608763f43e265eb8d672ed7baf2849930bd Mon Sep 17 00:00:00 2001 From: konsoletyper Date: Sat, 31 Jan 2015 00:02:41 +0400 Subject: [PATCH 2/8] Implement CFG splitting --- .../java/org/teavm/javascript/Decompiler.java | 34 ++--- .../java/org/teavm/javascript/Renderer.java | 5 + .../teavm/javascript/StatementGenerator.java | 14 +- .../org/teavm/javascript/ast/ClassNode.java | 5 + .../java/org/teavm/javascript/ast/Expr.java | 6 +- .../model/util/AsyncProgramSplitter.java | 126 +++++++++++++++++- .../org/teavm/model/util/ProgramUtils.java | 57 +++++--- .../src/main/java/org/teavm/vm/TeaVM.java | 2 +- 8 files changed, 206 insertions(+), 43 deletions(-) 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 42a220077..8319ae223 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java @@ -24,6 +24,7 @@ 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() { @@ -146,6 +149,7 @@ public class Decompiler { if (method.getAnnotations().get(PreserveOriginalName.class.getName()) != null) { methodNode.setOriginalNamePreserved(true); } + clsNode.getAsyncMethods().add(decompileAsync(method)); } clsNode.getInterfaces().addAll(cls.getInterfaces()); clsNode.getModifiers().addAll(mapModifiers(cls.getModifiers())); @@ -159,7 +163,7 @@ public class Decompiler { public MethodNode decompileAsync(MethodHolder method) { return method.getModifiers().contains(ElementModifier.NATIVE) ? decompileNative(method, true) : - decompileAsync(method); + decompileRegularAsync(method); } public NativeMethodNode decompileNative(MethodHolder method, boolean async) { @@ -189,7 +193,6 @@ public class Decompiler { } public RegularMethodNode decompileRegular(MethodHolder method) { - // TODO: add caching in case of incremental build if (regularMethodCache == null) { return decompileRegularCacheMiss(method); } @@ -198,24 +201,18 @@ public class Decompiler { node = decompileRegularCacheMiss(method); regularMethodCache.store(method.getReference(), node); } - // TODO: add optimization - node.getModifiers().addAll(mapModifiers(method.getModifiers())); - int paramCount = Math.min(method.getSignature().length, method.getProgram().variableCount()); - for (int i = 0; i < paramCount; ++i) { - Variable var = method.getProgram().variableAt(i); - node.getParameterDebugNames().add(new HashSet<>(var.getDebugNames())); - } return node; } public AsyncMethodNode decompileRegularAsync(MethodHolder method) { AsyncMethodNode node = new AsyncMethodNode(method.getReference()); - AsyncProgramSplitter splitter = new AsyncProgramSplitter(); + AsyncProgramSplitter splitter = new AsyncProgramSplitter(asyncMethods); splitter.split(method.getProgram()); for (int i = 0; i < splitter.size(); ++i) { AsyncMethodPart part = new AsyncMethodPart(); part.setInputVariable(splitter.getInput(i)); - part.setStatement(getRegularMethodStatement(splitter.getProgram(i))); + part.setStatement(getRegularMethodStatement(splitter.getProgram(i), i, splitter.getBlockSuccessors(i))); + node.getBody().add(part); } return node; } @@ -223,7 +220,7 @@ public class Decompiler { public RegularMethodNode decompileRegularCacheMiss(MethodHolder method) { RegularMethodNode methodNode = new RegularMethodNode(method.getReference()); Program program = method.getProgram(); - methodNode.setBody(getRegularMethodStatement(program)); + methodNode.setBody(getRegularMethodStatement(program, 0, new int[program.basicBlockCount()])); for (int i = 0; i < program.variableCount(); ++i) { methodNode.getVariables().add(program.variableAt(i).getRegister()); } @@ -238,8 +235,9 @@ public class Decompiler { return methodNode; } - private Statement getRegularMethodStatement(Program program) { + private Statement getRegularMethodStatement(Program program, int currentPart, int[] targetBlocks) { lastBlockId = 1; + graph = ProgramUtils.buildControlFlowGraph(program); indexer = new GraphIndexer(graph); graph = indexer.getGraph(); loopGraph = new LoopGraph(this.graph); @@ -289,9 +287,12 @@ 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(); + 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()); @@ -299,6 +300,9 @@ public class Decompiler { if (insn.getLocation() != null) { generator.setCurrentLocation(nodeLocation); } + if (targetBlocks[i] != currentPart && j == instructions.size() - 1) { + generator.asyncTarget = targetBlocks[i]; + } insn.acceptVisitor(generator); } for (TryCatchBlock tryCatch : generator.currentBlock.getTryCatchBlocks()) { 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 88528eaaf..393c63733 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java @@ -370,6 +370,11 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext for (MethodNode method : nonInitMethods) { renderBody(method, false); } + for (MethodNode method : cls.getAsyncMethods()) { + writer.append("/*").softNewLine(); + renderBody(method, false); + writer.append("*/").softNewLine(); + } renderVirtualDeclarations(cls.getName(), virtualMethods); } catch (NamingException e) { throw new RenderingException("Error rendering class " + cls.getName() + ". See a cause for details", e); 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/ClassNode.java b/teavm-core/src/main/java/org/teavm/javascript/ast/ClassNode.java index dcfcb3923..b76128af7 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/ClassNode.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/ClassNode.java @@ -30,6 +30,7 @@ public class ClassNode { private Set modifiers = EnumSet.noneOf(NodeModifier.class); private List fields = new ArrayList<>(); private List methods = new ArrayList<>(); + private List asyncMethods = new ArrayList<>(); private List interfaces = new ArrayList<>(); public ClassNode(String name, String parentName) { @@ -53,6 +54,10 @@ public class ClassNode { return methods; } + public List getAsyncMethods() { + return asyncMethods; + } + public List getInterfaces() { return interfaces; } 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..476f79e6b 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 @@ -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/model/util/AsyncProgramSplitter.java b/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java index 0ac79b9a7..0934da48b 100644 --- a/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java +++ b/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java @@ -15,11 +15,10 @@ */ package org.teavm.model.util; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.teavm.model.Program; +import java.util.*; +import org.teavm.model.*; +import org.teavm.model.instructions.InvokeInstruction; +import org.teavm.model.instructions.JumpInstruction; /** * @@ -28,13 +27,116 @@ import org.teavm.model.Program; 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(); - // TODO: implement splitting algorithm + Program initialProgram = createStubCopy(program); + Part initialPart = new Part(); + initialPart.program = initialProgram; + initialPart.blockSuccessors = new int[program.basicBlockCount()]; + parts.add(initialPart); + partMap.put(0L, 0); + Step initialStep = new Step(); + initialStep.source = 0; + initialStep.targetPart = initialPart; + Queue queue = new ArrayDeque<>(); + queue.add(initialStep); + + 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 end = step.sourceIndex; + boolean asyncOccured = false; + for (int i = step.sourceIndex; i < sourceBlock.getInstructions().size(); ++i) { + Instruction insn = sourceBlock.getInstructions().get(i); + if (insn instanceof InvokeInstruction) { + InvokeInstruction invoke = (InvokeInstruction)insn; + if (true) { //asyncMethods.contains(invoke)) { + asyncOccured = true; + long key = ((long)step.source << 32) | i; + if (partMap.containsKey(key)) { + step.targetPart.blockSuccessors[step.sourceIndex] = partMap.get(key); + break; + } + Program nextProgram = createStubCopy(program); + BasicBlock nextBlock = nextProgram.basicBlockAt(step.source); + if (step.source > 0) { + JumpInstruction jumpToNextBlock = new JumpInstruction(); + jumpToNextBlock.setTarget(nextBlock); + nextProgram.basicBlockAt(0).getInstructions().add(jumpToNextBlock); + } + Part part = new Part(); + part.input = invoke.getReceiver() != null ? invoke.getReceiver().getIndex() : null; + part.program = nextProgram; + int partId = parts.size(); + part.blockSuccessors = new int[program.basicBlockCount()]; + Arrays.fill(part.blockSuccessors, partId); + partMap.put(key, partId); + step.targetPart.blockSuccessors[step.source] = partId; + parts.add(part); + Step next = new Step(); + next.source = step.source; + next.sourceIndex = i + 1; + next.targetPart = part; + queue.add(next); + break; + } + } + } + targetBlock.getInstructions().addAll(ProgramUtils.copyInstructions(sourceBlock, step.sourceIndex, end + 1, + targetBlock.getProgram())); + if (step.sourceIndex == 0) { + targetBlock.getPhis().addAll(ProgramUtils.copyPhis(sourceBlock, 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.sourceIndex = 0; + next.targetPart = step.targetPart; + queue.add(next); + } + } + if (!asyncOccured) { + 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.sourceIndex = 0; + 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(); } @@ -47,8 +149,20 @@ public class AsyncProgramSplitter { 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; + int sourceIndex; } } 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/vm/TeaVM.java b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java index 27d261cd0..f7e43e5af 100644 --- a/teavm-core/src/main/java/org/teavm/vm/TeaVM.java +++ b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java @@ -527,7 +527,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { private List modelToAst(ListableClassHolderSource classes) { progressListener.phaseStarted(TeaVMPhase.DECOMPILATION, classes.getClassNames().size()); - Decompiler decompiler = new Decompiler(classes, classLoader); + Decompiler decompiler = new Decompiler(classes, classLoader, new HashSet()); decompiler.setRegularMethodCache(incremental ? astCache : null); for (Map.Entry entry : methodGenerators.entrySet()) { From 672de2f111de35df10749fbee272a4ad2306b20d Mon Sep 17 00:00:00 2001 From: konsoletyper Date: Sun, 1 Feb 2015 00:02:20 +0400 Subject: [PATCH 3/8] Fix errors. Generalize AST optimizer to handle async methods --- .../java/org/teavm/javascript/Decompiler.java | 12 +++++++++ .../java/org/teavm/javascript/Optimizer.java | 25 +++++++++++++++++++ .../java/org/teavm/javascript/Renderer.java | 2 +- .../model/util/AsyncProgramSplitter.java | 1 + .../resources/org/teavm/javascript/runtime.js | 13 ++++++++++ 5 files changed, 52 insertions(+), 1 deletion(-) 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 8319ae223..f60778ad2 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java @@ -214,6 +214,18 @@ public class Decompiler { part.setStatement(getRegularMethodStatement(splitter.getProgram(i), i, splitter.getBlockSuccessors(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; } 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/Renderer.java b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java index 393c63733..fb1d11f8f 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java @@ -1455,7 +1455,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext break; } if (expr.getAsyncTarget() != null) { - writer.append(',').ws().append("$part_").append(expr.getAsyncTarget()); + writer.append(',').ws().append("$rt_continue($part_").append(expr.getAsyncTarget()).append(')'); } writer.append(')'); if (lastCallSite != null) { 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 index 0934da48b..3df6610b3 100644 --- a/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java +++ b/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java @@ -57,6 +57,7 @@ public class AsyncProgramSplitter { int end = step.sourceIndex; boolean asyncOccured = false; for (int i = step.sourceIndex; i < sourceBlock.getInstructions().size(); ++i) { + end = i; Instruction insn = sourceBlock.getInstructions().get(i); if (insn instanceof InvokeInstruction) { InvokeInstruction invoke = (InvokeInstruction)insn; 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..f839305bc 100644 --- a/teavm-core/src/main/resources/org/teavm/javascript/runtime.js +++ b/teavm-core/src/main/resources/org/teavm/javascript/runtime.js @@ -402,6 +402,19 @@ 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; + var self = f; + var args = arguments; + setTimeout(function() { + f.apply(self, args); + }, 0); + } else { + return f; + } +} function $dbg_repr(obj) { return obj.toString ? obj.toString() : ""; From 3c9acd8fabf3f991d3a75f5085033a86a8065aa9 Mon Sep 17 00:00:00 2001 From: konsoletyper Date: Sun, 1 Feb 2015 17:43:21 +0400 Subject: [PATCH 4/8] Further work on CPS generator --- .../org/teavm/classlib/java/lang/TThread.java | 16 +++- .../java/lang/ThreadNativeGenerator.java | 49 ++++++++++ .../teavm/codegen/DefaultNamingStrategy.java | 13 ++- .../org/teavm/codegen/NamingStrategy.java | 2 + .../java/org/teavm/javascript/Decompiler.java | 10 +-- .../teavm/javascript/OptimizingVisitor.java | 4 +- .../java/org/teavm/javascript/Renderer.java | 44 +++++++-- .../teavm/javascript/ast/AsyncMethodNode.java | 5 ++ .../org/teavm/javascript/ast/ClassNode.java | 5 -- .../java/org/teavm/javascript/ast/Expr.java | 2 +- .../org/teavm/javascript/ast/MethodNode.java | 2 + .../javascript/ast/NativeMethodNode.java | 1 + .../javascript/ast/RegularMethodNode.java | 5 ++ .../teavm/javascript/ni/GeneratorContext.java | 6 ++ .../model/instructions/InvokeInstruction.java | 16 ---- .../teavm/model/util/AsyncMethodFinder.java | 89 +++++++++++++++++++ .../model/util/AsyncProgramSplitter.java | 2 +- .../src/main/java/org/teavm/runtime/Sync.java | 30 +++++++ .../src/main/java/org/teavm/vm/TeaVM.java | 5 +- .../resources/org/teavm/javascript/runtime.js | 14 +++ 20 files changed, 273 insertions(+), 47 deletions(-) create mode 100644 teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ThreadNativeGenerator.java create mode 100644 teavm-core/src/main/java/org/teavm/model/util/AsyncMethodFinder.java create mode 100644 teavm-core/src/main/java/org/teavm/runtime/Sync.java 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..56ca7bfa3 --- /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("setTimer(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("setTimer(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/Decompiler.java b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java index f60778ad2..af8aaf9a0 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java @@ -149,7 +149,6 @@ public class Decompiler { if (method.getAnnotations().get(PreserveOriginalName.class.getName()) != null) { methodNode.setOriginalNamePreserved(true); } - clsNode.getAsyncMethods().add(decompileAsync(method)); } clsNode.getInterfaces().addAll(cls.getInterfaces()); clsNode.getModifiers().addAll(mapModifiers(cls.getModifiers())); @@ -158,12 +157,7 @@ public class Decompiler { public MethodNode decompile(MethodHolder method) { return method.getModifiers().contains(ElementModifier.NATIVE) ? decompileNative(method, false) : - decompileRegular(method); - } - - public MethodNode decompileAsync(MethodHolder method) { - return method.getModifiers().contains(ElementModifier.NATIVE) ? decompileNative(method, true) : - decompileRegularAsync(method); + !asyncMethods.contains(method.getReference()) ? decompileRegular(method) : decompileAsync(method); } public NativeMethodNode decompileNative(MethodHolder method, boolean async) { @@ -204,7 +198,7 @@ public class Decompiler { return node; } - public AsyncMethodNode decompileRegularAsync(MethodHolder method) { + public AsyncMethodNode decompileAsync(MethodHolder method) { AsyncMethodNode node = new AsyncMethodNode(method.getReference()); AsyncProgramSplitter splitter = new AsyncProgramSplitter(asyncMethods); splitter.split(method.getProgram()); 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 fb1d11f8f..16befdbe6 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java @@ -370,11 +370,6 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext for (MethodNode method : nonInitMethods) { renderBody(method, false); } - for (MethodNode method : cls.getAsyncMethods()) { - writer.append("/*").softNewLine(); - renderBody(method, false); - writer.append("*/").softNewLine(); - } renderVirtualDeclarations(cls.getName(), virtualMethods); } catch (NamingException e) { throw new RenderingException("Error rendering class " + cls.getName() + ". See a cause for details", e); @@ -475,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(); } @@ -524,6 +525,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } writer.append(variableName(i)); } + if (method.isAsync()) { + writer.append(',').ws().append("$return,").ws().append("$throw"); + } writer.append(")").ws().append("{").softNewLine().indent(); method.acceptVisitor(new MethodBodyRenderer()); writer.outdent().append("}").newLine(); @@ -531,9 +535,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); @@ -544,6 +552,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext 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]), @@ -583,6 +592,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext 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]), @@ -607,8 +617,8 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append(";").softNewLine(); } for (int i = 0; i < methodNode.getBody().size(); ++i) { - writer.append("function $part_").append(i).append("($input,").ws().append("$return,").ws() - .append("$throw)").ws().append('{').indent().softNewLine(); + 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;") @@ -617,7 +627,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext part.getStatement().acceptVisitor(Renderer.this); writer.outdent().append('}').softNewLine(); } - writer.append("return $part_0;").softNewLine(); + writer.append("return $part_0();").softNewLine(); } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -647,6 +657,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) { @@ -1404,7 +1429,8 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext 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; 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 index 9c017e342..bdeb269e3 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodNode.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodNode.java @@ -49,4 +49,9 @@ public class AsyncMethodNode extends MethodNode { 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/ClassNode.java b/teavm-core/src/main/java/org/teavm/javascript/ast/ClassNode.java index b76128af7..dcfcb3923 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/ClassNode.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/ClassNode.java @@ -30,7 +30,6 @@ public class ClassNode { private Set modifiers = EnumSet.noneOf(NodeModifier.class); private List fields = new ArrayList<>(); private List methods = new ArrayList<>(); - private List asyncMethods = new ArrayList<>(); private List interfaces = new ArrayList<>(); public ClassNode(String name, String parentName) { @@ -54,10 +53,6 @@ public class ClassNode { return methods; } - public List getAsyncMethods() { - return asyncMethods; - } - public List getInterfaces() { return interfaces; } 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 476f79e6b..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); 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/NativeMethodNode.java b/teavm-core/src/main/java/org/teavm/javascript/ast/NativeMethodNode.java index 0849a4cfa..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 @@ -38,6 +38,7 @@ public class NativeMethodNode extends MethodNode { this.generator = generator; } + @Override public boolean isAsync() { return async; } 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/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/instructions/InvokeInstruction.java b/teavm-core/src/main/java/org/teavm/model/instructions/InvokeInstruction.java index 982b4d792..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 @@ -16,9 +16,7 @@ package org.teavm.model.instructions; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; import org.teavm.model.Instruction; import org.teavm.model.MethodReference; import org.teavm.model.Variable; @@ -32,8 +30,6 @@ public class InvokeInstruction extends Instruction { private MethodReference method; private Variable instance; private List arguments = new ArrayList<>(); - private Set implemenetations = new HashSet(); - private boolean resolved; private Variable receiver; public InvocationType getType() { @@ -72,18 +68,6 @@ public class InvokeInstruction extends Instruction { this.receiver = receiver; } - public Set getImplemenetations() { - return implemenetations; - } - - public boolean isResolved() { - return resolved; - } - - public void setResolved(boolean resolved) { - this.resolved = resolved; - } - @Override public void acceptVisitor(InstructionVisitor visitor) { visitor.visit(this); 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 index 3df6610b3..da46951ac 100644 --- a/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java +++ b/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java @@ -61,7 +61,7 @@ public class AsyncProgramSplitter { Instruction insn = sourceBlock.getInstructions().get(i); if (insn instanceof InvokeInstruction) { InvokeInstruction invoke = (InvokeInstruction)insn; - if (true) { //asyncMethods.contains(invoke)) { + if (asyncMethods.contains(invoke.getMethod())) { asyncOccured = true; long key = ((long)step.source << 32) | i; if (partMap.containsKey(key)) { 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 f7e43e5af..ea59790cc 100644 --- a/teavm-core/src/main/java/org/teavm/vm/TeaVM.java +++ b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java @@ -526,8 +526,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, new HashSet()); + 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 f839305bc..c95809d7c 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,20 @@ 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); + } +} var $rt_stringPool_instance; function $rt_stringPool(strings) { $rt_stringPool_instance = new Array(strings.length); From 62d3e9f40e3ba7e681774658c30a81c8c8608802 Mon Sep 17 00:00:00 2001 From: konsoletyper Date: Sun, 1 Feb 2015 22:08:04 +0400 Subject: [PATCH 5/8] Further work on CPS generator --- .../java/lang/ThreadNativeGenerator.java | 8 +- .../java/org/teavm/javascript/Decompiler.java | 10 +- .../model/util/AsyncProgramSplitter.java | 109 +++++++++--------- .../resources/org/teavm/javascript/runtime.js | 2 +- 4 files changed, 65 insertions(+), 64 deletions(-) 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 index 56ca7bfa3..2560d7931 100644 --- 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 @@ -36,14 +36,14 @@ public class ThreadNativeGenerator implements Generator { } private void generateSleep(GeneratorContext context, SourceWriter writer) throws IOException { - writer.append("setTimer(function() {").indent().softNewLine(); + writer.append("setTimeout(function() {").indent().softNewLine(); writer.append(context.getCompleteContinuation()).append("();").softNewLine(); - writer.outdent().append(',').ws().append(context.getParameterName(1)).append(");").softNewLine(); + writer.outdent().append("},").ws().append(context.getParameterName(1)).append(");").softNewLine(); } private void generateYield(GeneratorContext context, SourceWriter writer) throws IOException { - writer.append("setTimer(function() {").indent().softNewLine(); + writer.append("setTimeout(function() {").indent().softNewLine(); writer.append(context.getCompleteContinuation()).append("();").softNewLine(); - writer.outdent().append(',').ws().append("0);").softNewLine(); + writer.outdent().append("},").ws().append("0);").softNewLine(); } } 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 af8aaf9a0..e11b7c07a 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java @@ -205,7 +205,7 @@ public class Decompiler { for (int i = 0; i < splitter.size(); ++i) { AsyncMethodPart part = new AsyncMethodPart(); part.setInputVariable(splitter.getInput(i)); - part.setStatement(getRegularMethodStatement(splitter.getProgram(i), i, splitter.getBlockSuccessors(i))); + part.setStatement(getRegularMethodStatement(splitter.getProgram(i), splitter.getBlockSuccessors(i))); node.getBody().add(part); } Program program = method.getProgram(); @@ -226,7 +226,7 @@ public class Decompiler { public RegularMethodNode decompileRegularCacheMiss(MethodHolder method) { RegularMethodNode methodNode = new RegularMethodNode(method.getReference()); Program program = method.getProgram(); - methodNode.setBody(getRegularMethodStatement(program, 0, new int[program.basicBlockCount()])); + methodNode.setBody(getRegularMethodStatement(program, new int[program.basicBlockCount()])); for (int i = 0; i < program.variableCount(); ++i) { methodNode.getVariables().add(program.variableAt(i).getRegister()); } @@ -241,7 +241,7 @@ public class Decompiler { return methodNode; } - private Statement getRegularMethodStatement(Program program, int currentPart, int[] targetBlocks) { + private Statement getRegularMethodStatement(Program program, int[] targetBlocks) { lastBlockId = 1; graph = ProgramUtils.buildControlFlowGraph(program); indexer = new GraphIndexer(graph); @@ -306,8 +306,8 @@ public class Decompiler { if (insn.getLocation() != null) { generator.setCurrentLocation(nodeLocation); } - if (targetBlocks[i] != currentPart && j == instructions.size() - 1) { - generator.asyncTarget = targetBlocks[i]; + if (targetBlocks[node] >= 0 && j == instructions.size() - 1) { + generator.asyncTarget = targetBlocks[node]; } insn.acceptVisitor(generator); } 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 index da46951ac..41628de0a 100644 --- a/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java +++ b/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java @@ -39,6 +39,7 @@ public class AsyncProgramSplitter { 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(); @@ -47,79 +48,80 @@ public class AsyncProgramSplitter { Queue queue = new ArrayDeque<>(); queue.add(initialStep); - while (!queue.isEmpty()) { + 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 end = step.sourceIndex; - boolean asyncOccured = false; - for (int i = step.sourceIndex; i < sourceBlock.getInstructions().size(); ++i) { - end = i; + 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())) { - asyncOccured = true; - long key = ((long)step.source << 32) | i; - if (partMap.containsKey(key)) { - step.targetPart.blockSuccessors[step.sourceIndex] = partMap.get(key); - break; - } - Program nextProgram = createStubCopy(program); - BasicBlock nextBlock = nextProgram.basicBlockAt(step.source); - if (step.source > 0) { - JumpInstruction jumpToNextBlock = new JumpInstruction(); - jumpToNextBlock.setTarget(nextBlock); - nextProgram.basicBlockAt(0).getInstructions().add(jumpToNextBlock); - } - Part part = new Part(); - part.input = invoke.getReceiver() != null ? invoke.getReceiver().getIndex() : null; - part.program = nextProgram; - int partId = parts.size(); - part.blockSuccessors = new int[program.basicBlockCount()]; - Arrays.fill(part.blockSuccessors, partId); - partMap.put(key, partId); - step.targetPart.blockSuccessors[step.source] = partId; - parts.add(part); - Step next = new Step(); - next.source = step.source; - next.sourceIndex = i + 1; - next.targetPart = part; - queue.add(next); - break; + 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()); + 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, step.sourceIndex, end + 1, - targetBlock.getProgram())); - if (step.sourceIndex == 0) { - targetBlock.getPhis().addAll(ProgramUtils.copyPhis(sourceBlock, targetBlock.getProgram())); - } - ProgramUtils.copyTryCatches(sourceBlock, targetBlock.getProgram()); + 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.sourceIndex = 0; next.targetPart = step.targetPart; queue.add(next); } } - if (!asyncOccured) { - 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.sourceIndex = 0; - 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); } } } @@ -164,6 +166,5 @@ public class AsyncProgramSplitter { private static class Step { Part targetPart; int source; - int sourceIndex; } } 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 c95809d7c..e4ade6e48 100644 --- a/teavm-core/src/main/resources/org/teavm/javascript/runtime.js +++ b/teavm-core/src/main/resources/org/teavm/javascript/runtime.js @@ -397,7 +397,7 @@ function $rt_asyncAdapter(f) { var e; var args = Array.prototype.slice.apply(arguments); var $throw = args.pop(); - var $return args.pop(); + var $return = args.pop(); try { var result = f.apply(this, args); } catch (e) { From 071f2bb46caab356b6cc1544b4e486f930025be4 Mon Sep 17 00:00:00 2001 From: konsoletyper Date: Sun, 1 Feb 2015 22:55:33 +0400 Subject: [PATCH 6/8] First working prototype of CPS --- .../java/org/teavm/javascript/Decompiler.java | 6 +++--- .../java/org/teavm/javascript/Renderer.java | 15 ++++++++++++-- .../src/main/java/org/teavm/vm/TeaVM.java | 3 ++- .../resources/org/teavm/javascript/runtime.js | 20 ++++++++++++++----- 4 files changed, 33 insertions(+), 11 deletions(-) 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 e11b7c07a..8e97a19d9 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java @@ -156,11 +156,11 @@ public class Decompiler { } public MethodNode decompile(MethodHolder method) { - return method.getModifiers().contains(ElementModifier.NATIVE) ? decompileNative(method, false) : + return method.getModifiers().contains(ElementModifier.NATIVE) ? decompileNative(method) : !asyncMethods.contains(method.getReference()) ? decompileRegular(method) : decompileAsync(method); } - public NativeMethodNode decompileNative(MethodHolder method, boolean async) { + public NativeMethodNode decompileNative(MethodHolder method) { Generator generator = generators.get(method.getReference()); if (generator == null) { AnnotationHolder annotHolder = method.getAnnotations().get(GeneratedBy.class.getName()); @@ -182,7 +182,7 @@ public class Decompiler { method.getDescriptor())); methodNode.getModifiers().addAll(mapModifiers(method.getModifiers())); methodNode.setGenerator(generator); - methodNode.setAsync(async); + methodNode.setAsync(asyncMethods.contains(method.getReference())); return methodNode; } 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 16befdbe6..d6b55904d 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java @@ -526,7 +526,10 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append(variableName(i)); } if (method.isAsync()) { - writer.append(',').ws().append("$return,").ws().append("$throw"); + if (startParam < ref.parameterCount() + 1) { + writer.append(',').ws(); + } + writer.append("$return,").ws().append("$throw"); } writer.append(")").ws().append("{").softNewLine().indent(); method.acceptVisitor(new MethodBodyRenderer()); @@ -1438,6 +1441,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("("); @@ -1447,12 +1451,14 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append(",").ws(); } expr.getArguments().get(i).acceptVisitor(this); + hasParams = true; } 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); @@ -1465,6 +1471,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (i > 1) { writer.append(",").ws(); } + hasParams = true; expr.getArguments().get(i).acceptVisitor(this); } virtual = true; @@ -1476,12 +1483,16 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (i > 0) { writer.append(",").ws(); } + hasParams = true; expr.getArguments().get(i).acceptVisitor(this); } break; } if (expr.getAsyncTarget() != null) { - writer.append(',').ws().append("$rt_continue($part_").append(expr.getAsyncTarget()).append(')'); + if (hasParams) { + writer.append(',').ws(); + } + writer.append("$rt_continue($part_").append(expr.getAsyncTarget()).append(')'); } writer.append(')'); if (lastCallSite != null) { 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 ea59790cc..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() 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 e4ade6e48..e71694365 100644 --- a/teavm-core/src/main/resources/org/teavm/javascript/runtime.js +++ b/teavm-core/src/main/resources/org/teavm/javascript/runtime.js @@ -406,6 +406,14 @@ function $rt_asyncAdapter(f) { 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); @@ -420,11 +428,13 @@ var $rt_continueCounter = 0; function $rt_continue(f) { if ($rt_continueCounter++ == 10) { $rt_continueCounter = 0; - var self = f; - var args = arguments; - setTimeout(function() { - f.apply(self, args); - }, 0); + return function() { + var self = this; + var args = arguments; + setTimeout(function() { + f.apply(self, args); + }, 0); + }; } else { return f; } From 4496b1ab302d5be8b337e9048dcdf6ba6ddda182 Mon Sep 17 00:00:00 2001 From: konsoletyper Date: Sun, 1 Feb 2015 23:08:42 +0400 Subject: [PATCH 7/8] Add sample application to show how CPS generator works --- teavm-samples/pom.xml | 1 + teavm-samples/teavm-samples-async/.gitignore | 4 + teavm-samples/teavm-samples-async/pom.xml | 93 +++++++++++++++++++ .../org/teavm/samples/async/AsyncProgram.java | 59 ++++++++++++ .../src/main/webapp/WEB-INF/web.xml | 21 +++++ .../src/main/webapp/index.html | 27 ++++++ 6 files changed, 205 insertions(+) create mode 100644 teavm-samples/teavm-samples-async/.gitignore create mode 100644 teavm-samples/teavm-samples-async/pom.xml create mode 100644 teavm-samples/teavm-samples-async/src/main/java/org/teavm/samples/async/AsyncProgram.java create mode 100644 teavm-samples/teavm-samples-async/src/main/webapp/WEB-INF/web.xml create mode 100644 teavm-samples/teavm-samples-async/src/main/webapp/index.html 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 From b36c10760c361a0b7e87586e365a3bc293b46d47 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 2 Feb 2015 18:58:44 +0400 Subject: [PATCH 8/8] Working on async exception catching --- .../teavm/javascript/AsyncInvocationType.java | 25 +++++++++ .../java/org/teavm/javascript/Decompiler.java | 55 ++++++++++++++----- .../java/org/teavm/javascript/Renderer.java | 6 +- .../javascript/ast/AsyncMethodCatch.java | 49 +++++++++++++++++ .../teavm/javascript/ast/AsyncMethodPart.java | 8 +++ .../javascript/ast/TryCatchStatement.java | 9 +++ .../model/util/AsyncProgramSplitter.java | 8 +++ 7 files changed, 140 insertions(+), 20 deletions(-) create mode 100644 teavm-core/src/main/java/org/teavm/javascript/AsyncInvocationType.java create mode 100644 teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodCatch.java 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 8e97a19d9..c5f4ca06a 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java @@ -203,9 +203,8 @@ public class Decompiler { AsyncProgramSplitter splitter = new AsyncProgramSplitter(asyncMethods); splitter.split(method.getProgram()); for (int i = 0; i < splitter.size(); ++i) { - AsyncMethodPart part = new AsyncMethodPart(); + AsyncMethodPart part = getRegularMethodStatement(splitter.getProgram(i), splitter.getBlockSuccessors(i)); part.setInputVariable(splitter.getInput(i)); - part.setStatement(getRegularMethodStatement(splitter.getProgram(i), splitter.getBlockSuccessors(i))); node.getBody().add(part); } Program program = method.getProgram(); @@ -226,7 +225,9 @@ public class Decompiler { public RegularMethodNode decompileRegularCacheMiss(MethodHolder method) { RegularMethodNode methodNode = new RegularMethodNode(method.getReference()); Program program = method.getProgram(); - methodNode.setBody(getRegularMethodStatement(program, new int[program.basicBlockCount()])); + 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()); } @@ -241,7 +242,8 @@ public class Decompiler { return methodNode; } - private Statement getRegularMethodStatement(Program program, int[] targetBlocks) { + private AsyncMethodPart getRegularMethodStatement(Program program, int[] targetBlocks) { + AsyncMethodPart result = new AsyncMethodPart(); lastBlockId = 1; graph = ProgramUtils.buildControlFlowGraph(program); indexer = new GraphIndexer(graph); @@ -297,6 +299,7 @@ public class Decompiler { InstructionLocation lastLocation = null; NodeLocation nodeLocation = null; 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()) { @@ -308,26 +311,48 @@ public class Decompiler { } 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()); + SequentialStatement resultBody = new SequentialStatement(); + resultBody.getSequence().addAll(rootStmt.getBody()); + result.setStatement(resultBody); return result; } 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 d6b55904d..e2ae3b11b 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java @@ -939,11 +939,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (statement.getLocation() != null) { pushLocation(statement.getLocation()); } - if (!async) { - writer.append("$rt_throw("); - } else { - writer.append("return $throw("); - } + writer.append("$rt_throw("); prevCallSite = debugEmitter.emitCallSite(); statement.getException().acceptVisitor(this); writer.append(");").softNewLine(); 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/AsyncMethodPart.java b/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodPart.java index aed946ea0..85f216951 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodPart.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodPart.java @@ -15,6 +15,9 @@ */ package org.teavm.javascript.ast; +import java.util.ArrayList; +import java.util.List; + /** * * @author Alexey Andreev @@ -22,6 +25,7 @@ package org.teavm.javascript.ast; public class AsyncMethodPart { private Statement statement; private Integer inputVariable; + private List catches = new ArrayList<>(); public Statement getStatement() { return statement; @@ -38,4 +42,8 @@ public class AsyncMethodPart { 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/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/model/util/AsyncProgramSplitter.java b/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java index 41628de0a..602a58c15 100644 --- a/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java +++ b/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java @@ -69,6 +69,14 @@ public class AsyncProgramSplitter { 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