From 097820cc2b5506f18cd80eb6ac3810a50702e10b Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sun, 20 May 2018 23:54:23 +0300 Subject: [PATCH] Wasm backend: implement remaining types of resources --- .../org/teavm/classlib/impl/JCLPlugin.java | 6 + .../impl/tz/DateTimeZoneProvider.java | 2 + .../tz/DateTimeZoneProviderIntrinsic.java | 32 +++++- .../org/teavm/backend/wasm/WasmRuntime.java | 94 ++++++++++++++++ .../org/teavm/backend/wasm/WasmTarget.java | 8 +- .../wasm/intrinsics/AddressIntrinsic.java | 30 ++++- .../org/teavm/backend/wasm/wasm-runtime.js | 6 +- .../main/java/org/teavm/interop/Address.java | 2 + .../plugin/BuildTimeResourceProxyBuilder.java | 10 +- .../platform/plugin/MetadataCIntrinsic.java | 4 +- .../platform/plugin/MetadataIntrinsic.java | 106 ++++++++++++++++++ .../plugin/ResourceReadIntrinsic.java | 88 +++++++++++++++ 12 files changed, 376 insertions(+), 12 deletions(-) diff --git a/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java b/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java index 25953500d..bf5d1b176 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java @@ -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); diff --git a/classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneProvider.java b/classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneProvider.java index 62e3b0c55..a64088118 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneProvider.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneProvider.java @@ -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) diff --git a/classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneProviderIntrinsic.java b/classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneProviderIntrinsic.java index b36de7c97..72c042fe9 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneProviderIntrinsic.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneProviderIntrinsic.java @@ -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(); + } + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmRuntime.java b/core/src/main/java/org/teavm/backend/wasm/WasmRuntime.java index 10b380f1f..21099791a 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmRuntime.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmRuntime.java @@ -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; + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java index 67de85609..9d237aacd 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -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()); } } diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/AddressIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/AddressIntrinsic.java index 576ff07b0..3520cd78b 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/AddressIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/AddressIntrinsic.java @@ -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; + } } diff --git a/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js b/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js index f4b17fbef..40fce2274 100644 --- a/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js +++ b/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js @@ -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; diff --git a/interop/core/src/main/java/org/teavm/interop/Address.java b/interop/core/src/main/java/org/teavm/interop/Address.java index 3c3372eb7..382565ac5 100644 --- a/interop/core/src/main/java/org/teavm/interop/Address.java +++ b/interop/core/src/main/java/org/teavm/interop/Address.java @@ -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(); diff --git a/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyBuilder.java b/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyBuilder.java index f058eccce..1adf69bc6 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyBuilder.java +++ b/platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyBuilder.java @@ -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 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()); - } } } diff --git a/platform/src/main/java/org/teavm/platform/plugin/MetadataCIntrinsic.java b/platform/src/main/java/org/teavm/platform/plugin/MetadataCIntrinsic.java index 21b0ed3ac..8926ec55f 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/MetadataCIntrinsic.java +++ b/platform/src/main/java/org/teavm/platform/plugin/MetadataCIntrinsic.java @@ -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) { diff --git a/platform/src/main/java/org/teavm/platform/plugin/MetadataIntrinsic.java b/platform/src/main/java/org/teavm/platform/plugin/MetadataIntrinsic.java index f89996d3c..c2b967e0d 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/MetadataIntrinsic.java +++ b/platform/src/main/java/org/teavm/platform/plugin/MetadataIntrinsic.java @@ -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); } diff --git a/platform/src/main/java/org/teavm/platform/plugin/ResourceReadIntrinsic.java b/platform/src/main/java/org/teavm/platform/plugin/ResourceReadIntrinsic.java index 9723dca5f..c40e66530 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/ResourceReadIntrinsic.java +++ b/platform/src/main/java/org/teavm/platform/plugin/ResourceReadIntrinsic.java @@ -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 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;