jso: implement vararg support for method calls

This commit is contained in:
Alexey Andreev 2024-03-08 21:06:08 +01:00
parent ccfe19994b
commit 582fcf904c
16 changed files with 512 additions and 25 deletions

View File

@ -125,7 +125,7 @@ public class Decompiler {
methodNode.getVariables().add(variable);
}
Optimizer optimizer = new Optimizer();
Optimizer optimizer = new Optimizer(classSource);
optimizer.optimize(methodNode, method.getProgram(), friendlyToDebugger);
methodNode.getModifiers().addAll(method.getModifiers());
@ -158,7 +158,7 @@ public class Decompiler {
node.getVariables().add(variable);
}
Optimizer optimizer = new Optimizer();
Optimizer optimizer = new Optimizer(classSource);
optimizer.optimize(node, splitter, friendlyToDebugger);
node.getModifiers().addAll(method.getModifiers());

View File

@ -21,6 +21,7 @@ import org.teavm.ast.AsyncMethodPart;
import org.teavm.ast.RegularMethodNode;
import org.teavm.common.Graph;
import org.teavm.model.BasicBlock;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.Instruction;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
@ -32,6 +33,12 @@ import org.teavm.model.util.ProgramUtils;
import org.teavm.model.util.UsageExtractor;
public class Optimizer {
private ClassReaderSource classes;
public Optimizer(ClassReaderSource classes) {
this.classes = classes;
}
public void optimize(RegularMethodNode method, Program program, boolean friendlyToDebugger) {
ReadWriteStatsBuilder stats = new ReadWriteStatsBuilder(method.getVariables().size());
stats.analyze(program);
@ -48,7 +55,7 @@ public class Optimizer {
}
}
OptimizingVisitor optimizer = new OptimizingVisitor(preservedVars, stats.writes, stats.reads,
stats.constants, friendlyToDebugger);
stats.constants, friendlyToDebugger, classes);
method.getBody().acceptVisitor(optimizer);
method.setBody(optimizer.resultStmt);
int paramCount = method.getReference().parameterCount();
@ -85,7 +92,7 @@ public class Optimizer {
BreakEliminator breakEliminator = new BreakEliminator();
breakEliminator.eliminate(part.getStatement());
OptimizingVisitor optimizer = new OptimizingVisitor(preservedVars, stats.writes, stats.reads,
stats.constants, friendlyToDebugger);
stats.constants, friendlyToDebugger, classes);
part.getStatement().acceptVisitor(optimizer);
part.setStatement(optimizer.resultStmt);
}

View File

