Wasm backend: implement remaining types of resources

This commit is contained in:
Alexey Andreev 2018-05-20 23:54:23 +03:00
parent 5ce48ce866
commit 097820cc2b
12 changed files with 376 additions and 12 deletions

View File

@ -21,6 +21,7 @@ import java.util.List;
import java.util.ServiceLoader;
import org.teavm.backend.c.TeaVMCHost;
import org.teavm.backend.javascript.TeaVMJavaScriptHost;
import org.teavm.backend.wasm.TeaVMWasmHost;
import org.teavm.classlib.ReflectionSupplier;
import org.teavm.classlib.impl.lambda.LambdaMetafactorySubstitutor;
import org.teavm.classlib.impl.tz.DateTimeZoneProviderIntrinsic;
@ -92,6 +93,11 @@ public class JCLPlugin implements TeaVMPlugin {
if (cHost != null) {
cHost.addIntrinsic(context -> new DateTimeZoneProviderIntrinsic(context.getProperties()));
}
TeaVMWasmHost wasmHost = host.getExtension(TeaVMWasmHost.class);
if (wasmHost != null) {
wasmHost.add(context -> new DateTimeZoneProviderIntrinsic(context.getProperties()));
}
}
TeaVMPluginUtil.handleNatives(host, Class.class);

View File

@ -26,6 +26,7 @@ import java.util.Set;
import org.teavm.backend.javascript.spi.GeneratedBy;
import org.teavm.classlib.impl.Base46;
import org.teavm.classlib.impl.CharFlow;
import org.teavm.interop.Import;
import org.teavm.jso.JSBody;
import org.teavm.platform.metadata.MetadataProvider;
import org.teavm.platform.metadata.ResourceMap;
@ -189,6 +190,7 @@ public final class DateTimeZoneProvider {
}
@JSBody(params = "instant", script = "return new Date(instant).getTimezoneOffset();")
@Import(module = "teavm", name = "getNativeOffset")
private static native int getNativeOffset(double instant);
@MetadataProvider(TimeZoneGenerator.class)

View File

