wasm gc: implement String.intern

This commit is contained in:
Alexey Andreev 2024-09-20 20:46:24 +02:00
parent 8ed8322b17
commit 4546029a5a
17 changed files with 391 additions and 8 deletions

View File

@ -28,6 +28,7 @@ import org.teavm.backend.wasm.model.expression.WasmFunctionReference;
import org.teavm.backend.wasm.model.expression.WasmGetGlobal;
import org.teavm.backend.wasm.model.expression.WasmGetLocal;
import org.teavm.backend.wasm.model.expression.WasmSetGlobal;
import org.teavm.backend.wasm.runtime.StringInternPool;
import org.teavm.backend.wasm.runtime.WasmGCSupport;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
@ -168,6 +169,23 @@ public class WasmGCModuleGenerator {
return caller;
}
public WasmFunction generateReportGarbageCollectedStringFunction() {
var entryType = ValueType.object(StringInternPool.class.getName() + "$Entry");
var function = declarationsGenerator.functions().forStaticMethod(new MethodReference(
StringInternPool.class.getName(),
"remove",
entryType,
ValueType.VOID
));
var caller = new WasmFunction(function.getType());
var entryLocal = new WasmLocal(declarationsGenerator.typeMapper().mapType(entryType));
caller.add(entryLocal);
caller.getBody().add(callInitializer());
caller.getBody().add(new WasmCall(function, new WasmGetLocal(entryLocal)));
declarationsGenerator.module.functions.add(caller);
return caller;
}
private void createInitializer() {
if (initializer != null) {
return;

View File

@ -37,6 +37,7 @@ import org.teavm.backend.wasm.render.WasmBinaryRenderer;
import org.teavm.backend.wasm.render.WasmBinaryStatsCollector;
import org.teavm.backend.wasm.render.WasmBinaryVersion;
import org.teavm.backend.wasm.render.WasmBinaryWriter;
import org.teavm.backend.wasm.runtime.StringInternPool;
import org.teavm.backend.wasm.transformation.gc.BaseClassesTransformation;
import org.teavm.dependency.DependencyAnalyzer;
import org.teavm.dependency.DependencyListener;
@ -209,6 +210,13 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost {
var charAtFunction = moduleGenerator.generateCharAtFunction();
charAtFunction.setExportName("charAt");
var internMethod = controller.getDependencyInfo().getMethod(new MethodReference(String.class,
"intern", String.class));
if (internMethod != null && internMethod.isUsed()) {
var removeStringEntryFunction = moduleGenerator.generateReportGarbageCollectedStringFunction();
removeStringEntryFunction.setExportName("reportGarbageCollectedString");
}
moduleGenerator.generate();
adjustModuleMemory(module);
@ -257,4 +265,12 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost {
public boolean needsSystemArrayCopyOptimization() {
return false;
}
@Override
public boolean filterClassInitializer(String initializer) {
if (initializer.equals(StringInternPool.class.getName())) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2024 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.backend.wasm.gc;
import org.teavm.backend.wasm.runtime.StringInternPool;
import org.teavm.dependency.AbstractDependencyListener;
import org.teavm.dependency.DependencyAgent;
import org.teavm.dependency.MethodDependency;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
public class StringInternDependencySupport extends AbstractDependencyListener {
private static final MethodReference STRING_INTERN = new MethodReference(String.class, "intern", String.class);
@Override
public void methodReached(DependencyAgent agent, MethodDependency method) {
if (method.getMethod().getReference().equals(STRING_INTERN)) {
var query = agent.linkMethod(new MethodReference(StringInternPool.class, "query",
String.class, String.class));
query.getVariable(1).propagate(agent.getType("java.lang.String"));
query.use();
var entryTypeName = StringInternPool.class.getName() + "$Entry";
var remove = agent.linkMethod(new MethodReference(
StringInternPool.class.getName(),
"remove",
ValueType.object(entryTypeName),
ValueType.VOID));
remove.getVariable(1).propagate(agent.getType(entryTypeName));
remove.use();
var clinit = agent.linkMethod(new MethodReference(StringInternPool.class, "<clinit>",
void.class));
clinit.use();
var getValue = agent.linkMethod(new MethodReference(StringInternPool.class.getName() + "$Entry",
"getValue", ValueType.parse(String.class)));
getValue.getResult().propagate(agent.getType(String.class.getName()));
}
}
}

View File

@ -35,6 +35,7 @@ public class WasmGCDependencies {
contributeMathUtils();
contributeExceptionUtils();
contributeInitializerUtils();
contributeString();
analyzer.addDependencyListener(new WasmGCReferenceQueueDependency());
}
@ -107,4 +108,8 @@ public class WasmGCDependencies {
private void contributeInitializerUtils() {
analyzer.linkMethod(new MethodReference(WasmGCSupport.class, "nextCharArray", char[].class)).use();
}
private void contributeString() {
analyzer.addDependencyListener(new StringInternDependencySupport());
}
}

View File

@ -75,6 +75,7 @@ import org.teavm.backend.wasm.model.expression.WasmStructGet;
import org.teavm.backend.wasm.model.expression.WasmStructNew;
import org.teavm.backend.wasm.model.expression.WasmStructNewDefault;
import org.teavm.backend.wasm.model.expression.WasmStructSet;
import org.teavm.backend.wasm.runtime.StringInternPool;
import org.teavm.backend.wasm.runtime.WasmGCSupport;
import org.teavm.dependency.DependencyInfo;
import org.teavm.model.ClassHierarchy;
@ -173,7 +174,8 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
this.names = names;
this.classInitializerInfo = classInitializerInfo;
standardClasses = new WasmGCStandardClasses(this);
strings = new WasmGCStringPool(standardClasses, module, functionProvider, names, functionTypes);
strings = new WasmGCStringPool(standardClasses, module, functionProvider, names, functionTypes,
dependencyInfo);
supertypeGenerator = new WasmGCSupertypeFunctionGenerator(module, this, names, tagRegistry, functionTypes);
newArrayGenerator = new WasmGCNewArrayFunctionGenerator(module, functionTypes, this, names, queue);
typeMapper = new WasmGCTypeMapper(classSource, this, functionTypes, module);
@ -1138,6 +1140,10 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
fields.add(wasmField);
}
}
if (className.equals(StringInternPool.class.getName() + "$Entry")) {
var field = new WasmField(WasmType.Reference.EXTERN.asStorage(), "nativeRef");
fields.add(field);
}
if (className.equals("java.lang.Class")) {
var cls = classSource.get("java.lang.Class");
classFlagsOffset = fields.size();

View File

@ -27,6 +27,7 @@ public interface WasmGCClassInfoProvider {
int CUSTOM_FIELD_OFFSETS = 2;
int ARRAY_DATA_FIELD_OFFSET = 2;
int WEAK_REFERENCE_OFFSET = 2;
int STRING_POOL_ENTRY_OFFSET = 5;
WasmGCClassInfo getClassInfo(ValueType type);

View File

@ -80,6 +80,7 @@ import org.teavm.backend.wasm.model.expression.WasmStructSet;
import org.teavm.backend.wasm.model.expression.WasmTest;
import org.teavm.backend.wasm.model.expression.WasmThrow;
import org.teavm.backend.wasm.model.expression.WasmUnreachable;
import org.teavm.backend.wasm.runtime.StringInternPool;
import org.teavm.model.ClassHierarchy;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldReference;
@ -539,6 +540,9 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor {
@Override
protected boolean needsClassInitializer(String className) {
if (className.equals(StringInternPool.class.getName())) {
return false;
}
return context.classInfoProvider().getClassInfo(className).getInitializerPointer() != null;
}

View File

@ -217,7 +217,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository {
var printWriter = new PrintWriter(buffer);
e.printStackTrace(printWriter);
diagnostics.error(new CallLocation(method.getReference()),
"Failed generating method body due to internal exception: " + buffer.toString());
"Failed generating method body due to internal exception: " + buffer);
}
}

View File

@ -29,12 +29,15 @@ import org.teavm.backend.wasm.model.WasmLocal;
import org.teavm.backend.wasm.model.WasmMemorySegment;
import org.teavm.backend.wasm.model.WasmModule;
import org.teavm.backend.wasm.model.expression.WasmCall;
import org.teavm.backend.wasm.model.expression.WasmDrop;
import org.teavm.backend.wasm.model.expression.WasmGetGlobal;
import org.teavm.backend.wasm.model.expression.WasmGetLocal;
import org.teavm.backend.wasm.model.expression.WasmStructNewDefault;
import org.teavm.backend.wasm.model.expression.WasmStructSet;
import org.teavm.backend.wasm.render.WasmBinaryWriter;
import org.teavm.backend.wasm.runtime.StringInternPool;
import org.teavm.backend.wasm.runtime.WasmGCSupport;
import org.teavm.dependency.DependencyInfo;
import org.teavm.model.MethodReference;
public class WasmGCStringPool implements WasmGCStringProvider, WasmGCInitializerContributor {
@ -46,15 +49,17 @@ public class WasmGCStringPool implements WasmGCStringProvider, WasmGCInitializer
private WasmFunction initNextStringFunction;
private WasmGCNameProvider names;
private WasmFunctionTypes functionTypes;
private DependencyInfo dependencyInfo;
public WasmGCStringPool(WasmGCStandardClasses standardClasses, WasmModule module,
BaseWasmFunctionRepository functionProvider, WasmGCNameProvider names,
WasmFunctionTypes functionTypes) {
WasmFunctionTypes functionTypes, DependencyInfo dependencyInfo) {
this.standardClasses = standardClasses;
this.module = module;
this.functionProvider = functionProvider;
this.names = names;
this.functionTypes = functionTypes;
this.dependencyInfo = dependencyInfo;
}
@Override
@ -69,7 +74,11 @@ public class WasmGCStringPool implements WasmGCStringProvider, WasmGCInitializer
if (initNextStringFunction == null) {
return;
}
var stringStruct = standardClasses.stringClass().getStructure();
if (hasIntern()) {
var internInit = functionProvider.forStaticMethod(new MethodReference(StringInternPool.class, "<clinit>",
void.class));
function.getBody().add(new WasmCall(internInit));
}
for (var str : stringMap.values()) {
function.getBody().add(new WasmCall(initNextStringFunction, new WasmGetGlobal(str.global)));
}
@ -127,7 +136,19 @@ public class WasmGCStringPool implements WasmGCStringProvider, WasmGCInitializer
function.getBody().add(new WasmStructSet(stringTypeInfo.getStructure(), new WasmGetLocal(stringLocal),
WasmGCClassInfoProvider.CLASS_FIELD_OFFSET,
new WasmGetGlobal(stringTypeInfo.getPointer())));
if (hasIntern()) {
var queryFunction = functionProvider.forStaticMethod(new MethodReference(StringInternPool.class,
"query", String.class, String.class));
function.getBody().add(new WasmDrop(new WasmCall(queryFunction, new WasmGetLocal(stringLocal))));
functionProvider.forStaticMethod(new MethodReference(StringInternPool.class, "<clinit>",
void.class));
}
module.functions.add(function);
initNextStringFunction = function;
}
private boolean hasIntern() {
var intern = dependencyInfo.getMethod(new MethodReference(String.class, "intern", String.class));
return intern != null && intern.isUsed();
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2024 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.backend.wasm.generators.gc;
import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.model.WasmLocal;
import org.teavm.backend.wasm.model.expression.WasmCall;
import org.teavm.backend.wasm.model.expression.WasmGetLocal;
import org.teavm.backend.wasm.runtime.StringInternPool;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
public class StringGenerator implements WasmGCCustomGenerator {
@Override
public void apply(MethodReference method, WasmFunction function, WasmGCCustomGeneratorContext context) {
var worker = context.functions().forStaticMethod(new MethodReference(StringInternPool.class,
"query", String.class, String.class));
var instanceLocal = new WasmLocal(context.typeMapper().mapType(ValueType.parse(String.class)), "this");
function.add(instanceLocal);
function.getBody().add(new WasmCall(worker, new WasmGetLocal(instanceLocal)));
}
}

View File

@ -44,6 +44,7 @@ public class WasmGCCustomGenerators implements WasmGCCustomGeneratorProvider {
fillSystem();
fillArray();
fillWeakReference();
fillString();
for (var entry : generators.entrySet()) {
add(entry.getKey(), entry.getValue());
}
@ -79,6 +80,11 @@ public class WasmGCCustomGenerators implements WasmGCCustomGeneratorProvider {
add(new MethodReference(WeakReference.class, "clear", void.class), generator);
}
private void fillString() {
var generator = new StringGenerator();
add(new MethodReference(String.class, "intern", String.class), generator);
}
@Override
public WasmGCCustomGenerator get(MethodReference method) {
var result = generators.get(method);

View File

@ -0,0 +1,81 @@
/*
* Copyright 2024 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.backend.wasm.intrinsics.gc;
import org.teavm.ast.InvocationExpr;
import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider;
import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.model.WasmType;
import org.teavm.backend.wasm.model.expression.WasmBlock;
import org.teavm.backend.wasm.model.expression.WasmCall;
import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmStructGet;
import org.teavm.backend.wasm.model.expression.WasmStructSet;
import org.teavm.backend.wasm.runtime.StringInternPool;
import org.teavm.model.ValueType;
class StringInternPoolIntrinsic implements WasmGCIntrinsic {
@Override
public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) {
var entryStruct = context.classInfoProvider().getClassInfo(StringInternPool.class.getName() + "$Entry")
.getStructure();
switch (invocation.getMethod().getName()) {
case "getValue": {
var weakRef = new WasmStructGet(entryStruct, context.generate(invocation.getArguments().get(0)),
WasmGCClassInfoProvider.STRING_POOL_ENTRY_OFFSET);
return new WasmCall(createDerefFunction(context), weakRef);
}
case "setValue": {
var block = new WasmBlock(false);
var instance = context.exprCache().create(context.generate(invocation.getArguments().get(0)),
entryStruct.getReference(), invocation.getLocation(), block.getBody());
var value = context.generate(invocation.getArguments().get(1));
var ref = new WasmCall(createRefFunction(context), value, instance.expr());
block.getBody().add(new WasmStructSet(entryStruct, instance.expr(),
WasmGCClassInfoProvider.STRING_POOL_ENTRY_OFFSET, ref));
instance.release();
return block;
}
default:
throw new IllegalArgumentException();
}
}
private WasmFunction createRefFunction(WasmGCIntrinsicContext context) {
var function = new WasmFunction(context.functionTypes().of(
WasmType.Reference.EXTERN,
context.typeMapper().mapType(ValueType.parse(String.class)),
context.typeMapper().mapType(ValueType.object(StringInternPool.class.getName() + "$Entry"))
));
function.setName(context.names().topLevel("teavm@stringRef"));
function.setImportModule("teavm");
function.setImportName("createStringWeakRef");
context.module().functions.add(function);
return function;
}
private WasmFunction createDerefFunction(WasmGCIntrinsicContext context) {
var function = new WasmFunction(context.functionTypes().of(
context.typeMapper().mapType(ValueType.parse(String.class)),
WasmType.Reference.EXTERN
));
function.setName(context.names().topLevel("teavm@stringDeref"));
function.setImportModule("teavm");
function.setImportName("stringDeref");
context.module().functions.add(function);
return function;
}
}

View File

@ -22,6 +22,7 @@ import java.util.Map;
import org.teavm.backend.wasm.WasmRuntime;
import org.teavm.backend.wasm.generate.gc.methods.WasmGCIntrinsicProvider;
import org.teavm.backend.wasm.model.expression.WasmIntType;
import org.teavm.backend.wasm.runtime.StringInternPool;
import org.teavm.common.ServiceRepository;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.MethodReference;
@ -38,7 +39,6 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider {
this.classes = classes;
this.services = services;
this.factories = List.copyOf(factories);
factories = List.copyOf(factories);
fillWasmRuntime();
fillObject();
fillClass();
@ -48,6 +48,7 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider {
fillFloat();
fillDouble();
fillArray();
fillString();
for (var entry : customIntrinsics.entrySet()) {
add(entry.getKey(), entry.getValue());
}
@ -148,6 +149,13 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider {
add(new MethodReference(Array.class, "getImpl", Object.class, int.class, Object.class), intrinsic);
}
private void fillString() {
var intrinsic = new StringInternPoolIntrinsic();
var className = StringInternPool.class.getName() + "$Entry";
add(new MethodReference(className, "getValue", ValueType.parse(String.class)), intrinsic);
add(new MethodReference(className, "setValue", ValueType.parse(String.class), ValueType.VOID), intrinsic);
}
private void add(MethodReference methodRef, WasmGCIntrinsic intrinsic) {
intrinsics.put(methodRef, new IntrinsicContainer(intrinsic));
}

View File

@ -0,0 +1,111 @@
/*
* Copyright 2024 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.backend.wasm.runtime;
public final class StringInternPool {
private static Entry[] table = new Entry[16];
private static int occupiedCells;
private static int occupiedCellsThreshold = table.length * 3 / 4;
private StringInternPool() {
}
public static String query(String s) {
var hash = s.hashCode();
var index = Integer.remainderUnsigned(hash, table.length);
var entry = table[index];
while (entry != null) {
if (entry.hash == hash) {
var value = entry.getValue();
if (value.equals(s)) {
return value;
}
}
entry = entry.next;
}
if (table[index] == null) {
if (++occupiedCells > occupiedCellsThreshold) {
rehash();
}
index = Integer.remainderUnsigned(hash, table.length);
if (table[index] == null) {
++occupiedCells;
}
}
table[index] = new Entry(index, hash, table[index], s);
return s;
}
private static void rehash() {
var oldTable = table;
table = new Entry[oldTable.length * 2];
occupiedCells = 0;
occupiedCellsThreshold = table.length * 3 / 4;
for (var value : oldTable) {
var entry = value;
while (entry != null) {
var next = entry.next;
var index = Integer.remainderUnsigned(entry.hash, table.length);
entry.index = index;
entry.next = table[index];
if (entry.next == null) {
++occupiedCells;
}
table[index] = entry;
entry = next;
}
}
}
static void remove(Entry entry) {
var index = entry.index;
Entry previous = null;
var e = table[index];
while (e != null) {
if (e == entry) {
if (previous == null) {
table[index] = e.next;
if (e.next == null) {
--occupiedCells;
}
} else {
previous.next = e.next;
}
break;
}
previous = e;
e = e.next;
}
}
static class Entry {
int index;
final int hash;
Entry next;
Entry(int index, int hash, Entry next, String str) {
this.index = index;
this.hash = hash;
this.next = next;
setValue(str);
}
final native String getValue();
final native void setValue(String str);
}
}

View File

@ -574,9 +574,11 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
BasicBlock block = program.basicBlockAt(0);
Instruction first = block.getFirstInstruction();
for (String className : classInitializerInfo.getInitializationOrder()) {
var invoke = new InvokeInstruction();
invoke.setMethod(new MethodReference(className, CLINIT_DESC));
first.insertPrevious(invoke);
if (target.filterClassInitializer(className)) {
var invoke = new InvokeInstruction();
invoke.setMethod(new MethodReference(className, CLINIT_DESC));
first.insertPrevious(invoke);
}
}
}

View File

@ -70,4 +70,8 @@ public interface TeaVMTarget {
default boolean needsSystemArrayCopyOptimization() {
return true;
}
default boolean filterClassInitializer(String initializer) {
return true;
}
}

View File

@ -25,6 +25,9 @@ TeaVM.wasm = function() {
exports.reportGarbageCollectedValue(heldValue)
}
});
let stringFinalizationRegistry = new FinalizationRegistry(heldValue => {
exports.reportGarbageCollectedString(heldValue);
});
imports.teavm = {
putcharStderr(c) {
if (c === 10) {
@ -57,6 +60,14 @@ TeaVM.wasm = function() {
},
deref(weakRef) {
return weakRef.deref();
},
createStringWeakRef(value, heldValue) {
let weakRef = new WeakRef(value);
stringFinalizationRegistry.register(value, heldValue)
return weakRef;
},
stringDeref(weakRef) {
return weakRef.deref();
}
};
imports.teavmMath = Math;