wasm gc: sort types, functions and globals in usage count order to reduce binary size

This commit is contained in:
Alexey Andreev 2024-09-15 11:02:55 +02:00
parent 86e8cfd0db
commit 7e622d8bc7
4 changed files with 307 additions and 0 deletions

View File

@ -17,6 +17,7 @@ package org.teavm.backend.wasm;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -29,6 +30,7 @@ import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsic;
import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicFactory;
import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsics;
import org.teavm.backend.wasm.model.WasmModule;
import org.teavm.backend.wasm.optimization.WasmUsageCounter;
import org.teavm.backend.wasm.render.WasmBinaryRenderer;
import org.teavm.backend.wasm.render.WasmBinaryStatsCollector;
import org.teavm.backend.wasm.render.WasmBinaryVersion;
@ -209,6 +211,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost {
var binaryWriter = new WasmBinaryWriter();
var binaryRenderer = new WasmBinaryRenderer(binaryWriter, WasmBinaryVersion.V_0x1, obfuscated,
null, null, null, null, WasmBinaryStatsCollector.EMPTY);
optimizeIndexes(module);
module.prepareForRendering();
binaryRenderer.render(module);
var data = binaryWriter.getData();
@ -220,6 +223,14 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost {
}
}
private void optimizeIndexes(WasmModule module) {
var usageCounter = new WasmUsageCounter();
usageCounter.applyToModule(module);
module.functions.sort(Comparator.comparingInt(f -> -usageCounter.usages(f)));
module.globals.sort(Comparator.comparingInt(g -> -usageCounter.usages(g)));
module.types.sort(Comparator.comparingInt(t -> -usageCounter.usages(t)));
}
@Override
public boolean needsSystemArrayCopyOptimization() {
return false;

View File

@ -17,6 +17,7 @@ package org.teavm.backend.wasm.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
@ -104,4 +105,9 @@ public class WasmCollection<T extends WasmEntity> implements Iterable<T> {
public Stream<T> stream() {
return readonlyItems.stream();
}
public void sort(Comparator<T> comparator) {
items.sort(comparator);
indexesInvalid = true;
}
}

View File

@ -17,9 +17,13 @@ package org.teavm.backend.wasm.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.teavm.backend.wasm.model.expression.WasmDefaultExpressionVisitor;
import org.teavm.backend.wasm.model.expression.WasmGetGlobal;
import org.teavm.common.Graph;
import org.teavm.common.GraphUtils;
@ -94,9 +98,44 @@ public class WasmModule {
}
public void prepareForRendering() {
prepareGlobals();
prepareTypes();
}
private void prepareGlobals() {
var sorting = new GlobalSorting();
sorting.sort(globals);
globals.clear();
for (var global : sorting.sorted) {
globals.add(global);
}
}
private static class GlobalSorting extends WasmDefaultExpressionVisitor {
List<WasmGlobal> sorted = new ArrayList<>();
private Set<WasmGlobal> visited = new HashSet<>();
void sort(Iterable<WasmGlobal> globals) {
for (var global : globals) {
add(global);
}
}
private void add(WasmGlobal global) {
if (!visited.add(global)) {
return;
}
global.getInitialValue().acceptVisitor(this);
sorted.add(global);
}
@Override
public void visit(WasmGetGlobal expression) {
super.visit(expression);
add(expression.getGlobal());
}
}
private void prepareTypes() {
var typeGraph = WasmTypeGraphBuilder.buildTypeGraph(this, types, types.size());
var sccs = GraphUtils.findStronglyConnectedComponents(typeGraph);

View File

@ -0,0 +1,251 @@
/*
* Copyright 2024 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.wasm.optimization;
import com.carrotsearch.hppc.ObjectIntHashMap;
import com.carrotsearch.hppc.ObjectIntMap;
import org.teavm.backend.wasm.model.WasmArray;
import org.teavm.backend.wasm.model.WasmCompositeType;
import org.teavm.backend.wasm.model.WasmCompositeTypeVisitor;
import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.model.WasmFunctionType;
import org.teavm.backend.wasm.model.WasmGlobal;
import org.teavm.backend.wasm.model.WasmModule;
import org.teavm.backend.wasm.model.WasmStructure;
import org.teavm.backend.wasm.model.WasmType;
import org.teavm.backend.wasm.model.expression.WasmArrayCopy;
import org.teavm.backend.wasm.model.expression.WasmArrayGet;
import org.teavm.backend.wasm.model.expression.WasmArrayNewDefault;
import org.teavm.backend.wasm.model.expression.WasmArrayNewFixed;
import org.teavm.backend.wasm.model.expression.WasmArraySet;
import org.teavm.backend.wasm.model.expression.WasmBlock;
import org.teavm.backend.wasm.model.expression.WasmCall;
import org.teavm.backend.wasm.model.expression.WasmCallReference;
import org.teavm.backend.wasm.model.expression.WasmCast;
import org.teavm.backend.wasm.model.expression.WasmCastBranch;
import org.teavm.backend.wasm.model.expression.WasmDefaultExpressionVisitor;
import org.teavm.backend.wasm.model.expression.WasmFunctionReference;
import org.teavm.backend.wasm.model.expression.WasmGetGlobal;
import org.teavm.backend.wasm.model.expression.WasmIndirectCall;
import org.teavm.backend.wasm.model.expression.WasmSetGlobal;
import org.teavm.backend.wasm.model.expression.WasmStructGet;
import org.teavm.backend.wasm.model.expression.WasmStructNew;
import org.teavm.backend.wasm.model.expression.WasmStructNewDefault;
import org.teavm.backend.wasm.model.expression.WasmStructSet;
import org.teavm.backend.wasm.model.expression.WasmTest;
public class WasmUsageCounter extends WasmDefaultExpressionVisitor implements WasmCompositeTypeVisitor {
private ObjectIntMap<WasmFunction> usagesByFunction = new ObjectIntHashMap<>();
private ObjectIntMap<WasmGlobal> usagesByGlobals = new ObjectIntHashMap<>();
private ObjectIntMap<WasmCompositeType> usagesByTypes = new ObjectIntHashMap<>();
public void applyToModule(WasmModule module) {
for (var type : module.types) {
type.acceptVisitor(this);
}
for (var function : module.functions) {
for (var part : function.getBody()) {
part.acceptVisitor(this);
}
addUsage(function.getType());
}
for (var global : module.globals) {
global.getInitialValue().acceptVisitor(this);
addUsage(global.getType());
}
}
public int usages(WasmFunction function) {
return usagesByFunction.get(function);
}
public int usages(WasmGlobal global) {
return usagesByGlobals.get(global);
}
public int usages(WasmCompositeType type) {
return usagesByTypes.get(type);
}
@Override
public void visit(WasmStructure type) {
for (var field : type.getFields()) {
addUsage(field.getUnpackedType());
}
}
@Override
public void visit(WasmArray type) {
addUsage(type.getElementType().asUnpackedType());
}
@Override
public void visit(WasmFunctionType type) {
addUsage(type.getReturnType());
for (var param : type.getParameterTypes()) {
addUsage(param);
}
}
@Override
public void visit(WasmCall expression) {
super.visit(expression);
addUsage(expression.getFunction());
}
@Override
public void visit(WasmFunctionReference expression) {
super.visit(expression);
addUsage(expression.getFunction());
}
@Override
public void visit(WasmGetGlobal expression) {
super.visit(expression);
addUsage(expression.getGlobal());
}
@Override
public void visit(WasmSetGlobal expression) {
super.visit(expression);
addUsage(expression.getGlobal());
}
@Override
public void visit(WasmBlock expression) {
super.visit(expression);
addUsage(expression.getType());
}
@Override
public void visit(WasmCastBranch expression) {
super.visit(expression);
addUsage(expression.getSourceType());
addUsage(expression.getType());
}
@Override
public void visit(WasmCallReference expression) {
super.visit(expression);
addUsage(expression.getType());
}
@Override
public void visit(WasmIndirectCall expression) {
super.visit(expression);
addUsage(expression.getType());
}
@Override
public void visit(WasmCast expression) {
super.visit(expression);
addUsage(expression.getTargetType());
}
@Override
public void visit(WasmTest expression) {
super.visit(expression);
addUsage(expression.getTestType());
}
@Override
public void visit(WasmStructNew expression) {
super.visit(expression);
addUsage(expression.getType());
}
@Override
public void visit(WasmStructNewDefault expression) {
super.visit(expression);
addUsage(expression.getType());
}
@Override
public void visit(WasmStructGet expression) {
super.visit(expression);
addUsage(expression.getType());
}
@Override
public void visit(WasmStructSet expression) {
super.visit(expression);
addUsage(expression.getType());
}
@Override
public void visit(WasmArrayNewDefault expression) {
super.visit(expression);
addUsage(expression.getType());
}
@Override
public void visit(WasmArrayNewFixed expression) {
super.visit(expression);
addUsage(expression.getType());
}
@Override
public void visit(WasmArrayGet expression) {
super.visit(expression);
addUsage(expression.getType());
}
@Override
public void visit(WasmArraySet expression) {
super.visit(expression);
addUsage(expression.getType());
}
@Override
public void visit(WasmArrayCopy expression) {
super.visit(expression);
addUsage(expression.getSourceArrayType());
addUsage(expression.getTargetArrayType());
}
private void addUsage(WasmType type) {
if (type instanceof WasmType.CompositeReference) {
addUsage(((WasmType.CompositeReference) type).composite);
}
}
private void addUsage(WasmFunction function) {
var index = usagesByFunction.indexOf(function);
if (index < 0) {
usagesByFunction.put(function, 1);
} else {
usagesByFunction.indexReplace(index, usagesByFunction.indexGet(index) + 1);
}
}
private void addUsage(WasmGlobal function) {
var index = usagesByGlobals.indexOf(function);
if (index < 0) {
usagesByGlobals.put(function, 1);
} else {
usagesByGlobals.indexReplace(index, usagesByGlobals.indexGet(index) + 1);
}
}
private void addUsage(WasmCompositeType type) {
var index = usagesByTypes.indexOf(type);
if (index < 0) {
usagesByTypes.put(type, 1);
} else {
usagesByTypes.indexReplace(index, usagesByTypes.indexGet(index) + 1);
}
}
}