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

View File

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

View File

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

View File

@ -85,7 +85,7 @@ public class InvokeInstruction extends Instruction {
private List<? extends Variable> argumentList = new AbstractList<Variable>() { private List<? extends Variable> argumentList = new AbstractList<Variable>() {
@Override @Override
public Variable get(int index) { public Variable get(int index) {
if (arguments == null) { if (arguments == null || index >= arguments.length) {
throw new IndexOutOfBoundsException(); throw new IndexOutOfBoundsException();
} }
return arguments[index]; 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.GetElementInstruction;
import org.teavm.model.instructions.IntegerConstantInstruction; import org.teavm.model.instructions.IntegerConstantInstruction;
import org.teavm.model.instructions.JumpInstruction; import org.teavm.model.instructions.JumpInstruction;
import org.teavm.model.instructions.NullCheckInstruction;
import org.teavm.model.instructions.NumericOperandType; import org.teavm.model.instructions.NumericOperandType;
import org.teavm.model.instructions.PutElementInstruction; import org.teavm.model.instructions.PutElementInstruction;
import org.teavm.model.instructions.UnwrapArrayInstruction; import org.teavm.model.instructions.UnwrapArrayInstruction;
@ -344,6 +345,11 @@ public class BoundCheckInsertion {
assign(insn.getArray(), insn.getReceiver()); 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) { private void assign(Variable from, Variable to) {
map[to.getIndex()] = map[from.getIndex()]; map[to.getIndex()] = map[from.getIndex()];
} }

View File

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

View File

@ -280,3 +280,19 @@ let $rt_createMultiArrayImpl = (cls, arrays, dimensions, start) => {
} }
return arrays[0]; 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); 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; import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE }) @Target({ ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR })
public @interface NoSideEffects { public @interface NoSideEffects {
} }

View File

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

View File

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

View File

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

View File

@ -31,10 +31,14 @@ final class JSMethods {
JSObject.class, void.class); JSObject.class, void.class);
public static final MethodReference SET_PURE = new MethodReference(JS.class, "setPure", JSObject.class, public static final MethodReference SET_PURE = new MethodReference(JS.class, "setPure", JSObject.class,
JSObject.class, JSObject.class, void.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, public static final MethodReference FUNCTION = new MethodReference(JS.class, "function", JSObject.class,
JSObject.class, JSObject.class); JSObject.class, JSObject.class);
public static final MethodReference ARRAY_DATA = new MethodReference(JS.class, "arrayData", public static final MethodReference ARRAY_DATA = new MethodReference(JS.class, "arrayData",
Object.class, JSObject.class); 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", public static final MethodReference ARRAY_MAPPER = new MethodReference(JS.class, "arrayMapper",
JS.WrapFunction.class, JS.WrapFunction.class); JS.WrapFunction.class, JS.WrapFunction.class);
public static final MethodReference BOOLEAN_ARRAY_WRAPPER = new MethodReference(JS.class, "booleanArrayWrapper", 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, public static final MethodReference DATA_TO_ARRAY = new MethodReference(JS.class,
"dataToArray", JSObject.class, JSObject[].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", public static final MethodReference FUNCTION_AS_OBJECT = new MethodReference(JS.class, "functionAsObject",
JSObject.class, JSObject.class, JSObject.class); JSObject.class, JSObject.class, JSObject.class);
@ -122,6 +129,7 @@ final class JSMethods {
public static final ValueType JS_ARRAY = ValueType.object(JSArray.class.getName()); public static final ValueType JS_ARRAY = ValueType.object(JSArray.class.getName());
private static final MethodReference[] INVOKE_METHODS = new MethodReference[13]; private static final MethodReference[] INVOKE_METHODS = new MethodReference[13];
private static final MethodReference[] CONSTRUCT_METHODS = new MethodReference[13]; private static final MethodReference[] CONSTRUCT_METHODS = new MethodReference[13];
private static final MethodReference[] ARRAY_OF_METHODS = new MethodReference[13];
static { static {
for (int i = 0; i < INVOKE_METHODS.length; ++i) { for (int i = 0; i < INVOKE_METHODS.length; ++i) {
@ -132,6 +140,10 @@ final class JSMethods {
var constructSignature = new ValueType[i + 2]; var constructSignature = new ValueType[i + 2];
Arrays.fill(constructSignature, JS_OBJECT); Arrays.fill(constructSignature, JS_OBJECT);
CONSTRUCT_METHODS[i] = new MethodReference(JS.class.getName(), "construct", constructSignature); 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) { public static MethodReference construct(int parameterCount) {
return CONSTRUCT_METHODS[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; package org.teavm.jso.impl;
import static org.teavm.backend.javascript.rendering.RenderingUtil.escapeString; import static org.teavm.backend.javascript.rendering.RenderingUtil.escapeString;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import org.teavm.ast.ArrayFromDataExpr;
import org.teavm.ast.ConstantExpr; import org.teavm.ast.ConstantExpr;
import org.teavm.ast.Expr; import org.teavm.ast.Expr;
import org.teavm.ast.InvocationExpr; 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.codegen.SourceWriter;
import org.teavm.backend.javascript.rendering.Precedence; import org.teavm.backend.javascript.rendering.Precedence;
import org.teavm.backend.javascript.spi.Injector; import org.teavm.backend.javascript.spi.Injector;
@ -38,6 +44,7 @@ import org.teavm.model.ValueType;
public class JSNativeInjector implements Injector, DependencyPlugin { public class JSNativeInjector implements Injector, DependencyPlugin {
private Set<MethodReference> reachedFunctorMethods = new HashSet<>(); private Set<MethodReference> reachedFunctorMethods = new HashSet<>();
private Set<DependencyNode> functorParamNodes = new HashSet<>(); private Set<DependencyNode> functorParamNodes = new HashSet<>();
private static final ValueType STRING_ARRAY = ValueType.arrayOf(ValueType.object("java.lang.String"));
@Override @Override
public void generate(InjectorContext context, MethodReference methodRef) { public void generate(InjectorContext context, MethodReference methodRef) {
@ -47,6 +54,13 @@ public class JSNativeInjector implements Injector, DependencyPlugin {
context.writeExpr(context.getArgument(0)); context.writeExpr(context.getArgument(0));
writer.append(".data"); writer.append(".data");
break; 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 "get":
case "getPure": case "getPure":
context.writeExpr(context.getArgument(0), Precedence.MEMBER_ACCESS); context.writeExpr(context.getArgument(0), Precedence.MEMBER_ACCESS);
@ -71,6 +85,19 @@ public class JSNativeInjector implements Injector, DependencyPlugin {
} }
writer.append(')'); writer.append(')');
break; 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": case "construct":
if (context.getPrecedence().ordinal() >= Precedence.FUNCTION_CALL.ordinal()) { if (context.getPrecedence().ordinal() >= Precedence.FUNCTION_CALL.ordinal()) {
writer.append("("); 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) { private void dataToArray(InjectorContext context, String className) {
var writer = context.getWriter(); var writer = context.getWriter();
writer.appendFunction("$rt_wrapArray").append("(").appendFunction(className).append(",").ws(); 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;
}
}