diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml index dd1eb8841..19fcd1bed 100644 --- a/.idea/checkstyle-idea.xml +++ b/.idea/checkstyle-idea.xml @@ -4,14 +4,15 @@ diff --git a/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java b/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java index 1eb19529c..793c3a95d 100644 --- a/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java +++ b/core/src/main/java/org/teavm/ast/decompilation/Decompiler.java @@ -93,15 +93,17 @@ public class Decompiler { private Deque stack; private Program program; private boolean friendlyToDebugger; + private boolean moveConstants; public Decompiler(ClassHolderSource classSource, ClassLoader classLoader, Set asyncMethods, - Set asyncFamilyMethods, boolean friendlyToDebugger) { + Set asyncFamilyMethods, boolean friendlyToDebugger, boolean moveConstants) { this.classSource = classSource; this.classLoader = classLoader; this.asyncMethods = asyncMethods; splitMethods.addAll(asyncMethods); splitMethods.addAll(asyncFamilyMethods); this.friendlyToDebugger = friendlyToDebugger; + this.moveConstants = moveConstants; } public MethodNodeCache getRegularMethodCache() { @@ -283,7 +285,7 @@ public class Decompiler { methodNode.getVariables().add(variable); } - Optimizer optimizer = new Optimizer(); + Optimizer optimizer = new Optimizer(moveConstants); optimizer.optimize(methodNode, method.getProgram(), friendlyToDebugger); methodNode.getModifiers().addAll(method.getModifiers()); @@ -347,7 +349,7 @@ public class Decompiler { node.getVariables().add(variable); } - Optimizer optimizer = new Optimizer(); + Optimizer optimizer = new Optimizer(moveConstants); optimizer.optimize(node, splitter, friendlyToDebugger); node.getModifiers().addAll(method.getModifiers()); diff --git a/core/src/main/java/org/teavm/ast/optimization/Optimizer.java b/core/src/main/java/org/teavm/ast/optimization/Optimizer.java index a977fa287..b66c5d19e 100644 --- a/core/src/main/java/org/teavm/ast/optimization/Optimizer.java +++ b/core/src/main/java/org/teavm/ast/optimization/Optimizer.java @@ -18,9 +18,7 @@ package org.teavm.ast.optimization; import java.util.BitSet; import org.teavm.ast.AsyncMethodNode; import org.teavm.ast.AsyncMethodPart; -import org.teavm.ast.MethodNode; import org.teavm.ast.RegularMethodNode; -import org.teavm.ast.VariableNode; import org.teavm.common.Graph; import org.teavm.model.BasicBlock; import org.teavm.model.Instruction; @@ -33,6 +31,12 @@ import org.teavm.model.util.ProgramUtils; import org.teavm.model.util.UsageExtractor; public class Optimizer { + private boolean moveConstants; + + public Optimizer(boolean moveConstants) { + this.moveConstants = moveConstants; + } + public void optimize(RegularMethodNode method, Program program, boolean friendlyToDebugger) { ReadWriteStatsBuilder stats = new ReadWriteStatsBuilder(method.getVariables().size()); stats.analyze(program); @@ -40,7 +44,7 @@ public class Optimizer { BreakEliminator breakEliminator = new BreakEliminator(); breakEliminator.eliminate(method.getBody()); OptimizingVisitor optimizer = new OptimizingVisitor(preservedVars, stats.writes, stats.reads, - friendlyToDebugger); + moveConstants ? stats.constants : new Object[stats.constants.length], friendlyToDebugger); method.getBody().acceptVisitor(optimizer); method.setBody(optimizer.resultStmt); int paramCount = method.getReference().parameterCount(); @@ -74,7 +78,7 @@ public class Optimizer { breakEliminator.eliminate(part.getStatement()); findEscapingLiveVars(liveness, cfg, splitter, i, preservedVars); OptimizingVisitor optimizer = new OptimizingVisitor(preservedVars, stats.writes, stats.reads, - friendlyToDebugger); + moveConstants ? stats.constants : new Object[stats.constants.length], friendlyToDebugger); part.getStatement().acceptVisitor(optimizer); part.setStatement(optimizer.resultStmt); } @@ -97,14 +101,6 @@ public class Optimizer { } } - private void preserveDebuggableVars(boolean[] variablesToPreserve, MethodNode methodNode) { - for (VariableNode varNode : methodNode.getVariables()) { - if (varNode.getName() != null) { - variablesToPreserve[varNode.getIndex()] = true; - } - } - } - private void findEscapingLiveVars(LivenessAnalyzer liveness, Graph cfg, AsyncProgramSplitter splitter, int partIndex, boolean[] output) { Program originalProgram = splitter.getOriginalProgram(); diff --git a/core/src/main/java/org/teavm/ast/optimization/OptimizingVisitor.java b/core/src/main/java/org/teavm/ast/optimization/OptimizingVisitor.java index c35159531..af37a28f9 100644 --- a/core/src/main/java/org/teavm/ast/optimization/OptimizingVisitor.java +++ b/core/src/main/java/org/teavm/ast/optimization/OptimizingVisitor.java @@ -69,17 +69,19 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { private final boolean[] preservedVars; private final int[] writeFrequencies; private final int[] readFrequencies; + private final Object[] constants; private List resultSequence; private boolean friendlyToDebugger; private TextLocation currentLocation; private Deque locationStack = new LinkedList<>(); private Deque notNullLocationStack = new ArrayDeque<>(); - OptimizingVisitor(boolean[] preservedVars, int[] writeFrequencies, int[] readFrequencies, + OptimizingVisitor(boolean[] preservedVars, int[] writeFrequencies, int[] readFrequencies, Object[] constants, boolean friendlyToDebugger) { this.preservedVars = preservedVars; this.writeFrequencies = writeFrequencies; this.readFrequencies = readFrequencies; + this.constants = constants; this.friendlyToDebugger = friendlyToDebugger; } @@ -273,6 +275,15 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { if (writeFrequencies[index] != 1) { return; } + + if (constants[index] != null) { + ConstantExpr constantExpr = new ConstantExpr(); + constantExpr.setValue(constants[index]); + constantExpr.setLocation(expr.getLocation()); + resultExpr = constantExpr; + return; + } + if (readFrequencies[index] != 1 || preservedVars[index]) { return; } @@ -542,6 +553,12 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { if (!(statement.getLeftValue() instanceof VariableExpr)) { statement.getLeftValue().acceptVisitor(this); left = resultExpr; + } else { + int varIndex = ((VariableExpr) statement.getLeftValue()).getIndex(); + if (writeFrequencies[varIndex] == 1 && constants[varIndex] != null) { + resultStmt = new SequentialStatement(); + return; + } } statement.setLeftValue(left); statement.setRightValue(right); diff --git a/core/src/main/java/org/teavm/ast/optimization/ReadWriteStatsBuilder.java b/core/src/main/java/org/teavm/ast/optimization/ReadWriteStatsBuilder.java index 5a685a198..43a2cecfd 100644 --- a/core/src/main/java/org/teavm/ast/optimization/ReadWriteStatsBuilder.java +++ b/core/src/main/java/org/teavm/ast/optimization/ReadWriteStatsBuilder.java @@ -20,6 +20,8 @@ import org.teavm.common.Graph; import org.teavm.common.GraphUtils; import org.teavm.common.IntegerStack; import org.teavm.model.*; +import org.teavm.model.instructions.ClassConstantInstruction; +import org.teavm.model.instructions.StringConstantInstruction; import org.teavm.model.util.DefinitionExtractor; import org.teavm.model.util.ProgramUtils; import org.teavm.model.util.UsageExtractor; @@ -27,6 +29,7 @@ import org.teavm.model.util.UsageExtractor; class ReadWriteStatsBuilder { public int[] reads; public int[] writes; + public Object[] constants; private ReadWriteStatsBuilder() { } @@ -34,6 +37,7 @@ class ReadWriteStatsBuilder { public ReadWriteStatsBuilder(int variableCount) { reads = new int[variableCount]; writes = new int[variableCount]; + constants = new Object[variableCount]; } public ReadWriteStatsBuilder copy() { @@ -68,6 +72,14 @@ class ReadWriteStatsBuilder { for (Variable var : useExtractor.getUsedVariables()) { reads[var.getIndex()]++; } + + if (insn instanceof StringConstantInstruction) { + StringConstantInstruction stringConstant = (StringConstantInstruction) insn; + constants[stringConstant.getReceiver().getIndex()] = stringConstant.getConstant(); + } else if (insn instanceof ClassConstantInstruction) { + ClassConstantInstruction classConstant = (ClassConstantInstruction) insn; + constants[classConstant.getReceiver().getIndex()] = classConstant.getConstant(); + } } for (Phi phi : block.getPhis()) { diff --git a/core/src/main/java/org/teavm/backend/c/CTarget.java b/core/src/main/java/org/teavm/backend/c/CTarget.java index 10e65b349..0f34a8132 100644 --- a/core/src/main/java/org/teavm/backend/c/CTarget.java +++ b/core/src/main/java/org/teavm/backend/c/CTarget.java @@ -83,6 +83,7 @@ import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.lowlevel.Characteristics; import org.teavm.model.lowlevel.ClassInitializerEliminator; import org.teavm.model.lowlevel.ClassInitializerTransformer; +import org.teavm.model.lowlevel.ExportDependencyListener; import org.teavm.model.lowlevel.NullCheckInsertion; import org.teavm.model.lowlevel.NullCheckTransformation; import org.teavm.model.lowlevel.ShadowStackTransformer; @@ -107,6 +108,7 @@ public class CTarget implements TeaVMTarget { private ShadowStackTransformer shadowStackTransformer; private NullCheckInsertion nullCheckInsertion; private NullCheckTransformation nullCheckTransformation; + private ExportDependencyListener exportDependencyListener = new ExportDependencyListener(); private int minHeapSize = 32 * 1024 * 1024; public void setMinHeapSize(int minHeapSize) { @@ -123,7 +125,7 @@ public class CTarget implements TeaVMTarget { @Override public List getDependencyListeners() { - return Collections.singletonList(new CDependencyListener()); + return Arrays.asList(new CDependencyListener(), exportDependencyListener); } @Override @@ -204,7 +206,7 @@ public class CTarget implements TeaVMTarget { StringPool stringPool = new StringPool(); Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), new HashSet<>(), - new HashSet<>(), false); + new HashSet<>(), false, true); Characteristics characteristics = new Characteristics(controller.getUnprocessedClassSource()); NameProvider nameProvider = new NameProvider(controller.getUnprocessedClassSource()); @@ -221,7 +223,7 @@ public class CTarget implements TeaVMTarget { intrinsics.add(new GCIntrinsic()); intrinsics.add(new MutatorIntrinsic()); intrinsics.add(new ExceptionHandlingIntrinsic()); - intrinsics.add(new FunctionIntrinsic(characteristics)); + intrinsics.add(new FunctionIntrinsic(characteristics, exportDependencyListener.getResolvedMethods())); List generators = new ArrayList<>(); generators.add(new ArrayGenerator()); diff --git a/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java b/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java index 328ab6443..e4b30382f 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/c/generate/ClassGenerator.java @@ -62,6 +62,8 @@ public class ClassGenerator { private List layouts = new ArrayList<>(); private int currentLayoutIndex; private Set types = new LinkedHashSet<>(); + private Set includes = new LinkedHashSet<>(); + private CodeWriter includesWriter; private CodeWriter forwardDeclarationsWriter; private CodeWriter structuresWriter; private CodeWriter vtableStructuresWriter; @@ -81,6 +83,7 @@ public class ClassGenerator { this.tagRegistry = tagRegistry; this.decompiler = decompiler; + includesWriter = writer.fragment(); forwardDeclarationsWriter = writer.fragment(); structuresWriter = writer.fragment(); vtableStructuresWriter = writer.fragment(); @@ -93,7 +96,7 @@ public class ClassGenerator { callSiteWriter = writer.fragment(); codeWriter = writer.fragment(); - codeGenerator = new CodeGenerator(context, codeWriter); + codeGenerator = new CodeGenerator(context, codeWriter, includes); } public void generateClass(ClassHolder cls) { @@ -113,6 +116,10 @@ public class ClassGenerator { generateLayoutArray(); new StringPoolGenerator(stringPoolWriter, context.getNames()).generate(context.getStringPool().getStrings()); + + for (String include : includes) { + includesWriter.println("#include " + include); + } } public Set getTypes() { diff --git a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java index f730e5cd5..42d972c3e 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerationVisitor.java @@ -16,6 +16,7 @@ package org.teavm.backend.c.generate; import java.util.List; +import java.util.Set; import org.teavm.ast.ArrayType; import org.teavm.ast.AssignmentStatement; import org.teavm.ast.BinaryExpr; @@ -57,6 +58,11 @@ import org.teavm.backend.c.intrinsic.Intrinsic; import org.teavm.backend.c.intrinsic.IntrinsicContext; import org.teavm.diagnostics.Diagnostics; import org.teavm.interop.Address; +import org.teavm.interop.c.Include; +import org.teavm.model.AnnotationContainerReader; +import org.teavm.model.AnnotationReader; +import org.teavm.model.AnnotationValue; +import org.teavm.model.ClassReader; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; @@ -80,11 +86,13 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { private int temporaryReceiverLevel; private int maxTemporaryReceiverLevel; private MethodReference callingMethod; + private Set includes; - public CodeGenerationVisitor(GenerationContext context, CodeWriter writer) { + public CodeGenerationVisitor(GenerationContext context, CodeWriter writer, Set includes) { this.context = context; this.writer = writer; this.names = context.getNames(); + this.includes = includes; } public int getTemporaryReceivers() { @@ -371,6 +379,15 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { @Override public void visit(InvocationExpr expr) { + ClassReader cls = context.getClassSource().get(expr.getMethod().getClassName()); + if (cls != null) { + processInclude(cls.getAnnotations()); + MethodReader method = cls.getMethod(expr.getMethod().getDescriptor()); + if (method != null) { + processInclude(method.getAnnotations()); + } + } + Intrinsic intrinsic = context.getIntrinsic(expr.getMethod()); if (intrinsic != null) { intrinsic.apply(intrinsicContext, expr); @@ -439,6 +456,23 @@ public class CodeGenerationVisitor implements ExprVisitor, StatementVisitor { } } + private void processInclude(AnnotationContainerReader container) { + AnnotationReader annot = container.get(Include.class.getName()); + if (annot == null) { + return; + } + String includeString = annot.getValue("value").getString(); + + AnnotationValue systemValue = annot.getValue("isSystem"); + if (systemValue == null || systemValue.getBoolean()) { + includeString = "<" + includeString + ">"; + } else { + includeString = "\"" + includeString + "\""; + } + + includes.add(includeString); + } + @Override public void visit(QualificationExpr expr) { if (expr.getQualified() != null) { diff --git a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerator.java b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerator.java index 49b1a2ff1..9211b5873 100644 --- a/core/src/main/java/org/teavm/backend/c/generate/CodeGenerator.java +++ b/core/src/main/java/org/teavm/backend/c/generate/CodeGenerator.java @@ -15,6 +15,7 @@ */ package org.teavm.backend.c.generate; +import java.util.Set; import org.teavm.ast.RegularMethodNode; import org.teavm.ast.VariableNode; import org.teavm.model.ElementModifier; @@ -26,11 +27,13 @@ public class CodeGenerator { private CodeWriter writer; private CodeWriter localsWriter; private NameProvider names; + private Set includes; - public CodeGenerator(GenerationContext context, CodeWriter writer) { + public CodeGenerator(GenerationContext context, CodeWriter writer, Set includes) { this.context = context; this.writer = writer; this.names = context.getNames(); + this.includes = includes; } public void generateMethod(RegularMethodNode methodNode) { @@ -41,7 +44,7 @@ public class CodeGenerator { localsWriter = writer.fragment(); - CodeGenerationVisitor visitor = new CodeGenerationVisitor(context, writer); + CodeGenerationVisitor visitor = new CodeGenerationVisitor(context, writer, includes); visitor.setCallingMethod(methodNode.getReference()); methodNode.getBody().acceptVisitor(visitor); diff --git a/core/src/main/java/org/teavm/backend/c/intrinsic/AddressIntrinsic.java b/core/src/main/java/org/teavm/backend/c/intrinsic/AddressIntrinsic.java index 248c0ae40..517547b3b 100644 --- a/core/src/main/java/org/teavm/backend/c/intrinsic/AddressIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/c/intrinsic/AddressIntrinsic.java @@ -16,8 +16,10 @@ package org.teavm.backend.c.intrinsic; import org.teavm.ast.InvocationExpr; +import org.teavm.backend.c.util.ConstantUtil; import org.teavm.interop.Address; import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; public class AddressIntrinsic implements Intrinsic { @Override @@ -56,6 +58,8 @@ public class AddressIntrinsic implements Intrinsic { case "isLessThan": case "align": case "sizeOf": + + case "ofData": return true; default: return false; @@ -165,7 +169,7 @@ public class AddressIntrinsic implements Intrinsic { context.writer().print("ADDRESS_ADD("); context.emit(invocation.getArguments().get(0)); context.writer().print(", "); - String className = StructureIntrinsic.getClassLiteral(context, invocation, + String className = ConstantUtil.getClassLiteral(context, invocation, invocation.getArguments().get(1)); context.emit(invocation.getArguments().get(2)); context.writer().print(" * sizeof(") @@ -191,6 +195,14 @@ public class AddressIntrinsic implements Intrinsic { case "sizeOf": context.writer().print("sizeof(void*)"); break; + case "ofData": { + ValueType.Array type = (ValueType.Array) invocation.getMethod().parameterType(0); + context.writer().print("((char*) "); + context.emit(invocation.getArguments().get(0)); + context.writer().print(" + sizeof(JavaArray) + (intptr_t) ALIGN(NULL, " + + sizeOf(type.getItemType()) + "))"); + break; + } } } @@ -207,4 +219,22 @@ public class AddressIntrinsic implements Intrinsic { context.emit(invocation.getArguments().get(1)); context.writer().print(")"); } + + private int sizeOf(ValueType type) { + switch (((ValueType.Primitive) type).getKind()) { + case BYTE: + return 1; + case SHORT: + return 2; + case INTEGER: + case FLOAT: + return 4; + case LONG: + case DOUBLE: + return 8; + default: + break; + } + return 0; + } } diff --git a/core/src/main/java/org/teavm/backend/c/intrinsic/FunctionIntrinsic.java b/core/src/main/java/org/teavm/backend/c/intrinsic/FunctionIntrinsic.java index 6aa9258ec..380dad851 100644 --- a/core/src/main/java/org/teavm/backend/c/intrinsic/FunctionIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/c/intrinsic/FunctionIntrinsic.java @@ -15,25 +15,41 @@ */ package org.teavm.backend.c.intrinsic; +import java.util.Map; +import org.teavm.ast.ConstantExpr; import org.teavm.ast.InvocationExpr; +import org.teavm.interop.Function; import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; import org.teavm.model.lowlevel.Characteristics; +import org.teavm.model.lowlevel.ExportedMethodKey; public class FunctionIntrinsic implements Intrinsic { private Characteristics characteristics; + private Map resolvedMethods; - public FunctionIntrinsic(Characteristics characteristics) { + public FunctionIntrinsic(Characteristics characteristics, + Map resolvedMethods) { this.characteristics = characteristics; + this.resolvedMethods = resolvedMethods; } @Override public boolean canHandle(MethodReference method) { + if (method.getClassName().equals(Function.class.getName()) && method.getName().equals("get")) { + return true; + } return characteristics.isFunction(method.getClassName()); } @Override public void apply(IntrinsicContext context, InvocationExpr invocation) { MethodReference method = invocation.getMethod(); + if (method.getClassName().equals(Function.class.getName())) { + generateGetFunction(context, invocation); + return; + } + context.writer().print("(((").printType(method.getReturnType()).print(" (*)("); if (method.parameterCount() > 0) { context.writer().printType(method.parameterType(0)); @@ -54,4 +70,32 @@ public class FunctionIntrinsic implements Intrinsic { } context.writer().print("))"); } + + private void generateGetFunction(IntrinsicContext context, InvocationExpr invocation) { + if (!(invocation.getArguments().get(0) instanceof ConstantExpr) + || !(invocation.getArguments().get(1) instanceof ConstantExpr) + || !(invocation.getArguments().get(2) instanceof ConstantExpr)) { + return; + } + + Object functionClassValue = ((ConstantExpr) invocation.getArguments().get(0)).getValue(); + Object classValue = ((ConstantExpr) invocation.getArguments().get(1)).getValue(); + Object methodValue = ((ConstantExpr) invocation.getArguments().get(2)).getValue(); + if (!(functionClassValue instanceof ValueType.Object) + || !(classValue instanceof ValueType.Object) + || !(methodValue instanceof String)) { + return; + } + + String functionClassName = ((ValueType.Object) functionClassValue).getClassName(); + String className = ((ValueType.Object) classValue).getClassName(); + String methodName = (String) methodValue; + ExportedMethodKey key = new ExportedMethodKey(functionClassName, className, methodName); + MethodReference method = resolvedMethods.get(key); + if (method == null) { + return; + } + + context.writer().print("&").print(context.names().forMethod(method)); + } } diff --git a/core/src/main/java/org/teavm/backend/c/intrinsic/StructureIntrinsic.java b/core/src/main/java/org/teavm/backend/c/intrinsic/StructureIntrinsic.java index 1a4462bd6..96bd661aa 100644 --- a/core/src/main/java/org/teavm/backend/c/intrinsic/StructureIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/c/intrinsic/StructureIntrinsic.java @@ -15,11 +15,9 @@ */ package org.teavm.backend.c.intrinsic; -import org.teavm.ast.ConstantExpr; -import org.teavm.ast.Expr; import org.teavm.ast.InvocationExpr; +import org.teavm.backend.c.util.ConstantUtil; import org.teavm.interop.Structure; -import org.teavm.model.CallLocation; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; import org.teavm.model.lowlevel.Characteristics; @@ -62,14 +60,14 @@ public class StructureIntrinsic implements Intrinsic { context.emit(invocation.getArguments().get(0)); break; case "sizeOf": { - String className = getClassLiteral(context, invocation, invocation.getArguments().get(0)); + String className = ConstantUtil.getClassLiteral(context, invocation, invocation.getArguments().get(0)); if (className != null) { context.writer().print("sizeof(").print(context.names().forClass(className)).print(")"); } break; } case "add": { - String className = getClassLiteral(context, invocation, invocation.getArguments().get(0)); + String className = ConstantUtil.getClassLiteral(context, invocation, invocation.getArguments().get(0)); if (className != null) { context.writer().print("STRUCTURE_ADD(").print(context.names().forClass(className)).print(", "); context.emit(invocation.getArguments().get(1)); @@ -81,17 +79,4 @@ public class StructureIntrinsic implements Intrinsic { } } } - - static String getClassLiteral(IntrinsicContext context, InvocationExpr invocation, Expr expr) { - if (expr instanceof ConstantExpr) { - Object cst = ((ConstantExpr) expr).getValue(); - if (cst instanceof ValueType.Object) { - return ((ValueType.Object) cst).getClassName(); - } - } - context.getDiagnotics().error( - new CallLocation(context.getCallingMethod(), invocation.getLocation()), - "This method should take class literal"); - return ""; - } } diff --git a/core/src/main/java/org/teavm/backend/c/util/ConstantUtil.java b/core/src/main/java/org/teavm/backend/c/util/ConstantUtil.java new file mode 100644 index 000000000..344edf7ad --- /dev/null +++ b/core/src/main/java/org/teavm/backend/c/util/ConstantUtil.java @@ -0,0 +1,54 @@ +/* + * Copyright 2018 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.backend.c.util; + +import org.teavm.ast.ConstantExpr; +import org.teavm.ast.Expr; +import org.teavm.ast.InvocationExpr; +import org.teavm.backend.c.intrinsic.IntrinsicContext; +import org.teavm.model.CallLocation; +import org.teavm.model.ValueType; + +public final class ConstantUtil { + private ConstantUtil() { + } + + public static String getClassLiteral(IntrinsicContext context, InvocationExpr invocation, Expr expr) { + if (expr instanceof ConstantExpr) { + Object cst = ((ConstantExpr) expr).getValue(); + if (cst instanceof ValueType.Object) { + return ((ValueType.Object) cst).getClassName(); + } + } + context.getDiagnotics().error( + new CallLocation(context.getCallingMethod(), invocation.getLocation()), + "This method should take class literal"); + return "java.lang.Object"; + } + + public static String getStringLiteral(IntrinsicContext context, InvocationExpr invocation, Expr expr) { + if (expr instanceof ConstantExpr) { + Object cst = ((ConstantExpr) expr).getValue(); + if (cst instanceof String) { + return (String) cst; + } + } + context.getDiagnotics().error( + new CallLocation(context.getCallingMethod(), invocation.getLocation()), + "This method should take string literal"); + return ""; + } +} diff --git a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java index cc72b066c..6e69610b0 100644 --- a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java +++ b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java @@ -313,7 +313,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { asyncFamilyMethods.addAll(asyncFinder.getAsyncFamilyMethods()); Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), asyncMethods, asyncFamilyMethods, - controller.isFriendlyToDebugger()); + controller.isFriendlyToDebugger(), false); decompiler.setRegularMethodCache(controller.isIncremental() ? astCache : null); for (Map.Entry entry : methodGenerators.entrySet()) { diff --git a/core/src/main/java/org/teavm/backend/wasm/GtkExample.java b/core/src/main/java/org/teavm/backend/wasm/GtkExample.java new file mode 100644 index 000000000..d109a5c24 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/GtkExample.java @@ -0,0 +1,146 @@ +/* + * Copyright 2018 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.backend.wasm; + +import java.util.HashSet; +import java.util.Set; +import org.teavm.interop.Address; +import org.teavm.interop.Function; +import org.teavm.interop.Import; +import org.teavm.interop.Structure; +import org.teavm.interop.c.Include; + +public final class GtkExample { + private static Set pinnedObjects = new HashSet<>(); + + private GtkExample() { + } + + public static void main(String[] args) { + Gtk.init(0, null); + Gtk.Window window = Gtk.windowNew(Gtk.WINDOW_TOPLEVEL); + + CString deleteEventString = new CString("delete-event"); + CString destroyString = new CString("destroy"); + GLib.signalConnect(window, deleteEventString.data, + Function.get(GLib.Callback.class, GtkExample.class, "windowDeleted"), null); + GLib.signalConnect(window, destroyString.data, + Function.get(GLib.Callback.class, GtkExample.class, "destroy"), null); + deleteEventString.dispose(); + destroyString.dispose(); + + Gtk.setBorderWidth(window, 10); + + CString helloWorld = new CString("Hello, world"); + Gtk.Button button = Gtk.buttonNewWithLabel(helloWorld.data); + helloWorld.dispose(); + + CString clickedString = new CString("clicked"); + GLib.signalConnect(button, clickedString.data, + Function.get(GLib.Callback.class, GtkExample.class, "hello"), null); + + Gtk.add(window, button); + Gtk.show(button); + + Gtk.show(window); + Gtk.main(); + } + + private static void hello(Gtk.Widget widget, Address data) { + System.out.println("Hello, world!"); + } + + private static boolean windowDeleted(Gtk.Widget widget, Address event, Address data) { + System.out.println("System event occurred"); + return false; + } + + private static void destroy(Gtk.Widget widget, Address data) { + Gtk.mainQuit(); + } + + @Include("gtk/gtk.h") + static class Gtk { + static class Widget extends GLib.GObject { + } + + static class Window extends Container { + } + + static class Container extends Widget { + } + + static class Button extends Widget { + } + + @Import(name = "gtk_init") + static native void init(int argc, Address argv); + + @Import(name = "gtk_window_new") + static native Window windowNew(int type); + + @Import(name = "gtk_widget_show") + static native void show(Widget widget); + + @Import(name = "gtk_main") + static native void main(); + + @Import(name = "gtk_main_quit") + static native void mainQuit(); + + @Import(name = "gtk_container_set_border_width") + static native void setBorderWidth(Container container, int width); + + @Import(name = "gtk_button_new_with_label") + static native Button buttonNewWithLabel(Address label); + + @Import(name = "gtk_container_add") + static native void add(Container container, Widget widget); + + static final int WINDOW_TOPLEVEL = 0; + static final int WINDOW_POPUP = 1; + } + + static class GLib { + static class GObject extends Structure { + } + + @Import(name = "g_signal_connect") + static native long signalConnect(GObject instance, Address signalName, Callback callback, Address data); + + static abstract class Callback extends Function { + abstract void call(); + } + } + + static class CString { + final Address data; + private final byte[] array; + + CString(String str) { + array = new byte[str.length() + 1]; + for (int i = 0; i < str.length(); ++i) { + array[i] = (byte) str.charAt(i); + } + pinnedObjects.add(array); + data = Address.ofData(array); + } + + void dispose() { + pinnedObjects.remove(array); + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java index 02e1c009c..33f80d59f 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -301,7 +301,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { controller.getUnprocessedClassSource(), vtableProvider, tagRegistry, binaryWriter); Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), new HashSet<>(), - new HashSet<>(), false); + new HashSet<>(), false, true); WasmStringPool stringPool = classGenerator.getStringPool(); WasmGenerationContext context = new WasmGenerationContext(classes, module, controller.getDiagnostics(), vtableProvider, tagRegistry, stringPool); diff --git a/core/src/main/java/org/teavm/model/lowlevel/ExportDependencyListener.java b/core/src/main/java/org/teavm/model/lowlevel/ExportDependencyListener.java new file mode 100644 index 000000000..3cea1652d --- /dev/null +++ b/core/src/main/java/org/teavm/model/lowlevel/ExportDependencyListener.java @@ -0,0 +1,311 @@ +/* + * Copyright 2018 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.model.lowlevel; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.teavm.common.DisjointSet; +import org.teavm.dependency.AbstractDependencyListener; +import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.MethodDependency; +import org.teavm.diagnostics.Diagnostics; +import org.teavm.interop.Function; +import org.teavm.model.BasicBlockReader; +import org.teavm.model.CallLocation; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.ElementModifier; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; +import org.teavm.model.ProgramReader; +import org.teavm.model.TextLocation; +import org.teavm.model.ValueType; +import org.teavm.model.VariableReader; +import org.teavm.model.instructions.AbstractInstructionReader; +import org.teavm.model.instructions.InvocationType; + +public class ExportDependencyListener extends AbstractDependencyListener { + private Set exportedMethods = new LinkedHashSet<>(); + private Set readonlyExportedMethods = Collections.unmodifiableSet(exportedMethods); + private Map resolvedMethods = new HashMap<>(); + private Map readonlyResolvedMethods = + Collections.unmodifiableMap(resolvedMethods); + private Characteristics characteristics; + + @Override + public void started(DependencyAgent agent) { + characteristics = new Characteristics(agent.getClassSource()); + } + + public Set getExportedMethods() { + return readonlyExportedMethods; + } + + public Map getResolvedMethods() { + return readonlyResolvedMethods; + } + + @Override + public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { + if (method.getMethod() == null || method.getMethod().getProgram() == null) { + return; + } + + ProgramReader program = method.getMethod().getProgram(); + FunctionGetFinder finder = new FunctionGetFinder(program.variableCount()); + for (BasicBlockReader block : program.getBasicBlocks()) { + block.readAllInstructions(finder); + } + + if (!finder.invocations.isEmpty()) { + processInvocations(agent, method.getMethod(), finder); + } + } + + private void processInvocations(DependencyAgent agent, MethodReader method, FunctionGetFinder finder) { + int[] variableClasses = finder.variableClasses.pack(method.getProgram().variableCount()); + String[] stringConstants = new String[finder.stringConstants.length]; + ValueType[] classConstants = new ValueType[finder.classConstants.length]; + for (int i = 0; i < stringConstants.length; ++i) { + stringConstants[variableClasses[i]] = finder.stringConstants[i]; + classConstants[variableClasses[i]] = finder.classConstants[i]; + } + + Diagnostics diagnostics = agent.getDiagnostics(); + + for (Invocation invocation : finder.invocations) { + ValueType functionClass = classConstants[variableClasses[invocation.functionClassVar]]; + ValueType targetClass = classConstants[variableClasses[invocation.classVar]]; + + String methodName = stringConstants[variableClasses[invocation.methodVar]]; + CallLocation location = new CallLocation(method.getReference(), invocation.location); + + boolean valid = true; + if (!(functionClass instanceof ValueType.Object)) { + diagnostics.error(location, "First argument must be class literal representing " + + "non-array and no-primitive class"); + valid = false; + } + + if (!(targetClass instanceof ValueType.Object)) { + diagnostics.error(location, "Second argument must be class literal representing " + + "non-array and no-primitive class"); + valid = false; + } + + if (methodName == null) { + diagnostics.error(location, "Third argument must be string literal"); + valid = false; + } + + if (valid) { + processInvocation(agent, location, ((ValueType.Object) functionClass).getClassName(), + ((ValueType.Object) targetClass).getClassName(), methodName); + } + } + } + + private void processInvocation(DependencyAgent agent, CallLocation location, String functionClassName, + String targetClassName, String methodName) { + Diagnostics diagnostics = agent.getDiagnostics(); + ClassReaderSource classSource = agent.getClassSource(); + boolean valid = true; + + ClassReader functionClass = classSource.get(functionClassName); + if (functionClass == null) { + diagnostics.error(location, "Class '{{c0}}' not found in class path", functionClassName); + valid = false; + } else if (!characteristics.isFunction(functionClassName)) { + diagnostics.error(location, "Class '{{c0}}' does not represent a function", functionClassName); + valid = false; + } + + ClassReader targetClass = classSource.get(targetClassName); + if (targetClass == null) { + diagnostics.error(location, "Class '{{c0}}' not found in class path", functionClassName); + valid = false; + } + + if (!valid) { + return; + } + + MethodReader sam = extractSingleMethod(diagnostics, location, functionClass); + if (sam == null) { + valid = false; + } + + List candidates = targetClass.getMethods().stream() + .filter(method -> method.getName().equals(methodName) && method.hasModifier(ElementModifier.STATIC)) + .collect(Collectors.toList()); + if (candidates.isEmpty()) { + diagnostics.error(location, "There's no static method '" + methodName + "' in class '{{c0}}'", + targetClass); + valid = false; + } + + if (!valid) { + return; + } + + List signatureCandidates = candidates.stream() + .filter(method -> matchSignature(classSource, sam, method)) + .collect(Collectors.toList()); + if (signatureCandidates.isEmpty()) { + if (candidates.size() == 1) { + diagnostics.error(location, "Method '{{m0}}' does not match signature of function method '{{m1}}'", + candidates.get(0).getReference(), sam.getReference()); + } else { + diagnostics.error(location, "None of '" + methodName + "' methods match signature of function " + + "method '{{m0}}'", sam.getReference()); + } + return; + } + + MethodReader resolvedMethod = findMostSpecific(diagnostics, location, classSource, signatureCandidates); + if (resolvedMethod != null) { + MethodReference reference = resolvedMethod.getReference(); + resolvedMethods.put(new ExportedMethodKey(functionClassName, targetClassName, methodName), reference); + exportedMethods.add(reference); + agent.linkMethod(reference, location).use(); + } + } + + private MethodReader extractSingleMethod(Diagnostics diagnostics, CallLocation location, ClassReader cls) { + MethodReader candidate = null; + for (MethodReader method : cls.getMethods()) { + if (method.hasModifier(ElementModifier.STATIC) || !method.hasModifier(ElementModifier.ABSTRACT)) { + continue; + } + + if (candidate != null) { + diagnostics.error(location, "Function class {{c0}} must have one abstract method, it has multiple", + cls.getName()); + return null; + } else { + candidate = method; + } + } + + if (candidate == null) { + diagnostics.error(location, "Function class {{c0}} must have one abstract method, it has none", + cls.getName()); + return null; + } + + return candidate; + } + + private MethodReader findMostSpecific(Diagnostics diagnostics, CallLocation location, + ClassReaderSource classSource, List methods) { + MethodReader mostSpecificSoFar = methods.get(0); + for (int i = 1; i < methods.size(); ++i) { + MethodReader candidate = methods.get(i); + if (matchSignature(classSource, mostSpecificSoFar, candidate)) { + mostSpecificSoFar = candidate; + } else if (!matchSignature(classSource, candidate, mostSpecificSoFar)) { + diagnostics.error(location, "Ambiguous methods found for this export, examples are '{{m0}}' " + + "and {{m1}}", candidate, mostSpecificSoFar); + return null; + } + } + + return mostSpecificSoFar; + } + + private boolean matchSignature(ClassReaderSource classSource, MethodReader functionMethod, + MethodReader candidateMethod) { + if (functionMethod.parameterCount() > candidateMethod.parameterCount()) { + return false; + } + + for (int i = 0; i < functionMethod.parameterCount(); ++i) { + if (!classSource.isSuperType(functionMethod.parameterType(i), + candidateMethod.parameterType(i)).orElse(false)) { + return false; + } + } + + return true; + } + + class FunctionGetFinder extends AbstractInstructionReader { + DisjointSet variableClasses = new DisjointSet(); + String[] stringConstants; + ValueType[] classConstants; + List invocations = new ArrayList<>(); + private TextLocation location; + + FunctionGetFinder(int variableCount) { + for (int i = 0; i < variableCount; ++i) { + variableClasses.create(); + } + stringConstants = new String[variableCount]; + classConstants = new ValueType[variableCount]; + } + + @Override + public void location(TextLocation location) { + this.location = location; + } + + @Override + public void classConstant(VariableReader receiver, ValueType cst) { + classConstants[receiver.getIndex()] = cst; + } + + @Override + public void stringConstant(VariableReader receiver, String cst) { + stringConstants[receiver.getIndex()] = cst; + } + + @Override + public void assign(VariableReader receiver, VariableReader assignee) { + variableClasses.union(receiver.getIndex(), assignee.getIndex()); + } + + @Override + public void invoke(VariableReader receiver, VariableReader instance, MethodReference method, + List arguments, InvocationType type) { + if (method.getClassName().equals(Function.class.getName()) && method.getName().equals("get") + && type == InvocationType.SPECIAL && instance == null && arguments.size() == 3) { + Invocation invocation = new Invocation(location, arguments.get(0).getIndex(), + arguments.get(1).getIndex(), arguments.get(2).getIndex()); + invocations.add(invocation); + } + } + } + + static class Invocation { + TextLocation location; + int functionClassVar; + int classVar; + int methodVar; + + Invocation(TextLocation location, int functionClassVar, int classVar, int methodVar) { + this.location = location; + this.functionClassVar = functionClassVar; + this.classVar = classVar; + this.methodVar = methodVar; + } + } +} diff --git a/core/src/main/java/org/teavm/model/lowlevel/ExportedMethodKey.java b/core/src/main/java/org/teavm/model/lowlevel/ExportedMethodKey.java new file mode 100644 index 000000000..ee3d2789b --- /dev/null +++ b/core/src/main/java/org/teavm/model/lowlevel/ExportedMethodKey.java @@ -0,0 +1,49 @@ +/* + * Copyright 2018 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.model.lowlevel; + +import java.util.Objects; + +public class ExportedMethodKey { + public final String functionClassName; + public final String className; + public final String methodName; + + public ExportedMethodKey(String functionClassName, String className, String methodName) { + this.functionClassName = functionClassName; + this.className = className; + this.methodName = methodName; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ExportedMethodKey that = (ExportedMethodKey) o; + return Objects.equals(functionClassName, that.functionClassName) + && Objects.equals(className, that.className) + && Objects.equals(methodName, that.methodName); + } + + @Override + public int hashCode() { + return Objects.hash(functionClassName, className, methodName); + } +} diff --git a/interop/core/src/main/java/org/teavm/interop/Address.java b/interop/core/src/main/java/org/teavm/interop/Address.java index d61bde530..3c3372eb7 100644 --- a/interop/core/src/main/java/org/teavm/interop/Address.java +++ b/interop/core/src/main/java/org/teavm/interop/Address.java @@ -68,6 +68,20 @@ public final class Address { public static native Address ofObject(Object obj); + public static native Address ofData(byte[] data); + + public static native Address ofData(char[] data); + + public static native Address ofData(short[] data); + + public static native Address ofData(int[] data); + + public static native Address ofData(long[] data); + + public static native Address ofData(float[] data); + + public static native Address ofData(double[] data); + public static native Address align(Address address, int alignment); public static native int sizeOf(); diff --git a/interop/core/src/main/java/org/teavm/interop/Function.java b/interop/core/src/main/java/org/teavm/interop/Function.java index 74d54ff93..eb3813b75 100644 --- a/interop/core/src/main/java/org/teavm/interop/Function.java +++ b/interop/core/src/main/java/org/teavm/interop/Function.java @@ -16,4 +16,5 @@ package org.teavm.interop; public abstract class Function { + public static native T get(Class functionType, Class cls, String methodName); } diff --git a/interop/core/src/main/java/org/teavm/interop/c/Include.java b/interop/core/src/main/java/org/teavm/interop/c/Include.java new file mode 100644 index 000000000..9f17c4abf --- /dev/null +++ b/interop/core/src/main/java/org/teavm/interop/c/Include.java @@ -0,0 +1,29 @@ +/* + * Copyright 2018 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.interop.c; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +public @interface Include { + String value(); + + boolean isSystem() default true; +} diff --git a/interop/core/src/main/java/org/teavm/interop/c/PlatformInt.java b/interop/core/src/main/java/org/teavm/interop/c/PlatformInt.java new file mode 100644 index 000000000..d6bf81a28 --- /dev/null +++ b/interop/core/src/main/java/org/teavm/interop/c/PlatformInt.java @@ -0,0 +1,26 @@ +/* + * Copyright 2018 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.interop.c; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface PlatformInt { +}