wasm: support ServiceLoader

This commit is contained in:
Alexey Andreev 2023-08-28 19:32:22 +02:00
parent 70d0658b47
commit 401fcabeae
12 changed files with 292 additions and 79 deletions

View File

@ -84,6 +84,11 @@ public class JCLPlugin implements TeaVMPlugin {
if (cHost != null) {
cHost.addGenerator(new ServiceLoaderCSupport());
}
var wasmHost = host.getExtension(TeaVMWasmHost.class);
if (wasmHost != null) {
wasmHost.add(new ServiceLoaderWasmSupport());
}
}
if (!isBootstrap()) {

View File

@ -0,0 +1,136 @@
/*
* Copyright 2023 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.ServiceLoader;
import org.teavm.ast.InvocationExpr;
import org.teavm.backend.wasm.WasmRuntime;
import org.teavm.backend.wasm.binary.DataPrimitives;
import org.teavm.backend.wasm.binary.DataStructure;
import org.teavm.backend.wasm.intrinsics.WasmIntrinsic;
import org.teavm.backend.wasm.intrinsics.WasmIntrinsicFactory;
import org.teavm.backend.wasm.intrinsics.WasmIntrinsicFactoryContext;
import org.teavm.backend.wasm.intrinsics.WasmIntrinsicManager;
import org.teavm.backend.wasm.model.expression.WasmCall;
import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmInt32Constant;
import org.teavm.dependency.DependencyAnalyzer;
import org.teavm.interop.Address;
import org.teavm.interop.Structure;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
import org.teavm.runtime.Allocator;
import org.teavm.runtime.GC;
import org.teavm.runtime.RuntimeArray;
import org.teavm.runtime.RuntimeClass;
import org.teavm.runtime.RuntimeObject;
public class ServiceLoaderWasmSupport implements WasmIntrinsicFactory {
private static final DataStructure ENTRY = new DataStructure(
(byte) 4,
DataPrimitives.ADDRESS,
DataPrimitives.INT
);
private static final MethodReference CREATE_SERVICES_METHOD = new MethodReference(
ServiceLoaderWasmSupport.class, "createServices", Address.class, Address.class);
@Override
public WasmIntrinsic create(WasmIntrinsicFactoryContext context) {
return new ServiceLoaderIntrinsic(context.getServices().getService(ServiceLoaderInformation.class));
}
@Override
public void contributeDependencies(DependencyAnalyzer analyzer) {
analyzer.linkMethod(CREATE_SERVICES_METHOD);
}
private static class ServiceLoaderIntrinsic implements WasmIntrinsic {
private ServiceLoaderInformation information;
ServiceLoaderIntrinsic(ServiceLoaderInformation information) {
this.information = information;
}
@Override
public boolean isApplicable(MethodReference methodReference) {
return methodReference.getClassName().equals(ServiceLoader.class.getName())
&& methodReference.getName().equals("loadServices");
}
@Override
public WasmExpression apply(InvocationExpr invocation, WasmIntrinsicManager manager) {
var table = createServiceData(manager);
var tableArg = new WasmInt32Constant(table);
var serviceClassAddress = manager.generate(invocation.getArguments().get(0));
return new WasmCall(manager.getNames().forMethod(CREATE_SERVICES_METHOD), tableArg,
serviceClassAddress);
}
private int createServiceData(WasmIntrinsicManager manager) {
var writer = manager.getBinaryWriter();
return writer.writeMap(
information.serviceTypes().toArray(new String[0]),
cls -> manager.getClassPointer(ValueType.object(cls)),
cls -> manager.getClassPointer(ValueType.object(cls)),
cls -> {
var implementations = information.serviceImplementations(cls);
var result = writer.getAddress();
var count = DataPrimitives.INT.createValue();
writer.append(count);
count.setInt(0, implementations.size());
for (var implementation : implementations) {
var entry = ENTRY.createValue();
writer.append(entry);
var implPointer = manager.getClassPointer(ValueType.object(implementation));
entry.setAddress(0, implPointer);
var constructorName = manager.getNames().forMethod(new MethodReference(
implementation, "<init>", ValueType.VOID
));
entry.setInt(1, manager.getFunctionPointer(constructorName));
}
return result;
}
);
}
}
static RuntimeObject createServices(Address table, RuntimeClass cls) {
var entry = WasmRuntime.lookupResource(table, cls.toAddress());
if (entry == null) {
return null;
}
entry = entry.add(Address.sizeOf()).getAddress();
var size = entry.getInt();
entry = entry.add(4);
RuntimeArray result = Allocator.allocateArray(cls, size).toStructure();
var resultData = WasmRuntime.align(result.toAddress().add(Structure.sizeOf(RuntimeArray.class)),
Address.sizeOf());
for (var i = 0; i < size; ++i) {
RuntimeObject obj = Allocator.allocate(entry.getAddress().toStructure()).toStructure();
entry = entry.add(Address.sizeOf());
WasmRuntime.callFunctionFromTable(entry.getInt(), obj);
entry = entry.add(4);
resultData.putAddress(obj.toAddress());
resultData = resultData.add(Address.sizeOf());
GC.writeBarrier(result);
}
return result;
}
}

View File

@ -31,14 +31,13 @@ public class TDate implements TComparable<TDate> {
private long value;
static {
if (PlatformDetector.isLowLevel()) {
if (PlatformDetector.isC()) {
initLowLevel();
}
}
@Import(name = "teavm_date_init")
@RuntimeInclude("date.h")
@UnsupportedOn(Platforms.WEBASSEMBLY)
@NoSideEffects
@Unmanaged
private static native void initLowLevel();

View File

@ -234,6 +234,7 @@ public final class WasmRuntime {
return resource.add(Address.sizeOf());
}
@Unmanaged
public static Address lookupResource(Address map, String string) {
RuntimeString runtimeString = Address.ofObject(string).toStructure();
int hashCode = hashCode(runtimeString);
@ -256,6 +257,30 @@ public final class WasmRuntime {
return null;
}
@Unmanaged
public static Address lookupResource(Address map, Address key) {
int sz = map.getInt();
Address content = contentStart(map);
var hash = key.toInt();
for (int i = 0; i < sz; ++i) {
int index = (hash + i) % sz;
if (index < 0) {
index += sz;
}
var entry = content.add(index * Address.sizeOf() * 2);
var entryKey = entry.getAddress();
if (entryKey == null) {
return null;
}
if (key == entryKey) {
return entry;
}
}
return null;
}
public static native void callFunctionFromTable(int index, RuntimeObject instance);
static class RuntimeString extends RuntimeObject {
char[] characters;
}

View File

@ -348,6 +348,8 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
String[].class)).use();
dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "lookupResource", Address.class,
String.class, Address.class)).use();
dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "lookupResource", Address.class,
int.class, Address.class)).use();
dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "printString",
String.class, void.class)).use();
dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "printInt",
@ -408,6 +410,10 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
}
dependencyAnalyzer.addDependencyListener(new StringsDependencyListener());
for (var intrinsic : additionalIntrinsics) {
intrinsic.contributeDependencies(dependencyAnalyzer);
}
}
@Override

View File

@ -18,6 +18,8 @@ package org.teavm.backend.wasm.binary;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
public class BinaryWriter {
private int address;
@ -173,4 +175,70 @@ public class BinaryWriter {
result[offset++] = (byte) (v >> 56);
return offset;
}
public <T> int writeMap(T[] keys, ToIntFunction<T> hashCodeF, ToLongFunction<T> keyWriter,
ToLongFunction<T> valueWriter) {
int tableSize = keys.length * 2;
int maxTableSize = Math.min(keys.length * 5 / 2, tableSize + 10);
Object[] bestTable = null;
int bestCollisionRatio = 0;
while (tableSize <= maxTableSize) {
var table = new Object[tableSize];
int maxCollisionRatio = 0;
for (var key : keys) {
int hashCode = hashCodeF.applyAsInt(key);
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++;
}
var sizeValue = DataPrimitives.ADDRESS.createValue();
int start = append(sizeValue);
sizeValue.setAddress(0, bestTable.length);
var keyValues = new DataValue[bestTable.length];
var valueValues = new DataValue[bestTable.length];
for (int i = 0; i < bestTable.length; ++i) {
var keyValue = DataPrimitives.ADDRESS.createValue();
var valueValue = DataPrimitives.ADDRESS.createValue();
append(keyValue);
append(valueValue);
keyValues[i] = keyValue;
valueValues[i] = valueValue;
}
for (int i = 0; i < bestTable.length; ++i) {
@SuppressWarnings("unchecked")
var key = (T) bestTable[i];
if (key != null) {
keyValues[i].setAddress(0, keyWriter.applyAsLong(key));
valueValues[i].setAddress(0, valueWriter.applyAsLong(key));
}
}
return start;
}
private static int mod(int a, int b) {
a %= b;
if (a < 0) {
a += b;
}
return a;
}
}

View File

@ -65,6 +65,7 @@ public class WasmClassGenerator {
private BinaryWriter binaryWriter;
private Map<MethodReference, Integer> functions = new HashMap<>();
private List<String> functionTable = new ArrayList<>();
private ObjectIntMap<String> functionIdMap = new ObjectIntHashMap<>();
private VirtualTableProvider vtableProvider;
private TagRegistry tagRegistry;
private WasmStringPool stringPool;
@ -222,9 +223,8 @@ public class WasmClassGenerator {
binaryData.data = wrapper.getValue(0);
binaryData.data.setInt(CLASS_SIZE, 4);
binaryData.data.setAddress(CLASS_ITEM_TYPE, itemBinaryData.start);
binaryData.data.setInt(CLASS_IS_INSTANCE, functionTable.size());
binaryData.data.setInt(CLASS_IS_INSTANCE, getFunctionPointer(names.forSupertypeFunction(type)));
binaryData.data.setInt(CLASS_CANARY, RuntimeClass.computeCanary(4, 0));
functionTable.add(names.forSupertypeFunction(type));
binaryData.data.setAddress(CLASS_NAME, stringPool.getStringPointer(type.toString().replace('/', '.')));
binaryData.data.setAddress(CLASS_SIMPLE_NAME, 0);
binaryData.data.setInt(CLASS_INIT, -1);
@ -238,10 +238,9 @@ public class WasmClassGenerator {
private DataValue createPrimitiveClassData(DataValue value, int size, ValueType type) {
value.setInt(CLASS_SIZE, size);
value.setInt(CLASS_FLAGS, RuntimeClass.PRIMITIVE);
value.setInt(CLASS_IS_INSTANCE, functionTable.size());
value.setInt(CLASS_IS_INSTANCE, getFunctionPointer(names.forSupertypeFunction(type)));
value.setAddress(CLASS_SIMPLE_NAME, 0);
value.setInt(CLASS_INIT, -1);
functionTable.add(names.forSupertypeFunction(type));
String name;
if (type == ValueType.VOID) {
@ -282,10 +281,20 @@ public class WasmClassGenerator {
return value;
}
public List<String> getFunctionTable() {
public Iterable<? extends String> getFunctionTable() {
return functionTable;
}
public int getFunctionPointer(String name) {
var result = functionIdMap.getOrDefault(name, -1);
if (result < 0) {
result = functionTable.size();
functionTable.add(name);
functionIdMap.put(name, result);
}
return result;
}
private DataValue createStructure(ClassBinaryData binaryData) {
String parent = binaryData.cls.getParent();
int parentPtr = !binaryData.isInferface && parent != null
@ -316,8 +325,7 @@ public class WasmClassGenerator {
header.setInt(CLASS_CANARY, RuntimeClass.computeCanary(occupiedSize, tag));
int nameAddress = requirements.name() ? stringPool.getStringPointer(name) : 0;
header.setAddress(CLASS_NAME, nameAddress);
header.setInt(CLASS_IS_INSTANCE, functionTable.size());
functionTable.add(names.forSupertypeFunction(ValueType.object(name)));
header.setInt(CLASS_IS_INSTANCE, getFunctionPointer(names.forSupertypeFunction(ValueType.object(name))));
header.setAddress(CLASS_PARENT, parentPtr);
ClassReader cls = processedClassSource.get(name);
@ -372,8 +380,7 @@ public class WasmClassGenerator {
if (cls != null && binaryData.start >= 0
&& cls.getMethod(new MethodDescriptor("<clinit>", ValueType.VOID)) != null
&& classInitializerInfo.isDynamicInitializer(name)) {
header.setInt(CLASS_INIT, functionTable.size());
functionTable.add(names.forClassInitializer(name));
header.setInt(CLASS_INIT, getFunctionPointer(names.forClassInitializer(name)));
} else {
header.setInt(CLASS_INIT, -1);
}
@ -450,11 +457,8 @@ public class WasmClassGenerator {
if (method != null) {
VirtualTableEntry entry = vtable.getEntry(method);
if (entry != null) {
methodIndex = functions.computeIfAbsent(entry.getImplementor(), implementor -> {
int result = functionTable.size();
functionTable.add(names.forMethod(implementor));
return result;
});
methodIndex = functions.computeIfAbsent(entry.getImplementor(),
implementor -> getFunctionPointer(names.forMethod(implementor)));
}
}

View File

@ -1654,6 +1654,16 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor {
public int getStaticField(FieldReference field) {
return classGenerator.getFieldOffset(field);
}
@Override
public int getClassPointer(ValueType type) {
return classGenerator.getClassPointer(type);
}
@Override
public int getFunctionPointer(String name) {
return classGenerator.getFunctionPointer(name);
}
};
private WasmLocal getTemporary(WasmType type) {

View File

@ -15,7 +15,12 @@
*/
package org.teavm.backend.wasm.intrinsics;
import org.teavm.dependency.DependencyAnalyzer;
@FunctionalInterface
public interface WasmIntrinsicFactory {
WasmIntrinsic create(WasmIntrinsicFactoryContext context);
default void contributeDependencies(DependencyAnalyzer analyzer) {
}
}

View File

@ -24,6 +24,7 @@ import org.teavm.backend.wasm.model.WasmType;
import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.FieldReference;
import org.teavm.model.ValueType;
public interface WasmIntrinsicManager {
WasmExpression generate(Expr expr);
@ -40,5 +41,9 @@ public interface WasmIntrinsicManager {
int getStaticField(FieldReference field);
int getClassPointer(ValueType type);
int getFunctionPointer(String name);
void releaseTemporary(WasmLocal local);
}

View File

@ -23,6 +23,7 @@ import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmFloatBinary;
import org.teavm.backend.wasm.model.expression.WasmFloatBinaryOperation;
import org.teavm.backend.wasm.model.expression.WasmFloatType;
import org.teavm.backend.wasm.model.expression.WasmIndirectCall;
import org.teavm.backend.wasm.model.expression.WasmIntBinary;
import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation;
import org.teavm.backend.wasm.model.expression.WasmIntType;
@ -40,6 +41,7 @@ public class WasmRuntimeIntrinsic implements WasmIntrinsic {
case "gtu":
case "ltu":
case "initStack":
case "callFunctionFromTable":
return true;
default:
return false;
@ -61,6 +63,11 @@ public class WasmRuntimeIntrinsic implements WasmIntrinsic {
case "gtu":
return comparison(WasmIntBinaryOperation.GT_UNSIGNED, WasmFloatBinaryOperation.GT,
invocation, manager);
case "callFunctionFromTable": {
var call = new WasmIndirectCall(manager.generate(invocation.getArguments().get(0)));
call.getArguments().add(manager.generate(invocation.getArguments().get(1)));
return call;
}
default:
throw new IllegalArgumentException(invocation.getMethod().getName());
}

View File

@ -120,61 +120,12 @@ class MetadataIntrinsic implements WasmIntrinsic {
}
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;
return writer.writeMap(
resourceMap.keys(),
String::hashCode,
stringPool::getStringPointer,
key -> writeValue(writer, stringPool, resourceMap.get(key))
);
}
private int writeResource(BinaryWriter writer, WasmStringPool stringPool, ResourceArray<?> resourceArray) {
@ -195,14 +146,6 @@ class MetadataIntrinsic implements WasmIntrinsic {
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) {