@ -43,6 +43,7 @@ import org.teavm.ast.IdentifiedStatement;
import org.teavm.ast.InitClassStatement;
import org.teavm.ast.InstanceOfExpr;
import org.teavm.ast.InvocationExpr;
import org.teavm.ast.InvocationType;
import org.teavm.ast.MonitorEnterStatement;
import org.teavm.ast.MonitorExitStatement;
import org.teavm.ast.NewArrayExpr;
@ -64,6 +65,9 @@ import org.teavm.ast.UnaryOperation;
import org.teavm.ast.UnwrapArrayExpr;
import org.teavm.ast.VariableExpr;
import org.teavm.ast.WhileStatement;
import org.teavm.interop.NoSideEffects;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.MethodReference;
import org.teavm.model.TextLocation;
import org.teavm.model.ValueType;
@ -82,15 +86,17 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
private Deque<TextLocation> locationStack = new LinkedList<>();
private Deque<TextLocation> notNullLocationStack = new ArrayDeque<>();
private List<ArrayOptimization> pendingArrayOptimizations;
private ClassReaderSource classes;
OptimizingVisitor(boolean[] preservedVars, int[] writeFrequencies, int[] readFrequencies, Object[] constants,
boolean friendlyToDebugger) {
boolean friendlyToDebugger, ClassReaderSource classes) {
this.preservedVars = preservedVars;
this.writeFrequencies = writeFrequencies;
this.initialWriteFrequencies = writeFrequencies.clone();
this.readFrequencies = readFrequencies;
this.constants = constants;
this.friendlyToDebugger = friendlyToDebugger;
this.classes = classes;
}
private static boolean isZero(Expr expr) {
@ -385,6 +391,23 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
}
}
private boolean isSideEffectFree(MethodReference method) {
var cls = classes.get(method.getClassName());
if (cls == null) {
return false;
}
var methodReader = cls.getMethod(method.getDescriptor());
if (methodReader == null) {
return false;
}
return methodReader.getAnnotations().get(NoSideEffects.class.getName()) != null;
}
private boolean isSideEffectFreeCall(InvocationExpr expr) {
return (expr.getType() == InvocationType.SPECIAL || expr.getType() == InvocationType.STATIC)
&& isSideEffectFree(expr.getMethod());
}
@Override
public void visit(InvocationExpr expr) {
pushLocation(expr.getLocation());
@ -392,9 +415,13 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
Expr[] args = expr.getArguments().toArray(new Expr[0]);
int barrierPos;
for (barrierPos = 0; barrierPos < args.length; ++barrierPos) {
if (!isSideEffectFree(args[barrierPos])) {
break;
if (isSideEffectFreeCall(expr)) {
barrierPos = args.length;
} else {
for (barrierPos = 0; barrierPos < args.length; ++barrierPos) {
if (!isSideEffectFree(args[barrierPos])) {
break;
}
}
}
@ -639,8 +666,9 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
while (!pendingArrayOptimizations.isEmpty()) {
statement = resultSequence.get(resultSequence.size() - 1);
int i = pendingArrayOptimizations.size() - 1;
if (!tryArrayUnwrap(pendingArrayOptimizations.get(i), statement)
|| tryArraySet(pendingArrayOptimizations.get(i), statement)) {
var opt = pendingArrayOptimizations.get(i);
var result = opt.isSet ? tryArraySet(opt, statement) : tryArrayUnwrap(opt, statement);
if (!result) {
pendingArrayOptimizations.remove(i);
} else {
break;
@ -721,11 +749,9 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
return false;
}
if (optimization.arraySize != readFrequencies[optimization.unwrappedArrayVariable]) {
return false;
}
optimization.arrayElementIndex = 0;
optimization.remainingUseCount = readFrequencies[optimization.unwrappedArrayVariable];
optimization.isSet = true;
return true;
}
@ -756,10 +782,22 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
return false;
}
if (!(subscript.getIndex() instanceof ConstantExpr)) {
var index = subscript.getIndex();
if (index instanceof BoundCheckExpr) {
var boundCheck = (BoundCheckExpr) index;
if (!(boundCheck.getArray() instanceof VariableExpr)) {
return false;
}
if (((VariableExpr) boundCheck.getArray()).getIndex() != optimization.unwrappedArrayVariable) {
return false;
}
index = boundCheck.getIndex();
optimization.remainingUseCount--;
}
if (!(index instanceof ConstantExpr)) {
return false;
}
Object constantValue = ((ConstantExpr) subscript.getIndex()).getValue();
Object constantValue = ((ConstantExpr) index).getValue();
if (!Integer.valueOf(optimization.arrayElementIndex).equals(constantValue)) {
return false;
}
@ -772,11 +810,14 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
}
optimization.elements.add(assign.getRightValue());
--optimization.remainingUseCount;
if (++optimization.arrayElementIndex == optimization.arraySize) {
applyArrayOptimization(optimization);
return true;
if (optimization.remainingUseCount == 0) {
applyArrayOptimization(optimization);
}
pendingArrayOptimizations.remove(pendingArrayOptimizations.size() - 1);
}
return false;
return true;
}
private void applyArrayOptimization(ArrayOptimization optimization) {
@ -1303,6 +1344,29 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
return isSideEffectFree(((PrimitiveCastExpr) expr).getValue());
}
if (expr instanceof ArrayFromDataExpr) {
var list = ((ArrayFromDataExpr) expr).getData();
for (var element : list) {
if (!isSideEffectFree(element)) {
return false;
}
}
return true;
}
if (expr instanceof InvocationExpr) {
var invocation = (InvocationExpr) expr;
if (isSideEffectFreeCall(invocation)) {
for (var arg : invocation.getArguments()) {
if (!isSideEffectFree(arg)) {
return false;
}
}
return true;
}
return false;
}
if (expr instanceof NewExpr) {
return true;
}
@ -1326,6 +1390,7 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
}
static class ArrayOptimization {
boolean isSet;
int index;
NewArrayExpr array;
int arrayVariable;
@ -1333,6 +1398,7 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor {
int unwrappedArrayVariable;
int arrayElementIndex;
int arraySize;
int remainingUseCount;
List<Expr> elements = new ArrayList<>();
}
}

View File

@ -85,7 +85,7 @@ public class InvokeInstruction extends Instruction {
private List<? extends Variable> argumentList = new AbstractList<Variable>() {
@Override
public Variable get(int index) {
if (arguments == null) {
if (arguments == null || index >= arguments.length) {
throw new IndexOutOfBoundsException();
}
return arguments[index];

View File

@ -37,6 +37,7 @@ import org.teavm.model.instructions.ConstructArrayInstruction;
import org.teavm.model.instructions.GetElementInstruction;
import org.teavm.model.instructions.IntegerConstantInstruction;
import org.teavm.model.instructions.JumpInstruction;
import org.teavm.model.instructions.NullCheckInstruction;
import org.teavm.model.instructions.NumericOperandType;
import org.teavm.model.instructions.PutElementInstruction;
import org.teavm.model.instructions.UnwrapArrayInstruction;
@ -344,6 +345,11 @@ public class BoundCheckInsertion {
assign(insn.getArray(), insn.getReceiver());
}
@Override
public void visit(NullCheckInstruction insn) {
isConstantSizedArray[index(insn.getReceiver())] = isConstantSizedArray[index(insn.getValue())];
}
private void assign(Variable from, Variable to) {
map[to.getIndex()] = map[from.getIndex()];
}

View File

@ -496,7 +496,7 @@ public class Parser {
}
}
if ((access & Opcodes.ACC_VARARGS) != 0) {
if (type == DECL_FIELD) {
if (type == DECL_METHOD) {
member.getModifiers().add(ElementModifier.VARARGS);
}
}

View File

@ -280,3 +280,19 @@ let $rt_createMultiArrayImpl = (cls, arrays, dimensions, start) => {
}
return arrays[0];
}
let $rt_concatArrays = (a, b) => {
if (a.length === 0) {
return b;
}
if (b.length === 0) {
return a;
}
if (!teavm_globals.Array.isArray(a)) {
a = teavm_globals.Array.from(a);
}
if (!teavm_globals.Array.isArray(b)) {
b = teavm_globals.Array.from(b);
}
return a.concat(b);
}

View File

@ -107,3 +107,5 @@ let $rt_setThread = t => {
return teavm_javaMethod("java.lang.Thread", "setCurrentThread(Ljava/lang/Thread;)V")(t);
}
}
let $rt_apply = (instance, method, args) => instance[method].apply(instance, args);

View File

@ -21,6 +21,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
@Target({ ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR })
public @interface NoSideEffects {
}

View File

@ -23,9 +23,11 @@ import org.teavm.jso.JSProperty;
@JSClass(name = "Array")
public class JSArray<T> implements JSArrayReader<T> {
@NoSideEffects
public JSArray(int size) {
}
@NoSideEffects
public JSArray() {
}

View File

@ -36,6 +36,10 @@ final class JS {
@NoSideEffects
public static native JSObject arrayData(Object array);
@InjectedBy(JSNativeInjector.class)
@NoSideEffects
public static native JSObject concatArray(JSObject a, JSObject b);
@InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeInjector.class)
@NoSideEffects
@ -249,6 +253,7 @@ final class JS {
return JS::wrap;
}
@NoSideEffects
public static JSArray<JSString> wrap(String[] array) {
if (array == null) {
return null;
@ -516,6 +521,80 @@ final class JS {
JSObject d, JSObject e, JSObject f, JSObject g, JSObject h, JSObject i, JSObject j, JSObject k,
JSObject l, JSObject m);
@InjectedBy(JSNativeInjector.class)
public static native JSObject apply(JSObject instance, JSObject method, JSArray<JSObject> v);
@InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeInjector.class)
@NoSideEffects
public static native JSObject arrayOf(JSObject a);
@InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeInjector.class)
@NoSideEffects
public static native JSObject arrayOf(JSObject a, JSObject b);
@InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeInjector.class)
@NoSideEffects
public static native JSObject arrayOf(JSObject a, JSObject b, JSObject c);
@InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeInjector.class)
@NoSideEffects
public static native JSObject arrayOf(JSObject a, JSObject b, JSObject c, JSObject d);
@InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeInjector.class)
@NoSideEffects
public static native JSObject arrayOf(JSObject a, JSObject b, JSObject c, JSObject d, JSObject e);
@InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeInjector.class)
@NoSideEffects
public static native JSObject arrayOf(JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f);
@InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeInjector.class)
@NoSideEffects
public static native JSObject arrayOf(JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f,
JSObject g);
@InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeInjector.class)
@NoSideEffects
public static native JSObject arrayOf(JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f,
JSObject g, JSObject h);
@InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeInjector.class)
public static native JSObject arrayOf(JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f,
JSObject g, JSObject h, JSObject i);
@InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeInjector.class)
@NoSideEffects
public static native JSObject arrayOf(JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f,
JSObject g, JSObject h, JSObject i, JSObject j);
@InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeInjector.class)
@NoSideEffects
public static native JSObject arrayOf(JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f,
JSObject g, JSObject h, JSObject i, JSObject j, JSObject k);
@InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeInjector.class)
@NoSideEffects
public static native JSObject arrayOf(JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f,
JSObject g, JSObject h, JSObject i, JSObject j, JSObject k, JSObject l);
@InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeInjector.class)
@NoSideEffects
public static native JSObject arrayOf(JSObject a, JSObject b, JSObject c, JSObject d, JSObject e, JSObject f,
JSObject g, JSObject h, JSObject i, JSObject j, JSObject k, JSObject l, JSObject m);
@InjectedBy(JSNativeInjector.class)
@PluggableDependency(JSNativeInjector.class)
public static native JSObject construct(JSObject cls);

