From 675abe87404895db6979fd7a7ce8e26686c66e1c Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 8 Aug 2016 20:26:16 +0300 Subject: [PATCH] Start supporting classes in WASM --- .../java/org/teavm/runtime/Allocator.java | 34 +++ .../java/org/teavm/runtime/RuntimeClass.java | 24 ++ .../java/org/teavm/runtime/RuntimeObject.java | 22 ++ .../src/main/java/org/teavm/wasm/Example.java | 13 + .../main/java/org/teavm/wasm/WasmTarget.java | 126 ++++++++- .../wasm/generate/WasmClassGenerator.java | 155 +++++++++++ .../wasm/generate/WasmGenerationContext.java | 13 + .../wasm/generate/WasmGenerationVisitor.java | 257 +++++++++++++++++- .../teavm/wasm/generate/WasmGenerator.java | 7 +- .../org/teavm/wasm/generate/WasmMangling.java | 4 + .../teavm/wasm/model/WasmMemorySegment.java | 51 ++++ .../java/org/teavm/wasm/model/WasmModule.java | 23 ++ .../org/teavm/wasm/render/WasmRenderer.java | 31 +++ .../wasm/render/WasmRenderingVisitor.java | 108 +++++++- .../main/java/org/teavm/interop/Address.java | 60 ++++ .../java/org/teavm/interop/StaticInit.java | 26 ++ .../java/org/teavm/interop/Structure.java | 26 ++ 17 files changed, 962 insertions(+), 18 deletions(-) create mode 100644 core/src/main/java/org/teavm/runtime/Allocator.java create mode 100644 core/src/main/java/org/teavm/runtime/RuntimeClass.java create mode 100644 core/src/main/java/org/teavm/runtime/RuntimeObject.java create mode 100644 core/src/main/java/org/teavm/wasm/generate/WasmClassGenerator.java create mode 100644 core/src/main/java/org/teavm/wasm/model/WasmMemorySegment.java create mode 100644 interop/core/src/main/java/org/teavm/interop/Address.java create mode 100644 interop/core/src/main/java/org/teavm/interop/StaticInit.java create mode 100644 interop/core/src/main/java/org/teavm/interop/Structure.java diff --git a/core/src/main/java/org/teavm/runtime/Allocator.java b/core/src/main/java/org/teavm/runtime/Allocator.java new file mode 100644 index 000000000..47720ace5 --- /dev/null +++ b/core/src/main/java/org/teavm/runtime/Allocator.java @@ -0,0 +1,34 @@ +/* + * 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.runtime; + +import org.teavm.interop.Address; +import org.teavm.interop.StaticInit; + +@StaticInit +public final class Allocator { + private static Address address = initialize(); + + private static native Address initialize(); + + public static Address allocate(RuntimeClass tag) { + Address result = address; + address = result.add(tag.size); + RuntimeObject object = result.toStructure(); + object.classInfo = tag; + return result; + } +} diff --git a/core/src/main/java/org/teavm/runtime/RuntimeClass.java b/core/src/main/java/org/teavm/runtime/RuntimeClass.java new file mode 100644 index 000000000..6b187c754 --- /dev/null +++ b/core/src/main/java/org/teavm/runtime/RuntimeClass.java @@ -0,0 +1,24 @@ +/* + * 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.runtime; + +import org.teavm.interop.Structure; + +public class RuntimeClass extends Structure { + public static int INITIALIZED = 1; + public int size; + public int flags; +} diff --git a/core/src/main/java/org/teavm/runtime/RuntimeObject.java b/core/src/main/java/org/teavm/runtime/RuntimeObject.java new file mode 100644 index 000000000..0145013b6 --- /dev/null +++ b/core/src/main/java/org/teavm/runtime/RuntimeObject.java @@ -0,0 +1,22 @@ +/* + * 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.runtime; + +import org.teavm.interop.Structure; + +public class RuntimeObject extends Structure { + public RuntimeClass classInfo; +} diff --git a/core/src/main/java/org/teavm/wasm/Example.java b/core/src/main/java/org/teavm/wasm/Example.java index 091263b4c..9ad86e29d 100644 --- a/core/src/main/java/org/teavm/wasm/Example.java +++ b/core/src/main/java/org/teavm/wasm/Example.java @@ -30,5 +30,18 @@ public final class Example { b = c; WasmRuntime.print(a); } + WasmRuntime.print(new A(2).getValue() + new A(3).getValue()); + } + + private static class A { + private int value; + + public A(int value) { + this.value = value; + } + + public int getValue() { + return value; + } } } diff --git a/core/src/main/java/org/teavm/wasm/WasmTarget.java b/core/src/main/java/org/teavm/wasm/WasmTarget.java index b4f1d5f5a..622ebb261 100644 --- a/core/src/main/java/org/teavm/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/wasm/WasmTarget.java @@ -26,13 +26,22 @@ import java.util.HashSet; import java.util.List; 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.CallLocation; import org.teavm.model.ClassHolder; +import org.teavm.model.ClassReader; import org.teavm.model.ElementModifier; 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.runtime.Allocator; +import org.teavm.runtime.RuntimeClass; import org.teavm.vm.BuildTarget; import org.teavm.vm.TeaVM; import org.teavm.vm.TeaVMBuilder; @@ -40,11 +49,25 @@ import org.teavm.vm.TeaVMEntryPoint; import org.teavm.vm.TeaVMTarget; import org.teavm.vm.TeaVMTargetController; import org.teavm.vm.spi.TeaVMHostExtension; +import org.teavm.wasm.generate.WasmClassGenerator; import org.teavm.wasm.generate.WasmGenerationContext; import org.teavm.wasm.generate.WasmGenerator; import org.teavm.wasm.generate.WasmMangling; import org.teavm.wasm.model.WasmFunction; import org.teavm.wasm.model.WasmModule; +import org.teavm.wasm.model.WasmType; +import org.teavm.wasm.model.expression.WasmBlock; +import org.teavm.wasm.model.expression.WasmBranch; +import org.teavm.wasm.model.expression.WasmCall; +import org.teavm.wasm.model.expression.WasmExpression; +import org.teavm.wasm.model.expression.WasmInt32Constant; +import org.teavm.wasm.model.expression.WasmInt32Subtype; +import org.teavm.wasm.model.expression.WasmIntBinary; +import org.teavm.wasm.model.expression.WasmIntBinaryOperation; +import org.teavm.wasm.model.expression.WasmIntType; +import org.teavm.wasm.model.expression.WasmLoadInt32; +import org.teavm.wasm.model.expression.WasmReturn; +import org.teavm.wasm.model.expression.WasmStoreInt32; import org.teavm.wasm.render.WasmRenderer; import org.teavm.wasm.runtime.WasmRuntime; @@ -76,19 +99,46 @@ public class WasmTarget implements TeaVMTarget { MethodReference method = new MethodReference(WasmRuntime.class, "remainder", type, type, type); dependencyChecker.linkMethod(method, null).use(); } + + dependencyChecker.linkMethod(new MethodReference(Allocator.class, "allocate", + RuntimeClass.class, Address.class), null).use(); + dependencyChecker.linkMethod(new MethodReference(Allocator.class, "", void.class), null).use(); } @Override public void emit(ListableClassHolderSource classes, OutputStream output, BuildTarget buildTarget) { - Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), new HashSet<>(), new HashSet<>()); + int address = 256; + + WasmClassGenerator classGenerator = new WasmClassGenerator(classes, address); + for (String className : classes.getClassNames()) { + classGenerator.addClass(className); + if (controller.wasCancelled()) { + return; + } + } + address = classGenerator.getAddress(); + + Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), new HashSet<>(), + new HashSet<>()); WasmGenerationContext context = new WasmGenerationContext(classes); - WasmGenerator generator = new WasmGenerator(decompiler, classes, context); + WasmGenerator generator = new WasmGenerator(decompiler, classes, context, classGenerator); WasmModule module = new WasmModule(); + module.setMemorySize(64); + for (String className : classes.getClassNames()) { ClassHolder cls = classes.get(className); for (MethodHolder method : cls.getMethods()) { + if (method.getOwnerName().equals(Allocator.class.getName()) + && method.getName().equals("initialize")) { + continue; + } + if (method.hasModifier(ElementModifier.NATIVE)) { + if (method.getOwnerName().equals(Structure.class.getName()) + || method.getOwnerName().equals(Address.class.getName())) { + continue; + } if (context.getImportedMethod(method.getReference()) == null) { CallLocation location = new CallLocation(method.getReference()); controller.getDiagnostics().error(location, "Method {{m0}} is native but " @@ -107,6 +157,27 @@ public class WasmTarget implements TeaVMTarget { } } + renderAllocatorInit(module, address); + renderClinit(classes, classGenerator, module); + if (controller.wasCancelled()) { + return; + } + + WasmFunction initFunction = new WasmFunction("__start__"); + classGenerator.contributeToInitializer(initFunction.getBody()); + for (String className : classes.getClassNames()) { + ClassReader cls = classes.get(className); + if (cls.getAnnotations().get(StaticInit.class.getName()) == null) { + continue; + } + MethodReader clinit = cls.getMethod(new MethodDescriptor("", void.class)); + if (clinit == null) { + continue; + } + initFunction.getBody().add(new WasmCall(WasmMangling.mangleMethod(clinit.getReference()))); + } + module.add(initFunction); + for (TeaVMEntryPoint entryPoint : controller.getEntryPoints().values()) { String mangledName = WasmMangling.mangleMethod(entryPoint.getReference()); WasmFunction function = module.getFunctions().get(mangledName); @@ -127,6 +198,57 @@ public class WasmTarget implements TeaVMTarget { } } + private void renderClinit(ListableClassReaderSource classes, WasmClassGenerator classGenerator, + WasmModule module) { + for (String className : classes.getClassNames()) { + if (classGenerator.isStructure(className)) { + continue; + } + + ClassReader cls = classes.get(className); + MethodReader method = cls.getMethod(new MethodDescriptor("", void.class)); + if (method == null) { + continue; + } + + WasmFunction initFunction = new WasmFunction(WasmMangling.mangleInitializer(className)); + module.add(initFunction); + + WasmBlock block = new WasmBlock(false); + + int index = classGenerator.getClassPointer(className); + WasmExpression initFlag = new WasmLoadInt32(4, new WasmInt32Constant(index), WasmInt32Subtype.INT32); + initFlag = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.AND, initFlag, + new WasmInt32Constant(RuntimeClass.INITIALIZED)); + block.getBody().add(new WasmBranch(initFlag, block)); + initFunction.getBody().add(block); + + initFlag = new WasmLoadInt32(4, new WasmInt32Constant(index), WasmInt32Subtype.INT32); + initFlag = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.OR, initFlag, + new WasmInt32Constant(RuntimeClass.INITIALIZED)); + block.getBody().add(new WasmStoreInt32(4, new WasmInt32Constant(index), initFlag, + WasmInt32Subtype.INT32)); + + if (method != null) { + block.getBody().add(new WasmCall(WasmMangling.mangleMethod(method.getReference()))); + } + + if (controller.wasCancelled()) { + break; + } + } + } + + private void renderAllocatorInit(WasmModule module, int address) { + address = (((address - 1) / 4096) + 1) * 4096; + + WasmFunction function = new WasmFunction(WasmMangling.mangleMethod(new MethodReference( + Allocator.class, "initialize", Address.class))); + function.setResult(WasmType.INT32); + function.getBody().add(new WasmReturn(new WasmInt32Constant(address))); + module.add(function); + } + 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 new file mode 100644 index 000000000..3239ca45b --- /dev/null +++ b/core/src/main/java/org/teavm/wasm/generate/WasmClassGenerator.java @@ -0,0 +1,155 @@ +/* + * 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.generate; + +import com.carrotsearch.hppc.ObjectIntMap; +import com.carrotsearch.hppc.ObjectIntOpenHashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.teavm.interop.Address; +import org.teavm.interop.Structure; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.ElementModifier; +import org.teavm.model.FieldReader; +import org.teavm.model.FieldReference; +import org.teavm.model.ValueType; +import org.teavm.wasm.model.expression.WasmExpression; +import org.teavm.wasm.model.expression.WasmInt32Constant; +import org.teavm.wasm.model.expression.WasmInt32Subtype; +import org.teavm.wasm.model.expression.WasmStoreInt32; + +public class WasmClassGenerator { + private ClassReaderSource classSource; + private int address; + private Map binaryDataMap = new LinkedHashMap<>(); + + public WasmClassGenerator(ClassReaderSource classSource, int address) { + this.classSource = classSource; + this.address = address; + } + + public void addClass(String className) { + if (binaryDataMap.containsKey(className)) { + return; + } + + ClassReader cls = classSource.get(className); + ClassBinaryData binaryData = new ClassBinaryData(); + binaryDataMap.put(className, binaryData); + + calculateLayout(cls, binaryData); + if (binaryData.start < 0) { + return; + } + binaryData.start = align(address, 4); + binaryData.end = binaryData.start + 8; + + address = binaryData.end; + } + + public int getAddress() { + return address; + } + + public void contributeToInitializer(List initializer) { + for (ClassBinaryData binaryData : binaryDataMap.values()) { + if (binaryData.start < 0) { + continue; + } + WasmExpression index = new WasmInt32Constant(binaryData.start); + WasmExpression size = new WasmInt32Constant(binaryData.size); + initializer.add(new WasmStoreInt32(4, index, size, WasmInt32Subtype.INT32)); + } + } + + public int getClassPointer(String className) { + ClassBinaryData data = binaryDataMap.get(className); + return data.start; + } + + public int getFieldOffset(FieldReference field) { + ClassBinaryData data = binaryDataMap.get(field.getClassName()); + return data.fieldLayout.get(field.getFieldName()); + } + + public boolean isStructure(String className) { + ClassBinaryData data = binaryDataMap.get(className); + return data.start < 0; + } + + private void calculateLayout(ClassReader cls, ClassBinaryData data) { + if (cls.getName().equals(Structure.class.getName()) || cls.getName().equals(Address.class.getName())) { + data.size = 0; + data.start = -1; + } else if (cls.getParent() != null && !cls.getParent().equals(cls.getName())) { + addClass(cls.getParent()); + ClassBinaryData parentData = binaryDataMap.get(cls.getParent()); + data.size = parentData.size; + if (parentData.start == -1) { + data.start = -1; + } + } else { + data.size = 4; + } + + for (FieldReader field : cls.getFields()) { + int desiredAlignment = getDesiredAlignment(field.getType()); + if (field.hasModifier(ElementModifier.STATIC)) { + int offset = align(address, desiredAlignment); + data.fieldLayout.put(field.getName(), offset); + address = offset + desiredAlignment; + } else { + int offset = align(data.size, desiredAlignment); + data.fieldLayout.put(field.getName(), offset); + data.size = offset + desiredAlignment; + } + } + } + + private static int align(int base, int alignment) { + return ((base - 1) / alignment + 1) * alignment; + } + + private int getDesiredAlignment(ValueType type) { + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + case BYTE: + return 1; + case SHORT: + case CHARACTER: + return 2; + case INTEGER: + case FLOAT: + return 4; + case LONG: + case DOUBLE: + return 8; + } + } + return 4; + } + + private class ClassBinaryData { + int start; + int end; + + 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 e97c8a3e5..b7c844fc6 100644 --- a/core/src/main/java/org/teavm/wasm/generate/WasmGenerationContext.java +++ b/core/src/main/java/org/teavm/wasm/generate/WasmGenerationContext.java @@ -22,8 +22,11 @@ import org.teavm.model.AnnotationReader; import org.teavm.model.AnnotationValue; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; +import org.teavm.model.FieldReader; +import org.teavm.model.FieldReference; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; public class WasmGenerationContext { private ClassReaderSource classSource; @@ -58,6 +61,16 @@ public class WasmGenerationContext { }); } + public ClassReaderSource getClassSource() { + return classSource; + } + + public ValueType getFieldType(FieldReference fieldReference) { + ClassReader cls = classSource.get(fieldReference.getClassName()); + FieldReader field = cls.getField(fieldReference.getFieldName()); + return field.getType(); + } + 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 2f5704f54..00d5678db 100644 --- a/core/src/main/java/org/teavm/wasm/generate/WasmGenerationVisitor.java +++ b/core/src/main/java/org/teavm/wasm/generate/WasmGenerationVisitor.java @@ -59,9 +59,17 @@ import org.teavm.ast.UnaryExpr; import org.teavm.ast.UnwrapArrayExpr; import org.teavm.ast.VariableExpr; import org.teavm.ast.WhileStatement; +import org.teavm.interop.Address; +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.runtime.Allocator; +import org.teavm.runtime.RuntimeClass; import org.teavm.wasm.model.WasmFunction; import org.teavm.wasm.model.WasmLocal; +import org.teavm.wasm.model.WasmType; import org.teavm.wasm.model.expression.WasmBlock; import org.teavm.wasm.model.expression.WasmBranch; import org.teavm.wasm.model.expression.WasmBreak; @@ -77,17 +85,28 @@ 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.WasmInt32Constant; +import org.teavm.wasm.model.expression.WasmInt32Subtype; import org.teavm.wasm.model.expression.WasmInt64Constant; +import org.teavm.wasm.model.expression.WasmInt64Subtype; import org.teavm.wasm.model.expression.WasmIntBinary; import org.teavm.wasm.model.expression.WasmIntBinaryOperation; import org.teavm.wasm.model.expression.WasmIntType; +import org.teavm.wasm.model.expression.WasmLoadFloat32; +import org.teavm.wasm.model.expression.WasmLoadFloat64; +import org.teavm.wasm.model.expression.WasmLoadInt32; +import org.teavm.wasm.model.expression.WasmLoadInt64; import org.teavm.wasm.model.expression.WasmReturn; import org.teavm.wasm.model.expression.WasmSetLocal; +import org.teavm.wasm.model.expression.WasmStoreFloat32; +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.runtime.WasmRuntime; class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { private WasmGenerationContext context; + private WasmClassGenerator classGenerator; private WasmFunction function; private int firstVariable; private IdentifiedStatement currentContinueTarget; @@ -97,8 +116,10 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { private Set usedBlocks = new HashSet<>(); WasmExpression result; - WasmGenerationVisitor(WasmGenerationContext context, WasmFunction function, int firstVariable) { + WasmGenerationVisitor(WasmGenerationContext context, WasmClassGenerator classGenerator, + WasmFunction function, int firstVariable) { this.context = context; + this.classGenerator = classGenerator; this.function = function; this.firstVariable = firstVariable; } @@ -350,11 +371,48 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { WasmLocal local = function.getLocalVariables().get(varExpr.getIndex() - firstVariable); statement.getRightValue().acceptVisitor(this); result = new WasmSetLocal(local, result); + } else if (left instanceof QualificationExpr) { + QualificationExpr lhs = (QualificationExpr) left; + storeField(lhs.getQualified(), lhs.getField(), statement.getRightValue()); } else { throw new UnsupportedOperationException("This expression is not supported yet"); } } + private void storeField(Expr qualified, FieldReference field, Expr value) { + WasmExpression address = getAddress(qualified, field); + ValueType type = context.getFieldType(field); + value.acceptVisitor(this); + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + case BYTE: + result = new WasmStoreInt32(1, address, result, WasmInt32Subtype.INT8); + break; + case SHORT: + result = new WasmStoreInt32(2, address, result, WasmInt32Subtype.INT16); + break; + case CHARACTER: + result = new WasmStoreInt32(2, address, result, WasmInt32Subtype.UINT16); + break; + case INTEGER: + result = new WasmStoreInt32(4, address, result, WasmInt32Subtype.INT32); + break; + case LONG: + result = new WasmStoreInt64(8, address, result, WasmInt64Subtype.INT64); + break; + case FLOAT: + result = new WasmStoreFloat32(4, address, result); + break; + case DOUBLE: + result = new WasmStoreFloat64(8, address, result); + break; + } + } else { + result = new WasmStoreInt32(4, address, result, WasmInt32Subtype.INT32); + } + } + @Override public void visit(ConditionalExpr expr) { expr.getCondition().acceptVisitor(this); @@ -371,7 +429,9 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { WasmBlock block = new WasmBlock(false); for (Statement part : statement.getSequence()) { part.acceptVisitor(this); - block.getBody().add(result); + if (result != null) { + block.getBody().add(result); + } } result = block; } @@ -399,11 +459,15 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { WasmConditional conditional = new WasmConditional(result); for (Statement part : statement.getConsequent()) { part.acceptVisitor(this); - conditional.getThenBlock().getBody().add(result); + if (result != null) { + conditional.getThenBlock().getBody().add(result); + } } for (Statement part : statement.getAlternative()) { part.acceptVisitor(this); - conditional.getElseBlock().getBody().add(result); + if (result != null) { + conditional.getElseBlock().getBody().add(result); + } } result = conditional; } @@ -479,7 +543,9 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { for (Statement part : statement.getBody()) { part.acceptVisitor(this); - loop.getBody().add(result); + if (result != null) { + loop.getBody().add(result); + } } loop.getBody().add(new WasmBreak(loop)); @@ -498,6 +564,11 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { @Override public void visit(InvocationExpr expr) { + if (expr.getMethod().getClassName().equals(Address.class.getName())) { + generateAddressInvocation(expr); + return; + } + if (expr.getType() == InvocationType.STATIC || expr.getType() == InvocationType.SPECIAL) { String methodName = WasmMangling.mangleMethod(expr.getMethod()); @@ -513,6 +584,114 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { } } + private void generateAddressInvocation(InvocationExpr expr) { + switch (expr.getMethod().getName()) { + case "toInt": + case "toStructure": + expr.getArguments().get(0).acceptVisitor(this); + break; + case "toLong": + expr.getArguments().get(0).acceptVisitor(this); + result = new WasmConversion(WasmType.INT32, WasmType.INT64, false, result); + break; + case "fromInt": + expr.getArguments().get(0).acceptVisitor(this); + break; + case "fromLong": + expr.getArguments().get(0).acceptVisitor(this); + result = new WasmConversion(WasmType.INT64, WasmType.INT32, false, result); + break; + case "add": { + expr.getArguments().get(0).acceptVisitor(this); + WasmExpression base = result; + expr.getArguments().get(1).acceptVisitor(this); + WasmExpression offset = result; + if (expr.getMethod().parameterType(0) == ValueType.LONG) { + offset = new WasmConversion(WasmType.INT64, WasmType.INT32, false, offset); + } + result = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD, base, offset); + break; + } + case "getByte": + expr.getArguments().get(0).acceptVisitor(this); + result = new WasmLoadInt32(1, result, WasmInt32Subtype.INT8); + break; + case "getShort": + expr.getArguments().get(0).acceptVisitor(this); + result = new WasmLoadInt32(2, result, WasmInt32Subtype.INT16); + break; + case "getChar": + expr.getArguments().get(0).acceptVisitor(this); + result = new WasmLoadInt32(2, result, WasmInt32Subtype.UINT16); + break; + case "getInt": + expr.getArguments().get(0).acceptVisitor(this); + result = new WasmLoadInt32(4, result, WasmInt32Subtype.INT32); + break; + case "getLong": + expr.getArguments().get(0).acceptVisitor(this); + result = new WasmLoadInt64(8, result, WasmInt64Subtype.INT64); + break; + case "getFloat": + expr.getArguments().get(0).acceptVisitor(this); + result = new WasmLoadFloat32(4, result); + break; + case "getDouble": + expr.getArguments().get(0).acceptVisitor(this); + result = new WasmLoadFloat64(8, result); + break; + case "putByte": { + expr.getArguments().get(0).acceptVisitor(this); + WasmExpression address = result; + expr.getArguments().get(1).acceptVisitor(this); + result = new WasmStoreInt32(1, address, result, WasmInt32Subtype.INT8); + break; + } + case "putShort": { + expr.getArguments().get(0).acceptVisitor(this); + WasmExpression address = result; + expr.getArguments().get(1).acceptVisitor(this); + result = new WasmStoreInt32(2, address, result, WasmInt32Subtype.INT16); + break; + } + case "putChar": { + expr.getArguments().get(0).acceptVisitor(this); + WasmExpression address = result; + expr.getArguments().get(1).acceptVisitor(this); + result = new WasmStoreInt32(2, address, result, WasmInt32Subtype.UINT16); + break; + } + case "putInt": { + expr.getArguments().get(0).acceptVisitor(this); + WasmExpression address = result; + expr.getArguments().get(1).acceptVisitor(this); + result = new WasmStoreInt32(4, address, result, WasmInt32Subtype.INT32); + break; + } + case "putLong": { + expr.getArguments().get(0).acceptVisitor(this); + WasmExpression address = result; + expr.getArguments().get(1).acceptVisitor(this); + result = new WasmStoreInt64(8, address, result, WasmInt64Subtype.INT64); + break; + } + case "putFloat": { + expr.getArguments().get(0).acceptVisitor(this); + WasmExpression address = result; + expr.getArguments().get(1).acceptVisitor(this); + result = new WasmStoreFloat32(4, address, result); + break; + } + case "putDouble": { + expr.getArguments().get(0).acceptVisitor(this); + WasmExpression address = result; + expr.getArguments().get(1).acceptVisitor(this); + result = new WasmStoreFloat64(8, address, result); + break; + } + } + } + @Override public void visit(BlockStatement statement) { WasmBlock block = new WasmBlock(false); @@ -523,7 +702,9 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { for (Statement part : statement.getBody()) { part.acceptVisitor(this); - block.getBody().add(result); + if (result != null) { + block.getBody().add(result); + } } if (statement.getId() != null) { @@ -535,6 +716,48 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { @Override public void visit(QualificationExpr expr) { + WasmExpression address = getAddress(expr.getQualified(), expr.getField()); + + ValueType type = context.getFieldType(expr.getField()); + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + case BYTE: + result = new WasmLoadInt32(1, address, WasmInt32Subtype.INT8); + break; + case SHORT: + result = new WasmLoadInt32(2, address, WasmInt32Subtype.INT16); + break; + case CHARACTER: + result = new WasmLoadInt32(2, address, WasmInt32Subtype.UINT16); + break; + case INTEGER: + result = new WasmLoadInt32(4, address, WasmInt32Subtype.INT32); + break; + case LONG: + result = new WasmLoadInt64(8, address, WasmInt64Subtype.INT64); + break; + case FLOAT: + result = new WasmLoadFloat32(4, address); + break; + case DOUBLE: + result = new WasmLoadFloat64(8, address); + break; + } + } else { + result = new WasmLoadInt32(4, address, WasmInt32Subtype.INT32); + } + } + + private WasmExpression getAddress(Expr qualified, FieldReference field) { + int offset = classGenerator.getFieldOffset(field); + if (qualified == null) { + return new WasmInt32Constant(offset); + } else { + qualified.acceptVisitor(this); + return new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD, result, + new WasmInt32Constant(offset)); + } } @Override @@ -550,6 +773,12 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { @Override public void visit(NewExpr expr) { + int tag = classGenerator.getClassPointer(expr.getConstructedClass()); + String allocName = WasmMangling.mangleMethod(new MethodReference(Allocator.class, "allocate", + RuntimeClass.class, Address.class)); + WasmCall call = new WasmCall(allocName); + call.getArguments().add(new WasmInt32Constant(tag)); + result = call; } @Override @@ -598,6 +827,22 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { @Override public void visit(InitClassStatement statement) { + if (hasClinit(statement.getClassName())) { + result = new WasmCall(WasmMangling.mangleInitializer(statement.getClassName())); + } else { + result = null; + } + } + + private boolean hasClinit(String className) { + if (classGenerator.isStructure(className)) { + return false; + } + ClassReader cls = context.getClassSource().get(className); + if (cls == null) { + return false; + } + return cls.getMethod(new MethodDescriptor("", ValueType.VOID)) != null; } @Override diff --git a/core/src/main/java/org/teavm/wasm/generate/WasmGenerator.java b/core/src/main/java/org/teavm/wasm/generate/WasmGenerator.java index 5a30bd219..29206d824 100644 --- a/core/src/main/java/org/teavm/wasm/generate/WasmGenerator.java +++ b/core/src/main/java/org/teavm/wasm/generate/WasmGenerator.java @@ -35,11 +35,14 @@ public class WasmGenerator { private Decompiler decompiler; private ClassHolderSource classSource; private WasmGenerationContext context; + private WasmClassGenerator classGenerator; - public WasmGenerator(Decompiler decompiler, ClassHolderSource classSource, WasmGenerationContext context) { + public WasmGenerator(Decompiler decompiler, ClassHolderSource classSource, WasmGenerationContext context, + WasmClassGenerator classGenerator) { this.decompiler = decompiler; this.classSource = classSource; this.context = context; + this.classGenerator = classGenerator; } public WasmFunction generate(MethodReference methodReference) { @@ -79,7 +82,7 @@ public class WasmGenerator { function.setResult(WasmGeneratorUtil.mapType(methodReference.getReturnType())); } - WasmGenerationVisitor visitor = new WasmGenerationVisitor(context, function, firstVariable); + WasmGenerationVisitor visitor = new WasmGenerationVisitor(context, classGenerator, function, firstVariable); methodAst.getBody().acceptVisitor(visitor); function.getBody().add(visitor.result); 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 e1ce17365..e1f406869 100644 --- a/core/src/main/java/org/teavm/wasm/generate/WasmMangling.java +++ b/core/src/main/java/org/teavm/wasm/generate/WasmMangling.java @@ -36,6 +36,10 @@ public final class WasmMangling { return sb.toString(); } + public static String mangleInitializer(String className) { + return "clinit$" + mangleString(className); + } + private static String mangleString(String string) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < string.length(); ++i) { diff --git a/core/src/main/java/org/teavm/wasm/model/WasmMemorySegment.java b/core/src/main/java/org/teavm/wasm/model/WasmMemorySegment.java new file mode 100644 index 000000000..ff96a4fbc --- /dev/null +++ b/core/src/main/java/org/teavm/wasm/model/WasmMemorySegment.java @@ -0,0 +1,51 @@ +/* + * 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; + +import java.util.Arrays; + +public class WasmMemorySegment { + private int offset; + private byte[] data = new byte[0]; + + public int getOffset() { + return offset; + } + + public void setOffset(int offset) { + this.offset = offset; + } + + public byte[] getData() { + return data.clone(); + } + + public void setData(byte[] data) { + this.data = data.clone(); + } + + public int getLength() { + return data.length; + } + + public void setLength(int value) { + data = Arrays.copyOf(data, value); + } + + public byte[] getData(int offset, int size) { + return Arrays.copyOfRange(data, offset, size); + } +} diff --git a/core/src/main/java/org/teavm/wasm/model/WasmModule.java b/core/src/main/java/org/teavm/wasm/model/WasmModule.java index 707e92b5e..5fb9629e7 100644 --- a/core/src/main/java/org/teavm/wasm/model/WasmModule.java +++ b/core/src/main/java/org/teavm/wasm/model/WasmModule.java @@ -22,9 +22,12 @@ import java.util.List; import java.util.Map; public class WasmModule { + private int memorySize; + private List segments = new ArrayList<>(); private Map functions = new LinkedHashMap<>(); private Map readonlyFunctions = Collections.unmodifiableMap(functions); private List functionTable = new ArrayList<>(); + private WasmFunction startFunction; public void add(WasmFunction function) { if (functions.containsKey(function.getName())) { @@ -44,4 +47,24 @@ public class WasmModule { public List getFunctionTable() { return functionTable; } + + public List getSegments() { + return segments; + } + + public int getMemorySize() { + return memorySize; + } + + public void setMemorySize(int memorySize) { + this.memorySize = memorySize; + } + + public WasmFunction getStartFunction() { + return startFunction; + } + + public void setStartFunction(WasmFunction startFunction) { + this.startFunction = startFunction; + } } 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 ba3c9fea0..537462a0e 100644 --- a/core/src/main/java/org/teavm/wasm/render/WasmRenderer.java +++ b/core/src/main/java/org/teavm/wasm/render/WasmRenderer.java @@ -18,6 +18,7 @@ package org.teavm.wasm.render; import java.util.List; import org.teavm.wasm.model.WasmFunction; import org.teavm.wasm.model.WasmLocal; +import org.teavm.wasm.model.WasmMemorySegment; import org.teavm.wasm.model.WasmModule; import org.teavm.wasm.model.WasmType; import org.teavm.wasm.model.expression.WasmExpression; @@ -47,6 +48,7 @@ public class WasmRenderer { public void render(WasmModule module) { visitor.open().append("module"); + renderMemory(module); for (WasmFunction function : module.getFunctions().values()) { if (function.getImportName() == null) { continue; @@ -68,6 +70,35 @@ public class WasmRenderer { visitor.close().lf(); } + public void renderMemory(WasmModule module) { + visitor.open().append("memory " + module.getMemorySize()); + for (WasmMemorySegment segment : module.getSegments()) { + visitor.lf().open().append("segment " + segment.getLength()); + visitor.indent(); + for (int i = 0; i < segment.getLength(); i += 256) { + visitor.lf().append("\""); + byte[] part = segment.getData(i, Math.max(segment.getLength(), i + 256) - i); + StringBuilder sb = new StringBuilder(); + for (int j = 0; j < part.length; ++j) { + int b = part[j] << 24 >>> 24; + if (b < ' ' || b > 126) { + sb.append("\\0x" + Character.forDigit(b >> 4, 16) + Character.forDigit(b & 0xF, 16)); + } else if (b == '\\') { + sb.append("\\\\"); + } else if (b == '"') { + sb.append("\\\""); + } else { + sb.append((char) b); + } + } + visitor.append(sb.toString()).append("\""); + } + visitor.outdent(); + visitor.close(); + } + visitor.close().lf(); + } + public void renderImport(WasmFunction function) { String importModule = function.getImportModule(); if (importModule == null) { 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 e37f96399..b3f539fa7 100644 --- a/core/src/main/java/org/teavm/wasm/render/WasmRenderingVisitor.java +++ b/core/src/main/java/org/teavm/wasm/render/WasmRenderingVisitor.java @@ -356,42 +356,134 @@ class WasmRenderingVisitor implements WasmExpressionVisitor { @Override public void visit(WasmLoadInt32 expression) { - + open(); + switch (expression.getConvertFrom()) { + case INT8: + append("i32.load8_s"); + break; + case UINT8: + append("i32.load8_u"); + break; + case INT16: + append("i32.load16_s"); + break; + case UINT16: + append("i32.load16_u"); + break; + case INT32: + append("i32.load"); + break; + } + append(" align=" + expression.getAlignment()); + line(expression.getIndex()); + close(); } @Override public void visit(WasmLoadInt64 expression) { - + open(); + switch (expression.getConvertFrom()) { + case INT8: + append("i64.load8_s"); + break; + case UINT8: + append("i64.load8_u"); + break; + case INT16: + append("i64.load16_s"); + break; + case UINT16: + append("i64.load16_u"); + break; + case INT32: + append("i64.load32_s"); + break; + case UINT32: + append("i64.load32_u"); + break; + case INT64: + append("i64.load"); + break; + } + append(" align=" + expression.getAlignment()); + line(expression.getIndex()); + close(); } @Override public void visit(WasmLoadFloat32 expression) { - + open().append("f32.load align=" + expression.getAlignment()); + line(expression.getIndex()); + close(); } @Override public void visit(WasmLoadFloat64 expression) { - + open().append("f64.load align=" + expression.getAlignment()); + line(expression.getIndex()); + close(); } @Override public void visit(WasmStoreInt32 expression) { - + open(); + switch (expression.getConvertTo()) { + case INT8: + case UINT8: + append("i32.store8"); + case INT16: + case UINT16: + append("i32.store16"); + break; + case INT32: + append("i32.store"); + break; + } + append(" align=" + expression.getAlignment()); + line(expression.getIndex()); + line(expression.getValue()); + close(); } @Override public void visit(WasmStoreInt64 expression) { - + open(); + switch (expression.getConvertTo()) { + case INT8: + case UINT8: + append("i64.store8"); + case INT16: + case UINT16: + append("i64.store16"); + break; + case INT32: + case UINT32: + append("i64.store32"); + break; + case INT64: + append("i64.store"); + break; + } + append(" align=" + expression.getAlignment()); + line(expression.getIndex()); + line(expression.getValue()); + close(); } @Override public void visit(WasmStoreFloat32 expression) { - + open().append("f32.store align=" + expression.getAlignment()); + line(expression.getIndex()); + line(expression.getValue()); + close(); } @Override public void visit(WasmStoreFloat64 expression) { - + open().append("f64.store align=" + expression.getAlignment()); + line(expression.getIndex()); + line(expression.getValue()); + close(); } private String getIdentifier(String suggested) { diff --git a/interop/core/src/main/java/org/teavm/interop/Address.java b/interop/core/src/main/java/org/teavm/interop/Address.java new file mode 100644 index 000000000..c89c599c2 --- /dev/null +++ b/interop/core/src/main/java/org/teavm/interop/Address.java @@ -0,0 +1,60 @@ +/* + * 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.interop; + +public final class Address { + public final native Address add(int offset); + + public final native Address add(long offset); + + public final native int toInt(); + + public final native long toLong(); + + public final native T toStructure(); + + public final native byte getByte(); + + public final native void putByte(byte value); + + public final native char getChar(); + + public final native void putChar(char value); + + public final native short getShort(); + + public final native void putShort(short value); + + public final native int getInt(); + + public final native void putInt(int value); + + public final native long getLong(); + + public final native void putLong(long value); + + public final native float getFloat(); + + public final native void putFloat(float value); + + public final native double getDouble(); + + public final native void putDouble(double value); + + public static native Address fromInt(int value); + + public static native Address fromLong(long value); +} diff --git a/interop/core/src/main/java/org/teavm/interop/StaticInit.java b/interop/core/src/main/java/org/teavm/interop/StaticInit.java new file mode 100644 index 000000000..6700018aa --- /dev/null +++ b/interop/core/src/main/java/org/teavm/interop/StaticInit.java @@ -0,0 +1,26 @@ +/* + * 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.interop; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface StaticInit { +} diff --git a/interop/core/src/main/java/org/teavm/interop/Structure.java b/interop/core/src/main/java/org/teavm/interop/Structure.java new file mode 100644 index 000000000..3910f19f4 --- /dev/null +++ b/interop/core/src/main/java/org/teavm/interop/Structure.java @@ -0,0 +1,26 @@ +/* + * 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.interop; + +public class Structure { + public native T cast(); + + public native Address toAddress(); + + public native int sizeOf(Class type); + + public native T add(T base, int offset); +}