wasm gc: support exporting Java classes to JavaScript

This commit is contained in:
Alexey Andreev 2024-09-29 13:26:09 +02:00
parent 0897a1bbd5
commit 383fee67c5
45 changed files with 1183 additions and 377 deletions

View File

@ -77,6 +77,7 @@ public class Renderer implements RenderingManager {
private final SourceWriter writer; private final SourceWriter writer;
private final ListableClassReaderSource classSource; private final ListableClassReaderSource classSource;
private final ClassReaderSource originalClassSource;
private final ClassLoader classLoader; private final ClassLoader classLoader;
private final Properties properties = new Properties(); private final Properties properties = new Properties();
private final ServiceRepository services; private final ServiceRepository services;
@ -103,6 +104,7 @@ public class Renderer implements RenderingManager {
List<ExportedDeclaration> exports, String entryPoint) { List<ExportedDeclaration> exports, String entryPoint) {
this.writer = writer; this.writer = writer;
this.classSource = context.getClassSource(); this.classSource = context.getClassSource();
this.originalClassSource = context.getInitialClassSource();
this.classLoader = context.getClassLoader(); this.classLoader = context.getClassLoader();
this.services = context.getServices(); this.services = context.getServices();
this.asyncMethods = new HashSet<>(asyncMethods); this.asyncMethods = new HashSet<>(asyncMethods);
@ -152,6 +154,11 @@ public class Renderer implements RenderingManager {
return classSource; return classSource;
} }
@Override
public ClassReaderSource getOriginalClassSource() {
return originalClassSource;
}
@Override @Override
public ClassLoader getClassLoader() { public ClassLoader getClassLoader() {
return classLoader; return classLoader;

View File

@ -18,6 +18,7 @@ package org.teavm.backend.javascript.rendering;
import java.util.Properties; import java.util.Properties;
import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.common.ServiceRepository; import org.teavm.common.ServiceRepository;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ListableClassReaderSource; import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
@ -32,6 +33,8 @@ public interface RenderingManager extends ServiceRepository {
ListableClassReaderSource getClassSource(); ListableClassReaderSource getClassSource();
ClassReaderSource getOriginalClassSource();
ClassLoader getClassLoader(); ClassLoader getClassLoader();
Properties getProperties(); Properties getProperties();

View File

@ -175,6 +175,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost {
var declarationsGenerator = new WasmGCDeclarationsGenerator( var declarationsGenerator = new WasmGCDeclarationsGenerator(
module, module,
classes, classes,
controller.getUnprocessedClassSource(),
controller.getClassLoader(), controller.getClassLoader(),
controller.getClassInitializerInfo(), controller.getClassInitializerInfo(),
controller.getDependencyInfo(), controller.getDependencyInfo(),

View File

@ -34,6 +34,7 @@ import org.teavm.backend.wasm.model.WasmModule;
import org.teavm.dependency.DependencyInfo; import org.teavm.dependency.DependencyInfo;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ListableClassHolderSource; import org.teavm.model.ListableClassHolderSource;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.analysis.ClassInitializerInfo; import org.teavm.model.analysis.ClassInitializerInfo;
@ -52,6 +53,7 @@ public class WasmGCDeclarationsGenerator {
public WasmGCDeclarationsGenerator( public WasmGCDeclarationsGenerator(
WasmModule module, WasmModule module,
ListableClassHolderSource classes, ListableClassHolderSource classes,
ClassReaderSource originalClasses,
ClassLoader classLoader, ClassLoader classLoader,
ClassInitializerInfo classInitializerInfo, ClassInitializerInfo classInitializerInfo,
DependencyInfo dependencyInfo, DependencyInfo dependencyInfo,
@ -87,6 +89,7 @@ public class WasmGCDeclarationsGenerator {
classGenerator = new WasmGCClassGenerator( classGenerator = new WasmGCClassGenerator(
module, module,
classes, classes,
originalClasses,
hierarchy, hierarchy,
dependencyInfo, dependencyInfo,
functionTypes, functionTypes,

View File

@ -102,6 +102,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
private final WasmModule module; private final WasmModule module;
private ClassReaderSource classSource; private ClassReaderSource classSource;
private ClassReaderSource originalClassSource;
private ClassHierarchy hierarchy; private ClassHierarchy hierarchy;
private WasmFunctionTypes functionTypes; private WasmFunctionTypes functionTypes;
private TagRegistry tagRegistry; private TagRegistry tagRegistry;
@ -158,7 +159,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
private boolean hasLoadServices; private boolean hasLoadServices;
public WasmGCClassGenerator(WasmModule module, ClassReaderSource classSource, public WasmGCClassGenerator(WasmModule module, ClassReaderSource classSource,
ClassHierarchy hierarchy, DependencyInfo dependencyInfo, ClassReaderSource originalClassSource, ClassHierarchy hierarchy, DependencyInfo dependencyInfo,
WasmFunctionTypes functionTypes, TagRegistry tagRegistry, WasmFunctionTypes functionTypes, TagRegistry tagRegistry,
ClassMetadataRequirements metadataRequirements, WasmGCVirtualTableProvider virtualTables, ClassMetadataRequirements metadataRequirements, WasmGCVirtualTableProvider virtualTables,
BaseWasmFunctionRepository functionProvider, WasmGCNameProvider names, BaseWasmFunctionRepository functionProvider, WasmGCNameProvider names,
@ -166,6 +167,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
List<WasmGCCustomTypeMapperFactory> customTypeMapperFactories) { List<WasmGCCustomTypeMapperFactory> customTypeMapperFactories) {
this.module = module; this.module = module;
this.classSource = classSource; this.classSource = classSource;
this.originalClassSource = originalClassSource;
this.hierarchy = hierarchy; this.hierarchy = hierarchy;
this.functionTypes = functionTypes; this.functionTypes = functionTypes;
this.tagRegistry = tagRegistry; this.tagRegistry = tagRegistry;
@ -195,6 +197,11 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
private WasmGCCustomTypeMapperFactoryContext customTypeMapperFactoryContext() { private WasmGCCustomTypeMapperFactoryContext customTypeMapperFactoryContext() {
return new WasmGCCustomTypeMapperFactoryContext() { return new WasmGCCustomTypeMapperFactoryContext() {
@Override
public ClassReaderSource originalClasses() {
return originalClassSource;
}
@Override @Override
public ClassReaderSource classes() { public ClassReaderSource classes() {
return classSource; return classSource;

View File

@ -22,6 +22,8 @@ import org.teavm.model.ClassReaderSource;
public interface WasmGCCustomTypeMapperFactoryContext { public interface WasmGCCustomTypeMapperFactoryContext {
ClassReaderSource classes(); ClassReaderSource classes();
ClassReaderSource originalClasses();
WasmModule module(); WasmModule module();
WasmGCClassInfoProvider classInfoProvider(); WasmGCClassInfoProvider classInfoProvider();

View File

@ -433,5 +433,10 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository {
public WasmGCStringProvider strings() { public WasmGCStringProvider strings() {
return context.strings(); return context.strings();
} }
@Override
public void addToInitializer(Consumer<WasmFunction> initializerContributor) {
context.addToInitializer(initializerContributor);
}
}; };
} }

View File

@ -15,12 +15,14 @@
*/ */
package org.teavm.backend.wasm.generators.gc; package org.teavm.backend.wasm.generators.gc;
import java.util.function.Consumer;
import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.BaseWasmFunctionRepository;
import org.teavm.backend.wasm.WasmFunctionTypes; import org.teavm.backend.wasm.WasmFunctionTypes;
import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider; 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.WasmGCClassInfoProvider;
import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper; import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper;
import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringProvider; 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.WasmModule;
import org.teavm.backend.wasm.model.WasmTag; import org.teavm.backend.wasm.model.WasmTag;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
@ -48,4 +50,6 @@ public interface WasmGCCustomGeneratorContext {
Diagnostics diagnostics(); Diagnostics diagnostics();
WasmGCStringProvider strings(); WasmGCStringProvider strings();
void addToInitializer(Consumer<WasmFunction> initializerContributor);
} }

View File

@ -108,6 +108,9 @@ public class WasmGCCustomGenerators implements WasmGCCustomGeneratorProvider {
WasmGCCustomGenerator generator = null; WasmGCCustomGenerator generator = null;
for (var factory : factories) { for (var factory : factories) {
generator = factory.createGenerator(method, factoryContext); generator = factory.createGenerator(method, factoryContext);
if (generator != null) {
break;
}
} }
result = new Container(generator); result = new Container(generator);
generators.put(method, result); generators.put(method, result);

View File

@ -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 {
}

View File

@ -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
}

View File

@ -42,7 +42,6 @@ import org.teavm.common.ServiceRepository;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.interop.PlatformMarker; import org.teavm.interop.PlatformMarker;
import org.teavm.model.AnnotationReader; import org.teavm.model.AnnotationReader;
import org.teavm.model.BasicBlock;
import org.teavm.model.CallLocation; import org.teavm.model.CallLocation;
import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolder;
@ -52,8 +51,6 @@ import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier; import org.teavm.model.ElementModifier;
import org.teavm.model.FieldReader; import org.teavm.model.FieldReader;
import org.teavm.model.FieldReference; import org.teavm.model.FieldReference;
import org.teavm.model.Instruction;
import org.teavm.model.InvokeDynamicInstruction;
import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodHolder; import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader; import org.teavm.model.MethodReader;
@ -61,12 +58,7 @@ import org.teavm.model.MethodReference;
import org.teavm.model.Program; import org.teavm.model.Program;
import org.teavm.model.ReferenceCache; import org.teavm.model.ReferenceCache;
import org.teavm.model.ValueType; 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.optimization.UnreachableBasicBlockEliminator;
import org.teavm.model.util.BasicBlockSplitter;
import org.teavm.model.util.ModelUtils; import org.teavm.model.util.ModelUtils;
import org.teavm.model.util.ProgramUtils; import org.teavm.model.util.ProgramUtils;
import org.teavm.parsing.Parser; import org.teavm.parsing.Parser;
@ -102,7 +94,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
private Diagnostics diagnostics; private Diagnostics diagnostics;
DefaultCallGraph callGraph = new DefaultCallGraph(); DefaultCallGraph callGraph = new DefaultCallGraph();
private DependencyAgent agent; private DependencyAgent agent;
Map<MethodReference, BootstrapMethodSubstitutor> bootstrapMethodSubstitutors = new HashMap<>();
Map<MethodReference, DependencyPlugin> dependencyPlugins = new HashMap<>(); Map<MethodReference, DependencyPlugin> dependencyPlugins = new HashMap<>();
private boolean completing; private boolean completing;
private Map<String, DependencyTypeFilter> superClassFilters = new HashMap<>(); private Map<String, DependencyTypeFilter> superClassFilters = new HashMap<>();
@ -119,7 +110,8 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
this.unprocessedClassSource = classSource; this.unprocessedClassSource = classSource;
this.diagnostics = diagnostics; this.diagnostics = diagnostics;
this.referenceCache = referenceCache; 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; agentClassSource = this.classSource;
classHierarchy = new ClassHierarchy(this.classSource); classHierarchy = new ClassHierarchy(this.classSource);
this.classLoader = classLoader; this.classLoader = classLoader;
@ -138,7 +130,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
classCache = new CachedFunction<>(this::createClassDependency); classCache = new CachedFunction<>(this::createClassDependency);
agent = new DependencyAgent(this);
classType = getType("java.lang.Class"); classType = getType("java.lang.Class");
} }
@ -274,7 +265,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
dep.used = false; dep.used = false;
lock(dep, false); lock(dep, false);
deferredTasks.add(() -> { deferredTasks.add(() -> {
processInvokeDynamic(dep);
classSource.getReferenceResolver().use(dep.method.getReference(), diagnostics); classSource.getReferenceResolver().use(dep.method.getReference(), diagnostics);
processMethod(dep); processMethod(dep);
dep.used = true; dep.used = true;
@ -489,7 +479,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
void scheduleMethodAnalysis(MethodDependency dep) { void scheduleMethodAnalysis(MethodDependency dep) {
classSource.getReferenceResolver().use(dep.getReference(), diagnostics); classSource.getReferenceResolver().use(dep.getReference(), diagnostics);
deferredTasks.add(() -> { deferredTasks.add(() -> {
processInvokeDynamic(dep);
classSource.getReferenceResolver().use(dep.getReference(), diagnostics); classSource.getReferenceResolver().use(dep.getReference(), diagnostics);
processMethod(dep); processMethod(dep);
}); });
@ -755,6 +744,7 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
agent.cleanup(); agent.cleanup();
listeners.clear(); listeners.clear();
classSource.dispose();
agentClassSource = classSourcePacker.pack(classSource, agentClassSource = classSourcePacker.pack(classSource,
ClassClosureAnalyzer.build(classSource, new ArrayList<>(classSource.cache.keySet()))); ClassClosureAnalyzer.build(classSource, new ArrayList<>(classSource.cache.keySet())));
if (classSource != agentClassSource) { if (classSource != agentClassSource) {
@ -762,7 +752,7 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
generatedClassNames.addAll(classSource.getGeneratedClassNames()); generatedClassNames.addAll(classSource.getGeneratedClassNames());
} }
classSource.innerHierarchy = null; classSource.innerHierarchy = null;
classSource.dispose();
classSource = null; classSource = null;
methodReaderCache = null; methodReaderCache = null;
} }
@ -827,7 +817,7 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
} }
public void addBootstrapMethodSubstitutor(MethodReference method, BootstrapMethodSubstitutor substitutor) { public void addBootstrapMethodSubstitutor(MethodReference method, BootstrapMethodSubstitutor substitutor) {
bootstrapMethodSubstitutors.put(method, substitutor); classSource.bootstrapMethodSubstitutors.put(method, substitutor);
} }
public void addDependencyPlugin(MethodReference method, DependencyPlugin dependencyPlugin) { public void addDependencyPlugin(MethodReference method, DependencyPlugin dependencyPlugin) {
@ -863,70 +853,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
return result; 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<ValueEmitter> 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 { static class IncrementalCache implements IncrementalDependencyProvider, IncrementalDependencyRegistration {
private final String[] emptyArray = new String[0]; private final String[] emptyArray = new String[0];

View File

@ -17,12 +17,15 @@ package org.teavm.dependency;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import org.teavm.cache.IncrementalDependencyRegistration; import org.teavm.cache.IncrementalDependencyRegistration;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.BasicBlock;
import org.teavm.model.CallLocation;
import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderSource; import org.teavm.model.ClassHolderSource;
@ -30,12 +33,23 @@ import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassHolderTransformerContext; import org.teavm.model.ClassHolderTransformerContext;
import org.teavm.model.ClassReader; import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ClassReaderSource;
import org.teavm.model.Instruction;
import org.teavm.model.InvokeDynamicInstruction;
import org.teavm.model.MethodHolder; 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.optimization.UnreachableBasicBlockEliminator;
import org.teavm.model.transformation.ClassInitInsertion; import org.teavm.model.transformation.ClassInitInsertion;
import org.teavm.model.util.BasicBlockSplitter;
import org.teavm.model.util.ModelUtils; import org.teavm.model.util.ModelUtils;
class DependencyClassSource implements ClassHolderSource { class DependencyClassSource implements ClassHolderSource {
private DependencyAgent agent;
private ClassReaderSource innerSource; private ClassReaderSource innerSource;
ClassHierarchy innerHierarchy; ClassHierarchy innerHierarchy;
private Diagnostics diagnostics; private Diagnostics diagnostics;
@ -48,9 +62,12 @@ class DependencyClassSource implements ClassHolderSource {
private ReferenceResolver referenceResolver; private ReferenceResolver referenceResolver;
private ClassInitInsertion classInitInsertion; private ClassInitInsertion classInitInsertion;
private String entryPoint; private String entryPoint;
Map<MethodReference, BootstrapMethodSubstitutor> bootstrapMethodSubstitutors = new HashMap<>();
private boolean disposed;
DependencyClassSource(ClassReaderSource innerSource, Diagnostics diagnostics, DependencyClassSource(DependencyAgent agent, ClassReaderSource innerSource, Diagnostics diagnostics,
IncrementalDependencyRegistration dependencyRegistration, String[] platformTags) { IncrementalDependencyRegistration dependencyRegistration, String[] platformTags) {
this.agent = agent;
this.innerSource = innerSource; this.innerSource = innerSource;
this.diagnostics = diagnostics; this.diagnostics = diagnostics;
innerHierarchy = new ClassHierarchy(innerSource); innerHierarchy = new ClassHierarchy(innerSource);
@ -65,7 +82,16 @@ class DependencyClassSource implements ClassHolderSource {
@Override @Override
public ClassHolder get(String name) { 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) { public void submit(ClassHolder cls) {
@ -84,26 +110,27 @@ class DependencyClassSource implements ClassHolderSource {
cache.remove(cls.getName()); cache.remove(cls.getName());
} }
private ClassHolder findAndTransformClass(String name) { private void transformClass(ClassHolder cls) {
var cls = findClass(name); if (!disposed) {
if (cls != null) {
if (!transformers.isEmpty()) {
for (var transformer : transformers) {
transformer.transformClass(cls, transformContext);
}
}
for (var method : cls.getMethods()) { for (var method : cls.getMethods()) {
if (method.getProgram() != null) { processInvokeDynamic(method);
var program = method.getProgram(); }
method.setProgramSupplier(m -> { }
referenceResolver.resolve(m, program); if (!transformers.isEmpty()) {
classInitInsertion.apply(m, program); for (var transformer : transformers) {
return program; 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) { private ClassHolder findClass(String name) {
@ -132,6 +159,69 @@ class DependencyClassSource implements ClassHolderSource {
public void dispose() { public void dispose() {
transformers.clear(); 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<ValueEmitter> 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() { final ClassHolderTransformerContext transformContext = new ClassHolderTransformerContext() {

View File

@ -119,3 +119,7 @@ let $rt_apply = (instance, method, args) => instance[method].apply(instance, arg
let $rt_apply_topLevel = (method, args) => method.apply(null, args); let $rt_apply_topLevel = (method, args) => method.apply(null, args);
let $rt_skip = (array, count) => count === 0 ? array : Array.prototype.slice.call(array, count); 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)));
}

View File

@ -20,6 +20,9 @@ TeaVM.wasm = function() {
let getGlobalName = function(name) { let getGlobalName = function(name) {
return eval(name); return eval(name);
} }
let javaObjectSymbol = Symbol("javaObject");
let functionsSymbol = Symbol("functions");
let javaWrappers = new WeakMap();
function defaults(imports) { function defaults(imports) {
let stderr = ""; let stderr = "";
let stdout = ""; let stdout = "";
@ -32,42 +35,28 @@ TeaVM.wasm = function() {
exports.reportGarbageCollectedString(heldValue); exports.reportGarbageCollectedString(heldValue);
}); });
imports.teavmDate = { imports.teavmDate = {
currentTimeMillis() { currentTimeMillis: () => new Date().getTime(),
return new Date().getTime(); dateToString: timestamp => stringToJava(new Date(timestamp).toString()),
}, getYear: timestamp =>new Date(timestamp).getFullYear(),
dateToString(timestamp) {
return stringToJava(new Date(timestamp).toString());
},
getYear(timestamp) {
return new Date(timestamp).getFullYear();
},
setYear(timestamp, year) { setYear(timestamp, year) {
let date = new Date(timestamp); let date = new Date(timestamp);
date.setFullYear(year); date.setFullYear(year);
return date.getTime(); return date.getTime();
}, },
getMonth(timestamp) { getMonth: timestamp =>new Date(timestamp).getMonth(),
return new Date(timestamp).getMonth();
},
setMonth(timestamp, month) { setMonth(timestamp, month) {
let date = new Date(timestamp); let date = new Date(timestamp);
date.setMonth(month); date.setMonth(month);
return date.getTime(); return date.getTime();
}, },
getDate(timestamp) { getDate: timestamp =>new Date(timestamp).getDate(),
return new Date(timestamp).getDate();
},
setDate(timestamp, value) { setDate(timestamp, value) {
let date = new Date(timestamp); let date = new Date(timestamp);
date.setDate(value); date.setDate(value);
return date.getTime(); return date.getTime();
}, },
create(year, month, date, hrs, min, sec) { create: (year, month, date, hrs, min, sec) => new Date(year, month, date, hrs, min, sec).getTime(),
return new Date(year, month, date, hrs, min, sec).getTime(); createFromUTC: (year, month, date, hrs, min, sec) => Date.UTC(year, month, date, hrs, min, sec)
},
createFromUTC(year, month, date, hrs, min, sec) {
return Date.UTC(year, month, date, hrs, min, sec);
}
}; };
imports.teavmConsole = { imports.teavmConsole = {
putcharStderr(c) { putcharStderr(c) {
@ -106,6 +95,22 @@ TeaVM.wasm = function() {
function identity(value) { function identity(value) {
return 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 = { imports.teavmJso = {
emptyString: () => "", emptyString: () => "",
stringFromCharCode: code => String.fromCharCode(code), stringFromCharCode: code => String.fromCharCode(code),
@ -120,25 +125,76 @@ TeaVM.wasm = function() {
getPropertyPure: (obj, prop) => obj[prop], getPropertyPure: (obj, prop) => obj[prop],
setProperty: (obj, prop, value) => obj[prop] = value, setProperty: (obj, prop, value) => obj[prop] = value,
setPropertyPure: (obj, prop) => 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", for (let name of ["wrapByte", "wrapShort", "wrapChar", "wrapInt", "wrapFloat", "wrapDouble", "unwrapByte",
"unwrapShort", "unwrapChar", "unwrapInt", "unwrapFloat", "unwrapDouble"]) { "unwrapShort", "unwrapChar", "unwrapInt", "unwrapFloat", "unwrapDouble"]) {
imports.teavmJso[name] = identity; imports.teavmJso[name] = identity;
} }
for (let i = 0; i < 32; ++i) { for (let i = 0; i < 32; ++i) {
imports.teavmJso["createFunction" + i] = function() { imports.teavmJso["createFunction" + i] = (...args) => new Function(...args);
return new Function(...arguments); imports.teavmJso["callFunction" + i] = (fn, ...args) => fn(...args);
}; imports.teavmJso["callMethod" + i] = (instance, method, ...args) => instance[method](...args);
imports.teavmJso["callFunction" + i] = function(fn, ...args) { imports.teavmJso["construct" + i] = (constructor, ...args) => new constructor(...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.teavmMath = Math; imports.teavmMath = Math;
} }

View File

@ -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<MethodReader> filter) {
var methods = new HashMap<String, MethodReference>();
var properties = new HashMap<String, PropertyInfo>();
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<String, MethodReference> methods;
public final Map<String, PropertyInfo> properties;
public final MethodReference constructor;
Members(Map<String, MethodReference> methods, Map<String, PropertyInfo> 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
}
}

View File

@ -738,6 +738,7 @@ public final class JS {
@GeneratedBy(JSNativeGenerator.class) @GeneratedBy(JSNativeGenerator.class)
@PluggableDependency(JSNativeInjector.class) @PluggableDependency(JSNativeInjector.class)
@Import(name = "asFunction", module = "teavmJso")
public static native JSObject function(JSObject instance, JSObject property); public static native JSObject function(JSObject instance, JSObject property);
@GeneratedBy(JSNativeGenerator.class) @GeneratedBy(JSNativeGenerator.class)

View File

@ -15,9 +15,9 @@
*/ */
package org.teavm.jso.impl; 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.HashMap;
import java.util.Map;
import java.util.function.Predicate;
import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.rendering.RenderingManager; import org.teavm.backend.javascript.rendering.RenderingManager;
import org.teavm.backend.javascript.spi.MethodContributor; import org.teavm.backend.javascript.spi.MethodContributor;
@ -47,7 +47,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
public void begin(RenderingManager context, BuildTarget buildTarget) { public void begin(RenderingManager context, BuildTarget buildTarget) {
writer = context.getWriter(); writer = context.getWriter();
classSource = context.getClassSource(); classSource = context.getClassSource();
typeHelper = new JSTypeHelper(context.getClassSource()); typeHelper = new JSTypeHelper(context.getOriginalClassSource());
this.context = context; this.context = context;
} }
@ -91,7 +91,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
} }
private boolean exportClassInstanceMembers(ClassReader classReader) { 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()); var isJsClassImpl = typeHelper.isJavaScriptImplementation(classReader.getName());
if (members.methods.isEmpty() && members.properties.isEmpty() && !isJsClassImpl) { if (members.methods.isEmpty() && members.properties.isEmpty() && !isJsClassImpl) {
@ -106,23 +106,25 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
} }
for (var aliasEntry : members.methods.entrySet()) { for (var aliasEntry : members.methods.entrySet()) {
if (classReader.getMethod(aliasEntry.getValue()) == null) { if (classReader.getMethod(aliasEntry.getValue().getDescriptor()) == null) {
continue; continue;
} }
appendMethodAlias(aliasEntry.getKey()); appendMethodAlias(aliasEntry.getKey());
writer.ws().append("=").ws().append("c.").appendVirtualMethod(aliasEntry.getValue()) writer.ws().append("=").ws().appendFunction("$rt_callWithReceiver").append("(")
.append(";").softNewLine(); .appendMethod(aliasEntry.getValue()).append(");").softNewLine();
} }
for (var aliasEntry : members.properties.entrySet()) { for (var aliasEntry : members.properties.entrySet()) {
var propInfo = aliasEntry.getValue(); var propInfo = aliasEntry.getValue();
if (propInfo.getter == null || classReader.getMethod(propInfo.getter) == null) { if (propInfo.getter == null || classReader.getMethod(propInfo.getter.getDescriptor()) == null) {
continue; continue;
} }
appendPropertyAlias(aliasEntry.getKey()); appendPropertyAlias(aliasEntry.getKey());
writer.append("get:").ws().append("c.").appendVirtualMethod(propInfo.getter); writer.append("get:").ws().appendFunction("$rt_callWithReceiver").append("(")
if (propInfo.setter != null && classReader.getMethod(propInfo.setter) != null) { .appendMethod(propInfo.getter).append(")");
if (propInfo.setter != null && classReader.getMethod(propInfo.setter.getDescriptor()) != null) {
writer.append(",").softNewLine(); 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(); writer.softNewLine().outdent().append("});").softNewLine();
} }
@ -136,7 +138,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
} }
private boolean exportClassStaticMembers(ClassReader classReader, String name) { 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()) { if (members.methods.isEmpty() && members.properties.isEmpty()) {
return false; return false;
@ -146,7 +148,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
for (var aliasEntry : members.methods.entrySet()) { for (var aliasEntry : members.methods.entrySet()) {
appendMethodAlias(aliasEntry.getKey()); appendMethodAlias(aliasEntry.getKey());
var fullRef = new MethodReference(classReader.getName(), aliasEntry.getValue()); var fullRef = aliasEntry.getValue();
writer.ws().append("=").ws().appendMethod(fullRef).append(";").softNewLine(); writer.ws().append("=").ws().appendMethod(fullRef).append(";").softNewLine();
} }
for (var aliasEntry : members.properties.entrySet()) { for (var aliasEntry : members.properties.entrySet()) {
@ -155,11 +157,11 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
continue; continue;
} }
appendPropertyAlias(aliasEntry.getKey()); appendPropertyAlias(aliasEntry.getKey());
var fullGetter = new MethodReference(classReader.getName(), propInfo.getter); var fullGetter = propInfo.getter;
writer.append("get:").ws().appendMethod(fullGetter); writer.append("get:").ws().appendMethod(fullGetter);
if (propInfo.setter != null) { if (propInfo.setter != null) {
writer.append(",").softNewLine(); writer.append(",").softNewLine();
var fullSetter = new MethodReference(classReader.getName(), propInfo.setter); var fullSetter = propInfo.setter;
writer.append("set:").ws().appendMethod(fullSetter); writer.append("set:").ws().appendMethod(fullSetter);
} }
writer.softNewLine().outdent().append("});").softNewLine(); writer.softNewLine().outdent().append("});").softNewLine();
@ -182,39 +184,6 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
.ws().append("{").indent().softNewLine(); .ws().append("{").indent().softNewLine();
} }
private Members collectMembers(ClassReader classReader, Predicate<MethodReader> filter) {
var methods = new HashMap<String, MethodDescriptor>();
var properties = new HashMap<String, PropertyInfo>();
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() { private void exportModule() {
var cls = classSource.get(context.getEntryPoint()); var cls = classSource.get(context.getEntryPoint());
for (var method : cls.getMethods()) { for (var method : cls.getMethods()) {
@ -222,7 +191,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
continue; continue;
} }
var methodAlias = getPublicAlias(method); 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); context.exportMethod(method.getReference(), methodAlias.name);
} }
} }
@ -230,7 +199,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
private void exportClassFromModule(ClassReader cls, String functionName) { private void exportClassFromModule(ClassReader cls, String functionName) {
var name = getClassAliasName(cls); var name = getClassAliasName(cls);
var constructors = collectMembers(cls, method -> !method.hasModifier(ElementModifier.STATIC)); var constructors = collectMembers(cls, AliasCollector::isInstanceMember);
var method = constructors.constructor; var method = constructors.constructor;
writer.append("function ").appendFunction(functionName).append("("); writer.append("function ").appendFunction(functionName).append("(");
@ -245,7 +214,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
writer.append(")").ws().appendBlockStart(); writer.append(")").ws().appendBlockStart();
if (method != null) { if (method != null) {
writer.appendClass(cls.getName()).append(".call(this);").softNewLine(); 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) { for (var i = 0; i < method.parameterCount(); ++i) {
writer.append(",").ws().append("p" + i); writer.append(",").ws().append("p" + i);
} }
@ -293,31 +262,6 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
} }
return false; 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) { private FieldReader getFunctorField(ClassReader cls) {
return cls.getField("$$jso_functor$$"); return cls.getField("$$jso_functor$$");
} }
@ -403,38 +347,5 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
return methodReader != null && getPublicAlias(methodReader) != null; return methodReader != null && getPublicAlias(methodReader) != null;
} }
private static class Members {
final Map<String, MethodDescriptor> methods;
final Map<String, PropertyInfo> properties;
final MethodDescriptor constructor;
Members(Map<String, MethodDescriptor> methods, Map<String, PropertyInfo> 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
}
} }

View File

@ -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 {
}

View File

@ -80,20 +80,6 @@ import org.teavm.model.util.ProgramUtils;
class JSClassProcessor { class JSClassProcessor {
private static final String NO_SIDE_EFFECTS = NoSideEffects.class.getName(); 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 ClassReaderSource classSource;
private final JSBodyRepository repository; private final JSBodyRepository repository;
private final JavaInvocationProcessor javaInvocationProcessor; private final JavaInvocationProcessor javaInvocationProcessor;
@ -111,6 +97,7 @@ class JSClassProcessor {
private JSImportAnnotationCache annotationCache; private JSImportAnnotationCache annotationCache;
private ClassReader objectClass; private ClassReader objectClass;
private Predicate<String> classFilter = n -> true; private Predicate<String> classFilter = n -> true;
private boolean wasmGC;
JSClassProcessor(ClassReaderSource classSource, JSTypeHelper typeHelper, JSBodyRepository repository, JSClassProcessor(ClassReaderSource classSource, JSTypeHelper typeHelper, JSBodyRepository repository,
Diagnostics diagnostics, IncrementalDependencyRegistration incrementalCache, boolean strict) { Diagnostics diagnostics, IncrementalDependencyRegistration incrementalCache, boolean strict) {
@ -125,7 +112,11 @@ class JSClassProcessor {
annotationCache = new JSImportAnnotationCache(classSource, diagnostics); annotationCache = new JSImportAnnotationCache(classSource, diagnostics);
} }
public void setClassFilter(Predicate<String> classFilter) { void setWasmGC(boolean wasmGC) {
this.wasmGC = wasmGC;
}
void setClassFilter(Predicate<String> classFilter) {
this.classFilter = classFilter; this.classFilter = classFilter;
} }
@ -286,8 +277,11 @@ class JSClassProcessor {
void processProgram(MethodHolder methodToProcess) { void processProgram(MethodHolder methodToProcess) {
setCurrentProgram(methodToProcess.getProgram()); setCurrentProgram(methodToProcess.getProgram());
types = new JSTypeInference(typeHelper, classSource, program, methodToProcess.getReference()); types = new JSTypeInference(typeHelper, classSource, program, methodToProcess.getReference(), wasmGC);
types.ensure(); types.ensure();
if (wasmGC) {
wrapJsPhis(methodToProcess.getReference());
}
for (int i = 0; i < program.basicBlockCount(); ++i) { for (int i = 0; i < program.basicBlockCount(); ++i) {
var block = program.basicBlockAt(i); var block = program.basicBlockAt(i);
for (var insn : block) { 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) { private void processInvokeArgs(InvokeInstruction invoke, MethodReader methodToInvoke) {
if (methodToInvoke != null && methodToInvoke.getAnnotations().get(JSBody.class.getName()) != null) { if (methodToInvoke != null && methodToInvoke.getAnnotations().get(JSBody.class.getName()) != null) {
return; return;
@ -402,7 +422,7 @@ class JSClassProcessor {
if (type == JSType.JS || type == JSType.MIXED) { if (type == JSType.JS || type == JSType.MIXED) {
var unwrap = new InvokeInstruction(); var unwrap = new InvokeInstruction();
unwrap.setType(InvocationType.SPECIAL); 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.setArguments(program.createVariable());
unwrap.setReceiver(insn.getReceiver()); unwrap.setReceiver(insn.getReceiver());
unwrap.setLocation(insn.getLocation()); unwrap.setLocation(insn.getLocation());
@ -420,7 +440,7 @@ class JSClassProcessor {
if (type == JSType.JS || type == JSType.MIXED) { if (type == JSType.JS || type == JSType.MIXED) {
var wrap = new InvokeInstruction(); var wrap = new InvokeInstruction();
wrap.setType(InvocationType.SPECIAL); 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.setArguments(insn.getValue());
wrap.setReceiver(program.createVariable()); wrap.setReceiver(program.createVariable());
wrap.setLocation(insn.getLocation()); wrap.setLocation(insn.getLocation());
@ -563,7 +583,7 @@ class JSClassProcessor {
if (isTransparent(targetClassName)) { if (isTransparent(targetClassName)) {
var invoke = new InvokeInstruction(); var invoke = new InvokeInstruction();
invoke.setType(InvocationType.SPECIAL); invoke.setType(InvocationType.SPECIAL);
invoke.setMethod(IS_JS); invoke.setMethod(JSMethods.IS_JS);
invoke.setArguments(value); invoke.setArguments(value);
invoke.setReceiver(receiver); invoke.setReceiver(receiver);
invoke.setLocation(location); invoke.setLocation(location);
@ -572,7 +592,9 @@ class JSClassProcessor {
var primitiveType = getPrimitiveType(targetClassName); var primitiveType = getPrimitiveType(targetClassName);
var invoke = new InvokeInstruction(); var invoke = new InvokeInstruction();
invoke.setType(InvocationType.SPECIAL); 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 var secondArg = primitiveType != null
? marshaller.addJsString(primitiveType, location) ? marshaller.addJsString(primitiveType, location)
: marshaller.classRef(targetClassName, location); : marshaller.classRef(targetClassName, location);
@ -648,7 +670,7 @@ class JSClassProcessor {
} }
var wrap = new InvokeInstruction(); var wrap = new InvokeInstruction();
wrap.setType(InvocationType.SPECIAL); 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.setArguments(var);
wrap.setReceiver(program.createVariable()); wrap.setReceiver(program.createVariable());
wrap.setLocation(instruction.getLocation()); wrap.setLocation(instruction.getLocation());
@ -663,7 +685,7 @@ class JSClassProcessor {
} }
var unwrap = new InvokeInstruction(); var unwrap = new InvokeInstruction();
unwrap.setType(InvocationType.SPECIAL); 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.setArguments(var);
unwrap.setReceiver(program.createVariable()); unwrap.setReceiver(program.createVariable());
unwrap.setLocation(instruction.getLocation()); unwrap.setLocation(instruction.getLocation());
@ -1187,6 +1209,7 @@ class JSClassProcessor {
if (method.getAnnotations().get(NoSideEffects.class.getName()) != null) { if (method.getAnnotations().get(NoSideEffects.class.getName()) != null) {
proxyMethod.getAnnotations().add(new AnnotationHolder(NoSideEffects.class.getName())); proxyMethod.getAnnotations().add(new AnnotationHolder(NoSideEffects.class.getName()));
} }
proxyMethod.getAnnotations().add(new AnnotationHolder(JSBodyDelegate.class.getName()));
boolean inline = repository.inlineMethods.contains(methodRef); boolean inline = repository.inlineMethods.contains(methodRef);
AnnotationHolder generatorAnnot = new AnnotationHolder(inline AnnotationHolder generatorAnnot = new AnnotationHolder(inline
? DynamicInjector.class.getName() : DynamicGenerator.class.getName()); ? DynamicInjector.class.getName() : DynamicGenerator.class.getName());

View File

@ -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 {
}

View File

@ -17,23 +17,29 @@ package org.teavm.jso.impl;
import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.AbstractDependencyListener;
import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyAgent;
import org.teavm.dependency.DependencyNode;
import org.teavm.dependency.MethodDependency; import org.teavm.dependency.MethodDependency;
import org.teavm.jso.JSExportClasses; import org.teavm.jso.JSExportClasses;
import org.teavm.model.AnnotationReader; import org.teavm.model.AnnotationReader;
import org.teavm.model.CallLocation; import org.teavm.model.CallLocation;
import org.teavm.model.ClassReader; import org.teavm.model.ClassReader;
import org.teavm.model.ElementModifier;
import org.teavm.model.MethodReader; import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
class JSDependencyListener extends AbstractDependencyListener { class JSDependencyListener extends AbstractDependencyListener {
private JSBodyRepository repository; private JSBodyRepository repository;
private DependencyNode exceptions;
JSDependencyListener(JSBodyRepository repository) { JSDependencyListener(JSBodyRepository repository) {
this.repository = repository; this.repository = repository;
} }
@Override
public void started(DependencyAgent agent) {
exceptions = agent.createNode();
}
@Override @Override
public void methodReached(DependencyAgent agent, MethodDependency method) { public void methodReached(DependencyAgent agent, MethodDependency method) {
MethodReference ref = method.getReference(); MethodReference ref = method.getReference();
@ -43,6 +49,22 @@ class JSDependencyListener extends AbstractDependencyListener {
agent.linkMethod(callbackMethod).addLocation(new CallLocation(ref)).use(); 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 @Override
@ -62,10 +84,8 @@ class JSDependencyListener extends AbstractDependencyListener {
if (exposeAnnot != null) { if (exposeAnnot != null) {
MethodDependency methodDep = agent.linkMethod(method.getReference()); MethodDependency methodDep = agent.linkMethod(method.getReference());
if (methodDep.getMethod() != null) { if (methodDep.getMethod() != null) {
if (!methodDep.getMethod().hasModifier(ElementModifier.STATIC)) {
methodDep.getVariable(0).propagate(agent.getType(className));
}
methodDep.use(); methodDep.use();
methodDep.getThrown().connect(exceptions);
} }
} }
} }

View File

@ -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 {
}

View File

@ -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();
}

View File

@ -19,10 +19,11 @@ import java.util.Arrays;
import org.teavm.jso.JSObject; import org.teavm.jso.JSObject;
import org.teavm.jso.core.JSArray; import org.teavm.jso.core.JSArray;
import org.teavm.jso.core.JSArrayReader; import org.teavm.jso.core.JSArrayReader;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
final class JSMethods { public final class JSMethods {
public static final MethodReference GET = new MethodReference(JS.class, "get", JSObject.class, public static final MethodReference GET = new MethodReference(JS.class, "get", JSObject.class,
JSObject.class, JSObject.class); JSObject.class, JSObject.class);
public static final MethodReference GET_PURE = new MethodReference(JS.class, "getPure", 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[] CONSTRUCT_METHODS = new MethodReference[13];
private static final MethodReference[] ARRAY_OF_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 { static {
for (int i = 0; i < INVOKE_METHODS.length; ++i) { for (int i = 0; i < INVOKE_METHODS.length; ++i) {
var signature = new ValueType[i + 3]; var signature = new ValueType[i + 3];

View File

@ -46,12 +46,7 @@ public class JSOPlugin implements TeaVMPlugin {
host.add(new JSExceptionsDependencyListener()); host.add(new JSExceptionsDependencyListener());
var wrapperDependency = new JSWrapperDependency(); var wrapperDependency = new JSWrapperDependency();
host.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class), host.add(wrapperDependency);
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);
TeaVMPluginUtil.handleNatives(host, JS.class); TeaVMPluginUtil.handleNatives(host, JS.class);
@ -61,6 +56,7 @@ public class JSOPlugin implements TeaVMPlugin {
if (wasmGCHost != null) { if (wasmGCHost != null) {
classTransformer.setClassFilter(n -> !n.startsWith("java.")); classTransformer.setClassFilter(n -> !n.startsWith("java."));
classTransformer.forWasmGC();
WasmGCJso.install(host, wasmGCHost, repository); WasmGCJso.install(host, wasmGCHost, repository);
} }
} }
@ -90,6 +86,10 @@ public class JSOPlugin implements TeaVMPlugin {
wrapperGenerator); wrapperGenerator);
jsHost.add(new MethodReference(JSWrapper.class, "dependencyJsToJava", JSObject.class, Object.class), jsHost.add(new MethodReference(JSWrapper.class, "dependencyJsToJava", JSObject.class, Object.class),
wrapperGenerator); 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), jsHost.add(new MethodReference(JSWrapper.class, "isJava", Object.class, boolean.class),
wrapperGenerator); wrapperGenerator);
jsHost.add(new MethodReference(JSWrapper.class, "isJava", JSObject.class, boolean.class), jsHost.add(new MethodReference(JSWrapper.class, "isJava", JSObject.class, boolean.class),

View File

@ -49,6 +49,7 @@ import org.teavm.model.MethodReference;
import org.teavm.model.Program; import org.teavm.model.Program;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
import org.teavm.model.Variable; import org.teavm.model.Variable;
import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.ExitInstruction; import org.teavm.model.instructions.ExitInstruction;
import org.teavm.model.instructions.IntegerConstantInstruction; import org.teavm.model.instructions.IntegerConstantInstruction;
import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvocationType;
@ -61,6 +62,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
private ClassHierarchy hierarchy; private ClassHierarchy hierarchy;
private Map<String, ExposedClass> exposedClasses = new HashMap<>(); private Map<String, ExposedClass> exposedClasses = new HashMap<>();
private Predicate<String> classFilter = n -> true; private Predicate<String> classFilter = n -> true;
private boolean wasmGC;
JSObjectClassTransformer(JSBodyRepository repository) { JSObjectClassTransformer(JSBodyRepository repository) {
this.repository = repository; this.repository = repository;
@ -70,6 +72,10 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
this.classFilter = classFilter; this.classFilter = classFilter;
} }
void forWasmGC() {
wasmGC = true;
}
@Override @Override
public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) { public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) {
this.hierarchy = context.getHierarchy(); this.hierarchy = context.getHierarchy();
@ -77,6 +83,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
typeHelper = new JSTypeHelper(hierarchy.getClassSource()); typeHelper = new JSTypeHelper(hierarchy.getClassSource());
processor = new JSClassProcessor(hierarchy.getClassSource(), typeHelper, repository, processor = new JSClassProcessor(hierarchy.getClassSource(), typeHelper, repository,
context.getDiagnostics(), context.getIncrementalCache(), context.isStrict()); context.getDiagnostics(), context.getIncrementalCache(), context.isStrict());
processor.setWasmGC(wasmGC);
processor.setClassFilter(classFilter); processor.setClassFilter(classFilter);
} }
processor.processClass(cls); processor.processClass(cls);
@ -114,6 +121,21 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
exposeMethods(cls, exposedClass, context.getDiagnostics(), functorMethod); exposeMethods(cls, exposedClass, context.getDiagnostics(), functorMethod);
exportStaticMethods(cls, context.getDiagnostics()); 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, private void exposeMethods(ClassHolder classHolder, ExposedClass classToExpose, Diagnostics diagnostics,
@ -129,14 +151,20 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
if (export.vararg) { if (export.vararg) {
--paramCount; --paramCount;
} }
var exportedMethodSignature = new ValueType[paramCount + 1]; var exportedMethodSignature = new ValueType[paramCount + 2];
Arrays.fill(exportedMethodSignature, JSMethods.JS_OBJECT); 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++, MethodDescriptor exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++,
exportedMethodSignature); exportedMethodSignature);
MethodHolder exportedMethod = new MethodHolder(exportedMethodDesc); MethodHolder exportedMethod = new MethodHolder(exportedMethodDesc);
exportedMethod.getModifiers().add(ElementModifier.STATIC);
exportedMethod.getAnnotations().add(new AnnotationHolder(JSInstanceExpose.class.getName()));
Program program = new Program(); Program program = new Program();
exportedMethod.setProgram(program); exportedMethod.setProgram(program);
program.createVariable(); program.createVariable();
program.createVariable();
BasicBlock basicBlock = program.createBasicBlock(); BasicBlock basicBlock = program.createBasicBlock();
List<Instruction> marshallInstructions = new ArrayList<>(); List<Instruction> marshallInstructions = new ArrayList<>();
@ -161,9 +189,24 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
basicBlock.addAll(marshallInstructions); basicBlock.addAll(marshallInstructions);
marshallInstructions.clear(); 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(); InvokeInstruction invocation = new InvokeInstruction();
invocation.setType(InvocationType.VIRTUAL); invocation.setType(method.getName().equals("<init>") ? InvocationType.SPECIAL : InvocationType.VIRTUAL);
invocation.setInstance(program.variableAt(0)); invocation.setInstance(castInstance.getReceiver());
invocation.setMethod(methodRef); invocation.setMethod(methodRef);
invocation.setArguments(variablesToPass); invocation.setArguments(variablesToPass);
basicBlock.add(invocation); basicBlock.add(invocation);

View File

@ -24,12 +24,12 @@ import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier; import org.teavm.model.ElementModifier;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
class JSTypeHelper { public class JSTypeHelper {
private ClassReaderSource classSource; private ClassReaderSource classSource;
private Map<String, Boolean> knownJavaScriptClasses = new HashMap<>(); private Map<String, Boolean> knownJavaScriptClasses = new HashMap<>();
private Map<String, Boolean> knownJavaScriptImplementations = new HashMap<>(); private Map<String, Boolean> knownJavaScriptImplementations = new HashMap<>();
JSTypeHelper(ClassReaderSource classSource) { public JSTypeHelper(ClassReaderSource classSource) {
this.classSource = classSource; this.classSource = classSource;
knownJavaScriptClasses.put(JSObject.class.getName(), true); knownJavaScriptClasses.put(JSObject.class.getName(), true);
} }

View File

@ -26,11 +26,14 @@ import org.teavm.model.instructions.InvocationType;
class JSTypeInference extends BaseTypeInference<JSType> { class JSTypeInference extends BaseTypeInference<JSType> {
private JSTypeHelper typeHelper; private JSTypeHelper typeHelper;
private ClassReaderSource classes; 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); super(program, reference);
this.typeHelper = typeHelper; this.typeHelper = typeHelper;
this.classes = classes; this.classes = classes;
this.wasmGC = wasmGC;
} }
@Override @Override
@ -93,7 +96,7 @@ class JSTypeInference extends BaseTypeInference<JSType> {
if (!methodRef.getReturnType().isObject(Object.class)) { if (!methodRef.getReturnType().isObject(Object.class)) {
return mapType(methodRef.getReturnType()); return mapType(methodRef.getReturnType());
} }
return isJsMethod(methodRef) ? JSType.MIXED : JSType.JAVA; return !wasmGC && isJsMethod(methodRef) ? JSType.MIXED : JSType.JAVA;
} }
private boolean isJsMethod(MethodReference methodRef) { private boolean isJsMethod(MethodReference methodRef) {

View File

@ -87,6 +87,16 @@ class JSValueMarshaller {
diagnostics.error(location, "Wrong functor: {{c0}}", type.getName()); diagnostics.error(location, "Wrong functor: {{c0}}", type.getName());
return var; 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() String name = type.getMethods().stream()
.filter(method -> method.hasModifier(ElementModifier.ABSTRACT)) .filter(method -> method.hasModifier(ElementModifier.ABSTRACT))
.findFirst().get().getName(); .findFirst().get().getName();
@ -142,6 +152,16 @@ class JSValueMarshaller {
replacement.add(unwrapNative); replacement.add(unwrapNative);
return unwrapNative.getReceiver(); 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; return var;
} }
} }

View File

@ -15,6 +15,7 @@
*/ */
package org.teavm.jso.impl; package org.teavm.jso.impl;
import org.teavm.interop.Import;
import org.teavm.interop.NoSideEffects; import org.teavm.interop.NoSideEffects;
import org.teavm.jso.JSBody; import org.teavm.jso.JSBody;
import org.teavm.jso.JSClass; import org.teavm.jso.JSClass;
@ -30,60 +31,61 @@ import org.teavm.jso.core.JSWeakMap;
import org.teavm.jso.core.JSWeakRef; import org.teavm.jso.core.JSWeakRef;
public final class JSWrapper { public final class JSWrapper {
private static final JSWeakMap<JSObject, JSTransparentInt> hashCodes = new JSWeakMap<>(); private static class Helper {
private static final JSWeakMap<JSObject, JSWeakRef<JSObject>> wrappers = JSWeakRef.isSupported() private static final JSWeakMap<JSObject, JSTransparentInt> hashCodes = new JSWeakMap<>();
? new JSWeakMap<>() : null; private static final JSWeakMap<JSObject, JSWeakRef<JSObject>> wrappers = JSWeakRef.isSupported()
private static final JSMap<JSString, JSWeakRef<JSObject>> stringWrappers = JSWeakRef.isSupported() ? new JSWeakMap<>() : null;
? new JSMap<>() : null; private static final JSMap<JSString, JSWeakRef<JSObject>> stringWrappers = JSWeakRef.isSupported()
private static final JSMap<JSNumber, JSWeakRef<JSObject>> numberWrappers = JSWeakRef.isSupported() ? new JSMap<>() : null;
? new JSMap<>() : null; private static final JSMap<JSNumber, JSWeakRef<JSObject>> numberWrappers = JSWeakRef.isSupported()
private static JSWeakRef<JSObject> undefinedWrapper; ? new JSMap<>() : null;
private static final JSFinalizationRegistry stringFinalizationRegistry; private static JSWeakRef<JSObject> undefinedWrapper;
private static final JSFinalizationRegistry numberFinalizationRegistry; private static JSFinalizationRegistry stringFinalizationRegistry;
private static int hashCodeGen; 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; 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) { private JSWrapper(JSObject js) {
this.js = js; this.js = js;
} }
public static Object wrap(Object o) { public static Object wrap(JSObject o) {
if (o == null) { if (o == null) {
return null; return null;
} }
var js = directJavaToJs(o); var type = JSObjects.typeOf(o);
var type = JSObjects.typeOf(js);
var isObject = type.equals("object") || type.equals("function"); var isObject = type.equals("object") || type.equals("function");
if (isObject && isJSImplementation(o)) { var wrappers = Helper.wrappers;
return o;
}
if (wrappers != null) { if (wrappers != null) {
if (isObject) { if (isObject) {
var existingRef = get(wrappers, js); var existingRef = get(wrappers, o);
var existing = !isUndefined(existingRef) ? deref(existingRef) : JSUndefined.instance(); var existing = !isUndefined(existingRef) ? deref(existingRef) : JSUndefined.instance();
if (isUndefined(existing)) { if (isUndefined(existing)) {
var wrapper = new JSWrapper(js); var wrapper = new JSWrapper(o);
set(wrappers, js, createWeakRef(wrapperToJs(wrapper))); set(wrappers, o, createWeakRef(wrapperToJs(wrapper)));
return wrapper; return wrapper;
} else { } else {
return jsToWrapper(existing); return jsToWrapper(existing);
} }
} else if (type.equals("string")) { } 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 existingRef = get(stringWrappers, jsString);
var existing = !isUndefined(existingRef) ? deref(existingRef) : JSUndefined.instance(); var existing = !isUndefined(existingRef) ? deref(existingRef) : JSUndefined.instance();
if (isUndefined(existing)) { if (isUndefined(existing)) {
var wrapper = new JSWrapper(js); var wrapper = new JSWrapper(o);
var wrapperAsJs = wrapperToJs(wrapper); var wrapperAsJs = wrapperToJs(wrapper);
set(stringWrappers, jsString, createWeakRef(wrapperAsJs)); set(stringWrappers, jsString, createWeakRef(wrapperAsJs));
register(stringFinalizationRegistry, wrapperAsJs, jsString); register(stringFinalizationRegistry, wrapperAsJs, jsString);
@ -92,11 +94,13 @@ public final class JSWrapper {
return jsToWrapper(existing); return jsToWrapper(existing);
} }
} else if (type.equals("number")) { } 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 existingRef = get(numberWrappers, jsNumber);
var existing = !isUndefined(existingRef) ? deref(existingRef) : JSUndefined.instance(); var existing = !isUndefined(existingRef) ? deref(existingRef) : JSUndefined.instance();
if (isUndefined(existing)) { if (isUndefined(existing)) {
var wrapper = new JSWrapper(js); var wrapper = new JSWrapper(o);
var wrapperAsJs = wrapperToJs(wrapper); var wrapperAsJs = wrapperToJs(wrapper);
set(numberWrappers, jsNumber, createWeakRef(wrapperAsJs)); set(numberWrappers, jsNumber, createWeakRef(wrapperAsJs));
register(numberFinalizationRegistry, wrapperAsJs, jsNumber); register(numberFinalizationRegistry, wrapperAsJs, jsNumber);
@ -105,19 +109,19 @@ public final class JSWrapper {
return jsToWrapper(existing); return jsToWrapper(existing);
} }
} else if (type.equals("undefined")) { } else if (type.equals("undefined")) {
var existingRef = undefinedWrapper; var existingRef = Helper.undefinedWrapper;
var existing = existingRef != null ? deref(existingRef) : JSUndefined.instance(); var existing = existingRef != null ? deref(existingRef) : JSUndefined.instance();
if (isUndefined(existing)) { if (isUndefined(existing)) {
var wrapper = new JSWrapper(js); var wrapper = new JSWrapper(o);
var wrapperAsJs = wrapperToJs(wrapper); var wrapperAsJs = wrapperToJs(wrapper);
undefinedWrapper = createWeakRef(wrapperAsJs); Helper.undefinedWrapper = createWeakRef(wrapperAsJs);
return wrapper; return wrapper;
} else { } else {
return jsToWrapper(existing); return jsToWrapper(existing);
} }
} }
} }
return new JSWrapper(js); return new JSWrapper(o);
} }
@JSBody(params = "target", script = "return new WeakRef(target);") @JSBody(params = "target", script = "return new WeakRef(target);")
@ -152,14 +156,18 @@ public final class JSWrapper {
@NoSideEffects @NoSideEffects
public static Object maybeWrap(Object o) { public static Object maybeWrap(Object o) {
return o == null || isJava(o) ? o : wrap(o); return o == null || isJava(o) ? o : wrap(directJavaToJs(o));
} }
@NoSideEffects @NoSideEffects
public static native JSObject directJavaToJs(Object obj); public static native JSObject directJavaToJs(Object obj);
@NoSideEffects @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 @NoSideEffects
public static native JSObject dependencyJavaToJs(Object obj); public static native JSObject dependencyJavaToJs(Object obj);
@ -186,14 +194,14 @@ public final class JSWrapper {
if (o == null) { if (o == null) {
return 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) { public static JSObject maybeUnwrap(Object o) {
if (o == null) { if (o == null) {
return null; return null;
} }
return isJava(o) ? unwrap(o) : directJavaToJs(o); return isJava(o) ? unwrap(o) : marshallJavaToJs(o);
} }
public static JSObject javaToJs(Object o) { public static JSObject javaToJs(Object o) {
@ -207,7 +215,7 @@ public final class JSWrapper {
if (o == null) { if (o == null) {
return null; return null;
} }
return !isJava(o) ? wrap(directJsToJava(o)) : dependencyJsToJava(o); return !isJava(o) ? wrap(o) : dependencyJsToJava(o);
} }
public static boolean isJs(Object o) { public static boolean isJs(Object o) {
@ -229,10 +237,10 @@ public final class JSWrapper {
public int hashCode() { public int hashCode() {
var type = JSObjects.typeOf(js); var type = JSObjects.typeOf(js);
if (type.equals("object") || type.equals("symbol") || type.equals("function")) { if (type.equals("object") || type.equals("symbol") || type.equals("function")) {
var code = hashCodes.get(js); var code = Helper.hashCodes.get(js);
if (isUndefined(code)) { if (isUndefined(code)) {
code = JSTransparentInt.valueOf(++hashCodeGen); code = JSTransparentInt.valueOf(++Helper.hashCodeGen);
hashCodes.set(js, code); Helper.hashCodes.set(js, code);
} }
return code.intValue(); return code.intValue();
} else if (type.equals("number")) { } else if (type.equals("number")) {

View File

@ -15,33 +15,43 @@
*/ */
package org.teavm.jso.impl; package org.teavm.jso.impl;
import org.teavm.dependency.AbstractDependencyListener;
import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyAgent;
import org.teavm.dependency.DependencyNode; import org.teavm.dependency.DependencyNode;
import org.teavm.dependency.DependencyPlugin;
import org.teavm.dependency.MethodDependency; import org.teavm.dependency.MethodDependency;
public class JSWrapperDependency implements DependencyPlugin { public class JSWrapperDependency extends AbstractDependencyListener {
private DependencyNode externalClassesNode; private DependencyNode externalClassesNode;
@Override @Override
public void methodReached(DependencyAgent agent, MethodDependency method) { public void started(DependencyAgent agent) {
switch (method.getMethod().getName()) { externalClassesNode = agent.createNode();
case "jsToWrapper": }
method.getResult().propagate(agent.getType(JSWrapper.class.getName()));
break; @Override
case "dependencyJavaToJs": public void classReached(DependencyAgent agent, String className) {
method.getVariable(1).connect(getExternalClassesNode(agent)); var cls = agent.getClassSource().get(className);
break; if (cls.getAnnotations().get(JSClassToExpose.class.getName()) != null) {
case "dependencyJsToJava": externalClassesNode.propagate(agent.getType(className));
getExternalClassesNode(agent).connect(method.getResult());
break;
} }
} }
private DependencyNode getExternalClassesNode(DependencyAgent agent) { @Override
if (externalClassesNode == null) { public void methodReached(DependencyAgent agent, MethodDependency method) {
externalClassesNode = agent.createNode(); 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;
} }
} }

View File

@ -34,6 +34,8 @@ public class JSWrapperGenerator implements Injector, DependencyPlugin {
case "directJsToJava": case "directJsToJava":
case "dependencyJavaToJs": case "dependencyJavaToJs":
case "dependencyJsToJava": case "dependencyJsToJava":
case "marshallJavaToJs":
case "unmarshallJavaFromJs":
case "wrapperToJs": case "wrapperToJs":
case "jsToWrapper": case "jsToWrapper":
context.writeExpr(context.getArgument(0), context.getPrecedence()); context.writeExpr(context.getArgument(0), context.getPrecedence());

View File

@ -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.intrinsics.gc.WasmGCIntrinsicContext;
import org.teavm.backend.wasm.model.WasmGlobal; import org.teavm.backend.wasm.model.WasmGlobal;
import org.teavm.backend.wasm.model.expression.WasmCall; 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.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmGetGlobal; import org.teavm.backend.wasm.model.expression.WasmGetGlobal;
import org.teavm.jso.impl.JSBodyEmitter; import org.teavm.jso.impl.JSBodyEmitter;
import org.teavm.model.ValueType;
class WasmGCBodyIntrinsic implements WasmGCIntrinsic { class WasmGCBodyIntrinsic implements WasmGCIntrinsic {
private JSBodyEmitter emitter; private JSBodyEmitter emitter;
private boolean inlined; private boolean inlined;
private WasmGCBodyGenerator bodyGenerator; private WasmGCJsoCommonGenerator commonGen;
private WasmGlobal global; private WasmGlobal global;
private WasmGCJSFunctions jsFunctions; private WasmGCJSFunctions jsFunctions;
WasmGCBodyIntrinsic(JSBodyEmitter emitter, boolean inlined, WasmGCBodyGenerator bodyGenerator, WasmGCBodyIntrinsic(JSBodyEmitter emitter, boolean inlined, WasmGCJsoCommonGenerator commonGen,
WasmGCJSFunctions jsFunctions) { WasmGCJSFunctions jsFunctions) {
this.emitter = emitter; this.emitter = emitter;
this.inlined = inlined; this.inlined = inlined;
this.bodyGenerator = bodyGenerator; this.commonGen = commonGen;
this.jsFunctions = jsFunctions; this.jsFunctions = jsFunctions;
} }
@Override @Override
public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) { public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) {
var jsoContext = WasmGCJsoContext.wrap(context);
if (global == null) { 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)); call.getArguments().add(new WasmGetGlobal(global));
for (var arg : invocation.getArguments()) { for (var arg : invocation.getArguments()) {
call.getArguments().add(context.generate(arg)); call.getArguments().add(context.generate(arg));
} }
return call; WasmExpression result = call;
if (invocation.getMethod().getReturnType() == ValueType.VOID) {
result = new WasmDrop(result);
}
return result;
} }
} }

View File

@ -24,12 +24,13 @@ import org.teavm.model.MethodReference;
class WasmGCJSBodyRenderer implements WasmGCIntrinsicFactory { class WasmGCJSBodyRenderer implements WasmGCIntrinsicFactory {
private JSBodyRepository repository; private JSBodyRepository repository;
private WasmGCJSFunctions jsFunctions; private WasmGCJSFunctions jsFunctions;
private WasmGCBodyGenerator bodyGenerator; private WasmGCJsoCommonGenerator commonGen;
WasmGCJSBodyRenderer(JSBodyRepository repository) { WasmGCJSBodyRenderer(JSBodyRepository repository, WasmGCJSFunctions jsFunctions,
WasmGCJsoCommonGenerator commonGen) {
this.repository = repository; this.repository = repository;
jsFunctions = new WasmGCJSFunctions(); this.jsFunctions = jsFunctions;
bodyGenerator = new WasmGCBodyGenerator(jsFunctions); this.commonGen = commonGen;
} }
@Override @Override
@ -39,6 +40,6 @@ class WasmGCJSBodyRenderer implements WasmGCIntrinsicFactory {
return null; return null;
} }
var inlined = repository.inlineMethods.contains(emitter.method()); var inlined = repository.inlineMethods.contains(emitter.method());
return new WasmGCBodyIntrinsic(emitter, inlined, bodyGenerator, jsFunctions); return new WasmGCBodyIntrinsic(emitter, inlined, commonGen, jsFunctions);
} }
} }

View File

@ -16,7 +16,6 @@
package org.teavm.jso.impl.wasmgc; package org.teavm.jso.impl.wasmgc;
import java.util.Arrays; 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.WasmFunction;
import org.teavm.backend.wasm.model.WasmType; import org.teavm.backend.wasm.model.WasmType;
@ -24,7 +23,7 @@ class WasmGCJSFunctions {
private WasmFunction[] constructors = new WasmFunction[32]; private WasmFunction[] constructors = new WasmFunction[32];
private WasmFunction[] callers = 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]; var function = constructors[index];
if (function == null) { if (function == null) {
var extern = WasmType.SpecialReferenceKind.EXTERN.asNonNullType(); var extern = WasmType.SpecialReferenceKind.EXTERN.asNonNullType();
@ -41,7 +40,7 @@ class WasmGCJSFunctions {
return function; return function;
} }
WasmFunction getFunctionCaller(WasmGCIntrinsicContext context, int index) { WasmFunction getFunctionCaller(WasmGCJsoContext context, int index) {
var function = callers[index]; var function = callers[index];
if (function == null) { if (function == null) {
var paramTypes = new WasmType[index + 1]; var paramTypes = new WasmType[index + 1];

View File

@ -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.WasmGCCustomTypeMapperFactory;
import org.teavm.backend.wasm.generate.gc.classes.WasmGCCustomTypeMapperFactoryContext; import org.teavm.backend.wasm.generate.gc.classes.WasmGCCustomTypeMapperFactoryContext;
import org.teavm.backend.wasm.model.WasmType; import org.teavm.backend.wasm.model.WasmType;
import org.teavm.jso.JSObject; import org.teavm.jso.impl.JSTypeHelper;
import org.teavm.jso.core.JSArray;
class WasmGCJSTypeMapper implements WasmGCCustomTypeMapper, WasmGCCustomTypeMapperFactory { class WasmGCJSTypeMapper implements WasmGCCustomTypeMapper, WasmGCCustomTypeMapperFactory {
private JSTypeHelper typeHelper;
@Override @Override
public WasmType map(String className) { public WasmType map(String className) {
if (className.equals(JSObject.class.getName()) if (typeHelper.isJavaScriptClass(className)) {
|| className.equals(JSArray.class.getName())) {
return WasmType.Reference.EXTERN; return WasmType.Reference.EXTERN;
} }
return null; return null;
@ -34,6 +34,7 @@ class WasmGCJSTypeMapper implements WasmGCCustomTypeMapper, WasmGCCustomTypeMapp
@Override @Override
public WasmGCCustomTypeMapper createTypeMapper(WasmGCCustomTypeMapperFactoryContext context) { public WasmGCCustomTypeMapper createTypeMapper(WasmGCCustomTypeMapperFactoryContext context) {
this.typeHelper = new JSTypeHelper(context.originalClasses());
return this; return this;
} }
} }

View File

@ -0,0 +1,54 @@
/*
* Copyright 2024 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.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();
}
}

View File

@ -28,8 +28,12 @@ public final class WasmGCJso {
public static void install(TeaVMHost host, TeaVMWasmGCHost wasmGCHost, JSBodyRepository jsBodyRepository) { public static void install(TeaVMHost host, TeaVMWasmGCHost wasmGCHost, JSBodyRepository jsBodyRepository) {
host.add(new WasmGCJSDependencies()); host.add(new WasmGCJSDependencies());
host.add(new WasmGCJSWrapperTransformer());
var jsFunctions = new WasmGCJSFunctions();
var commonGen = new WasmGCJsoCommonGenerator(jsFunctions);
wasmGCHost.addCustomTypeMapperFactory(new WasmGCJSTypeMapper()); 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(); var jsIntrinsic = new WasmGCJSIntrinsic();
wasmGCHost.addIntrinsic(new MethodReference(JS.class, "wrap", String.class, JSObject.class), jsIntrinsic); wasmGCHost.addIntrinsic(new MethodReference(JS.class, "wrap", String.class, JSObject.class), jsIntrinsic);

View File

@ -20,11 +20,11 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.teavm.backend.javascript.rendering.AstWriter; 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.WasmFunction;
import org.teavm.backend.wasm.model.WasmGlobal; import org.teavm.backend.wasm.model.WasmGlobal;
import org.teavm.backend.wasm.model.WasmType; import org.teavm.backend.wasm.model.WasmType;
import org.teavm.backend.wasm.model.expression.WasmCall; 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.WasmGetGlobal;
import org.teavm.backend.wasm.model.expression.WasmNullConstant; import org.teavm.backend.wasm.model.expression.WasmNullConstant;
import org.teavm.backend.wasm.model.expression.WasmSetGlobal; 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.JSBodyBloatedEmitter;
import org.teavm.jso.impl.JSBodyEmitter; import org.teavm.jso.impl.JSBodyEmitter;
class WasmGCBodyGenerator { class WasmGCJsoCommonGenerator {
private WasmGCJSFunctions jsFunctions; private WasmGCJSFunctions jsFunctions;
private boolean initialized; private boolean initialized;
private List<Consumer<WasmFunction>> initializerParts = new ArrayList<>(); private List<Consumer<WasmFunction>> initializerParts = new ArrayList<>();
WasmGCBodyGenerator(WasmGCJSFunctions jsFunctions) { WasmGCJsoCommonGenerator(WasmGCJSFunctions jsFunctions) {
this.jsFunctions = jsFunctions; this.jsFunctions = jsFunctions;
} }
private void initialize(WasmGCIntrinsicContext context) { private void initialize(WasmGCJsoContext context) {
if (initialized) { if (initialized) {
return; return;
} }
@ -49,14 +49,17 @@ class WasmGCBodyGenerator {
context.addToInitializer(this::writeToInitializer); context.addToInitializer(this::writeToInitializer);
} }
private void writeToInitializer(WasmFunction function) { private void writeToInitializer(WasmFunction function) {
for (var part : initializerParts) { for (var part : initializerParts) {
part.accept(function); part.accept(function);
} }
} }
WasmGlobal addBody(WasmGCIntrinsicContext context, JSBodyEmitter emitter, boolean inlined) { void addInitializerPart(Consumer<WasmFunction> part) {
initializerParts.add(part);
}
WasmGlobal addJSBody(WasmGCJsoContext context, JSBodyEmitter emitter, boolean inlined) {
initialize(context); initialize(context);
var paramCount = emitter.method().parameterCount(); var paramCount = emitter.method().parameterCount();
if (!emitter.isStatic()) { if (!emitter.isStatic()) {
@ -87,9 +90,7 @@ class WasmGCBodyGenerator {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
var constructor = new WasmCall(jsFunctions.getFunctionConstructor(context, var constructor = new WasmCall(jsFunctions.getFunctionConstructor(context, paramCount));
paramCount));
var stringToJs = context.functions().forStaticMethod(STRING_TO_JS);
var paramNames = new ArrayList<String>(); var paramNames = new ArrayList<String>();
if (!emitter.isStatic()) { if (!emitter.isStatic()) {
paramNames.add("__this__"); paramNames.add("__this__");
@ -97,12 +98,20 @@ class WasmGCBodyGenerator {
paramNames.addAll(List.of(emitter.parameterNames())); paramNames.addAll(List.of(emitter.parameterNames()));
for (var parameter : paramNames) { for (var parameter : paramNames) {
var paramName = new WasmGetGlobal(context.strings().getStringConstant(parameter).global); 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); 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))); initializerParts.add(initializer -> initializer.getBody().add(new WasmSetGlobal(global, constructor)));
return global; 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);
}
} }

View File

@ -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<WasmFunction> 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<WasmFunction> 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<WasmFunction> initializerContributor) {
context.addToInitializer(initializerContributor);
}
};
}
}

View File

@ -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<WasmExpression>();
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;
}
}

View File

@ -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;
}
}

View File

@ -29,7 +29,7 @@ import org.teavm.junit.TestPlatform;
@RunWith(TeaVMTestRunner.class) @RunWith(TeaVMTestRunner.class)
@SkipJVM @SkipJVM
@OnlyPlatform(TestPlatform.JAVASCRIPT) @OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC})
@EachTestCompiledSeparately @EachTestCompiledSeparately
public class ExportClassTest { public class ExportClassTest {
@Test @Test

View File

@ -65,7 +65,7 @@ val generateLibJs by tasks.register<JavaExec>("generateLibJs") {
} }
val zipWithJs by tasks.register<Jar>("zipWithJs") { val zipWithJs by tasks.register<Jar>("zipWithJs") {
dependsOn(generateJs, generateLibJs) //dependsOn(generateJs, generateLibJs)
archiveClassifier = "js" archiveClassifier = "js"
from(layout.buildDirectory.dir("teavm"), layout.buildDirectory.dir("teavm-lib")) from(layout.buildDirectory.dir("teavm"), layout.buildDirectory.dir("teavm-lib"))
entryCompression = ZipEntryCompression.DEFLATED entryCompression = ZipEntryCompression.DEFLATED