C: implement ServiceLoader

This commit is contained in:
Alexey Andreev 2021-04-14 23:01:53 +03:00
parent 4a62b58f82
commit 73bd139b7e
14 changed files with 380 additions and 81 deletions

View File

@ -67,15 +67,22 @@ public class JCLPlugin implements TeaVMPlugin {
host.add(new ObfuscationHacks());
if (!isBootstrap()) {
ServiceLoaderSupport serviceLoaderSupp = new ServiceLoaderSupport(host.getClassLoader());
host.add(serviceLoaderSupp);
ServiceLoaderSupport serviceLoaderSupport = new ServiceLoaderSupport(host.getClassLoader());
host.add(serviceLoaderSupport);
host.registerService(ServiceLoaderInformation.class, serviceLoaderSupport);
MethodReference loadServicesMethod = new MethodReference(ServiceLoader.class, "loadServices",
PlatformClass.class, Object[].class);
TeaVMJavaScriptHost jsExtension = host.getExtension(TeaVMJavaScriptHost.class);
if (jsExtension != null) {
jsExtension.add(loadServicesMethod, serviceLoaderSupp);
jsExtension.add(loadServicesMethod, new ServiceLoaderJSSupport());
jsExtension.addVirtualMethods(new AnnotationVirtualMethods());
}
TeaVMCHost cHost = host.getExtension(TeaVMCHost.class);
if (cHost != null) {
cHost.addGenerator(new ServiceLoaderCSupport());
}
}
if (!isBootstrap()) {

View File

@ -0,0 +1,150 @@
/*
* Copyright 2021 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;
import java.util.Collection;
import java.util.ServiceLoader;
import org.teavm.backend.c.generate.CodeWriter;
import org.teavm.backend.c.generators.Generator;
import org.teavm.backend.c.generators.GeneratorContext;
import org.teavm.backend.c.generators.GeneratorFactory;
import org.teavm.backend.c.generators.GeneratorFactoryContext;
import org.teavm.backend.lowlevel.generate.NameProvider;
import org.teavm.interop.Address;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
import org.teavm.model.lowlevel.CallSiteDescriptor;
import org.teavm.model.lowlevel.CallSiteLocation;
import org.teavm.model.lowlevel.ExceptionHandlerDescriptor;
import org.teavm.runtime.Allocator;
import org.teavm.runtime.RuntimeClass;
public class ServiceLoaderCSupport implements GeneratorFactory {
static final MethodReference ALLOC_ARRAY_METHOD = new MethodReference(Allocator.class,
"allocateArray", RuntimeClass.class, int.class, Address.class);
static final MethodReference ALLOC_METHOD = new MethodReference(Allocator.class,
"allocate", RuntimeClass.class, Address.class);
static final MethodDescriptor INIT_METHOD = new MethodDescriptor("<init>", ValueType.VOID);
@Override
public Generator createGenerator(GeneratorFactoryContext context) {
return new ServiceLoaderIntrinsic(context.getServices().getService(ServiceLoaderInformation.class));
}
static class ServiceLoaderIntrinsic implements Generator {
private ServiceLoaderInformation information;
ServiceLoaderIntrinsic(ServiceLoaderInformation information) {
this.information = information;
}
@Override
public boolean canHandle(MethodReference method) {
if (!method.getClassName().equals(ServiceLoader.class.getName())) {
return false;
}
return method.getName().equals("loadServices");
}
@Override
public void generate(GeneratorContext context, MethodReference method) {
CodeWriter writer = context.writer();
CodeWriter beforeWriter = context.writerBefore();
NameProvider names = context.names();
context.includes().addInclude("<stdbool.h>");
Collection<? extends String> serviceTypes = information.serviceTypes();
writer.println("static bool initialized = false;");
writer.println("if (!initialized) {").indent();
String methodName = names.forMethod(method);
int index = 0;
for (String serviceType : serviceTypes) {
Collection<? extends String> implementations = information.serviceImplementations(serviceType);
if (implementations.isEmpty()) {
continue;
}
String staticFieldName = methodName + "_" + index++;
context.includes().includeClass(serviceType);
writer.print(names.forClassInstance(ValueType.object(serviceType)))
.print(".services = (TeaVM_Services*) &").print(staticFieldName).println(";");
beforeWriter.print("static struct { int32_t size; ")
.print("TeaVM_Service entries[" + implementations.size() + "]; } ")
.print(staticFieldName + " = { .size = " + implementations.size() + ", ")
.print(".entries = {").indent();
boolean first = true;
for (String implementation : implementations) {
if (!first) {
beforeWriter.print(",");
}
first = false;
context.includes().includeClass(implementation);
MethodReference constructor = new MethodReference(implementation, INIT_METHOD);
context.importMethod(constructor, false);
beforeWriter.println().print("{ .cls = (TeaVM_Class*) &")
.print(names.forClassInstance(ValueType.object(implementation)))
.print(", .constructor = &").print(names.forMethod(constructor))
.print(" }");
}
if (!first) {
beforeWriter.println();
}
beforeWriter.outdent().println("}};");
}
writer.outdent().println("}");
CallSiteLocation location = new CallSiteLocation(null, method.getClassName(), method.getName(), -1);
CallSiteDescriptor callSite = context.createCallSite(new CallSiteLocation[] { location },
new ExceptionHandlerDescriptor[0]);
writer.println("TEAVM_ALLOC_STACK(INT32_C(2));");
writer.println("TEAVM_CALL_SITE(" + callSite.getId() + ");");
writer.println("TeaVM_Array* result = NULL;");
writer.print("TeaVM_Services* services = ((TeaVM_Class*) ").print(context.parameterName(1))
.println(")->services;");
writer.println("if (services == NULL) goto exit;");
writer.println("TEAVM_GC_ROOT_RELEASE(0);");
writer.println("TEAVM_GC_ROOT_RELEASE(1);");
writer.print("result = ")
.print(names.forMethod(ALLOC_ARRAY_METHOD)).print("(&")
.print(names.forClassInstance(ValueType.parse(Object[].class))).print(", ")
.println("services->size);");
if (!context.usesLongjmp()) {
writer.println("if (TEAVM_EXCEPTION_HANDLER != " + callSite.getId() + ") goto exit;");
}
writer.println("TEAVM_GC_ROOT(0, result);");
writer.println("void** arrayData = (void**) TEAVM_ARRAY_DATA(result, void*);");
writer.println("for (int32_t i = 0; i < services->size; ++i) {").indent();
writer.print("void* obj = ").print(names.forMethod(ALLOC_METHOD)).println("(services->entries[i].cls);");
if (!context.usesLongjmp()) {
writer.println("if (TEAVM_EXCEPTION_HANDLER != " + callSite.getId() + ") goto exit;");
}
writer.println("TEAVM_GC_ROOT(1, obj);");
writer.println("services->entries[i].constructor(obj);");
if (!context.usesLongjmp()) {
writer.println("if (TEAVM_EXCEPTION_HANDLER != " + callSite.getId() + ") goto exit;");
}
writer.println("arrayData[i] = obj;");
writer.outdent().println("}");
writer.println("exit: TEAVM_RELEASE_STACK;");
writer.println("return result;");
}
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2021 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;
import java.util.Collection;
public interface ServiceLoaderInformation {
Collection<? extends String> serviceTypes();
Collection<? extends String> serviceImplementations(String type);
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2021 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;
import java.io.IOException;
import java.util.Collection;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
public class ServiceLoaderJSSupport implements Generator {
private static final MethodDescriptor INIT_METHOD = new MethodDescriptor("<init>", void.class);
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
ServiceLoaderInformation information = context.getService(ServiceLoaderInformation.class);
writer.append("if (!").appendClass("java.util.ServiceLoader").append(".$$services$$) {").indent()
.softNewLine();
writer.appendClass("java.util.ServiceLoader").append(".$$services$$ = true;").softNewLine();
for (String serviceType : information.serviceTypes()) {
writer.appendClass(serviceType).append(".$$serviceList$$ = [");
Collection<? extends String> implementations = information.serviceImplementations(serviceType);
boolean first = true;
for (String implName : implementations) {
if (context.getClassSource().getClassNames().contains(implName)) {
if (!first) {
writer.append(", ");
}
first = false;
writer.append("[").appendClass(implName).append(", ").appendMethodBody(
new MethodReference(implName, INIT_METHOD))
.append("]");
}
}
writer.append("];").softNewLine();
}
writer.outdent().append("}").softNewLine();
String param = context.getParameterName(1);
writer.append("var cls = " + param + ";").softNewLine();
writer.append("if (!cls.$$serviceList$$) {").indent().softNewLine();
writer.append("return $rt_createArray($rt_objcls(), 0);").softNewLine();
writer.outdent().append("}").softNewLine();
writer.append("var result = $rt_createArray($rt_objcls(), cls.$$serviceList$$.length);").softNewLine();
writer.append("for (var i = 0; i < result.data.length; ++i) {").indent().softNewLine();
writer.append("var serviceDesc = cls.$$serviceList$$[i];").softNewLine();
writer.append("result.data[i] = new serviceDesc[0]();").softNewLine();
writer.append("serviceDesc[1](result.data[i]);").softNewLine();
writer.outdent().append("}").softNewLine();
writer.append("return result;").softNewLine();
}
}

View File

@ -23,6 +23,8 @@ import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
@ -30,9 +32,6 @@ import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.classlib.ServiceLoaderFilter;
import org.teavm.dependency.AbstractDependencyListener;
import org.teavm.dependency.DependencyAgent;
@ -42,7 +41,7 @@ import org.teavm.model.CallLocation;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
public class ServiceLoaderSupport extends AbstractDependencyListener implements Generator {
public class ServiceLoaderSupport extends AbstractDependencyListener implements ServiceLoaderInformation {
private static final MethodReference LOAD_METHOD = new MethodReference(ServiceLoader.class, "load", Class.class,
ServiceLoader.class);
private static final MethodDescriptor INIT_METHOD = new MethodDescriptor("<init>", void.class);
@ -54,40 +53,17 @@ public class ServiceLoaderSupport extends AbstractDependencyListener implements
}
@Override
public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException {
writer.append("if (!").appendClass("java.util.ServiceLoader").append(".$$services$$) {").indent()
.softNewLine();
writer.appendClass("java.util.ServiceLoader").append(".$$services$$ = true;").softNewLine();
for (Map.Entry<String, List<String>> entry : serviceMap.entrySet()) {
writer.appendClass(entry.getKey()).append(".$$serviceList$$ = [");
List<String> implementations = entry.getValue();
boolean first = true;
for (String implName : implementations) {
if (context.getClassSource().getClassNames().contains(implName)) {
if (!first) {
writer.append(", ");
public Collection<? extends String> serviceTypes() {
return serviceMap.keySet();
}
first = false;
writer.append("[").appendClass(implName).append(", ").appendMethodBody(
new MethodReference(implName, INIT_METHOD))
.append("]");
@Override
public Collection<? extends String> serviceImplementations(String type) {
Collection<? extends String> result = serviceMap.get(type);
if (result == null) {
result = Collections.emptyList();
}
}
writer.append("];").softNewLine();
}
writer.outdent().append("}").softNewLine();
String param = context.getParameterName(1);
writer.append("var cls = " + param + ";").softNewLine();
writer.append("if (!cls.$$serviceList$$) {").indent().softNewLine();
writer.append("return $rt_createArray($rt_objcls(), 0);").softNewLine();
writer.outdent().append("}").softNewLine();
writer.append("var result = $rt_createArray($rt_objcls(), cls.$$serviceList$$.length);").softNewLine();
writer.append("for (var i = 0; i < result.data.length; ++i) {").indent().softNewLine();
writer.append("var serviceDesc = cls.$$serviceList$$[i];").softNewLine();
writer.append("result.data[i] = new serviceDesc[0]();").softNewLine();
writer.append("serviceDesc[1](result.data[i]);").softNewLine();
writer.outdent().append("}").softNewLine();
writer.append("return result;").softNewLine();
return result;
}
@Override

View File

@ -18,11 +18,6 @@ package org.teavm.classlib.java.util;
import org.teavm.classlib.java.lang.*;
import org.teavm.platform.PlatformClass;
/**
*
* @author Alexey Andreev
* @param <S>
*/
public final class TServiceLoader<S> extends TObject implements TIterable<S> {
private Object[] services;
@ -50,7 +45,7 @@ public final class TServiceLoader<S> extends TObject implements TIterable<S> {
}
public static <S> TServiceLoader<S> load(TClass<S> service) {
return new TServiceLoader<>(loadServices(service.getPlatformClass()));
return new TServiceLoader<>(doLoadServices(service.getPlatformClass()));
}
public static <S> TServiceLoader<S> load(TClass<S> service, @SuppressWarnings("unused") TClassLoader loader) {
@ -61,7 +56,15 @@ public final class TServiceLoader<S> extends TObject implements TIterable<S> {
return load(service);
}
private static native <T> T[] loadServices(PlatformClass cls);
private static Object[] doLoadServices(PlatformClass cls) {
Object[] result = loadServices(cls);
if (result == null) {
result = new Object[0];
}
return result;
}
private static native Object[] loadServices(PlatformClass cls);
public void reload() {
// Do nothing, services are bound at build time

View File

@ -1272,7 +1272,7 @@ public class ClassGenerator {
codeWriter.outdent().println("}");
GeneratorContextImpl generatorContext = new GeneratorContextImpl(codeGenerator.getClassContext(),
bodyWriter, writerBefore, codeWriter, includes);
bodyWriter, writerBefore, codeWriter, includes, callSites, context.isLongjmp());
generator.generate(generatorContext, methodRef);
try {
generatorContext.flush();

View File

@ -17,6 +17,7 @@ package org.teavm.backend.c.generate;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.teavm.backend.c.generators.GeneratorContext;
import org.teavm.backend.lowlevel.generate.NameProvider;
@ -24,6 +25,9 @@ import org.teavm.dependency.DependencyInfo;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.MethodReference;
import org.teavm.model.lowlevel.CallSiteDescriptor;
import org.teavm.model.lowlevel.CallSiteLocation;
import org.teavm.model.lowlevel.ExceptionHandlerDescriptor;
class GeneratorContextImpl implements GeneratorContext {
private GenerationContext context;
@ -33,15 +37,20 @@ class GeneratorContextImpl implements GeneratorContext {
private CodeWriter writerAfter;
private IncludeManager includes;
private List<FileGeneratorImpl> fileGenerators = new ArrayList<>();
private List<CallSiteDescriptor> callSites;
private boolean longjmp;
public GeneratorContextImpl(ClassGenerationContext classContext, CodeWriter bodyWriter,
CodeWriter writerBefore, CodeWriter writerAfter, IncludeManager includes) {
CodeWriter writerBefore, CodeWriter writerAfter, IncludeManager includes,
List<CallSiteDescriptor> callSites, boolean longjmp) {
this.context = classContext.getContext();
this.classContext = classContext;
this.bodyWriter = bodyWriter;
this.writerBefore = writerBefore;
this.writerAfter = writerAfter;
this.includes = includes;
this.callSites = callSites;
this.longjmp = longjmp;
}
@Override
@ -126,6 +135,20 @@ class GeneratorContextImpl implements GeneratorContext {
return sb.toString();
}
@Override
public CallSiteDescriptor createCallSite(CallSiteLocation[] locations,
ExceptionHandlerDescriptor[] exceptionHandlers) {
CallSiteDescriptor callSite = new CallSiteDescriptor(callSites.size(), locations);
callSite.getHandlers().addAll(Arrays.asList(exceptionHandlers));
callSites.add(callSite);
return callSite;
}
@Override
public boolean usesLongjmp() {
return longjmp;
}
void flush() throws IOException {
for (FileGeneratorImpl generator : fileGenerators) {
OutputFileUtil.write(generator.writer, generator.path, context.getBuildTarget());

View File

@ -24,6 +24,9 @@ import org.teavm.dependency.DependencyInfo;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.MethodReference;
import org.teavm.model.lowlevel.CallSiteDescriptor;
import org.teavm.model.lowlevel.CallSiteLocation;
import org.teavm.model.lowlevel.ExceptionHandlerDescriptor;
public interface GeneratorContext {
CodeWriter writer();
@ -53,4 +56,8 @@ public interface GeneratorContext {
String escapeFileName(String name);
void importMethod(MethodReference method, boolean isStatic);
boolean usesLongjmp();
CallSiteDescriptor createCallSite(CallSiteLocation[] locations, ExceptionHandlerDescriptor[] exceptionHandlers);
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2021 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.common;
public final class HashUtils {
private HashUtils() {
}
public static String[] createHashTable(String[] values) {
int tableSize = values.length * 2;
int maxTableSize = Math.min(values.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 : values) {
int hashCode = key.hashCode();
int collisionRatio = 0;
while (true) {
int index = Integer.remainderUnsigned(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++;
}
return bestTable;
}
}

View File

@ -122,7 +122,10 @@ public final class ExceptionHandling {
if (stackFrame == null) {
stackFrame = ShadowStack.getStackTop();
while (stackFrame != null) {
ShadowStack.setExceptionHandlerId(stackFrame, ShadowStack.getCallSiteId(stackFrame) + 1);
int callSiteId = ShadowStack.getCallSiteId(stackFrame);
if (callSiteId >= 0) {
ShadowStack.setExceptionHandlerId(stackFrame, callSiteId + 1);
}
stackFrame = ShadowStack.getNextStackFrame(stackFrame);
}
printStack();

View File

@ -70,6 +70,7 @@ void teavm_initClasses() {
int32_t classHeader = TEAVM_PACK_CLASS(teavm_classClass) | (int32_t) INT32_C(0x80000000);
for (int i = 0; i < teavm_classReferencesCount; ++i) {
teavm_classReferences[i]->parent.header = classHeader;
teavm_classReferences[i]->services = NULL;
}
}

View File

@ -22,6 +22,8 @@ typedef struct TeaVM_Array {
int32_t size;
} TeaVM_Array;
struct TeaVM_Services;
typedef struct TeaVM_Class {
TeaVM_Object parent;
int32_t size;
@ -44,12 +46,23 @@ typedef struct TeaVM_Class {
TeaVM_Object** simpleName;
TeaVM_Object* simpleNameCache;
TeaVM_Object* canonicalName;
struct TeaVM_Services* services;
#if TEAVM_HEAP_DUMP
TeaVM_FieldDescriptors* fieldDescriptors;
TeaVM_StaticFieldDescriptors* staticFieldDescriptors;
#endif
} TeaVM_Class;
typedef struct TeaVM_Service {
TeaVM_Class* cls;
void (*constructor)(void*);
} TeaVM_Service;
typedef struct TeaVM_Services {
int32_t size;
TeaVM_Service entries[1];
} TeaVM_Services;
typedef struct TeaVM_String {
TeaVM_Object parent;
TeaVM_Array* characters;

View File

@ -23,6 +23,7 @@ import java.util.Set;
import org.teavm.backend.c.generate.FileGenerator;
import org.teavm.backend.c.generators.Generator;
import org.teavm.backend.c.generators.GeneratorContext;
import org.teavm.common.HashUtils;
import org.teavm.common.ServiceRepository;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.MethodReference;
@ -191,37 +192,7 @@ class MetadataCIntrinsic implements Generator {
}
private void writeResourceMap(GeneratorContext 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 = Integer.remainderUnsigned(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++;
}
String[] bestTable = HashUtils.createHashTable(resourceMap.keys());
context.includes().includePath("resource.h");
context.writerBefore().println("&(struct { int32_t size; TeaVM_ResourceMapEntry entries["
+ bestTable.length + "]; }) {").indent();