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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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.interop.PlatformMarker;
import org.teavm.model.AnnotationReader;
import org.teavm.model.BasicBlock;
import org.teavm.model.CallLocation;
import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassHolder;
@ -52,8 +51,6 @@ import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldReader;
import org.teavm.model.FieldReference;
import org.teavm.model.Instruction;
import org.teavm.model.InvokeDynamicInstruction;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader;
@ -61,12 +58,7 @@ import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.ReferenceCache;
import org.teavm.model.ValueType;
import org.teavm.model.emit.ProgramEmitter;
import org.teavm.model.emit.ValueEmitter;
import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.NullConstantInstruction;
import org.teavm.model.optimization.UnreachableBasicBlockEliminator;
import org.teavm.model.util.BasicBlockSplitter;
import org.teavm.model.util.ModelUtils;
import org.teavm.model.util.ProgramUtils;
import org.teavm.parsing.Parser;
@ -102,7 +94,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
private Diagnostics diagnostics;
DefaultCallGraph callGraph = new DefaultCallGraph();
private DependencyAgent agent;
Map<MethodReference, BootstrapMethodSubstitutor> bootstrapMethodSubstitutors = new HashMap<>();
Map<MethodReference, DependencyPlugin> dependencyPlugins = new HashMap<>();
private boolean completing;
private Map<String, DependencyTypeFilter> superClassFilters = new HashMap<>();
@ -119,7 +110,8 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
this.unprocessedClassSource = classSource;
this.diagnostics = diagnostics;
this.referenceCache = referenceCache;
this.classSource = new DependencyClassSource(classSource, diagnostics, incrementalCache, platformTags);
agent = new DependencyAgent(this);
this.classSource = new DependencyClassSource(agent, classSource, diagnostics, incrementalCache, platformTags);
agentClassSource = this.classSource;
classHierarchy = new ClassHierarchy(this.classSource);
this.classLoader = classLoader;
@ -138,7 +130,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
classCache = new CachedFunction<>(this::createClassDependency);
agent = new DependencyAgent(this);
classType = getType("java.lang.Class");
}
@ -274,7 +265,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
dep.used = false;
lock(dep, false);
deferredTasks.add(() -> {
processInvokeDynamic(dep);
classSource.getReferenceResolver().use(dep.method.getReference(), diagnostics);
processMethod(dep);
dep.used = true;
@ -489,7 +479,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
void scheduleMethodAnalysis(MethodDependency dep) {
classSource.getReferenceResolver().use(dep.getReference(), diagnostics);
deferredTasks.add(() -> {
processInvokeDynamic(dep);
classSource.getReferenceResolver().use(dep.getReference(), diagnostics);
processMethod(dep);
});
@ -755,6 +744,7 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
agent.cleanup();
listeners.clear();
classSource.dispose();
agentClassSource = classSourcePacker.pack(classSource,
ClassClosureAnalyzer.build(classSource, new ArrayList<>(classSource.cache.keySet())));
if (classSource != agentClassSource) {
@ -762,7 +752,7 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
generatedClassNames.addAll(classSource.getGeneratedClassNames());
}
classSource.innerHierarchy = null;
classSource.dispose();
classSource = null;
methodReaderCache = null;
}
@ -827,7 +817,7 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
}
public void addBootstrapMethodSubstitutor(MethodReference method, BootstrapMethodSubstitutor substitutor) {
bootstrapMethodSubstitutors.put(method, substitutor);
classSource.bootstrapMethodSubstitutors.put(method, substitutor);
}
public void addDependencyPlugin(MethodReference method, DependencyPlugin dependencyPlugin) {
@ -863,70 +853,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
return result;
}
private void processInvokeDynamic(MethodDependency methodDep) {
if (methodDep.method == null) {
return;
}
Program program = methodDep.method.getProgram();
if (program == null) {
return;
}
ProgramEmitter pe = ProgramEmitter.create(program, classHierarchy);
BasicBlockSplitter splitter = new BasicBlockSplitter(program);
for (int i = 0; i < program.basicBlockCount(); ++i) {
BasicBlock block = program.basicBlockAt(i);
for (Instruction insn : block) {
if (!(insn instanceof InvokeDynamicInstruction)) {
continue;
}
block = insn.getBasicBlock();
InvokeDynamicInstruction indy = (InvokeDynamicInstruction) insn;
MethodReference bootstrapMethod = new MethodReference(indy.getBootstrapMethod().getClassName(),
indy.getBootstrapMethod().getName(), indy.getBootstrapMethod().signature());
BootstrapMethodSubstitutor substitutor = bootstrapMethodSubstitutors.get(bootstrapMethod);
if (substitutor == null) {
NullConstantInstruction nullInsn = new NullConstantInstruction();
nullInsn.setReceiver(indy.getReceiver());
nullInsn.setLocation(indy.getLocation());
insn.replace(nullInsn);
CallLocation location = new CallLocation(methodDep.getReference(), insn.getLocation());
diagnostics.error(location, "Substitutor for bootstrap method {{m0}} was not found",
bootstrapMethod);
continue;
}
BasicBlock splitBlock = splitter.split(block, insn);
pe.enter(block);
pe.setCurrentLocation(indy.getLocation());
insn.delete();
List<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 {
private final String[] emptyArray = new String[0];

View File

@ -17,12 +17,15 @@ package org.teavm.dependency;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.teavm.cache.IncrementalDependencyRegistration;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.BasicBlock;
import org.teavm.model.CallLocation;
import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderSource;
@ -30,12 +33,23 @@ import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassHolderTransformerContext;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.Instruction;
import org.teavm.model.InvokeDynamicInstruction;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.emit.ProgramEmitter;
import org.teavm.model.emit.ValueEmitter;
import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.NullConstantInstruction;
import org.teavm.model.optimization.UnreachableBasicBlockEliminator;
import org.teavm.model.transformation.ClassInitInsertion;
import org.teavm.model.util.BasicBlockSplitter;
import org.teavm.model.util.ModelUtils;
class DependencyClassSource implements ClassHolderSource {
private DependencyAgent agent;
private ClassReaderSource innerSource;
ClassHierarchy innerHierarchy;
private Diagnostics diagnostics;
@ -48,9 +62,12 @@ class DependencyClassSource implements ClassHolderSource {
private ReferenceResolver referenceResolver;
private ClassInitInsertion classInitInsertion;
private String entryPoint;
Map<MethodReference, BootstrapMethodSubstitutor> bootstrapMethodSubstitutors = new HashMap<>();
private boolean disposed;
DependencyClassSource(ClassReaderSource innerSource, Diagnostics diagnostics,
DependencyClassSource(DependencyAgent agent, ClassReaderSource innerSource, Diagnostics diagnostics,
IncrementalDependencyRegistration dependencyRegistration, String[] platformTags) {
this.agent = agent;
this.innerSource = innerSource;
this.diagnostics = diagnostics;
innerHierarchy = new ClassHierarchy(innerSource);
@ -65,7 +82,16 @@ class DependencyClassSource implements ClassHolderSource {
@Override
public ClassHolder get(String name) {
return cache.computeIfAbsent(name, n -> Optional.ofNullable(findAndTransformClass(n))).orElse(null);
var result = cache.get(name);
if (result == null) {
var cls = findClass(name);
result = Optional.ofNullable(cls);
cache.put(name, result);
if (cls != null) {
transformClass(cls);
}
}
return result.orElse(null);
}
public void submit(ClassHolder cls) {
@ -84,26 +110,27 @@ class DependencyClassSource implements ClassHolderSource {
cache.remove(cls.getName());
}
private ClassHolder findAndTransformClass(String name) {
var cls = findClass(name);
if (cls != null) {
if (!transformers.isEmpty()) {
for (var transformer : transformers) {
transformer.transformClass(cls, transformContext);
}
}
private void transformClass(ClassHolder cls) {
if (!disposed) {
for (var method : cls.getMethods()) {
if (method.getProgram() != null) {
var program = method.getProgram();
method.setProgramSupplier(m -> {
referenceResolver.resolve(m, program);
classInitInsertion.apply(m, program);
return program;
});
}
processInvokeDynamic(method);
}
}
if (!transformers.isEmpty()) {
for (var transformer : transformers) {
transformer.transformClass(cls, transformContext);
}
}
for (var method : cls.getMethods()) {
if (method.getProgram() != null) {
var program = method.getProgram();
method.setProgramSupplier(m -> {
referenceResolver.resolve(m, program);
classInitInsertion.apply(m, program);
return program;
});
}
}
return cls;
}
private ClassHolder findClass(String name) {
@ -132,6 +159,69 @@ class DependencyClassSource implements ClassHolderSource {
public void dispose() {
transformers.clear();
bootstrapMethodSubstitutors.clear();
disposed = true;
}
private void processInvokeDynamic(MethodHolder method) {
Program program = method.getProgram();
if (program == null) {
return;
}
ProgramEmitter pe = ProgramEmitter.create(program, innerHierarchy);
BasicBlockSplitter splitter = new BasicBlockSplitter(program);
for (int i = 0; i < program.basicBlockCount(); ++i) {
BasicBlock block = program.basicBlockAt(i);
for (Instruction insn : block) {
if (!(insn instanceof InvokeDynamicInstruction)) {
continue;
}
block = insn.getBasicBlock();
InvokeDynamicInstruction indy = (InvokeDynamicInstruction) insn;
MethodReference bootstrapMethod = new MethodReference(indy.getBootstrapMethod().getClassName(),
indy.getBootstrapMethod().getName(), indy.getBootstrapMethod().signature());
BootstrapMethodSubstitutor substitutor = bootstrapMethodSubstitutors.get(bootstrapMethod);
if (substitutor == null) {
NullConstantInstruction nullInsn = new NullConstantInstruction();
nullInsn.setReceiver(indy.getReceiver());
nullInsn.setLocation(indy.getLocation());
insn.replace(nullInsn);
CallLocation location = new CallLocation(method.getReference(), insn.getLocation());
diagnostics.error(location, "Substitutor for bootstrap method {{m0}} was not found",
bootstrapMethod);
continue;
}
BasicBlock splitBlock = splitter.split(block, insn);
pe.enter(block);
pe.setCurrentLocation(indy.getLocation());
insn.delete();
List<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() {

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_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) {
return eval(name);
}
let javaObjectSymbol = Symbol("javaObject");
let functionsSymbol = Symbol("functions");
let javaWrappers = new WeakMap();
function defaults(imports) {
let stderr = "";
let stdout = "";
@ -32,42 +35,28 @@ TeaVM.wasm = function() {
exports.reportGarbageCollectedString(heldValue);
});
imports.teavmDate = {
currentTimeMillis() {
return new Date().getTime();
},
dateToString(timestamp) {
return stringToJava(new Date(timestamp).toString());
},
getYear(timestamp) {
return new Date(timestamp).getFullYear();
},
currentTimeMillis: () => new Date().getTime(),
dateToString: timestamp => stringToJava(new Date(timestamp).toString()),
getYear: timestamp =>new Date(timestamp).getFullYear(),
setYear(timestamp, year) {
let date = new Date(timestamp);
date.setFullYear(year);
return date.getTime();
},
getMonth(timestamp) {
return new Date(timestamp).getMonth();
},
getMonth: timestamp =>new Date(timestamp).getMonth(),
setMonth(timestamp, month) {
let date = new Date(timestamp);
date.setMonth(month);
return date.getTime();
},
getDate(timestamp) {
return new Date(timestamp).getDate();
},
getDate: timestamp =>new Date(timestamp).getDate(),
setDate(timestamp, value) {
let date = new Date(timestamp);
date.setDate(value);
return date.getTime();
},
create(year, month, date, hrs, min, sec) {
return new Date(year, month, date, hrs, min, sec).getTime();
},
createFromUTC(year, month, date, hrs, min, sec) {
return Date.UTC(year, month, date, hrs, min, sec);
}
create: (year, month, date, hrs, min, sec) => new Date(year, month, date, hrs, min, sec).getTime(),
createFromUTC: (year, month, date, hrs, min, sec) => Date.UTC(year, month, date, hrs, min, sec)
};
imports.teavmConsole = {
putcharStderr(c) {
@ -106,6 +95,22 @@ TeaVM.wasm = function() {
function identity(value) {
return value;
}
function sanitizeName(str) {
let result = "";
let firstChar = str.charAt(0);
result += isIdentifierStart(firstChar) ? firstChar : '_';
for (let i = 1; i < str.length; ++i) {
let c = str.charAt(i)
result += isIdentifierPart(c) ? c : '_';
}
return result;
}
function isIdentifierStart(s) {
return s >= 'A' && s <= 'Z' || s >= 'a' && s <= 'z' || s === '_' || s === '$';
}
function isIdentifierPart(s) {
return isIdentifierStart(s) || s >= '0' && s <= '9';
}
imports.teavmJso = {
emptyString: () => "",
stringFromCharCode: code => String.fromCharCode(code),
@ -120,25 +125,76 @@ TeaVM.wasm = function() {
getPropertyPure: (obj, prop) => obj[prop],
setProperty: (obj, prop, value) => obj[prop] = value,
setPropertyPure: (obj, prop) => obj[prop] = value,
global: getGlobalName
global: getGlobalName,
createClass(name) {
let fn = new Function(
"javaObjectSymbol",
"functionsSymbol",
`return function JavaClass_${sanitizeName(name)}(javaObject) {
this[javaObjectSymbol] = javaObject;
this[functionsSymbol] = null;
};`
);
return fn(javaObjectSymbol, functionsSymbol);
},
defineMethod(cls, name, fn) {
cls.prototype[name] = function(...args) {
return fn(this, ...args);
}
},
defineProperty(cls, name, getFn, setFn) {
let descriptor = {
get() {
return getFn(this);
}
};
if (setFn !== null) {
descriptor.set = function(value) {
setFn(this, value);
}
}
Object.defineProperty(cls.prototype, name, descriptor);
},
javaObjectToJS(instance, cls) {
let existing = javaWrappers.get(instance);
if (typeof existing != "undefined") {
let result = existing.deref();
if (typeof result !== "undefined") {
return result;
}
}
let obj = new cls(instance);
javaWrappers.set(instance, new WeakRef(obj));
return obj;
},
unwrapJavaObject(instance) {
return instance[javaObjectSymbol];
},
asFunction(instance, propertyName) {
let functions = instance[functionsSymbol];
if (functions === null) {
functions = Object.create(null);
instance[functionsSymbol] = functions;
}
let result = functions[propertyName];
if (typeof result !== 'function') {
result = function() {
return instance[propertyName].apply(instance, arguments);
}
functions[propertyName] = result;
}
return result;
}
};
for (let name of ["wrapByte", "wrapShort", "wrapChar", "wrapInt", "wrapFloat", "wrapDouble", "unwrapByte",
"unwrapShort", "unwrapChar", "unwrapInt", "unwrapFloat", "unwrapDouble"]) {
imports.teavmJso[name] = identity;
}
for (let i = 0; i < 32; ++i) {
imports.teavmJso["createFunction" + i] = function() {
return new Function(...arguments);
};
imports.teavmJso["callFunction" + i] = function(fn, ...args) {
return fn(...args);
};
imports.teavmJso["callMethod" + i] = function(instance, method, ...args) {
return instance[method](...args);
};
imports.teavmJso["construct" + i] = function(constructor, ...args) {
return new constructor(...args);
};
imports.teavmJso["createFunction" + i] = (...args) => new Function(...args);
imports.teavmJso["callFunction" + i] = (fn, ...args) => fn(...args);
imports.teavmJso["callMethod" + i] = (instance, method, ...args) => instance[method](...args);
imports.teavmJso["construct" + i] = (constructor, ...args) => new constructor(...args);
}
imports.teavmMath = Math;
}

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)
@PluggableDependency(JSNativeInjector.class)
@Import(name = "asFunction", module = "teavmJso")
public static native JSObject function(JSObject instance, JSObject property);
@GeneratedBy(JSNativeGenerator.class)

View File

@ -15,9 +15,9 @@
*/
package org.teavm.jso.impl;
import static org.teavm.jso.impl.AliasCollector.collectMembers;
import static org.teavm.jso.impl.AliasCollector.getPublicAlias;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.rendering.RenderingManager;
import org.teavm.backend.javascript.spi.MethodContributor;
@ -47,7 +47,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
public void begin(RenderingManager context, BuildTarget buildTarget) {
writer = context.getWriter();
classSource = context.getClassSource();
typeHelper = new JSTypeHelper(context.getClassSource());
typeHelper = new JSTypeHelper(context.getOriginalClassSource());
this.context = context;
}
@ -91,7 +91,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
}
private boolean exportClassInstanceMembers(ClassReader classReader) {
var members = collectMembers(classReader, method -> !method.hasModifier(ElementModifier.STATIC));
var members = collectMembers(classReader, AliasCollector::isInstanceMember);
var isJsClassImpl = typeHelper.isJavaScriptImplementation(classReader.getName());
if (members.methods.isEmpty() && members.properties.isEmpty() && !isJsClassImpl) {
@ -106,23 +106,25 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
}
for (var aliasEntry : members.methods.entrySet()) {
if (classReader.getMethod(aliasEntry.getValue()) == null) {
if (classReader.getMethod(aliasEntry.getValue().getDescriptor()) == null) {
continue;
}
appendMethodAlias(aliasEntry.getKey());
writer.ws().append("=").ws().append("c.").appendVirtualMethod(aliasEntry.getValue())
.append(";").softNewLine();
writer.ws().append("=").ws().appendFunction("$rt_callWithReceiver").append("(")
.appendMethod(aliasEntry.getValue()).append(");").softNewLine();
}
for (var aliasEntry : members.properties.entrySet()) {
var propInfo = aliasEntry.getValue();
if (propInfo.getter == null || classReader.getMethod(propInfo.getter) == null) {
if (propInfo.getter == null || classReader.getMethod(propInfo.getter.getDescriptor()) == null) {
continue;
}
appendPropertyAlias(aliasEntry.getKey());
writer.append("get:").ws().append("c.").appendVirtualMethod(propInfo.getter);
if (propInfo.setter != null && classReader.getMethod(propInfo.setter) != null) {
writer.append("get:").ws().appendFunction("$rt_callWithReceiver").append("(")
.appendMethod(propInfo.getter).append(")");
if (propInfo.setter != null && classReader.getMethod(propInfo.setter.getDescriptor()) != null) {
writer.append(",").softNewLine();
writer.append("set:").ws().append("c.").appendVirtualMethod(propInfo.setter);
writer.append("set:").ws().appendFunction("$rt_callWithReceiver").append("(")
.appendMethod(propInfo.setter).append(")");
}
writer.softNewLine().outdent().append("});").softNewLine();
}
@ -136,7 +138,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
}
private boolean exportClassStaticMembers(ClassReader classReader, String name) {
var members = collectMembers(classReader, c -> c.hasModifier(ElementModifier.STATIC));
var members = collectMembers(classReader, AliasCollector::isStaticMember);
if (members.methods.isEmpty() && members.properties.isEmpty()) {
return false;
@ -146,7 +148,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
for (var aliasEntry : members.methods.entrySet()) {
appendMethodAlias(aliasEntry.getKey());
var fullRef = new MethodReference(classReader.getName(), aliasEntry.getValue());
var fullRef = aliasEntry.getValue();
writer.ws().append("=").ws().appendMethod(fullRef).append(";").softNewLine();
}
for (var aliasEntry : members.properties.entrySet()) {
@ -155,11 +157,11 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
continue;
}
appendPropertyAlias(aliasEntry.getKey());
var fullGetter = new MethodReference(classReader.getName(), propInfo.getter);
var fullGetter = propInfo.getter;
writer.append("get:").ws().appendMethod(fullGetter);
if (propInfo.setter != null) {
writer.append(",").softNewLine();
var fullSetter = new MethodReference(classReader.getName(), propInfo.setter);
var fullSetter = propInfo.setter;
writer.append("set:").ws().appendMethod(fullSetter);
}
writer.softNewLine().outdent().append("});").softNewLine();
@ -182,39 +184,6 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
.ws().append("{").indent().softNewLine();
}
private Members collectMembers(ClassReader classReader, Predicate<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() {
var cls = classSource.get(context.getEntryPoint());
for (var method : cls.getMethods()) {
@ -222,7 +191,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
continue;
}
var methodAlias = getPublicAlias(method);
if (methodAlias != null && methodAlias.kind == AliasKind.METHOD) {
if (methodAlias != null && methodAlias.kind == AliasCollector.AliasKind.METHOD) {
context.exportMethod(method.getReference(), methodAlias.name);
}
}
@ -230,7 +199,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
private void exportClassFromModule(ClassReader cls, String functionName) {
var name = getClassAliasName(cls);
var constructors = collectMembers(cls, method -> !method.hasModifier(ElementModifier.STATIC));
var constructors = collectMembers(cls, AliasCollector::isInstanceMember);
var method = constructors.constructor;
writer.append("function ").appendFunction(functionName).append("(");
@ -245,7 +214,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
writer.append(")").ws().appendBlockStart();
if (method != null) {
writer.appendClass(cls.getName()).append(".call(this);").softNewLine();
writer.appendMethod(new MethodReference(cls.getName(), method)).append("(this");
writer.appendMethod(method).append("(this");
for (var i = 0; i < method.parameterCount(); ++i) {
writer.append(",").ws().append("p" + i);
}
@ -293,31 +262,6 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
}
return false;
}
private Alias getPublicAlias(MethodReader method) {
var annot = method.getAnnotations().get(JSMethodToExpose.class.getName());
if (annot != null) {
return new Alias(annot.getValue("name").getString(), AliasKind.METHOD);
}
annot = method.getAnnotations().get(JSGetterToExpose.class.getName());
if (annot != null) {
return new Alias(annot.getValue("name").getString(), AliasKind.GETTER);
}
annot = method.getAnnotations().get(JSSetterToExpose.class.getName());
if (annot != null) {
return new Alias(annot.getValue("name").getString(), AliasKind.SETTER);
}
annot = method.getAnnotations().get(JSConstructorToExpose.class.getName());
if (annot != null) {
return new Alias(null, AliasKind.CONSTRUCTOR);
}
return null;
}
private FieldReader getFunctorField(ClassReader cls) {
return cls.getField("$$jso_functor$$");
}
@ -403,38 +347,5 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
return methodReader != null && getPublicAlias(methodReader) != null;
}
private static class Members {
final Map<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 {
private static final String NO_SIDE_EFFECTS = NoSideEffects.class.getName();
private static final MethodReference WRAP = new MethodReference(JSWrapper.class, "wrap", Object.class,
Object.class);
private static final MethodReference MAYBE_WRAP = new MethodReference(JSWrapper.class, "maybeWrap", Object.class,
Object.class);
private static final MethodReference UNWRAP = new MethodReference(JSWrapper.class, "unwrap", Object.class,
JSObject.class);
private static final MethodReference MAYBE_UNWRAP = new MethodReference(JSWrapper.class, "maybeUnwrap",
Object.class, JSObject.class);
private static final MethodReference IS_JS = new MethodReference(JSWrapper.class, "isJs",
Object.class, boolean.class);
private static final MethodReference IS_PRIMITIVE = new MethodReference(JSWrapper.class, "isPrimitive",
Object.class, JSObject.class, boolean.class);
private static final MethodReference INSTANCE_OF = new MethodReference(JSWrapper.class, "instanceOf",
Object.class, JSObject.class, boolean.class);
private final ClassReaderSource classSource;
private final JSBodyRepository repository;
private final JavaInvocationProcessor javaInvocationProcessor;
@ -111,6 +97,7 @@ class JSClassProcessor {
private JSImportAnnotationCache annotationCache;
private ClassReader objectClass;
private Predicate<String> classFilter = n -> true;
private boolean wasmGC;
JSClassProcessor(ClassReaderSource classSource, JSTypeHelper typeHelper, JSBodyRepository repository,
Diagnostics diagnostics, IncrementalDependencyRegistration incrementalCache, boolean strict) {
@ -125,7 +112,11 @@ class JSClassProcessor {
annotationCache = new JSImportAnnotationCache(classSource, diagnostics);
}
public void setClassFilter(Predicate<String> classFilter) {
void setWasmGC(boolean wasmGC) {
this.wasmGC = wasmGC;
}
void setClassFilter(Predicate<String> classFilter) {
this.classFilter = classFilter;
}
@ -286,8 +277,11 @@ class JSClassProcessor {
void processProgram(MethodHolder methodToProcess) {
setCurrentProgram(methodToProcess.getProgram());
types = new JSTypeInference(typeHelper, classSource, program, methodToProcess.getReference());
types = new JSTypeInference(typeHelper, classSource, program, methodToProcess.getReference(), wasmGC);
types.ensure();
if (wasmGC) {
wrapJsPhis(methodToProcess.getReference());
}
for (int i = 0; i < program.basicBlockCount(); ++i) {
var block = program.basicBlockAt(i);
for (var insn : block) {
@ -355,6 +349,32 @@ class JSClassProcessor {
}
}
private void wrapJsPhis(MethodReference methodReference) {
var changed = false;
for (var block : program.getBasicBlocks()) {
for (var phi : block.getPhis()) {
if (types.typeOf(phi.getReceiver()) == JSType.MIXED) {
for (var incoming : phi.getIncomings()) {
if (types.typeOf(incoming.getValue()) == JSType.JS) {
changed = true;
var wrap = new InvokeInstruction();
wrap.setType(InvocationType.SPECIAL);
wrap.setMethod(new MethodReference(JSWrapper.class, "wrap", JSObject.class, Object.class));
wrap.setArguments(incoming.getValue());
wrap.setReceiver(program.createVariable());
incoming.getSource().getLastInstruction().insertPrevious(wrap);
incoming.setValue(wrap.getReceiver());
}
}
}
}
}
if (changed) {
types = new JSTypeInference(typeHelper, classSource, program, methodReference, wasmGC);
types.ensure();
}
}
private void processInvokeArgs(InvokeInstruction invoke, MethodReader methodToInvoke) {
if (methodToInvoke != null && methodToInvoke.getAnnotations().get(JSBody.class.getName()) != null) {
return;
@ -402,7 +422,7 @@ class JSClassProcessor {
if (type == JSType.JS || type == JSType.MIXED) {
var unwrap = new InvokeInstruction();
unwrap.setType(InvocationType.SPECIAL);
unwrap.setMethod(type == JSType.MIXED ? MAYBE_UNWRAP : UNWRAP);
unwrap.setMethod(type == JSType.MIXED ? JSMethods.MAYBE_UNWRAP : JSMethods.UNWRAP);
unwrap.setArguments(program.createVariable());
unwrap.setReceiver(insn.getReceiver());
unwrap.setLocation(insn.getLocation());
@ -420,7 +440,7 @@ class JSClassProcessor {
if (type == JSType.JS || type == JSType.MIXED) {
var wrap = new InvokeInstruction();
wrap.setType(InvocationType.SPECIAL);
wrap.setMethod(type == JSType.MIXED ? MAYBE_WRAP : WRAP);
wrap.setMethod(type == JSType.MIXED ? JSMethods.MAYBE_WRAP : JSMethods.WRAP);
wrap.setArguments(insn.getValue());
wrap.setReceiver(program.createVariable());
wrap.setLocation(insn.getLocation());
@ -563,7 +583,7 @@ class JSClassProcessor {
if (isTransparent(targetClassName)) {
var invoke = new InvokeInstruction();
invoke.setType(InvocationType.SPECIAL);
invoke.setMethod(IS_JS);
invoke.setMethod(JSMethods.IS_JS);
invoke.setArguments(value);
invoke.setReceiver(receiver);
invoke.setLocation(location);
@ -572,7 +592,9 @@ class JSClassProcessor {
var primitiveType = getPrimitiveType(targetClassName);
var invoke = new InvokeInstruction();
invoke.setType(InvocationType.SPECIAL);
invoke.setMethod(primitiveType != null ? IS_PRIMITIVE : INSTANCE_OF);
invoke.setMethod(primitiveType != null
? JSMethods.WRAPPER_IS_PRIMITIVE
: JSMethods.WRAPPER_INSTANCE_OF);
var secondArg = primitiveType != null
? marshaller.addJsString(primitiveType, location)
: marshaller.classRef(targetClassName, location);
@ -648,7 +670,7 @@ class JSClassProcessor {
}
var wrap = new InvokeInstruction();
wrap.setType(InvocationType.SPECIAL);
wrap.setMethod(varType == JSType.JS ? WRAP : MAYBE_WRAP);
wrap.setMethod(varType == JSType.JS ? JSMethods.WRAP : JSMethods.MAYBE_WRAP);
wrap.setArguments(var);
wrap.setReceiver(program.createVariable());
wrap.setLocation(instruction.getLocation());
@ -663,7 +685,7 @@ class JSClassProcessor {
}
var unwrap = new InvokeInstruction();
unwrap.setType(InvocationType.SPECIAL);
unwrap.setMethod(varType == JSType.JAVA ? UNWRAP : MAYBE_UNWRAP);
unwrap.setMethod(varType == JSType.JAVA ? JSMethods.UNWRAP : JSMethods.MAYBE_UNWRAP);
unwrap.setArguments(var);
unwrap.setReceiver(program.createVariable());
unwrap.setLocation(instruction.getLocation());
@ -1187,6 +1209,7 @@ class JSClassProcessor {
if (method.getAnnotations().get(NoSideEffects.class.getName()) != null) {
proxyMethod.getAnnotations().add(new AnnotationHolder(NoSideEffects.class.getName()));
}
proxyMethod.getAnnotations().add(new AnnotationHolder(JSBodyDelegate.class.getName()));
boolean inline = repository.inlineMethods.contains(methodRef);
AnnotationHolder generatorAnnot = new AnnotationHolder(inline
? DynamicInjector.class.getName() : DynamicGenerator.class.getName());

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.DependencyAgent;
import org.teavm.dependency.DependencyNode;
import org.teavm.dependency.MethodDependency;
import org.teavm.jso.JSExportClasses;
import org.teavm.model.AnnotationReader;
import org.teavm.model.CallLocation;
import org.teavm.model.ClassReader;
import org.teavm.model.ElementModifier;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
class JSDependencyListener extends AbstractDependencyListener {
private JSBodyRepository repository;
private DependencyNode exceptions;
JSDependencyListener(JSBodyRepository repository) {
this.repository = repository;
}
@Override
public void started(DependencyAgent agent) {
exceptions = agent.createNode();
}
@Override
public void methodReached(DependencyAgent agent, MethodDependency method) {
MethodReference ref = method.getReference();
@ -43,6 +49,22 @@ class JSDependencyListener extends AbstractDependencyListener {
agent.linkMethod(callbackMethod).addLocation(new CallLocation(ref)).use();
}
}
if (method.getMethod().getAnnotations().get(JSBodyDelegate.class.getName()) != null) {
exceptions.connect(method.getThrown());
}
if (method.getMethod().getOwnerName().equals(JS.class.getName())) {
switch (method.getMethod().getName()) {
case "invoke":
case "construct":
case "apply":
case "get":
case "getPure":
case "set":
case "setPure":
exceptions.connect(method.getThrown());
break;
}
}
}
@Override
@ -62,10 +84,8 @@ class JSDependencyListener extends AbstractDependencyListener {
if (exposeAnnot != null) {
MethodDependency methodDep = agent.linkMethod(method.getReference());
if (methodDep.getMethod() != null) {
if (!methodDep.getMethod().hasModifier(ElementModifier.STATIC)) {
methodDep.getVariable(0).propagate(agent.getType(className));
}
methodDep.use();
methodDep.getThrown().connect(exceptions);
}
}
}

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.core.JSArray;
import org.teavm.jso.core.JSArrayReader;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
final class JSMethods {
public final class JSMethods {
public static final MethodReference GET = new MethodReference(JS.class, "get", JSObject.class,
JSObject.class, JSObject.class);
public static final MethodReference GET_PURE = new MethodReference(JS.class, "getPure", JSObject.class,
@ -143,6 +144,25 @@ final class JSMethods {
private static final MethodReference[] CONSTRUCT_METHODS = new MethodReference[13];
private static final MethodReference[] ARRAY_OF_METHODS = new MethodReference[13];
public static final MethodReference WRAP = new MethodReference(JSWrapper.class, "wrap", JSObject.class,
Object.class);
public static final MethodReference MAYBE_WRAP = new MethodReference(JSWrapper.class, "maybeWrap", Object.class,
Object.class);
public static final MethodReference UNWRAP = new MethodReference(JSWrapper.class, "unwrap", Object.class,
JSObject.class);
public static final MethodReference MAYBE_UNWRAP = new MethodReference(JSWrapper.class, "maybeUnwrap",
Object.class, JSObject.class);
public static final MethodReference IS_JS = new MethodReference(JSWrapper.class, "isJs",
Object.class, boolean.class);
public static final MethodReference WRAPPER_IS_PRIMITIVE = new MethodReference(JSWrapper.class, "isPrimitive",
Object.class, JSObject.class, boolean.class);
public static final MethodReference WRAPPER_INSTANCE_OF = new MethodReference(JSWrapper.class, "instanceOf",
Object.class, JSObject.class, boolean.class);
public static final String JS_MARSHALLABLE = JSMarshallable.class.getName();
public static final MethodDescriptor MARSHALL_TO_JS = new MethodDescriptor("marshallToJs", JS_OBJECT);
public static final MethodReference MARSHALL_TO_JS_REF = new MethodReference(JS_MARSHALLABLE, MARSHALL_TO_JS);
static {
for (int i = 0; i < INVOKE_METHODS.length; ++i) {
var signature = new ValueType[i + 3];

View File

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

View File

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

View File

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

View File

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

View File

@ -87,6 +87,16 @@ class JSValueMarshaller {
diagnostics.error(location, "Wrong functor: {{c0}}", type.getName());
return var;
}
var unwrapNative = new InvokeInstruction();
unwrapNative.setLocation(location.getSourceLocation());
unwrapNative.setType(InvocationType.SPECIAL);
unwrapNative.setMethod(JSMethods.UNWRAP);
unwrapNative.setArguments(var);
unwrapNative.setReceiver(program.createVariable());
replacement.add(unwrapNative);
var = unwrapNative.getReceiver();
String name = type.getMethods().stream()
.filter(method -> method.hasModifier(ElementModifier.ABSTRACT))
.findFirst().get().getName();
@ -142,6 +152,16 @@ class JSValueMarshaller {
replacement.add(unwrapNative);
return unwrapNative.getReceiver();
}
if (typeHelper.isJavaScriptClass(className) && jsType == JSType.JAVA) {
var unwrapNative = new InvokeInstruction();
unwrapNative.setLocation(location);
unwrapNative.setType(InvocationType.SPECIAL);
unwrapNative.setMethod(JSMethods.UNWRAP);
unwrapNative.setArguments(var);
unwrapNative.setReceiver(program.createVariable());
replacement.add(unwrapNative);
return unwrapNative.getReceiver();
}
return var;
}
}

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

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) {
host.add(new WasmGCJSDependencies());
host.add(new WasmGCJSWrapperTransformer());
var jsFunctions = new WasmGCJSFunctions();
var commonGen = new WasmGCJsoCommonGenerator(jsFunctions);
wasmGCHost.addCustomTypeMapperFactory(new WasmGCJSTypeMapper());
wasmGCHost.addIntrinsicFactory(new WasmGCJSBodyRenderer(jsBodyRepository));
wasmGCHost.addIntrinsicFactory(new WasmGCJSBodyRenderer(jsBodyRepository, jsFunctions, commonGen));
wasmGCHost.addGeneratorFactory(new WasmGCMarshallMethodGeneratorFactory(commonGen));
var jsIntrinsic = new WasmGCJSIntrinsic();
wasmGCHost.addIntrinsic(new MethodReference(JS.class, "wrap", String.class, JSObject.class), jsIntrinsic);

View File

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

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)
@SkipJVM
@OnlyPlatform(TestPlatform.JAVASCRIPT)
@OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC})
@EachTestCompiledSeparately
public class ExportClassTest {
@Test

View File

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