JS: strip unused functions from hand-written runtime

This commit is contained in:
Alexey Andreev 2023-11-05 22:41:57 +01:00
parent 485d23d675
commit 717bbf4a57
4 changed files with 252 additions and 12 deletions

View File

@ -462,15 +462,13 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
int start = sourceWriter.getOffset();
RuntimeRenderer runtimeRenderer = new RuntimeRenderer(classes, sourceWriter);
runtimeRenderer.prepareAstParts(renderer.isThreadLibraryUsed());
declarations.replay(runtimeRenderer.sink, RememberedSource.FILTER_REF);
epilogue.replay(runtimeRenderer.sink, RememberedSource.FILTER_REF);
runtimeRenderer.removeUnusedParts();
runtimeRenderer.renderRuntime();
runtimeRenderer.renderHandWrittenRuntime("long.js");
if (renderer.isThreadLibraryUsed()) {
runtimeRenderer.renderHandWrittenRuntime("thread.js");
} else {
runtimeRenderer.renderHandWrittenRuntime("simpleThread.js");
}
declarations.write(sourceWriter, 0);
runtimeRenderer.renderHandWrittenRuntime("array.js");
runtimeRenderer.renderEpilogue();
epilogue.write(sourceWriter, 0);
printWrapperEnd(sourceWriter);

View File

