From 850609bb72d5b5ab25c28a3d7e985d69ec5b4f12 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 12 Aug 2016 21:32:32 +0300 Subject: [PATCH] Provide more information in class header --- .../org/teavm/model/classes/TagRegistry.java | 147 ++++++++++++++++++ .../java/org/teavm/runtime/RuntimeClass.java | 32 ++++ .../main/java/org/teavm/wasm/WasmTarget.java | 15 +- .../wasm/generate/WasmClassGenerator.java | 107 ++++++++----- .../wasm/generate/WasmGenerationVisitor.java | 3 +- 5 files changed, 261 insertions(+), 43 deletions(-) create mode 100644 core/src/main/java/org/teavm/model/classes/TagRegistry.java diff --git a/core/src/main/java/org/teavm/model/classes/TagRegistry.java b/core/src/main/java/org/teavm/model/classes/TagRegistry.java new file mode 100644 index 000000000..c2d78caca --- /dev/null +++ b/core/src/main/java/org/teavm/model/classes/TagRegistry.java @@ -0,0 +1,147 @@ +/* + * Copyright 2016 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.classes; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.ElementModifier; +import org.teavm.model.ListableClassReaderSource; + +public class TagRegistry { + private Map> ranges = new HashMap<>(); + + public TagRegistry(ListableClassReaderSource classSource) { + List roots = new ArrayList<>(); + Map> implementedBy = new HashMap<>(); + + Map> hierarchy = new HashMap<>(); + for (String className : classSource.getClassNames()) { + ClassReader cls = classSource.get(className); + if (cls == null || cls.hasModifier(ElementModifier.INTERFACE)) { + continue; + } + for (String iface : cls.getInterfaces()) { + String topmostImplementor = findTopmostImplementor(classSource, className, iface); + markImplementor(classSource, topmostImplementor, iface, implementedBy); + } + if (cls.getParent() == null || cls.getParent().equals(cls.getName())) { + roots.add(className); + } else { + hierarchy.computeIfAbsent(cls.getParent(), key -> new ArrayList<>()).add(className); + } + } + + Map simpleRanges = new HashMap<>(); + int current = 0; + for (String root : roots) { + assignRange(current, hierarchy, root, simpleRanges); + ++current; + } + + for (String className : classSource.getClassNames()) { + ClassReader cls = classSource.get(className); + if (cls == null) { + continue; + } + if (cls.hasModifier(ElementModifier.INTERFACE)) { + Set implementorRoots = implementedBy.get(cls.getName()); + if (implementorRoots != null) { + List ifaceRanges = implementorRoots.stream() + .map(implementor -> simpleRanges.get(implementor)) + .filter(Objects::nonNull) + .sorted(Comparator.comparing(range -> range.lower)) + .collect(Collectors.toList()); + if (!ifaceRanges.isEmpty()) { + ranges.put(className, ifaceRanges); + } + } + } else { + Range simpleRange = simpleRanges.get(className); + if (simpleRange != null) { + ranges.put(className, new ArrayList<>(Arrays.asList(simpleRange))); + } + } + } + } + + private String findTopmostImplementor(ClassReaderSource classSource, String className, String ifaceName) { + ClassReader cls = classSource.get(className); + if (cls == null) { + return null; + } + + if (cls.getParent() != null && !cls.getParent().equals(cls.getName())) { + String candidate = findTopmostImplementor(classSource, cls.getParent(), ifaceName); + if (candidate != null) { + return candidate; + } + } + + return cls.getInterfaces().contains(ifaceName) ? className : null; + } + + private void markImplementor(ClassReaderSource classSource, String className, String ifaceName, + Map> implementedBy) { + if (!implementedBy.computeIfAbsent(ifaceName, key -> new HashSet<>()).add(className)) { + return; + } + + ClassReader iface = classSource.get(ifaceName); + if (iface == null) { + return; + } + + for (String superIface : iface.getInterfaces()) { + markImplementor(classSource, className, superIface, implementedBy); + } + } + + private int assignRange(int start, Map> hierarhy, String className, + Map ranges) { + int end = start + 1; + for (String childClass : hierarhy.getOrDefault(className, Collections.emptyList())) { + end = assignRange(end, hierarhy, childClass, ranges); + } + ++end; + ranges.put(className, new Range(start, end)); + return end; + } + + public List getRanges(String className) { + return new ArrayList<>(ranges.getOrDefault(className, Collections.emptyList())); + } + + public static class Range { + public int lower; + public int upper; + + private Range(int lower, int upper) { + this.lower = lower; + this.upper = upper; + } + } +} diff --git a/core/src/main/java/org/teavm/runtime/RuntimeClass.java b/core/src/main/java/org/teavm/runtime/RuntimeClass.java index 6b187c754..b7b4b84ca 100644 --- a/core/src/main/java/org/teavm/runtime/RuntimeClass.java +++ b/core/src/main/java/org/teavm/runtime/RuntimeClass.java @@ -19,6 +19,38 @@ import org.teavm.interop.Structure; public class RuntimeClass extends Structure { public static int INITIALIZED = 1; + + public static int SIZE_OFFSET = 0; + public static int FLAGS_OFFSET = 4; + public static int LOWER_TAG_OFFSET = 8; + public static int UPPER_TAG_OFFSET = 12; + public static int EXCLUDED_RANGE_COUNT_OFFSET = 16; + public static int EXCLUDED_RANGE_ADDRESS_OFFSET = 20; + public static int CANARY_OFFSET = 24; + public static int VIRTUAL_TABLE_OFFSET = 28; + + public static int ARRAY_CLASS = -1; + public static int BOOLEAN_CLASS = -2; + public static int BYTE_CLASS = -3; + public static int SHORT_CLASS = -4; + public static int CHAR_CLASS = -5; + public static int INT_CLASS = -6; + public static int LONG_CLASS = -7; + public static int FLOAT_CLASS = -8; + public static int DOUBLE_CLASS = -9; + public int size; public int flags; + public int lowerTag; + public int upperTag; + public int excludedRangeCount; + public int excludedRangesAddress; + + public static int computeCanary(int size, int lowerTag, int upperTag) { + return size ^ (lowerTag << 8) ^ (lowerTag >>> 24) ^ (upperTag << 24) ^ (lowerTag >>> 8); + } + + public int computeCanary() { + return computeCanary(size, lowerTag, upperTag); + } } diff --git a/core/src/main/java/org/teavm/wasm/WasmTarget.java b/core/src/main/java/org/teavm/wasm/WasmTarget.java index 262d471f7..dacf1ba22 100644 --- a/core/src/main/java/org/teavm/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/wasm/WasmTarget.java @@ -44,6 +44,7 @@ import org.teavm.model.MethodHolder; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.Program; +import org.teavm.model.classes.TagRegistry; import org.teavm.model.classes.VirtualTableProvider; import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvokeInstruction; @@ -115,9 +116,14 @@ public class WasmTarget implements TeaVMTarget { @Override public void emit(ListableClassHolderSource classes, OutputStream output, BuildTarget buildTarget) { int address = 256; + WasmModule module = new WasmModule(); + WasmFunction initFunction = new WasmFunction("__start__"); VirtualTableProvider vtableProvider = createVirtualTableProvider(classes); - WasmClassGenerator classGenerator = new WasmClassGenerator(classes, vtableProvider, address); + TagRegistry tagRegistry = new TagRegistry(classes); + WasmClassGenerator classGenerator = new WasmClassGenerator(classes, vtableProvider, tagRegistry, + initFunction.getBody()); + classGenerator.setAddress(address); for (String className : classes.getClassNames()) { classGenerator.addClass(className); if (controller.wasCancelled()) { @@ -132,7 +138,6 @@ public class WasmTarget implements TeaVMTarget { context.addIntrinsic(new WasmRuntimeIntrinsic()); WasmGenerator generator = new WasmGenerator(decompiler, classes, context, classGenerator); - WasmModule module = new WasmModule(); module.setMemorySize(64); for (String className : classes.getClassNames()) { @@ -175,8 +180,6 @@ public class WasmTarget implements TeaVMTarget { return; } - WasmFunction initFunction = new WasmFunction("__start__"); - classGenerator.contributeToInitializer(initFunction.getBody(), module); for (String className : classes.getClassNames()) { ClassReader cls = classes.get(className); if (cls.getAnnotations().get(StaticInit.class.getName()) == null) { @@ -199,6 +202,10 @@ public class WasmTarget implements TeaVMTarget { } } + for (String function : classGenerator.getFunctionTable()) { + module.getFunctionTable().add(module.getFunctions().get(function)); + } + WasmRenderer renderer = new WasmRenderer(); renderer.render(module); diff --git a/core/src/main/java/org/teavm/wasm/generate/WasmClassGenerator.java b/core/src/main/java/org/teavm/wasm/generate/WasmClassGenerator.java index f27d1057f..ffb5da92a 100644 --- a/core/src/main/java/org/teavm/wasm/generate/WasmClassGenerator.java +++ b/core/src/main/java/org/teavm/wasm/generate/WasmClassGenerator.java @@ -17,6 +17,9 @@ package org.teavm.wasm.generate; import com.carrotsearch.hppc.ObjectIntMap; import com.carrotsearch.hppc.ObjectIntOpenHashMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -30,10 +33,11 @@ import org.teavm.model.FieldReader; import org.teavm.model.FieldReference; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; +import org.teavm.model.classes.TagRegistry; import org.teavm.model.classes.VirtualTable; import org.teavm.model.classes.VirtualTableEntry; import org.teavm.model.classes.VirtualTableProvider; -import org.teavm.wasm.model.WasmModule; +import org.teavm.runtime.RuntimeClass; import org.teavm.wasm.model.expression.WasmExpression; import org.teavm.wasm.model.expression.WasmInt32Constant; import org.teavm.wasm.model.expression.WasmInt32Subtype; @@ -43,12 +47,18 @@ public class WasmClassGenerator { private ClassReaderSource classSource; private int address; private Map binaryDataMap = new LinkedHashMap<>(); + private List initializer; + private Map functions = new HashMap<>(); + private List functionTable = new ArrayList<>(); private VirtualTableProvider vtableProvider; + private TagRegistry tagRegistry; - public WasmClassGenerator(ClassReaderSource classSource, VirtualTableProvider vtableProvider, int address) { + public WasmClassGenerator(ClassReaderSource classSource, VirtualTableProvider vtableProvider, + TagRegistry tagRegistry, List initializer) { this.classSource = classSource; this.vtableProvider = vtableProvider; - this.address = address; + this.tagRegistry = tagRegistry; + this.initializer = initializer; } public void addClass(String className) { @@ -58,6 +68,7 @@ public class WasmClassGenerator { ClassReader cls = classSource.get(className); ClassBinaryData binaryData = new ClassBinaryData(); + binaryData.name = className; binaryDataMap.put(className, binaryData); calculateLayout(cls, binaryData); @@ -65,49 +76,71 @@ public class WasmClassGenerator { return; } - binaryData.start = align(address, 8); - binaryData.vtable = vtableProvider.lookup(className); - int vtableSize = binaryData.vtable != null ? binaryData.vtable.getEntries().size() : 0; - binaryData.end = binaryData.start + 8 + vtableSize * 4; - - address = binaryData.end; + address = align(address, 8); + binaryData.start = address; + contributeToInitializer(binaryData); } public int getAddress() { return address; } - public void contributeToInitializer(List initializer, WasmModule module) { - Map functions = new HashMap<>(); + public void setAddress(int address) { + this.address = address; + } - for (ClassBinaryData binaryData : binaryDataMap.values()) { - if (binaryData.start < 0) { - continue; - } - WasmExpression index = new WasmInt32Constant(binaryData.start); - WasmExpression size = new WasmInt32Constant(binaryData.size); - initializer.add(new WasmStoreInt32(4, index, size, WasmInt32Subtype.INT32)); + public List getFunctionTable() { + return functionTable; + } - if (binaryData.vtable != null) { - for (VirtualTableEntry vtableEntry : binaryData.vtable.getEntries().values()) { - index = new WasmInt32Constant(binaryData.start + 8 + vtableEntry.getIndex() * 4); - int methodIndex; - if (vtableEntry.getImplementor() == null) { - methodIndex = -1; - } else { - methodIndex = functions.computeIfAbsent(vtableEntry.getImplementor(), implementor -> { - int result = module.getFunctionTable().size(); - String name = WasmMangling.mangleMethod(implementor); - module.getFunctionTable().add(module.getFunctions().get(name)); - return result; - }); - } + private void contributeToInitializer(ClassBinaryData binaryData) { + contributeInt32Value(binaryData.start + RuntimeClass.SIZE_OFFSET, binaryData.size); - WasmExpression methodIndexExpr = new WasmInt32Constant(methodIndex); - initializer.add(new WasmStoreInt32(4, index, methodIndexExpr, WasmInt32Subtype.INT32)); + List ranges = tagRegistry.getRanges(binaryData.name); + int lower = ranges.stream().mapToInt(range -> range.lower).min().orElse(0); + int upper = ranges.stream().mapToInt(range -> range.upper).max().orElse(0); + contributeInt32Value(binaryData.start + RuntimeClass.LOWER_TAG_OFFSET, lower); + contributeInt32Value(binaryData.start + RuntimeClass.UPPER_TAG_OFFSET, upper); + contributeInt32Value(binaryData.start + RuntimeClass.CANARY_OFFSET, + RuntimeClass.computeCanary(binaryData.size, lower, upper)); + + address = binaryData.start + RuntimeClass.VIRTUAL_TABLE_OFFSET; + VirtualTable vtable = vtableProvider.lookup(binaryData.name); + if (vtable != null) { + for (VirtualTableEntry vtableEntry : vtable.getEntries().values()) { + int methodIndex; + if (vtableEntry.getImplementor() == null) { + methodIndex = -1; + } else { + methodIndex = functions.computeIfAbsent(vtableEntry.getImplementor(), implementor -> { + int result = functionTable.size(); + functionTable.add(WasmMangling.mangleMethod(implementor)); + return result; + }); } + + contributeInt32Value(address, methodIndex); + address += 4; } } + + contributeInt32Value(binaryData.start + RuntimeClass.EXCLUDED_RANGE_COUNT_OFFSET, ranges.size() - 1); + + if (ranges.size() > 1) { + contributeInt32Value(binaryData.start + RuntimeClass.EXCLUDED_RANGE_ADDRESS_OFFSET, address); + + Collections.sort(ranges, Comparator.comparingInt(range -> range.lower)); + for (int i = 1; i < ranges.size(); ++i) { + contributeInt32Value(address, ranges.get(i - 1).upper); + contributeInt32Value(address + 4, ranges.get(i).lower); + address += 8; + } + } + } + + private void contributeInt32Value(int index, int value) { + initializer.add(new WasmStoreInt32(4, new WasmInt32Constant(index), new WasmInt32Constant(value), + WasmInt32Subtype.INT32)); } public int getClassPointer(String className) { @@ -182,11 +215,9 @@ public class WasmClassGenerator { } private class ClassBinaryData { - int start; - int end; - - VirtualTable vtable; + String name; int size; + int start; ObjectIntMap fieldLayout = new ObjectIntOpenHashMap<>(); } } diff --git a/core/src/main/java/org/teavm/wasm/generate/WasmGenerationVisitor.java b/core/src/main/java/org/teavm/wasm/generate/WasmGenerationVisitor.java index 4b77cdf0f..18677ea7c 100644 --- a/core/src/main/java/org/teavm/wasm/generate/WasmGenerationVisitor.java +++ b/core/src/main/java/org/teavm/wasm/generate/WasmGenerationVisitor.java @@ -615,7 +615,8 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { VirtualTableEntry vtableEntry = context.getVirtualTableProvider().lookup(expr.getMethod()); WasmExpression methodIndex = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD, - classIndex, new WasmInt32Constant(vtableEntry.getIndex() * 4 + 8)); + classIndex, new WasmInt32Constant(vtableEntry.getIndex() * 4 + + RuntimeClass.VIRTUAL_TABLE_OFFSET)); methodIndex = new WasmLoadInt32(4, methodIndex, WasmInt32Subtype.INT32); WasmIndirectCall call = new WasmIndirectCall(methodIndex);