jso: support varargs in exported classes and modules

This commit is contained in:
Alexey Andreev 2024-04-15 20:53:24 +02:00
parent abb1ea0070
commit 931f0f1f4a
23 changed files with 354 additions and 61 deletions

View File

@ -15,15 +15,15 @@
*/ */
package org.teavm.classlib.impl; package org.teavm.classlib.impl;
import org.teavm.backend.javascript.spi.VirtualMethodContributor; import org.teavm.backend.javascript.spi.MethodContributor;
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext; import org.teavm.backend.javascript.spi.MethodContributorContext;
import org.teavm.model.ClassReader; import org.teavm.model.ClassReader;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.platform.PlatformAnnotationProvider; import org.teavm.platform.PlatformAnnotationProvider;
public class AnnotationVirtualMethods implements VirtualMethodContributor { public class AnnotationVirtualMethods implements MethodContributor {
@Override @Override
public boolean isVirtual(VirtualMethodContributorContext context, MethodReference methodRef) { public boolean isContributing(MethodContributorContext context, MethodReference methodRef) {
ClassReader cls = context.getClassSource().get(methodRef.getClassName()); ClassReader cls = context.getClassSource().get(methodRef.getClassName());
if (cls == null || !cls.getInterfaces().contains(PlatformAnnotationProvider.class.getName())) { if (cls == null || !cls.getInterfaces().contains(PlatformAnnotationProvider.class.getName())) {
return false; return false;

View File

@ -60,8 +60,8 @@ import org.teavm.backend.javascript.spi.GeneratedBy;
import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.InjectedBy; import org.teavm.backend.javascript.spi.InjectedBy;
import org.teavm.backend.javascript.spi.Injector; import org.teavm.backend.javascript.spi.Injector;
import org.teavm.backend.javascript.spi.VirtualMethodContributor; import org.teavm.backend.javascript.spi.MethodContributor;
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext; import org.teavm.backend.javascript.spi.MethodContributorContext;
import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory; import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory;
import org.teavm.cache.EmptyMethodNodeCache; import org.teavm.cache.EmptyMethodNodeCache;
import org.teavm.cache.MethodNodeCache; import org.teavm.cache.MethodNodeCache;
@ -122,7 +122,8 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
private DebugInformationEmitter debugEmitter; private DebugInformationEmitter debugEmitter;
private MethodNodeCache astCache = EmptyMethodNodeCache.INSTANCE; private MethodNodeCache astCache = EmptyMethodNodeCache.INSTANCE;
private final Set<MethodReference> asyncMethods = new HashSet<>(); private final Set<MethodReference> asyncMethods = new HashSet<>();
private List<VirtualMethodContributor> customVirtualMethods = new ArrayList<>(); private List<MethodContributor> customVirtualMethods = new ArrayList<>();
private List<MethodContributor> forcedFunctionMethods = new ArrayList<>();
private boolean strict; private boolean strict;
private BoundCheckInsertion boundCheckInsertion = new BoundCheckInsertion(); private BoundCheckInsertion boundCheckInsertion = new BoundCheckInsertion();
private NullCheckInsertion nullCheckInsertion = new NullCheckInsertion(NullCheckFilter.EMPTY); private NullCheckInsertion nullCheckInsertion = new NullCheckInsertion(NullCheckFilter.EMPTY);
@ -366,11 +367,13 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
debugEmitterToUse = new DummyDebugInformationEmitter(); debugEmitterToUse = new DummyDebugInformationEmitter();
} }
var virtualMethodContributorContext = new VirtualMethodContributorContextImpl(classes); var methodContributorContext = new MethodContributorContextImpl(classes);
RenderingContext renderingContext = new RenderingContext(debugEmitterToUse, RenderingContext renderingContext = new RenderingContext(debugEmitterToUse,
controller.getUnprocessedClassSource(), classes, controller.getUnprocessedClassSource(), classes,
controller.getClassLoader(), controller.getServices(), controller.getProperties(), naming, controller.getClassLoader(), controller.getServices(), controller.getProperties(), naming,
controller.getDependencyInfo(), m -> isVirtual(virtualMethodContributorContext, m), controller.getDependencyInfo(),
m -> isVirtual(methodContributorContext, m),
m -> isForcedFunction(methodContributorContext, m),
controller.getClassInitializerInfo(), strict controller.getClassInitializerInfo(), strict
) { ) {
@Override @Override
@ -804,31 +807,45 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
} }
@Override @Override
public void addVirtualMethods(VirtualMethodContributor virtualMethods) { public void addVirtualMethods(MethodContributor virtualMethods) {
customVirtualMethods.add(virtualMethods); customVirtualMethods.add(virtualMethods);
} }
@Override
public void addForcedFunctionMethods(MethodContributor forcedFunctionMethods) {
this.forcedFunctionMethods.add(forcedFunctionMethods);
}
@Override @Override
public boolean isAsyncSupported() { public boolean isAsyncSupported() {
return true; return true;
} }
private boolean isVirtual(VirtualMethodContributorContext context, MethodReference method) { private boolean isVirtual(MethodContributorContext context, MethodReference method) {
if (controller.isVirtual(method)) { if (controller.isVirtual(method)) {
return true; return true;
} }
for (VirtualMethodContributor predicate : customVirtualMethods) { for (MethodContributor predicate : customVirtualMethods) {
if (predicate.isVirtual(context, method)) { if (predicate.isContributing(context, method)) {
return true; return true;
} }
} }
return false; return false;
} }
static class VirtualMethodContributorContextImpl implements VirtualMethodContributorContext { private boolean isForcedFunction(MethodContributorContext context, MethodReference method) {
for (var predicate : forcedFunctionMethods) {
if (predicate.isContributing(context, method)) {
return true;
}
}
return false;
}
private static class MethodContributorContextImpl implements MethodContributorContext {
private ClassReaderSource classSource; private ClassReaderSource classSource;
VirtualMethodContributorContextImpl(ClassReaderSource classSource) { MethodContributorContextImpl(ClassReaderSource classSource) {
this.classSource = classSource; this.classSource = classSource;
} }

View File

@ -18,7 +18,7 @@ package org.teavm.backend.javascript;
import java.util.function.Function; import java.util.function.Function;
import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.Injector; import org.teavm.backend.javascript.spi.Injector;
import org.teavm.backend.javascript.spi.VirtualMethodContributor; import org.teavm.backend.javascript.spi.MethodContributor;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.vm.spi.RendererListener; import org.teavm.vm.spi.RendererListener;
import org.teavm.vm.spi.TeaVMHostExtension; import org.teavm.vm.spi.TeaVMHostExtension;
@ -34,5 +34,7 @@ public interface TeaVMJavaScriptHost extends TeaVMHostExtension {
void add(RendererListener listener); void add(RendererListener listener);
void addVirtualMethods(VirtualMethodContributor virtualMethods); void addVirtualMethods(MethodContributor virtualMethods);
void addForcedFunctionMethods(MethodContributor forcedFunctionMethods);
} }

View File

@ -93,12 +93,16 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
} }
public void renderParameters(MethodReference reference, Set<ElementModifier> modifiers) { public void renderParameters(MethodReference reference, Set<ElementModifier> modifiers) {
renderParameters(reference, modifiers, false);
}
public void renderParameters(MethodReference reference, Set<ElementModifier> modifiers, boolean forceParentheses) {
int startParam = 0; int startParam = 0;
if (modifiers.contains(ElementModifier.STATIC)) { if (modifiers.contains(ElementModifier.STATIC)) {
startParam = 1; startParam = 1;
} }
var count = reference.parameterCount() - startParam + 1; var count = reference.parameterCount() - startParam + 1;
if (count != 1) { if (count != 1 || forceParentheses) {
writer.append("("); writer.append("(");
} }
for (int i = startParam; i <= reference.parameterCount(); ++i) { for (int i = startParam; i <= reference.parameterCount(); ++i) {
@ -107,7 +111,7 @@ public class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext {
} }
writer.append(statementRenderer.variableName(i)); writer.append(statementRenderer.variableName(i));
} }
if (count != 1) { if (count != 1 || forceParentheses) {
writer.append(")"); writer.append(")");
} }
} }

View File

@ -372,8 +372,13 @@ public class Renderer implements RenderingManager {
if (!filterMethod(method)) { if (!filterMethod(method)) {
continue; continue;
} }
writer.startVariableDeclaration(); var isFunction = context.isForcedFunction(method.getReference());
renderBody(method, decompiler); if (isFunction) {
writer.startFunctionDeclaration();
} else {
writer.startVariableDeclaration();
}
renderBody(method, decompiler, isFunction);
writer.endDeclaration(); writer.endDeclaration();
if (needsInitializers && !method.hasModifier(ElementModifier.STATIC) if (needsInitializers && !method.hasModifier(ElementModifier.STATIC)
&& method.getName().equals("<init>")) { && method.getName().equals("<init>")) {
@ -381,7 +386,6 @@ public class Renderer implements RenderingManager {
} }
} }
writer.emitClass(null); writer.emitClass(null);
} }
@ -781,7 +785,12 @@ public class Renderer implements RenderingManager {
} }
private void emitVirtualFunctionWrapper(MethodReference method) { private void emitVirtualFunctionWrapper(MethodReference method) {
if (method.parameterCount() <= 4) { var forced = context.isForcedFunction(method);
if (forced) {
writer.appendFunction("$rt_wrapFunctionVararg").append("(").appendMethod(method).append(")");
return;
}
if (method.parameterCount() <= 4 && !forced) {
writer.appendFunction("$rt_wrapFunction" + method.parameterCount()); writer.appendFunction("$rt_wrapFunction" + method.parameterCount());
writer.append("(").appendMethod(method).append(")"); writer.append("(").appendMethod(method).append(")");
return; return;
@ -810,22 +819,22 @@ public class Renderer implements RenderingManager {
writer.append(");").ws().append("}"); writer.append(");").ws().append("}");
} }
private void renderBody(MethodHolder method, Decompiler decompiler) { private void renderBody(MethodHolder method, Decompiler decompiler, boolean isFunction) {
MethodReference ref = method.getReference(); MethodReference ref = method.getReference();
writer.emitMethod(ref.getDescriptor()); writer.emitMethod(ref.getDescriptor());
writer.appendMethod(ref); writer.appendMethod(ref);
if (method.hasModifier(ElementModifier.NATIVE)) { if (method.hasModifier(ElementModifier.NATIVE)) {
renderNativeBody(method, classSource); renderNativeBody(method, classSource, isFunction);
} else { } else {
renderRegularBody(method, decompiler); renderRegularBody(method, decompiler, isFunction);
} }
writer.outdent().append("}"); writer.outdent().append("}");
writer.emitMethod(null); writer.emitMethod(null);
} }
private void renderNativeBody(MethodHolder method, ClassReaderSource classes) { private void renderNativeBody(MethodHolder method, ClassReaderSource classes, boolean isFunction) {
var reference = method.getReference(); var reference = method.getReference();
var generator = generators.get(reference); var generator = generators.get(reference);
if (generator == null) { if (generator == null) {
@ -841,7 +850,7 @@ public class Renderer implements RenderingManager {
} }
var async = asyncMethods.contains(reference); var async = asyncMethods.contains(reference);
renderMethodPrologue(reference, method.getModifiers()); renderMethodPrologue(reference, method.getModifiers(), isFunction);
methodBodyRenderer.renderNative(generator, async, reference); methodBodyRenderer.renderNative(generator, async, reference);
threadLibraryUsed |= methodBodyRenderer.isThreadLibraryUsed(); threadLibraryUsed |= methodBodyRenderer.isThreadLibraryUsed();
} }
@ -895,7 +904,7 @@ public class Renderer implements RenderingManager {
} }
} }
private void renderRegularBody(MethodHolder method, Decompiler decompiler) { private void renderRegularBody(MethodHolder method, Decompiler decompiler, boolean isFunction) {
MethodReference reference = method.getReference(); MethodReference reference = method.getReference();
MethodNode node; MethodNode node;
var async = asyncMethods.contains(reference); var async = asyncMethods.contains(reference);
@ -907,14 +916,17 @@ public class Renderer implements RenderingManager {
} }
methodBodyRenderer.setCurrentMethod(node); methodBodyRenderer.setCurrentMethod(node);
renderMethodPrologue(method.getReference(), method.getModifiers()); renderMethodPrologue(method.getReference(), method.getModifiers(), isFunction);
methodBodyRenderer.render(node, async); methodBodyRenderer.render(node, async);
threadLibraryUsed |= methodBodyRenderer.isThreadLibraryUsed(); threadLibraryUsed |= methodBodyRenderer.isThreadLibraryUsed();
} }
private void renderMethodPrologue(MethodReference reference, Set<ElementModifier> modifier) { private void renderMethodPrologue(MethodReference reference, Set<ElementModifier> modifier, boolean isFunction) {
methodBodyRenderer.renderParameters(reference, modifier); methodBodyRenderer.renderParameters(reference, modifier, isFunction);
writer.sameLineWs().append("=>").ws().append("{").indent().softNewLine(); if (!isFunction) {
writer.sameLineWs().append("=>");
}
writer.ws().append("{").indent().softNewLine();
} }
private AstCacheEntry decompileRegular(Decompiler decompiler, MethodHolder method) { private AstCacheEntry decompileRegular(Decompiler decompiler, MethodHolder method) {

View File

@ -51,6 +51,7 @@ public abstract class RenderingContext {
private NamingStrategy naming; private NamingStrategy naming;
private DependencyInfo dependencyInfo; private DependencyInfo dependencyInfo;
private Predicate<MethodReference> virtualPredicate; private Predicate<MethodReference> virtualPredicate;
private Predicate<MethodReference> forcedFunctionPredicate;
private final Map<String, Integer> stringPoolMap = new HashMap<>(); private final Map<String, Integer> stringPoolMap = new HashMap<>();
private final List<String> stringPool = new ArrayList<>(); private final List<String> stringPool = new ArrayList<>();
private final List<String> readonlyStringPool = Collections.unmodifiableList(stringPool); private final List<String> readonlyStringPool = Collections.unmodifiableList(stringPool);
@ -63,7 +64,9 @@ public abstract class RenderingContext {
ClassReaderSource initialClassSource, ListableClassReaderSource classSource, ClassReaderSource initialClassSource, ListableClassReaderSource classSource,
ClassLoader classLoader, ServiceRepository services, Properties properties, ClassLoader classLoader, ServiceRepository services, Properties properties,
NamingStrategy naming, DependencyInfo dependencyInfo, NamingStrategy naming, DependencyInfo dependencyInfo,
Predicate<MethodReference> virtualPredicate, ClassInitializerInfo classInitializerInfo, Predicate<MethodReference> virtualPredicate,
Predicate<MethodReference> forcedFunctionPredicate,
ClassInitializerInfo classInitializerInfo,
boolean strict) { boolean strict) {
this.debugEmitter = debugEmitter; this.debugEmitter = debugEmitter;
this.initialClassSource = initialClassSource; this.initialClassSource = initialClassSource;
@ -74,6 +77,7 @@ public abstract class RenderingContext {
this.naming = naming; this.naming = naming;
this.dependencyInfo = dependencyInfo; this.dependencyInfo = dependencyInfo;
this.virtualPredicate = virtualPredicate; this.virtualPredicate = virtualPredicate;
this.forcedFunctionPredicate = forcedFunctionPredicate;
this.classInitializerInfo = classInitializerInfo; this.classInitializerInfo = classInitializerInfo;
this.strict = strict; this.strict = strict;
} }
@ -118,6 +122,10 @@ public abstract class RenderingContext {
return virtualPredicate.test(method); return virtualPredicate.test(method);
} }
public boolean isForcedFunction(MethodReference method) {
return forcedFunctionPredicate.test(method);
}
public boolean isDynamicInitializer(String className) { public boolean isDynamicInitializer(String className) {
return classInitializerInfo.isDynamicInitializer(className); return classInitializerInfo.isDynamicInitializer(className);
} }

View File

@ -17,6 +17,6 @@ package org.teavm.backend.javascript.spi;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
public interface VirtualMethodContributor { public interface MethodContributor {
boolean isVirtual(VirtualMethodContributorContext context, MethodReference methodRef); boolean isContributing(MethodContributorContext context, MethodReference methodRef);
} }

View File

@ -17,6 +17,6 @@ package org.teavm.backend.javascript.spi;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ClassReaderSource;
public interface VirtualMethodContributorContext { public interface MethodContributorContext {
ClassReaderSource getClassSource(); ClassReaderSource getClassSource();
} }

View File

@ -119,4 +119,12 @@ public interface ClassReaderSource {
default Optional<Boolean> isSuperType(String superType, String subType) { default Optional<Boolean> isSuperType(String superType, String subType) {
return ClassReaderSourceHelper.isSuperType(this, superType, subType); return ClassReaderSourceHelper.isSuperType(this, superType, subType);
} }
default MethodReader getMethod(MethodReference ref) {
var cls = get(ref.getClassName());
if (cls == null) {
return null;
}
return cls.getMethod(ref.getDescriptor());
}
} }

View File

@ -40,6 +40,12 @@ let $rt_wrapFunction3 = f => function(p1, p2, p3) {
let $rt_wrapFunction4 = f => function(p1, p2, p3, p4) { let $rt_wrapFunction4 = f => function(p1, p2, p3, p4) {
return f(this, p1, p2, p3, p4); return f(this, p1, p2, p3, p4);
} }
let $rt_wrapFunctionVararg = f => function() {
let array = new teavm_globals.Array();
array.push(this);
Array.prototype.push.apply(array, arguments);
return f.apply(null, array);
}
let $rt_threadStarter = f => function() { let $rt_threadStarter = f => function() {
let args = teavm_globals.Array.prototype.slice.apply(arguments); let args = teavm_globals.Array.prototype.slice.apply(arguments);
$rt_startThread(function() { $rt_startThread(function() {
@ -110,4 +116,6 @@ let $rt_setThread = t => {
let $rt_apply = (instance, method, args) => instance[method].apply(instance, args); let $rt_apply = (instance, method, args) => instance[method].apply(instance, args);
let $rt_apply_topLevel = (method, args) => method.apply(null, args); let $rt_apply_topLevel = (method, args) => method.apply(null, args);
let $rt_skip = (array, count) => count === 0 ? array : Array.prototype.slice.call(array, count);

View File

@ -716,4 +716,8 @@ final class JS {
@InjectedBy(JSNativeInjector.class) @InjectedBy(JSNativeInjector.class)
@NoSideEffects @NoSideEffects
public static native JSObject throwCCEIfFalse(boolean value, JSObject o); public static native JSObject throwCCEIfFalse(boolean value, JSObject o);
@InjectedBy(JSNativeInjector.class)
@NoSideEffects
public static native JSObject argumentsBeginningAt(int index);
} }

View File

@ -20,8 +20,8 @@ import java.util.Map;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.rendering.RenderingManager; import org.teavm.backend.javascript.rendering.RenderingManager;
import org.teavm.backend.javascript.spi.VirtualMethodContributor; import org.teavm.backend.javascript.spi.MethodContributor;
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext; import org.teavm.backend.javascript.spi.MethodContributorContext;
import org.teavm.jso.JSClass; import org.teavm.jso.JSClass;
import org.teavm.model.AnnotationReader; import org.teavm.model.AnnotationReader;
import org.teavm.model.ClassReader; import org.teavm.model.ClassReader;
@ -35,7 +35,7 @@ import org.teavm.model.MethodReference;
import org.teavm.vm.BuildTarget; import org.teavm.vm.BuildTarget;
import org.teavm.vm.spi.RendererListener; import org.teavm.vm.spi.RendererListener;
class JSAliasRenderer implements RendererListener, VirtualMethodContributor { class JSAliasRenderer implements RendererListener, MethodContributor {
private static String variableChars = "abcdefghijklmnopqrstuvwxyz"; private static String variableChars = "abcdefghijklmnopqrstuvwxyz";
private SourceWriter writer; private SourceWriter writer;
private ListableClassReaderSource classSource; private ListableClassReaderSource classSource;
@ -389,7 +389,7 @@ class JSAliasRenderer implements RendererListener, VirtualMethodContributor {
} }
@Override @Override
public boolean isVirtual(VirtualMethodContributorContext context, MethodReference methodRef) { public boolean isContributing(MethodContributorContext context, MethodReference methodRef) {
ClassReader classReader = context.getClassSource().get(methodRef.getClassName()); ClassReader classReader = context.getClassSource().get(methodRef.getClassName());
if (classReader == null) { if (classReader == null) {
return false; return false;

View File

@ -0,0 +1,34 @@
/*
* 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.backend.javascript.spi.MethodContributor;
import org.teavm.backend.javascript.spi.MethodContributorContext;
import org.teavm.model.MethodReference;
public class JSExportedMethodAsFunction implements MethodContributor {
@Override
public boolean isContributing(MethodContributorContext context, MethodReference methodRef) {
var method = context.getClassSource().getMethod(methodRef);
if (method != null) {
if (method.getAnnotations().get(JSVararg.class.getName()) != null) {
return true;
}
}
return false;
}
}

View File

@ -131,6 +131,8 @@ final class JSMethods {
JSObject.class, boolean.class); JSObject.class, boolean.class);
public static final MethodReference THROW_CCE_IF_FALSE = new MethodReference(JS.class, "throwCCEIfFalse", public static final MethodReference THROW_CCE_IF_FALSE = new MethodReference(JS.class, "throwCCEIfFalse",
boolean.class, JSObject.class, JSObject.class); boolean.class, JSObject.class, JSObject.class);
public static final MethodReference ARGUMENTS_BEGINNING_AT = new MethodReference(JS.class,
"argumentsBeginningAt", int.class, JSObject.class);
public static final ValueType JS_OBJECT = ValueType.object(JSObject.class.getName()); public static final ValueType JS_OBJECT = ValueType.object(JSObject.class.getName());
public static final ValueType OBJECT = ValueType.object("java.lang.Object"); public static final ValueType OBJECT = ValueType.object("java.lang.Object");

View File

@ -237,6 +237,12 @@ public class JSNativeInjector implements Injector, DependencyPlugin {
writer.append(")"); writer.append(")");
break; break;
} }
case "argumentsBeginningAt": {
writer.appendFunction("$rt_skip").append("(arguments,").ws();
context.writeExpr(context.getArgument(0), Precedence.min());
writer.append(")");
break;
}
default: default:
if (methodRef.getName().startsWith("unwrap")) { if (methodRef.getName().startsWith("unwrap")) {

View File

@ -48,6 +48,7 @@ public class JSOPlugin implements TeaVMPlugin {
jsHost.addInjectorProvider(new GeneratorAnnotationInstaller<>(new JSBodyGenerator(), jsHost.addInjectorProvider(new GeneratorAnnotationInstaller<>(new JSBodyGenerator(),
DynamicInjector.class.getName())); DynamicInjector.class.getName()));
jsHost.addVirtualMethods(aliasRenderer); jsHost.addVirtualMethods(aliasRenderer);
jsHost.addForcedFunctionMethods(new JSExportedMethodAsFunction());
JSExceptionsGenerator exceptionsGenerator = new JSExceptionsGenerator(); JSExceptionsGenerator exceptionsGenerator = new JSExceptionsGenerator();
jsHost.add(new MethodReference(JSExceptions.class, "getJavaException", JSObject.class, Throwable.class), jsHost.add(new MethodReference(JSExceptions.class, "getJavaException", JSObject.class, Throwable.class),

View File

@ -49,6 +49,7 @@ import org.teavm.model.Program;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
import org.teavm.model.Variable; import org.teavm.model.Variable;
import org.teavm.model.instructions.ExitInstruction; import org.teavm.model.instructions.ExitInstruction;
import org.teavm.model.instructions.IntegerConstantInstruction;
import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.InvokeInstruction;
@ -111,13 +112,18 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
private void exposeMethods(ClassHolder classHolder, ExposedClass classToExpose, Diagnostics diagnostics, private void exposeMethods(ClassHolder classHolder, ExposedClass classToExpose, Diagnostics diagnostics,
MethodReference functorMethod) { MethodReference functorMethod) {
int index = 0; int index = 0;
for (MethodDescriptor method : classToExpose.methods.keySet()) { for (var entry : classToExpose.methods.entrySet()) {
var method = entry.getKey();
var export = entry.getValue();
MethodReference methodRef = new MethodReference(classHolder.getName(), method); MethodReference methodRef = new MethodReference(classHolder.getName(), method);
CallLocation callLocation = new CallLocation(methodRef); CallLocation callLocation = new CallLocation(methodRef);
ValueType[] exportedMethodSignature = Arrays.stream(method.getSignature()) var paramCount = method.parameterCount();
.map(type -> ValueType.object(JSObject.class.getName())) if (export.vararg) {
.toArray(ValueType[]::new); --paramCount;
}
var exportedMethodSignature = new ValueType[paramCount + 1];
Arrays.fill(exportedMethodSignature, JSMethods.JS_OBJECT);
MethodDescriptor exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++, MethodDescriptor exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++,
exportedMethodSignature); exportedMethodSignature);
MethodHolder exportedMethod = new MethodHolder(exportedMethodDesc); MethodHolder exportedMethod = new MethodHolder(exportedMethodDesc);
@ -134,10 +140,15 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
for (int i = 0; i < method.parameterCount(); ++i) { for (int i = 0; i < method.parameterCount(); ++i) {
variablesToPass[i] = program.createVariable(); variablesToPass[i] = program.createVariable();
} }
if (export.vararg) {
transformVarargParam(variablesToPass, program, marshallInstructions, exportedMethod, 1);
}
for (int i = 0; i < method.parameterCount(); ++i) { for (int i = 0; i < method.parameterCount(); ++i) {
var byRef = i == method.parameterCount() - 1 && export.vararg
&& typeHelper.isSupportedByRefType(method.parameterType(i));
variablesToPass[i] = marshaller.unwrapReturnValue(callLocation, variablesToPass[i], variablesToPass[i] = marshaller.unwrapReturnValue(callLocation, variablesToPass[i],
method.parameterType(i), false, true); method.parameterType(i), byRef, true);
} }
basicBlock.addAll(marshallInstructions); basicBlock.addAll(marshallInstructions);
@ -161,8 +172,6 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
basicBlock.add(exit); basicBlock.add(exit);
classHolder.addMethod(exportedMethod); classHolder.addMethod(exportedMethod);
var export = classToExpose.methods.get(method);
exportedMethod.getAnnotations().add(createExportAnnotation(export)); exportedMethod.getAnnotations().add(createExportAnnotation(export));
if (methodRef.equals(functorMethod)) { if (methodRef.equals(functorMethod)) {
@ -179,10 +188,14 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
continue; continue;
} }
var paramCount = method.parameterCount();
var vararg = method.hasModifier(ElementModifier.VARARGS);
if (vararg) {
--paramCount;
}
var callLocation = new CallLocation(method.getReference()); var callLocation = new CallLocation(method.getReference());
var exportedMethodSignature = Arrays.stream(method.getSignature()) var exportedMethodSignature = new ValueType[paramCount + 1];
.map(type -> ValueType.object(JSObject.class.getName())) Arrays.fill(exportedMethodSignature, JSMethods.JS_OBJECT);
.toArray(ValueType[]::new);
var exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++, var exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++,
exportedMethodSignature); exportedMethodSignature);
var exportedMethod = new MethodHolder(exportedMethodDesc); var exportedMethod = new MethodHolder(exportedMethodDesc);
@ -200,10 +213,15 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
for (int i = 0; i < method.parameterCount(); ++i) { for (int i = 0; i < method.parameterCount(); ++i) {
variablesToPass[i] = program.createVariable(); variablesToPass[i] = program.createVariable();
} }
if (vararg) {
transformVarargParam(variablesToPass, program, marshallInstructions, exportedMethod, 0);
}
for (int i = 0; i < method.parameterCount(); ++i) { for (int i = 0; i < method.parameterCount(); ++i) {
var byRef = i == method.parameterCount() - 1 && vararg
&& typeHelper.isSupportedByRefType(method.parameterType(i));
variablesToPass[i] = marshaller.unwrapReturnValue(callLocation, variablesToPass[i], variablesToPass[i] = marshaller.unwrapReturnValue(callLocation, variablesToPass[i],
method.parameterType(i), false, true); method.parameterType(i), byRef, true);
} }
basicBlock.addAll(marshallInstructions); basicBlock.addAll(marshallInstructions);
@ -232,6 +250,25 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
} }
} }
private void transformVarargParam(Variable[] variablesToPass, Program program,
List<Instruction> instructions, MethodHolder method, int additionalSkip) {
var last = variablesToPass.length - 1;
var lastConstant = new IntegerConstantInstruction();
lastConstant.setReceiver(program.createVariable());
lastConstant.setConstant(last + additionalSkip);
instructions.add(lastConstant);
var extractVarargs = new InvokeInstruction();
extractVarargs.setType(InvocationType.SPECIAL);
extractVarargs.setMethod(JSMethods.ARGUMENTS_BEGINNING_AT);
extractVarargs.setArguments(lastConstant.getReceiver());
extractVarargs.setReceiver(variablesToPass[last]);
instructions.add(extractVarargs);
method.getAnnotations().add(new AnnotationHolder(JSVararg.class.getName()));
}
private AnnotationHolder createExportAnnotation(MethodExport export) { private AnnotationHolder createExportAnnotation(MethodExport export) {
String annotationName; String annotationName;
switch (export.kind) { switch (export.kind) {
@ -403,7 +440,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
name = method.getName(); name = method.getName();
} }
} }
return new MethodExport(name, kind); return new MethodExport(name, kind, method.hasModifier(ElementModifier.VARARGS));
} }
private void addFunctorField(ClassHolder cls, MethodReference method) { private void addFunctorField(ClassHolder cls, MethodReference method) {
@ -437,10 +474,12 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
static class MethodExport { static class MethodExport {
final String alias; final String alias;
final MethodKind kind; final MethodKind kind;
boolean vararg;
MethodExport(String alias, MethodKind kind) { MethodExport(String alias, MethodKind kind, boolean vararg) {
this.alias = alias; this.alias = alias;
this.kind = kind; this.kind = kind;
this.vararg = vararg;
} }
} }
} }

View File

@ -121,8 +121,6 @@ class JSTypeHelper {
default: default:
return false; return false;
} }
} else if (itemType instanceof ValueType.Object) {
return isJavaScriptClass(((ValueType.Object) itemType).getClassName());
} else { } else {
return false; return false;
} }

View File

@ -0,0 +1,26 @@
/*
* 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.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
@interface JSVararg {
}

View File

@ -18,8 +18,8 @@ package org.teavm.platform.plugin;
import org.teavm.backend.javascript.codegen.SourceWriter; import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.spi.Generator; import org.teavm.backend.javascript.spi.Generator;
import org.teavm.backend.javascript.spi.GeneratorContext; import org.teavm.backend.javascript.spi.GeneratorContext;
import org.teavm.backend.javascript.spi.VirtualMethodContributor; import org.teavm.backend.javascript.spi.MethodContributor;
import org.teavm.backend.javascript.spi.VirtualMethodContributorContext; import org.teavm.backend.javascript.spi.MethodContributorContext;
import org.teavm.backend.javascript.templating.JavaScriptTemplate; import org.teavm.backend.javascript.templating.JavaScriptTemplate;
import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory; import org.teavm.backend.javascript.templating.JavaScriptTemplateFactory;
import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyAgent;
@ -34,7 +34,7 @@ import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
public class AsyncMethodGenerator implements Generator, DependencyPlugin, VirtualMethodContributor { public class AsyncMethodGenerator implements Generator, DependencyPlugin, MethodContributor {
private static final MethodDescriptor completeMethod = new MethodDescriptor("complete", Object.class, void.class); private static final MethodDescriptor completeMethod = new MethodDescriptor("complete", Object.class, void.class);
private static final MethodDescriptor errorMethod = new MethodDescriptor("error", Throwable.class, void.class); private static final MethodDescriptor errorMethod = new MethodDescriptor("error", Throwable.class, void.class);
private JavaScriptTemplate template; private JavaScriptTemplate template;
@ -101,7 +101,7 @@ public class AsyncMethodGenerator implements Generator, DependencyPlugin, Virtua
} }
@Override @Override
public boolean isVirtual(VirtualMethodContributorContext context, MethodReference methodRef) { public boolean isContributing(MethodContributorContext context, MethodReference methodRef) {
ClassReader cls = context.getClassSource().get(methodRef.getClassName()); ClassReader cls = context.getClassSource().get(methodRef.getClassName());
if (cls == null) { if (cls == null) {
return false; return false;

View File

@ -82,6 +82,11 @@ public class ExportTest {
testExport("exportClasses", ModuleWithExportedClasses.class); testExport("exportClasses", ModuleWithExportedClasses.class);
} }
@Test
public void varargs() {
testExport("varargs", ModuleWithVararg.class);
}
private void testExport(String name, Class<?> moduleClass) { private void testExport(String name, Class<?> moduleClass) {
if (!Boolean.parseBoolean(System.getProperty("teavm.junit.js", "true"))) { if (!Boolean.parseBoolean(System.getProperty("teavm.junit.js", "true"))) {
return; return;
@ -89,6 +94,7 @@ public class ExportTest {
try { try {
var jsTarget = new JavaScriptTarget(); var jsTarget = new JavaScriptTarget();
jsTarget.setModuleType(JSModuleType.ES2015); jsTarget.setModuleType(JSModuleType.ES2015);
jsTarget.setObfuscated(false);
var teavm = new TeaVMBuilder(jsTarget).build(); var teavm = new TeaVMBuilder(jsTarget).build();
var outputDir = new File(targetFile, name); var outputDir = new File(targetFile, name);
teavm.installPlugins(); teavm.installPlugins();

View File

@ -0,0 +1,90 @@
/*
* 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.export;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.teavm.jso.JSExport;
import org.teavm.jso.JSExportClasses;
import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty;
import org.testng.util.Strings;
@JSExportClasses(ModuleWithVararg.A.class)
public class ModuleWithVararg {
private ModuleWithVararg() {
}
@JSExport
public static String strings(String... values) {
var sb = new StringBuilder();
sb.append("strings: ");
sb.append(Strings.join(", ", values));
return sb.toString();
}
@JSExport
public static String prefixStrings(int a, String... values) {
var sb = new StringBuilder();
sb.append("strings(").append(a).append("): ");
sb.append(Strings.join(", ", values));
return sb.toString();
}
@JSExport
public static String ints(int... values) {
var sb = new StringBuilder();
sb.append("ints: ");
sb.append(IntStream.of(values).mapToObj(Integer::toString).collect(Collectors.joining(", ")));
return sb.toString();
}
@JSExport
public static String prefixInts(String prefix, int... values) {
var sb = new StringBuilder();
sb.append("ints(").append(prefix).append("): ");
sb.append(IntStream.of(values).mapToObj(Integer::toString).collect(Collectors.joining(", ")));
return sb.toString();
}
@JSExport
public static String objects(ObjectWithString... values) {
var sb = new StringBuilder();
sb.append("objects: ");
sb.append(Stream.of(values).map(v -> v.getStringValue()).collect(Collectors.joining(", ")));
return sb.toString();
}
public interface ObjectWithString extends JSObject {
@JSProperty
String getStringValue();
}
public static class A {
@JSExport
public A() {
}
@JSExport
public String strings(String... values) {
var sb = new StringBuilder();
sb.append("A.strings: ");
sb.append(Strings.join(", ", values));
return sb.toString();
}
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.
*/
import * as varargs from '/tests/varargs/test.js';
export async function test() {
assertEquals("strings: a, b", varargs.strings("a", "b"));
assertEquals("strings(23): a, b", varargs.prefixStrings(23, "a", "b"));
assertEquals("ints: 23, 42", varargs.ints(23, 42));
assertEquals("ints(*): 23, 42", varargs.prefixInts("*", 23, 42));
assertEquals("objects: a, b", varargs.objects({ stringValue: "a" }, { stringValue: "b" }));
let obj = new varargs.A();
assertEquals("A.strings: a, b", obj.strings("a", "b"));
}