From 0dcc25d66b75e732d5d32da1a5906625c84aa643 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sun, 6 Oct 2024 21:00:17 +0200 Subject: [PATCH] wasm gc: support exporting declarations as JS entities from module --- .../org/teavm/classlib/java/util/TDate.java | 6 +- .../backend/wasm/WasmGCModuleGenerator.java | 42 --- .../org/teavm/backend/wasm/WasmGCTarget.java | 121 ++++++-- .../backend/wasm/gc/TeaVMWasmGCHost.java | 2 + .../backend/wasm/gc/WasmGCClassConsumer.java | 20 ++ .../wasm/gc/WasmGCClassConsumerContext.java | 49 +++ .../gc/WasmGCDeclarationsGenerator.java | 23 +- .../gc/methods/WasmGCGenerationContext.java | 11 +- .../gc/methods/WasmGCGenerationVisitor.java | 5 + .../gc/methods/WasmGCMethodGenerator.java | 13 +- .../gc/WasmGCCustomGeneratorContext.java | 2 + .../intrinsics/gc/WasmGCIntrinsicContext.java | 2 + .../teavm/backend/wasm/model/WasmGlobal.java | 9 + .../teavm/backend/wasm/model/WasmModule.java | 1 + .../wasm/render/WasmBinaryRenderer.java | 16 +- .../gc/EntryPointTransformation.java | 50 +++ core/src/main/java/org/teavm/vm/TeaVM.java | 3 +- .../main/java/org/teavm/vm/TeaVMTarget.java | 3 + .../org/teavm/backend/wasm/wasm-gc-runtime.js | 215 +++++++++---- .../org/teavm/jso/impl/JSAliasRenderer.java | 10 +- .../teavm/jso/impl/JSClassObjectToExpose.java | 19 ++ .../java/org/teavm/jso/impl/JSMethods.java | 1 - .../jso/impl/JSObjectClassTransformer.java | 85 ++++-- .../java/org/teavm/jso/impl/JSTypeHelper.java | 13 + .../org/teavm/jso/impl/JSTypeInference.java | 11 +- .../org/teavm/jso/impl/wasmgc/WasmGCJso.java | 7 + .../impl/wasmgc/WasmGCJsoCommonGenerator.java | 289 +++++++++++++++++- .../jso/impl/wasmgc/WasmGCJsoContext.java | 80 +++++ .../wasmgc/WasmGCMarshallMethodGenerator.java | 101 +----- .../src/main/webapp/teavm-wasm-gc.html | 2 +- samples/pi/src/main/webapp/wasm-gc.html | 7 +- .../java/org/teavm/jso/export/ExportTest.java | 114 ++++++- .../teavm/jso/export/exportClassMembers.js | 2 +- .../org/teavm/jso/export/exportClasses.js | 2 +- .../teavm/jso/export/importClassMembers.js | 2 +- .../org/teavm/jso/export/initializer.js | 2 +- .../org/teavm/jso/export/primitives.js | 2 +- .../resources/org/teavm/jso/export/simple.js | 2 +- .../resources/org/teavm/jso/export/varargs.js | 2 +- .../src/main/resources/test-server/frame.js | 16 +- .../org/teavm/junit/TestWasmGCEntryPoint.java | 5 +- .../resources/teavm-run-test-wasm-gc.html | 18 +- 42 files changed, 1061 insertions(+), 324 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/gc/WasmGCClassConsumer.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/gc/WasmGCClassConsumerContext.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/transformation/gc/EntryPointTransformation.java create mode 100644 jso/impl/src/main/java/org/teavm/jso/impl/JSClassObjectToExpose.java diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TDate.java b/classlib/src/main/java/org/teavm/classlib/java/util/TDate.java index f7459572c..16e591725 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TDate.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TDate.java @@ -25,7 +25,9 @@ import org.teavm.interop.NoSideEffects; import org.teavm.interop.Platforms; import org.teavm.interop.Unmanaged; import org.teavm.interop.UnsupportedOn; +import org.teavm.jso.JSObject; import org.teavm.jso.core.JSDate; +import org.teavm.jso.impl.JS; public class TDate implements TComparable { private long value; @@ -420,7 +422,7 @@ public class TDate implements TComparable { } else if (PlatformDetector.isWebAssembly()) { return toStringWebAssembly(value); } else if (PlatformDetector.isWebAssemblyGC()) { - return toStringWebAssemblyGC(value); + return JS.unwrapString(toStringWebAssemblyGC(value)); } else { return JSDate.create(value).stringValue(); } @@ -435,7 +437,7 @@ public class TDate implements TComparable { private static native String toStringWebAssembly(double date); @Import(module = "teavmDate", name = "dateToString") - private static native String toStringWebAssemblyGC(double date); + private static native JSObject toStringWebAssemblyGC(double date); @Deprecated public String toLocaleString() { 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 a500fefec..29f1f3620 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCModuleGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCModuleGenerator.java @@ -18,7 +18,6 @@ package org.teavm.backend.wasm; import org.teavm.backend.wasm.generate.gc.WasmGCDeclarationsGenerator; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.runtime.StringInternPool; -import org.teavm.backend.wasm.runtime.gc.WasmGCSupport; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; @@ -34,47 +33,6 @@ public class WasmGCModuleGenerator { createInitializer(); } - - public WasmFunction generateMainFunction(String entryPoint) { - return declarationsGenerator.functions().forStaticMethod(new MethodReference(entryPoint, - "main", ValueType.parse(String[].class), ValueType.VOID)); - } - - public WasmFunction generateCreateStringBuilderFunction() { - return declarationsGenerator.functions().forStaticMethod(new MethodReference( - WasmGCSupport.class, "createStringBuilder", StringBuilder.class)); - } - - public WasmFunction generateCreateStringArrayFunction() { - return declarationsGenerator.functions().forStaticMethod(new MethodReference( - WasmGCSupport.class, "createStringArray", int.class, String[].class)); - } - - public WasmFunction generateAppendCharFunction() { - return declarationsGenerator.functions().forInstanceMethod(new MethodReference( - StringBuilder.class, "append", char.class, StringBuilder.class)); - } - - public WasmFunction generateBuildStringFunction() { - return declarationsGenerator.functions().forInstanceMethod(new MethodReference( - StringBuilder.class, "toString", String.class)); - } - - public WasmFunction generateSetToStringArrayFunction() { - return declarationsGenerator.functions().forStaticMethod(new MethodReference( - WasmGCSupport.class, "setToStringArray", String[].class, int.class, String.class, void.class)); - } - - public WasmFunction generateStringLengthFunction() { - return declarationsGenerator.functions().forInstanceMethod(new MethodReference( - String.class, "length", int.class)); - } - - public WasmFunction generateCharAtFunction() { - return declarationsGenerator.functions().forInstanceMethod(new MethodReference( - String.class, "charAt", int.class, char.class)); - } - public WasmFunction generateReportGarbageCollectedStringFunction() { var entryType = ValueType.object(StringInternPool.class.getName() + "$Entry"); return declarationsGenerator.functions().forStaticMethod(new MethodReference( 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 47ed4c9ba..7d19c91db 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -21,20 +21,28 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import org.teavm.backend.wasm.debug.DebugLines; import org.teavm.backend.wasm.debug.ExternalDebugFile; import org.teavm.backend.wasm.debug.GCDebugInfoBuilder; import org.teavm.backend.wasm.gc.TeaVMWasmGCHost; +import org.teavm.backend.wasm.gc.WasmGCClassConsumer; +import org.teavm.backend.wasm.gc.WasmGCClassConsumerContext; import org.teavm.backend.wasm.gc.WasmGCDependencies; import org.teavm.backend.wasm.generate.gc.WasmGCDeclarationsGenerator; +import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCCustomTypeMapperFactory; +import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper; +import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringProvider; import org.teavm.backend.wasm.generators.gc.WasmGCCustomGenerator; import org.teavm.backend.wasm.generators.gc.WasmGCCustomGeneratorFactory; import org.teavm.backend.wasm.generators.gc.WasmGCCustomGenerators; import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsic; import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicFactory; import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsics; +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.optimization.WasmUsageCounter; import org.teavm.backend.wasm.render.WasmBinaryRenderer; import org.teavm.backend.wasm.render.WasmBinaryStatsCollector; @@ -43,10 +51,12 @@ 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.backend.wasm.transformation.gc.EntryPointTransformation; import org.teavm.dependency.DependencyAnalyzer; import org.teavm.dependency.DependencyListener; import org.teavm.interop.Platforms; import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassReaderSource; import org.teavm.model.ListableClassHolderSource; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; @@ -73,6 +83,8 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { private List customTypeMapperFactories = new ArrayList<>(); private Map customCustomGenerators = new HashMap<>(); private List customGeneratorFactories = new ArrayList<>(); + private EntryPointTransformation entryPointTransformation = new EntryPointTransformation(); + private List classConsumers = new ArrayList<>(); public void setObfuscated(boolean obfuscated) { this.obfuscated = obfuscated; @@ -115,11 +127,22 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { customTypeMapperFactories.add(customTypeMapperFactory); } + @Override + public void addClassConsumer(WasmGCClassConsumer consumer) { + classConsumers.add(consumer); + } + @Override public void setController(TeaVMTargetController controller) { this.controller = controller; } + @Override + public void setEntryPoint(String entryPoint, String name) { + entryPointTransformation.setEntryPoint(entryPoint); + entryPointTransformation.setEntryPointName(name); + } + @Override public VariableCategoryProvider variableCategoryProvider() { return null; @@ -134,7 +157,8 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { public List getTransformers() { return List.of( new BaseClassesTransformation(), - new ClassLoaderResourceTransformation() + new ClassLoaderResourceTransformation(), + entryPointTransformation ); } @@ -181,6 +205,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { @Override public void emit(ListableClassHolderSource classes, BuildTarget buildTarget, String outputName) throws IOException { var module = new WasmModule(); + module.memoryExportName = "teavm.memory"; var customGenerators = new WasmGCCustomGenerators(classes, controller.getServices(), customGeneratorFactories, customCustomGenerators, controller.getProperties()); @@ -198,47 +223,30 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { intrinsics, customTypeMapperFactories, controller::isVirtual, - strict + strict, + controller.getEntryPoint() ); declarationsGenerator.setFriendlyToDebugger(controller.isFriendlyToDebugger()); var moduleGenerator = new WasmGCModuleGenerator(declarationsGenerator); - var mainFunction = moduleGenerator.generateMainFunction(controller.getEntryPoint()); - mainFunction.setExportName(controller.getEntryPointName()); - mainFunction.setName(controller.getEntryPointName()); - - var stringBuilderFunction = moduleGenerator.generateCreateStringBuilderFunction(); - stringBuilderFunction.setExportName("createStringBuilder"); - - var createStringArrayFunction = moduleGenerator.generateCreateStringArrayFunction(); - createStringArrayFunction.setExportName("createStringArray"); - - var appendCharFunction = moduleGenerator.generateAppendCharFunction(); - appendCharFunction.setExportName("appendChar"); - - var buildStringFunction = moduleGenerator.generateBuildStringFunction(); - buildStringFunction.setExportName("buildString"); - - var setArrayFunction = moduleGenerator.generateSetToStringArrayFunction(); - setArrayFunction.setExportName("setToStringArray"); - - var stringLengthFunction = moduleGenerator.generateStringLengthFunction(); - stringLengthFunction.setExportName("stringLength"); - - var charAtFunction = moduleGenerator.generateCharAtFunction(); - charAtFunction.setExportName("charAt"); + var classConsumerContext = createClassConsumerContext(classes, declarationsGenerator); + for (var cls : classes.getClassNames()) { + for (var consumer : classConsumers) { + consumer.accept(classConsumerContext, cls); + } + } var internMethod = controller.getDependencyInfo().getMethod(new MethodReference(String.class, "intern", String.class)); if (internMethod != null && internMethod.isUsed()) { var removeStringEntryFunction = moduleGenerator.generateReportGarbageCollectedStringFunction(); - removeStringEntryFunction.setExportName("reportGarbageCollectedString"); + removeStringEntryFunction.setExportName("teavm.reportGarbageCollectedString"); } var exceptionMessageRef = new MethodReference(Throwable.class, "getMessage", Throwable.class); if (controller.getDependencyInfo().getMethod(exceptionMessageRef) != null) { var exceptionMessageFunction = declarationsGenerator.functions().forInstanceMethod(exceptionMessageRef); - exceptionMessageFunction.setExportName("exceptionMessage"); + exceptionMessageFunction.setExportName("teavm.exceptionMessage"); } moduleGenerator.generate(); @@ -248,6 +256,63 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { emitWasmFile(module, buildTarget, outputName, debugInfoBuilder); } + private WasmGCClassConsumerContext createClassConsumerContext( + ClassReaderSource classes, + WasmGCDeclarationsGenerator generator + ) { + return new WasmGCClassConsumerContext() { + @Override + public ClassReaderSource classes() { + return classes; + } + + @Override + public WasmModule module() { + return generator.module; + } + + @Override + public WasmFunctionTypes functionTypes() { + return generator.functionTypes; + } + + @Override + public BaseWasmFunctionRepository functions() { + return generator.functions(); + } + + @Override + public WasmGCNameProvider names() { + return generator.names(); + } + + @Override + public WasmGCStringProvider strings() { + return generator.strings(); + } + + @Override + public WasmGCTypeMapper typeMapper() { + return generator.typeMapper(); + } + + @Override + public WasmTag exceptionTag() { + return generator.exceptionTag(); + } + + @Override + public String entryPoint() { + return controller.getEntryPoint(); + } + + @Override + public void addToInitializer(Consumer initializerContributor) { + generator.addToInitializer(initializerContributor); + } + }; + } + private void adjustModuleMemory(WasmModule module) { var memorySize = 0; for (var segment : module.getSegments()) { diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/TeaVMWasmGCHost.java b/core/src/main/java/org/teavm/backend/wasm/gc/TeaVMWasmGCHost.java index 8dcd28933..785136d40 100644 --- a/core/src/main/java/org/teavm/backend/wasm/gc/TeaVMWasmGCHost.java +++ b/core/src/main/java/org/teavm/backend/wasm/gc/TeaVMWasmGCHost.java @@ -33,4 +33,6 @@ public interface TeaVMWasmGCHost extends TeaVMHostExtension { void addGenerator(MethodReference method, WasmGCCustomGenerator generator); void addCustomTypeMapperFactory(WasmGCCustomTypeMapperFactory customTypeMapperFactory); + + void addClassConsumer(WasmGCClassConsumer consumer); } diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCClassConsumer.java b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCClassConsumer.java new file mode 100644 index 000000000..c709ee56c --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCClassConsumer.java @@ -0,0 +1,20 @@ +/* + * 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.gc; + +public interface WasmGCClassConsumer { + void accept(WasmGCClassConsumerContext context, String className); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCClassConsumerContext.java b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCClassConsumerContext.java new file mode 100644 index 000000000..8f33ed496 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCClassConsumerContext.java @@ -0,0 +1,49 @@ +/* + * 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.gc; + +import java.util.function.Consumer; +import org.teavm.backend.wasm.BaseWasmFunctionRepository; +import org.teavm.backend.wasm.WasmFunctionTypes; +import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider; +import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper; +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.model.ClassReaderSource; + +public interface WasmGCClassConsumerContext { + ClassReaderSource classes(); + + WasmModule module(); + + WasmFunctionTypes functionTypes(); + + BaseWasmFunctionRepository functions(); + + WasmGCNameProvider names(); + + WasmGCStringProvider strings(); + + WasmGCTypeMapper typeMapper(); + + WasmTag exceptionTag(); + + String entryPoint(); + + void addToInitializer(Consumer initializerContributor); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java index 1bf83fd49..28433d47a 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java @@ -17,6 +17,7 @@ package org.teavm.backend.wasm.generate.gc; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import java.util.function.Predicate; import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.WasmFunctionTypes; @@ -29,8 +30,10 @@ import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper; import org.teavm.backend.wasm.generate.gc.methods.WasmGCCustomGeneratorProvider; import org.teavm.backend.wasm.generate.gc.methods.WasmGCIntrinsicProvider; import org.teavm.backend.wasm.generate.gc.methods.WasmGCMethodGenerator; +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.dependency.DependencyInfo; import org.teavm.diagnostics.Diagnostics; import org.teavm.model.ClassHierarchy; @@ -62,7 +65,8 @@ public class WasmGCDeclarationsGenerator { WasmGCIntrinsicProvider intrinsics, List customTypeMapperFactories, Predicate isVirtual, - boolean strict + boolean strict, + String entryPoint ) { this.module = module; hierarchy = new ClassHierarchy(classes); @@ -82,6 +86,7 @@ public class WasmGCDeclarationsGenerator { customGenerators, intrinsics, strict, + entryPoint, initializerContributors::add ); var tags = new TagRegistry(classes, hierarchy); @@ -157,4 +162,20 @@ public class WasmGCDeclarationsGenerator { public WasmFunction dummyInitializer() { return methodGenerator.getDummyInitializer(); } + + public WasmGCNameProvider names() { + return methodGenerator.names; + } + + public WasmGCStringProvider strings() { + return classGenerator.strings; + } + + public WasmTag exceptionTag() { + return methodGenerator.getGenerationContext().getExceptionTag(); + } + + public void addToInitializer(Consumer contributor) { + methodGenerator.getGenerationContext().addToInitializer(contributor); + } } 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 94cc5e203..c01f54891 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 @@ -65,6 +65,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { private Map> interfaceImplementors; private WasmGCNameProvider names; private boolean strict; + private String entryPoint; private Consumer initializerContributors; public WasmGCGenerationContext(WasmModule module, WasmGCVirtualTableProvider virtualTables, @@ -73,7 +74,8 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { WasmGCSupertypeFunctionProvider supertypeFunctions, WasmGCClassInfoProvider classInfoProvider, WasmGCStandardClasses standardClasses, WasmGCStringProvider strings, WasmGCCustomGeneratorProvider customGenerators, WasmGCIntrinsicProvider intrinsics, - WasmGCNameProvider names, boolean strict, Consumer initializerContributors) { + WasmGCNameProvider names, boolean strict, String entryPoint, + Consumer initializerContributors) { this.module = module; this.virtualTables = virtualTables; this.typeMapper = typeMapper; @@ -90,6 +92,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { this.intrinsics = intrinsics; this.names = names; this.strict = strict; + this.entryPoint = entryPoint; this.initializerContributors = initializerContributors; } @@ -109,6 +112,10 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { return strings; } + public String entryPoint() { + return entryPoint; + } + public WasmGCVirtualTableProvider virtualTables() { return virtualTables; } @@ -136,7 +143,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { if (exceptionTag == null) { exceptionTag = new WasmTag(functionTypes.of(null, classInfoProvider.getClassInfo("java.lang.Throwable").getStructure().getReference())); - exceptionTag.setExportName("javaException"); + exceptionTag.setExportName("teavm.javaException"); module.tags.add(exceptionTag); } return exceptionTag; 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 d77006d35..3da315056 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 @@ -844,6 +844,11 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { return context.getExceptionTag(); } + @Override + public String entryPoint() { + return context.entryPoint(); + } + @Override public void addToInitializer(Consumer initializerContributor) { context.addToInitializer(initializerContributor); 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 4d9b4a399..6e2b7f5a1 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 @@ -72,7 +72,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { private ClassInitializerInfo classInitInfo; private WasmFunctionTypes functionTypes; private WasmGCSupertypeFunctionProvider supertypeFunctions; - private WasmGCNameProvider names; + public final WasmGCNameProvider names; private Diagnostics diagnostics; private WasmGCTypeMapper typeMapper; private WasmGCCustomGeneratorProvider customGenerators; @@ -88,6 +88,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { private WasmGCStandardClasses standardClasses; private WasmGCStringProvider strings; private boolean strict; + private String entryPoint; private Consumer initializerContributors; public WasmGCMethodGenerator( @@ -103,6 +104,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { WasmGCCustomGeneratorProvider customGenerators, WasmGCIntrinsicProvider intrinsics, boolean strict, + String entryPoint, Consumer initializerContributors ) { this.module = module; @@ -117,6 +119,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { this.customGenerators = customGenerators; this.intrinsics = intrinsics; this.strict = strict; + this.entryPoint = entryPoint; this.initializerContributors = initializerContributors; } @@ -343,7 +346,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { return decompiler; } - private WasmGCGenerationContext getGenerationContext() { + public WasmGCGenerationContext getGenerationContext() { if (context == null) { context = new WasmGCGenerationContext( module, @@ -362,6 +365,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { intrinsics, names, strict, + entryPoint, initializerContributors ); } @@ -434,6 +438,11 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { return context.strings(); } + @Override + public String entryPoint() { + return context.entryPoint(); + } + @Override public void addToInitializer(Consumer initializerContributor) { context.addToInitializer(initializerContributor); 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 f6adb6cd9..f90353048 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 @@ -51,5 +51,7 @@ public interface WasmGCCustomGeneratorContext { WasmGCStringProvider strings(); + String entryPoint(); + void addToInitializer(Consumer initializerContributor); } diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsicContext.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsicContext.java index c88ae2532..32e4f8b68 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsicContext.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsicContext.java @@ -61,5 +61,7 @@ public interface WasmGCIntrinsicContext { WasmTag exceptionTag(); + String entryPoint(); + void addToInitializer(Consumer initializerContributor); } diff --git a/core/src/main/java/org/teavm/backend/wasm/model/WasmGlobal.java b/core/src/main/java/org/teavm/backend/wasm/model/WasmGlobal.java index 3d71cad75..9548f15d2 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/WasmGlobal.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/WasmGlobal.java @@ -23,6 +23,7 @@ public class WasmGlobal extends WasmEntity { private WasmType type; private WasmExpression initialValue; private boolean immutable; + private String exportName; public WasmGlobal(String name, WasmType type, WasmExpression initialValue) { this.name = name; @@ -57,4 +58,12 @@ public class WasmGlobal extends WasmEntity { public void setImmutable(boolean immutable) { this.immutable = immutable; } + + public String getExportName() { + return exportName; + } + + public void setExportName(String exportName) { + this.exportName = exportName; + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/model/WasmModule.java b/core/src/main/java/org/teavm/backend/wasm/model/WasmModule.java index a9fedfe62..afe0f71f2 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/WasmModule.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/WasmModule.java @@ -40,6 +40,7 @@ public class WasmModule { public final WasmCollection globals = new WasmCollection<>(); public final WasmCollection types = new WasmCollection<>(); public final WasmCollection tags = new WasmCollection<>(); + public String memoryExportName = "memory"; public void add(WasmCustomSection customSection) { if (customSections.containsKey(customSection.getName())) { diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java index 0c827c8c7..89b03b8eb 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java @@ -49,6 +49,7 @@ public class WasmBinaryRenderer { private static final int EXTERNAL_KIND_FUNCTION = 0; private static final int EXTERNAL_KIND_MEMORY = 2; + private static final int EXTERNAL_KIND_GLOBAL = 3; private static final int EXTERNAL_KIND_TAG = 4; private WasmBinaryWriter output; @@ -241,7 +242,11 @@ public class WasmBinaryRenderer { .filter(tag -> tag.getExportName() != null) .collect(Collectors.toList()); - section.writeLEB(functions.size() + tags.size() + 1); + var globals = module.globals.stream() + .filter(global -> global.getExportName() != null) + .collect(Collectors.toList()); + + section.writeLEB(functions.size() + tags.size() + globals.size() + 1); for (var function : functions) { int functionIndex = module.functions.indexOf(function); @@ -257,9 +262,16 @@ public class WasmBinaryRenderer { section.writeByte(EXTERNAL_KIND_TAG); section.writeLEB(tagIndex); } + for (var global : globals) { + var index = module.globals.indexOf(global); + section.writeAsciiString(global.getExportName()); + + section.writeByte(EXTERNAL_KIND_GLOBAL); + section.writeLEB(index); + } // We also need to export the memory to make it accessible - section.writeAsciiString("memory"); + section.writeAsciiString(module.memoryExportName); section.writeByte(EXTERNAL_KIND_MEMORY); section.writeLEB(0); diff --git a/core/src/main/java/org/teavm/backend/wasm/transformation/gc/EntryPointTransformation.java b/core/src/main/java/org/teavm/backend/wasm/transformation/gc/EntryPointTransformation.java new file mode 100644 index 000000000..14b676925 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/transformation/gc/EntryPointTransformation.java @@ -0,0 +1,50 @@ +/* + * 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 org.teavm.model.AnnotationHolder; +import org.teavm.model.AnnotationValue; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; +import org.teavm.model.MethodDescriptor; + +public class EntryPointTransformation implements ClassHolderTransformer { + private static final MethodDescriptor MAIN_METHOD = new MethodDescriptor("main", String[].class, void.class); + private String entryPoint; + private String entryPointName; + + public void setEntryPoint(String entryPoint) { + this.entryPoint = entryPoint; + } + + public void setEntryPointName(String entryPointName) { + this.entryPointName = entryPointName; + } + + @Override + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { + if (cls.getName().equals(entryPoint)) { + var mainMethod = cls.getMethod(MAIN_METHOD); + if (mainMethod != null) { + mainMethod.getAnnotations().add(new AnnotationHolder("org.teavm.jso.JSExport")); + + var methodAnnot = new AnnotationHolder("org.teavm.jso.JSMethod"); + methodAnnot.getValues().put("value", new AnnotationValue(entryPointName)); + } + } + } +} diff --git a/core/src/main/java/org/teavm/vm/TeaVM.java b/core/src/main/java/org/teavm/vm/TeaVM.java index b3c07b482..7eeb33ee2 100644 --- a/core/src/main/java/org/teavm/vm/TeaVM.java +++ b/core/src/main/java/org/teavm/vm/TeaVM.java @@ -381,7 +381,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return; } - processEntryPoint(); + target.setEntryPoint(entryPoint, entryPointName); dependencyAnalyzer.setAsyncSupported(target.isAsyncSupported()); dependencyAnalyzer.setInterruptor(() -> { int progress = dependencyAnalyzer.getReachableClasses().size(); @@ -390,6 +390,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { }); target.contributeDependencies(dependencyAnalyzer); dependencyAnalyzer.initDependencies(); + processEntryPoint(); if (target.needsSystemArrayCopyOptimization()) { dependencyAnalyzer.addDependencyListener(new StdlibDependencyListener()); } diff --git a/core/src/main/java/org/teavm/vm/TeaVMTarget.java b/core/src/main/java/org/teavm/vm/TeaVMTarget.java index 320c4ff8d..dbb26d0f7 100644 --- a/core/src/main/java/org/teavm/vm/TeaVMTarget.java +++ b/core/src/main/java/org/teavm/vm/TeaVMTarget.java @@ -35,6 +35,9 @@ public interface TeaVMTarget { List getDependencyListeners(); + default void setEntryPoint(String entryPoint, String name) { + } + void setController(TeaVMTargetController controller); List getHostExtensions(); 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 86bf6de6d..8dcdfc7a4 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 @@ -15,9 +15,14 @@ */ var TeaVM = TeaVM || {}; -TeaVM.wasm = function() { +if (window && window.TeaVM === undefined) { + window.TeaVM = TeaVM; +} +TeaVM.wasmGC = TeaVM.wasmGC || function() { let exports; let globalsCache = new Map(); + let stackDeobfuscator = null; + let chromeExceptionRegex = / *at .+\.wasm:wasm-function\[[0-9]+]:0x([0-9a-f]+).*/; let getGlobalName = function(name) { let result = globalsCache.get(name); if (typeof result === "undefined") { @@ -45,11 +50,11 @@ TeaVM.wasm = function() { this[javaExceptionSymbol] = javaException; } get message() { - let exceptionMessage = exports.exceptionMessage; + let exceptionMessage = exports["teavm.exceptionMessage"]; if (typeof exceptionMessage === "function") { let message = exceptionMessage(this[javaExceptionSymbol]); if (message != null) { - return stringToJava(message); + return message; } } return "(could not fetch message)"; @@ -59,8 +64,8 @@ TeaVM.wasm = function() { function dateImports(imports) { imports.teavmDate = { currentTimeMillis: () => new Date().getTime(), - dateToString: timestamp => stringToJava(new Date(timestamp).toString()), - getYear: timestamp =>new Date(timestamp).getFullYear(), + dateToString: timestamp => new Date(timestamp).toString(), + getYear: timestamp => new Date(timestamp).getFullYear(), setYear(timestamp, year) { let date = new Date(timestamp); date.setFullYear(year); @@ -108,12 +113,16 @@ TeaVM.wasm = function() { function coreImports(imports) { let finalizationRegistry = new FinalizationRegistry(heldValue => { - if (typeof exports.reportGarbageCollectedValue === "function") { - exports.reportGarbageCollectedValue(heldValue) + let report = exports["teavm.reportGarbageCollectedValue"]; + if (typeof report === "function") { + report(heldValue) } }); let stringFinalizationRegistry = new FinalizationRegistry(heldValue => { - exports.reportGarbageCollectedString(heldValue); + let report = exports["teavm.reportGarbageCollectedString"]; + if (typeof report === "function") { + report(heldValue); + } }); imports.teavm = { createWeakRef(value, heldValue) { @@ -129,7 +138,28 @@ TeaVM.wasm = function() { stringFinalizationRegistry.register(value, heldValue) return weakRef; }, - stringDeref: weakRef => weakRef.deref() + stringDeref: weakRef => weakRef.deref(), + currentStackTrace() { + if (stackDeobfuscator) { + return; + } + let reportCallFrame = exports["teavm.reportCallFrame"]; + if (typeof reportCallFrame !== "function") { + return; + } + let stack = new Error().stack; + for (let line in stack.split("\n")) { + let match = chromeExceptionRegex.exec(line); + if (match !== null) { + let address = parseInt(match.groups[1], 16); + let frames = stackDeobfuscator(address); + for (let frame of frames) { + let line = frame.line; + reportCallFrame(file, method, cls, line); + } + } + } + } }; } @@ -137,6 +167,7 @@ TeaVM.wasm = function() { let javaObjectSymbol = Symbol("javaObject"); let functionsSymbol = Symbol("functions"); let functionOriginSymbol = Symbol("functionOrigin"); + let wrapperCallMarkerSymbol = Symbol("wrapperCallMarker"); let jsWrappers = new WeakMap(); let javaWrappers = new WeakMap(); @@ -182,7 +213,7 @@ TeaVM.wasm = function() { } function javaExceptionToJs(e) { if (e instanceof WebAssembly.Exception) { - let tag = exports["javaException"]; + let tag = exports["teavm.javaException"]; if (e.is(tag)) { let javaException = e.getArg(tag, 0); let extracted = extractException(javaException); @@ -226,6 +257,22 @@ TeaVM.wasm = function() { rethrowJsAsJava(e); } } + function defineFunction(fn) { + let params = []; + for (let i = 0; i < fn.length; ++i) { + params.push("p" + i); + } + let paramsAsString = params.length === 0 ? "" : params.join(", "); + return new Function("rethrowJavaAsJs", "fn", ` + return function(${paramsAsString}) { + try { + return fn(${paramsAsString}); + } catch (e) { + rethrowJavaAsJs(e); + } + }; + `)(rethrowJavaAsJs, fn); + } imports.teavmJso = { emptyString: () => "", stringFromCharCode: code => String.fromCharCode(code), @@ -247,16 +294,68 @@ TeaVM.wasm = function() { rethrowJsAsJava(e); } }, - createClass(name) { - let fn = new Function( - "javaObjectSymbol", - "functionsSymbol", - `return function JavaClass_${sanitizeName(name)}(javaObject) { - this[javaObjectSymbol] = javaObject; - this[functionsSymbol] = null; - };` - ); - return fn(javaObjectSymbol, functionsSymbol, functionOriginSymbol); + createClass(name, parent, constructor) { + name = sanitizeName(name); + if (parent === null) { + let fn = new Function( + "javaObjectSymbol", + "functionsSymbol", + "wrapperCallMarker", + "constructor", + "rethrowJavaAsJs", + `let fn; + fn = function ${name}(marker, javaObject) { + if (marker === wrapperCallMarker) { + this[javaObjectSymbol] = javaObject; + this[functionsSymbol] = null; + } else if (constructor === null) { + throw new Error("This class can't be instantiated directly"); + } else { + try { + return fn(wrapperCallMarker, constructor(arguments)); + } catch (e) { + rethrowJavaAsJs(e); + } + } + }; + let boundFn = function(javaObject) { return fn.call(this, wrapperCallMarker, javaObject); }; + boundFn[wrapperCallMarker] = fn; + boundFn.prototype = fn.prototype; + return boundFn;` + ); + return fn(javaObjectSymbol, functionsSymbol, wrapperCallMarkerSymbol, constructor, rethrowJavaAsJs); + } else { + let fn = new Function( + "parent", + "wrapperCallMarker", + "constructor", + "rethrowJavaAsJs", + `let fn + fn = function ${name}(marker, javaObject) { + if (marker === wrapperCallMarker) { + parent.call(this, javaObject); + } else if (constructor === null) { + throw new Error("This class can't be instantiated directly"); + } else { + try { + return fn(wrapperCallMarker, constructor(arguments)); + } catch (e) { + rethrowJavaAsJs(e); + } + } + }; + fn.prototype = Object.create(parent); + fn.prototype.constructor = parent; + let boundFn = function(javaObject) { return fn.call(this, wrapperCallMarker, javaObject); }; + boundFn[wrapperCallMarker] = fn; + boundFn.prototype = fn.prototype; + return fn;` + ); + return fn(parent, wrapperCallMarkerSymbol, constructor, rethrowJavaAsJs); + } + }, + exportClass(cls) { + return cls[wrapperCallMarkerSymbol]; }, defineMethod(cls, name, fn) { let params = []; @@ -274,6 +373,10 @@ TeaVM.wasm = function() { }; `)(rethrowJavaAsJs, fn); }, + defineStaticMethod(cls, name, fn) { + cls[name] = defineFunction(fn); + }, + defineFunction: defineFunction, defineProperty(cls, name, getFn, setFn) { let descriptor = { get() { @@ -295,6 +398,27 @@ TeaVM.wasm = function() { } Object.defineProperty(cls.prototype, name, descriptor); }, + defineStaticProperty(cls, name, getFn, setFn) { + let descriptor = { + get() { + try { + return getFn(); + } catch (e) { + rethrowJavaAsJs(e); + } + } + }; + if (setFn !== null) { + descriptor.set = function(value) { + try { + setFn(value); + } catch (e) { + rethrowJavaAsJs(e); + } + } + } + Object.defineProperty(cls, name, descriptor); + }, javaObjectToJS(instance, cls) { let existing = jsWrappers.get(instance); if (typeof existing != "undefined") { @@ -474,41 +598,26 @@ TeaVM.wasm = function() { options.installImports(importObj); } - return WebAssembly.instantiateStreaming(fetch(path), importObj).then((obj => { - let teavm = {}; - teavm.main = createMain(obj.instance); - teavm.instance = obj.instance; - return teavm; - })); - } - - function stringToJava(str) { - let sb = exports.createStringBuilder(); - for (let i = 0; i < str.length; ++i) { - exports.appendChar(sb, str.charCodeAt(i)); - } - return exports.buildString(sb); - } - - function createMain(instance) { - return args => { - if (typeof args === "undefined") { - args = []; - } - return new Promise((resolve, reject) => { - exports = instance.exports; - let javaArgs = exports.createStringArray(args.length); - for (let i = 0; i < args.length; ++i) { - exports.setToStringArray(javaArgs, i, stringToJava(args[i])); - } - try { - exports.main(javaArgs); - } catch (e) { - reject(e); + return WebAssembly.instantiateStreaming(fetch(path), importObj) + .then(r => { + exports = r.instance.exports; + let userExports = {}; + let teavm = { + exports: userExports, + instance: r.instance, + module: r.module + }; + for (let key in r.instance.exports) { + let exportObj = r.instance.exports[key]; + if (exportObj instanceof WebAssembly.Global) { + Object.defineProperty(userExports, key, { + get: () => exportObj.value + }); + } } + return teavm; }); - } } - return { load }; + return { load, defaults }; }(); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java index 13f642c2c..deb2a8ade 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSAliasRenderer.java @@ -72,7 +72,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor { var hasExportedMembers = false; hasExportedMembers |= exportClassInstanceMembers(classReader); if (!className.equals(context.getEntryPoint())) { - var name = "$rt_export_class_ " + getClassAliasName(classReader) + "_" + lastExportIndex++; + var name = "$rt_export_class_" + getClassAliasName(classReader) + "_" + lastExportIndex++; hasExportedMembers |= exportClassStaticMembers(classReader, name); if (hasExportedMembers) { exportedNamesByClass.put(className, name); @@ -213,10 +213,12 @@ class JSAliasRenderer implements RendererListener, MethodContributor { } writer.append(")").ws().appendBlockStart(); if (method != null) { - writer.appendClass(cls.getName()).append(".call(this);").softNewLine(); - writer.appendMethod(method).append("(this"); + writer.append("return ").appendMethod(method).append("("); for (var i = 0; i < method.parameterCount(); ++i) { - writer.append(",").ws().append("p" + i); + if (i > 0) { + writer.append(",").ws(); + } + writer.append("p" + i); } writer.append(");").softNewLine(); } else { diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassObjectToExpose.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassObjectToExpose.java new file mode 100644 index 000000000..d4059deed --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassObjectToExpose.java @@ -0,0 +1,19 @@ +/* + * 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.jso.impl; + +public @interface JSClassObjectToExpose { +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java index 88da51cd1..89bf1b4e5 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSMethods.java @@ -161,7 +161,6 @@ public final class JSMethods { public static final String JS_MARSHALLABLE = JSMarshallable.class.getName(); public static final MethodDescriptor MARSHALL_TO_JS = new MethodDescriptor("marshallToJs", JS_OBJECT); - public static final MethodReference MARSHALL_TO_JS_REF = new MethodReference(JS_MARSHALLABLE, MARSHALL_TO_JS); static { for (int i = 0; i < INVOKE_METHODS.length; ++i) { diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java index 162adf1b5..731e9f237 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSObjectClassTransformer.java @@ -50,6 +50,7 @@ import org.teavm.model.Program; import org.teavm.model.ValueType; import org.teavm.model.Variable; import org.teavm.model.instructions.CastInstruction; +import org.teavm.model.instructions.ConstructInstruction; import org.teavm.model.instructions.ExitInstruction; import org.teavm.model.instructions.IntegerConstantInstruction; import org.teavm.model.instructions.InvocationType; @@ -120,11 +121,14 @@ class JSObjectClassTransformer implements ClassHolderTransformer { } exposeMethods(cls, exposedClass, context.getDiagnostics(), functorMethod); - exportStaticMethods(cls, context.getDiagnostics()); + var hasStaticMethods = exportStaticMethods(cls, context.getDiagnostics()); if (isJavaScriptImplementation(cls) || !exposedClass.methods.isEmpty()) { cls.getAnnotations().add(new AnnotationHolder(JSClassToExpose.class.getName())); } + if (isJavaScriptImplementation(cls) || !exposedClass.methods.isEmpty() || hasStaticMethods) { + cls.getAnnotations().add(new AnnotationHolder(JSClassObjectToExpose.class.getName())); + } if (wasmGC && (!exposedClass.methods.isEmpty() || isJavaScriptClass(cls))) { var createWrapperMethod = new MethodHolder(JSMethods.MARSHALL_TO_JS); @@ -132,7 +136,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer { createWrapperMethod.getModifiers().add(ElementModifier.NATIVE); cls.addMethod(createWrapperMethod); - if (isJavaScriptImplementation(cls)) { + if (!isJavaScriptClass(cls) || isJavaScriptImplementation(cls)) { cls.getInterfaces().add(JSMethods.JS_MARSHALLABLE); } } @@ -147,13 +151,17 @@ class JSObjectClassTransformer implements ClassHolderTransformer { MethodReference methodRef = new MethodReference(classHolder.getName(), method); CallLocation callLocation = new CallLocation(methodRef); + var isConstructor = entry.getKey().getName().equals(""); var paramCount = method.parameterCount(); if (export.vararg) { --paramCount; } + if (isConstructor) { + --paramCount; + } var exportedMethodSignature = new ValueType[paramCount + 2]; Arrays.fill(exportedMethodSignature, JSMethods.JS_OBJECT); - if (methodRef.getReturnType() == ValueType.VOID) { + if (methodRef.getReturnType() == ValueType.VOID && !isConstructor) { exportedMethodSignature[exportedMethodSignature.length - 1] = ValueType.VOID; } MethodDescriptor exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++, @@ -164,7 +172,9 @@ class JSObjectClassTransformer implements ClassHolderTransformer { Program program = new Program(); exportedMethod.setProgram(program); program.createVariable(); - program.createVariable(); + if (!isConstructor) { + program.createVariable(); + } BasicBlock basicBlock = program.createBasicBlock(); List marshallInstructions = new ArrayList<>(); @@ -189,33 +199,52 @@ class JSObjectClassTransformer implements ClassHolderTransformer { basicBlock.addAll(marshallInstructions); marshallInstructions.clear(); - var unmarshalledInstance = new InvokeInstruction(); - unmarshalledInstance.setType(InvocationType.SPECIAL); - unmarshalledInstance.setReceiver(program.createVariable()); - unmarshalledInstance.setArguments(program.variableAt(1)); - unmarshalledInstance.setMethod(new MethodReference(JSWrapper.class, - "unmarshallJavaFromJs", JSObject.class, Object.class)); - basicBlock.add(unmarshalledInstance); + Variable receiverToPass; + if (isConstructor) { + var create = new ConstructInstruction(); + create.setReceiver(program.createVariable()); + create.setType(classHolder.getName()); + basicBlock.add(create); + receiverToPass = create.getReceiver(); + } else { + var unmarshalledInstance = new InvokeInstruction(); + unmarshalledInstance.setType(InvocationType.SPECIAL); + unmarshalledInstance.setReceiver(program.createVariable()); + unmarshalledInstance.setArguments(program.variableAt(1)); + unmarshalledInstance.setMethod(new MethodReference(JSWrapper.class, + "unmarshallJavaFromJs", JSObject.class, Object.class)); + basicBlock.add(unmarshalledInstance); - var castInstance = new CastInstruction(); - castInstance.setValue(unmarshalledInstance.getReceiver()); - castInstance.setReceiver(program.createVariable()); - castInstance.setWeak(true); - castInstance.setTargetType(ValueType.object(classHolder.getName())); - basicBlock.add(castInstance); + var castInstance = new CastInstruction(); + castInstance.setValue(unmarshalledInstance.getReceiver()); + castInstance.setReceiver(program.createVariable()); + castInstance.setWeak(true); + castInstance.setTargetType(ValueType.object(classHolder.getName())); + basicBlock.add(castInstance); + + receiverToPass = castInstance.getReceiver(); + } InvokeInstruction invocation = new InvokeInstruction(); - invocation.setType(method.getName().equals("") ? InvocationType.SPECIAL : InvocationType.VIRTUAL); - invocation.setInstance(castInstance.getReceiver()); + invocation.setType(isConstructor ? InvocationType.SPECIAL : InvocationType.VIRTUAL); + invocation.setInstance(receiverToPass); invocation.setMethod(methodRef); invocation.setArguments(variablesToPass); basicBlock.add(invocation); ExitInstruction exit = new ExitInstruction(); - if (method.getResultType() != ValueType.VOID) { - invocation.setReceiver(program.createVariable()); - exit.setValueToReturn(marshaller.wrapArgument(callLocation, invocation.getReceiver(), - method.getResultType(), JSType.MIXED, false)); + if (method.getResultType() != ValueType.VOID || isConstructor) { + Variable result; + ValueType resultType; + if (isConstructor) { + result = receiverToPass; + resultType = ValueType.object(classHolder.getName()); + } else { + result = program.createVariable(); + invocation.setReceiver(result); + resultType = method.getResultType(); + } + exit.setValueToReturn(marshaller.wrapArgument(callLocation, result, resultType, JSType.JAVA, false)); basicBlock.addAll(marshallInstructions); marshallInstructions.clear(); } @@ -230,13 +259,15 @@ class JSObjectClassTransformer implements ClassHolderTransformer { } } - private void exportStaticMethods(ClassHolder classHolder, Diagnostics diagnostics) { + private boolean exportStaticMethods(ClassHolder classHolder, Diagnostics diagnostics) { int index = 0; + var hasMethods = false; for (var method : classHolder.getMethods().toArray(new MethodHolder[0])) { if (!method.hasModifier(ElementModifier.STATIC) || method.getAnnotations().get(JSExport.class.getName()) == null) { continue; } + hasMethods = true; var paramCount = method.parameterCount(); var vararg = method.hasModifier(ElementModifier.VARARGS); @@ -246,6 +277,9 @@ class JSObjectClassTransformer implements ClassHolderTransformer { var callLocation = new CallLocation(method.getReference()); var exportedMethodSignature = new ValueType[paramCount + 1]; Arrays.fill(exportedMethodSignature, JSMethods.JS_OBJECT); + if (method.getResultType() == ValueType.VOID) { + exportedMethodSignature[exportedMethodSignature.length - 1] = ValueType.VOID; + } var exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++, exportedMethodSignature); var exportedMethod = new MethodHolder(exportedMethodDesc); @@ -287,7 +321,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer { if (method.getResultType() != ValueType.VOID) { invocation.setReceiver(program.createVariable()); exit.setValueToReturn(marshaller.wrapArgument(callLocation, invocation.getReceiver(), - method.getResultType(), JSType.MIXED, false)); + method.getResultType(), JSType.JAVA, false)); basicBlock.addAll(marshallInstructions); marshallInstructions.clear(); } @@ -298,6 +332,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer { var export = createMethodExport(method); exportedMethod.getAnnotations().add(createExportAnnotation(export)); } + return hasMethods; } private void transformVarargParam(Variable[] variablesToPass, Program program, diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSTypeHelper.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSTypeHelper.java index 9e9444487..af6005bb6 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSTypeHelper.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSTypeHelper.java @@ -34,6 +34,19 @@ public class JSTypeHelper { knownJavaScriptClasses.put(JSObject.class.getName(), true); } + public JSType mapType(ValueType type) { + if (type instanceof ValueType.Object) { + var className = ((ValueType.Object) type).getClassName(); + if (isJavaScriptClass(className)) { + return JSType.JS; + } + } else if (type instanceof ValueType.Array) { + var elementType = mapType(((ValueType.Array) type).getItemType()); + return JSType.arrayOf(elementType); + } + return JSType.JAVA; + } + public boolean isJavaScriptClass(String className) { Boolean isJsClass = knownJavaScriptClasses.get(className); if (isJsClass == null) { diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSTypeInference.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSTypeInference.java index d2858fd4a..fb11a2751 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSTypeInference.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSTypeInference.java @@ -38,16 +38,7 @@ class JSTypeInference extends BaseTypeInference { @Override protected JSType mapType(ValueType type) { - if (type instanceof ValueType.Object) { - var className = ((ValueType.Object) type).getClassName(); - if (typeHelper.isJavaScriptClass(className)) { - return JSType.JS; - } - } else if (type instanceof ValueType.Array) { - var elementType = mapType(((ValueType.Array) type).getItemType()); - return JSType.arrayOf(elementType); - } - return JSType.JAVA; + return typeHelper.mapType(type); } @Override diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java index 8b1ff49bc..797f33c58 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJso.java @@ -19,6 +19,7 @@ import org.teavm.backend.wasm.gc.TeaVMWasmGCHost; import org.teavm.jso.JSObject; import org.teavm.jso.impl.JS; import org.teavm.jso.impl.JSBodyRepository; +import org.teavm.jso.impl.JSClassObjectToExpose; import org.teavm.jso.impl.JSWrapper; import org.teavm.model.MethodReference; import org.teavm.vm.spi.TeaVMHost; @@ -35,6 +36,12 @@ public final class WasmGCJso { wasmGCHost.addCustomTypeMapperFactory(new WasmGCJSTypeMapper()); wasmGCHost.addIntrinsicFactory(new WasmGCJSBodyRenderer(jsBodyRepository, jsFunctions, commonGen)); wasmGCHost.addGeneratorFactory(new WasmGCMarshallMethodGeneratorFactory(commonGen)); + wasmGCHost.addClassConsumer((context, className) -> { + var cls = context.classes().get(className); + if (cls != null && cls.getAnnotations().get(JSClassObjectToExpose.class.getName()) != null) { + commonGen.getDefinedClass(WasmGCJsoContext.wrap(context), className); + } + }); var jsIntrinsic = new WasmGCJSIntrinsic(commonGen); wasmGCHost.addIntrinsic(new MethodReference(JS.class, "wrap", String.class, JSObject.class), jsIntrinsic); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoCommonGenerator.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoCommonGenerator.java index 421ecf8c9..122858d3a 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoCommonGenerator.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoCommonGenerator.java @@ -32,15 +32,20 @@ import org.teavm.backend.wasm.model.expression.WasmCast; import org.teavm.backend.wasm.model.expression.WasmExpression; import org.teavm.backend.wasm.model.expression.WasmExternConversion; import org.teavm.backend.wasm.model.expression.WasmExternConversionType; +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.WasmNullConstant; import org.teavm.backend.wasm.model.expression.WasmSetGlobal; import org.teavm.backend.wasm.model.expression.WasmThrow; +import org.teavm.jso.JSClass; import org.teavm.jso.JSObject; +import org.teavm.jso.impl.AliasCollector; import org.teavm.jso.impl.JSBodyAstEmitter; import org.teavm.jso.impl.JSBodyBloatedEmitter; import org.teavm.jso.impl.JSBodyEmitter; +import org.teavm.jso.impl.JSMarshallable; +import org.teavm.model.ClassReader; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; @@ -51,6 +56,15 @@ class WasmGCJsoCommonGenerator { private boolean rethrowExported; private Map stringsConstants = new HashMap<>(); + private WasmFunction createClassFunction; + private WasmFunction defineFunctionFunction; + private WasmFunction defineMethodFunction; + private WasmFunction defineStaticMethodFunction; + private WasmFunction definePropertyFunction; + private WasmFunction defineStaticPropertyFunction; + private WasmFunction exportClassFunction; + private Map definedClasses = new HashMap<>(); + WasmGCJsoCommonGenerator(WasmGCJSFunctions jsFunctions) { this.jsFunctions = jsFunctions; } @@ -70,7 +84,7 @@ class WasmGCJsoCommonGenerator { } } - void addInitializerPart(WasmGCJsoContext context, Consumer part) { + private void addInitializerPart(WasmGCJsoContext context, Consumer part) { initialize(context); initializerParts.add(part); } @@ -183,4 +197,277 @@ class WasmGCJsoCommonGenerator { }); return new WasmGetGlobal(global); } + + WasmGlobal getDefinedClass(WasmGCJsoContext context, String className) { + return definedClasses.computeIfAbsent(className, n -> defineClass(context, n)); + } + + private WasmGlobal defineClass(WasmGCJsoContext context, String className) { + var name = context.names().topLevel(context.names().suggestForClass(className + "@js")); + var global = new WasmGlobal(name, WasmType.Reference.EXTERN, new WasmNullConstant(WasmType.Reference.EXTERN)); + context.module().globals.add(global); + + var cls = context.classes().get(className); + var expressions = new ArrayList(); + var isModule = context.entryPoint().equals(className); + + var members = AliasCollector.collectMembers(cls, AliasCollector::isInstanceMember); + defineMethods(context, members, cls, global, expressions); + defineProperties(context, members, cls, global, expressions); + + var staticMembers = AliasCollector.collectMembers(cls, AliasCollector::isStaticMember); + defineStaticMethods(context, staticMembers, cls, global, expressions, isModule); + defineStaticProperties(context, staticMembers, cls, global, expressions); + + var simpleName = className.substring(className.lastIndexOf('.') + 1); + var javaClassName = context.strings().getStringConstant(simpleName); + var jsClassName = stringToJs(context, new WasmGetGlobal(javaClassName.global)); + + var exportedParent = parentExportedClass(context, cls.getParent()); + var jsExportedParent = exportedParent != null + ? new WasmGetGlobal(getDefinedClass(context, exportedParent)) + : new WasmNullConstant(WasmType.Reference.EXTERN); + + var needsExport = !className.equals(context.entryPoint()) + && (!staticMembers.methods.isEmpty() || !staticMembers.properties.isEmpty()); + WasmExpression constructor; + if (members.constructor != null) { + var function = context.functions().forStaticMethod(members.constructor); + constructor = new WasmFunctionReference(function); + needsExport = true; + } else { + constructor = new WasmNullConstant(WasmType.Reference.FUNC); + } + var createClass = new WasmCall(createClassFunction(context), jsClassName, jsExportedParent, constructor); + expressions.add(0, new WasmSetGlobal(global, createClass)); + if (needsExport) { + exportClass(context, cls, global, expressions); + } + + context.addToInitializer(f -> f.getBody().addAll(expressions)); + return global; + } + + private void defineMethods(WasmGCJsoContext context, AliasCollector.Members members, ClassReader cls, + WasmGlobal global, List expressions) { + for (var aliasEntry : members.methods.entrySet()) { + if (!aliasEntry.getValue().getClassName().equals(cls.getName())) { + continue; + } + var fn = context.functions().forStaticMethod(aliasEntry.getValue()); + fn.setReferenced(true); + var methodName = context.strings().getStringConstant(aliasEntry.getKey()); + var jsMethodName = stringToJs(context, new WasmGetGlobal(methodName.global)); + var defineMethod = new WasmCall(defineMethodFunction(context), new WasmGetGlobal(global), + jsMethodName, new WasmFunctionReference(fn)); + expressions.add(defineMethod); + } + } + + private void defineProperties(WasmGCJsoContext context, AliasCollector.Members members, ClassReader cls, + WasmGlobal global, List expressions) { + for (var aliasEntry : members.properties.entrySet()) { + var property = aliasEntry.getValue(); + if (!property.getter.getClassName().equals(cls.getName())) { + continue; + } + var getter = context.functions().forStaticMethod(property.getter); + getter.setReferenced(true); + WasmFunction setter = null; + if (property.setter != null) { + setter = context.functions().forStaticMethod(property.setter); + setter.setReferenced(true); + } + var setterRef = setter != null + ? new WasmFunctionReference(setter) + : new WasmNullConstant(WasmType.Reference.FUNC); + var methodName = context.strings().getStringConstant(aliasEntry.getKey()); + var jsMethodName = stringToJs(context, new WasmGetGlobal(methodName.global)); + var defineProperty = new WasmCall(definePropertyFunction(context), new WasmGetGlobal(global), + jsMethodName, new WasmFunctionReference(getter), setterRef); + expressions.add(defineProperty); + } + } + + private void defineStaticMethods(WasmGCJsoContext context, AliasCollector.Members members, ClassReader cls, + WasmGlobal global, List expressions, boolean isModule) { + for (var aliasEntry : members.methods.entrySet()) { + if (!aliasEntry.getValue().getClassName().equals(cls.getName())) { + continue; + } + var fn = context.functions().forStaticMethod(aliasEntry.getValue()); + fn.setReferenced(true); + if (isModule) { + var globalName = context.names().topLevel("teavm.js.export.function@" + aliasEntry.getKey()); + var functionGlobal = new WasmGlobal(globalName, WasmType.Reference.EXTERN, + new WasmNullConstant(WasmType.Reference.EXTERN)); + functionGlobal.setExportName(aliasEntry.getKey()); + context.module().globals.add(functionGlobal); + fn.setReferenced(true); + var exportedFn = new WasmCall(defineFunctionFunction(context), new WasmFunctionReference(fn)); + expressions.add(new WasmSetGlobal(functionGlobal, exportedFn)); + } + var methodName = context.strings().getStringConstant(aliasEntry.getKey()); + var jsMethodName = stringToJs(context, new WasmGetGlobal(methodName.global)); + var defineMethod = new WasmCall(defineStaticMethodFunction(context), new WasmGetGlobal(global), + jsMethodName, new WasmFunctionReference(fn)); + expressions.add(defineMethod); + } + } + + private void defineStaticProperties(WasmGCJsoContext context, AliasCollector.Members members, ClassReader cls, + WasmGlobal global, List expressions) { + for (var aliasEntry : members.properties.entrySet()) { + var property = aliasEntry.getValue(); + if (!property.getter.getClassName().equals(cls.getName())) { + continue; + } + var getter = context.functions().forStaticMethod(property.getter); + getter.setReferenced(true); + WasmFunction setter = null; + if (property.setter != null) { + setter = context.functions().forStaticMethod(property.setter); + setter.setReferenced(true); + } + var setterRef = setter != null + ? new WasmFunctionReference(setter) + : new WasmNullConstant(WasmType.Reference.FUNC); + var methodName = context.strings().getStringConstant(aliasEntry.getKey()); + var jsMethodName = stringToJs(context, new WasmGetGlobal(methodName.global)); + var defineProperty = new WasmCall(defineStaticPropertyFunction(context), new WasmGetGlobal(global), + jsMethodName, new WasmFunctionReference(getter), setterRef); + expressions.add(defineProperty); + } + } + + private void exportClass(WasmGCJsoContext context, ClassReader cls, WasmGlobal global, + List expressions) { + var exportName = getClassAliasName(cls); + var globalName = context.names().topLevel("teavm.js.export.class@" + exportName); + var exportGlobal = new WasmGlobal(globalName, WasmType.Reference.EXTERN, + new WasmNullConstant(WasmType.Reference.EXTERN)); + exportGlobal.setExportName(exportName); + context.module().globals.add(exportGlobal); + + var exported = new WasmCall(exportClassFunction(context), new WasmGetGlobal(global)); + expressions.add(new WasmSetGlobal(exportGlobal, exported)); + } + + private String parentExportedClass(WasmGCJsoContext context, String className) { + while (className != null) { + var cls = context.classes().get(className); + if (cls == null) { + return null; + } + if (cls.getInterfaces().contains(JSMarshallable.class.getName())) { + return className; + } + className = cls.getParent(); + } + return null; + } + + private WasmFunction createClassFunction(WasmGCJsoContext context) { + if (createClassFunction == null) { + createClassFunction = new WasmFunction(context.functionTypes().of(WasmType.Reference.EXTERN, + WasmType.Reference.EXTERN, WasmType.Reference.EXTERN, WasmType.Reference.FUNC)); + createClassFunction.setName(context.names().suggestForClass("teavm.jso@createClass")); + createClassFunction.setImportName("createClass"); + createClassFunction.setImportModule("teavmJso"); + context.module().functions.add(createClassFunction); + } + return createClassFunction; + } + + private WasmFunction defineFunctionFunction(WasmGCJsoContext context) { + if (defineFunctionFunction == null) { + defineFunctionFunction = new WasmFunction(context.functionTypes().of(WasmType.Reference.EXTERN, + WasmType.Reference.FUNC)); + defineFunctionFunction.setName(context.names().suggestForClass("teavm.jso@defineFunction")); + defineFunctionFunction.setImportName("defineFunction"); + defineFunctionFunction.setImportModule("teavmJso"); + context.module().functions.add(defineFunctionFunction); + } + return defineFunctionFunction; + } + + private WasmFunction defineMethodFunction(WasmGCJsoContext context) { + if (defineMethodFunction == null) { + defineMethodFunction = new WasmFunction(context.functionTypes().of(null, + WasmType.Reference.EXTERN, WasmType.Reference.EXTERN, WasmType.Reference.FUNC)); + defineMethodFunction.setName(context.names().suggestForClass("teavm.jso@defineMethod")); + defineMethodFunction.setImportName("defineMethod"); + defineMethodFunction.setImportModule("teavmJso"); + context.module().functions.add(defineMethodFunction); + } + return defineMethodFunction; + } + + private WasmFunction defineStaticMethodFunction(WasmGCJsoContext context) { + if (defineStaticMethodFunction == null) { + defineStaticMethodFunction = new WasmFunction(context.functionTypes().of(null, + WasmType.Reference.EXTERN, WasmType.Reference.EXTERN, WasmType.Reference.FUNC)); + defineStaticMethodFunction.setName(context.names().suggestForClass("teavm.jso@defineStaticMethod")); + defineStaticMethodFunction.setImportName("defineStaticMethod"); + defineStaticMethodFunction.setImportModule("teavmJso"); + context.module().functions.add(defineStaticMethodFunction); + } + return defineStaticMethodFunction; + } + + private WasmFunction definePropertyFunction(WasmGCJsoContext context) { + if (definePropertyFunction == null) { + definePropertyFunction = new WasmFunction(context.functionTypes().of(null, + WasmType.Reference.EXTERN, WasmType.Reference.EXTERN, WasmType.Reference.FUNC, + WasmType.Reference.FUNC)); + definePropertyFunction.setName(context.names().suggestForClass("teavm.jso@defineProperty")); + definePropertyFunction.setImportName("defineProperty"); + definePropertyFunction.setImportModule("teavmJso"); + context.module().functions.add(definePropertyFunction); + } + return definePropertyFunction; + } + + private WasmFunction defineStaticPropertyFunction(WasmGCJsoContext context) { + if (defineStaticPropertyFunction == null) { + defineStaticPropertyFunction = new WasmFunction(context.functionTypes().of(null, + WasmType.Reference.EXTERN, WasmType.Reference.EXTERN, WasmType.Reference.FUNC, + WasmType.Reference.FUNC)); + defineStaticPropertyFunction.setName(context.names().suggestForClass("teavm.jso@defineStaticProperty")); + defineStaticPropertyFunction.setImportName("defineStaticProperty"); + defineStaticPropertyFunction.setImportModule("teavmJso"); + context.module().functions.add(defineStaticPropertyFunction); + } + return defineStaticPropertyFunction; + } + + private WasmFunction exportClassFunction(WasmGCJsoContext context) { + if (exportClassFunction == null) { + exportClassFunction = new WasmFunction(context.functionTypes().of(WasmType.Reference.EXTERN, + WasmType.Reference.EXTERN)); + exportClassFunction.setName(context.names().suggestForClass("teavm.jso@exportClass")); + exportClassFunction.setImportName("exportClass"); + exportClassFunction.setImportModule("teavmJso"); + context.module().functions.add(exportClassFunction); + } + return exportClassFunction; + } + + private String getClassAliasName(ClassReader cls) { + var name = cls.getSimpleName(); + if (name == null) { + name = cls.getName().substring(cls.getName().lastIndexOf('.') + 1); + } + var jsExport = cls.getAnnotations().get(JSClass.class.getName()); + if (jsExport != null) { + var nameValue = jsExport.getValue("name"); + if (nameValue != null) { + var nameValueString = nameValue.getString(); + if (!nameValueString.isEmpty()) { + name = nameValueString; + } + } + } + return name; + } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoContext.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoContext.java index a29e014bf..269d1ddb2 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoContext.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoContext.java @@ -18,6 +18,7 @@ package org.teavm.jso.impl.wasmgc; import java.util.function.Consumer; import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.WasmFunctionTypes; +import org.teavm.backend.wasm.gc.WasmGCClassConsumerContext; import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper; import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringProvider; @@ -26,8 +27,11 @@ import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicContext; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmModule; import org.teavm.backend.wasm.model.WasmTag; +import org.teavm.model.ClassReaderSource; interface WasmGCJsoContext { + ClassReaderSource classes(); + WasmModule module(); WasmFunctionTypes functionTypes(); @@ -42,10 +46,17 @@ interface WasmGCJsoContext { WasmTag exceptionTag(); + String entryPoint(); + void addToInitializer(Consumer initializerContributor); static WasmGCJsoContext wrap(WasmGCIntrinsicContext context) { return new WasmGCJsoContext() { + @Override + public ClassReaderSource classes() { + return context.hierarchy().getClassSource(); + } + @Override public WasmModule module() { return context.module(); @@ -81,6 +92,11 @@ interface WasmGCJsoContext { return context.exceptionTag(); } + @Override + public String entryPoint() { + return context.entryPoint(); + } + @Override public void addToInitializer(Consumer initializerContributor) { context.addToInitializer(initializerContributor); @@ -90,6 +106,11 @@ interface WasmGCJsoContext { static WasmGCJsoContext wrap(WasmGCCustomGeneratorContext context) { return new WasmGCJsoContext() { + @Override + public ClassReaderSource classes() { + return context.classes(); + } + @Override public WasmModule module() { return context.module(); @@ -125,6 +146,65 @@ interface WasmGCJsoContext { return context.exceptionTag(); } + @Override + public String entryPoint() { + return context.entryPoint(); + } + + @Override + public void addToInitializer(Consumer initializerContributor) { + context.addToInitializer(initializerContributor); + } + }; + } + + static WasmGCJsoContext wrap(WasmGCClassConsumerContext context) { + return new WasmGCJsoContext() { + @Override + public ClassReaderSource classes() { + return context.classes(); + } + + @Override + public WasmModule module() { + return context.module(); + } + + @Override + public WasmFunctionTypes functionTypes() { + return context.functionTypes(); + } + + @Override + public BaseWasmFunctionRepository functions() { + return context.functions(); + } + + @Override + public WasmGCNameProvider names() { + return context.names(); + } + + @Override + public WasmGCStringProvider strings() { + return context.strings(); + } + + @Override + public WasmGCTypeMapper typeMapper() { + return context.typeMapper(); + } + + @Override + public WasmTag exceptionTag() { + return context.exceptionTag(); + } + + @Override + public String entryPoint() { + return context.entryPoint(); + } + @Override public void addToInitializer(Consumer initializerContributor) { context.addToInitializer(initializerContributor); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCMarshallMethodGenerator.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCMarshallMethodGenerator.java index bc9dc8d2c..9f475b63e 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCMarshallMethodGenerator.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCMarshallMethodGenerator.java @@ -15,31 +15,20 @@ */ package org.teavm.jso.impl.wasmgc; -import java.util.ArrayList; import org.teavm.backend.wasm.generators.gc.WasmGCCustomGenerator; import org.teavm.backend.wasm.generators.gc.WasmGCCustomGeneratorContext; 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.WasmType; import org.teavm.backend.wasm.model.expression.WasmCall; -import org.teavm.backend.wasm.model.expression.WasmExpression; -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.WasmNullConstant; -import org.teavm.backend.wasm.model.expression.WasmSetGlobal; -import org.teavm.jso.impl.AliasCollector; -import org.teavm.model.ClassReader; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; class WasmGCMarshallMethodGenerator implements WasmGCCustomGenerator { private WasmGCJsoCommonGenerator commonGen; private WasmFunction javaObjectToJSFunction; - private WasmFunction createClassFunction; - private WasmFunction defineMethodFunction; - private WasmFunction definePropertyFunction; WasmGCMarshallMethodGenerator(WasmGCJsoCommonGenerator commonGen) { this.commonGen = commonGen; @@ -52,8 +41,7 @@ class WasmGCMarshallMethodGenerator implements WasmGCCustomGenerator { var thisLocal = new WasmLocal(context.typeMapper().mapType(ValueType.object(method.getClassName())), "this"); function.add(thisLocal); - var cls = context.classes().get(method.getClassName()); - var jsClassGlobal = defineClass(jsoContext, cls); + var jsClassGlobal = commonGen.getDefinedClass(jsoContext, method.getClassName()); var wrapperFunction = javaObjectToJSFunction(context); function.getBody().add(new WasmCall(wrapperFunction, new WasmGetLocal(thisLocal), new WasmGetGlobal(jsClassGlobal))); @@ -71,91 +59,4 @@ class WasmGCMarshallMethodGenerator implements WasmGCCustomGenerator { return javaObjectToJSFunction; } - WasmGlobal defineClass(WasmGCJsoContext context, ClassReader cls) { - var name = context.names().topLevel(context.names().suggestForClass(cls.getName())); - var global = new WasmGlobal(name, WasmType.Reference.EXTERN, new WasmNullConstant(WasmType.Reference.EXTERN)); - context.module().globals.add(global); - - var expressions = new ArrayList(); - var className = context.strings().getStringConstant(cls.getName()); - var jsClassName = commonGen.stringToJs(context, new WasmGetGlobal(className.global)); - var createClass = new WasmCall(createClassFunction(context), jsClassName); - expressions.add(new WasmSetGlobal(global, createClass)); - - var members = AliasCollector.collectMembers(cls, AliasCollector::isInstanceMember); - for (var aliasEntry : members.methods.entrySet()) { - if (!aliasEntry.getValue().getClassName().equals(cls.getName())) { - continue; - } - var fn = context.functions().forStaticMethod(aliasEntry.getValue()); - fn.setReferenced(true); - var methodName = context.strings().getStringConstant(aliasEntry.getKey()); - var jsMethodName = commonGen.stringToJs(context, new WasmGetGlobal(methodName.global)); - var defineMethod = new WasmCall(defineMethodFunction(context), new WasmGetGlobal(global), - jsMethodName, new WasmFunctionReference(fn)); - expressions.add(defineMethod); - } - - for (var aliasEntry : members.properties.entrySet()) { - var property = aliasEntry.getValue(); - if (!property.getter.getClassName().equals(cls.getName())) { - continue; - } - var getter = context.functions().forStaticMethod(property.getter); - getter.setReferenced(true); - WasmFunction setter = null; - if (property.setter != null) { - setter = context.functions().forStaticMethod(property.setter); - setter.setReferenced(true); - } - var setterRef = setter != null - ? new WasmFunctionReference(setter) - : new WasmNullConstant(WasmType.Reference.FUNC); - var methodName = context.strings().getStringConstant(aliasEntry.getKey()); - var jsMethodName = commonGen.stringToJs(context, new WasmGetGlobal(methodName.global)); - var defineProperty = new WasmCall(definePropertyFunction(context), new WasmGetGlobal(global), - jsMethodName, new WasmFunctionReference(getter), setterRef); - expressions.add(defineProperty); - } - - context.addToInitializer(f -> f.getBody().addAll(expressions)); - return global; - } - - private WasmFunction createClassFunction(WasmGCJsoContext context) { - if (createClassFunction == null) { - createClassFunction = new WasmFunction(context.functionTypes().of(WasmType.Reference.EXTERN, - WasmType.Reference.EXTERN)); - createClassFunction.setName(context.names().suggestForClass("teavm.jso@createClass")); - createClassFunction.setImportName("createClass"); - createClassFunction.setImportModule("teavmJso"); - context.module().functions.add(createClassFunction); - } - return createClassFunction; - } - - private WasmFunction defineMethodFunction(WasmGCJsoContext context) { - if (defineMethodFunction == null) { - defineMethodFunction = new WasmFunction(context.functionTypes().of(null, - WasmType.Reference.EXTERN, WasmType.Reference.EXTERN, WasmType.Reference.FUNC)); - defineMethodFunction.setName(context.names().suggestForClass("teavm.jso@defineMethod")); - defineMethodFunction.setImportName("defineMethod"); - defineMethodFunction.setImportModule("teavmJso"); - context.module().functions.add(defineMethodFunction); - } - return defineMethodFunction; - } - - private WasmFunction definePropertyFunction(WasmGCJsoContext context) { - if (definePropertyFunction == null) { - definePropertyFunction = new WasmFunction(context.functionTypes().of(null, - WasmType.Reference.EXTERN, WasmType.Reference.EXTERN, WasmType.Reference.FUNC, - WasmType.Reference.FUNC)); - definePropertyFunction.setName(context.names().suggestForClass("teavm.jso@defineProperty")); - definePropertyFunction.setImportName("defineProperty"); - definePropertyFunction.setImportModule("teavmJso"); - context.module().functions.add(definePropertyFunction); - } - return definePropertyFunction; - } } diff --git a/samples/benchmark/src/main/webapp/teavm-wasm-gc.html b/samples/benchmark/src/main/webapp/teavm-wasm-gc.html index 461e3698b..5676d49fe 100644 --- a/samples/benchmark/src/main/webapp/teavm-wasm-gc.html +++ b/samples/benchmark/src/main/webapp/teavm-wasm-gc.html @@ -22,7 +22,7 @@ diff --git a/samples/pi/src/main/webapp/wasm-gc.html b/samples/pi/src/main/webapp/wasm-gc.html index dced17163..0268b8ad3 100644 --- a/samples/pi/src/main/webapp/wasm-gc.html +++ b/samples/pi/src/main/webapp/wasm-gc.html @@ -30,7 +30,7 @@ \ No newline at end of file