Start supporting classes in WASM

This commit is contained in:
Alexey Andreev 2016-08-08 20:26:16 +03:00
parent 081efd2d60
commit 675abe8740
17 changed files with 962 additions and 18 deletions

View File

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

View File

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

View File

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

View File

@ -30,5 +30,18 @@ public final class Example {
b = c; b = c;
WasmRuntime.print(a); 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;
}
} }
} }

View File

@ -26,13 +26,22 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import org.teavm.ast.decompilation.Decompiler; import org.teavm.ast.decompilation.Decompiler;
import org.teavm.dependency.DependencyChecker; import org.teavm.dependency.DependencyChecker;
import org.teavm.interop.Address;
import org.teavm.interop.Import; import org.teavm.interop.Import;
import org.teavm.interop.StaticInit;
import org.teavm.interop.Structure;
import org.teavm.model.CallLocation; import org.teavm.model.CallLocation;
import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolder;
import org.teavm.model.ClassReader;
import org.teavm.model.ElementModifier; import org.teavm.model.ElementModifier;
import org.teavm.model.ListableClassHolderSource; import org.teavm.model.ListableClassHolderSource;
import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodHolder; import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.runtime.Allocator;
import org.teavm.runtime.RuntimeClass;
import org.teavm.vm.BuildTarget; import org.teavm.vm.BuildTarget;
import org.teavm.vm.TeaVM; import org.teavm.vm.TeaVM;
import org.teavm.vm.TeaVMBuilder; import org.teavm.vm.TeaVMBuilder;
@ -40,11 +49,25 @@ import org.teavm.vm.TeaVMEntryPoint;
import org.teavm.vm.TeaVMTarget; import org.teavm.vm.TeaVMTarget;
import org.teavm.vm.TeaVMTargetController; import org.teavm.vm.TeaVMTargetController;
import org.teavm.vm.spi.TeaVMHostExtension; import org.teavm.vm.spi.TeaVMHostExtension;
import org.teavm.wasm.generate.WasmClassGenerator;
import org.teavm.wasm.generate.WasmGenerationContext; import org.teavm.wasm.generate.WasmGenerationContext;
import org.teavm.wasm.generate.WasmGenerator; import org.teavm.wasm.generate.WasmGenerator;
import org.teavm.wasm.generate.WasmMangling; import org.teavm.wasm.generate.WasmMangling;
import org.teavm.wasm.model.WasmFunction; import org.teavm.wasm.model.WasmFunction;
import org.teavm.wasm.model.WasmModule; 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.render.WasmRenderer;
import org.teavm.wasm.runtime.WasmRuntime; 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); MethodReference method = new MethodReference(WasmRuntime.class, "remainder", type, type, type);
dependencyChecker.linkMethod(method, null).use(); dependencyChecker.linkMethod(method, null).use();
} }
dependencyChecker.linkMethod(new MethodReference(Allocator.class, "allocate",
RuntimeClass.class, Address.class), null).use();
dependencyChecker.linkMethod(new MethodReference(Allocator.class, "<clinit>", void.class), null).use();
} }
@Override @Override
public void emit(ListableClassHolderSource classes, OutputStream output, BuildTarget buildTarget) { 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); WasmGenerationContext context = new WasmGenerationContext(classes);
WasmGenerator generator = new WasmGenerator(decompiler, classes, context); WasmGenerator generator = new WasmGenerator(decompiler, classes, context, classGenerator);
WasmModule module = new WasmModule(); WasmModule module = new WasmModule();
module.setMemorySize(64);
for (String className : classes.getClassNames()) { for (String className : classes.getClassNames()) {
ClassHolder cls = classes.get(className); ClassHolder cls = classes.get(className);
for (MethodHolder method : cls.getMethods()) { for (MethodHolder method : cls.getMethods()) {
if (method.getOwnerName().equals(Allocator.class.getName())
&& method.getName().equals("initialize")) {
continue;
}
if (method.hasModifier(ElementModifier.NATIVE)) { 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) { if (context.getImportedMethod(method.getReference()) == null) {
CallLocation location = new CallLocation(method.getReference()); CallLocation location = new CallLocation(method.getReference());
controller.getDiagnostics().error(location, "Method {{m0}} is native but " 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("<clinit>", void.class));
if (clinit == null) {
continue;
}
initFunction.getBody().add(new WasmCall(WasmMangling.mangleMethod(clinit.getReference())));
}
module.add(initFunction);
for (TeaVMEntryPoint entryPoint : controller.getEntryPoints().values()) { for (TeaVMEntryPoint entryPoint : controller.getEntryPoints().values()) {
String mangledName = WasmMangling.mangleMethod(entryPoint.getReference()); String mangledName = WasmMangling.mangleMethod(entryPoint.getReference());
WasmFunction function = module.getFunctions().get(mangledName); 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("<clinit>", 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 { public static void main(String[] args) throws IOException {
TeaVM vm = new TeaVMBuilder(new WasmTarget()).build(); TeaVM vm = new TeaVMBuilder(new WasmTarget()).build();
vm.installPlugins(); vm.installPlugins();

View File

@ -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<String, ClassBinaryData> 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<WasmExpression> 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<String> fieldLayout = new ObjectIntOpenHashMap<>();
}
}

View File

@ -22,8 +22,11 @@ import org.teavm.model.AnnotationReader;
import org.teavm.model.AnnotationValue; import org.teavm.model.AnnotationValue;
import org.teavm.model.ClassReader; import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ClassReaderSource;
import org.teavm.model.FieldReader;
import org.teavm.model.FieldReference;
import org.teavm.model.MethodReader; import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
public class WasmGenerationContext { public class WasmGenerationContext {
private ClassReaderSource classSource; 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 class ImportedMethod {
public final String name; public final String name;
public final String module; public final String module;

View File

@ -59,9 +59,17 @@ import org.teavm.ast.UnaryExpr;
import org.teavm.ast.UnwrapArrayExpr; import org.teavm.ast.UnwrapArrayExpr;
import org.teavm.ast.VariableExpr; import org.teavm.ast.VariableExpr;
import org.teavm.ast.WhileStatement; 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.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.WasmFunction;
import org.teavm.wasm.model.WasmLocal; 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.WasmBlock;
import org.teavm.wasm.model.expression.WasmBranch; import org.teavm.wasm.model.expression.WasmBranch;
import org.teavm.wasm.model.expression.WasmBreak; 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.WasmFloatType;
import org.teavm.wasm.model.expression.WasmGetLocal; import org.teavm.wasm.model.expression.WasmGetLocal;
import org.teavm.wasm.model.expression.WasmInt32Constant; 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.WasmInt64Constant;
import org.teavm.wasm.model.expression.WasmInt64Subtype;
import org.teavm.wasm.model.expression.WasmIntBinary; import org.teavm.wasm.model.expression.WasmIntBinary;
import org.teavm.wasm.model.expression.WasmIntBinaryOperation; import org.teavm.wasm.model.expression.WasmIntBinaryOperation;
import org.teavm.wasm.model.expression.WasmIntType; 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.WasmReturn;
import org.teavm.wasm.model.expression.WasmSetLocal; 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.model.expression.WasmSwitch;
import org.teavm.wasm.runtime.WasmRuntime; import org.teavm.wasm.runtime.WasmRuntime;
class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { class WasmGenerationVisitor implements StatementVisitor, ExprVisitor {
private WasmGenerationContext context; private WasmGenerationContext context;
private WasmClassGenerator classGenerator;
private WasmFunction function; private WasmFunction function;
private int firstVariable; private int firstVariable;
private IdentifiedStatement currentContinueTarget; private IdentifiedStatement currentContinueTarget;
@ -97,8 +116,10 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor {
private Set<WasmBlock> usedBlocks = new HashSet<>(); private Set<WasmBlock> usedBlocks = new HashSet<>();
WasmExpression result; WasmExpression result;
WasmGenerationVisitor(WasmGenerationContext context, WasmFunction function, int firstVariable) { WasmGenerationVisitor(WasmGenerationContext context, WasmClassGenerator classGenerator,
WasmFunction function, int firstVariable) {
this.context = context; this.context = context;
this.classGenerator = classGenerator;
this.function = function; this.function = function;
this.firstVariable = firstVariable; this.firstVariable = firstVariable;
} }
@ -350,11 +371,48 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor {
WasmLocal local = function.getLocalVariables().get(varExpr.getIndex() - firstVariable); WasmLocal local = function.getLocalVariables().get(varExpr.getIndex() - firstVariable);
statement.getRightValue().acceptVisitor(this); statement.getRightValue().acceptVisitor(this);
result = new WasmSetLocal(local, result); result = new WasmSetLocal(local, result);
} else if (left instanceof QualificationExpr) {
QualificationExpr lhs = (QualificationExpr) left;
storeField(lhs.getQualified(), lhs.getField(), statement.getRightValue());
} else { } else {
throw new UnsupportedOperationException("This expression is not supported yet"); 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 @Override
public void visit(ConditionalExpr expr) { public void visit(ConditionalExpr expr) {
expr.getCondition().acceptVisitor(this); expr.getCondition().acceptVisitor(this);
@ -371,7 +429,9 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor {
WasmBlock block = new WasmBlock(false); WasmBlock block = new WasmBlock(false);
for (Statement part : statement.getSequence()) { for (Statement part : statement.getSequence()) {
part.acceptVisitor(this); part.acceptVisitor(this);
block.getBody().add(result); if (result != null) {
block.getBody().add(result);
}
} }
result = block; result = block;
} }
@ -399,11 +459,15 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor {
WasmConditional conditional = new WasmConditional(result); WasmConditional conditional = new WasmConditional(result);
for (Statement part : statement.getConsequent()) { for (Statement part : statement.getConsequent()) {
part.acceptVisitor(this); part.acceptVisitor(this);
conditional.getThenBlock().getBody().add(result); if (result != null) {
conditional.getThenBlock().getBody().add(result);
}
} }
for (Statement part : statement.getAlternative()) { for (Statement part : statement.getAlternative()) {
part.acceptVisitor(this); part.acceptVisitor(this);
conditional.getElseBlock().getBody().add(result); if (result != null) {
conditional.getElseBlock().getBody().add(result);
}
} }
result = conditional; result = conditional;
} }
@ -479,7 +543,9 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor {
for (Statement part : statement.getBody()) { for (Statement part : statement.getBody()) {
part.acceptVisitor(this); part.acceptVisitor(this);
loop.getBody().add(result); if (result != null) {
loop.getBody().add(result);
}
} }
loop.getBody().add(new WasmBreak(loop)); loop.getBody().add(new WasmBreak(loop));
@ -498,6 +564,11 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor {
@Override @Override
public void visit(InvocationExpr expr) { 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) { if (expr.getType() == InvocationType.STATIC || expr.getType() == InvocationType.SPECIAL) {
String methodName = WasmMangling.mangleMethod(expr.getMethod()); 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 @Override
public void visit(BlockStatement statement) { public void visit(BlockStatement statement) {
WasmBlock block = new WasmBlock(false); WasmBlock block = new WasmBlock(false);
@ -523,7 +702,9 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor {
for (Statement part : statement.getBody()) { for (Statement part : statement.getBody()) {
part.acceptVisitor(this); part.acceptVisitor(this);
block.getBody().add(result); if (result != null) {
block.getBody().add(result);
}
} }
if (statement.getId() != null) { if (statement.getId() != null) {
@ -535,6 +716,48 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor {
@Override @Override
public void visit(QualificationExpr expr) { 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 @Override
@ -550,6 +773,12 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor {
@Override @Override
public void visit(NewExpr expr) { 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 @Override
@ -598,6 +827,22 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor {
@Override @Override
public void visit(InitClassStatement statement) { 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("<clinit>", ValueType.VOID)) != null;
} }
@Override @Override

View File

@ -35,11 +35,14 @@ public class WasmGenerator {
private Decompiler decompiler; private Decompiler decompiler;
private ClassHolderSource classSource; private ClassHolderSource classSource;
private WasmGenerationContext context; 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.decompiler = decompiler;
this.classSource = classSource; this.classSource = classSource;
this.context = context; this.context = context;
this.classGenerator = classGenerator;
} }
public WasmFunction generate(MethodReference methodReference) { public WasmFunction generate(MethodReference methodReference) {
@ -79,7 +82,7 @@ public class WasmGenerator {
function.setResult(WasmGeneratorUtil.mapType(methodReference.getReturnType())); 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); methodAst.getBody().acceptVisitor(visitor);
function.getBody().add(visitor.result); function.getBody().add(visitor.result);

View File

@ -36,6 +36,10 @@ public final class WasmMangling {
return sb.toString(); return sb.toString();
} }
public static String mangleInitializer(String className) {
return "clinit$" + mangleString(className);
}
private static String mangleString(String string) { private static String mangleString(String string) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (int i = 0; i < string.length(); ++i) { for (int i = 0; i < string.length(); ++i) {

View File

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

View File

@ -22,9 +22,12 @@ import java.util.List;
import java.util.Map; import java.util.Map;
public class WasmModule { public class WasmModule {
private int memorySize;
private List<WasmMemorySegment> segments = new ArrayList<>();
private Map<String, WasmFunction> functions = new LinkedHashMap<>(); private Map<String, WasmFunction> functions = new LinkedHashMap<>();
private Map<String, WasmFunction> readonlyFunctions = Collections.unmodifiableMap(functions); private Map<String, WasmFunction> readonlyFunctions = Collections.unmodifiableMap(functions);
private List<WasmFunction> functionTable = new ArrayList<>(); private List<WasmFunction> functionTable = new ArrayList<>();
private WasmFunction startFunction;
public void add(WasmFunction function) { public void add(WasmFunction function) {
if (functions.containsKey(function.getName())) { if (functions.containsKey(function.getName())) {
@ -44,4 +47,24 @@ public class WasmModule {
public List<WasmFunction> getFunctionTable() { public List<WasmFunction> getFunctionTable() {
return functionTable; return functionTable;
} }
public List<WasmMemorySegment> 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;
}
} }

View File

@ -18,6 +18,7 @@ package org.teavm.wasm.render;
import java.util.List; import java.util.List;
import org.teavm.wasm.model.WasmFunction; import org.teavm.wasm.model.WasmFunction;
import org.teavm.wasm.model.WasmLocal; import org.teavm.wasm.model.WasmLocal;
import org.teavm.wasm.model.WasmMemorySegment;
import org.teavm.wasm.model.WasmModule; import org.teavm.wasm.model.WasmModule;
import org.teavm.wasm.model.WasmType; import org.teavm.wasm.model.WasmType;
import org.teavm.wasm.model.expression.WasmExpression; import org.teavm.wasm.model.expression.WasmExpression;
@ -47,6 +48,7 @@ public class WasmRenderer {
public void render(WasmModule module) { public void render(WasmModule module) {
visitor.open().append("module"); visitor.open().append("module");
renderMemory(module);
for (WasmFunction function : module.getFunctions().values()) { for (WasmFunction function : module.getFunctions().values()) {
if (function.getImportName() == null) { if (function.getImportName() == null) {
continue; continue;
@ -68,6 +70,35 @@ public class WasmRenderer {
visitor.close().lf(); 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) { public void renderImport(WasmFunction function) {
String importModule = function.getImportModule(); String importModule = function.getImportModule();
if (importModule == null) { if (importModule == null) {

View File

@ -356,42 +356,134 @@ class WasmRenderingVisitor implements WasmExpressionVisitor {
@Override @Override
public void visit(WasmLoadInt32 expression) { 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 @Override
public void visit(WasmLoadInt64 expression) { 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 @Override
public void visit(WasmLoadFloat32 expression) { public void visit(WasmLoadFloat32 expression) {
open().append("f32.load align=" + expression.getAlignment());
line(expression.getIndex());
close();
} }
@Override @Override
public void visit(WasmLoadFloat64 expression) { public void visit(WasmLoadFloat64 expression) {
open().append("f64.load align=" + expression.getAlignment());
line(expression.getIndex());
close();
} }
@Override @Override
public void visit(WasmStoreInt32 expression) { 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 @Override
public void visit(WasmStoreInt64 expression) { 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 @Override
public void visit(WasmStoreFloat32 expression) { public void visit(WasmStoreFloat32 expression) {
open().append("f32.store align=" + expression.getAlignment());
line(expression.getIndex());
line(expression.getValue());
close();
} }
@Override @Override
public void visit(WasmStoreFloat64 expression) { public void visit(WasmStoreFloat64 expression) {
open().append("f64.store align=" + expression.getAlignment());
line(expression.getIndex());
line(expression.getValue());
close();
} }
private String getIdentifier(String suggested) { private String getIdentifier(String suggested) {

View File

@ -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 extends Structure> 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);
}

View File

@ -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 {
}

View File

@ -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 extends Structure> T cast();
public native Address toAddress();
public native int sizeOf(Class<? extends Structure> type);
public native <T extends Structure> T add(T base, int offset);
}