From 1aebe5125631932d397d067ebdc4a88baa00733a Mon Sep 17 00:00:00 2001 From: Alexey Andreev <konsoletyper@gmail.com> Date: Tue, 20 Aug 2024 21:03:51 +0200 Subject: [PATCH] wasm gc: implement backend-specific virtual table builder --- .../org/teavm/backend/wasm/WasmGCTarget.java | 1 - .../wasm/gc/vtable/WasmGCVirtualTable.java | 61 +++++ .../gc/vtable/WasmGCVirtualTableBuilder.java | 243 ++++++++++++++++++ .../gc/vtable/WasmGCVirtualTableEntry.java | 42 +++ .../gc/vtable/WasmGCVirtualTableProvider.java | 38 +++ .../gc/WasmGCDeclarationsGenerator.java | 15 +- .../gc/classes/WasmGCClassGenerator.java | 67 ++--- .../gc/methods/WasmGCGenerationContext.java | 8 +- .../gc/methods/WasmGCGenerationVisitor.java | 33 +-- .../gc/methods/WasmGCMethodGenerator.java | 6 +- .../main/java/org/teavm/common/LCATree.java | 2 +- .../org/teavm/junit/TestWasmGCEntryPoint.java | 10 + 12 files changed, 436 insertions(+), 90 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTable.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableBuilder.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableEntry.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableProvider.java diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java index 41be0c3b1..f4920357e 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -118,7 +118,6 @@ public class WasmGCTarget implements TeaVMTarget { var declarationsGenerator = new WasmGCDeclarationsGenerator( module, classes, - controller::isVirtual, controller.getClassInitializerInfo(), controller.getDependencyInfo(), controller.getDiagnostics(), diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTable.java b/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTable.java new file mode 100644 index 000000000..39e895b9d --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTable.java @@ -0,0 +1,61 @@ +/* + * 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.gc.vtable; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; + +public class WasmGCVirtualTable { + private WasmGCVirtualTable parent; + private String className; + List<WasmGCVirtualTableEntry> entries; + MethodReference[] implementors; + private Map<MethodDescriptor, WasmGCVirtualTableEntry> entryMap; + + WasmGCVirtualTable(WasmGCVirtualTable parent, String className) { + this.parent = parent; + this.className = className; + } + + public WasmGCVirtualTable getParent() { + return parent; + } + + public String getClassName() { + return className; + } + + public List<? extends WasmGCVirtualTableEntry> getEntries() { + return entries; + } + + public MethodReference implementor(WasmGCVirtualTableEntry entry) { + return implementors[entry.getIndex()]; + } + + public WasmGCVirtualTableEntry entry(MethodDescriptor method) { + if (entryMap == null) { + entryMap = new HashMap<>(); + for (var entry : entries) { + entryMap.put(entry.getMethod(), entry); + } + } + return entryMap.get(method); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableBuilder.java b/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableBuilder.java new file mode 100644 index 000000000..429cf6278 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableBuilder.java @@ -0,0 +1,243 @@ +/* + * 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.gc.vtable; + +import com.carrotsearch.hppc.ObjectIntHashMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.teavm.common.LCATree; +import org.teavm.model.ClassReader; +import org.teavm.model.ElementModifier; +import org.teavm.model.ListableClassReaderSource; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; + +class WasmGCVirtualTableBuilder { + ListableClassReaderSource classes; + Collection<MethodReference> methodsAtCallSites; + private Map<String, Set<MethodDescriptor>> groupedMethodsAtCallSites = new HashMap<>(); + private List<Table> tables = new ArrayList<>(); + private Map<String, Table> tableMap = new HashMap<>(); + private LCATree lcaTree; + private Map<String, Table> interfaceImplementors = new HashMap<>(); + Map<String, WasmGCVirtualTable> result = new HashMap<>(); + + void build() { + initTables(); + buildLCA(); + fillInterfaceImplementors(); + groupMethodsFromCallSites(); + fillTables(); + buildResult(); + } + + private void initTables() { + for (var className : classes.getClassNames()) { + initTable(className); + } + } + + private void initTable(String className) { + var cls = classes.get(className); + if (!cls.hasModifier(ElementModifier.INTERFACE) && !tableMap.containsKey(className)) { + if (cls.getParent() != null) { + initTable(cls.getParent()); + } + var table = new Table(cls, tables.size()); + tables.add(table); + tableMap.put(className, table); + } + } + + private void buildLCA() { + lcaTree = new LCATree(tables.size() + 1); + for (var i = 0; i < tables.size(); i++) { + var table = tables.get(i); + var parentTable = table.cls.getParent() != null ? tableMap.get(table.cls.getParent()) : null; + lcaTree.addNode(parentTable != null ? parentTable.index + 1 : 0); + } + } + + private void fillInterfaceImplementors() { + for (var className : classes.getClassNames()) { + var cls = classes.get(className); + if (!cls.hasModifier(ElementModifier.INTERFACE)) { + var visited = new HashSet<String>(); + do { + var table = tableMap.get(cls.getName()); + for (var itfName : cls.getInterfaces()) { + addImplementorToInterface(itfName, table, visited); + } + cls = cls.getParent() != null ? classes.get(cls.getParent()) : null; + } while (cls != null); + } + } + } + + private void addImplementorToInterface(String interfaceName, Table newImplementor, Set<String> visited) { + if (!visited.add(interfaceName)) { + return; + } + var knownImplementor = interfaceImplementors.get(interfaceName); + if (knownImplementor == null) { + interfaceImplementors.put(interfaceName, newImplementor); + } else { + var lcaIndex = lcaTree.lcaOf(newImplementor.index + 1, knownImplementor.index + 1); + if (lcaIndex > 0) { + interfaceImplementors.put(interfaceName, tables.get(lcaIndex - 1)); + } + } + var cls = classes.get(interfaceName); + if (cls != null) { + for (var superInterface : cls.getInterfaces()) { + addImplementorToInterface(superInterface, newImplementor, visited); + } + } + } + + private void groupMethodsFromCallSites() { + for (var methodRef : methodsAtCallSites) { + var className = mapInterface(methodRef.getClassName()); + var group = groupedMethodsAtCallSites.computeIfAbsent(className, k -> new LinkedHashSet<>()); + group.add(methodRef.getDescriptor()); + } + } + + private String mapInterface(String name) { + var cls = classes.get(name); + if (cls == null || !cls.hasModifier(ElementModifier.INTERFACE)) { + return name; + } + var implementor = interfaceImplementors.get(cls.getName()); + if (implementor == null) { + return name; + } + return implementor.cls.getName(); + } + + private void fillTables() { + for (var className : classes.getClassNames()) { + var table = tableMap.get(className); + if (table != null) { + fillTable(table); + } + } + } + + private void fillTable(Table table) { + if (table.filled) { + return; + } + table.filled = true; + var parent = table.cls.getParent() != null ? tableMap.get(table.cls.getParent()) : null; + table.parent = parent; + var indexes = new ObjectIntHashMap<MethodDescriptor>(); + if (parent != null) { + fillTable(parent); + table.entries.addAll(parent.entries); + table.implementors.addAll(parent.implementors); + for (var entry : table.entries) { + indexes.put(entry.method, entry.index); + } + } + + var group = groupedMethodsAtCallSites.get(table.cls.getName()); + if (group != null) { + for (var method : group) { + if (indexes.getOrDefault(method, -1) < 0) { + var entry = new Entry(method, table, table.entries.size()); + table.entries.add(entry); + indexes.put(method, entry.index); + table.implementors.add(null); + } + } + } + + for (var method : table.cls.getMethods()) { + if (!method.hasModifier(ElementModifier.STATIC) && !method.hasModifier(ElementModifier.ABSTRACT)) { + var index = indexes.getOrDefault(method.getDescriptor(), -1); + if (index >= 0) { + table.implementors.set(index, method.getReference()); + } + } + } + } + + private void buildResult() { + for (var className : classes.getClassNames()) { + var cls = classes.get(className); + var table = !cls.hasModifier(ElementModifier.INTERFACE) + ? tableMap.get(className) + : interfaceImplementors.get(className); + if (table != null) { + result.put(className, table.getBuildResult()); + } + } + } + + private static class Table { + final ClassReader cls; + int index; + boolean filled; + Table parent; + List<Entry> entries = new ArrayList<>(); + List<MethodReference> implementors = new ArrayList<>(); + private WasmGCVirtualTable buildResult; + + Table(ClassReader cls, int index) { + this.cls = cls; + this.index = index; + } + + WasmGCVirtualTable getBuildResult() { + if (buildResult == null) { + buildResult = new WasmGCVirtualTable(parent != null ? parent.getBuildResult() : null, cls.getName()); + buildResult.entries = entries.stream() + .map(Entry::getBuildResult) + .collect(Collectors.toList()); + buildResult.implementors = implementors.toArray(new MethodReference[0]); + } + return buildResult; + } + } + + private static class Entry { + MethodDescriptor method; + Table origin; + int index; + private WasmGCVirtualTableEntry buildResult; + + Entry(MethodDescriptor method, Table origin, int index) { + this.method = method; + this.origin = origin; + this.index = index; + } + + WasmGCVirtualTableEntry getBuildResult() { + if (buildResult == null) { + buildResult = new WasmGCVirtualTableEntry(origin.getBuildResult(), method, index); + } + return buildResult; + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableEntry.java b/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableEntry.java new file mode 100644 index 000000000..7ccdd07b8 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableEntry.java @@ -0,0 +1,42 @@ +/* + * 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.gc.vtable; + +import org.teavm.model.MethodDescriptor; + +public class WasmGCVirtualTableEntry { + private WasmGCVirtualTable origin; + private MethodDescriptor method; + private int index; + + WasmGCVirtualTableEntry(WasmGCVirtualTable origin, MethodDescriptor method, int index) { + this.origin = origin; + this.method = method; + this.index = index; + } + + public WasmGCVirtualTable getOrigin() { + return origin; + } + + public MethodDescriptor getMethod() { + return method; + } + + public int getIndex() { + return index; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableProvider.java b/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableProvider.java new file mode 100644 index 000000000..70f9ba394 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableProvider.java @@ -0,0 +1,38 @@ +/* + * 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.gc.vtable; + +import java.util.Collection; +import java.util.Map; +import org.teavm.model.ListableClassReaderSource; +import org.teavm.model.MethodReference; + +public class WasmGCVirtualTableProvider { + private Map<String, WasmGCVirtualTable> virtualTables; + + public WasmGCVirtualTableProvider(ListableClassReaderSource classes, + Collection<MethodReference> methodsAtCallSites) { + var builder = new WasmGCVirtualTableBuilder(); + builder.classes = classes; + builder.methodsAtCallSites = methodsAtCallSites; + builder.build(); + virtualTables = builder.result; + } + + public WasmGCVirtualTable lookup(String name) { + return virtualTables.get(name); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java index b93f05dbf..4f9e217db 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java @@ -16,9 +16,9 @@ package org.teavm.backend.wasm.generate.gc; import java.util.List; -import java.util.function.Predicate; import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.WasmFunctionTypes; +import org.teavm.backend.wasm.gc.vtable.WasmGCVirtualTableProvider; import org.teavm.backend.wasm.generate.WasmNameProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassGenerator; import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; @@ -33,12 +33,10 @@ import org.teavm.dependency.DependencyInfo; import org.teavm.diagnostics.Diagnostics; import org.teavm.model.ClassHierarchy; import org.teavm.model.ListableClassHolderSource; -import org.teavm.model.MethodReference; import org.teavm.model.analysis.ClassInitializerInfo; import org.teavm.model.analysis.ClassMetadataRequirements; import org.teavm.model.classes.TagRegistry; import org.teavm.model.classes.VirtualTableBuilder; -import org.teavm.model.classes.VirtualTableProvider; public class WasmGCDeclarationsGenerator { public final ClassHierarchy hierarchy; @@ -50,7 +48,6 @@ public class WasmGCDeclarationsGenerator { public WasmGCDeclarationsGenerator( WasmModule module, ListableClassHolderSource classes, - Predicate<MethodReference> virtualMethods, ClassInitializerInfo classInitializerInfo, DependencyInfo dependencyInfo, Diagnostics diagnostics, @@ -59,7 +56,7 @@ public class WasmGCDeclarationsGenerator { ) { this.module = module; hierarchy = new ClassHierarchy(classes); - var virtualTables = createVirtualTableProvider(classes, virtualMethods); + var virtualTables = createVirtualTableProvider(classes); functionTypes = new WasmFunctionTypes(module); var names = new WasmNameProvider(); methodGenerator = new WasmGCMethodGenerator( @@ -133,12 +130,8 @@ public class WasmGCDeclarationsGenerator { } } - private static VirtualTableProvider createVirtualTableProvider(ListableClassHolderSource classes, - Predicate<MethodReference> virtualMethods) { - var builder = new VirtualTableBuilder(classes); - builder.setMethodsUsedAtCallSites(VirtualTableBuilder.getMethodsUsedOnCallSites(classes, false)); - builder.setMethodCalledVirtually(virtualMethods); - return builder.build(); + private static WasmGCVirtualTableProvider createVirtualTableProvider(ListableClassHolderSource classes) { + return new WasmGCVirtualTableProvider(classes, VirtualTableBuilder.getMethodsUsedOnCallSites(classes, false)); } public WasmFunction dummyInitializer() { diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java index 41a0637b1..21e894aff 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java @@ -20,16 +20,16 @@ import com.carrotsearch.hppc.ObjectIntMap; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Queue; -import java.util.Set; import java.util.function.Consumer; import org.teavm.backend.lowlevel.generate.NameProvider; import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.WasmFunctionTypes; +import org.teavm.backend.wasm.gc.vtable.WasmGCVirtualTable; +import org.teavm.backend.wasm.gc.vtable.WasmGCVirtualTableProvider; import org.teavm.backend.wasm.generate.gc.WasmGCInitializerContributor; import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringPool; import org.teavm.backend.wasm.model.WasmArray; @@ -66,8 +66,6 @@ import org.teavm.model.ValueType; import org.teavm.model.analysis.ClassInitializerInfo; import org.teavm.model.analysis.ClassMetadataRequirements; import org.teavm.model.classes.TagRegistry; -import org.teavm.model.classes.VirtualTable; -import org.teavm.model.classes.VirtualTableProvider; import org.teavm.model.util.ReflectionUtil; public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInitializerContributor { @@ -82,7 +80,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit private WasmFunctionTypes functionTypes; private TagRegistry tagRegistry; private ClassMetadataRequirements metadataRequirements; - private VirtualTableProvider virtualTables; + private WasmGCVirtualTableProvider virtualTables; private BaseWasmFunctionRepository functionProvider; private Map<ValueType, WasmGCClassInfo> classInfoMap = new LinkedHashMap<>(); private Queue<WasmGCClassInfo> classInfoQueue = new ArrayDeque<>(); @@ -113,7 +111,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit public WasmGCClassGenerator(WasmModule module, ClassReaderSource classSource, WasmFunctionTypes functionTypes, TagRegistry tagRegistry, - ClassMetadataRequirements metadataRequirements, VirtualTableProvider virtualTables, + ClassMetadataRequirements metadataRequirements, WasmGCVirtualTableProvider virtualTables, BaseWasmFunctionRepository functionProvider, NameProvider names, ClassInitializerInfo classInitializerInfo) { this.module = module; @@ -236,7 +234,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit classInfo = new WasmGCClassInfo(type); classInfoQueue.add(classInfo); classInfoMap.put(type, classInfo); - VirtualTable virtualTable = null; + WasmGCVirtualTable virtualTable = null; if (!(type instanceof ValueType.Primitive)) { var name = type instanceof ValueType.Object ? ((ValueType.Object) type).getClassName() @@ -265,7 +263,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit } } var pointerName = names.forClassInstance(type); - classInfo.hasOwnVirtualTable = virtualTable != null && virtualTable.hasValidEntries(); + classInfo.hasOwnVirtualTable = virtualTable != null && !virtualTable.getEntries().isEmpty(); var classStructure = classInfo.hasOwnVirtualTable ? initRegularClassStructure(((ValueType.Object) type).getClassName()) : standardClasses.classClass().getStructure(); @@ -366,8 +364,8 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit }; } - private void initRegularClass(WasmGCClassInfo classInfo, VirtualTable virtualTable, WasmStructure classStructure, - String name) { + private void initRegularClass(WasmGCClassInfo classInfo, WasmGCVirtualTable virtualTable, + WasmStructure classStructure, String name) { var cls = classSource.get(name); if (classInitializerInfo.isDynamicInitializer(name)) { if (cls != null && cls.getMethod(CLINIT_METHOD_DESC) != null) { @@ -396,39 +394,34 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit target.add(setClassField(classInfo, classParentOffset, new WasmGetGlobal(parent.pointer))); } } - if (virtualTable != null && virtualTable.hasValidEntries()) { - fillVirtualTableMethods(target, classStructure, classInfo.pointer, virtualTable, virtualTable, - new HashSet<>()); + if (virtualTable != null) { + fillVirtualTableMethods(target, classStructure, classInfo.pointer, virtualTable); } }; } private void fillVirtualTableMethods(List<WasmExpression> target, WasmStructure structure, WasmGlobal global, - VirtualTable virtualTable, VirtualTable original, Set<MethodDescriptor> filled) { - if (virtualTable.getParent() != null) { - fillVirtualTableMethods(target, structure, global, virtualTable.getParent(), original, filled); - } - for (var method : virtualTable.getMethods()) { - var entry = original.getEntry(method); - if (entry != null && entry.getImplementor() != null && filled.add(method) - && !method.equals(GET_CLASS_METHOD)) { + WasmGCVirtualTable virtualTable) { + for (var entry : virtualTable.getEntries()) { + var implementor = virtualTable.implementor(entry); + if (implementor != null && !entry.getMethod().equals(GET_CLASS_METHOD)) { var fieldIndex = virtualTableFieldOffset + entry.getIndex(); var expectedType = (WasmType.CompositeReference) structure.getFields().get(fieldIndex) .getUnpackedType(); var expectedFunctionType = (WasmFunctionType) expectedType.composite; - var function = functionProvider.forInstanceMethod(entry.getImplementor()); - if (!virtualTable.getClassName().equals(entry.getImplementor().getClassName()) + var function = functionProvider.forInstanceMethod(implementor); + if (entry.getOrigin().getClassName().equals(implementor.getClassName()) || expectedFunctionType != function.getType()) { var wrapperFunction = new WasmFunction(expectedFunctionType); module.functions.add(wrapperFunction); var call = new WasmCall(function); var instanceParam = new WasmLocal(getClassInfo(virtualTable.getClassName()).getType()); wrapperFunction.add(instanceParam); - var castTarget = getClassInfo(entry.getImplementor().getClassName()).getType(); + var castTarget = getClassInfo(implementor.getClassName()).getType(); call.getArguments().add(new WasmCast(new WasmGetLocal(instanceParam), castTarget)); - var params = new WasmLocal[method.parameterCount()]; - for (var i = 0; i < method.parameterCount(); ++i) { - params[i] = new WasmLocal(typeMapper.mapType(method.parameterType(i))); + var params = new WasmLocal[entry.getMethod().parameterCount()]; + for (var i = 0; i < entry.getMethod().parameterCount(); ++i) { + params[i] = new WasmLocal(typeMapper.mapType(entry.getMethod().parameterType(i))); call.getArguments().add(new WasmGetLocal(params[i])); wrapperFunction.add(params[i]); } @@ -462,20 +455,12 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit fields.add(monitorField); } - private void addVirtualTableFields(WasmStructure structure, VirtualTable virtualTable) { - if (virtualTable.getParent() != null) { - addVirtualTableFields(structure, virtualTable.getParent()); - } - for (var methodDesc : virtualTable.getMethods()) { - if (methodDesc == null) { - structure.getFields().add(new WasmField(WasmType.Reference.FUNC.asStorage())); - } else { - var originalVirtualTable = virtualTable.findMethodContainer(methodDesc); - var functionType = typeMapper.getFunctionType(originalVirtualTable.getClassName(), methodDesc, false); - var field = new WasmField(functionType.getReference().asStorage()); - field.setName(names.forVirtualMethod(methodDesc)); - structure.getFields().add(field); - } + private void addVirtualTableFields(WasmStructure structure, WasmGCVirtualTable virtualTable) { + for (var entry : virtualTable.getEntries()) { + var functionType = typeMapper.getFunctionType(entry.getOrigin().getClassName(), entry.getMethod(), false); + var field = new WasmField(functionType.getReference().asStorage()); + field.setName(names.forVirtualMethod(entry.getMethod())); + structure.getFields().add(field); } } diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java index fcb901f0a..b0fa0847f 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Set; import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.WasmFunctionTypes; +import org.teavm.backend.wasm.gc.vtable.WasmGCVirtualTableProvider; import org.teavm.backend.wasm.generate.common.methods.BaseWasmGenerationContext; import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCStandardClasses; @@ -40,14 +41,13 @@ import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; import org.teavm.model.ListableClassReaderSource; import org.teavm.model.MethodReference; -import org.teavm.model.classes.VirtualTableProvider; public class WasmGCGenerationContext implements BaseWasmGenerationContext { private WasmModule module; private WasmGCClassInfoProvider classInfoProvider; private WasmGCStandardClasses standardClasses; private WasmGCStringProvider strings; - private VirtualTableProvider virtualTables; + private WasmGCVirtualTableProvider virtualTables; private WasmGCTypeMapper typeMapper; private WasmFunctionTypes functionTypes; private ListableClassReaderSource classes; @@ -63,7 +63,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { private WasmTag exceptionTag; private Map<String, Set<String>> interfaceImplementors; - public WasmGCGenerationContext(WasmModule module, VirtualTableProvider virtualTables, + public WasmGCGenerationContext(WasmModule module, WasmGCVirtualTableProvider virtualTables, WasmGCTypeMapper typeMapper, WasmFunctionTypes functionTypes, ListableClassReaderSource classes, ClassHierarchy hierarchy, BaseWasmFunctionRepository functions, WasmGCSupertypeFunctionProvider supertypeFunctions, WasmGCClassInfoProvider classInfoProvider, @@ -96,7 +96,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { return strings; } - public VirtualTableProvider virtualTables() { + public WasmGCVirtualTableProvider virtualTables() { return virtualTables; } diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java index 91bbfffa8..47df8e791 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java @@ -62,13 +62,10 @@ import org.teavm.backend.wasm.model.expression.WasmStructSet; import org.teavm.backend.wasm.model.expression.WasmThrow; import org.teavm.backend.wasm.model.expression.WasmUnreachable; import org.teavm.model.ClassHierarchy; -import org.teavm.model.ElementModifier; import org.teavm.model.FieldReference; -import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; import org.teavm.model.TextLocation; import org.teavm.model.ValueType; -import org.teavm.model.classes.VirtualTable; public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { private WasmGCGenerationContext context; @@ -259,26 +256,18 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { protected WasmExpression generateVirtualCall(WasmLocal instance, MethodReference method, List<WasmExpression> arguments) { var vtable = context.virtualTables().lookup(method.getClassName()); - if (vtable != null) { - var cls = context.classes().get(method.getClassName()); - assert cls != null : "Virtual table can't be generated for absent class"; - if (cls.hasModifier(ElementModifier.INTERFACE)) { - vtable = pickVirtualTableForInterfaceCall(vtable, method.getDescriptor()); - } - vtable = vtable.findMethodContainer(method.getDescriptor()); - } if (vtable == null) { return new WasmUnreachable(); } - int vtableIndex = vtable.getMethods().indexOf(method.getDescriptor()); - if (vtable.getParent() != null) { - vtableIndex += vtable.getParent().size(); + var entry = vtable.entry(method.getDescriptor()); + if (entry == null) { + return new WasmUnreachable(); } WasmExpression classRef = new WasmStructGet(context.standardClasses().objectClass().getStructure(), new WasmGetLocal(instance), WasmGCClassInfoProvider.CLASS_FIELD_OFFSET); - var index = context.classInfoProvider().getVirtualMethodsOffset() + vtableIndex; + var index = context.classInfoProvider().getVirtualMethodsOffset() + entry.getIndex(); var expectedInstanceClassInfo = context.classInfoProvider().getClassInfo(vtable.getClassName()); var vtableStruct = expectedInstanceClassInfo.getVirtualTableStructure(); classRef = new WasmCast(classRef, vtableStruct.getReference()); @@ -298,20 +287,6 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { return invoke; } - private VirtualTable pickVirtualTableForInterfaceCall(VirtualTable virtualTable, MethodDescriptor descriptor) { - var implementors = context.getInterfaceImplementors(virtualTable.getClassName()); - for (var implementor : implementors) { - var implementorVtable = context.virtualTables().lookup(implementor); - if (implementorVtable != null && implementorVtable.hasMethod(descriptor)) { - while (implementorVtable.getParent() != null && implementorVtable.getParent().hasMethod(descriptor)) { - implementorVtable = implementorVtable.getParent(); - } - return implementorVtable; - } - } - throw new IllegalStateException(); - } - @Override protected void allocateObject(String className, TextLocation location, WasmLocal local, List<WasmExpression> target) { diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java index 7c7051912..f57f24f80 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java @@ -26,6 +26,7 @@ import org.teavm.backend.lowlevel.generate.NameProvider; import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.WasmFunctionTypes; import org.teavm.backend.wasm.gc.WasmGCVariableCategoryProvider; +import org.teavm.backend.wasm.gc.vtable.WasmGCVirtualTableProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCStandardClasses; import org.teavm.backend.wasm.generate.gc.classes.WasmGCSupertypeFunctionProvider; @@ -51,14 +52,13 @@ import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; import org.teavm.model.analysis.ClassInitializerInfo; -import org.teavm.model.classes.VirtualTableProvider; import org.teavm.model.util.RegisterAllocator; public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { private WasmModule module; private ClassHierarchy hierarchy; private ListableClassHolderSource classes; - private VirtualTableProvider virtualTables; + private WasmGCVirtualTableProvider virtualTables; private ClassInitializerInfo classInitInfo; private WasmFunctionTypes functionTypes; private WasmGCSupertypeFunctionProvider supertypeFunctions; @@ -82,7 +82,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { WasmModule module, ClassHierarchy hierarchy, ListableClassHolderSource classes, - VirtualTableProvider virtualTables, + WasmGCVirtualTableProvider virtualTables, ClassInitializerInfo classInitInfo, WasmFunctionTypes functionTypes, NameProvider names, diff --git a/core/src/main/java/org/teavm/common/LCATree.java b/core/src/main/java/org/teavm/common/LCATree.java index 3d0e4ba66..271e48ef3 100644 --- a/core/src/main/java/org/teavm/common/LCATree.java +++ b/core/src/main/java/org/teavm/common/LCATree.java @@ -25,7 +25,7 @@ public class LCATree { pathsToRoot = new int[capacity][]; sz = 1; depths[0] = 0; - pathsToRoot[0] = new int[0]; + pathsToRoot[0] = new int[] { 0 }; } public int size() { diff --git a/tools/junit/src/main/java/org/teavm/junit/TestWasmGCEntryPoint.java b/tools/junit/src/main/java/org/teavm/junit/TestWasmGCEntryPoint.java index dbdb2505c..236c82569 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestWasmGCEntryPoint.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestWasmGCEntryPoint.java @@ -15,11 +15,21 @@ */ package org.teavm.junit; +import org.teavm.classlib.impl.console.JSStdoutPrintStream; + final class TestWasmGCEntryPoint { private TestWasmGCEntryPoint() { } public static void main(String[] args) throws Throwable { + try { + TestEntryPoint.run(args.length > 0 ? args[0] : null); + new JSStdoutPrintStream().println("SUCCESS"); + } catch (Throwable e) { + var out = new JSStdoutPrintStream(); + e.printStackTrace(out); + out.println("FAILURE"); + } TestEntryPoint.run(args.length > 0 ? args[0] : null); } }