@ -19,9 +19,13 @@ import java.util.Properties;
import org.teavm.ast.InvocationExpr;
import org.teavm.backend.c.intrinsic.Intrinsic;
import org.teavm.backend.c.intrinsic.IntrinsicContext;
import org.teavm.backend.wasm.intrinsics.WasmIntrinsic;
import org.teavm.backend.wasm.intrinsics.WasmIntrinsicManager;
import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmInt32Constant;
import org.teavm.model.MethodReference;
public class DateTimeZoneProviderIntrinsic implements Intrinsic {
public class DateTimeZoneProviderIntrinsic implements Intrinsic, WasmIntrinsic {
private Properties properties;
public DateTimeZoneProviderIntrinsic(Properties properties) {
@ -43,6 +47,20 @@ public class DateTimeZoneProviderIntrinsic implements Intrinsic {
}
}
@Override
public boolean isApplicable(MethodReference methodReference) {
if (!methodReference.getClassName().equals(DateTimeZoneProvider.class.getName())) {
return false;
}
switch (methodReference.getName()) {
case "timeZoneDetectionEnabled":
return true;
default:
return false;
}
}
@Override
public void apply(IntrinsicContext context, InvocationExpr invocation) {
switch (invocation.getMethod().getName()) {
@ -56,4 +74,16 @@ public class DateTimeZoneProviderIntrinsic implements Intrinsic {
break;
}
}
@Override
public WasmExpression apply(InvocationExpr invocation, WasmIntrinsicManager manager) {
switch (invocation.getMethod().getName()) {
case "timeZoneDetectionEnabled": {
boolean enabled = properties.getProperty("java.util.TimeZone.autodetect", "false").equals("true");
return new WasmInt32Constant(enabled ? 1 : 0);
}
default:
throw new AssertionError();
}
}
}

View File

@ -19,6 +19,7 @@ import org.teavm.interop.Address;
import org.teavm.interop.Import;
import org.teavm.interop.StaticInit;
import org.teavm.interop.Unmanaged;
import org.teavm.runtime.RuntimeObject;
@StaticInit
@Unmanaged
@ -282,4 +283,97 @@ public final class WasmRuntime {
public static void setExceptionHandlerId(Address stackFrame, int id) {
getExceptionHandlerPtr(stackFrame).putInt(id);
}
private static int hashCode(RuntimeString string) {
int hashCode = 0;
int length = string.characters.length;
Address chars = Address.ofData(string.characters);
for (int i = 0; i < length; ++i) {
hashCode = 31 * hashCode + chars.getChar();
chars = chars.add(2);
}
return hashCode;
}
private static boolean equals(RuntimeString first, RuntimeString second) {
if (first.characters.length != second.characters.length) {
return false;
}
Address firstChars = Address.ofData(first.characters);
Address secondChars = Address.ofData(second.characters);
int length = first.characters.length;
for (int i = 0; i < length; ++i) {
if (firstChars.getChar() != secondChars.getChar()) {
return false;
}
firstChars = firstChars.add(2);
secondChars = secondChars.add(2);
}
return true;
}
public static String[] resourceMapKeys(Address map) {
String[] result = new String[resourceMapSize(map)];
fillResourceMapKeys(map, result);
return result;
}
private static int resourceMapSize(Address map) {
int result = 0;
int sz = map.getInt();
Address data = contentStart(map);
for (int i = 0; i < sz; ++i) {
if (data.getAddress() != null) {
result++;
}
data = data.add(Address.sizeOf() * 2);
}
return result;
}
private static void fillResourceMapKeys(Address map, String[] target) {
int sz = map.getInt();
Address data = contentStart(map);
Address targetData = Address.ofData(target);
for (int i = 0; i < sz; ++i) {
Address entry = data.getAddress();
if (entry != null) {
targetData.putAddress(entry);
targetData = targetData.add(Address.sizeOf());
}
data = data.add(Address.sizeOf());
}
}
private static Address contentStart(Address resource) {
return resource.add(Address.sizeOf());
}
public static Address lookupResource(Address map, String string) {
RuntimeString runtimeString = Address.ofObject(string).toStructure();
int hashCode = hashCode(runtimeString);
int sz = map.getInt();
Address content = contentStart(map);
for (int i = 0; i < sz; ++i) {
int index = (hashCode + i) % sz;
if (index < 0) {
index += sz;
}
Address entry = content.add(index * Address.sizeOf() * 2);
Address key = entry.getAddress();
if (key == null) {
return null;
}
if (equals(key.toStructure(), runtimeString)) {
return entry;
}
}
return null;
}
static class RuntimeString extends RuntimeObject {
char[] characters;
}
}

View File

@ -266,6 +266,10 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
int.class, void.class), null).use();
dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "getCallSiteId", Address.class,
int.class), null).use();
dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "resourceMapKeys", Address.class,
String[].class), null).use();
dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "lookupResource", Address.class,
String.class, Address.class), null).use();
dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "allocate",
RuntimeClass.class, Address.class), null).use();
@ -466,7 +470,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
renderer.setLineNumbersEmitted(debugging);
renderer.render(module);
try (OutputStream output = buildTarget.createResource(outputName);
Writer writer = new OutputStreamWriter(output, "UTF-8")) {
Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) {
writer.write(renderer.toString());
}
}
@ -477,7 +481,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
renderer.setMemoryAccessChecked(Boolean.parseBoolean(System.getProperty("wasm.c.assertMemory", "false")));
renderer.render(module);
try (OutputStream output = buildTarget.createResource(outputName);
Writer writer = new OutputStreamWriter(output, "UTF-8")) {
Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) {
writer.write(renderer.toString());
}
}

View File

@ -41,6 +41,7 @@ import org.teavm.backend.wasm.model.expression.WasmStoreInt64;
import org.teavm.interop.Address;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
import org.teavm.runtime.RuntimeArray;
public class AddressIntrinsic implements WasmIntrinsic {
private WasmClassGenerator classGenerator;
@ -159,13 +160,40 @@ public class AddressIntrinsic implements WasmIntrinsic {
.collect(Collectors.toList()));
return call;
}
case "isLessThan": {
case "isLessThan":
return new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.LT_UNSIGNED,
manager.generate(invocation.getArguments().get(0)),
manager.generate(invocation.getArguments().get(1)));
case "ofData": {
ValueType.Array type = (ValueType.Array) invocation.getMethod().parameterType(0);
int alignment = getAlignment(type.getItemType());
int start = WasmClassGenerator.align(classGenerator.getClassSize(RuntimeArray.class.getName()),
alignment);
return new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD,
manager.generate(invocation.getArguments().get(0)), new WasmInt32Constant(start));
}
default:
throw new IllegalArgumentException(invocation.getMethod().toString());
}
}
private static int getAlignment(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;
}
}

View File

