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

View File

@ -98,7 +98,5 @@ public class JSOPlugin implements TeaVMPlugin {
wrapperGenerator);
jsHost.add(new MethodReference(JSWrapper.class, "jsToWrapper", JSObject.class, JSWrapper.class),
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.processClass(cls);
if (isJavaScriptClass(cls)) {
if (isJavaScriptClass(cls) && !isJavaScriptImplementation(cls)) {
processor.processMemberMethods(cls);
}
@ -99,11 +99,14 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
}
processor.createJSMethods(cls);
if (cls.hasModifier(ElementModifier.ABSTRACT)
|| cls.getAnnotations().get(JSClass.class.getName()) != null && isJavaScriptClass(cls)) {
if (isJavaScriptClass(cls) && !isJavaScriptImplementation(cls)) {
return;
}
var hasStaticMethods = false;
var hasMemberMethods = false;
if (!cls.hasModifier(ElementModifier.ABSTRACT)) {
MethodReference functorMethod = processor.isFunctor(cls.getName());
if (functorMethod != null) {
if (processor.isFunctor(cls.getParent()) != null) {
@ -121,22 +124,23 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
}
exposeMethods(cls, exposedClass, context.getDiagnostics(), functorMethod);
var hasStaticMethods = exportStaticMethods(cls, context.getDiagnostics());
if (isJavaScriptImplementation(cls) || !exposedClass.methods.isEmpty()) {
if (!exposedClass.methods.isEmpty()) {
hasMemberMethods = true;
cls.getAnnotations().add(new AnnotationHolder(JSClassToExpose.class.getName()));
}
if (isJavaScriptImplementation(cls) || !exposedClass.methods.isEmpty() || hasStaticMethods) {
}
hasStaticMethods = exportStaticMethods(cls, context.getDiagnostics());
if (hasMemberMethods || hasStaticMethods) {
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);
createWrapperMethod.setLevel(AccessLevel.PUBLIC);
createWrapperMethod.getModifiers().add(ElementModifier.NATIVE);
cls.addMethod(createWrapperMethod);
if (!isJavaScriptClass(cls) || isJavaScriptImplementation(cls)) {
if (!isJavaScriptClass(cls)) {
cls.getInterfaces().add(JSMethods.JS_MARSHALLABLE);
}
}
@ -429,7 +433,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
if (typeHelper.isJavaScriptImplementation(cls.getName())) {
return true;
}
if (cls.getAnnotations().get(JSClass.class.getName()) != null) {
if (cls.getAnnotations().get(JSClass.class.getName()) != null || cls.hasModifier(ElementModifier.ABSTRACT)) {
return false;
}
if (cls.getParent() != null) {

View File

@ -84,7 +84,8 @@ public class JSTypeHelper {
return false;
}
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;
}
if (cls.getParent() != null) {

View File

@ -188,14 +188,11 @@ public final class JSWrapper {
@NoSideEffects
public static native boolean isJava(JSObject obj);
@NoSideEffects
private static native boolean isJSImplementation(Object obj);
public static JSObject unwrap(Object o) {
if (o == 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) {

View File

@ -50,17 +50,6 @@ public class JSWrapperGenerator implements Injector, DependencyPlugin {
context.getWriter().append(")");
}
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.MethodDescriptor;
import org.teavm.model.MethodHolder;
import org.teavm.model.ValueType;
import org.teavm.model.emit.ProgramEmitter;
class WasmGCJSWrapperTransformer implements ClassHolderTransformer {
@ -35,8 +34,6 @@ class WasmGCJSWrapperTransformer implements ClassHolderTransformer {
transformMarshallMethod(cls.getMethod(new MethodDescriptor("marshallJavaToJs", Object.class,
JSObject.class)), context);
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);
addCreateWrapperMethod(cls, context);
}
@ -54,13 +51,6 @@ class WasmGCJSWrapperTransformer implements ClassHolderTransformer {
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) {
var method = new MethodHolder(new MethodDescriptor("createWrapper", JSObject.class, Object.class));
method.getModifiers().add(ElementModifier.STATIC);

View File

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