From 4546029a5af470f5ea52ac7e33abdef659b99ccf Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 20 Sep 2024 20:46:24 +0200 Subject: [PATCH] wasm gc: implement String.intern --- .../backend/wasm/WasmGCModuleGenerator.java | 18 +++ .../org/teavm/backend/wasm/WasmGCTarget.java | 16 +++ .../gc/StringInternDependencySupport.java | 54 +++++++++ .../backend/wasm/gc/WasmGCDependencies.java | 5 + .../gc/classes/WasmGCClassGenerator.java | 8 +- .../gc/classes/WasmGCClassInfoProvider.java | 1 + .../gc/methods/WasmGCGenerationVisitor.java | 4 + .../gc/methods/WasmGCMethodGenerator.java | 2 +- .../generate/gc/strings/WasmGCStringPool.java | 25 +++- .../wasm/generators/gc/StringGenerator.java | 35 ++++++ .../generators/gc/WasmGCCustomGenerators.java | 6 + .../gc/StringInternPoolIntrinsic.java | 81 +++++++++++++ .../wasm/intrinsics/gc/WasmGCIntrinsics.java | 10 +- .../wasm/runtime/StringInternPool.java | 111 ++++++++++++++++++ core/src/main/java/org/teavm/vm/TeaVM.java | 8 +- .../main/java/org/teavm/vm/TeaVMTarget.java | 4 + .../org/teavm/backend/wasm/wasm-gc-runtime.js | 11 ++ 17 files changed, 391 insertions(+), 8 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/gc/StringInternDependencySupport.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/generators/gc/StringGenerator.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/StringInternPoolIntrinsic.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/runtime/StringInternPool.java diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmGCModuleGenerator.java b/core/src/main/java/org/teavm/backend/wasm/WasmGCModuleGenerator.java index e2050b3ad..062b7234f 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCModuleGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCModuleGenerator.java @@ -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; diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java index ce42896c1..872d36b94 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -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; + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/StringInternDependencySupport.java b/core/src/main/java/org/teavm/backend/wasm/gc/StringInternDependencySupport.java new file mode 100644 index 000000000..50cebcc12 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/gc/StringInternDependencySupport.java @@ -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, "", + 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())); + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java index 16af962af..9385b0344 100644 --- a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java +++ b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java @@ -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()); + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java index 95e238f02..30a5a3da9 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java @@ -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(); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java index 2e4dbd676..45bb6703b 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java @@ -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); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java index ef1ee968d..e38951336 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java @@ -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; } diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java index f8f8c549e..508bad013 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java @@ -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); } } diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/strings/WasmGCStringPool.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/strings/WasmGCStringPool.java index f3aa74348..dac10bae4 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/strings/WasmGCStringPool.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/strings/WasmGCStringPool.java @@ -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, "", + 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, "", + 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(); + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/generators/gc/StringGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generators/gc/StringGenerator.java new file mode 100644 index 000000000..a850fdfb1 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/generators/gc/StringGenerator.java @@ -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))); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/generators/gc/WasmGCCustomGenerators.java b/core/src/main/java/org/teavm/backend/wasm/generators/gc/WasmGCCustomGenerators.java index 1a6785dc8..cf00c7f48 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generators/gc/WasmGCCustomGenerators.java +++ b/core/src/main/java/org/teavm/backend/wasm/generators/gc/WasmGCCustomGenerators.java @@ -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); diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/StringInternPoolIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/StringInternPoolIntrinsic.java new file mode 100644 index 000000000..0ac50cd0b --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/StringInternPoolIntrinsic.java @@ -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; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java index 0879bbda1..a98b1a18b 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java @@ -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)); } diff --git a/core/src/main/java/org/teavm/backend/wasm/runtime/StringInternPool.java b/core/src/main/java/org/teavm/backend/wasm/runtime/StringInternPool.java new file mode 100644 index 000000000..870ec19c5 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/StringInternPool.java @@ -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); + } +} diff --git a/core/src/main/java/org/teavm/vm/TeaVM.java b/core/src/main/java/org/teavm/vm/TeaVM.java index ed5950da8..0a84ab164 100644 --- a/core/src/main/java/org/teavm/vm/TeaVM.java +++ b/core/src/main/java/org/teavm/vm/TeaVM.java @@ -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); + } } } diff --git a/core/src/main/java/org/teavm/vm/TeaVMTarget.java b/core/src/main/java/org/teavm/vm/TeaVMTarget.java index d61490ef7..320c4ff8d 100644 --- a/core/src/main/java/org/teavm/vm/TeaVMTarget.java +++ b/core/src/main/java/org/teavm/vm/TeaVMTarget.java @@ -70,4 +70,8 @@ public interface TeaVMTarget { default boolean needsSystemArrayCopyOptimization() { return true; } + + default boolean filterClassInitializer(String initializer) { + return true; + } } diff --git a/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js b/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js index c39c36334..612cd3832 100644 --- a/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js +++ b/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js @@ -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;