diff --git a/classlib/src/main/java/org/teavm/classlib/impl/console/Console.java b/classlib/src/main/java/org/teavm/classlib/impl/console/Console.java index 310533d8a..b72eea1db 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/console/Console.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/console/Console.java @@ -16,8 +16,8 @@ package org.teavm.classlib.impl.console; import org.teavm.backend.c.intrinsic.RuntimeInclude; -import org.teavm.backend.wasm.runtime.WasmGCSupport; import org.teavm.backend.wasm.runtime.WasmSupport; +import org.teavm.backend.wasm.runtime.gc.WasmGCSupport; import org.teavm.classlib.PlatformDetector; import org.teavm.interop.Address; import org.teavm.interop.Import; diff --git a/classlib/src/main/java/org/teavm/classlib/impl/console/JSStderrPrintStream.java b/classlib/src/main/java/org/teavm/classlib/impl/console/JSStderrPrintStream.java index f86fe035a..5f3cd3bb9 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/console/JSStderrPrintStream.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/console/JSStderrPrintStream.java @@ -15,7 +15,7 @@ */ package org.teavm.classlib.impl.console; -import org.teavm.backend.wasm.runtime.WasmGCSupport; +import org.teavm.backend.wasm.runtime.gc.WasmGCSupport; import org.teavm.classlib.PlatformDetector; import org.teavm.jso.JSBody; diff --git a/classlib/src/main/java/org/teavm/classlib/impl/console/JSStdoutPrintStream.java b/classlib/src/main/java/org/teavm/classlib/impl/console/JSStdoutPrintStream.java index 9e3b99627..4dddcdb5b 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/console/JSStdoutPrintStream.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/console/JSStdoutPrintStream.java @@ -15,7 +15,7 @@ */ package org.teavm.classlib.impl.console; -import org.teavm.backend.wasm.runtime.WasmGCSupport; +import org.teavm.backend.wasm.runtime.gc.WasmGCSupport; import org.teavm.classlib.PlatformDetector; import org.teavm.jso.JSBody; diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java index 5cadc4cc2..7e1051975 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java @@ -15,7 +15,7 @@ */ package org.teavm.classlib.java.lang; -import org.teavm.backend.wasm.runtime.WasmGCSupport; +import org.teavm.backend.wasm.runtime.gc.WasmGCSupport; import org.teavm.classlib.PlatformDetector; import org.teavm.dependency.PluggableDependency; import org.teavm.interop.Address; 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 062b7234f..1713ee6e4 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCModuleGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCModuleGenerator.java @@ -29,7 +29,7 @@ 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.backend.wasm.runtime.gc.WasmGCSupport; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; 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 872d36b94..32bab4292 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -39,6 +39,7 @@ 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.backend.wasm.transformation.gc.ClassLoaderResourceTransformation; import org.teavm.dependency.DependencyAnalyzer; import org.teavm.dependency.DependencyListener; import org.teavm.interop.Platforms; @@ -119,7 +120,8 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { @Override public List getTransformers() { return List.of( - new BaseClassesTransformation() + new BaseClassesTransformation(), + new ClassLoaderResourceTransformation() ); } @@ -167,7 +169,8 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { public void emit(ListableClassHolderSource classes, BuildTarget buildTarget, String outputName) throws IOException { var module = new WasmModule(); var customGenerators = new WasmGCCustomGenerators(classes, controller.getServices(), - customGeneratorFactories, customCustomGenerators); + customGeneratorFactories, customCustomGenerators, + controller.getProperties()); var intrinsics = new WasmGCIntrinsics(classes, controller.getServices(), intrinsicFactories, customIntrinsics); var declarationsGenerator = new WasmGCDeclarationsGenerator( module, @@ -218,6 +221,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { } moduleGenerator.generate(); + customGenerators.contributeToModule(module); adjustModuleMemory(module); emitWasmFile(module, buildTarget, outputName); 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 9385b0344..5582c526f 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 @@ -18,7 +18,7 @@ package org.teavm.backend.wasm.gc; import java.util.Arrays; import java.util.List; import org.teavm.backend.wasm.WasmRuntime; -import org.teavm.backend.wasm.runtime.WasmGCSupport; +import org.teavm.backend.wasm.runtime.gc.WasmGCSupport; import org.teavm.dependency.DependencyAnalyzer; import org.teavm.model.MethodReference; @@ -37,6 +37,7 @@ public class WasmGCDependencies { contributeInitializerUtils(); contributeString(); analyzer.addDependencyListener(new WasmGCReferenceQueueDependency()); + analyzer.addDependencyListener(new WasmGCResourceDependency()); } public void contributeStandardExports() { diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCResourceDependency.java b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCResourceDependency.java new file mode 100644 index 000000000..32536b83e --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCResourceDependency.java @@ -0,0 +1,39 @@ +/* + * 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.gc.WasmGCResources; +import org.teavm.dependency.AbstractDependencyListener; +import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.MethodDependency; +import org.teavm.model.MethodReference; + +public class WasmGCResourceDependency extends AbstractDependencyListener { + private static final MethodReference ACQUIRE_METHOD = new MethodReference(WasmGCResources.class, + "acquireResources", WasmGCResources.Resource[].class); + + @Override + public void methodReached(DependencyAgent agent, MethodDependency method) { + if (method.getMethod().getReference().equals(ACQUIRE_METHOD)) { + var create = agent.linkMethod(new MethodReference(WasmGCResources.class, + "create", String.class, int.class, int.class, WasmGCResources.Resource.class)); + create.propagate(1, agent.getType("java.lang.String")); + create.use(); + method.getResult().propagate(agent.getType("[" + WasmGCResources.Resource.class.getName())); + method.getResult().getArrayItem().propagate(agent.getType(WasmGCResources.Resource.class.getName())); + } + } +} 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 1e93c731b..ecd4a2ae5 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 @@ -76,7 +76,7 @@ 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.backend.wasm.runtime.gc.WasmGCSupport; import org.teavm.dependency.DependencyInfo; import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassReader; diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java index a84eb7fd6..d87586291 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java @@ -34,7 +34,7 @@ import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringProvider; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmModule; import org.teavm.backend.wasm.model.WasmTag; -import org.teavm.backend.wasm.runtime.WasmGCSupport; +import org.teavm.backend.wasm.runtime.gc.WasmGCSupport; import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; 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 508bad013..a0b386c7f 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 @@ -52,6 +52,7 @@ import org.teavm.model.CallLocation; import org.teavm.model.ClassHierarchy; import org.teavm.model.ElementModifier; import org.teavm.model.ListableClassHolderSource; +import org.teavm.model.ListableClassReaderSource; import org.teavm.model.MethodHolder; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; @@ -353,6 +354,16 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { } private WasmGCCustomGeneratorContext customGeneratorContext = new WasmGCCustomGeneratorContext() { + @Override + public ClassLoader classLoader() { + return classLoader; + } + + @Override + public ListableClassReaderSource classes() { + return classes; + } + @Override public WasmModule module() { return module; @@ -387,5 +398,15 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { public BaseWasmFunctionRepository functions() { return WasmGCMethodGenerator.this; } + + @Override + public Diagnostics diagnostics() { + return diagnostics; + } + + @Override + public WasmGCStringProvider strings() { + return context.strings(); + } }; } 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 dac10bae4..5dffa518e 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 @@ -36,7 +36,7 @@ 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.backend.wasm.runtime.gc.WasmGCSupport; import org.teavm.dependency.DependencyInfo; import org.teavm.model.MethodReference; diff --git a/core/src/main/java/org/teavm/backend/wasm/generators/gc/ClassGenerators.java b/core/src/main/java/org/teavm/backend/wasm/generators/gc/ClassGenerators.java index f146411a2..3140face3 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generators/gc/ClassGenerators.java +++ b/core/src/main/java/org/teavm/backend/wasm/generators/gc/ClassGenerators.java @@ -25,7 +25,7 @@ import org.teavm.backend.wasm.model.expression.WasmGetLocal; import org.teavm.backend.wasm.model.expression.WasmIsNull; import org.teavm.backend.wasm.model.expression.WasmStructGet; import org.teavm.backend.wasm.model.expression.WasmThrow; -import org.teavm.backend.wasm.runtime.WasmGCSupport; +import org.teavm.backend.wasm.runtime.gc.WasmGCSupport; import org.teavm.model.MethodReference; public class ClassGenerators implements WasmGCCustomGenerator { diff --git a/core/src/main/java/org/teavm/backend/wasm/generators/gc/WasmGCCustomGeneratorContext.java b/core/src/main/java/org/teavm/backend/wasm/generators/gc/WasmGCCustomGeneratorContext.java index 8249e03da..389d94924 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generators/gc/WasmGCCustomGeneratorContext.java +++ b/core/src/main/java/org/teavm/backend/wasm/generators/gc/WasmGCCustomGeneratorContext.java @@ -20,10 +20,17 @@ import org.teavm.backend.wasm.WasmFunctionTypes; import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper; +import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringProvider; import org.teavm.backend.wasm.model.WasmModule; import org.teavm.backend.wasm.model.WasmTag; +import org.teavm.diagnostics.Diagnostics; +import org.teavm.model.ListableClassReaderSource; public interface WasmGCCustomGeneratorContext { + ClassLoader classLoader(); + + ListableClassReaderSource classes(); + WasmModule module(); WasmFunctionTypes functionTypes(); @@ -37,4 +44,8 @@ public interface WasmGCCustomGeneratorContext { BaseWasmFunctionRepository functions(); WasmTag exceptionTag(); + + Diagnostics diagnostics(); + + WasmGCStringProvider strings(); } 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 cf00c7f48..19809cdd0 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 @@ -21,8 +21,11 @@ import java.lang.reflect.Array; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Properties; import org.teavm.backend.wasm.generate.gc.methods.WasmGCCustomGeneratorProvider; -import org.teavm.backend.wasm.runtime.WasmGCSupport; +import org.teavm.backend.wasm.model.WasmModule; +import org.teavm.backend.wasm.runtime.gc.WasmGCResources; +import org.teavm.backend.wasm.runtime.gc.WasmGCSupport; import org.teavm.common.ServiceRepository; import org.teavm.model.ClassReaderSource; import org.teavm.model.MethodReference; @@ -32,24 +35,32 @@ public class WasmGCCustomGenerators implements WasmGCCustomGeneratorProvider { private Map generators = new HashMap<>(); private ClassReaderSource classes; private ServiceRepository services; + private WasmGCResourcesGenerator resourcesGenerator; public WasmGCCustomGenerators(ClassReaderSource classes, ServiceRepository services, List factories, - Map generators) { + Map generators, + Properties properties) { this.factories = List.copyOf(factories); this.classes = classes; this.services = services; + resourcesGenerator = new WasmGCResourcesGenerator(properties); fillClass(); fillStringPool(); fillSystem(); fillArray(); fillWeakReference(); fillString(); + fillResources(); for (var entry : generators.entrySet()) { add(entry.getKey(), entry.getValue()); } } + public void contributeToModule(WasmModule module) { + resourcesGenerator.writeModule(module); + } + private void fillClass() { var classGenerators = new ClassGenerators(); add(new MethodReference(Class.class, "isAssignableFrom", Class.class, boolean.class), classGenerators); @@ -85,6 +96,11 @@ public class WasmGCCustomGenerators implements WasmGCCustomGeneratorProvider { add(new MethodReference(String.class, "intern", String.class), generator); } + private void fillResources() { + add(new MethodReference(WasmGCResources.class, "acquireResources", WasmGCResources.Resource[].class), + resourcesGenerator); + } + @Override public WasmGCCustomGenerator get(MethodReference method) { var result = generators.get(method); diff --git a/core/src/main/java/org/teavm/backend/wasm/generators/gc/WasmGCResourcesGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generators/gc/WasmGCResourcesGenerator.java new file mode 100644 index 000000000..dc824ef59 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/generators/gc/WasmGCResourcesGenerator.java @@ -0,0 +1,174 @@ +/* + * 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 java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Properties; +import java.util.ServiceLoader; +import org.teavm.backend.wasm.generate.TemporaryVariablePool; +import org.teavm.backend.wasm.generate.gc.methods.WasmGCGenerationUtil; +import org.teavm.backend.wasm.model.WasmFunction; +import org.teavm.backend.wasm.model.WasmGlobal; +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.WasmType; +import org.teavm.backend.wasm.model.expression.WasmCall; +import org.teavm.backend.wasm.model.expression.WasmExpression; +import org.teavm.backend.wasm.model.expression.WasmGetGlobal; +import org.teavm.backend.wasm.model.expression.WasmGetLocal; +import org.teavm.backend.wasm.model.expression.WasmInt32Constant; +import org.teavm.backend.wasm.model.expression.WasmIntBinary; +import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation; +import org.teavm.backend.wasm.model.expression.WasmIntType; +import org.teavm.backend.wasm.runtime.gc.WasmGCResources; +import org.teavm.classlib.ResourceSupplier; +import org.teavm.classlib.ResourceSupplierContext; +import org.teavm.model.CallLocation; +import org.teavm.model.ListableClassReaderSource; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; + +public class WasmGCResourcesGenerator implements WasmGCCustomGenerator { + private Properties properties; + private ByteArrayOutputStream resources = new ByteArrayOutputStream(); + private WasmGlobal baseGlobal; + + public WasmGCResourcesGenerator(Properties properties) { + this.properties = properties; + } + + public void writeModule(WasmModule module) { + if (resources.size() == 0) { + return; + } + + var segment = new WasmMemorySegment(); + if (!module.getSegments().isEmpty()) { + var lastSegment = module.getSegments().get(module.getSegments().size() - 1); + segment.setOffset(lastSegment.getOffset() + lastSegment.getLength()); + } + segment.setData(resources.toByteArray()); + module.getSegments().add(segment); + baseGlobal.setInitialValue(new WasmInt32Constant(segment.getOffset())); + } + + @Override + public void apply(MethodReference method, WasmFunction function, WasmGCCustomGeneratorContext context) { + var supplierContext = new SupplierContextImpl(context.classLoader(), context.classes(), properties); + var resourceSet = new LinkedHashSet(); + for (var supplier : ServiceLoader.load(ResourceSupplier.class, context.classLoader())) { + var resources = supplier.supplyResources(supplierContext); + if (resources != null) { + resourceSet.addAll(Arrays.asList(resources)); + } + } + + var descriptors = new ArrayList(); + var location = new CallLocation(new MethodReference(ClassLoader.class, + "getResourceAsStream", String.class, InputStream.class)); + for (var resource : resourceSet) { + try (var input = context.classLoader().getResourceAsStream(resource)) { + if (input == null) { + context.diagnostics().error(location, "Resource not found: " + resource); + } else { + var start = resources.size(); + input.transferTo(resources); + var end = resources.size(); + descriptors.add(new ResourceDescriptor(resource, start, end)); + } + } catch (IOException e) { + context.diagnostics().error(location, "Error occurred reading resource '" + resource + "'"); + } + } + + if (!descriptors.isEmpty()) { + baseGlobal = new WasmGlobal(context.names().topLevel("teavm@resourcesBaseAddress"), + WasmType.INT32, new WasmInt32Constant(0)); + context.module().globals.add(baseGlobal); + + var genUtil = new WasmGCGenerationUtil(context.classInfoProvider(), new TemporaryVariablePool(function)); + var local = new WasmLocal(context.typeMapper().mapType(ValueType.parse(WasmGCResources.Resource[].class))); + function.add(local); + var constructor = context.functions().forStaticMethod(new MethodReference(WasmGCResources.class, + "create", String.class, int.class, int.class, WasmGCResources.Resource.class)); + + genUtil.allocateArrayWithElements( + ValueType.parse(WasmGCResources.Resource.class), + () -> { + var items = new ArrayList(); + for (var descriptor : descriptors) { + var name = context.strings().getStringConstant(descriptor.name); + var offset = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD, + new WasmGetGlobal(baseGlobal), new WasmInt32Constant(descriptor.address)); + var end = new WasmInt32Constant(descriptor.end - descriptor.address); + items.add(new WasmCall(constructor, new WasmGetGlobal(name.global), offset, end)); + } + return items; + }, + null, + local, + function.getBody() + ); + function.getBody().add(new WasmGetLocal(local)); + } + } + + private static class ResourceDescriptor { + String name; + int address; + int end; + + ResourceDescriptor(String name, int address, int end) { + this.name = name; + this.address = address; + this.end = end; + } + } + + static class SupplierContextImpl implements ResourceSupplierContext { + private ClassLoader classLoader; + private ListableClassReaderSource classSource; + private Properties properties; + + SupplierContextImpl(ClassLoader classLoader, ListableClassReaderSource classSource, + Properties properties) { + this.classLoader = classLoader; + this.classSource = classSource; + this.properties = properties; + } + + @Override + public ClassLoader getClassLoader() { + return classLoader; + } + + @Override + public ListableClassReaderSource getClassSource() { + return classSource; + } + + @Override + public Properties getProperties() { + return properties; + } + } +} 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 a98b1a18b..c1646e76b 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 @@ -23,6 +23,7 @@ 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.backend.wasm.runtime.gc.WasmGCResources; import org.teavm.common.ServiceRepository; import org.teavm.model.ClassReaderSource; import org.teavm.model.MethodReference; @@ -49,6 +50,7 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider { fillDouble(); fillArray(); fillString(); + fillResources(); for (var entry : customIntrinsics.entrySet()) { add(entry.getKey(), entry.getValue()); } @@ -156,6 +158,11 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider { add(new MethodReference(className, "setValue", ValueType.parse(String.class), ValueType.VOID), intrinsic); } + private void fillResources() { + var intrinsic = new WasmGCResourcesIntrinsic(); + add(new MethodReference(WasmGCResources.class, "readSingleByte", int.class, int.class), 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/intrinsics/gc/WasmGCResourcesIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCResourcesIntrinsic.java new file mode 100644 index 000000000..ceac5ecff --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCResourcesIntrinsic.java @@ -0,0 +1,28 @@ +/* + * Copyright 2024 konsoletyper. + * + * 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.model.expression.WasmExpression; +import org.teavm.backend.wasm.model.expression.WasmInt32Subtype; +import org.teavm.backend.wasm.model.expression.WasmLoadInt32; + +public class WasmGCResourcesIntrinsic implements WasmGCIntrinsic { + @Override + public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) { + return new WasmLoadInt32(1, context.generate(invocation.getArguments().get(0)), WasmInt32Subtype.UINT8); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/runtime/gc/WasmGCResources.java b/core/src/main/java/org/teavm/backend/wasm/runtime/gc/WasmGCResources.java new file mode 100644 index 000000000..04cfe4650 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/gc/WasmGCResources.java @@ -0,0 +1,94 @@ +/* + * 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.gc; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +public final class WasmGCResources { + private static Map resources = new HashMap<>(); + + private WasmGCResources() { + } + + static { + for (var resource : acquireResources()) { + resources.put(resource.name, resource); + } + } + + private static native Resource[] acquireResources(); + + private static Resource create(String name, int start, int length) { + return new Resource(name, start, start + length); + } + + public static InputStream getResource(String name) { + var resource = resources.get(name); + if (resource == null) { + return null; + } + return new ResourceInputStream(resource.start, resource.end); + } + + public static class Resource { + public final String name; + public final int start; + public final int end; + + public Resource(String name, int start, int end) { + this.name = name; + this.start = start; + this.end = end; + } + } + + public static final class ResourceInputStream extends InputStream { + private int address; + private int end; + + private ResourceInputStream(int address, int end) { + this.address = address; + this.end = end; + } + + @Override + public int read() throws IOException { + return address < end ? readSingleByte(address++) : -1; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (address >= end) { + return -1; + } + var bytesToRead = Math.min(len, end - address); + readBytes(b, off, bytesToRead, address); + address += bytesToRead; + return bytesToRead; + } + } + + private static void readBytes(byte[] bytes, int off, int len, int fromAddress) { + for (int i = 0; i < len; ++i) { + bytes[i] = (byte) readSingleByte(fromAddress++); + } + } + + private static native int readSingleByte(int address); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/runtime/WasmGCSupport.java b/core/src/main/java/org/teavm/backend/wasm/runtime/gc/WasmGCSupport.java similarity index 98% rename from core/src/main/java/org/teavm/backend/wasm/runtime/WasmGCSupport.java rename to core/src/main/java/org/teavm/backend/wasm/runtime/gc/WasmGCSupport.java index 62a2538a3..6cd582d0f 100644 --- a/core/src/main/java/org/teavm/backend/wasm/runtime/WasmGCSupport.java +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/gc/WasmGCSupport.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.teavm.backend.wasm.runtime; +package org.teavm.backend.wasm.runtime.gc; import org.teavm.interop.Import; diff --git a/core/src/main/java/org/teavm/backend/wasm/transformation/gc/ClassLoaderResourceTransformation.java b/core/src/main/java/org/teavm/backend/wasm/transformation/gc/ClassLoaderResourceTransformation.java new file mode 100644 index 000000000..f37c472c5 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/transformation/gc/ClassLoaderResourceTransformation.java @@ -0,0 +1,60 @@ +/* + * 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.transformation.gc; + +import java.io.InputStream; +import org.teavm.backend.wasm.runtime.gc.WasmGCResources; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.instructions.ExitInstruction; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.InvokeInstruction; + +public class ClassLoaderResourceTransformation implements ClassHolderTransformer { + @Override + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { + if (cls.getName().equals(ClassLoader.class.getName())) { + var method = cls.getMethod(new MethodDescriptor("getResourceAsStream", String.class, InputStream.class)); + transformGetResourceAsStream(method); + } + } + + private void transformGetResourceAsStream(MethodHolder method) { + var program = new Program(); + program.createVariable(); + var nameVar = program.createVariable(); + var block = program.createBasicBlock(); + + var invoke = new InvokeInstruction(); + invoke.setType(InvocationType.SPECIAL); + invoke.setMethod(new MethodReference(WasmGCResources.class, "getResource", String.class, + InputStream.class)); + invoke.setArguments(nameVar); + invoke.setReceiver(program.createVariable()); + block.add(invoke); + + var exit = new ExitInstruction(); + exit.setValueToReturn(invoke.getReceiver()); + block.add(exit); + + method.setProgram(program); + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/PlatformDetector.java b/core/src/main/java/org/teavm/classlib/PlatformDetector.java similarity index 100% rename from classlib/src/main/java/org/teavm/classlib/PlatformDetector.java rename to core/src/main/java/org/teavm/classlib/PlatformDetector.java diff --git a/classlib/src/main/java/org/teavm/classlib/ReflectionContext.java b/core/src/main/java/org/teavm/classlib/ReflectionContext.java similarity index 100% rename from classlib/src/main/java/org/teavm/classlib/ReflectionContext.java rename to core/src/main/java/org/teavm/classlib/ReflectionContext.java diff --git a/classlib/src/main/java/org/teavm/classlib/ReflectionSupplier.java b/core/src/main/java/org/teavm/classlib/ReflectionSupplier.java similarity index 100% rename from classlib/src/main/java/org/teavm/classlib/ReflectionSupplier.java rename to core/src/main/java/org/teavm/classlib/ReflectionSupplier.java diff --git a/classlib/src/main/java/org/teavm/classlib/ResourceSupplier.java b/core/src/main/java/org/teavm/classlib/ResourceSupplier.java similarity index 100% rename from classlib/src/main/java/org/teavm/classlib/ResourceSupplier.java rename to core/src/main/java/org/teavm/classlib/ResourceSupplier.java diff --git a/classlib/src/main/java/org/teavm/classlib/ResourceSupplierContext.java b/core/src/main/java/org/teavm/classlib/ResourceSupplierContext.java similarity index 100% rename from classlib/src/main/java/org/teavm/classlib/ResourceSupplierContext.java rename to core/src/main/java/org/teavm/classlib/ResourceSupplierContext.java diff --git a/classlib/src/main/java/org/teavm/classlib/ServiceLoaderFilter.java b/core/src/main/java/org/teavm/classlib/ServiceLoaderFilter.java similarity index 100% rename from classlib/src/main/java/org/teavm/classlib/ServiceLoaderFilter.java rename to core/src/main/java/org/teavm/classlib/ServiceLoaderFilter.java diff --git a/classlib/src/main/java/org/teavm/classlib/ServiceLoaderFilterContext.java b/core/src/main/java/org/teavm/classlib/ServiceLoaderFilterContext.java similarity index 100% rename from classlib/src/main/java/org/teavm/classlib/ServiceLoaderFilterContext.java rename to core/src/main/java/org/teavm/classlib/ServiceLoaderFilterContext.java diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/ClassLoaderTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/ClassLoaderTest.java index 753f5c0c6..7c8adfaf8 100644 --- a/tests/src/test/java/org/teavm/classlib/java/lang/ClassLoaderTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/lang/ClassLoaderTest.java @@ -19,8 +19,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import org.junit.Test; import org.junit.runner.RunWith; import org.teavm.junit.EachTestCompiledSeparately; @@ -48,14 +48,14 @@ public class ClassLoaderTest { @Test @SkipPlatform({TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI}) public void returnsNullForNonExistentResource() { - InputStream input = ClassLoader.getSystemClassLoader().getResourceAsStream("non-existent-resource.txt"); + var input = ClassLoader.getSystemClassLoader().getResourceAsStream("non-existent-resource.txt"); assertNull(input); } private static String loadResource(String name) { - ClassLoader classLoader = ClassLoader.getSystemClassLoader(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(classLoader.getResourceAsStream( - "resources-for-test/" + name), "UTF-8"))) { + var classLoader = ClassLoader.getSystemClassLoader(); + try (var reader = new BufferedReader(new InputStreamReader(classLoader.getResourceAsStream( + "resources-for-test/" + name), StandardCharsets.UTF_8))) { return reader.readLine(); } catch (IOException e) { return "";