@ -20,16 +20,24 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ast.AstRoot;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.codegen.SourceWriterSink;
import org.teavm.backend.javascript.templating.AstRemoval;
import org.teavm.backend.javascript.templating.RemovablePartsFinder;
import org.teavm.backend.javascript.templating.TemplatingAstTransformer;
import org.teavm.backend.javascript.templating.TemplatingAstWriter;
import org.teavm.model.ClassReaderSource;
import org.teavm.vm.RenderingException;
public class RuntimeRenderer {
private final List<AstRoot> runtimeAstParts = new ArrayList<>();
private final List<AstRoot> epilogueAstParts = new ArrayList<>();
private final RemovablePartsFinder removablePartsFinder = new RemovablePartsFinder();
private final ClassReaderSource classSource;
private final SourceWriter writer;
@ -38,15 +46,35 @@ public class RuntimeRenderer {
this.writer = writer;
}
public void renderRuntime() throws RenderingException {
renderHandWrittenRuntime("runtime.js");
renderHandWrittenRuntime("intern.js");
public void prepareAstParts(boolean threadLibraryUsed) {
runtimeAstParts.add(prepareAstPart("runtime.js"));
runtimeAstParts.add(prepareAstPart("intern.js"));
runtimeAstParts.add(prepareAstPart("long.js"));
runtimeAstParts.add(prepareAstPart(threadLibraryUsed ? "thread.js" : "simpleThread.js"));
epilogueAstParts.add(prepareAstPart("array.js"));
}
public void renderHandWrittenRuntime(String name) {
AstRoot ast = parseRuntime(name);
public void renderRuntime() {
for (var ast : runtimeAstParts) {
renderHandWrittenRuntime(ast);
}
}
public void renderEpilogue() {
for (var ast : epilogueAstParts) {
renderHandWrittenRuntime(ast);
}
}
private AstRoot prepareAstPart(String name) {
var ast = parseRuntime(name);
ast.visit(new StringConstantElimination());
new TemplatingAstTransformer(classSource).visit(ast);
removablePartsFinder.visit(ast);
return ast;
}
private void renderHandWrittenRuntime(AstRoot ast) {
var astWriter = new TemplatingAstWriter(writer, null, null);
astWriter.hoist(ast);
astWriter.print(ast);
@ -66,4 +94,22 @@ public class RuntimeRenderer {
throw new RenderingException(e);
}
}
public final SourceWriterSink sink = new SourceWriterSink() {
@Override
public SourceWriterSink appendFunction(String name) {
removablePartsFinder.markUsedDeclaration(name);
return this;
}
};
public void removeUnusedParts() {
var removal = new AstRemoval(removablePartsFinder.getAllRemovableParts());
for (var part : runtimeAstParts) {
removal.visit(part);
}
for (var part : epilogueAstParts) {
removal.visit(part);
}
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2023 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.backend.javascript.templating;
import java.util.Set;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.ExpressionStatement;
import org.mozilla.javascript.ast.VariableDeclaration;
import org.teavm.backend.javascript.ast.AstVisitor;
public class AstRemoval extends AstVisitor {
private Set<AstNode> nodes;
public AstRemoval(Set<AstNode> nodes) {
this.nodes = nodes;
}
@Override
public void visit(ExpressionStatement node) {
if (nodes.contains(node.getExpression())) {
replaceWith(null);
} else {
super.visit(node);
}
}
@Override
public void visit(VariableDeclaration node) {
for (var iter = node.getVariables().iterator(); iter.hasNext();) {
var initializer = iter.next();
if (nodes.contains(initializer)) {
iter.remove();
}
}
if (node.getVariables().isEmpty()) {
replaceWith(null);
}
}
}

View File

@ -0,0 +1,144 @@
/*
* Copyright 2023 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.backend.javascript.templating;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.mozilla.javascript.ast.Assignment;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.ElementGet;
import org.mozilla.javascript.ast.ExpressionStatement;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.Name;
import org.mozilla.javascript.ast.PropertyGet;
import org.mozilla.javascript.ast.Scope;
import org.mozilla.javascript.ast.VariableDeclaration;
import org.teavm.backend.javascript.ast.AstVisitor;
public class RemovablePartsFinder extends AstVisitor {
private Map<String, List<AstNode>> removableDeclarations = new HashMap<>();
private Map<String, Scope> removableDeclarationScopes = new HashMap<>();
private Map<String, Set<String>> dependencies = new HashMap<>();
private String insideDeclaration;
private boolean topLevel = true;
@Override
public void visit(FunctionNode node) {
if (topLevel) {
if (node.getName() != null && !node.getName().isEmpty()) {
removableDeclarations.computeIfAbsent(node.getName(), k -> new ArrayList<>()).add(node);
removableDeclarationScopes.put(node.getName(), scopeOfId(node.getName()));
}
topLevel = false;
insideDeclaration = node.getName();
visit(node.getBody());
insideDeclaration = null;
topLevel = true;
} else {
super.visit(node);
}
}
@Override
public void visit(VariableDeclaration node) {
if (topLevel) {
for (var initializer : node.getVariables()) {
var name = extractName(initializer.getTarget());
if (name != null) {
removableDeclarations.computeIfAbsent(name.getIdentifier(), k -> new ArrayList<>())
.add(initializer);
removableDeclarationScopes.put(name.getIdentifier(), scopeOfId(name.getIdentifier()));
if (initializer.getInitializer() != null) {
topLevel = false;
insideDeclaration = name.getIdentifier();
visit(initializer.getInitializer());
insideDeclaration = null;
topLevel = true;
}
}
}
} else {
super.visit(node);
}
}
@Override
public void visit(ExpressionStatement node) {
if (topLevel && node.getExpression() instanceof Assignment) {
var assign = (Assignment) node.getExpression();
var name = extractName(assign.getLeft());
removableDeclarations.computeIfAbsent(name.getIdentifier(), k -> new ArrayList<>())
.add(node.getExpression());
removableDeclarationScopes.put(name.getIdentifier(), scopeOfId(name.getIdentifier()));
if (name != null) {
topLevel = false;
insideDeclaration = name.getIdentifier();
visit(assign.getRight());
insideDeclaration = null;
topLevel = true;
return;
}
}
super.visit(node);
}
@Override
public void visit(PropertyGet node) {
visit(node.getTarget());
}
@Override
public void visit(Name node) {
if (scopeOfId(node.getIdentifier()) == removableDeclarationScopes.get(node.getIdentifier())
&& insideDeclaration != null) {
dependencies.computeIfAbsent(insideDeclaration, k -> new HashSet<>()).add(node.getIdentifier());
}
}
private Name extractName(AstNode node) {
if (node instanceof Name) {
return (Name) node;
} else if (node instanceof PropertyGet) {
return extractName(((PropertyGet) node).getTarget());
} else if (node instanceof ElementGet) {
return extractName(((ElementGet) node).getTarget());
} else {
return null;
}
}
public void markUsedDeclaration(String name) {
removableDeclarations.remove(name);
var dependenciesToFollow = dependencies.remove(name);
if (dependenciesToFollow != null) {
for (var dependency : dependenciesToFollow) {
markUsedDeclaration(dependency);
}
}
}
public Set<AstNode> getAllRemovableParts() {
var nodes = new HashSet<AstNode>();
for (var parts : removableDeclarations.values()) {
nodes.addAll(parts);
}
return nodes;
}
}