@ -34,6 +34,9 @@ TeaVM.wasm = function() {
function currentTimeMillis() {
return new Date().getTime();
}
function getNativeOffset(instant) {
return new Date(instant).getTimezoneOffset();
}
function importDefaults(obj) {
obj.teavm = {
@ -44,7 +47,8 @@ TeaVM.wasm = function() {
isfinite: isFinite,
putwchar: putwchar,
towlower: towlower,
towupper: towupper
towupper: towupper,
getNativeOffset: getNativeOffset
};
obj.teavmMath = Math;

View File

@ -82,6 +82,8 @@ public final class Address {
public static native Address ofData(double[] data);
public static native Address ofData(Object[] data);
public static native Address align(Address address, int alignment);
public static native int sizeOf();

View File

@ -53,13 +53,17 @@ class BuildTimeResourceProxyBuilder {
public ProxyFactoryCreation(ResourceTypeDescriptor descriptor) {
this.descriptor = descriptor;
int index = 0;
for (String propertyName : descriptor.getPropertyTypes().keySet()) {
propertyIndexes.put(propertyName, index++);
}
}
BuildTimeResourceProxyFactory create() {
for (Map.Entry<Method, ResourceMethodDescriptor> entry : descriptor.getMethods().entrySet()) {
Method method = entry.getKey();
ResourceMethodDescriptor methodDescriptor = entry.getValue();
int index = getPropertyIndex(methodDescriptor.getPropertyName());
int index = propertyIndexes.get(methodDescriptor.getPropertyName());
switch (methodDescriptor.getType()) {
case GETTER:
methods.put(method, new BuildTimeResourceGetter(index));
@ -109,9 +113,5 @@ class BuildTimeResourceProxyBuilder {
return new BuildTimeResourceProxyFactory(methods, initialData, descriptor);
}
private int getPropertyIndex(String propertyName) {
return propertyIndexes.computeIfAbsent(propertyName, k -> propertyIndexes.size());
}
}
}

View File

@ -166,13 +166,13 @@ public class MetadataCIntrinsic implements Intrinsic {
for (String propertyName : structure.getPropertyTypes().keySet()) {
Class<?> propertyType = structure.getPropertyTypes().get(propertyName);
structuresWriter.println(typeToString(context, propertyType) + " " + propertyName + ";");
structuresWriter.println(typeToString(propertyType) + " " + propertyName + ";");
}
structuresWriter.outdent().println("} " + structureName + ";");
}
private String typeToString(IntrinsicContext context, Class<?> cls) {
private String typeToString(Class<?> cls) {
if (cls == boolean.class || cls == byte.class) {
return "int8_t";
} else if (cls == short.class || cls == char.class) {

View File

@ -39,6 +39,8 @@ import org.teavm.model.MethodReference;
import org.teavm.platform.metadata.MetadataGenerator;
import org.teavm.platform.metadata.MetadataProvider;
import org.teavm.platform.metadata.Resource;
import org.teavm.platform.metadata.ResourceArray;
import org.teavm.platform.metadata.ResourceMap;
public class MetadataIntrinsic implements WasmIntrinsic {
private ClassReaderSource classSource;
@ -85,6 +87,22 @@ public class MetadataIntrinsic implements WasmIntrinsic {
private int writeValue(BinaryWriter writer, WasmStringPool stringPool, Object value) {
if (value instanceof String) {
return stringPool.getStringPointer((String) value);
} else if (value instanceof Boolean) {
DataValue dataValue = DataPrimitives.BYTE.createValue();
dataValue.setByte(0, (Boolean) value ? (byte) 1 : 0);
return writer.append(dataValue);
} else if (value instanceof Integer) {
DataValue dataValue = DataPrimitives.INT.createValue();
dataValue.setInt(0, (Integer) value);
return writer.append(dataValue);
} else if (value instanceof Long) {
DataValue dataValue = DataPrimitives.LONG.createValue();
dataValue.setLong(0, (Long) value);
return writer.append(dataValue);
} else if (value instanceof ResourceMap) {
return writeResource(writer, stringPool, (ResourceMap<?>) value);
} else if (value instanceof ResourceArray) {
return writeResource(writer, stringPool, (ResourceArray<?>) value);
} else if (value instanceof ResourceTypeDescriptorProvider && value instanceof Resource) {
return writeResource(writer, stringPool, (ResourceTypeDescriptorProvider) value);
} else {
@ -109,6 +127,90 @@ public class MetadataIntrinsic implements WasmIntrinsic {
return address;
}
private int writeResource(BinaryWriter writer, WasmStringPool stringPool, ResourceMap<?> resourceMap) {
String[] keys = resourceMap.keys();
int tableSize = keys.length * 2;
int maxTableSize = Math.min(keys.length * 5 / 2, tableSize + 10);
String[] bestTable = null;
int bestCollisionRatio = 0;
while (tableSize <= maxTableSize) {
String[] table = new String[tableSize];
int maxCollisionRatio = 0;
for (String key : keys) {
int hashCode = key.hashCode();
int collisionRatio = 0;
while (true) {
int index = mod(hashCode++, table.length);
if (table[index] == null) {
table[index] = key;
break;
}
collisionRatio++;
}
maxCollisionRatio = Math.max(maxCollisionRatio, collisionRatio);
}
if (bestTable == null || bestCollisionRatio > maxCollisionRatio) {
bestCollisionRatio = maxCollisionRatio;
bestTable = table;
}
tableSize++;
}
DataValue sizeValue = DataPrimitives.ADDRESS.createValue();
int start = writer.append(sizeValue);
sizeValue.setAddress(0, bestTable.length);
DataValue[] keyValues = new DataValue[bestTable.length];
DataValue[] valueValues = new DataValue[bestTable.length];
for (int i = 0; i < bestTable.length; ++i) {
DataValue keyValue = DataPrimitives.ADDRESS.createValue();
DataValue valueValue = DataPrimitives.ADDRESS.createValue();
writer.append(keyValue);
writer.append(valueValue);
keyValues[i] = keyValue;
valueValues[i] = valueValue;
}
for (int i = 0; i < bestTable.length; ++i) {
String key = bestTable[i];
if (key != null) {
keyValues[i].setAddress(0, stringPool.getStringPointer(key));
valueValues[i].setAddress(0, writeValue(writer, stringPool, resourceMap.get(key)));
}
}
return start;
}
private int writeResource(BinaryWriter writer, WasmStringPool stringPool, ResourceArray<?> resourceArray) {
DataValue sizeValue = DataPrimitives.ADDRESS.createValue();
int start = writer.append(sizeValue);
sizeValue.setAddress(0, resourceArray.size());
DataValue[] arrayValues = new DataValue[resourceArray.size()];
for (int i = 0; i < resourceArray.size(); ++i) {
arrayValues[i] = DataPrimitives.ADDRESS.createValue();
writer.append(arrayValues[i]);
}
for (int i = 0; i < resourceArray.size(); ++i) {
arrayValues[i].setAddress(0, writeValue(writer, stringPool, resourceArray.get(i)));
}
return start;
}
private static int mod(int a, int b) {
a %= b;
if (a < 0) {
a += b;
}
return a;
}
private void writeValueTo(BinaryWriter writer, WasmStringPool stringPool, Class<?> type, DataValue target,
int index, Object value) {
if (type == String.class) {
@ -134,6 +236,10 @@ public class MetadataIntrinsic implements WasmIntrinsic {
target.setAddress(index, address);
} else if (value == null) {
target.setAddress(index, 0);
} else if (value instanceof ResourceMap) {
target.setAddress(index, writeResource(writer, stringPool, (ResourceMap<?>) value));
} else if (value instanceof ResourceArray) {
target.setAddress(index, writeResource(writer, stringPool, (ResourceArray<?>) value));
} else {
throw new IllegalArgumentException("Don't know how to write resource: " + value);
}

View File

@ -21,23 +21,44 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.teavm.ast.InvocationExpr;
import org.teavm.backend.wasm.WasmRuntime;
import org.teavm.backend.wasm.binary.BinaryWriter;
import org.teavm.backend.wasm.generate.WasmClassGenerator;
import org.teavm.backend.wasm.intrinsics.WasmIntrinsic;
import org.teavm.backend.wasm.intrinsics.WasmIntrinsicManager;
import org.teavm.backend.wasm.model.WasmLocal;
import org.teavm.backend.wasm.model.WasmType;
import org.teavm.backend.wasm.model.expression.WasmBlock;
import org.teavm.backend.wasm.model.expression.WasmBranch;
import org.teavm.backend.wasm.model.expression.WasmCall;
import org.teavm.backend.wasm.model.expression.WasmDrop;
import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmGetLocal;
import org.teavm.backend.wasm.model.expression.WasmInt32Constant;
import org.teavm.backend.wasm.model.expression.WasmInt32Subtype;
import org.teavm.backend.wasm.model.expression.WasmInt64Subtype;
import org.teavm.backend.wasm.model.expression.WasmIntBinary;
import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation;
import org.teavm.backend.wasm.model.expression.WasmIntType;
import org.teavm.backend.wasm.model.expression.WasmLoadFloat32;
import org.teavm.backend.wasm.model.expression.WasmLoadFloat64;
import org.teavm.backend.wasm.model.expression.WasmLoadInt32;
import org.teavm.backend.wasm.model.expression.WasmLoadInt64;
import org.teavm.backend.wasm.model.expression.WasmSetLocal;
import org.teavm.interop.Address;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
import org.teavm.platform.metadata.Resource;
import org.teavm.platform.metadata.ResourceArray;
import org.teavm.platform.metadata.ResourceMap;
public class ResourceReadIntrinsic implements WasmIntrinsic {
private static final MethodReference LOOKUP_METHOD = new MethodReference(WasmRuntime.class,
"lookupResource", Address.class, String.class, Address.class);
private static final MethodReference KEYS_METHOD = new MethodReference(WasmRuntime.class,
"resourceMapKeys", Address.class, String[].class);
private ClassReaderSource classSource;
private ClassLoader classLoader;
private Map<String, StructureDescriptor> typeDescriptorCache = new HashMap<>();
@ -54,6 +75,12 @@ public class ResourceReadIntrinsic implements WasmIntrinsic {
@Override
public WasmExpression apply(InvocationExpr invocation, WasmIntrinsicManager manager) {
if (invocation.getMethod().getClassName().equals(ResourceMap.class.getName())) {
return applyForResourceMap(manager, invocation);
} else if (invocation.getMethod().getClassName().equals(ResourceArray.class.getName())) {
return applyForResourceArray(manager, invocation);
}
StructureDescriptor typeDescriptor = getTypeDescriptor(invocation.getMethod().getClassName());
PropertyDescriptor property = typeDescriptor.layout.get(invocation.getMethod());
@ -82,6 +109,67 @@ public class ResourceReadIntrinsic implements WasmIntrinsic {
return new WasmLoadInt32(4, base, WasmInt32Subtype.INT32, property.offset);
}
private WasmExpression applyForResourceArray(WasmIntrinsicManager manager, InvocationExpr invocation) {
switch (invocation.getMethod().getName()) {
case "get": {
WasmExpression map = manager.generate(invocation.getArguments().get(0));
WasmExpression index = manager.generate(invocation.getArguments().get(1));
WasmExpression offset = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.SHL,
index, new WasmInt32Constant(2));
WasmExpression address = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD,
map, offset);
return new WasmLoadInt32(4, address, WasmInt32Subtype.INT32, 4);
}
case "size":
return new WasmLoadInt32(4, manager.generate(invocation.getArguments().get(0)),
WasmInt32Subtype.INT32, 0);
default:
throw new AssertionError();
}
}
private WasmExpression applyForResourceMap(WasmIntrinsicManager manager, InvocationExpr invocation) {
switch (invocation.getMethod().getName()) {
case "keys": {
WasmExpression map = manager.generate(invocation.getArguments().get(0));
WasmCall call = new WasmCall(manager.getNames().forMethod(KEYS_METHOD));
call.getArguments().add(map);
return call;
}
case "has": {
WasmExpression map = manager.generate(invocation.getArguments().get(0));
WasmExpression key = manager.generate(invocation.getArguments().get(1));
WasmCall call = new WasmCall(manager.getNames().forMethod(LOOKUP_METHOD));
call.getArguments().add(map);
call.getArguments().add(key);
return new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.NE, call,
new WasmInt32Constant(0));
}
case "get": {
WasmBlock block = new WasmBlock(false);
block.setType(WasmType.INT32);
WasmExpression map = manager.generate(invocation.getArguments().get(0));
WasmExpression key = manager.generate(invocation.getArguments().get(1));
WasmCall call = new WasmCall(manager.getNames().forMethod(LOOKUP_METHOD));
call.getArguments().add(map);
call.getArguments().add(key);
WasmLocal entryVar = manager.getTemporary(WasmType.INT32);
block.getBody().add(new WasmSetLocal(entryVar, call));
WasmBranch ifNull = new WasmBranch(new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.EQ,
new WasmGetLocal(entryVar), new WasmInt32Constant(0)), block);
ifNull.setResult(new WasmInt32Constant(0));
block.getBody().add(new WasmDrop(ifNull));
block.getBody().add(new WasmLoadInt32(4, new WasmGetLocal(entryVar), WasmInt32Subtype.INT32, 4));
return block;
}
default:
throw new AssertionError();
}
}
private StructureDescriptor getTypeDescriptor(String className) {
return typeDescriptorCache.computeIfAbsent(className, n -> {
Class<?> cls;