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);
     }
 }