View File

@ -769,23 +769,64 @@ class JSClassProcessor {
return false;
}
var vararg = method.hasModifier(ElementModifier.VARARGS);
Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
InvokeInstruction newInvoke = new InvokeInstruction();
newInvoke.setMethod(JSMethods.invoke(method.parameterCount()));
newInvoke.setMethod(vararg ? JSMethods.APPLY : JSMethods.invoke(method.parameterCount()));
newInvoke.setType(InvocationType.SPECIAL);
newInvoke.setReceiver(result);
List<Variable> newArguments = new ArrayList<>();
newArguments.add(getCallTarget(invoke));
newArguments.add(marshaller.addStringWrap(marshaller.addString(name, invoke.getLocation()),
invoke.getLocation()));
newInvoke.setLocation(invoke.getLocation());
var callArguments = new ArrayList<Variable>();
for (int i = 0; i < invoke.getArguments().size(); ++i) {
var arg = invoke.getArguments().get(i);
var byRef = byRefParams[i];
if (vararg && i == invoke.getArguments().size() - 1
&& typeHelper.isSupportedByRefType(method.parameterType(i))) {
byRef = true;
}
arg = marshaller.wrapArgument(callLocation, arg,
method.parameterType(i), types.typeOf(arg), byRefParams[i]);
newArguments.add(arg);
method.parameterType(i), types.typeOf(arg), byRef);
callArguments.add(arg);
}
if (vararg) {
Variable prefixArg = null;
if (callArguments.size() > 1) {
var arrayOfInvocation = new InvokeInstruction();
arrayOfInvocation.setType(InvocationType.SPECIAL);
arrayOfInvocation.setArguments(callArguments.subList(0, callArguments.size() - 1)
.toArray(new Variable[0]));
arrayOfInvocation.setMethod(JSMethods.arrayOf(callArguments.size() - 1));
arrayOfInvocation.setReceiver(program.createVariable());
arrayOfInvocation.setLocation(invoke.getLocation());
replacement.add(arrayOfInvocation);
prefixArg = arrayOfInvocation.getReceiver();
}
var arrayArg = callArguments.get(callArguments.size() - 1);
if (prefixArg != null) {
var concat = new InvokeInstruction();
concat.setType(InvocationType.SPECIAL);
concat.setArguments(prefixArg, arrayArg);
concat.setMethod(JSMethods.CONCAT_ARRAY);
concat.setReceiver(program.createVariable());
concat.setLocation(invoke.getLocation());
replacement.add(concat);
arrayArg = concat.getReceiver();
}
newArguments.add(arrayArg);
} else {
newArguments.addAll(callArguments);
}
newInvoke.setArguments(newArguments.toArray(new Variable[0]));
replacement.add(newInvoke);
if (result != null) {
result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType(), false,

View File

@ -31,10 +31,14 @@ final class JSMethods {
JSObject.class, void.class);
public static final MethodReference SET_PURE = new MethodReference(JS.class, "setPure", JSObject.class,
JSObject.class, JSObject.class, void.class);
public static final MethodReference APPLY = new MethodReference(JS.class, "apply", JSObject.class, JSObject.class,
JSArray.class, JSObject.class);
public static final MethodReference FUNCTION = new MethodReference(JS.class, "function", JSObject.class,
JSObject.class, JSObject.class);
public static final MethodReference ARRAY_DATA = new MethodReference(JS.class, "arrayData",
Object.class, JSObject.class);
public static final MethodReference CONCAT_ARRAY = new MethodReference(JS.class, "concatArray",
JSObject.class, JSObject.class, JSObject.class);
public static final MethodReference ARRAY_MAPPER = new MethodReference(JS.class, "arrayMapper",
JS.WrapFunction.class, JS.WrapFunction.class);
public static final MethodReference BOOLEAN_ARRAY_WRAPPER = new MethodReference(JS.class, "booleanArrayWrapper",
@ -111,6 +115,9 @@ final class JSMethods {
public static final MethodReference DATA_TO_ARRAY = new MethodReference(JS.class,
"dataToArray", JSObject.class, JSObject[].class);
public static final MethodReference WRAP_STRING = new MethodReference(JS.class, "wrap",
String.class, JSObject.class);
public static final MethodReference FUNCTION_AS_OBJECT = new MethodReference(JS.class, "functionAsObject",
JSObject.class, JSObject.class, JSObject.class);
@ -122,6 +129,7 @@ final class JSMethods {
public static final ValueType JS_ARRAY = ValueType.object(JSArray.class.getName());
private static final MethodReference[] INVOKE_METHODS = new MethodReference[13];
private static final MethodReference[] CONSTRUCT_METHODS = new MethodReference[13];
private static final MethodReference[] ARRAY_OF_METHODS = new MethodReference[13];
static {
for (int i = 0; i < INVOKE_METHODS.length; ++i) {
@ -132,6 +140,10 @@ final class JSMethods {
var constructSignature = new ValueType[i + 2];
Arrays.fill(constructSignature, JS_OBJECT);
CONSTRUCT_METHODS[i] = new MethodReference(JS.class.getName(), "construct", constructSignature);
var arrayOfSignature = new ValueType[i + 1];
Arrays.fill(arrayOfSignature, JS_OBJECT);
ARRAY_OF_METHODS[i] = new MethodReference(JS.class.getName(), "arrayOf", arrayOfSignature);
}
}
@ -145,4 +157,8 @@ final class JSMethods {
public static MethodReference construct(int parameterCount) {
return CONSTRUCT_METHODS[parameterCount];
}
public static MethodReference arrayOf(int parameterCount) {
return ARRAY_OF_METHODS[parameterCount];
}
}

View File

@ -16,11 +16,17 @@
package org.teavm.jso.impl;
import static org.teavm.backend.javascript.rendering.RenderingUtil.escapeString;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.teavm.ast.ArrayFromDataExpr;
import org.teavm.ast.ConstantExpr;
import org.teavm.ast.Expr;
import org.teavm.ast.InvocationExpr;
import org.teavm.ast.InvocationType;
import org.teavm.ast.NewArrayExpr;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.backend.javascript.rendering.Precedence;
import org.teavm.backend.javascript.spi.Injector;
@ -38,6 +44,7 @@ import org.teavm.model.ValueType;
public class JSNativeInjector implements Injector, DependencyPlugin {
private Set<MethodReference> reachedFunctorMethods = new HashSet<>();
private Set<DependencyNode> functorParamNodes = new HashSet<>();
private static final ValueType STRING_ARRAY = ValueType.arrayOf(ValueType.object("java.lang.String"));
@Override
public void generate(InjectorContext context, MethodReference methodRef) {
@ -47,6 +54,13 @@ public class JSNativeInjector implements Injector, DependencyPlugin {
context.writeExpr(context.getArgument(0));
writer.append(".data");
break;
case "concatArray":
writer.appendFunction("$rt_concatArrays").append("(");
context.writeExpr(context.getArgument(0), Precedence.ASSIGNMENT);
writer.append(",").ws();
context.writeExpr(context.getArgument(1), Precedence.ASSIGNMENT);
writer.append(")");
break;
case "get":
case "getPure":
context.writeExpr(context.getArgument(0), Precedence.MEMBER_ACCESS);
@ -71,6 +85,19 @@ public class JSNativeInjector implements Injector, DependencyPlugin {
}
writer.append(')');
break;
case "apply":
applyFunction(context);
break;
case "arrayOf":
writer.append('[');
for (int i = 0; i < context.argumentCount(); ++i) {
if (i > 0) {
writer.append(',').ws();
}
context.writeExpr(context.getArgument(i), Precedence.min());
}
writer.append(']');
break;
case "construct":
if (context.getPrecedence().ordinal() >= Precedence.FUNCTION_CALL.ordinal()) {
writer.append("(");
@ -172,6 +199,107 @@ public class JSNativeInjector implements Injector, DependencyPlugin {
}
}
private void applyFunction(InjectorContext context) {
if (tryApplyFunctionOptimized(context)) {
return;
}
var writer = context.getWriter();
writer.appendFunction("$rt_apply").append("(");
context.writeExpr(context.getArgument(0), Precedence.ASSIGNMENT);
writer.append(",").ws();
context.writeExpr(context.getArgument(1), Precedence.ASSIGNMENT);
writer.append(",").ws();
context.writeExpr(context.getArgument(2), Precedence.ASSIGNMENT);
writer.append(")");
}
private boolean tryApplyFunctionOptimized(InjectorContext context) {
var paramList = new ArrayList<Expr>();
if (!extractConstantArgList(context.getArgument(2), paramList) || paramList.size() >= 13) {
return false;
}
applyFunctionOptimized(context, paramList);
return true;
}
private boolean extractConstantArgList(Expr expr, List<Expr> target) {
if (!(expr instanceof InvocationExpr)) {
return false;
}
var invocation = (InvocationExpr) expr;
if (!invocation.getMethod().getClassName().equals(JS.class.getName())) {
return false;
}
switch (invocation.getMethod().getName()) {
case "arrayOf":
target.addAll(invocation.getArguments());
return true;
case "concatArray":
return extractConstantArgList(invocation.getArguments().get(0), target)
&& extractConstantArgList(invocation.getArguments().get(1), target);
case "arrayData": {
var arg = invocation.getArguments().get(0);
if (arg instanceof ArrayFromDataExpr) {
target.addAll(((ArrayFromDataExpr) arg).getData());
return true;
}
if (arg instanceof NewArrayExpr && isEmptyArrayConstructor((NewArrayExpr) arg)) {
return true;
}
break;
}
case "wrap": {
if (invocation.getMethod().parameterType(0).equals(STRING_ARRAY)) {
var arg = invocation.getArguments().get(0);
if (arg instanceof ArrayFromDataExpr) {
extractConstantStringArgList(((ArrayFromDataExpr) arg).getData(), target);
return true;
}
if (arg instanceof NewArrayExpr && isEmptyArrayConstructor((NewArrayExpr) arg)) {
return true;
}
}
break;
}
}
return false;
}
private boolean isEmptyArrayConstructor(NewArrayExpr expr) {
var length = expr.getLength();
if (!(length instanceof ConstantExpr)) {
return false;
}
return Objects.equals(((ConstantExpr) length).getValue(), 0);
}
private void extractConstantStringArgList(List<Expr> source, List<Expr> target) {
for (var element : source) {
var invocation = new InvocationExpr();
invocation.setType(InvocationType.STATIC);
invocation.setMethod(JSMethods.WRAP_STRING);
invocation.setLocation(element.getLocation());
invocation.getArguments().add(element);
target.add(invocation);
}
}
private void applyFunctionOptimized(InjectorContext context, List<Expr> paramList) {
var writer = context.getWriter();
context.writeExpr(context.getArgument(0), Precedence.GROUPING);
renderProperty(context.getArgument(1), context);
writer.append('(');
for (int i = 0; i < paramList.size(); ++i) {
if (i > 0) {
writer.append(',').ws();
}
context.writeExpr(paramList.get(i), Precedence.min());
}
writer.append(')');
}
private void dataToArray(InjectorContext context, String className) {
var writer = context.getWriter();
writer.appendFunction("$rt_wrapArray").append("(").appendFunction(className).append(",").ws();

View File

@ -0,0 +1,91 @@
/*
* 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.test;
import static org.junit.Assert.assertEquals;
import org.junit.runner.RunWith;
import org.teavm.jso.JSClass;
import org.teavm.jso.JSMethod;
import org.teavm.jso.JSObject;
import org.teavm.junit.AttachJavaScript;
import org.teavm.junit.EachTestCompiledSeparately;
import org.teavm.junit.OnlyPlatform;
import org.teavm.junit.SkipJVM;
import org.teavm.junit.TeaVMTestRunner;
import org.teavm.junit.TestPlatform;
import org.testng.annotations.Test;
@RunWith(TeaVMTestRunner.class)
@SkipJVM
@OnlyPlatform(TestPlatform.JAVASCRIPT)
@EachTestCompiledSeparately
public class CallTest {
@Test
@AttachJavaScript("org/teavm/jso/test/vararg.js")
public void simpleVararg() {
assertEquals("va:q:w", TestClass.allVararg("q", "w"));
assertEquals("va:23:42", TestClass.allVarargInt(23, 42));
var array = new String[3];
for (var i = 0; i < array.length; ++i) {
array[i] = String.valueOf((char) ('A' + i));
}
assertEquals("va:A:B:C", TestClass.allVararg(array));
var intArray = new int[3];
for (var i = 0; i < array.length; ++i) {
intArray[i] = 6 + i;
}
assertEquals("va:6:7:8", TestClass.allVarargInt(intArray));
assertEquals("va", TestClass.allVararg());
assertEquals("va", TestClass.allVarargInt());
}
@Test
@AttachJavaScript("org/teavm/jso/test/vararg.js")
public void restVararg() {
assertEquals("a:q,b:23,va:w:e", TestClass.restVararg("q", 23, "w", "e"));
assertEquals("a:23,b:q,va:5:7", TestClass.restVararg(23, "q", 5, 7));
assertEquals("a:q,b:23,va", TestClass.restVararg("q", 23));
assertEquals("a:23,b:q,va", TestClass.restVararg(23, "q"));
var array = new String[3];
for (var i = 0; i < array.length; ++i) {
array[i] = String.valueOf((char) ('A' + i));
}
assertEquals("a:q,b:23,va:A:B:C", TestClass.restVararg("q", 23, array));
var intArray = new int[3];
for (var i = 0; i < array.length; ++i) {
intArray[i] = 6 + i;
}
assertEquals("a:23,b:q,va:6:7:8", TestClass.restVararg(23, "q", intArray));
}
@JSClass
public static class TestClass implements JSObject {
public static native String allVararg(String... args);
@JSMethod("allVararg")
public static native String allVarargInt(int... args);
public static native String restVararg(String a, int b, String... args);
public static native String restVararg(int a, String b, int... args);
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.
*/
class TestClass {
static allVararg(...args) {
let result = "va";
for (const arg of args) {
result += ":" + arg;
}
return result;
}
static restVararg(a, b, ...args) {
let result = `a:${a},b:${b},va`;
for (const arg of args) {
result += ":" + arg;
}
return result;
}
}