diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java index 91a842b10..fe5a46331 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -103,7 +103,9 @@ import org.teavm.backend.wasm.model.expression.WasmSetLocal; import org.teavm.backend.wasm.model.expression.WasmStoreInt32; import org.teavm.backend.wasm.model.expression.WasmUnreachable; import org.teavm.backend.wasm.optimization.UnusedFunctionElimination; +import org.teavm.backend.wasm.render.ReportingWasmBinaryStatsCollector; import org.teavm.backend.wasm.render.WasmBinaryRenderer; +import org.teavm.backend.wasm.render.WasmBinaryStatsCollector; import org.teavm.backend.wasm.render.WasmBinaryVersion; import org.teavm.backend.wasm.render.WasmBinaryWriter; import org.teavm.backend.wasm.render.WasmCRenderer; @@ -205,6 +207,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { private Set asyncMethods; private boolean hasThreads; private WasmRuntimeType runtimeType = WasmRuntimeType.TEAVM; + private ReportingWasmBinaryStatsCollector statsCollector; @Override public void setController(TeaVMTargetController controller) { @@ -438,6 +441,9 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { @Override public void emit(ListableClassHolderSource classes, BuildTarget buildTarget, String outputName) throws IOException { + prepareStats(); + + var statsCollector = this.statsCollector != null ? this.statsCollector : WasmBinaryStatsCollector.EMPTY; WasmModule module = new WasmModule(); WasmFunction initFunction = new WasmFunction("__start__"); @@ -457,7 +463,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { : null; var classGenerator = new WasmClassGenerator(classes, controller.getUnprocessedClassSource(), vtableProvider, tagRegistry, binaryWriter, names, metadataRequirements, - controller.getClassInitializerInfo(), characteristics, dwarfClassGen); + controller.getClassInitializerInfo(), characteristics, dwarfClassGen, statsCollector); Decompiler decompiler = new Decompiler(classes, new HashSet<>(), false); var stringPool = classGenerator.getStringPool(); @@ -550,22 +556,8 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { new IndirectCallTraceTransformation(module).apply(); } - var writer = new WasmBinaryWriter(); - var debugBuilder = debugging ? new DebugInfoBuilder() : null; - if (debugBuilder != null) { - classGenerator.writeDebug(debugBuilder.classLayout()); - } - var renderer = new WasmBinaryRenderer( - writer, version, obfuscated, dwarfGenerator, dwarfClassGen, - debugBuilder != null ? debugBuilder.lines() : null, - debugBuilder != null ? debugBuilder.variables() : null - ); - renderer.render(module, buildDebug(dwarfGenerator, dwarfClassGen, debugBuilder)); - - try (OutputStream output = buildTarget.createResource(outputName)) { - output.write(writer.getData()); - output.flush(); - } + writeBinaryWasm(buildTarget, outputName, module, classGenerator, dwarfGenerator, dwarfClassGen, + statsCollector); if (wastEmitted) { emitWast(module, buildTarget, getBaseName(outputName) + ".wast"); @@ -573,11 +565,52 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { if (cEmitted) { emitC(module, buildTarget, getBaseName(outputName) + ".wasm.c"); } + if (statsCollector != null) { + writeStats(buildTarget, outputName); + } if (runtimeType == WasmRuntimeType.TEAVM) { emitRuntime(buildTarget, getBaseName(outputName) + ".wasm-runtime.js"); } } + + private void prepareStats() { + var statsProp = controller.getProperties().getProperty("teavm.wasm.stats"); + var stats = Boolean.parseBoolean(statsProp); + if (stats) { + statsCollector = new ReportingWasmBinaryStatsCollector(); + } + } + + private void writeStats(BuildTarget buildTarget, String outputName) throws IOException { + try (var writer = new OutputStreamWriter(buildTarget.createResource(outputName + ".stats.txt"))) { + statsCollector.write(writer); + } + } + + private void writeBinaryWasm(BuildTarget buildTarget, String outputName, + WasmModule module, WasmClassGenerator classGenerator, DwarfGenerator dwarfGenerator, + DwarfClassGenerator dwarfClassGen, WasmBinaryStatsCollector statsCollector) throws IOException { + + var writer = new WasmBinaryWriter(); + var debugBuilder = debugging ? new DebugInfoBuilder() : null; + if (debugBuilder != null) { + classGenerator.writeDebug(debugBuilder.classLayout()); + } + + var renderer = new WasmBinaryRenderer( + writer, version, obfuscated, dwarfGenerator, dwarfClassGen, + debugBuilder != null ? debugBuilder.lines() : null, + debugBuilder != null ? debugBuilder.variables() : null, + statsCollector + ); + renderer.render(module, buildDebug(dwarfGenerator, dwarfClassGen, debugBuilder)); + + try (var output = buildTarget.createResource(outputName)) { + output.write(writer.getData()); + output.flush(); + } + } private Supplier> buildDebug(DwarfGenerator generator, DwarfClassGenerator classGen, DebugInfoBuilder debugBuilder) { diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java index 1ce343108..78c37a94e 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java @@ -33,6 +33,7 @@ import org.teavm.backend.wasm.binary.DataType; import org.teavm.backend.wasm.binary.DataValue; import org.teavm.backend.wasm.debug.DebugClassLayout; import org.teavm.backend.wasm.debug.info.FieldType; +import org.teavm.backend.wasm.render.WasmBinaryStatsCollector; import org.teavm.common.IntegerArray; import org.teavm.interop.Address; import org.teavm.interop.Function; @@ -100,6 +101,7 @@ public class WasmClassGenerator { private ClassMetadataRequirements metadataRequirements; private ClassInitializerInfo classInitializerInfo; private DwarfClassGenerator dwarfClassGenerator; + private WasmBinaryStatsCollector statsCollector; private static final int CLASS_SIZE = 1; private static final int CLASS_FLAGS = 2; @@ -121,18 +123,19 @@ public class WasmClassGenerator { VirtualTableProvider vtableProvider, TagRegistry tagRegistry, BinaryWriter binaryWriter, NameProvider names, ClassMetadataRequirements metadataRequirements, ClassInitializerInfo classInitializerInfo, Characteristics characteristics, - DwarfClassGenerator dwarfClassGenerator) { + DwarfClassGenerator dwarfClassGenerator, WasmBinaryStatsCollector statsCollector) { this.processedClassSource = processedClassSource; this.classSource = classSource; this.vtableProvider = vtableProvider; this.tagRegistry = tagRegistry; this.binaryWriter = binaryWriter; - this.stringPool = new WasmStringPool(this, binaryWriter); + this.stringPool = new WasmStringPool(this, binaryWriter, statsCollector); this.names = names; this.metadataRequirements = metadataRequirements; this.classInitializerInfo = classInitializerInfo; this.characteristics = characteristics; this.dwarfClassGenerator = dwarfClassGenerator; + this.statsCollector = statsCollector; } public WasmStringPool getStringPool() { @@ -193,6 +196,8 @@ public class WasmClassGenerator { calculateLayout(cls, binaryData, dwarfClass); if (binaryData.start >= 0) { binaryData.start = binaryWriter.append(createStructure(binaryData)); + var size = binaryWriter.getAddress() - binaryData.start; + statsCollector.addClassMetadataSize(className, size); } if (dwarfClass != null) { dwarfClass.setSize(binaryData.size); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/WasmStringPool.java b/core/src/main/java/org/teavm/backend/wasm/generate/WasmStringPool.java index 40673fda5..3e0301696 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/WasmStringPool.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/WasmStringPool.java @@ -22,6 +22,7 @@ import org.teavm.backend.wasm.binary.DataArray; import org.teavm.backend.wasm.binary.DataPrimitives; import org.teavm.backend.wasm.binary.DataStructure; import org.teavm.backend.wasm.binary.DataValue; +import org.teavm.backend.wasm.render.WasmBinaryStatsCollector; import org.teavm.model.ValueType; import org.teavm.runtime.RuntimeObject; @@ -38,10 +39,13 @@ public class WasmStringPool { DataPrimitives.ADDRESS, /* monitor */ DataPrimitives.ADDRESS, /* characters */ DataPrimitives.INT /* hash code */); + private WasmBinaryStatsCollector statsCollector; - public WasmStringPool(WasmClassGenerator classGenerator, BinaryWriter binaryWriter) { + public WasmStringPool(WasmClassGenerator classGenerator, BinaryWriter binaryWriter, + WasmBinaryStatsCollector statsCollector) { this.classGenerator = classGenerator; this.binaryWriter = binaryWriter; + this.statsCollector = statsCollector; } public int getStringPointer(String value) { @@ -54,6 +58,8 @@ public class WasmStringPool { } private int generateStringPointer(String value) { + var start = binaryWriter.getAddress(); + DataArray charactersType = new DataArray(DataPrimitives.SHORT, value.length()); DataStructure wrapperType = new DataStructure((byte) 0, arrayHeaderType, charactersType); DataValue wrapper = wrapperType.createValue(); @@ -73,6 +79,9 @@ public class WasmStringPool { stringObject.setInt(0, (classPointer >>> 3) | RuntimeObject.GC_MARKED); stringObject.setAddress(2, binaryWriter.append(wrapper)); + var size = binaryWriter.getAddress() - start; + statsCollector.addStringsSize(size); + return stringPointer; } } diff --git a/core/src/main/java/org/teavm/backend/wasm/render/ReportingWasmBinaryStatsCollector.java b/core/src/main/java/org/teavm/backend/wasm/render/ReportingWasmBinaryStatsCollector.java new file mode 100644 index 000000000..cdf26cfcd --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/render/ReportingWasmBinaryStatsCollector.java @@ -0,0 +1,83 @@ +/* + * 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.wasm.render; + +import com.carrotsearch.hppc.ObjectIntHashMap; +import com.carrotsearch.hppc.ObjectIntMap; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.LinkedHashMap; +import java.util.Map; + +public class ReportingWasmBinaryStatsCollector implements WasmBinaryStatsCollector { + private Map statsByClass = new LinkedHashMap<>(); + private ObjectIntMap sectionSizes = new ObjectIntHashMap<>(); + private int stringsSize; + + @Override + public void addClassCodeSize(String className, int bytes) { + getStats(className).codeSize += bytes; + } + + @Override + public void addClassMetadataSize(String className, int bytes) { + getStats(className).metadataSize += bytes; + } + + private ClassStats getStats(String className) { + return statsByClass.computeIfAbsent(className, k -> new ClassStats()); + } + + @Override + public void addSectionSize(String name, int bytes) { + sectionSizes.put(name, sectionSizes.get(name) + bytes); + } + + @Override + public void addStringsSize(int bytes) { + stringsSize += bytes; + } + + public void write(Writer writer) throws IOException { + var pw = new PrintWriter(writer); + pw.println("[classes]"); + for (var entry : statsByClass.entrySet()) { + pw.append(entry.getKey()).append(" "); + pw.print(entry.getValue().codeSize); + pw.append(" "); + pw.print(entry.getValue().metadataSize); + pw.println(); + } + + pw.println(); + pw.println("[strings]"); + pw.println(stringsSize); + + pw.println(); + pw.println("[sections]"); + for (var entry : sectionSizes) { + pw.append(entry.key).append(" "); + pw.print(entry.value); + pw.println(); + } + } + + private static class ClassStats { + int codeSize; + int metadataSize; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java index d0eaa55b7..1e4b95612 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java @@ -61,10 +61,11 @@ public class WasmBinaryRenderer { private DwarfFunctionGenerator dwarfFunctionGen; private DebugLines debugLines; private DebugVariables debugVariables; + private WasmBinaryStatsCollector statsCollector; public WasmBinaryRenderer(WasmBinaryWriter output, WasmBinaryVersion version, boolean obfuscated, DwarfGenerator dwarfGenerator, DwarfClassGenerator dwarfClassGen, DebugLines debugLines, - DebugVariables debugVariables) { + DebugVariables debugVariables, WasmBinaryStatsCollector statsCollector) { this.output = output; this.version = version; this.obfuscated = obfuscated; @@ -72,6 +73,7 @@ public class WasmBinaryRenderer { dwarfFunctionGen = dwarfClassGen != null ? new DwarfFunctionGenerator(dwarfClassGen, dwarfGenerator) : null; this.debugLines = debugLines; this.debugVariables = debugVariables; + this.statsCollector = statsCollector; } public void render(WasmModule module) { @@ -279,8 +281,13 @@ public class WasmBinaryRenderer { section.writeLEB(functions.size()); for (var function : functions) { var body = renderFunction(function, section.getPosition() + 4); + var startPos = section.getPosition(); section.writeLEB4(body.length); section.writeBytes(body); + var size = section.getPosition() - startPos; + if (function.getJavaMethod() != null) { + statsCollector.addClassCodeSize(function.getJavaMethod().getClassName(), size); + } } if (dwarfGenerator != null) { @@ -441,6 +448,7 @@ public class WasmBinaryRenderer { } private void writeSection(int id, String name, byte[] data) { + var start = output.getPosition(); output.writeByte(id); int length = data.length; if (id == 0) { @@ -452,5 +460,7 @@ public class WasmBinaryRenderer { } output.writeBytes(data); + + statsCollector.addSectionSize(name, output.getPosition() - start); } } diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryStatsCollector.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryStatsCollector.java new file mode 100644 index 000000000..81d090c77 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryStatsCollector.java @@ -0,0 +1,44 @@ +/* + * 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.wasm.render; + +public interface WasmBinaryStatsCollector { + void addClassCodeSize(String className, int bytes); + + void addClassMetadataSize(String className, int bytes); + + void addStringsSize(int bytes); + + void addSectionSize(String name, int bytes); + + WasmBinaryStatsCollector EMPTY = new WasmBinaryStatsCollector() { + @Override + public void addClassCodeSize(String className, int bytes) { + } + + @Override + public void addClassMetadataSize(String className, int bytes) { + } + + @Override + public void addStringsSize(int bytes) { + } + + @Override + public void addSectionSize(String name, int bytes) { + } + }; +}