From 383fee67c57e5efea3535db711655203b68f04da Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sun, 29 Sep 2024 13:26:09 +0200 Subject: [PATCH] wasm gc: support exporting Java classes to JavaScript --- .../javascript/rendering/Renderer.java | 7 + .../rendering/RenderingManager.java | 3 + .../org/teavm/backend/wasm/WasmGCTarget.java | 1 + .../gc/WasmGCDeclarationsGenerator.java | 3 + .../gc/classes/WasmGCClassGenerator.java | 9 +- .../WasmGCCustomTypeMapperFactoryContext.java | 2 + .../gc/methods/WasmGCMethodGenerator.java | 5 + .../gc/WasmGCCustomGeneratorContext.java | 4 + .../generators/gc/WasmGCCustomGenerators.java | 3 + .../expression/WasmExternConversion.java | 19 +++ .../expression/WasmExternConversionType.java | 21 +++ .../teavm/dependency/DependencyAnalyzer.java | 84 +-------- .../dependency/DependencyClassSource.java | 128 +++++++++++--- .../org/teavm/backend/javascript/runtime.js | 6 +- .../org/teavm/backend/wasm/wasm-gc-runtime.js | 124 ++++++++++---- .../org/teavm/jso/impl/AliasCollector.java | 129 ++++++++++++++ .../src/main/java/org/teavm/jso/impl/JS.java | 1 + .../org/teavm/jso/impl/JSAliasRenderer.java | 129 +++----------- .../org/teavm/jso/impl/JSBodyDelegate.java | 19 +++ .../org/teavm/jso/impl/JSClassProcessor.java | 67 +++++--- .../org/teavm/jso/impl/JSClassToExpose.java | 19 +++ .../teavm/jso/impl/JSDependencyListener.java | 28 ++- .../org/teavm/jso/impl/JSInstanceExpose.java | 19 +++ .../org/teavm/jso/impl/JSMarshallable.java | 22 +++ .../java/org/teavm/jso/impl/JSMethods.java | 22 ++- .../java/org/teavm/jso/impl/JSOPlugin.java | 12 +- .../jso/impl/JSObjectClassTransformer.java | 49 +++++- .../java/org/teavm/jso/impl/JSTypeHelper.java | 4 +- .../org/teavm/jso/impl/JSTypeInference.java | 7 +- .../org/teavm/jso/impl/JSValueMarshaller.java | 20 +++ .../java/org/teavm/jso/impl/JSWrapper.java | 98 ++++++----- .../teavm/jso/impl/JSWrapperDependency.java | 44 +++-- .../teavm/jso/impl/JSWrapperGenerator.java | 2 + .../jso/impl/wasmgc/WasmGCBodyIntrinsic.java | 19 ++- .../jso/impl/wasmgc/WasmGCJSBodyRenderer.java | 11 +- .../jso/impl/wasmgc/WasmGCJSFunctions.java | 5 +- .../jso/impl/wasmgc/WasmGCJSTypeMapper.java | 9 +- .../wasmgc/WasmGCJSWrapperTransformer.java | 54 ++++++ .../org/teavm/jso/impl/wasmgc/WasmGCJso.java | 6 +- ...tor.java => WasmGCJsoCommonGenerator.java} | 31 ++-- .../jso/impl/wasmgc/WasmGCJsoContext.java | 108 ++++++++++++ .../wasmgc/WasmGCMarshallMethodGenerator.java | 161 ++++++++++++++++++ .../WasmGCMarshallMethodGeneratorFactory.java | 42 +++++ .../org/teavm/jso/test/ExportClassTest.java | 2 +- tools/deobfuscator-js/build.gradle.kts | 2 +- 45 files changed, 1183 insertions(+), 377 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversion.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversionType.java create mode 100644 jso/impl/src/main/java/org/teavm/jso/impl/AliasCollector.java create mode 100644 jso/impl/src/main/java/org/teavm/jso/impl/JSBodyDelegate.java create mode 100644 jso/impl/src/main/java/org/teavm/jso/impl/JSClassToExpose.java create mode 100644 jso/impl/src/main/java/org/teavm/jso/impl/JSInstanceExpose.java create mode 100644 jso/impl/src/main/java/org/teavm/jso/impl/JSMarshallable.java create mode 100644 jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSWrapperTransformer.java rename jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/{WasmGCBodyGenerator.java => WasmGCJsoCommonGenerator.java} (81%) create mode 100644 jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoContext.java create mode 100644 jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCMarshallMethodGenerator.java create mode 100644 jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCMarshallMethodGeneratorFactory.java diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java index 9fe0c84b6..36bfb4807 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/Renderer.java @@ -77,6 +77,7 @@ public class Renderer implements RenderingManager { private final SourceWriter writer; private final ListableClassReaderSource classSource; + private final ClassReaderSource originalClassSource; private final ClassLoader classLoader; private final Properties properties = new Properties(); private final ServiceRepository services; @@ -103,6 +104,7 @@ public class Renderer implements RenderingManager { List exports, String entryPoint) { this.writer = writer; this.classSource = context.getClassSource(); + this.originalClassSource = context.getInitialClassSource(); this.classLoader = context.getClassLoader(); this.services = context.getServices(); this.asyncMethods = new HashSet<>(asyncMethods); @@ -152,6 +154,11 @@ public class Renderer implements RenderingManager { return classSource; } + @Override + public ClassReaderSource getOriginalClassSource() { + return originalClassSource; + } + @Override public ClassLoader getClassLoader() { return classLoader; diff --git a/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingManager.java b/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingManager.java index 54f166dfe..2f908def3 100644 --- a/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingManager.java +++ b/core/src/main/java/org/teavm/backend/javascript/rendering/RenderingManager.java @@ -18,6 +18,7 @@ package org.teavm.backend.javascript.rendering; import java.util.Properties; import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.common.ServiceRepository; +import org.teavm.model.ClassReaderSource; import org.teavm.model.ListableClassReaderSource; import org.teavm.model.MethodReference; @@ -32,6 +33,8 @@ public interface RenderingManager extends ServiceRepository { ListableClassReaderSource getClassSource(); + ClassReaderSource getOriginalClassSource(); + ClassLoader getClassLoader(); Properties getProperties(); 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 32bab4292..e47e5cb18 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -175,6 +175,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { var declarationsGenerator = new WasmGCDeclarationsGenerator( module, classes, + controller.getUnprocessedClassSource(), controller.getClassLoader(), controller.getClassInitializerInfo(), controller.getDependencyInfo(), 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 a4601b738..1bf83fd49 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 @@ -34,6 +34,7 @@ import org.teavm.backend.wasm.model.WasmModule; import org.teavm.dependency.DependencyInfo; import org.teavm.diagnostics.Diagnostics; import org.teavm.model.ClassHierarchy; +import org.teavm.model.ClassReaderSource; import org.teavm.model.ListableClassHolderSource; import org.teavm.model.MethodReference; import org.teavm.model.analysis.ClassInitializerInfo; @@ -52,6 +53,7 @@ public class WasmGCDeclarationsGenerator { public WasmGCDeclarationsGenerator( WasmModule module, ListableClassHolderSource classes, + ClassReaderSource originalClasses, ClassLoader classLoader, ClassInitializerInfo classInitializerInfo, DependencyInfo dependencyInfo, @@ -87,6 +89,7 @@ public class WasmGCDeclarationsGenerator { classGenerator = new WasmGCClassGenerator( module, classes, + originalClasses, hierarchy, dependencyInfo, functionTypes, 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 5e198afc3..9fcd0a6dc 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 @@ -102,6 +102,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit private final WasmModule module; private ClassReaderSource classSource; + private ClassReaderSource originalClassSource; private ClassHierarchy hierarchy; private WasmFunctionTypes functionTypes; private TagRegistry tagRegistry; @@ -158,7 +159,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit private boolean hasLoadServices; public WasmGCClassGenerator(WasmModule module, ClassReaderSource classSource, - ClassHierarchy hierarchy, DependencyInfo dependencyInfo, + ClassReaderSource originalClassSource, ClassHierarchy hierarchy, DependencyInfo dependencyInfo, WasmFunctionTypes functionTypes, TagRegistry tagRegistry, ClassMetadataRequirements metadataRequirements, WasmGCVirtualTableProvider virtualTables, BaseWasmFunctionRepository functionProvider, WasmGCNameProvider names, @@ -166,6 +167,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit List customTypeMapperFactories) { this.module = module; this.classSource = classSource; + this.originalClassSource = originalClassSource; this.hierarchy = hierarchy; this.functionTypes = functionTypes; this.tagRegistry = tagRegistry; @@ -195,6 +197,11 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit private WasmGCCustomTypeMapperFactoryContext customTypeMapperFactoryContext() { return new WasmGCCustomTypeMapperFactoryContext() { + @Override + public ClassReaderSource originalClasses() { + return originalClassSource; + } + @Override public ClassReaderSource classes() { return classSource; diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCCustomTypeMapperFactoryContext.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCCustomTypeMapperFactoryContext.java index 50bf875df..09482419e 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCCustomTypeMapperFactoryContext.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCCustomTypeMapperFactoryContext.java @@ -22,6 +22,8 @@ import org.teavm.model.ClassReaderSource; public interface WasmGCCustomTypeMapperFactoryContext { ClassReaderSource classes(); + ClassReaderSource originalClasses(); + WasmModule module(); WasmGCClassInfoProvider classInfoProvider(); 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 6237f6f22..4d9b4a399 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 @@ -433,5 +433,10 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { public WasmGCStringProvider strings() { return context.strings(); } + + @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 389d94924..f6adb6cd9 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 @@ -15,12 +15,14 @@ */ package org.teavm.backend.wasm.generators.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.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.WasmFunction; import org.teavm.backend.wasm.model.WasmModule; import org.teavm.backend.wasm.model.WasmTag; import org.teavm.diagnostics.Diagnostics; @@ -48,4 +50,6 @@ public interface WasmGCCustomGeneratorContext { Diagnostics diagnostics(); WasmGCStringProvider strings(); + + void addToInitializer(Consumer initializerContributor); } 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 19809cdd0..734e257d4 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 @@ -108,6 +108,9 @@ public class WasmGCCustomGenerators implements WasmGCCustomGeneratorProvider { WasmGCCustomGenerator generator = null; for (var factory : factories) { generator = factory.createGenerator(method, factoryContext); + if (generator != null) { + break; + } } result = new Container(generator); generators.put(method, result); diff --git a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversion.java b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversion.java new file mode 100644 index 000000000..bdd4e27f8 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversion.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.backend.wasm.model.expression; + +public class WasmExternConversion { +} diff --git a/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversionType.java b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversionType.java new file mode 100644 index 000000000..379ad8b22 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/model/expression/WasmExternConversionType.java @@ -0,0 +1,21 @@ +/* + * 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.model.expression; + +public enum WasmExternConversionType { + EXTERN_TO_OBJECT, + OBJECT_TO_EXTERN +} diff --git a/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java b/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java index 2dd215208..94bfb7a85 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java +++ b/core/src/main/java/org/teavm/dependency/DependencyAnalyzer.java @@ -42,7 +42,6 @@ import org.teavm.common.ServiceRepository; import org.teavm.diagnostics.Diagnostics; import org.teavm.interop.PlatformMarker; import org.teavm.model.AnnotationReader; -import org.teavm.model.BasicBlock; import org.teavm.model.CallLocation; import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; @@ -52,8 +51,6 @@ import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; import org.teavm.model.FieldReader; import org.teavm.model.FieldReference; -import org.teavm.model.Instruction; -import org.teavm.model.InvokeDynamicInstruction; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodHolder; import org.teavm.model.MethodReader; @@ -61,12 +58,7 @@ import org.teavm.model.MethodReference; import org.teavm.model.Program; import org.teavm.model.ReferenceCache; import org.teavm.model.ValueType; -import org.teavm.model.emit.ProgramEmitter; -import org.teavm.model.emit.ValueEmitter; -import org.teavm.model.instructions.AssignInstruction; -import org.teavm.model.instructions.NullConstantInstruction; import org.teavm.model.optimization.UnreachableBasicBlockEliminator; -import org.teavm.model.util.BasicBlockSplitter; import org.teavm.model.util.ModelUtils; import org.teavm.model.util.ProgramUtils; import org.teavm.parsing.Parser; @@ -102,7 +94,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo { private Diagnostics diagnostics; DefaultCallGraph callGraph = new DefaultCallGraph(); private DependencyAgent agent; - Map bootstrapMethodSubstitutors = new HashMap<>(); Map dependencyPlugins = new HashMap<>(); private boolean completing; private Map superClassFilters = new HashMap<>(); @@ -119,7 +110,8 @@ public abstract class DependencyAnalyzer implements DependencyInfo { this.unprocessedClassSource = classSource; this.diagnostics = diagnostics; this.referenceCache = referenceCache; - this.classSource = new DependencyClassSource(classSource, diagnostics, incrementalCache, platformTags); + agent = new DependencyAgent(this); + this.classSource = new DependencyClassSource(agent, classSource, diagnostics, incrementalCache, platformTags); agentClassSource = this.classSource; classHierarchy = new ClassHierarchy(this.classSource); this.classLoader = classLoader; @@ -138,7 +130,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo { classCache = new CachedFunction<>(this::createClassDependency); - agent = new DependencyAgent(this); classType = getType("java.lang.Class"); } @@ -274,7 +265,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo { dep.used = false; lock(dep, false); deferredTasks.add(() -> { - processInvokeDynamic(dep); classSource.getReferenceResolver().use(dep.method.getReference(), diagnostics); processMethod(dep); dep.used = true; @@ -489,7 +479,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo { void scheduleMethodAnalysis(MethodDependency dep) { classSource.getReferenceResolver().use(dep.getReference(), diagnostics); deferredTasks.add(() -> { - processInvokeDynamic(dep); classSource.getReferenceResolver().use(dep.getReference(), diagnostics); processMethod(dep); }); @@ -755,6 +744,7 @@ public abstract class DependencyAnalyzer implements DependencyInfo { agent.cleanup(); listeners.clear(); + classSource.dispose(); agentClassSource = classSourcePacker.pack(classSource, ClassClosureAnalyzer.build(classSource, new ArrayList<>(classSource.cache.keySet()))); if (classSource != agentClassSource) { @@ -762,7 +752,7 @@ public abstract class DependencyAnalyzer implements DependencyInfo { generatedClassNames.addAll(classSource.getGeneratedClassNames()); } classSource.innerHierarchy = null; - classSource.dispose(); + classSource = null; methodReaderCache = null; } @@ -827,7 +817,7 @@ public abstract class DependencyAnalyzer implements DependencyInfo { } public void addBootstrapMethodSubstitutor(MethodReference method, BootstrapMethodSubstitutor substitutor) { - bootstrapMethodSubstitutors.put(method, substitutor); + classSource.bootstrapMethodSubstitutors.put(method, substitutor); } public void addDependencyPlugin(MethodReference method, DependencyPlugin dependencyPlugin) { @@ -863,70 +853,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo { return result; } - private void processInvokeDynamic(MethodDependency methodDep) { - if (methodDep.method == null) { - return; - } - - Program program = methodDep.method.getProgram(); - if (program == null) { - return; - } - - ProgramEmitter pe = ProgramEmitter.create(program, classHierarchy); - BasicBlockSplitter splitter = new BasicBlockSplitter(program); - for (int i = 0; i < program.basicBlockCount(); ++i) { - BasicBlock block = program.basicBlockAt(i); - for (Instruction insn : block) { - if (!(insn instanceof InvokeDynamicInstruction)) { - continue; - } - block = insn.getBasicBlock(); - - InvokeDynamicInstruction indy = (InvokeDynamicInstruction) insn; - MethodReference bootstrapMethod = new MethodReference(indy.getBootstrapMethod().getClassName(), - indy.getBootstrapMethod().getName(), indy.getBootstrapMethod().signature()); - BootstrapMethodSubstitutor substitutor = bootstrapMethodSubstitutors.get(bootstrapMethod); - if (substitutor == null) { - NullConstantInstruction nullInsn = new NullConstantInstruction(); - nullInsn.setReceiver(indy.getReceiver()); - nullInsn.setLocation(indy.getLocation()); - insn.replace(nullInsn); - CallLocation location = new CallLocation(methodDep.getReference(), insn.getLocation()); - diagnostics.error(location, "Substitutor for bootstrap method {{m0}} was not found", - bootstrapMethod); - continue; - } - - BasicBlock splitBlock = splitter.split(block, insn); - - pe.enter(block); - pe.setCurrentLocation(indy.getLocation()); - insn.delete(); - - List arguments = new ArrayList<>(); - for (int k = 0; k < indy.getArguments().size(); ++k) { - arguments.add(pe.var(indy.getArguments().get(k), indy.getMethod().parameterType(k))); - } - DynamicCallSite callSite = new DynamicCallSite( - methodDep.getReference(), indy.getMethod(), - indy.getInstance() != null ? pe.var(indy.getInstance(), - ValueType.object(methodDep.getMethod().getOwnerName())) : null, - arguments, indy.getBootstrapMethod(), indy.getBootstrapArguments(), - agent); - ValueEmitter result = substitutor.substitute(callSite, pe); - if (result.getVariable() != null && result.getVariable() != indy.getReceiver() - && indy.getReceiver() != null) { - AssignInstruction assign = new AssignInstruction(); - assign.setAssignee(result.getVariable()); - assign.setReceiver(indy.getReceiver()); - pe.addInstruction(assign); - } - pe.jump(splitBlock); - } - } - splitter.fixProgram(); - } static class IncrementalCache implements IncrementalDependencyProvider, IncrementalDependencyRegistration { private final String[] emptyArray = new String[0]; diff --git a/core/src/main/java/org/teavm/dependency/DependencyClassSource.java b/core/src/main/java/org/teavm/dependency/DependencyClassSource.java index 1deb5c2b6..7b6c7984b 100644 --- a/core/src/main/java/org/teavm/dependency/DependencyClassSource.java +++ b/core/src/main/java/org/teavm/dependency/DependencyClassSource.java @@ -17,12 +17,15 @@ package org.teavm.dependency; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import org.teavm.cache.IncrementalDependencyRegistration; import org.teavm.diagnostics.Diagnostics; +import org.teavm.model.BasicBlock; +import org.teavm.model.CallLocation; import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderSource; @@ -30,12 +33,23 @@ import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; +import org.teavm.model.Instruction; +import org.teavm.model.InvokeDynamicInstruction; import org.teavm.model.MethodHolder; +import org.teavm.model.MethodReference; +import org.teavm.model.Program; +import org.teavm.model.ValueType; +import org.teavm.model.emit.ProgramEmitter; +import org.teavm.model.emit.ValueEmitter; +import org.teavm.model.instructions.AssignInstruction; +import org.teavm.model.instructions.NullConstantInstruction; import org.teavm.model.optimization.UnreachableBasicBlockEliminator; import org.teavm.model.transformation.ClassInitInsertion; +import org.teavm.model.util.BasicBlockSplitter; import org.teavm.model.util.ModelUtils; class DependencyClassSource implements ClassHolderSource { + private DependencyAgent agent; private ClassReaderSource innerSource; ClassHierarchy innerHierarchy; private Diagnostics diagnostics; @@ -48,9 +62,12 @@ class DependencyClassSource implements ClassHolderSource { private ReferenceResolver referenceResolver; private ClassInitInsertion classInitInsertion; private String entryPoint; + Map bootstrapMethodSubstitutors = new HashMap<>(); + private boolean disposed; - DependencyClassSource(ClassReaderSource innerSource, Diagnostics diagnostics, + DependencyClassSource(DependencyAgent agent, ClassReaderSource innerSource, Diagnostics diagnostics, IncrementalDependencyRegistration dependencyRegistration, String[] platformTags) { + this.agent = agent; this.innerSource = innerSource; this.diagnostics = diagnostics; innerHierarchy = new ClassHierarchy(innerSource); @@ -65,7 +82,16 @@ class DependencyClassSource implements ClassHolderSource { @Override public ClassHolder get(String name) { - return cache.computeIfAbsent(name, n -> Optional.ofNullable(findAndTransformClass(n))).orElse(null); + var result = cache.get(name); + if (result == null) { + var cls = findClass(name); + result = Optional.ofNullable(cls); + cache.put(name, result); + if (cls != null) { + transformClass(cls); + } + } + return result.orElse(null); } public void submit(ClassHolder cls) { @@ -84,26 +110,27 @@ class DependencyClassSource implements ClassHolderSource { cache.remove(cls.getName()); } - private ClassHolder findAndTransformClass(String name) { - var cls = findClass(name); - if (cls != null) { - if (!transformers.isEmpty()) { - for (var transformer : transformers) { - transformer.transformClass(cls, transformContext); - } - } + private void transformClass(ClassHolder cls) { + if (!disposed) { for (var method : cls.getMethods()) { - if (method.getProgram() != null) { - var program = method.getProgram(); - method.setProgramSupplier(m -> { - referenceResolver.resolve(m, program); - classInitInsertion.apply(m, program); - return program; - }); - } + processInvokeDynamic(method); + } + } + if (!transformers.isEmpty()) { + for (var transformer : transformers) { + transformer.transformClass(cls, transformContext); + } + } + for (var method : cls.getMethods()) { + if (method.getProgram() != null) { + var program = method.getProgram(); + method.setProgramSupplier(m -> { + referenceResolver.resolve(m, program); + classInitInsertion.apply(m, program); + return program; + }); } } - return cls; } private ClassHolder findClass(String name) { @@ -132,6 +159,69 @@ class DependencyClassSource implements ClassHolderSource { public void dispose() { transformers.clear(); + bootstrapMethodSubstitutors.clear(); + disposed = true; + } + + private void processInvokeDynamic(MethodHolder method) { + Program program = method.getProgram(); + if (program == null) { + return; + } + + ProgramEmitter pe = ProgramEmitter.create(program, innerHierarchy); + BasicBlockSplitter splitter = new BasicBlockSplitter(program); + for (int i = 0; i < program.basicBlockCount(); ++i) { + BasicBlock block = program.basicBlockAt(i); + for (Instruction insn : block) { + if (!(insn instanceof InvokeDynamicInstruction)) { + continue; + } + block = insn.getBasicBlock(); + + InvokeDynamicInstruction indy = (InvokeDynamicInstruction) insn; + MethodReference bootstrapMethod = new MethodReference(indy.getBootstrapMethod().getClassName(), + indy.getBootstrapMethod().getName(), indy.getBootstrapMethod().signature()); + BootstrapMethodSubstitutor substitutor = bootstrapMethodSubstitutors.get(bootstrapMethod); + if (substitutor == null) { + NullConstantInstruction nullInsn = new NullConstantInstruction(); + nullInsn.setReceiver(indy.getReceiver()); + nullInsn.setLocation(indy.getLocation()); + insn.replace(nullInsn); + CallLocation location = new CallLocation(method.getReference(), insn.getLocation()); + diagnostics.error(location, "Substitutor for bootstrap method {{m0}} was not found", + bootstrapMethod); + continue; + } + + BasicBlock splitBlock = splitter.split(block, insn); + + pe.enter(block); + pe.setCurrentLocation(indy.getLocation()); + insn.delete(); + + List arguments = new ArrayList<>(); + for (int k = 0; k < indy.getArguments().size(); ++k) { + arguments.add(pe.var(indy.getArguments().get(k), indy.getMethod().parameterType(k))); + } + DynamicCallSite callSite = new DynamicCallSite( + method.getReference(), indy.getMethod(), + indy.getInstance() != null ? pe.var(indy.getInstance(), + ValueType.object(method.getOwnerName())) : null, + arguments, indy.getBootstrapMethod(), indy.getBootstrapArguments(), + agent); + ValueEmitter result = substitutor.substitute(callSite, pe); + if (result.getVariable() != null && result.getVariable() != indy.getReceiver() + && indy.getReceiver() != null) { + AssignInstruction assign = new AssignInstruction(); + assign.setAssignee(result.getVariable()); + assign.setReceiver(indy.getReceiver()); + pe.addInstruction(assign); + } + pe.jump(splitBlock); + } + } + splitter.fixProgram(); } final ClassHolderTransformerContext transformContext = new ClassHolderTransformerContext() { diff --git a/core/src/main/resources/org/teavm/backend/javascript/runtime.js b/core/src/main/resources/org/teavm/backend/javascript/runtime.js index 073016546..83820025b 100644 --- a/core/src/main/resources/org/teavm/backend/javascript/runtime.js +++ b/core/src/main/resources/org/teavm/backend/javascript/runtime.js @@ -118,4 +118,8 @@ let $rt_apply = (instance, method, args) => instance[method].apply(instance, arg let $rt_apply_topLevel = (method, args) => method.apply(null, args); -let $rt_skip = (array, count) => count === 0 ? array : Array.prototype.slice.call(array, count); \ No newline at end of file +let $rt_skip = (array, count) => count === 0 ? array : Array.prototype.slice.call(array, count); + +let $rt_callWithReceiver = f => function() { + return f.apply(null, [this].concat(Array.prototype.slice.call(arguments))); +} \ No newline at end of file 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 515f4477b..d1445fb70 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 @@ -20,6 +20,9 @@ TeaVM.wasm = function() { let getGlobalName = function(name) { return eval(name); } + let javaObjectSymbol = Symbol("javaObject"); + let functionsSymbol = Symbol("functions"); + let javaWrappers = new WeakMap(); function defaults(imports) { let stderr = ""; let stdout = ""; @@ -32,42 +35,28 @@ TeaVM.wasm = function() { exports.reportGarbageCollectedString(heldValue); }); imports.teavmDate = { - currentTimeMillis() { - return new Date().getTime(); - }, - dateToString(timestamp) { - return stringToJava(new Date(timestamp).toString()); - }, - getYear(timestamp) { - return new Date(timestamp).getFullYear(); - }, + currentTimeMillis: () => new Date().getTime(), + dateToString: timestamp => stringToJava(new Date(timestamp).toString()), + getYear: timestamp =>new Date(timestamp).getFullYear(), setYear(timestamp, year) { let date = new Date(timestamp); date.setFullYear(year); return date.getTime(); }, - getMonth(timestamp) { - return new Date(timestamp).getMonth(); - }, + getMonth: timestamp =>new Date(timestamp).getMonth(), setMonth(timestamp, month) { let date = new Date(timestamp); date.setMonth(month); return date.getTime(); }, - getDate(timestamp) { - return new Date(timestamp).getDate(); - }, + getDate: timestamp =>new Date(timestamp).getDate(), setDate(timestamp, value) { let date = new Date(timestamp); date.setDate(value); return date.getTime(); }, - create(year, month, date, hrs, min, sec) { - return new Date(year, month, date, hrs, min, sec).getTime(); - }, - createFromUTC(year, month, date, hrs, min, sec) { - return Date.UTC(year, month, date, hrs, min, sec); - } + create: (year, month, date, hrs, min, sec) => new Date(year, month, date, hrs, min, sec).getTime(), + createFromUTC: (year, month, date, hrs, min, sec) => Date.UTC(year, month, date, hrs, min, sec) }; imports.teavmConsole = { putcharStderr(c) { @@ -106,6 +95,22 @@ TeaVM.wasm = function() { function identity(value) { return value; } + function sanitizeName(str) { + let result = ""; + let firstChar = str.charAt(0); + result += isIdentifierStart(firstChar) ? firstChar : '_'; + for (let i = 1; i < str.length; ++i) { + let c = str.charAt(i) + result += isIdentifierPart(c) ? c : '_'; + } + return result; + } + function isIdentifierStart(s) { + return s >= 'A' && s <= 'Z' || s >= 'a' && s <= 'z' || s === '_' || s === '$'; + } + function isIdentifierPart(s) { + return isIdentifierStart(s) || s >= '0' && s <= '9'; + } imports.teavmJso = { emptyString: () => "", stringFromCharCode: code => String.fromCharCode(code), @@ -120,25 +125,76 @@ TeaVM.wasm = function() { getPropertyPure: (obj, prop) => obj[prop], setProperty: (obj, prop, value) => obj[prop] = value, setPropertyPure: (obj, prop) => obj[prop] = value, - global: getGlobalName + global: getGlobalName, + createClass(name) { + let fn = new Function( + "javaObjectSymbol", + "functionsSymbol", + `return function JavaClass_${sanitizeName(name)}(javaObject) { + this[javaObjectSymbol] = javaObject; + this[functionsSymbol] = null; + };` + ); + return fn(javaObjectSymbol, functionsSymbol); + }, + defineMethod(cls, name, fn) { + cls.prototype[name] = function(...args) { + return fn(this, ...args); + } + }, + defineProperty(cls, name, getFn, setFn) { + let descriptor = { + get() { + return getFn(this); + } + }; + if (setFn !== null) { + descriptor.set = function(value) { + setFn(this, value); + } + } + Object.defineProperty(cls.prototype, name, descriptor); + }, + javaObjectToJS(instance, cls) { + let existing = javaWrappers.get(instance); + if (typeof existing != "undefined") { + let result = existing.deref(); + if (typeof result !== "undefined") { + return result; + } + } + let obj = new cls(instance); + javaWrappers.set(instance, new WeakRef(obj)); + return obj; + }, + unwrapJavaObject(instance) { + return instance[javaObjectSymbol]; + }, + asFunction(instance, propertyName) { + let functions = instance[functionsSymbol]; + if (functions === null) { + functions = Object.create(null); + instance[functionsSymbol] = functions; + } + let result = functions[propertyName]; + if (typeof result !== 'function') { + result = function() { + return instance[propertyName].apply(instance, arguments); + } + functions[propertyName] = result; + } + return result; + } }; for (let name of ["wrapByte", "wrapShort", "wrapChar", "wrapInt", "wrapFloat", "wrapDouble", "unwrapByte", "unwrapShort", "unwrapChar", "unwrapInt", "unwrapFloat", "unwrapDouble"]) { imports.teavmJso[name] = identity; } for (let i = 0; i < 32; ++i) { - imports.teavmJso["createFunction" + i] = function() { - return new Function(...arguments); - }; - imports.teavmJso["callFunction" + i] = function(fn, ...args) { - return fn(...args); - }; - imports.teavmJso["callMethod" + i] = function(instance, method, ...args) { - return instance[method](...args); - }; - imports.teavmJso["construct" + i] = function(constructor, ...args) { - return new constructor(...args); - }; + imports.teavmJso["createFunction" + i] = (...args) => new Function(...args); + imports.teavmJso["callFunction" + i] = (fn, ...args) => fn(...args); + imports.teavmJso["callMethod" + i] = (instance, method, ...args) => instance[method](...args); + imports.teavmJso["construct" + i] = (constructor, ...args) => new constructor(...args); } imports.teavmMath = Math; } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/AliasCollector.java b/jso/impl/src/main/java/org/teavm/jso/impl/AliasCollector.java new file mode 100644 index 000000000..b8cfb8c6a --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/AliasCollector.java @@ -0,0 +1,129 @@ +/* + * 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; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; +import org.teavm.model.ClassReader; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; + +public class AliasCollector { + private AliasCollector() { + } + + public static boolean isStaticMember(MethodReader method) { + return !isInstanceMember(method); + } + + public static boolean isInstanceMember(MethodReader method) { + return method.getAnnotations().get(JSInstanceExpose.class.getName()) != null; + } + + public static Members collectMembers(ClassReader classReader, Predicate filter) { + var methods = new HashMap(); + var properties = new HashMap(); + MethodReference constructor = null; + for (var method : classReader.getMethods()) { + if (!filter.test(method)) { + continue; + } + var methodAlias = getPublicAlias(method); + if (methodAlias != null) { + switch (methodAlias.kind) { + case METHOD: + methods.put(methodAlias.name, method.getReference()); + break; + case GETTER: { + var propInfo = properties.computeIfAbsent(methodAlias.name, k -> new PropertyInfo()); + propInfo.getter = method.getReference(); + break; + } + case SETTER: { + var propInfo = properties.computeIfAbsent(methodAlias.name, k -> new PropertyInfo()); + propInfo.setter = method.getReference(); + break; + } + case CONSTRUCTOR: + constructor = method.getReference(); + break; + } + } + } + return new Members(methods, properties, constructor); + } + + public static Alias getPublicAlias(MethodReader method) { + var annot = method.getAnnotations().get(JSMethodToExpose.class.getName()); + if (annot != null) { + return new Alias(annot.getValue("name").getString(), AliasKind.METHOD); + } + + annot = method.getAnnotations().get(JSGetterToExpose.class.getName()); + if (annot != null) { + return new Alias(annot.getValue("name").getString(), AliasKind.GETTER); + } + + annot = method.getAnnotations().get(JSSetterToExpose.class.getName()); + if (annot != null) { + return new Alias(annot.getValue("name").getString(), AliasKind.SETTER); + } + + annot = method.getAnnotations().get(JSConstructorToExpose.class.getName()); + if (annot != null) { + return new Alias(null, AliasKind.CONSTRUCTOR); + } + + return null; + } + + public static class Members { + public final Map methods; + public final Map properties; + public final MethodReference constructor; + + Members(Map methods, Map properties, + MethodReference constructor) { + this.methods = methods; + this.properties = properties; + this.constructor = constructor; + } + } + + + public static class PropertyInfo { + public MethodReference getter; + public MethodReference setter; + } + + public static class Alias { + public final String name; + public final AliasKind kind; + + Alias(String name, AliasKind kind) { + this.name = name; + this.kind = kind; + } + } + + public enum AliasKind { + METHOD, + GETTER, + SETTER, + CONSTRUCTOR + } +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JS.java b/jso/impl/src/main/java/org/teavm/jso/impl/JS.java index 222f37d6a..4b5e77fc2 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JS.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JS.java @@ -738,6 +738,7 @@ public final class JS { @GeneratedBy(JSNativeGenerator.class) @PluggableDependency(JSNativeInjector.class) + @Import(name = "asFunction", module = "teavmJso") public static native JSObject function(JSObject instance, JSObject property); @GeneratedBy(JSNativeGenerator.class) 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 00f9a6860..13f642c2c 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 @@ -15,9 +15,9 @@ */ package org.teavm.jso.impl; +import static org.teavm.jso.impl.AliasCollector.collectMembers; +import static org.teavm.jso.impl.AliasCollector.getPublicAlias; import java.util.HashMap; -import java.util.Map; -import java.util.function.Predicate; import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.rendering.RenderingManager; import org.teavm.backend.javascript.spi.MethodContributor; @@ -47,7 +47,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor { public void begin(RenderingManager context, BuildTarget buildTarget) { writer = context.getWriter(); classSource = context.getClassSource(); - typeHelper = new JSTypeHelper(context.getClassSource()); + typeHelper = new JSTypeHelper(context.getOriginalClassSource()); this.context = context; } @@ -91,7 +91,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor { } private boolean exportClassInstanceMembers(ClassReader classReader) { - var members = collectMembers(classReader, method -> !method.hasModifier(ElementModifier.STATIC)); + var members = collectMembers(classReader, AliasCollector::isInstanceMember); var isJsClassImpl = typeHelper.isJavaScriptImplementation(classReader.getName()); if (members.methods.isEmpty() && members.properties.isEmpty() && !isJsClassImpl) { @@ -106,23 +106,25 @@ class JSAliasRenderer implements RendererListener, MethodContributor { } for (var aliasEntry : members.methods.entrySet()) { - if (classReader.getMethod(aliasEntry.getValue()) == null) { + if (classReader.getMethod(aliasEntry.getValue().getDescriptor()) == null) { continue; } appendMethodAlias(aliasEntry.getKey()); - writer.ws().append("=").ws().append("c.").appendVirtualMethod(aliasEntry.getValue()) - .append(";").softNewLine(); + writer.ws().append("=").ws().appendFunction("$rt_callWithReceiver").append("(") + .appendMethod(aliasEntry.getValue()).append(");").softNewLine(); } for (var aliasEntry : members.properties.entrySet()) { var propInfo = aliasEntry.getValue(); - if (propInfo.getter == null || classReader.getMethod(propInfo.getter) == null) { + if (propInfo.getter == null || classReader.getMethod(propInfo.getter.getDescriptor()) == null) { continue; } appendPropertyAlias(aliasEntry.getKey()); - writer.append("get:").ws().append("c.").appendVirtualMethod(propInfo.getter); - if (propInfo.setter != null && classReader.getMethod(propInfo.setter) != null) { + writer.append("get:").ws().appendFunction("$rt_callWithReceiver").append("(") + .appendMethod(propInfo.getter).append(")"); + if (propInfo.setter != null && classReader.getMethod(propInfo.setter.getDescriptor()) != null) { writer.append(",").softNewLine(); - writer.append("set:").ws().append("c.").appendVirtualMethod(propInfo.setter); + writer.append("set:").ws().appendFunction("$rt_callWithReceiver").append("(") + .appendMethod(propInfo.setter).append(")"); } writer.softNewLine().outdent().append("});").softNewLine(); } @@ -136,7 +138,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor { } private boolean exportClassStaticMembers(ClassReader classReader, String name) { - var members = collectMembers(classReader, c -> c.hasModifier(ElementModifier.STATIC)); + var members = collectMembers(classReader, AliasCollector::isStaticMember); if (members.methods.isEmpty() && members.properties.isEmpty()) { return false; @@ -146,7 +148,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor { for (var aliasEntry : members.methods.entrySet()) { appendMethodAlias(aliasEntry.getKey()); - var fullRef = new MethodReference(classReader.getName(), aliasEntry.getValue()); + var fullRef = aliasEntry.getValue(); writer.ws().append("=").ws().appendMethod(fullRef).append(";").softNewLine(); } for (var aliasEntry : members.properties.entrySet()) { @@ -155,11 +157,11 @@ class JSAliasRenderer implements RendererListener, MethodContributor { continue; } appendPropertyAlias(aliasEntry.getKey()); - var fullGetter = new MethodReference(classReader.getName(), propInfo.getter); + var fullGetter = propInfo.getter; writer.append("get:").ws().appendMethod(fullGetter); if (propInfo.setter != null) { writer.append(",").softNewLine(); - var fullSetter = new MethodReference(classReader.getName(), propInfo.setter); + var fullSetter = propInfo.setter; writer.append("set:").ws().appendMethod(fullSetter); } writer.softNewLine().outdent().append("});").softNewLine(); @@ -182,39 +184,6 @@ class JSAliasRenderer implements RendererListener, MethodContributor { .ws().append("{").indent().softNewLine(); } - private Members collectMembers(ClassReader classReader, Predicate filter) { - var methods = new HashMap(); - var properties = new HashMap(); - MethodDescriptor constructor = null; - for (var method : classReader.getMethods()) { - if (!filter.test(method)) { - continue; - } - var methodAlias = getPublicAlias(method); - if (methodAlias != null) { - switch (methodAlias.kind) { - case METHOD: - methods.put(methodAlias.name, method.getDescriptor()); - break; - case GETTER: { - var propInfo = properties.computeIfAbsent(methodAlias.name, k -> new PropertyInfo()); - propInfo.getter = method.getDescriptor(); - break; - } - case SETTER: { - var propInfo = properties.computeIfAbsent(methodAlias.name, k -> new PropertyInfo()); - propInfo.setter = method.getDescriptor(); - break; - } - case CONSTRUCTOR: - constructor = method.getDescriptor(); - break; - } - } - } - return new Members(methods, properties, constructor); - } - private void exportModule() { var cls = classSource.get(context.getEntryPoint()); for (var method : cls.getMethods()) { @@ -222,7 +191,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor { continue; } var methodAlias = getPublicAlias(method); - if (methodAlias != null && methodAlias.kind == AliasKind.METHOD) { + if (methodAlias != null && methodAlias.kind == AliasCollector.AliasKind.METHOD) { context.exportMethod(method.getReference(), methodAlias.name); } } @@ -230,7 +199,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor { private void exportClassFromModule(ClassReader cls, String functionName) { var name = getClassAliasName(cls); - var constructors = collectMembers(cls, method -> !method.hasModifier(ElementModifier.STATIC)); + var constructors = collectMembers(cls, AliasCollector::isInstanceMember); var method = constructors.constructor; writer.append("function ").appendFunction(functionName).append("("); @@ -245,7 +214,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor { writer.append(")").ws().appendBlockStart(); if (method != null) { writer.appendClass(cls.getName()).append(".call(this);").softNewLine(); - writer.appendMethod(new MethodReference(cls.getName(), method)).append("(this"); + writer.appendMethod(method).append("(this"); for (var i = 0; i < method.parameterCount(); ++i) { writer.append(",").ws().append("p" + i); } @@ -293,31 +262,6 @@ class JSAliasRenderer implements RendererListener, MethodContributor { } return false; } - - private Alias getPublicAlias(MethodReader method) { - var annot = method.getAnnotations().get(JSMethodToExpose.class.getName()); - if (annot != null) { - return new Alias(annot.getValue("name").getString(), AliasKind.METHOD); - } - - annot = method.getAnnotations().get(JSGetterToExpose.class.getName()); - if (annot != null) { - return new Alias(annot.getValue("name").getString(), AliasKind.GETTER); - } - - annot = method.getAnnotations().get(JSSetterToExpose.class.getName()); - if (annot != null) { - return new Alias(annot.getValue("name").getString(), AliasKind.SETTER); - } - - annot = method.getAnnotations().get(JSConstructorToExpose.class.getName()); - if (annot != null) { - return new Alias(null, AliasKind.CONSTRUCTOR); - } - - return null; - } - private FieldReader getFunctorField(ClassReader cls) { return cls.getField("$$jso_functor$$"); } @@ -403,38 +347,5 @@ class JSAliasRenderer implements RendererListener, MethodContributor { return methodReader != null && getPublicAlias(methodReader) != null; } - private static class Members { - final Map methods; - final Map properties; - final MethodDescriptor constructor; - Members(Map methods, Map properties, - MethodDescriptor constructor) { - this.methods = methods; - this.properties = properties; - this.constructor = constructor; - } - } - - private static class PropertyInfo { - MethodDescriptor getter; - MethodDescriptor setter; - } - - private static class Alias { - final String name; - final AliasKind kind; - - Alias(String name, AliasKind kind) { - this.name = name; - this.kind = kind; - } - } - - private enum AliasKind { - METHOD, - GETTER, - SETTER, - CONSTRUCTOR - } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyDelegate.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyDelegate.java new file mode 100644 index 000000000..52f3d907c --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSBodyDelegate.java @@ -0,0 +1,19 @@ +/* + * 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.jso.impl; + +@interface JSBodyDelegate { +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java index 121b13c2f..380263efc 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassProcessor.java @@ -80,20 +80,6 @@ import org.teavm.model.util.ProgramUtils; class JSClassProcessor { private static final String NO_SIDE_EFFECTS = NoSideEffects.class.getName(); - private static final MethodReference WRAP = new MethodReference(JSWrapper.class, "wrap", Object.class, - Object.class); - private static final MethodReference MAYBE_WRAP = new MethodReference(JSWrapper.class, "maybeWrap", Object.class, - Object.class); - private static final MethodReference UNWRAP = new MethodReference(JSWrapper.class, "unwrap", Object.class, - JSObject.class); - private static final MethodReference MAYBE_UNWRAP = new MethodReference(JSWrapper.class, "maybeUnwrap", - Object.class, JSObject.class); - private static final MethodReference IS_JS = new MethodReference(JSWrapper.class, "isJs", - Object.class, boolean.class); - private static final MethodReference IS_PRIMITIVE = new MethodReference(JSWrapper.class, "isPrimitive", - Object.class, JSObject.class, boolean.class); - private static final MethodReference INSTANCE_OF = new MethodReference(JSWrapper.class, "instanceOf", - Object.class, JSObject.class, boolean.class); private final ClassReaderSource classSource; private final JSBodyRepository repository; private final JavaInvocationProcessor javaInvocationProcessor; @@ -111,6 +97,7 @@ class JSClassProcessor { private JSImportAnnotationCache annotationCache; private ClassReader objectClass; private Predicate classFilter = n -> true; + private boolean wasmGC; JSClassProcessor(ClassReaderSource classSource, JSTypeHelper typeHelper, JSBodyRepository repository, Diagnostics diagnostics, IncrementalDependencyRegistration incrementalCache, boolean strict) { @@ -125,7 +112,11 @@ class JSClassProcessor { annotationCache = new JSImportAnnotationCache(classSource, diagnostics); } - public void setClassFilter(Predicate classFilter) { + void setWasmGC(boolean wasmGC) { + this.wasmGC = wasmGC; + } + + void setClassFilter(Predicate classFilter) { this.classFilter = classFilter; } @@ -286,8 +277,11 @@ class JSClassProcessor { void processProgram(MethodHolder methodToProcess) { setCurrentProgram(methodToProcess.getProgram()); - types = new JSTypeInference(typeHelper, classSource, program, methodToProcess.getReference()); + types = new JSTypeInference(typeHelper, classSource, program, methodToProcess.getReference(), wasmGC); types.ensure(); + if (wasmGC) { + wrapJsPhis(methodToProcess.getReference()); + } for (int i = 0; i < program.basicBlockCount(); ++i) { var block = program.basicBlockAt(i); for (var insn : block) { @@ -355,6 +349,32 @@ class JSClassProcessor { } } + private void wrapJsPhis(MethodReference methodReference) { + var changed = false; + for (var block : program.getBasicBlocks()) { + for (var phi : block.getPhis()) { + if (types.typeOf(phi.getReceiver()) == JSType.MIXED) { + for (var incoming : phi.getIncomings()) { + if (types.typeOf(incoming.getValue()) == JSType.JS) { + changed = true; + var wrap = new InvokeInstruction(); + wrap.setType(InvocationType.SPECIAL); + wrap.setMethod(new MethodReference(JSWrapper.class, "wrap", JSObject.class, Object.class)); + wrap.setArguments(incoming.getValue()); + wrap.setReceiver(program.createVariable()); + incoming.getSource().getLastInstruction().insertPrevious(wrap); + incoming.setValue(wrap.getReceiver()); + } + } + } + } + } + if (changed) { + types = new JSTypeInference(typeHelper, classSource, program, methodReference, wasmGC); + types.ensure(); + } + } + private void processInvokeArgs(InvokeInstruction invoke, MethodReader methodToInvoke) { if (methodToInvoke != null && methodToInvoke.getAnnotations().get(JSBody.class.getName()) != null) { return; @@ -402,7 +422,7 @@ class JSClassProcessor { if (type == JSType.JS || type == JSType.MIXED) { var unwrap = new InvokeInstruction(); unwrap.setType(InvocationType.SPECIAL); - unwrap.setMethod(type == JSType.MIXED ? MAYBE_UNWRAP : UNWRAP); + unwrap.setMethod(type == JSType.MIXED ? JSMethods.MAYBE_UNWRAP : JSMethods.UNWRAP); unwrap.setArguments(program.createVariable()); unwrap.setReceiver(insn.getReceiver()); unwrap.setLocation(insn.getLocation()); @@ -420,7 +440,7 @@ class JSClassProcessor { if (type == JSType.JS || type == JSType.MIXED) { var wrap = new InvokeInstruction(); wrap.setType(InvocationType.SPECIAL); - wrap.setMethod(type == JSType.MIXED ? MAYBE_WRAP : WRAP); + wrap.setMethod(type == JSType.MIXED ? JSMethods.MAYBE_WRAP : JSMethods.WRAP); wrap.setArguments(insn.getValue()); wrap.setReceiver(program.createVariable()); wrap.setLocation(insn.getLocation()); @@ -563,7 +583,7 @@ class JSClassProcessor { if (isTransparent(targetClassName)) { var invoke = new InvokeInstruction(); invoke.setType(InvocationType.SPECIAL); - invoke.setMethod(IS_JS); + invoke.setMethod(JSMethods.IS_JS); invoke.setArguments(value); invoke.setReceiver(receiver); invoke.setLocation(location); @@ -572,7 +592,9 @@ class JSClassProcessor { var primitiveType = getPrimitiveType(targetClassName); var invoke = new InvokeInstruction(); invoke.setType(InvocationType.SPECIAL); - invoke.setMethod(primitiveType != null ? IS_PRIMITIVE : INSTANCE_OF); + invoke.setMethod(primitiveType != null + ? JSMethods.WRAPPER_IS_PRIMITIVE + : JSMethods.WRAPPER_INSTANCE_OF); var secondArg = primitiveType != null ? marshaller.addJsString(primitiveType, location) : marshaller.classRef(targetClassName, location); @@ -648,7 +670,7 @@ class JSClassProcessor { } var wrap = new InvokeInstruction(); wrap.setType(InvocationType.SPECIAL); - wrap.setMethod(varType == JSType.JS ? WRAP : MAYBE_WRAP); + wrap.setMethod(varType == JSType.JS ? JSMethods.WRAP : JSMethods.MAYBE_WRAP); wrap.setArguments(var); wrap.setReceiver(program.createVariable()); wrap.setLocation(instruction.getLocation()); @@ -663,7 +685,7 @@ class JSClassProcessor { } var unwrap = new InvokeInstruction(); unwrap.setType(InvocationType.SPECIAL); - unwrap.setMethod(varType == JSType.JAVA ? UNWRAP : MAYBE_UNWRAP); + unwrap.setMethod(varType == JSType.JAVA ? JSMethods.UNWRAP : JSMethods.MAYBE_UNWRAP); unwrap.setArguments(var); unwrap.setReceiver(program.createVariable()); unwrap.setLocation(instruction.getLocation()); @@ -1187,6 +1209,7 @@ class JSClassProcessor { if (method.getAnnotations().get(NoSideEffects.class.getName()) != null) { proxyMethod.getAnnotations().add(new AnnotationHolder(NoSideEffects.class.getName())); } + proxyMethod.getAnnotations().add(new AnnotationHolder(JSBodyDelegate.class.getName())); boolean inline = repository.inlineMethods.contains(methodRef); AnnotationHolder generatorAnnot = new AnnotationHolder(inline ? DynamicInjector.class.getName() : DynamicGenerator.class.getName()); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSClassToExpose.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassToExpose.java new file mode 100644 index 000000000..ff02cf946 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSClassToExpose.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; + +@interface JSClassToExpose { +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSDependencyListener.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSDependencyListener.java index 63651175c..4b8655a4c 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSDependencyListener.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSDependencyListener.java @@ -17,23 +17,29 @@ package org.teavm.jso.impl; import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.DependencyNode; import org.teavm.dependency.MethodDependency; import org.teavm.jso.JSExportClasses; import org.teavm.model.AnnotationReader; import org.teavm.model.CallLocation; import org.teavm.model.ClassReader; -import org.teavm.model.ElementModifier; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; class JSDependencyListener extends AbstractDependencyListener { private JSBodyRepository repository; + private DependencyNode exceptions; JSDependencyListener(JSBodyRepository repository) { this.repository = repository; } + @Override + public void started(DependencyAgent agent) { + exceptions = agent.createNode(); + } + @Override public void methodReached(DependencyAgent agent, MethodDependency method) { MethodReference ref = method.getReference(); @@ -43,6 +49,22 @@ class JSDependencyListener extends AbstractDependencyListener { agent.linkMethod(callbackMethod).addLocation(new CallLocation(ref)).use(); } } + if (method.getMethod().getAnnotations().get(JSBodyDelegate.class.getName()) != null) { + exceptions.connect(method.getThrown()); + } + if (method.getMethod().getOwnerName().equals(JS.class.getName())) { + switch (method.getMethod().getName()) { + case "invoke": + case "construct": + case "apply": + case "get": + case "getPure": + case "set": + case "setPure": + exceptions.connect(method.getThrown()); + break; + } + } } @Override @@ -62,10 +84,8 @@ class JSDependencyListener extends AbstractDependencyListener { if (exposeAnnot != null) { MethodDependency methodDep = agent.linkMethod(method.getReference()); if (methodDep.getMethod() != null) { - if (!methodDep.getMethod().hasModifier(ElementModifier.STATIC)) { - methodDep.getVariable(0).propagate(agent.getType(className)); - } methodDep.use(); + methodDep.getThrown().connect(exceptions); } } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSInstanceExpose.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSInstanceExpose.java new file mode 100644 index 000000000..9293468b8 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSInstanceExpose.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; + +@interface JSInstanceExpose { +} diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSMarshallable.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSMarshallable.java new file mode 100644 index 000000000..88befc828 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSMarshallable.java @@ -0,0 +1,22 @@ +/* + * 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; + +import org.teavm.jso.JSObject; + +public interface JSMarshallable { + JSObject marshallToJs(); +} 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 1f0ade807..88da51cd1 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 @@ -19,10 +19,11 @@ import java.util.Arrays; import org.teavm.jso.JSObject; import org.teavm.jso.core.JSArray; import org.teavm.jso.core.JSArrayReader; +import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; -final class JSMethods { +public final class JSMethods { public static final MethodReference GET = new MethodReference(JS.class, "get", JSObject.class, JSObject.class, JSObject.class); public static final MethodReference GET_PURE = new MethodReference(JS.class, "getPure", JSObject.class, @@ -143,6 +144,25 @@ final class JSMethods { private static final MethodReference[] CONSTRUCT_METHODS = new MethodReference[13]; private static final MethodReference[] ARRAY_OF_METHODS = new MethodReference[13]; + public static final MethodReference WRAP = new MethodReference(JSWrapper.class, "wrap", JSObject.class, + Object.class); + public static final MethodReference MAYBE_WRAP = new MethodReference(JSWrapper.class, "maybeWrap", Object.class, + Object.class); + public static final MethodReference UNWRAP = new MethodReference(JSWrapper.class, "unwrap", Object.class, + JSObject.class); + public static final MethodReference MAYBE_UNWRAP = new MethodReference(JSWrapper.class, "maybeUnwrap", + Object.class, JSObject.class); + public static final MethodReference IS_JS = new MethodReference(JSWrapper.class, "isJs", + Object.class, boolean.class); + public static final MethodReference WRAPPER_IS_PRIMITIVE = new MethodReference(JSWrapper.class, "isPrimitive", + Object.class, JSObject.class, boolean.class); + public static final MethodReference WRAPPER_INSTANCE_OF = new MethodReference(JSWrapper.class, "instanceOf", + Object.class, JSObject.class, boolean.class); + + 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) { var signature = new ValueType[i + 3]; diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java index ec20ac87f..1c1a2ce69 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSOPlugin.java @@ -46,12 +46,7 @@ public class JSOPlugin implements TeaVMPlugin { host.add(new JSExceptionsDependencyListener()); var wrapperDependency = new JSWrapperDependency(); - host.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class), - wrapperDependency); - host.add(new MethodReference(JSWrapper.class, "dependencyJavaToJs", Object.class, JSObject.class), - wrapperDependency); - host.add(new MethodReference(JSWrapper.class, "dependencyJsToJava", JSObject.class, Object.class), - wrapperDependency); + host.add(wrapperDependency); TeaVMPluginUtil.handleNatives(host, JS.class); @@ -61,6 +56,7 @@ public class JSOPlugin implements TeaVMPlugin { if (wasmGCHost != null) { classTransformer.setClassFilter(n -> !n.startsWith("java.")); + classTransformer.forWasmGC(); WasmGCJso.install(host, wasmGCHost, repository); } } @@ -90,6 +86,10 @@ public class JSOPlugin implements TeaVMPlugin { wrapperGenerator); jsHost.add(new MethodReference(JSWrapper.class, "dependencyJsToJava", JSObject.class, Object.class), wrapperGenerator); + jsHost.add(new MethodReference(JSWrapper.class, "marshallJavaToJs", Object.class, JSObject.class), + wrapperGenerator); + jsHost.add(new MethodReference(JSWrapper.class, "unmarshallJavaFromJs", JSObject.class, Object.class), + wrapperGenerator); jsHost.add(new MethodReference(JSWrapper.class, "isJava", Object.class, boolean.class), wrapperGenerator); jsHost.add(new MethodReference(JSWrapper.class, "isJava", JSObject.class, boolean.class), 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 cc77ca901..f2d194332 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 @@ -49,6 +49,7 @@ import org.teavm.model.MethodReference; 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.ExitInstruction; import org.teavm.model.instructions.IntegerConstantInstruction; import org.teavm.model.instructions.InvocationType; @@ -61,6 +62,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer { private ClassHierarchy hierarchy; private Map exposedClasses = new HashMap<>(); private Predicate classFilter = n -> true; + private boolean wasmGC; JSObjectClassTransformer(JSBodyRepository repository) { this.repository = repository; @@ -70,6 +72,10 @@ class JSObjectClassTransformer implements ClassHolderTransformer { this.classFilter = classFilter; } + void forWasmGC() { + wasmGC = true; + } + @Override public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { this.hierarchy = context.getHierarchy(); @@ -77,6 +83,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer { typeHelper = new JSTypeHelper(hierarchy.getClassSource()); processor = new JSClassProcessor(hierarchy.getClassSource(), typeHelper, repository, context.getDiagnostics(), context.getIncrementalCache(), context.isStrict()); + processor.setWasmGC(wasmGC); processor.setClassFilter(classFilter); } processor.processClass(cls); @@ -114,6 +121,21 @@ class JSObjectClassTransformer implements ClassHolderTransformer { exposeMethods(cls, exposedClass, context.getDiagnostics(), functorMethod); exportStaticMethods(cls, context.getDiagnostics()); + + if (typeHelper.isJavaScriptImplementation(cls.getName()) || !exposedClass.methods.isEmpty()) { + cls.getAnnotations().add(new AnnotationHolder(JSClassToExpose.class.getName())); + } + + if (wasmGC && (!exposedClass.methods.isEmpty() || typeHelper.isJavaScriptClass(cls.getName()))) { + var createWrapperMethod = new MethodHolder(JSMethods.MARSHALL_TO_JS); + createWrapperMethod.setLevel(AccessLevel.PUBLIC); + createWrapperMethod.getModifiers().add(ElementModifier.NATIVE); + cls.addMethod(createWrapperMethod); + + if (typeHelper.isJavaScriptImplementation(cls.getName())) { + cls.getInterfaces().add(JSMethods.JS_MARSHALLABLE); + } + } } private void exposeMethods(ClassHolder classHolder, ExposedClass classToExpose, Diagnostics diagnostics, @@ -129,14 +151,20 @@ class JSObjectClassTransformer implements ClassHolderTransformer { if (export.vararg) { --paramCount; } - var exportedMethodSignature = new ValueType[paramCount + 1]; + var exportedMethodSignature = new ValueType[paramCount + 2]; Arrays.fill(exportedMethodSignature, JSMethods.JS_OBJECT); + if (methodRef.getReturnType() == ValueType.VOID) { + exportedMethodSignature[exportedMethodSignature.length - 1] = ValueType.VOID; + } MethodDescriptor exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++, exportedMethodSignature); MethodHolder exportedMethod = new MethodHolder(exportedMethodDesc); + exportedMethod.getModifiers().add(ElementModifier.STATIC); + exportedMethod.getAnnotations().add(new AnnotationHolder(JSInstanceExpose.class.getName())); Program program = new Program(); exportedMethod.setProgram(program); program.createVariable(); + program.createVariable(); BasicBlock basicBlock = program.createBasicBlock(); List marshallInstructions = new ArrayList<>(); @@ -161,9 +189,24 @@ 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); + + var castInstance = new CastInstruction(); + castInstance.setValue(unmarshalledInstance.getReceiver()); + castInstance.setReceiver(program.createVariable()); + castInstance.setWeak(true); + castInstance.setTargetType(ValueType.object(classHolder.getName())); + basicBlock.add(castInstance); + InvokeInstruction invocation = new InvokeInstruction(); - invocation.setType(InvocationType.VIRTUAL); - invocation.setInstance(program.variableAt(0)); + invocation.setType(method.getName().equals("") ? InvocationType.SPECIAL : InvocationType.VIRTUAL); + invocation.setInstance(castInstance.getReceiver()); invocation.setMethod(methodRef); invocation.setArguments(variablesToPass); basicBlock.add(invocation); 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 8aaf41d15..9e9444487 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 @@ -24,12 +24,12 @@ import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; import org.teavm.model.ValueType; -class JSTypeHelper { +public class JSTypeHelper { private ClassReaderSource classSource; private Map knownJavaScriptClasses = new HashMap<>(); private Map knownJavaScriptImplementations = new HashMap<>(); - JSTypeHelper(ClassReaderSource classSource) { + public JSTypeHelper(ClassReaderSource classSource) { this.classSource = classSource; knownJavaScriptClasses.put(JSObject.class.getName(), true); } 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 81a8412b2..d2858fd4a 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 @@ -26,11 +26,14 @@ import org.teavm.model.instructions.InvocationType; class JSTypeInference extends BaseTypeInference { private JSTypeHelper typeHelper; private ClassReaderSource classes; + private boolean wasmGC; - JSTypeInference(JSTypeHelper typeHelper, ClassReaderSource classes, Program program, MethodReference reference) { + JSTypeInference(JSTypeHelper typeHelper, ClassReaderSource classes, Program program, MethodReference reference, + boolean wasmGC) { super(program, reference); this.typeHelper = typeHelper; this.classes = classes; + this.wasmGC = wasmGC; } @Override @@ -93,7 +96,7 @@ class JSTypeInference extends BaseTypeInference { if (!methodRef.getReturnType().isObject(Object.class)) { return mapType(methodRef.getReturnType()); } - return isJsMethod(methodRef) ? JSType.MIXED : JSType.JAVA; + return !wasmGC && isJsMethod(methodRef) ? JSType.MIXED : JSType.JAVA; } private boolean isJsMethod(MethodReference methodRef) { diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java index 7fac833b0..f32e000b8 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSValueMarshaller.java @@ -87,6 +87,16 @@ class JSValueMarshaller { diagnostics.error(location, "Wrong functor: {{c0}}", type.getName()); return var; } + + var unwrapNative = new InvokeInstruction(); + unwrapNative.setLocation(location.getSourceLocation()); + unwrapNative.setType(InvocationType.SPECIAL); + unwrapNative.setMethod(JSMethods.UNWRAP); + unwrapNative.setArguments(var); + unwrapNative.setReceiver(program.createVariable()); + replacement.add(unwrapNative); + var = unwrapNative.getReceiver(); + String name = type.getMethods().stream() .filter(method -> method.hasModifier(ElementModifier.ABSTRACT)) .findFirst().get().getName(); @@ -142,6 +152,16 @@ class JSValueMarshaller { replacement.add(unwrapNative); return unwrapNative.getReceiver(); } + if (typeHelper.isJavaScriptClass(className) && jsType == JSType.JAVA) { + var unwrapNative = new InvokeInstruction(); + unwrapNative.setLocation(location); + unwrapNative.setType(InvocationType.SPECIAL); + unwrapNative.setMethod(JSMethods.UNWRAP); + unwrapNative.setArguments(var); + unwrapNative.setReceiver(program.createVariable()); + replacement.add(unwrapNative); + return unwrapNative.getReceiver(); + } return var; } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapper.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapper.java index 46122b4ae..e8a7545e2 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapper.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapper.java @@ -15,6 +15,7 @@ */ package org.teavm.jso.impl; +import org.teavm.interop.Import; import org.teavm.interop.NoSideEffects; import org.teavm.jso.JSBody; import org.teavm.jso.JSClass; @@ -30,60 +31,61 @@ import org.teavm.jso.core.JSWeakMap; import org.teavm.jso.core.JSWeakRef; public final class JSWrapper { - private static final JSWeakMap hashCodes = new JSWeakMap<>(); - private static final JSWeakMap> wrappers = JSWeakRef.isSupported() - ? new JSWeakMap<>() : null; - private static final JSMap> stringWrappers = JSWeakRef.isSupported() - ? new JSMap<>() : null; - private static final JSMap> numberWrappers = JSWeakRef.isSupported() - ? new JSMap<>() : null; - private static JSWeakRef undefinedWrapper; - private static final JSFinalizationRegistry stringFinalizationRegistry; - private static final JSFinalizationRegistry numberFinalizationRegistry; - private static int hashCodeGen; + private static class Helper { + private static final JSWeakMap hashCodes = new JSWeakMap<>(); + private static final JSWeakMap> wrappers = JSWeakRef.isSupported() + ? new JSWeakMap<>() : null; + private static final JSMap> stringWrappers = JSWeakRef.isSupported() + ? new JSMap<>() : null; + private static final JSMap> numberWrappers = JSWeakRef.isSupported() + ? new JSMap<>() : null; + private static JSWeakRef undefinedWrapper; + private static JSFinalizationRegistry stringFinalizationRegistry; + private static JSFinalizationRegistry numberFinalizationRegistry; + private static int hashCodeGen; + + static { + stringFinalizationRegistry = stringWrappers != null + ? new JSFinalizationRegistry(token -> stringWrappers.delete((JSString) token)) + : null; + numberFinalizationRegistry = numberWrappers != null + ? new JSFinalizationRegistry(token -> numberWrappers.delete((JSNumber) token)) + : null; + } + } public final JSObject js; - static { - stringFinalizationRegistry = stringWrappers != null - ? new JSFinalizationRegistry(token -> stringWrappers.delete((JSString) token)) - : null; - numberFinalizationRegistry = numberWrappers != null - ? new JSFinalizationRegistry(token -> numberWrappers.delete((JSNumber) token)) - : null; - } - private JSWrapper(JSObject js) { this.js = js; } - public static Object wrap(Object o) { + public static Object wrap(JSObject o) { if (o == null) { return null; } - var js = directJavaToJs(o); - var type = JSObjects.typeOf(js); + var type = JSObjects.typeOf(o); var isObject = type.equals("object") || type.equals("function"); - if (isObject && isJSImplementation(o)) { - return o; - } + var wrappers = Helper.wrappers; if (wrappers != null) { if (isObject) { - var existingRef = get(wrappers, js); + var existingRef = get(wrappers, o); var existing = !isUndefined(existingRef) ? deref(existingRef) : JSUndefined.instance(); if (isUndefined(existing)) { - var wrapper = new JSWrapper(js); - set(wrappers, js, createWeakRef(wrapperToJs(wrapper))); + var wrapper = new JSWrapper(o); + set(wrappers, o, createWeakRef(wrapperToJs(wrapper))); return wrapper; } else { return jsToWrapper(existing); } } else if (type.equals("string")) { - var jsString = (JSString) js; + var jsString = (JSString) o; + var stringWrappers = Helper.stringWrappers; + var stringFinalizationRegistry = Helper.stringFinalizationRegistry; var existingRef = get(stringWrappers, jsString); var existing = !isUndefined(existingRef) ? deref(existingRef) : JSUndefined.instance(); if (isUndefined(existing)) { - var wrapper = new JSWrapper(js); + var wrapper = new JSWrapper(o); var wrapperAsJs = wrapperToJs(wrapper); set(stringWrappers, jsString, createWeakRef(wrapperAsJs)); register(stringFinalizationRegistry, wrapperAsJs, jsString); @@ -92,11 +94,13 @@ public final class JSWrapper { return jsToWrapper(existing); } } else if (type.equals("number")) { - var jsNumber = (JSNumber) js; + var jsNumber = (JSNumber) o; + var numberWrappers = Helper.numberWrappers; + var numberFinalizationRegistry = Helper.numberFinalizationRegistry; var existingRef = get(numberWrappers, jsNumber); var existing = !isUndefined(existingRef) ? deref(existingRef) : JSUndefined.instance(); if (isUndefined(existing)) { - var wrapper = new JSWrapper(js); + var wrapper = new JSWrapper(o); var wrapperAsJs = wrapperToJs(wrapper); set(numberWrappers, jsNumber, createWeakRef(wrapperAsJs)); register(numberFinalizationRegistry, wrapperAsJs, jsNumber); @@ -105,19 +109,19 @@ public final class JSWrapper { return jsToWrapper(existing); } } else if (type.equals("undefined")) { - var existingRef = undefinedWrapper; + var existingRef = Helper.undefinedWrapper; var existing = existingRef != null ? deref(existingRef) : JSUndefined.instance(); if (isUndefined(existing)) { - var wrapper = new JSWrapper(js); + var wrapper = new JSWrapper(o); var wrapperAsJs = wrapperToJs(wrapper); - undefinedWrapper = createWeakRef(wrapperAsJs); + Helper.undefinedWrapper = createWeakRef(wrapperAsJs); return wrapper; } else { return jsToWrapper(existing); } } } - return new JSWrapper(js); + return new JSWrapper(o); } @JSBody(params = "target", script = "return new WeakRef(target);") @@ -152,14 +156,18 @@ public final class JSWrapper { @NoSideEffects public static Object maybeWrap(Object o) { - return o == null || isJava(o) ? o : wrap(o); + return o == null || isJava(o) ? o : wrap(directJavaToJs(o)); } @NoSideEffects public static native JSObject directJavaToJs(Object obj); @NoSideEffects - public static native Object directJsToJava(JSObject obj); + public static native JSObject marshallJavaToJs(Object obj); + + @NoSideEffects + @Import(name = "unwrapJavaObject", module = "teavmJso") + public static native Object unmarshallJavaFromJs(JSObject obj); @NoSideEffects public static native JSObject dependencyJavaToJs(Object obj); @@ -186,14 +194,14 @@ public final class JSWrapper { if (o == null) { return null; } - return isJSImplementation(o) ? directJavaToJs(o) : ((JSWrapper) o).js; + return isJSImplementation(o) ? marshallJavaToJs(o) : ((JSWrapper) o).js; } public static JSObject maybeUnwrap(Object o) { if (o == null) { return null; } - return isJava(o) ? unwrap(o) : directJavaToJs(o); + return isJava(o) ? unwrap(o) : marshallJavaToJs(o); } public static JSObject javaToJs(Object o) { @@ -207,7 +215,7 @@ public final class JSWrapper { if (o == null) { return null; } - return !isJava(o) ? wrap(directJsToJava(o)) : dependencyJsToJava(o); + return !isJava(o) ? wrap(o) : dependencyJsToJava(o); } public static boolean isJs(Object o) { @@ -229,10 +237,10 @@ public final class JSWrapper { public int hashCode() { var type = JSObjects.typeOf(js); if (type.equals("object") || type.equals("symbol") || type.equals("function")) { - var code = hashCodes.get(js); + var code = Helper.hashCodes.get(js); if (isUndefined(code)) { - code = JSTransparentInt.valueOf(++hashCodeGen); - hashCodes.set(js, code); + code = JSTransparentInt.valueOf(++Helper.hashCodeGen); + Helper.hashCodes.set(js, code); } return code.intValue(); } else if (type.equals("number")) { diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperDependency.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperDependency.java index cbe880fde..7c697633a 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperDependency.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperDependency.java @@ -15,33 +15,43 @@ */ package org.teavm.jso.impl; +import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyNode; -import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; -public class JSWrapperDependency implements DependencyPlugin { +public class JSWrapperDependency extends AbstractDependencyListener { private DependencyNode externalClassesNode; @Override - public void methodReached(DependencyAgent agent, MethodDependency method) { - switch (method.getMethod().getName()) { - case "jsToWrapper": - method.getResult().propagate(agent.getType(JSWrapper.class.getName())); - break; - case "dependencyJavaToJs": - method.getVariable(1).connect(getExternalClassesNode(agent)); - break; - case "dependencyJsToJava": - getExternalClassesNode(agent).connect(method.getResult()); - break; + public void started(DependencyAgent agent) { + externalClassesNode = agent.createNode(); + } + + @Override + public void classReached(DependencyAgent agent, String className) { + var cls = agent.getClassSource().get(className); + if (cls.getAnnotations().get(JSClassToExpose.class.getName()) != null) { + externalClassesNode.propagate(agent.getType(className)); } } - private DependencyNode getExternalClassesNode(DependencyAgent agent) { - if (externalClassesNode == null) { - externalClassesNode = agent.createNode(); + @Override + public void methodReached(DependencyAgent agent, MethodDependency method) { + if (method.getMethod().getOwnerName().equals(JSWrapper.class.getName())) { + switch (method.getMethod().getName()) { + case "jsToWrapper": + method.getResult().propagate(agent.getType(JSWrapper.class.getName())); + break; + case "dependencyJavaToJs": + case "marshallJavaToJs": + method.getVariable(1).connect(externalClassesNode); + break; + case "dependencyJsToJava": + case "unmarshallJavaFromJs": + externalClassesNode.connect(method.getResult()); + break; + } } - return externalClassesNode; } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperGenerator.java b/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperGenerator.java index a2bbb328e..283971f3c 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperGenerator.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/JSWrapperGenerator.java @@ -34,6 +34,8 @@ public class JSWrapperGenerator implements Injector, DependencyPlugin { case "directJsToJava": case "dependencyJavaToJs": case "dependencyJsToJava": + case "marshallJavaToJs": + case "unmarshallJavaFromJs": case "wrapperToJs": case "jsToWrapper": context.writeExpr(context.getArgument(0), context.getPrecedence()); diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCBodyIntrinsic.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCBodyIntrinsic.java index ee4d0ad76..817ecb550 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCBodyIntrinsic.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCBodyIntrinsic.java @@ -20,35 +20,42 @@ import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsic; import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicContext; import org.teavm.backend.wasm.model.WasmGlobal; import org.teavm.backend.wasm.model.expression.WasmCall; +import org.teavm.backend.wasm.model.expression.WasmDrop; import org.teavm.backend.wasm.model.expression.WasmExpression; import org.teavm.backend.wasm.model.expression.WasmGetGlobal; import org.teavm.jso.impl.JSBodyEmitter; +import org.teavm.model.ValueType; class WasmGCBodyIntrinsic implements WasmGCIntrinsic { private JSBodyEmitter emitter; private boolean inlined; - private WasmGCBodyGenerator bodyGenerator; + private WasmGCJsoCommonGenerator commonGen; private WasmGlobal global; private WasmGCJSFunctions jsFunctions; - WasmGCBodyIntrinsic(JSBodyEmitter emitter, boolean inlined, WasmGCBodyGenerator bodyGenerator, + WasmGCBodyIntrinsic(JSBodyEmitter emitter, boolean inlined, WasmGCJsoCommonGenerator commonGen, WasmGCJSFunctions jsFunctions) { this.emitter = emitter; this.inlined = inlined; - this.bodyGenerator = bodyGenerator; + this.commonGen = commonGen; this.jsFunctions = jsFunctions; } @Override public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) { + var jsoContext = WasmGCJsoContext.wrap(context); if (global == null) { - global = bodyGenerator.addBody(context, emitter, inlined); + global = commonGen.addJSBody(jsoContext, emitter, inlined); } - var call = new WasmCall(jsFunctions.getFunctionCaller(context, invocation.getArguments().size())); + var call = new WasmCall(jsFunctions.getFunctionCaller(jsoContext, invocation.getArguments().size())); call.getArguments().add(new WasmGetGlobal(global)); for (var arg : invocation.getArguments()) { call.getArguments().add(context.generate(arg)); } - return call; + WasmExpression result = call; + if (invocation.getMethod().getReturnType() == ValueType.VOID) { + result = new WasmDrop(result); + } + return result; } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSBodyRenderer.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSBodyRenderer.java index c22e341a9..04ac0400c 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSBodyRenderer.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSBodyRenderer.java @@ -24,12 +24,13 @@ import org.teavm.model.MethodReference; class WasmGCJSBodyRenderer implements WasmGCIntrinsicFactory { private JSBodyRepository repository; private WasmGCJSFunctions jsFunctions; - private WasmGCBodyGenerator bodyGenerator; + private WasmGCJsoCommonGenerator commonGen; - WasmGCJSBodyRenderer(JSBodyRepository repository) { + WasmGCJSBodyRenderer(JSBodyRepository repository, WasmGCJSFunctions jsFunctions, + WasmGCJsoCommonGenerator commonGen) { this.repository = repository; - jsFunctions = new WasmGCJSFunctions(); - bodyGenerator = new WasmGCBodyGenerator(jsFunctions); + this.jsFunctions = jsFunctions; + this.commonGen = commonGen; } @Override @@ -39,6 +40,6 @@ class WasmGCJSBodyRenderer implements WasmGCIntrinsicFactory { return null; } var inlined = repository.inlineMethods.contains(emitter.method()); - return new WasmGCBodyIntrinsic(emitter, inlined, bodyGenerator, jsFunctions); + return new WasmGCBodyIntrinsic(emitter, inlined, commonGen, jsFunctions); } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSFunctions.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSFunctions.java index aea83e10f..e4a18b0f1 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSFunctions.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSFunctions.java @@ -16,7 +16,6 @@ package org.teavm.jso.impl.wasmgc; import java.util.Arrays; -import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicContext; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmType; @@ -24,7 +23,7 @@ class WasmGCJSFunctions { private WasmFunction[] constructors = new WasmFunction[32]; private WasmFunction[] callers = new WasmFunction[32]; - WasmFunction getFunctionConstructor(WasmGCIntrinsicContext context, int index) { + WasmFunction getFunctionConstructor(WasmGCJsoContext context, int index) { var function = constructors[index]; if (function == null) { var extern = WasmType.SpecialReferenceKind.EXTERN.asNonNullType(); @@ -41,7 +40,7 @@ class WasmGCJSFunctions { return function; } - WasmFunction getFunctionCaller(WasmGCIntrinsicContext context, int index) { + WasmFunction getFunctionCaller(WasmGCJsoContext context, int index) { var function = callers[index]; if (function == null) { var paramTypes = new WasmType[index + 1]; diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSTypeMapper.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSTypeMapper.java index a676d6810..ba2acada3 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSTypeMapper.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSTypeMapper.java @@ -19,14 +19,14 @@ import org.teavm.backend.wasm.generate.gc.classes.WasmGCCustomTypeMapper; import org.teavm.backend.wasm.generate.gc.classes.WasmGCCustomTypeMapperFactory; import org.teavm.backend.wasm.generate.gc.classes.WasmGCCustomTypeMapperFactoryContext; import org.teavm.backend.wasm.model.WasmType; -import org.teavm.jso.JSObject; -import org.teavm.jso.core.JSArray; +import org.teavm.jso.impl.JSTypeHelper; class WasmGCJSTypeMapper implements WasmGCCustomTypeMapper, WasmGCCustomTypeMapperFactory { + private JSTypeHelper typeHelper; + @Override public WasmType map(String className) { - if (className.equals(JSObject.class.getName()) - || className.equals(JSArray.class.getName())) { + if (typeHelper.isJavaScriptClass(className)) { return WasmType.Reference.EXTERN; } return null; @@ -34,6 +34,7 @@ class WasmGCJSTypeMapper implements WasmGCCustomTypeMapper, WasmGCCustomTypeMapp @Override public WasmGCCustomTypeMapper createTypeMapper(WasmGCCustomTypeMapperFactoryContext context) { + this.typeHelper = new JSTypeHelper(context.originalClasses()); return this; } } diff --git a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSWrapperTransformer.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSWrapperTransformer.java new file mode 100644 index 000000000..151b4f9b2 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJSWrapperTransformer.java @@ -0,0 +1,54 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.jso.impl.wasmgc; + +import org.teavm.jso.JSObject; +import org.teavm.jso.impl.JSMarshallable; +import org.teavm.jso.impl.JSWrapper; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassHolderTransformerContext; +import org.teavm.model.ElementModifier; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodHolder; +import org.teavm.model.ValueType; +import org.teavm.model.emit.ProgramEmitter; + +class WasmGCJSWrapperTransformer implements ClassHolderTransformer { + @Override + public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { + if (cls.getName().equals(JSWrapper.class.getName())) { + transformMarshallMethod(cls.getMethod(new MethodDescriptor("marshallJavaToJs", Object.class, + JSObject.class)), context); + transformIsJsImplementation(cls.getMethod(new MethodDescriptor("isJSImplementation", + Object.class, boolean.class)), context); + } + } + + private void transformMarshallMethod(MethodHolder method, ClassHolderTransformerContext context) { + method.getModifiers().remove(ElementModifier.NATIVE); + var pe = ProgramEmitter.create(method, context.getHierarchy()); + var obj = pe.var(1, Object.class); + obj.cast(JSMarshallable.class).invokeVirtual("marshallToJs", JSObject.class).returnValue(); + } + + private void transformIsJsImplementation(MethodHolder method, ClassHolderTransformerContext context) { + method.getModifiers().remove(ElementModifier.NATIVE); + var pe = ProgramEmitter.create(method, context.getHierarchy()); + var obj = pe.var(1, JSObject.class); + obj.instanceOf(ValueType.parse(JSMarshallable.class)).returnValue(); + } +} 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 fa437cc8e..0a38aedae 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 @@ -28,8 +28,12 @@ public final class WasmGCJso { public static void install(TeaVMHost host, TeaVMWasmGCHost wasmGCHost, JSBodyRepository jsBodyRepository) { host.add(new WasmGCJSDependencies()); + host.add(new WasmGCJSWrapperTransformer()); + var jsFunctions = new WasmGCJSFunctions(); + var commonGen = new WasmGCJsoCommonGenerator(jsFunctions); wasmGCHost.addCustomTypeMapperFactory(new WasmGCJSTypeMapper()); - wasmGCHost.addIntrinsicFactory(new WasmGCJSBodyRenderer(jsBodyRepository)); + wasmGCHost.addIntrinsicFactory(new WasmGCJSBodyRenderer(jsBodyRepository, jsFunctions, commonGen)); + wasmGCHost.addGeneratorFactory(new WasmGCMarshallMethodGeneratorFactory(commonGen)); var jsIntrinsic = new WasmGCJSIntrinsic(); 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/WasmGCBodyGenerator.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoCommonGenerator.java similarity index 81% rename from jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCBodyGenerator.java rename to jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoCommonGenerator.java index a3c3af3d3..a453081a8 100644 --- a/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCBodyGenerator.java +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoCommonGenerator.java @@ -20,11 +20,11 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import org.teavm.backend.javascript.rendering.AstWriter; -import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicContext; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmGlobal; 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.WasmNullConstant; import org.teavm.backend.wasm.model.expression.WasmSetGlobal; @@ -32,16 +32,16 @@ import org.teavm.jso.impl.JSBodyAstEmitter; import org.teavm.jso.impl.JSBodyBloatedEmitter; import org.teavm.jso.impl.JSBodyEmitter; -class WasmGCBodyGenerator { +class WasmGCJsoCommonGenerator { private WasmGCJSFunctions jsFunctions; private boolean initialized; private List> initializerParts = new ArrayList<>(); - WasmGCBodyGenerator(WasmGCJSFunctions jsFunctions) { + WasmGCJsoCommonGenerator(WasmGCJSFunctions jsFunctions) { this.jsFunctions = jsFunctions; } - private void initialize(WasmGCIntrinsicContext context) { + private void initialize(WasmGCJsoContext context) { if (initialized) { return; } @@ -49,14 +49,17 @@ class WasmGCBodyGenerator { context.addToInitializer(this::writeToInitializer); } - private void writeToInitializer(WasmFunction function) { for (var part : initializerParts) { part.accept(function); } } - WasmGlobal addBody(WasmGCIntrinsicContext context, JSBodyEmitter emitter, boolean inlined) { + void addInitializerPart(Consumer part) { + initializerParts.add(part); + } + + WasmGlobal addJSBody(WasmGCJsoContext context, JSBodyEmitter emitter, boolean inlined) { initialize(context); var paramCount = emitter.method().parameterCount(); if (!emitter.isStatic()) { @@ -87,9 +90,7 @@ class WasmGCBodyGenerator { throw new IllegalArgumentException(); } - var constructor = new WasmCall(jsFunctions.getFunctionConstructor(context, - paramCount)); - var stringToJs = context.functions().forStaticMethod(STRING_TO_JS); + var constructor = new WasmCall(jsFunctions.getFunctionConstructor(context, paramCount)); var paramNames = new ArrayList(); if (!emitter.isStatic()) { paramNames.add("__this__"); @@ -97,12 +98,20 @@ class WasmGCBodyGenerator { paramNames.addAll(List.of(emitter.parameterNames())); for (var parameter : paramNames) { var paramName = new WasmGetGlobal(context.strings().getStringConstant(parameter).global); - constructor.getArguments().add(new WasmCall(stringToJs, paramName)); + constructor.getArguments().add(stringToJs(context, paramName)); } var functionBody = new WasmGetGlobal(context.strings().getStringConstant(body).global); - constructor.getArguments().add(new WasmCall(stringToJs, functionBody)); + constructor.getArguments().add(stringToJs(context, functionBody)); initializerParts.add(initializer -> initializer.getBody().add(new WasmSetGlobal(global, constructor))); return global; } + + private WasmFunction stringToJsFunction(WasmGCJsoContext context) { + return context.functions().forStaticMethod(STRING_TO_JS); + } + + WasmExpression stringToJs(WasmGCJsoContext context, WasmExpression str) { + return new WasmCall(stringToJsFunction(context), str); + } } 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 new file mode 100644 index 000000000..22dff5a72 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCJsoContext.java @@ -0,0 +1,108 @@ +/* + * 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.wasmgc; + +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.strings.WasmGCStringProvider; +import org.teavm.backend.wasm.generators.gc.WasmGCCustomGeneratorContext; +import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicContext; +import org.teavm.backend.wasm.model.WasmFunction; +import org.teavm.backend.wasm.model.WasmModule; + +interface WasmGCJsoContext { + WasmModule module(); + + WasmFunctionTypes functionTypes(); + + BaseWasmFunctionRepository functions(); + + WasmGCNameProvider names(); + + WasmGCStringProvider strings(); + + void addToInitializer(Consumer initializerContributor); + + static WasmGCJsoContext wrap(WasmGCIntrinsicContext context) { + return new WasmGCJsoContext() { + @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 void addToInitializer(Consumer initializerContributor) { + context.addToInitializer(initializerContributor); + } + }; + } + + static WasmGCJsoContext wrap(WasmGCCustomGeneratorContext context) { + return new WasmGCJsoContext() { + @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 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 new file mode 100644 index 000000000..bc9dc8d2c --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCMarshallMethodGenerator.java @@ -0,0 +1,161 @@ +/* + * 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.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; + } + + @Override + public void apply(MethodReference method, WasmFunction function, WasmGCCustomGeneratorContext context) { + var jsoContext = WasmGCJsoContext.wrap(context); + + 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 wrapperFunction = javaObjectToJSFunction(context); + function.getBody().add(new WasmCall(wrapperFunction, new WasmGetLocal(thisLocal), + new WasmGetGlobal(jsClassGlobal))); + } + + private WasmFunction javaObjectToJSFunction(WasmGCCustomGeneratorContext context) { + if (javaObjectToJSFunction == null) { + javaObjectToJSFunction = new WasmFunction(context.functionTypes().of(WasmType.Reference.EXTERN, + context.typeMapper().mapType(ValueType.parse(Object.class)), WasmType.Reference.EXTERN)); + javaObjectToJSFunction.setName(context.names().topLevel("teavm.jso@javaObjectToJS")); + javaObjectToJSFunction.setImportName("javaObjectToJS"); + javaObjectToJSFunction.setImportModule("teavmJso"); + context.module().functions.add(javaObjectToJSFunction); + } + 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/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCMarshallMethodGeneratorFactory.java b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCMarshallMethodGeneratorFactory.java new file mode 100644 index 000000000..19108e004 --- /dev/null +++ b/jso/impl/src/main/java/org/teavm/jso/impl/wasmgc/WasmGCMarshallMethodGeneratorFactory.java @@ -0,0 +1,42 @@ +/* + * 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.wasmgc; + +import org.teavm.backend.wasm.generators.gc.WasmGCCustomGenerator; +import org.teavm.backend.wasm.generators.gc.WasmGCCustomGeneratorFactory; +import org.teavm.backend.wasm.generators.gc.WasmGCCustomGeneratorFactoryContext; +import org.teavm.jso.impl.JSMethods; +import org.teavm.model.MethodReference; + +class WasmGCMarshallMethodGeneratorFactory implements WasmGCCustomGeneratorFactory { + private WasmGCJsoCommonGenerator commonGen; + + WasmGCMarshallMethodGeneratorFactory(WasmGCJsoCommonGenerator commonGen) { + this.commonGen = commonGen; + } + + @Override + public WasmGCCustomGenerator createGenerator(MethodReference methodRef, + WasmGCCustomGeneratorFactoryContext context) { + if (!methodRef.getName().equals(JSMethods.MARSHALL_TO_JS.getName())) { + return null; + } + var cls = context.classes().get(methodRef.getClassName()); + return cls != null && cls.getInterfaces().contains(JSMethods.JS_MARSHALLABLE) + ? new WasmGCMarshallMethodGenerator(commonGen) + : null; + } +} diff --git a/tests/src/test/java/org/teavm/jso/test/ExportClassTest.java b/tests/src/test/java/org/teavm/jso/test/ExportClassTest.java index 57a33e54c..fb5911405 100644 --- a/tests/src/test/java/org/teavm/jso/test/ExportClassTest.java +++ b/tests/src/test/java/org/teavm/jso/test/ExportClassTest.java @@ -29,7 +29,7 @@ import org.teavm.junit.TestPlatform; @RunWith(TeaVMTestRunner.class) @SkipJVM -@OnlyPlatform(TestPlatform.JAVASCRIPT) +@OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC}) @EachTestCompiledSeparately public class ExportClassTest { @Test diff --git a/tools/deobfuscator-js/build.gradle.kts b/tools/deobfuscator-js/build.gradle.kts index 80013a09a..3ce8ef535 100644 --- a/tools/deobfuscator-js/build.gradle.kts +++ b/tools/deobfuscator-js/build.gradle.kts @@ -65,7 +65,7 @@ val generateLibJs by tasks.register("generateLibJs") { } val zipWithJs by tasks.register("zipWithJs") { - dependsOn(generateJs, generateLibJs) + //dependsOn(generateJs, generateLibJs) archiveClassifier = "js" from(layout.buildDirectory.dir("teavm"), layout.buildDirectory.dir("teavm-lib")) entryCompression = ZipEntryCompression.DEFLATED