AST optimizations and output code minification

This commit is contained in:
konsoletyper 2014-01-31 14:03:29 +04:00
parent bc68602dae
commit ce0859beac
7 changed files with 266 additions and 75 deletions

View File

@ -120,6 +120,7 @@ public class Decompiler {
clsNode.getMethods().add(decompile(method));
}
clsNode.getInterfaces().addAll(cls.getInterfaces());
clsNode.getModifiers().addAll(mapModifiers(cls.getModifiers()));
return clsNode;
}
@ -213,6 +214,7 @@ public class Decompiler {
}
Optimizer optimizer = new Optimizer();
optimizer.optimize(methodNode, method.getProgram());
methodNode.getModifiers().addAll(mapModifiers(method.getModifiers()));
return methodNode;
}
@ -220,6 +222,8 @@ public class Decompiler {
Set<NodeModifier> result = EnumSet.noneOf(NodeModifier.class);
if (modifiers.contains(ElementModifier.STATIC)) {
result.add(NodeModifier.STATIC);
} else if (modifiers.contains(ElementModifier.INTERFACE)) {
result.add(NodeModifier.INTERFACE);
}
return result;
}

View File

@ -37,6 +37,8 @@ public class Optimizer {
UnusedVariableEliminator unusedEliminator = new UnusedVariableEliminator(paramCount, method.getVariables());
method.getBody().acceptVisitor(unusedEliminator);
method.getVariables().subList(unusedEliminator.lastIndex, method.getVariables().size()).clear();
RedundantLabelEliminator labelEliminator = new RedundantLabelEliminator();
method.getBody().acceptVisitor(labelEliminator);
for (int i = 0; i < method.getVariables().size(); ++i) {
method.getVariables().set(i, i);
}

View File

@ -28,6 +28,10 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
private ReadWriteStatsBuilder stats;
Map<IdentifiedStatement, Integer> referencedStatements = new HashMap<>();
private List<Statement> resultSequence;
private int[] variableDecl;
private List<Boolean> invocations;
private int lastMethodCall;
private boolean hasMethodCall;
public OptimizingVisitor(ReadWriteStatsBuilder stats) {
this.stats = stats;
@ -118,22 +122,27 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
if (stats.reads[index] != 1 || stats.writes[index] != 1) {
return;
}
if (resultSequence.isEmpty()) {
int declIndex = variableDecl[index];
if (declIndex < 0 || declIndex >= lastMethodCall) {
return;
}
Statement last = resultSequence.get(resultSequence.size() - 1);
if (!(last instanceof AssignmentStatement)) {
return;
if (invocations.get(declIndex)) {
for (int i = lastMethodCall - 1; i > declIndex; --i) {
if (invocations.get(i)) {
return;
}
}
}
AssignmentStatement assignment = (AssignmentStatement)last;
if (!(assignment.getLeftValue() instanceof VariableExpr)) {
return;
}
VariableExpr var = (VariableExpr)assignment.getLeftValue();
if (var.getIndex() == index) {
resultSequence.remove(resultSequence.size() - 1);
assignment.getRightValue().acceptVisitor(this);
for (int i = declIndex; i < lastMethodCall; ++i) {
if (invocations.get(i)) {
lastMethodCall = i;
break;
}
}
AssignmentStatement decl = (AssignmentStatement)resultSequence.get(declIndex);
resultSequence.set(declIndex, null);
variableDecl[index] = -1;
resultExpr = decl.getRightValue();
}
@Override
@ -157,6 +166,7 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
@Override
public void visit(InvocationExpr expr) {
hasMethodCall = true;
Expr[] args = new Expr[expr.getArguments().size()];
for (int i = expr.getArguments().size() - 1; i >= 0; --i) {
expr.getArguments().get(i).acceptVisitor(this);
@ -266,6 +276,9 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
if (!(statement.getLeftValue() instanceof VariableExpr)) {
statement.getLeftValue().acceptVisitor(this);
left = resultExpr;
} else {
VariableExpr var = (VariableExpr)statement.getLeftValue();
variableDecl[var.getIndex()] = resultSequence.size();
}
statement.setLeftValue(left);
statement.setRightValue(right);
@ -273,34 +286,57 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
}
}
private List<Statement> processSequence(List<Statement> statements, boolean strict) {
private List<Statement> processSequence(List<Statement> statements) {
boolean hasMethodCallBackup = hasMethodCall;
List<Statement> backup = resultSequence;
int[] variableDeclBackup = variableDecl;
List<Boolean> invocationsBackup = invocations;
invocations = new ArrayList<>();
resultSequence = new ArrayList<>();
variableDecl = new int[stats.reads.length];
Arrays.fill(variableDecl, -1);
processSequenceImpl(statements);
List<Statement> result = new ArrayList<>();
if (strict) {
resultSequence = result;
}
outer: for (int i = 0; i < statements.size(); ++i) {
Statement part = statements.get(i);
part.acceptVisitor(this);
List<Statement> newStatements = new ArrayList<>();
if (resultStmt instanceof SequentialStatement) {
newStatements.addAll(((SequentialStatement)resultStmt).getSequence());
} else {
newStatements.add(resultStmt);
}
for (int j = 0; j < newStatements.size(); ++j) {
Statement newStatement = newStatements.get(j);
resultSequence = result;
result.add(newStatement);
if (newStatement instanceof BreakStatement) {
break outer;
}
for (Statement part : resultSequence) {
if (part != null) {
result.add(part);
}
}
resultSequence = backup;
variableDecl = variableDeclBackup;
invocations = invocationsBackup;
hasMethodCall = hasMethodCallBackup;
return result;
}
private boolean processSequenceImpl(List<Statement> statements) {
for (int i = 0; i < statements.size(); ++i) {
Statement part = statements.get(i);
if (part instanceof SequentialStatement) {
if (!processSequenceImpl(((SequentialStatement)part).getSequence())) {
return false;
}
continue;
}
hasMethodCall = false;
lastMethodCall = resultSequence.size();
part.acceptVisitor(this);
invocations.add(hasMethodCall);
part = resultStmt;
if (part instanceof SequentialStatement) {
if (!processSequenceImpl(((SequentialStatement)part).getSequence())) {
return false;
}
continue;
}
resultSequence.add(part);
if (part instanceof BreakStatement) {
return false;
}
}
return true;
}
private void eliminateRedundantBreaks(List<Statement> statements, IdentifiedStatement exit) {
if (statements.isEmpty()) {
return;
@ -398,7 +434,7 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
@Override
public void visit(SequentialStatement statement) {
List<Statement> statements = processSequence(statement.getSequence(), false);
List<Statement> statements = processSequence(statement.getSequence());
if (statements.size() == 1) {
resultStmt = statements.get(0);
} else {
@ -412,8 +448,8 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
public void visit(ConditionalStatement statement) {
statement.getCondition().acceptVisitor(this);
statement.setCondition(resultExpr);
List<Statement> consequent = processSequence(statement.getConsequent(), true);
List<Statement> alternative = processSequence(statement.getAlternative(), true);
List<Statement> consequent = processSequence(statement.getConsequent());
List<Statement> alternative = processSequence(statement.getAlternative());
if (consequent.isEmpty()) {
consequent.addAll(alternative);
alternative.clear();
@ -428,6 +464,7 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
statement.getAlternative().clear();
statement.getAlternative().addAll(alternative);
resultStmt = statement;
hasMethodCall = true;
}
@Override
@ -435,14 +472,15 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
statement.getValue().acceptVisitor(this);
statement.setValue(resultExpr);
for (SwitchClause clause : statement.getClauses()) {
List<Statement> newBody = processSequence(clause.getBody(), true);
List<Statement> newBody = processSequence(clause.getBody());
clause.getBody().clear();
clause.getBody().addAll(newBody);
}
List<Statement> newDefault = processSequence(statement.getDefaultClause(), true);
List<Statement> newDefault = processSequence(statement.getDefaultClause());
statement.getDefaultClause().clear();
statement.getDefaultClause().addAll(newDefault);
resultStmt = statement;
hasMethodCall = true;
}
@Override
@ -454,7 +492,7 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
statement.getBody().clear();
statement.getBody().addAll(innerLoop.getBody());
}
List<Statement> statements = processSequence(statement.getBody(), true);
List<Statement> statements = processSequence(statement.getBody());
statement.getBody().clear();
statement.getBody().addAll(statements);
if (statement.getCondition() != null) {
@ -481,11 +519,12 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
break;
}
resultStmt = statement;
hasMethodCall = true;
}
@Override
public void visit(BlockStatement statement) {
List<Statement> statements = processSequence(statement.getBody(), false);
List<Statement> statements = processSequence(statement.getBody());
eliminateRedundantBreaks(statements, statement);
if (referencedStatements.get(statement).equals(0)) {
SequentialStatement result = new SequentialStatement();
@ -505,11 +544,13 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
@Override
public void visit(BreakStatement statement) {
resultStmt = statement;
hasMethodCall = true;
}
@Override
public void visit(ContinueStatement statement) {
resultStmt = statement;
hasMethodCall = true;
}
@Override
@ -519,6 +560,7 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
statement.setResult(resultExpr);
}
resultStmt = statement;
hasMethodCall = true;
}
@Override
@ -526,6 +568,7 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
statement.getException().acceptVisitor(this);
statement.setException(resultExpr);
resultStmt = statement;
hasMethodCall = true;
}
@Override

View File

@ -0,0 +1,122 @@
/*
* Copyright 2014 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;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.teavm.javascript.ast.*;
/**
*
* @author Alexey Andreev
*/
class RedundantLabelEliminator implements StatementVisitor {
private IdentifiedStatement currentBlock;
private Set<IdentifiedStatement> hasRefs = new HashSet<>();
void visitSequence(List<Statement> statements) {
for (Statement statement : statements) {
statement.acceptVisitor(this);
}
}
@Override
public void visit(AssignmentStatement statement) {
}
@Override
public void visit(SequentialStatement statement) {
visitSequence(statement.getSequence());
}
@Override
public void visit(ConditionalStatement statement) {
visitSequence(statement.getConsequent());
visitSequence(statement.getAlternative());
}
@Override
public void visit(SwitchStatement statement) {
IdentifiedStatement currentBlockBackup = currentBlock;
currentBlock = statement;
for (SwitchClause clause : statement.getClauses()) {
visitSequence(clause.getBody());
}
visitSequence(statement.getDefaultClause());
if (!hasRefs.contains(currentBlock)) {
currentBlock.setId(null);
}
currentBlock = currentBlockBackup;
}
@Override
public void visit(WhileStatement statement) {
IdentifiedStatement currentBlockBackup = currentBlock;
currentBlock = statement;
visitSequence(statement.getBody());
if (!hasRefs.contains(currentBlock)) {
currentBlock.setId(null);
}
currentBlock = currentBlockBackup;
}
@Override
public void visit(BlockStatement statement) {
IdentifiedStatement currentBlockBackup = currentBlock;
currentBlock = null;
visitSequence(statement.getBody());
currentBlock = currentBlockBackup;
}
@Override
public void visit(ForStatement statement) {
}
@Override
public void visit(BreakStatement statement) {
if (statement.getTarget() == currentBlock) {
statement.setTarget(null);
} else {
hasRefs.add(statement.getTarget());
}
}
@Override
public void visit(ContinueStatement statement) {
if (statement.getTarget() == currentBlock) {
statement.setTarget(null);
} else {
hasRefs.add(statement.getTarget());
}
}
@Override
public void visit(ReturnStatement statement) {
}
@Override
public void visit(ThrowStatement statement) {
}
@Override
public void visit(IncrementStatement statement) {
}
@Override
public void visit(InitClassStatement statement) {
}
}

View File

@ -150,11 +150,13 @@ public class Renderer implements ExprVisitor, StatementVisitor {
.append(constantToString(value)).append(";").softNewLine();
}
writer.appendClass(cls.getName()).append(".prototype").ws().append("=").ws().append("new ")
.append(cls.getParentName() != null ? naming.getNameFor(cls.getParentName()) :
"Object").append("();").softNewLine();
writer.appendClass(cls.getName()).append(".prototype.constructor").ws().append("=").ws()
.appendClass(cls.getName()).append(';').softNewLine();
if (!cls.getModifiers().contains(NodeModifier.INTERFACE)) {
writer.appendClass(cls.getName()).append(".prototype").ws().append("=").ws().append("new ")
.append(cls.getParentName() != null ? naming.getNameFor(cls.getParentName()) :
"Object").append("();").softNewLine();
writer.appendClass(cls.getName()).append(".prototype.constructor").ws().append("=").ws()
.appendClass(cls.getName()).append(';').softNewLine();
}
writer.appendClass(cls.getName()).append(".$meta").ws().append("=").ws().append("{").ws();
writer.append("name").ws().append(":").ws().append("\"").append(cls.getName()).append("\",").ws();
writer.append("primitive").ws().append(":").ws().append("false,").ws();
@ -173,37 +175,39 @@ public class Renderer implements ExprVisitor, StatementVisitor {
}
writer.append("]");
writer.ws().append("};").softNewLine();
writer.appendClass(cls.getName()).append("_$clinit").ws().append("=").ws().append("function()").ws()
.append("{").softNewLine().indent();
writer.appendClass(cls.getName()).append("_$clinit").ws().append("=").ws()
.append("function(){};").newLine();
List<String> stubNames = new ArrayList<>();
for (MethodNode method : cls.getMethods()) {
renderBody(method);
stubNames.add(naming.getFullNameFor(method.getReference()));
}
MethodHolder methodHolder = classSource.getClassHolder(cls.getName()).getMethod(
new MethodDescriptor("<clinit>", ValueType.VOID));
if (methodHolder != null) {
writer.appendMethodBody(new MethodReference(cls.getName(), methodHolder.getDescriptor()))
.append("();").softNewLine();
}
writer.outdent().append("}").newLine();
for (MethodNode method : cls.getMethods()) {
if (!method.getModifiers().contains(NodeModifier.STATIC)) {
renderDeclaration(method);
if (!cls.getModifiers().contains(NodeModifier.INTERFACE)) {
writer.appendClass(cls.getName()).append("_$clinit").ws().append("=").ws().append("function()").ws()
.append("{").softNewLine().indent();
writer.appendClass(cls.getName()).append("_$clinit").ws().append("=").ws()
.append("function(){};").newLine();
List<String> stubNames = new ArrayList<>();
for (MethodNode method : cls.getMethods()) {
renderBody(method);
stubNames.add(naming.getFullNameFor(method.getReference()));
}
}
if (stubNames.size() > 0) {
writer.append("$rt_methodStubs(").appendClass(cls.getName()).append("_$clinit")
.append(",").ws().append("[");
for (int i = 0; i < stubNames.size(); ++i) {
if (i > 0) {
writer.append(",").ws();
MethodHolder methodHolder = classSource.getClassHolder(cls.getName()).getMethod(
new MethodDescriptor("<clinit>", ValueType.VOID));
if (methodHolder != null) {
writer.appendMethodBody(new MethodReference(cls.getName(), methodHolder.getDescriptor()))
.append("();").softNewLine();
}
writer.outdent().append("}").newLine();
for (MethodNode method : cls.getMethods()) {
if (!method.getModifiers().contains(NodeModifier.STATIC)) {
renderDeclaration(method);
}
writer.append("'").append(stubNames.get(i)).append("'");
}
writer.append("]);").newLine();
if (stubNames.size() > 0) {
writer.append("$rt_methodStubs(").appendClass(cls.getName()).append("_$clinit")
.append(",").ws().append("[");
for (int i = 0; i < stubNames.size(); ++i) {
if (i > 0) {
writer.append(",").ws();
}
writer.append("'").append(stubNames.get(i)).append("'");
}
writer.append("]);").newLine();
}
}
} catch (NamingException e) {
throw new RenderingException("Error rendering class " + cls.getName() + ". See a cause for details", e);
@ -489,7 +493,11 @@ public class Renderer implements ExprVisitor, StatementVisitor {
@Override
public void visit(BreakStatement statement) {
try {
writer.append("break ").append(statement.getTarget().getId()).append(";").softNewLine();
writer.append("break");
if (statement.getTarget() != null) {
writer.append(' ').append(statement.getTarget().getId());
}
writer.append(";").softNewLine();
} catch (IOException e) {
throw new RenderingException("IO error occured", e);
}
@ -498,7 +506,11 @@ public class Renderer implements ExprVisitor, StatementVisitor {
@Override
public void visit(ContinueStatement statement) {
try {
writer.append("continue ").append(statement.getTarget().getId()).append(";").softNewLine();
writer.append("continue");
if (statement.getTarget() != null) {
writer.append(' ').append(statement.getTarget().getId());
}
writer.append(";").softNewLine();
} catch (IOException e) {
throw new RenderingException("IO error occured", e);
}

View File

@ -16,7 +16,9 @@
package org.teavm.javascript.ast;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
/**
*
@ -25,6 +27,7 @@ import java.util.List;
public class ClassNode {
private String name;
private String parentName;
private Set<NodeModifier> modifiers = EnumSet.noneOf(NodeModifier.class);
private List<FieldNode> fields = new ArrayList<>();
private List<MethodNode> methods = new ArrayList<>();
private List<String> interfaces = new ArrayList<>();
@ -53,4 +56,8 @@ public class ClassNode {
public List<String> getInterfaces() {
return interfaces;
}
public Set<NodeModifier> getModifiers() {
return modifiers;
}
}

View File

@ -20,5 +20,6 @@ package org.teavm.javascript.ast;
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
public enum NodeModifier {
STATIC
STATIC,
INTERFACE
}