From f532801f38a7f30c01976a9cefdbf5c915cf587e Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 7 May 2018 19:30:00 +0300 Subject: [PATCH] C backend: implement support for ResourceArray and ResourceMap --- .../org/teavm/classlib/impl/JCLPlugin.java | 7 + .../tz/DateTimeZoneProviderIntrinsic.java | 59 +++++++++ .../java/org/teavm/backend/c/CTarget.java | 11 ++ .../backend/c/intrinsic/IntrinsicContext.java | 2 + .../org/teavm/backend/c/runtime-epilogue.c | 46 +++++++ .../resources/org/teavm/backend/c/runtime.c | 47 +++++++ .../platform/plugin/MetadataCIntrinsic.java | 122 ++++++++++++++++-- .../plugin/ResourceReadCIntrinsic.java | 51 ++++++++ 8 files changed, 336 insertions(+), 9 deletions(-) create mode 100644 classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneProviderIntrinsic.java 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 1cfbaccd3..25953500d 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java @@ -19,9 +19,11 @@ import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader; +import org.teavm.backend.c.TeaVMCHost; import org.teavm.backend.javascript.TeaVMJavaScriptHost; import org.teavm.classlib.ReflectionSupplier; import org.teavm.classlib.impl.lambda.LambdaMetafactorySubstitutor; +import org.teavm.classlib.impl.tz.DateTimeZoneProviderIntrinsic; import org.teavm.classlib.impl.unicode.CLDRReader; import org.teavm.classlib.java.lang.reflect.AnnotationDependencyListener; import org.teavm.interop.PlatformMarker; @@ -85,6 +87,11 @@ public class JCLPlugin implements TeaVMPlugin { host.add(reflection); host.add(new PlatformMarkerSupport(host.getPlatformTags())); + + TeaVMCHost cHost = host.getExtension(TeaVMCHost.class); + if (cHost != null) { + cHost.addIntrinsic(context -> new DateTimeZoneProviderIntrinsic(context.getProperties())); + } } TeaVMPluginUtil.handleNatives(host, Class.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 new file mode 100644 index 000000000..b36de7c97 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/tz/DateTimeZoneProviderIntrinsic.java @@ -0,0 +1,59 @@ +/* + * Copyright 2018 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.classlib.impl.tz; + +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.model.MethodReference; + +public class DateTimeZoneProviderIntrinsic implements Intrinsic { + private Properties properties; + + public DateTimeZoneProviderIntrinsic(Properties properties) { + this.properties = properties; + } + + @Override + public boolean canHandle(MethodReference method) { + if (!method.getClassName().equals(DateTimeZoneProvider.class.getName())) { + return false; + } + + switch (method.getName()) { + case "timeZoneDetectionEnabled": + case "getNativeOffset": + return true; + default: + return false; + } + } + + @Override + public void apply(IntrinsicContext context, InvocationExpr invocation) { + switch (invocation.getMethod().getName()) { + case "timeZoneDetectionEnabled": { + boolean enabled = properties.getProperty("java.util.TimeZone.autodetect", "false").equals("true"); + context.writer().print(enabled ? "1" : "0"); + break; + } + case "getNativeOffset": + context.writer().print("teavm_timeZoneOffset()"); + break; + } + } +} diff --git a/core/src/main/java/org/teavm/backend/c/CTarget.java b/core/src/main/java/org/teavm/backend/c/CTarget.java index 7e4b8dbec..648f06743 100644 --- a/core/src/main/java/org/teavm/backend/c/CTarget.java +++ b/core/src/main/java/org/teavm/backend/c/CTarget.java @@ -353,6 +353,7 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { private void generateSpecialFunctions(GenerationContext context, CodeWriter writer) { generateThrowCCE(context, writer); + generateAllocateStringArray(context, writer); } private void generateThrowCCE(GenerationContext context, CodeWriter writer) { @@ -364,6 +365,16 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { writer.outdent().println("}"); } + private void generateAllocateStringArray(GenerationContext context, CodeWriter writer) { + writer.println("static JavaArray* teavm_allocateStringArray(int32_t size) {").indent(); + String allocateArrayName = context.getNames().forMethod(new MethodReference(Allocator.class, + "allocateArray", RuntimeClass.class, int.class, Address.class)); + String stringClassName = context.getNames().forClassInstance(ValueType.arrayOf( + ValueType.object(String.class.getName()))); + writer.println("return (JavaArray*) " + allocateArrayName + "(&" + stringClassName + ", size);"); + writer.outdent().println("}"); + } + private void generateMain(GenerationContext context, CodeWriter writer, ListableClassHolderSource classes, Set types) { writer.println("int main(int argc, char** argv) {").indent(); diff --git a/core/src/main/java/org/teavm/backend/c/intrinsic/IntrinsicContext.java b/core/src/main/java/org/teavm/backend/c/intrinsic/IntrinsicContext.java index 795a82856..95a348586 100644 --- a/core/src/main/java/org/teavm/backend/c/intrinsic/IntrinsicContext.java +++ b/core/src/main/java/org/teavm/backend/c/intrinsic/IntrinsicContext.java @@ -34,4 +34,6 @@ public interface IntrinsicContext { MethodReference getCallingMethod(); StringPool getStringPool(); + + } diff --git a/core/src/main/resources/org/teavm/backend/c/runtime-epilogue.c b/core/src/main/resources/org/teavm/backend/c/runtime-epilogue.c index 6f2b4cdfc..1b0a127f9 100644 --- a/core/src/main/resources/org/teavm/backend/c/runtime-epilogue.c +++ b/core/src/main/resources/org/teavm/backend/c/runtime-epilogue.c @@ -4,4 +4,50 @@ static inline int32_t instanceof(void* obj, int32_t (*cls)(JavaClass*)) { static inline void* checkcast(void* obj, int32_t (*cls)(JavaClass*)) { return obj == NULL || cls(CLASS_OF(obj)) ? obj : throwClassCastException(); +} + +static int32_t teavm_hashCode(JavaString* string) { + int32_t hashCode = INT32_C(0); + int32_t length = string->characters->size; + char16_t* chars = ARRAY_DATA(string->characters, char16_t); + for (int32_t i = INT32_C(0); i < length; ++i) { + hashCode = 31 * hashCode + chars[i]; + } + return hashCode; +} + +static int32_t teavm_equals(JavaString* first, JavaString* second) { + if (first->characters->size != second->characters->size) { + return 0; + } + + char16_t* firstChars = ARRAY_DATA(first->characters, char16_t); + char16_t* secondChars = ARRAY_DATA(second->characters, char16_t); + int32_t length = first->characters->size; + for (int32_t i = INT32_C(0); i < length; ++i) { + if (firstChars[i] != secondChars[i]) { + return 0; + } + } + return 1; +} + +static JavaArray* teavm_resourceMapKeys(TeaVM_ResourceMap *map) { + int32_t size = 0; + for (int32_t i = 0; i < map->size; ++i) { + if (map->entries[i].key != NULL) { + size++; + } + } + + int32_t index = 0; + void* array = teavm_allocateStringArray(size); + void** data = ARRAY_DATA(array, void*); + for (int32_t i = 0; i < map->size; ++i) { + if (map->entries[i].key != NULL) { + data[index++] = map->entries[i].key; + } + } + + return array; } \ No newline at end of file diff --git a/core/src/main/resources/org/teavm/backend/c/runtime.c b/core/src/main/resources/org/teavm/backend/c/runtime.c index 47c91794e..a093e27f1 100644 --- a/core/src/main/resources/org/teavm/backend/c/runtime.c +++ b/core/src/main/resources/org/teavm/backend/c/runtime.c @@ -114,6 +114,46 @@ static inline float TeaVM_getNaN() { return NAN; } +typedef struct { + int32_t size; + void* data[0]; +} TeaVM_ResourceArray; + +typedef struct { + JavaString* key; + void* value; +} TeaVM_ResourceMapEntry; + +typedef struct { + int32_t size; + TeaVM_ResourceMapEntry entries[0]; +} TeaVM_ResourceMap; + +static int32_t teavm_hashCode(JavaString*); +static int32_t teavm_equals(JavaString*, JavaString*); +static JavaArray* teavm_allocateStringArray(int32_t size); + +static TeaVM_ResourceMapEntry* teavm_lookupResource(TeaVM_ResourceMap *map, JavaString* string) { + uint32_t hashCode = teavm_hashCode(string); + for (int32_t i = 0; i < map->size; ++i) { + uint32_t index = (hashCode + i) % map->size; + if (map->entries[index].key == NULL) { + return NULL; + } + if (teavm_equals(map->entries[index].key, string)) { + return &map->entries[index]; + } + } + return NULL; +} + +static inline void* teavm_lookupResourceValue(TeaVM_ResourceMap *map, JavaString* string) { + TeaVM_ResourceMapEntry *entry = teavm_lookupResource(map, string); + return entry != NULL ? entry->value : NULL; +} + +static JavaArray* teavm_resourceMapKeys(TeaVM_ResourceMap *); + static void TeaVM_beforeInit() { srand(time(NULL)); } @@ -158,6 +198,13 @@ static int64_t currentTimeMillis() { return time.tv_sec * 1000 + (int64_t) round(time.tv_nsec / 1000000); } + +static int32_t teavm_timeZoneOffset() { + time_t t = time(NULL); + time_t local = mktime(localtime(&t)); + time_t utc = mktime(gmtime(&t)); + return difftime(utc, local) / 60; +} #endif #ifdef _MSC_VER 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 c0af20909..21b0ed3ac 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/MetadataCIntrinsic.java +++ b/platform/src/main/java/org/teavm/platform/plugin/MetadataCIntrinsic.java @@ -30,6 +30,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 MetadataCIntrinsic implements Intrinsic { private ClassReaderSource classSource; @@ -89,11 +91,36 @@ public class MetadataCIntrinsic implements Intrinsic { } private void writeValue(IntrinsicContext context, Object value) { - if (value instanceof String) { + if (value == null) { + staticFieldInitWriter.print("NULL"); + } else if (value instanceof String) { int stringIndex = context.getStringPool().getStringIndex((String) value); - staticFieldInitWriter.print("(stringPool + " + stringIndex + ")"); + staticFieldInitWriter.print("(JavaObject*) (stringPool + " + stringIndex + ")"); + } else if (value instanceof Boolean) { + staticFieldInitWriter.print((Boolean) value ? "1" : "0"); + } else if (value instanceof Integer) { + int n = (Integer) value; + if (n < 0) { + staticFieldInitWriter.print("-"); + n = -n; + } + staticFieldInitWriter.print("INT32_C(" + n + ")"); + } else if (value instanceof Long) { + long n = (Long) value; + if (n < 0) { + staticFieldInitWriter.print("-"); + n = -n; + } + staticFieldInitWriter.print("INT64_C(" + n + ")"); + } else if (value instanceof Byte || value instanceof Short || value instanceof Float + || value instanceof Double) { + staticFieldInitWriter.print(value.toString()); } else if (value instanceof ResourceTypeDescriptorProvider && value instanceof Resource) { writeResource(context, (ResourceTypeDescriptorProvider) value); + } else if (value instanceof ResourceMap) { + writeResourceMap(context, (ResourceMap) value); + } else if (value instanceof ResourceArray) { + writeResourceArray(context, (ResourceArray) value); } else { throw new IllegalArgumentException("Don't know how to write resource: " + value); } @@ -126,7 +153,8 @@ public class MetadataCIntrinsic implements Intrinsic { } for (Class propertyType : structure.getPropertyTypes().values()) { - if (Resource.class.isAssignableFrom(propertyType)) { + if (Resource.class.isAssignableFrom(propertyType) && !ResourceMap.class.isAssignableFrom(propertyType) + && !ResourceArray.class.isAssignableFrom(propertyType)) { ResourceTypeDescriptor propertyStructure = metadataContext.getTypeDescriptor( propertyType.asSubclass(Resource.class)); writeResourceStructure(context, propertyStructure); @@ -158,7 +186,7 @@ public class MetadataCIntrinsic implements Intrinsic { } else if (cls == double.class) { return "double"; } else if (Resource.class.isAssignableFrom(cls)) { - return "&" + context.names().forClass(cls.getName()); + return "void*"; } else if (cls == String.class) { return "JavaObject*"; } else { @@ -166,12 +194,88 @@ public class MetadataCIntrinsic implements Intrinsic { } } - static class MethodContext { - String baseName; - int suffix; + private void writeResourceArray(IntrinsicContext context, ResourceArray resourceArray) { + staticFieldInitWriter.println("&(struct { int32_t size; void* data[" + resourceArray.size() + "]; }) {") + .indent(); + staticFieldInitWriter.println(".size = " + resourceArray.size() + ","); + staticFieldInitWriter.print(".data = {").indent(); - MethodContext(String baseName) { - this.baseName = baseName; + boolean first = true; + for (int i = 0; i < resourceArray.size(); ++i) { + if (!first) { + staticFieldInitWriter.print(","); + } + staticFieldInitWriter.println(); + first = false; + writeValue(context, resourceArray.get(i)); } + + staticFieldInitWriter.println().outdent().println("}"); + staticFieldInitWriter.outdent().print("}"); + } + + private void writeResourceMap(IntrinsicContext context, 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++; + } + + staticFieldInitWriter.println("&(struct { int32_t size; TeaVM_ResourceMapEntry entries[" + + bestTable.length + "]; }) {").indent(); + staticFieldInitWriter.println(".size = " + bestTable.length + ","); + staticFieldInitWriter.print(".entries = {").indent(); + + boolean first = true; + for (String key : bestTable) { + if (!first) { + staticFieldInitWriter.print(","); + } + staticFieldInitWriter.println(); + first = false; + if (key == null) { + staticFieldInitWriter.print("{ NULL, NULL }"); + } else { + staticFieldInitWriter.print("{ stringPool + " + context.getStringPool().getStringIndex(key) + ", "); + writeValue(context, resourceMap.get(key)); + staticFieldInitWriter.print("}"); + } + } + + staticFieldInitWriter.println().outdent().println("}"); + staticFieldInitWriter.outdent().print("}"); + } + + private static int mod(int a, int b) { + a %= b; + if (a < 0) { + a += b; + } + return a; } } diff --git a/platform/src/main/java/org/teavm/platform/plugin/ResourceReadCIntrinsic.java b/platform/src/main/java/org/teavm/platform/plugin/ResourceReadCIntrinsic.java index 59552f1b6..d170d67bc 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/ResourceReadCIntrinsic.java +++ b/platform/src/main/java/org/teavm/platform/plugin/ResourceReadCIntrinsic.java @@ -21,6 +21,8 @@ import org.teavm.backend.c.intrinsic.IntrinsicContext; import org.teavm.model.ClassReaderSource; import org.teavm.model.MethodReference; import org.teavm.platform.metadata.Resource; +import org.teavm.platform.metadata.ResourceArray; +import org.teavm.platform.metadata.ResourceMap; public class ResourceReadCIntrinsic implements Intrinsic { private ClassReaderSource classSource; @@ -36,6 +38,14 @@ public class ResourceReadCIntrinsic implements Intrinsic { @Override public void apply(IntrinsicContext context, InvocationExpr invocation) { + if (invocation.getMethod().getClassName().equals(ResourceMap.class.getName())) { + applyForResourceMap(context, invocation); + return; + } else if (invocation.getMethod().getClassName().equals(ResourceArray.class.getName())) { + applyForResourceArray(context, invocation); + return; + } + String name = invocation.getMethod().getName(); if (name.startsWith("get")) { name = name.substring(3); @@ -52,4 +62,45 @@ public class ResourceReadCIntrinsic implements Intrinsic { context.writer().print(", ").print(context.names().forClass(invocation.getMethod().getClassName())); context.writer().print(", ").print(name).print(")"); } + + private void applyForResourceMap(IntrinsicContext context, InvocationExpr invocation) { + switch (invocation.getMethod().getName()) { + case "keys": + context.writer().print("teavm_resourceMapKeys((TeaVM_ResourceMap*) "); + context.emit(invocation.getArguments().get(0)); + context.writer().print(")"); + break; + case "has": + context.writer().print("(teavm_lookupResource((TeaVM_ResourceMap*) "); + context.emit(invocation.getArguments().get(0)); + context.writer().print(", (JavaString*) "); + context.emit(invocation.getArguments().get(1)); + context.writer().print(") != NULL)"); + break; + case "get": + context.writer().print("teavm_lookupResourceValue((TeaVM_ResourceMap*) "); + context.emit(invocation.getArguments().get(0)); + context.writer().print(", (JavaString*) "); + context.emit(invocation.getArguments().get(1)); + context.writer().print(")"); + break; + } + } + + private void applyForResourceArray(IntrinsicContext context, InvocationExpr invocation) { + switch (invocation.getMethod().getName()) { + case "size": + context.writer().print("((TeaVM_ResourceArray*) "); + context.emit(invocation.getArguments().get(0)); + context.writer().print(")->size"); + break; + case "get": + context.writer().print("((TeaVM_ResourceArray*) "); + context.emit(invocation.getArguments().get(0)); + context.writer().print(")->data["); + context.emit(invocation.getArguments().get(1)); + context.writer().print("]"); + break; + } + } }