From fe5aca51390393c716eb09f31d6c6b62e4e8925c Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 11 Aug 2016 13:33:22 +0300 Subject: [PATCH] Add support of virtual method invocation --- .../classes/InterfaceToClassMapping.java | 106 ++++++++++ .../org/teavm/model/classes/VirtualTable.java | 42 ++++ .../model/classes/VirtualTableEntry.java | 49 +++++ .../model/classes/VirtualTableProvider.java | 94 +++++++++ .../org/teavm/parsing/ClassRefsRenamer.java | 7 +- .../java/org/teavm/runtime/Allocator.java | 2 +- .../java/org/teavm/runtime/RuntimeObject.java | 2 +- .../src/main/java/org/teavm/wasm/Example.java | 45 +++++ .../main/java/org/teavm/wasm/WasmTarget.java | 42 +++- .../wasm/generate/WasmClassGenerator.java | 45 ++++- .../wasm/generate/WasmGenerationContext.java | 9 +- .../wasm/generate/WasmGenerationVisitor.java | 94 +++++++-- .../org/teavm/wasm/generate/WasmMangling.java | 1 + .../WasmDefaultExpressionVisitor.java | 183 ++++++++++++++++++ .../model/expression/WasmIndirectCall.java | 21 +- .../org/teavm/wasm/render/WasmRenderer.java | 68 ++++++- .../wasm/render/WasmRenderingVisitor.java | 19 +- .../org/teavm/wasm/render/WasmSignature.java | 44 +++++ .../wasm/render/WasmSignatureCollector.java | 39 ++++ .../java/org/teavm/interop/Structure.java | 8 +- 20 files changed, 870 insertions(+), 50 deletions(-) create mode 100644 core/src/main/java/org/teavm/model/classes/InterfaceToClassMapping.java create mode 100644 core/src/main/java/org/teavm/model/classes/VirtualTable.java create mode 100644 core/src/main/java/org/teavm/model/classes/VirtualTableEntry.java create mode 100644 core/src/main/java/org/teavm/model/classes/VirtualTableProvider.java create mode 100644 core/src/main/java/org/teavm/wasm/model/expression/WasmDefaultExpressionVisitor.java create mode 100644 core/src/main/java/org/teavm/wasm/render/WasmSignature.java create mode 100644 core/src/main/java/org/teavm/wasm/render/WasmSignatureCollector.java diff --git a/core/src/main/java/org/teavm/model/classes/InterfaceToClassMapping.java b/core/src/main/java/org/teavm/model/classes/InterfaceToClassMapping.java new file mode 100644 index 000000000..211af3383 --- /dev/null +++ b/core/src/main/java/org/teavm/model/classes/InterfaceToClassMapping.java @@ -0,0 +1,106 @@ +/* + * 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.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.ElementModifier; +import org.teavm.model.ListableClassReaderSource; + +public class InterfaceToClassMapping { + private Map map = new HashMap<>(); + + public InterfaceToClassMapping(ListableClassReaderSource classSource) { + for (String className : classSource.getClassNames()) { + ClassReader cls = classSource.get(className); + if (cls.hasModifier(ElementModifier.INTERFACE)) { + continue; + } + + map.put(className, className); + for (String iface : getInterfaces(classSource, className)) { + String existing = map.get(iface); + if (existing == null) { + map.put(iface, className); + } else { + map.put(iface, commonSuperClass(classSource, className, existing)); + } + } + } + } + + private static Set getInterfaces(ClassReaderSource classSource, String className) { + Set interfaces = new HashSet<>(); + getInterfaces(classSource, className, interfaces); + return interfaces; + } + + private static void getInterfaces(ClassReaderSource classSource, String className, Set interfaces) { + if (!interfaces.add(className)) { + return; + } + ClassReader cls = classSource.get(className); + if (cls == null) { + return; + } + for (String iface : cls.getInterfaces()) { + getInterfaces(classSource, iface, interfaces); + } + } + + private static String commonSuperClass(ClassReaderSource classSource, String a, String b) { + if (a.equals(b)) { + return a; + } + + List firstPath = pathToRoot(classSource, a); + List secondPath = pathToRoot(classSource, b); + Collections.reverse(firstPath); + Collections.reverse(secondPath); + int min = Math.min(firstPath.size(), secondPath.size()); + for (int i = 1; i < min; ++i) { + if (!firstPath.get(i).equals(secondPath.get(i))) { + return firstPath.get(i - 1); + } + } + + return a; + } + + private static List pathToRoot(ClassReaderSource classSource, String className) { + List path = new ArrayList<>(); + while (true) { + path.add(className); + ClassReader cls = classSource.get(className); + if (cls == null || cls.getParent() == null) { + break; + } + className = cls.getParent(); + } + return path; + } + + public String mapClass(String className) { + return map.get(className); + } +} diff --git a/core/src/main/java/org/teavm/model/classes/VirtualTable.java b/core/src/main/java/org/teavm/model/classes/VirtualTable.java new file mode 100644 index 000000000..42e2965db --- /dev/null +++ b/core/src/main/java/org/teavm/model/classes/VirtualTable.java @@ -0,0 +1,42 @@ +/* + * 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.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import org.teavm.model.MethodDescriptor; + +public class VirtualTable { + private String className; + Map entries = new LinkedHashMap<>(); + private Map readonlyEntries; + + VirtualTable(String className) { + this.className = className; + } + + public String getClassName() { + return className; + } + + public Map getEntries() { + if (readonlyEntries == null) { + readonlyEntries = Collections.unmodifiableMap(entries); + } + return readonlyEntries; + } +} diff --git a/core/src/main/java/org/teavm/model/classes/VirtualTableEntry.java b/core/src/main/java/org/teavm/model/classes/VirtualTableEntry.java new file mode 100644 index 000000000..92370304e --- /dev/null +++ b/core/src/main/java/org/teavm/model/classes/VirtualTableEntry.java @@ -0,0 +1,49 @@ +/* + * 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 org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; + +public class VirtualTableEntry { + private VirtualTable virtualTable; + private MethodDescriptor method; + MethodReference implementor; + private int index; + + VirtualTableEntry(VirtualTable virtualTable, MethodDescriptor method, MethodReference implementor, int index) { + this.virtualTable = virtualTable; + this.method = method; + this.implementor = implementor; + this.index = index; + } + + public VirtualTable getVirtualTable() { + return virtualTable; + } + + public MethodDescriptor getMethod() { + return method; + } + + public MethodReference getImplementor() { + return implementor; + } + + public int getIndex() { + return index; + } +} diff --git a/core/src/main/java/org/teavm/model/classes/VirtualTableProvider.java b/core/src/main/java/org/teavm/model/classes/VirtualTableProvider.java new file mode 100644 index 000000000..450e542b0 --- /dev/null +++ b/core/src/main/java/org/teavm/model/classes/VirtualTableProvider.java @@ -0,0 +1,94 @@ +/* + * 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.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.ListableClassReaderSource; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; + +public class VirtualTableProvider { + private ClassReaderSource classSource; + private Map> virtualMethodMap = new HashMap<>(); + private Map virtualTables = new LinkedHashMap<>(); + private InterfaceToClassMapping interfaceMapping; + + public VirtualTableProvider(ListableClassReaderSource classSource, Set virtualMethods) { + this.classSource = classSource; + interfaceMapping = new InterfaceToClassMapping(classSource); + + for (MethodReference virtualMethod : virtualMethods) { + String cls = interfaceMapping.mapClass(virtualMethod.getClassName()); + virtualMethodMap.computeIfAbsent(cls, c -> new HashSet<>()).add(virtualMethod.getDescriptor()); + } + + for (String className : classSource.getClassNames()) { + fillClass(className); + } + } + + private void fillClass(String className) { + if (virtualTables.containsKey(className)) { + return; + } + + VirtualTable table = new VirtualTable(className); + virtualTables.put(className, table); + ClassReader cls = classSource.get(className); + if (cls.getParent() != null) { + fillClass(cls.getParent()); + VirtualTable parentTable = virtualTables.get(cls.getParent()); + for (VirtualTableEntry parentEntry : parentTable.entries.values()) { + VirtualTableEntry entry = new VirtualTableEntry(table, parentEntry.getMethod(), + parentEntry.getImplementor(), parentEntry.getIndex()); + table.entries.put(entry.getMethod(), entry); + } + } + + Set newDescriptors = virtualMethodMap.get(className); + if (newDescriptors != null) { + for (MethodDescriptor method : newDescriptors) { + table.entries.put(method, new VirtualTableEntry(table, method, null, table.entries.size())); + } + } + + for (MethodReader method : cls.getMethods()) { + VirtualTableEntry entry = table.entries.get(method.getDescriptor()); + if (entry != null) { + entry.implementor = method.getReference(); + } + } + } + + public VirtualTableEntry lookup(MethodReference method) { + VirtualTable vtable = virtualTables.get(interfaceMapping.mapClass(method.getClassName())); + if (vtable == null) { + return null; + } + return vtable.getEntries().get(method.getDescriptor()); + } + + public VirtualTable lookup(String className) { + return virtualTables.get(interfaceMapping.mapClass(className)); + } +} diff --git a/core/src/main/java/org/teavm/parsing/ClassRefsRenamer.java b/core/src/main/java/org/teavm/parsing/ClassRefsRenamer.java index af23849c1..065883cf1 100644 --- a/core/src/main/java/org/teavm/parsing/ClassRefsRenamer.java +++ b/core/src/main/java/org/teavm/parsing/ClassRefsRenamer.java @@ -25,10 +25,6 @@ import org.teavm.model.*; import org.teavm.model.instructions.*; import org.teavm.model.util.ModelUtils; -/** - * - * @author Alexey Andreev - */ public class ClassRefsRenamer implements InstructionVisitor { private Mapper classNameMapper; @@ -49,6 +45,9 @@ public class ClassRefsRenamer implements InstructionVisitor { } } renamedCls.setParent(parent != null ? classNameMapper.map(parent) : null); + if (renamedCls.getName().equals(renamedCls.getParent())) { + renamedCls.setParent(null); + } for (MethodHolder method : cls.getMethods()) { if (method.getAnnotations().get(Remove.class.getName()) != null) { continue; diff --git a/core/src/main/java/org/teavm/runtime/Allocator.java b/core/src/main/java/org/teavm/runtime/Allocator.java index 47720ace5..e307c9f15 100644 --- a/core/src/main/java/org/teavm/runtime/Allocator.java +++ b/core/src/main/java/org/teavm/runtime/Allocator.java @@ -28,7 +28,7 @@ public final class Allocator { Address result = address; address = result.add(tag.size); RuntimeObject object = result.toStructure(); - object.classInfo = tag; + object.classReference = tag.toAddress().toInt() >> 3; return result; } } diff --git a/core/src/main/java/org/teavm/runtime/RuntimeObject.java b/core/src/main/java/org/teavm/runtime/RuntimeObject.java index 0145013b6..339ee9f9b 100644 --- a/core/src/main/java/org/teavm/runtime/RuntimeObject.java +++ b/core/src/main/java/org/teavm/runtime/RuntimeObject.java @@ -18,5 +18,5 @@ package org.teavm.runtime; import org.teavm.interop.Structure; public class RuntimeObject extends Structure { - public RuntimeClass classInfo; + public int classReference; } diff --git a/core/src/main/java/org/teavm/wasm/Example.java b/core/src/main/java/org/teavm/wasm/Example.java index 51f4b157a..cb562f902 100644 --- a/core/src/main/java/org/teavm/wasm/Example.java +++ b/core/src/main/java/org/teavm/wasm/Example.java @@ -29,6 +29,23 @@ public final class Example { WasmRuntime.print(a); } WasmRuntime.print(new A(2).getValue() + new A(3).getValue()); + + for (int i = 0; i < 4; ++i) { + WasmRuntime.print(instance(i).foo()); + } + } + + private static Base instance(int index) { + switch (index) { + case 0: + return new Derived1(); + case 1: + return new Derived2(); + case 2: + return new Derived3(); + default: + return new Derived4(); + } } private static class A { @@ -42,4 +59,32 @@ public final class Example { return value; } } + + interface Base { + int foo(); + } + + static class Derived1 implements Base { + @Override + public int foo() { + return 234; + } + } + + static class Derived2 implements Base { + @Override + public int foo() { + return 345; + } + } + + static class Derived3 extends Derived2 { + } + + static class Derived4 extends Derived1 { + @Override + public int foo() { + return 123; + } + } } diff --git a/core/src/main/java/org/teavm/wasm/WasmTarget.java b/core/src/main/java/org/teavm/wasm/WasmTarget.java index 40f3859b2..262d471f7 100644 --- a/core/src/main/java/org/teavm/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/wasm/WasmTarget.java @@ -24,22 +24,29 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Set; import org.teavm.ast.decompilation.Decompiler; import org.teavm.dependency.DependencyChecker; import org.teavm.interop.Address; import org.teavm.interop.Import; import org.teavm.interop.StaticInit; import org.teavm.interop.Structure; +import org.teavm.model.BasicBlock; import org.teavm.model.CallLocation; import org.teavm.model.ClassHolder; import org.teavm.model.ClassReader; import org.teavm.model.ElementModifier; +import org.teavm.model.Instruction; import org.teavm.model.ListableClassHolderSource; import org.teavm.model.ListableClassReaderSource; import org.teavm.model.MethodDescriptor; 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.VirtualTableProvider; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.InvokeInstruction; import org.teavm.runtime.Allocator; import org.teavm.runtime.RuntimeClass; import org.teavm.vm.BuildTarget; @@ -109,7 +116,8 @@ public class WasmTarget implements TeaVMTarget { public void emit(ListableClassHolderSource classes, OutputStream output, BuildTarget buildTarget) { int address = 256; - WasmClassGenerator classGenerator = new WasmClassGenerator(classes, address); + VirtualTableProvider vtableProvider = createVirtualTableProvider(classes); + WasmClassGenerator classGenerator = new WasmClassGenerator(classes, vtableProvider, address); for (String className : classes.getClassNames()) { classGenerator.addClass(className); if (controller.wasCancelled()) { @@ -120,7 +128,7 @@ public class WasmTarget implements TeaVMTarget { Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), new HashSet<>(), new HashSet<>()); - WasmGenerationContext context = new WasmGenerationContext(classes); + WasmGenerationContext context = new WasmGenerationContext(classes, vtableProvider); context.addIntrinsic(new WasmRuntimeIntrinsic()); WasmGenerator generator = new WasmGenerator(decompiler, classes, context, classGenerator); @@ -168,7 +176,7 @@ public class WasmTarget implements TeaVMTarget { } WasmFunction initFunction = new WasmFunction("__start__"); - classGenerator.contributeToInitializer(initFunction.getBody()); + classGenerator.contributeToInitializer(initFunction.getBody(), module); for (String className : classes.getClassNames()) { ClassReader cls = classes.get(className); if (cls.getAnnotations().get(StaticInit.class.getName()) == null) { @@ -181,6 +189,7 @@ public class WasmTarget implements TeaVMTarget { initFunction.getBody().add(new WasmCall(WasmMangling.mangleMethod(clinit.getReference()))); } module.add(initFunction); + module.setStartFunction(initFunction); for (TeaVMEntryPoint entryPoint : controller.getEntryPoints().values()) { String mangledName = WasmMangling.mangleMethod(entryPoint.getReference()); @@ -253,6 +262,33 @@ public class WasmTarget implements TeaVMTarget { module.add(function); } + private VirtualTableProvider createVirtualTableProvider(ListableClassHolderSource classes) { + Set virtualMethods = new HashSet<>(); + + for (String className : classes.getClassNames()) { + ClassHolder cls = classes.get(className); + for (MethodHolder method : cls.getMethods()) { + Program program = method.getProgram(); + if (program == null) { + continue; + } + for (int i = 0; i < program.basicBlockCount(); ++i) { + BasicBlock block = program.basicBlockAt(i); + for (Instruction insn : block.getInstructions()) { + if (insn instanceof InvokeInstruction) { + InvokeInstruction invoke = (InvokeInstruction) insn; + if (invoke.getType() == InvocationType.VIRTUAL) { + virtualMethods.add(invoke.getMethod()); + } + } + } + } + } + } + + return new VirtualTableProvider(classes, virtualMethods); + } + public static void main(String[] args) throws IOException { TeaVM vm = new TeaVMBuilder(new WasmTarget()).build(); vm.installPlugins(); 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 3239ca45b..f27d1057f 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,7 @@ package org.teavm.wasm.generate; import com.carrotsearch.hppc.ObjectIntMap; import com.carrotsearch.hppc.ObjectIntOpenHashMap; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -27,7 +28,12 @@ import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; 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.VirtualTable; +import org.teavm.model.classes.VirtualTableEntry; +import org.teavm.model.classes.VirtualTableProvider; +import org.teavm.wasm.model.WasmModule; import org.teavm.wasm.model.expression.WasmExpression; import org.teavm.wasm.model.expression.WasmInt32Constant; import org.teavm.wasm.model.expression.WasmInt32Subtype; @@ -37,9 +43,11 @@ public class WasmClassGenerator { private ClassReaderSource classSource; private int address; private Map binaryDataMap = new LinkedHashMap<>(); + private VirtualTableProvider vtableProvider; - public WasmClassGenerator(ClassReaderSource classSource, int address) { + public WasmClassGenerator(ClassReaderSource classSource, VirtualTableProvider vtableProvider, int address) { this.classSource = classSource; + this.vtableProvider = vtableProvider; this.address = address; } @@ -56,8 +64,11 @@ public class WasmClassGenerator { if (binaryData.start < 0) { return; } - binaryData.start = align(address, 4); - binaryData.end = binaryData.start + 8; + + 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; } @@ -66,7 +77,9 @@ public class WasmClassGenerator { return address; } - public void contributeToInitializer(List initializer) { + public void contributeToInitializer(List initializer, WasmModule module) { + Map functions = new HashMap<>(); + for (ClassBinaryData binaryData : binaryDataMap.values()) { if (binaryData.start < 0) { continue; @@ -74,6 +87,26 @@ public class WasmClassGenerator { WasmExpression index = new WasmInt32Constant(binaryData.start); WasmExpression size = new WasmInt32Constant(binaryData.size); initializer.add(new WasmStoreInt32(4, index, size, WasmInt32Subtype.INT32)); + + 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; + }); + } + + WasmExpression methodIndexExpr = new WasmInt32Constant(methodIndex); + initializer.add(new WasmStoreInt32(4, index, methodIndexExpr, WasmInt32Subtype.INT32)); + } + } } } @@ -122,6 +155,9 @@ public class WasmClassGenerator { } private static int align(int base, int alignment) { + if (base == 0) { + return 0; + } return ((base - 1) / alignment + 1) * alignment; } @@ -149,6 +185,7 @@ public class WasmClassGenerator { int start; int end; + VirtualTable vtable; int size; ObjectIntMap fieldLayout = new ObjectIntOpenHashMap<>(); } diff --git a/core/src/main/java/org/teavm/wasm/generate/WasmGenerationContext.java b/core/src/main/java/org/teavm/wasm/generate/WasmGenerationContext.java index 59c16be25..a0c3fd033 100644 --- a/core/src/main/java/org/teavm/wasm/generate/WasmGenerationContext.java +++ b/core/src/main/java/org/teavm/wasm/generate/WasmGenerationContext.java @@ -29,16 +29,19 @@ import org.teavm.model.FieldReference; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; +import org.teavm.model.classes.VirtualTableProvider; import org.teavm.wasm.intrinsics.WasmIntrinsic; public class WasmGenerationContext { private ClassReaderSource classSource; + private VirtualTableProvider vtableProvider; private Map importedMethods = new HashMap<>(); private List intrinsics = new ArrayList<>(); private Map intrinsicCache = new HashMap<>(); - public WasmGenerationContext(ClassReaderSource classSource) { + public WasmGenerationContext(ClassReaderSource classSource, VirtualTableProvider vtableProvider) { this.classSource = classSource; + this.vtableProvider = vtableProvider; } public void addIntrinsic(WasmIntrinsic intrinsic) { @@ -86,6 +89,10 @@ public class WasmGenerationContext { return field.getType(); } + public VirtualTableProvider getVirtualTableProvider() { + return vtableProvider; + } + public class ImportedMethod { public final String name; public final String module; 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 bdd562da4..4b77cdf0f 100644 --- a/core/src/main/java/org/teavm/wasm/generate/WasmGenerationVisitor.java +++ b/core/src/main/java/org/teavm/wasm/generate/WasmGenerationVisitor.java @@ -15,10 +15,8 @@ */ package org.teavm.wasm.generate; -import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import org.teavm.ast.AssignmentStatement; @@ -60,13 +58,16 @@ import org.teavm.ast.UnwrapArrayExpr; import org.teavm.ast.VariableExpr; import org.teavm.ast.WhileStatement; import org.teavm.interop.Address; +import org.teavm.interop.Structure; import org.teavm.model.ClassReader; import org.teavm.model.FieldReference; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; +import org.teavm.model.classes.VirtualTableEntry; import org.teavm.runtime.Allocator; import org.teavm.runtime.RuntimeClass; +import org.teavm.wasm.WasmRuntime; import org.teavm.wasm.intrinsics.WasmIntrinsic; import org.teavm.wasm.intrinsics.WasmIntrinsicManager; import org.teavm.wasm.model.WasmFunction; @@ -86,6 +87,7 @@ import org.teavm.wasm.model.expression.WasmFloatBinary; import org.teavm.wasm.model.expression.WasmFloatBinaryOperation; import org.teavm.wasm.model.expression.WasmFloatType; import org.teavm.wasm.model.expression.WasmGetLocal; +import org.teavm.wasm.model.expression.WasmIndirectCall; import org.teavm.wasm.model.expression.WasmInt32Constant; import org.teavm.wasm.model.expression.WasmInt32Subtype; import org.teavm.wasm.model.expression.WasmInt64Constant; @@ -104,7 +106,6 @@ import org.teavm.wasm.model.expression.WasmStoreFloat64; import org.teavm.wasm.model.expression.WasmStoreInt32; import org.teavm.wasm.model.expression.WasmStoreInt64; import org.teavm.wasm.model.expression.WasmSwitch; -import org.teavm.wasm.WasmRuntime; class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { private WasmGenerationContext context; @@ -116,6 +117,7 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { private Map breakTargets = new HashMap<>(); private Map continueTargets = new HashMap<>(); private Set usedBlocks = new HashSet<>(); + private int temporaryInt32 = -1; WasmExpression result; WasmGenerationVisitor(WasmGenerationContext context, WasmClassGenerator classGenerator, @@ -486,36 +488,42 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { @Override public void visit(SwitchStatement statement) { - List wrappers = new ArrayList<>(); + WasmBlock defaultBlock = new WasmBlock(false); + + breakTargets.put(statement, defaultBlock); + IdentifiedStatement oldBreakTarget = currentBreakTarget; + currentBreakTarget = statement; WasmBlock wrapper = new WasmBlock(false); statement.getValue().acceptVisitor(this); WasmSwitch wasmSwitch = new WasmSwitch(result, wrapper); wrapper.getBody().add(wasmSwitch); - WasmBlock defaultBlock = new WasmBlock(false); - defaultBlock.getBody().add(wrapper); - for (Statement part : statement.getDefaultClause()) { - part.acceptVisitor(this); - defaultBlock.getBody().add(result); - } - wrapper = defaultBlock; - for (SwitchClause clause : statement.getClauses()) { WasmBlock caseBlock = new WasmBlock(false); caseBlock.getBody().add(wrapper); wasmSwitch.getTargets().add(wrapper); for (Statement part : clause.getBody()) { part.acceptVisitor(this); - caseBlock.getBody().add(result); + if (result != null) { + caseBlock.getBody().add(result); + } } - wrappers.add(caseBlock); wrapper = caseBlock; } - for (WasmBlock nestedWrapper : wrappers) { - nestedWrapper.getBody().add(new WasmBreak(wrapper)); + defaultBlock.getBody().add(wrapper); + for (Statement part : statement.getDefaultClause()) { + part.acceptVisitor(this); + if (result != null) { + defaultBlock.getBody().add(result); + } } + wasmSwitch.setDefaultTarget(wrapper); + wrapper = defaultBlock; + + breakTargets.remove(statement); + currentBreakTarget = oldBreakTarget; result = wrapper; } @@ -570,6 +578,10 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { generateAddressInvocation(expr); return; } + if (expr.getMethod().getClassName().equals(Structure.class.getName())) { + generateStructureInvocation(expr); + return; + } WasmIntrinsic intrinsic = context.getIntrinsic(expr.getMethod()); if (intrinsic != null) { @@ -589,6 +601,40 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { call.getArguments().add(result); } result = call; + } else { + expr.getArguments().get(0).acceptVisitor(this); + WasmExpression instance = result; + WasmBlock block = new WasmBlock(false); + WasmLocal instanceVar = function.getLocalVariables().get(getTemporaryInt32()); + block.getBody().add(new WasmSetLocal(instanceVar, instance)); + instance = new WasmGetLocal(instanceVar); + + WasmExpression classIndex = new WasmLoadInt32(4, instance, WasmInt32Subtype.INT32); + classIndex = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.SHL, classIndex, + new WasmInt32Constant(3)); + + VirtualTableEntry vtableEntry = context.getVirtualTableProvider().lookup(expr.getMethod()); + WasmExpression methodIndex = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD, + classIndex, new WasmInt32Constant(vtableEntry.getIndex() * 4 + 8)); + methodIndex = new WasmLoadInt32(4, methodIndex, WasmInt32Subtype.INT32); + + WasmIndirectCall call = new WasmIndirectCall(methodIndex); + call.getParameterTypes().add(WasmType.INT32); + for (int i = 0; i < expr.getMethod().parameterCount(); ++i) { + call.getParameterTypes().add(WasmGeneratorUtil.mapType(expr.getMethod().parameterType(i))); + } + if (expr.getMethod().getReturnType() != ValueType.VOID) { + call.setReturnType(WasmGeneratorUtil.mapType(expr.getMethod().getReturnType())); + } + + call.getArguments().add(instance); + for (int i = 1; i < expr.getArguments().size(); ++i) { + expr.getArguments().get(i).acceptVisitor(this); + call.getArguments().add(result); + } + + block.getBody().add(call); + result = block; } } @@ -700,6 +746,14 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { } } + private void generateStructureInvocation(InvocationExpr expr) { + switch (expr.getMethod().getName()) { + case "toAddress": + expr.getArguments().get(0).acceptVisitor(this); + break; + } + } + @Override public void visit(BlockStatement statement) { WasmBlock block = new WasmBlock(false); @@ -990,4 +1044,12 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { return result; } }; + + private int getTemporaryInt32() { + if (temporaryInt32 < 0) { + temporaryInt32 = function.getLocalVariables().size(); + function.add(new WasmLocal(WasmType.INT32)); + } + return temporaryInt32; + } } diff --git a/core/src/main/java/org/teavm/wasm/generate/WasmMangling.java b/core/src/main/java/org/teavm/wasm/generate/WasmMangling.java index e1f406869..40627157d 100644 --- a/core/src/main/java/org/teavm/wasm/generate/WasmMangling.java +++ b/core/src/main/java/org/teavm/wasm/generate/WasmMangling.java @@ -17,6 +17,7 @@ package org.teavm.wasm.generate; import java.util.Arrays; import java.util.stream.Collectors; +import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; diff --git a/core/src/main/java/org/teavm/wasm/model/expression/WasmDefaultExpressionVisitor.java b/core/src/main/java/org/teavm/wasm/model/expression/WasmDefaultExpressionVisitor.java new file mode 100644 index 000000000..9fcac845f --- /dev/null +++ b/core/src/main/java/org/teavm/wasm/model/expression/WasmDefaultExpressionVisitor.java @@ -0,0 +1,183 @@ +/* + * 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.wasm.model.expression; + +public class WasmDefaultExpressionVisitor implements WasmExpressionVisitor { + @Override + public void visit(WasmBlock expression) { + for (WasmExpression part : expression.getBody()) { + part.acceptVisitor(this); + } + } + + @Override + public void visit(WasmBranch expression) { + expression.getCondition().acceptVisitor(this); + if (expression.getResult() != null) { + expression.getResult().acceptVisitor(this); + } + } + + @Override + public void visit(WasmBreak expression) { + if (expression.getResult() != null) { + expression.getResult().acceptVisitor(this); + } + } + + @Override + public void visit(WasmSwitch expression) { + expression.getSelector().acceptVisitor(this); + } + + @Override + public void visit(WasmConditional expression) { + expression.getCondition().acceptVisitor(this); + for (WasmExpression part : expression.getThenBlock().getBody()) { + part.acceptVisitor(this); + } + for (WasmExpression part : expression.getElseBlock().getBody()) { + part.acceptVisitor(this); + } + } + + @Override + public void visit(WasmReturn expression) { + if (expression.getValue() != null) { + expression.getValue().acceptVisitor(this); + } + } + + @Override + public void visit(WasmUnreachable expression) { + } + + @Override + public void visit(WasmInt32Constant expression) { + } + + @Override + public void visit(WasmInt64Constant expression) { + } + + @Override + public void visit(WasmFloat32Constant expression) { + } + + @Override + public void visit(WasmFloat64Constant expression) { + } + + @Override + public void visit(WasmGetLocal expression) { + } + + @Override + public void visit(WasmSetLocal expression) { + expression.getValue().acceptVisitor(this); + } + + @Override + public void visit(WasmIntBinary expression) { + expression.getFirst().acceptVisitor(this); + expression.getSecond().acceptVisitor(this); + } + + @Override + public void visit(WasmFloatBinary expression) { + expression.getFirst().acceptVisitor(this); + expression.getSecond().acceptVisitor(this); + } + + @Override + public void visit(WasmIntUnary expression) { + expression.getOperand().acceptVisitor(this); + } + + @Override + public void visit(WasmFloatUnary expression) { + expression.getOperand().acceptVisitor(this); + } + + @Override + public void visit(WasmConversion expression) { + expression.getOperand().acceptVisitor(this); + } + + @Override + public void visit(WasmCall expression) { + for (WasmExpression argument : expression.getArguments()) { + argument.acceptVisitor(this); + } + } + + @Override + public void visit(WasmIndirectCall expression) { + expression.getSelector().acceptVisitor(this); + for (WasmExpression argument : expression.getArguments()) { + argument.acceptVisitor(this); + } + } + + @Override + public void visit(WasmDrop expression) { + expression.getOperand().acceptVisitor(this); + } + + @Override + public void visit(WasmLoadInt32 expression) { + expression.getIndex().acceptVisitor(this); + } + + @Override + public void visit(WasmLoadInt64 expression) { + expression.getIndex().acceptVisitor(this); + } + + @Override + public void visit(WasmLoadFloat32 expression) { + expression.getIndex().acceptVisitor(this); + } + + @Override + public void visit(WasmLoadFloat64 expression) { + expression.getIndex().acceptVisitor(this); + } + + @Override + public void visit(WasmStoreInt32 expression) { + expression.getIndex().acceptVisitor(this); + expression.getValue().acceptVisitor(this); + } + + @Override + public void visit(WasmStoreInt64 expression) { + expression.getIndex().acceptVisitor(this); + expression.getValue().acceptVisitor(this); + } + + @Override + public void visit(WasmStoreFloat32 expression) { + expression.getIndex().acceptVisitor(this); + expression.getValue().acceptVisitor(this); + } + + @Override + public void visit(WasmStoreFloat64 expression) { + expression.getIndex().acceptVisitor(this); + expression.getValue().acceptVisitor(this); + } +} diff --git a/core/src/main/java/org/teavm/wasm/model/expression/WasmIndirectCall.java b/core/src/main/java/org/teavm/wasm/model/expression/WasmIndirectCall.java index 5ecb12a1c..0bd881d7d 100644 --- a/core/src/main/java/org/teavm/wasm/model/expression/WasmIndirectCall.java +++ b/core/src/main/java/org/teavm/wasm/model/expression/WasmIndirectCall.java @@ -18,16 +18,16 @@ package org.teavm.wasm.model.expression; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import org.teavm.wasm.model.WasmType; public class WasmIndirectCall extends WasmExpression { - private String typeName; + private List parameterTypes = new ArrayList<>(); + private WasmType returnType; private WasmExpression selector; private List arguments = new ArrayList<>(); - public WasmIndirectCall(String typeName, WasmExpression selector) { - Objects.requireNonNull(typeName); + public WasmIndirectCall(WasmExpression selector) { Objects.requireNonNull(selector); - this.typeName = typeName; this.selector = selector; } @@ -40,13 +40,16 @@ public class WasmIndirectCall extends WasmExpression { this.selector = selector; } - public String getTypeName() { - return typeName; + public List getParameterTypes() { + return parameterTypes; } - public void setTypeName(String typeName) { - Objects.requireNonNull(typeName); - this.typeName = typeName; + public WasmType getReturnType() { + return returnType; + } + + public void setReturnType(WasmType returnType) { + this.returnType = returnType; } public List getArguments() { diff --git a/core/src/main/java/org/teavm/wasm/render/WasmRenderer.java b/core/src/main/java/org/teavm/wasm/render/WasmRenderer.java index 537462a0e..d2544e2f4 100644 --- a/core/src/main/java/org/teavm/wasm/render/WasmRenderer.java +++ b/core/src/main/java/org/teavm/wasm/render/WasmRenderer.java @@ -49,6 +49,8 @@ public class WasmRenderer { public void render(WasmModule module) { visitor.open().append("module"); renderMemory(module); + renderTypes(module); + for (WasmFunction function : module.getFunctions().values()) { if (function.getImportName() == null) { continue; @@ -67,10 +69,15 @@ public class WasmRenderer { } lf().renderExport(function); } + renderTable(module); + if (module.getStartFunction() != null) { + visitor.lf().open().append("start $" + module.getStartFunction().getName()).close().lf(); + } visitor.close().lf(); } public void renderMemory(WasmModule module) { + visitor.lf(); visitor.open().append("memory " + module.getMemorySize()); for (WasmMemorySegment segment : module.getSegments()) { visitor.lf().open().append("segment " + segment.getLength()); @@ -136,16 +143,65 @@ public class WasmRenderer { } private void renderSignature(WasmFunction function) { - if (!function.getParameters().isEmpty()) { - visitor.append(" ").open().append("param"); - for (WasmType type : function.getParameters()) { - visitor.append(" ").append(type); + WasmSignature signature = signatureFromFunction(function); + visitor.append(" ").open().append("type $type" + visitor.getSignatureIndex(signature)).close(); + } + + private WasmSignature signatureFromFunction(WasmFunction function) { + WasmType[] types = new WasmType[function.getParameters().size() + 1]; + types[0] = function.getResult(); + for (int i = 0; i < function.getParameters().size(); ++i) { + types[i + 1] = function.getParameters().get(i); + } + return new WasmSignature(types); + } + + private void renderTypes(WasmModule module) { + WasmSignatureCollector signatureCollector = new WasmSignatureCollector(visitor); + for (WasmFunction function : module.getFunctions().values()) { + visitor.getSignatureIndex(signatureFromFunction(function)); + for (WasmExpression part : function.getBody()) { + part.acceptVisitor(signatureCollector); + } + } + + if (visitor.signatureList.isEmpty()) { + return; + } + + visitor.lf(); + int index = 0; + for (WasmSignature signature : visitor.signatureList) { + visitor.open().append("type $type" + index++ + " "); + visitor.open().append("func"); + if (signature.types.length > 1) { + visitor.append(" ").open().append("param"); + for (int i = 1; i < signature.types.length; ++i) { + visitor.append(" ").append(signature.types[i]); + } + visitor.close(); + } + if (signature.types[0] != null) { + visitor.append(" ").open().append("result "); + visitor.append(signature.types[0]); + visitor.close(); } visitor.close(); + visitor.close(); + visitor.lf(); } - if (function.getResult() != null) { - visitor.append(" ").open().append("result ").append(function.getResult()).close(); + } + + private void renderTable(WasmModule module) { + if (module.getFunctionTable().isEmpty()) { + return; } + + visitor.lf().open().append("table"); + for (WasmFunction function : module.getFunctionTable()) { + visitor.lf().append("$" + function.getName()); + } + visitor.close().lf(); } @Override diff --git a/core/src/main/java/org/teavm/wasm/render/WasmRenderingVisitor.java b/core/src/main/java/org/teavm/wasm/render/WasmRenderingVisitor.java index c3191640e..6a6ac8f00 100644 --- a/core/src/main/java/org/teavm/wasm/render/WasmRenderingVisitor.java +++ b/core/src/main/java/org/teavm/wasm/render/WasmRenderingVisitor.java @@ -15,8 +15,10 @@ */ package org.teavm.wasm.render; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import org.teavm.wasm.model.WasmLocal; @@ -65,6 +67,8 @@ class WasmRenderingVisitor implements WasmExpressionVisitor { private Map blockIdentifiers = new HashMap<>(); private int indentLevel; private boolean lfDeferred; + List signatureList = new ArrayList<>(); + Map signatureMap = new HashMap<>(); void clear() { blockIdentifiers.clear(); @@ -341,7 +345,13 @@ class WasmRenderingVisitor implements WasmExpressionVisitor { @Override public void visit(WasmIndirectCall expression) { - open().append("call_indirect").append(" " + expression.getTypeName()); + WasmType[] types = new WasmType[expression.getParameterTypes().size() + 1]; + types[0] = expression.getReturnType(); + for (int i = 0; i < expression.getParameterTypes().size(); ++i) { + types[i + 1] = expression.getParameterTypes().get(i); + } + + open().append("call_indirect").append(" $type" + getSignatureIndex(new WasmSignature(types))); line(expression.getSelector()); for (WasmExpression argument : expression.getArguments()) { line(argument); @@ -349,6 +359,13 @@ class WasmRenderingVisitor implements WasmExpressionVisitor { close(); } + int getSignatureIndex(WasmSignature signature) { + return signatureMap.computeIfAbsent(signature, key -> { + signatureList.add(key); + return signatureMap.size(); + }); + } + @Override public void visit(WasmDrop expression) { append(expression.getOperand()); diff --git a/core/src/main/java/org/teavm/wasm/render/WasmSignature.java b/core/src/main/java/org/teavm/wasm/render/WasmSignature.java new file mode 100644 index 000000000..2a9d22a60 --- /dev/null +++ b/core/src/main/java/org/teavm/wasm/render/WasmSignature.java @@ -0,0 +1,44 @@ +/* + * 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.wasm.render; + +import java.util.Arrays; +import org.teavm.wasm.model.WasmType; + +class WasmSignature { + WasmType[] types; + + public WasmSignature(WasmType[] types) { + this.types = types; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + WasmSignature that = (WasmSignature) o; + return Arrays.equals(types, that.types); + } + + @Override + public int hashCode() { + return Arrays.hashCode(types); + } +} diff --git a/core/src/main/java/org/teavm/wasm/render/WasmSignatureCollector.java b/core/src/main/java/org/teavm/wasm/render/WasmSignatureCollector.java new file mode 100644 index 000000000..17d12df7a --- /dev/null +++ b/core/src/main/java/org/teavm/wasm/render/WasmSignatureCollector.java @@ -0,0 +1,39 @@ +/* + * 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.wasm.render; + +import org.teavm.wasm.model.WasmType; +import org.teavm.wasm.model.expression.WasmDefaultExpressionVisitor; +import org.teavm.wasm.model.expression.WasmIndirectCall; + +class WasmSignatureCollector extends WasmDefaultExpressionVisitor { + WasmRenderingVisitor renderingVisitor; + + public WasmSignatureCollector(WasmRenderingVisitor renderingVisitor) { + this.renderingVisitor = renderingVisitor; + } + + @Override + public void visit(WasmIndirectCall expression) { + WasmType[] types = new WasmType[expression.getParameterTypes().size() + 1]; + types[0] = expression.getReturnType(); + for (int i = 0; i < expression.getParameterTypes().size(); ++i) { + types[i + 1] = expression.getParameterTypes().get(i); + } + + renderingVisitor.getSignatureIndex(new WasmSignature(types)); + } +} diff --git a/interop/core/src/main/java/org/teavm/interop/Structure.java b/interop/core/src/main/java/org/teavm/interop/Structure.java index 3910f19f4..bd9507413 100644 --- a/interop/core/src/main/java/org/teavm/interop/Structure.java +++ b/interop/core/src/main/java/org/teavm/interop/Structure.java @@ -16,11 +16,11 @@ package org.teavm.interop; public class Structure { - public native T cast(); + public final native T cast(); - public native Address toAddress(); + public final native Address toAddress(); - public native int sizeOf(Class type); + public final native int sizeOf(Class type); - public native T add(T base, int offset); + public final native T add(T base, int offset); }