wasm gc: fully support exporting classes to JS

This commit is contained in:
Alexey Andreev 2024-10-09 19:28:45 +02:00
parent 0dcc25d66b
commit f61d893b6d
8 changed files with 39 additions and 74 deletions

View File

@ -93,17 +93,12 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
private boolean exportClassInstanceMembers(ClassReader classReader) { private boolean exportClassInstanceMembers(ClassReader classReader) {
var members = collectMembers(classReader, AliasCollector::isInstanceMember); var members = collectMembers(classReader, AliasCollector::isInstanceMember);
var isJsClassImpl = typeHelper.isJavaScriptImplementation(classReader.getName()); if (members.methods.isEmpty() && members.properties.isEmpty()) {
if (members.methods.isEmpty() && members.properties.isEmpty() && !isJsClassImpl) {
return false; return false;
} }
writer.append("c").ws().append("=").ws().appendClass(classReader.getName()).append(".prototype;") writer.append("c").ws().append("=").ws().appendClass(classReader.getName()).append(".prototype;")
.softNewLine(); .softNewLine();
if (isJsClassImpl) {
writer.append("c[").appendFunction("$rt_jso_marker").append("]").ws().append("=").ws().append("true;")
.softNewLine();
}
for (var aliasEntry : members.methods.entrySet()) { for (var aliasEntry : members.methods.entrySet()) {
if (classReader.getMethod(aliasEntry.getValue().getDescriptor()) == null) { if (classReader.getMethod(aliasEntry.getValue().getDescriptor()) == null) {
@ -253,9 +248,6 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
private boolean hasClassesToExpose() { private boolean hasClassesToExpose() {
for (String className : classSource.getClassNames()) { for (String className : classSource.getClassNames()) {
ClassReader cls = classSource.get(className); ClassReader cls = classSource.get(className);
if (typeHelper.isJavaScriptImplementation(className)) {
return true;
}
for (var method : cls.getMethods()) { for (var method : cls.getMethods()) {
if (getPublicAlias(method) != null) { if (getPublicAlias(method) != null) {
return true; return true;
@ -348,6 +340,4 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
MethodReader methodReader = classReader.getMethod(methodRef.getDescriptor()); MethodReader methodReader = classReader.getMethod(methodRef.getDescriptor());
return methodReader != null && getPublicAlias(methodReader) != null; return methodReader != null && getPublicAlias(methodReader) != null;
} }
} }

View File

@ -98,7 +98,5 @@ public class JSOPlugin implements TeaVMPlugin {
wrapperGenerator); wrapperGenerator);
jsHost.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class), jsHost.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class),
wrapperGenerator); wrapperGenerator);
jsHost.add(new MethodReference(JSWrapper.class, "isJSImplementation", Object.class, boolean.class),
wrapperGenerator);
} }
} }

View File

@ -88,7 +88,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
processor.setClassFilter(classFilter); processor.setClassFilter(classFilter);
} }
processor.processClass(cls); processor.processClass(cls);
if (isJavaScriptClass(cls)) { if (isJavaScriptClass(cls) && !isJavaScriptImplementation(cls)) {
processor.processMemberMethods(cls); processor.processMemberMethods(cls);
} }
@ -99,44 +99,48 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
} }
processor.createJSMethods(cls); processor.createJSMethods(cls);
if (cls.hasModifier(ElementModifier.ABSTRACT) if (isJavaScriptClass(cls) && !isJavaScriptImplementation(cls)) {
|| cls.getAnnotations().get(JSClass.class.getName()) != null && isJavaScriptClass(cls)) {
return; return;
} }
MethodReference functorMethod = processor.isFunctor(cls.getName()); var hasStaticMethods = false;
if (functorMethod != null) { var hasMemberMethods = false;
if (processor.isFunctor(cls.getParent()) != null) {
functorMethod = null; if (!cls.hasModifier(ElementModifier.ABSTRACT)) {
MethodReference functorMethod = processor.isFunctor(cls.getName());
if (functorMethod != null) {
if (processor.isFunctor(cls.getParent()) != null) {
functorMethod = null;
}
}
ClassReader originalClass = hierarchy.getClassSource().get(cls.getName());
ExposedClass exposedClass;
if (originalClass != null) {
exposedClass = getExposedClass(cls.getName());
} else {
exposedClass = new ExposedClass();
createExposedClass(cls, exposedClass);
}
exposeMethods(cls, exposedClass, context.getDiagnostics(), functorMethod);
if (!exposedClass.methods.isEmpty()) {
hasMemberMethods = true;
cls.getAnnotations().add(new AnnotationHolder(JSClassToExpose.class.getName()));
} }
} }
hasStaticMethods = exportStaticMethods(cls, context.getDiagnostics());
ClassReader originalClass = hierarchy.getClassSource().get(cls.getName()); if (hasMemberMethods || hasStaticMethods) {
ExposedClass exposedClass;
if (originalClass != null) {
exposedClass = getExposedClass(cls.getName());
} else {
exposedClass = new ExposedClass();
createExposedClass(cls, exposedClass);
}
exposeMethods(cls, exposedClass, context.getDiagnostics(), functorMethod);
var hasStaticMethods = exportStaticMethods(cls, context.getDiagnostics());
if (isJavaScriptImplementation(cls) || !exposedClass.methods.isEmpty()) {
cls.getAnnotations().add(new AnnotationHolder(JSClassToExpose.class.getName()));
}
if (isJavaScriptImplementation(cls) || !exposedClass.methods.isEmpty() || hasStaticMethods) {
cls.getAnnotations().add(new AnnotationHolder(JSClassObjectToExpose.class.getName())); cls.getAnnotations().add(new AnnotationHolder(JSClassObjectToExpose.class.getName()));
} }
if (wasmGC && (!exposedClass.methods.isEmpty() || isJavaScriptClass(cls))) { if (wasmGC && hasMemberMethods) {
var createWrapperMethod = new MethodHolder(JSMethods.MARSHALL_TO_JS); var createWrapperMethod = new MethodHolder(JSMethods.MARSHALL_TO_JS);
createWrapperMethod.setLevel(AccessLevel.PUBLIC); createWrapperMethod.setLevel(AccessLevel.PUBLIC);
createWrapperMethod.getModifiers().add(ElementModifier.NATIVE); createWrapperMethod.getModifiers().add(ElementModifier.NATIVE);
cls.addMethod(createWrapperMethod); cls.addMethod(createWrapperMethod);
if (!isJavaScriptClass(cls) || isJavaScriptImplementation(cls)) { if (!isJavaScriptClass(cls)) {
cls.getInterfaces().add(JSMethods.JS_MARSHALLABLE); cls.getInterfaces().add(JSMethods.JS_MARSHALLABLE);
} }
} }
@ -429,7 +433,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
if (typeHelper.isJavaScriptImplementation(cls.getName())) { if (typeHelper.isJavaScriptImplementation(cls.getName())) {
return true; return true;
} }
if (cls.getAnnotations().get(JSClass.class.getName()) != null) { if (cls.getAnnotations().get(JSClass.class.getName()) != null || cls.hasModifier(ElementModifier.ABSTRACT)) {
return false; return false;
} }
if (cls.getParent() != null) { if (cls.getParent() != null) {

View File

@ -84,7 +84,8 @@ public class JSTypeHelper {
return false; return false;
} }
ClassReader cls = classSource.get(className); ClassReader cls = classSource.get(className);
if (cls == null || cls.getAnnotations().get(JSClass.class.getName()) != null) { if (cls == null || cls.getAnnotations().get(JSClass.class.getName()) != null
|| cls.hasModifier(ElementModifier.ABSTRACT)) {
return false; return false;
} }
if (cls.getParent() != null) { if (cls.getParent() != null) {

View File

@ -188,14 +188,11 @@ public final class JSWrapper {
@NoSideEffects @NoSideEffects
public static native boolean isJava(JSObject obj); public static native boolean isJava(JSObject obj);
@NoSideEffects
private static native boolean isJSImplementation(Object obj);
public static JSObject unwrap(Object o) { public static JSObject unwrap(Object o) {
if (o == null) { if (o == null) {
return null; return null;
} }
return isJSImplementation(o) ? marshallJavaToJs(o) : ((JSWrapper) o).js; return (!(o instanceof JSWrapper)) ? marshallJavaToJs(o) : ((JSWrapper) o).js;
} }
public static JSObject maybeUnwrap(Object o) { public static JSObject maybeUnwrap(Object o) {

View File

@ -50,17 +50,6 @@ public class JSWrapperGenerator implements Injector, DependencyPlugin {
context.getWriter().append(")"); context.getWriter().append(")");
} }
break; break;
case "isJSImplementation":
if (context.getPrecedence().ordinal() >= Precedence.EQUALITY.ordinal()) {
context.getWriter().append("(");
}
context.writeExpr(context.getArgument(0), Precedence.MEMBER_ACCESS);
context.getWriter().append("[").appendFunction("$rt_jso_marker").append("]")
.ws().append("===").ws().append("true");
if (context.getPrecedence().ordinal() >= Precedence.EQUALITY.ordinal()) {
context.getWriter().append(")");
}
break;
} }
} }

View File

@ -25,7 +25,6 @@ import org.teavm.model.ClassHolderTransformerContext;
import org.teavm.model.ElementModifier; import org.teavm.model.ElementModifier;
import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodHolder; import org.teavm.model.MethodHolder;
import org.teavm.model.ValueType;
import org.teavm.model.emit.ProgramEmitter; import org.teavm.model.emit.ProgramEmitter;
class WasmGCJSWrapperTransformer implements ClassHolderTransformer { class WasmGCJSWrapperTransformer implements ClassHolderTransformer {
@ -35,8 +34,6 @@ class WasmGCJSWrapperTransformer implements ClassHolderTransformer {
transformMarshallMethod(cls.getMethod(new MethodDescriptor("marshallJavaToJs", Object.class, transformMarshallMethod(cls.getMethod(new MethodDescriptor("marshallJavaToJs", Object.class,
JSObject.class)), context); JSObject.class)), context);
transformWrapMethod(cls.getMethod(new MethodDescriptor("wrap", JSObject.class, Object.class))); transformWrapMethod(cls.getMethod(new MethodDescriptor("wrap", JSObject.class, Object.class)));
transformIsJsImplementation(cls.getMethod(new MethodDescriptor("isJSImplementation",
Object.class, boolean.class)), context);
transformIsJava(cls.getMethod(new MethodDescriptor("isJava", Object.class, boolean.class)), context); transformIsJava(cls.getMethod(new MethodDescriptor("isJava", Object.class, boolean.class)), context);
addCreateWrapperMethod(cls, context); addCreateWrapperMethod(cls, context);
} }
@ -54,13 +51,6 @@ class WasmGCJSWrapperTransformer implements ClassHolderTransformer {
method.setProgram(null); method.setProgram(null);
} }
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();
}
private void addCreateWrapperMethod(ClassHolder cls, ClassHolderTransformerContext context) { private void addCreateWrapperMethod(ClassHolder cls, ClassHolderTransformerContext context) {
var method = new MethodHolder(new MethodDescriptor("createWrapper", JSObject.class, Object.class)); var method = new MethodHolder(new MethodDescriptor("createWrapper", JSObject.class, Object.class));
method.getModifiers().add(ElementModifier.STATIC); method.getModifiers().add(ElementModifier.STATIC);

View File

@ -97,7 +97,7 @@ public class ExportTest {
@Test @Test
public void exportClassMembers() { public void exportClassMembers() {
testExport("exportClassMembers", ModuleWithExportedClassMembers.class, true); testExport("exportClassMembers", ModuleWithExportedClassMembers.class);
} }
@Test @Test
@ -107,23 +107,19 @@ public class ExportTest {
@Test @Test
public void exportClasses() { public void exportClasses() {
testExport("exportClasses", ModuleWithExportedClasses.class, true); testExport("exportClasses", ModuleWithExportedClasses.class);
} }
@Test @Test
public void varargs() { public void varargs() {
testExport("varargs", ModuleWithVararg.class, true); testExport("varargs", ModuleWithVararg.class);
} }
private void testExport(String name, Class<?> moduleClass) { private void testExport(String name, Class<?> moduleClass) {
testExport(name, moduleClass, false);
}
private void testExport(String name, Class<?> moduleClass, boolean skipWasm) {
if (jsNeeded) { if (jsNeeded) {
testExportJs(name, moduleClass); testExportJs(name, moduleClass);
} }
if (wasmGCNeeded && !skipWasm) { if (wasmGCNeeded) {
testExportWasmGC(name, moduleClass); testExportWasmGC(name, moduleClass);
} }
} }