mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
jso: implement vararg support for method calls
This commit is contained in:
parent
ccfe19994b
commit
582fcf904c
|
@ -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());
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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()];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
|
@ -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 {
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
91
tests/src/test/java/org/teavm/jso/test/CallTest.java
Normal file
91
tests/src/test/java/org/teavm/jso/test/CallTest.java
Normal 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);
|
||||
}
|
||||
}
|
33
tests/src/test/resources/org/teavm/jso/test/vararg.js
Normal file
33
tests/src/test/resources/org/teavm/jso/test/vararg.js
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user