From 1678d02f305d0206abe2e4b18568e1168499b317 Mon Sep 17 00:00:00 2001 From: soywiz Date: Sun, 14 Jan 2018 20:15:31 +0100 Subject: [PATCH 01/60] Added @JTranscRelooper annotation to manual control relooping --- .../jtransc/annotation/JTranscRelooper.java | 15 ++++++++ jtransc-core/src/com/jtransc/ast/ast.kt | 4 +++ .../ast/feature/method/GotosFeature.kt | 2 +- .../src/relooper/RelooperTest.java | 35 +++++++++++++++++++ .../test/com/jtransc/gen/js/JsTest.kt | 3 ++ 5 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 jtransc-annotations/src/com/jtransc/annotation/JTranscRelooper.java create mode 100644 jtransc-gen-common-tests/src/relooper/RelooperTest.java diff --git a/jtransc-annotations/src/com/jtransc/annotation/JTranscRelooper.java b/jtransc-annotations/src/com/jtransc/annotation/JTranscRelooper.java new file mode 100644 index 00000000..f750a61d --- /dev/null +++ b/jtransc-annotations/src/com/jtransc/annotation/JTranscRelooper.java @@ -0,0 +1,15 @@ +package com.jtransc.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Overrides enabling/disabling relooper for specific method + */ +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.METHOD) +public @interface JTranscRelooper { + boolean value() default true; +} diff --git a/jtransc-core/src/com/jtransc/ast/ast.kt b/jtransc-core/src/com/jtransc/ast/ast.kt index 6d54c4b5..3a56766e 100644 --- a/jtransc-core/src/com/jtransc/ast/ast.kt +++ b/jtransc-core/src/com/jtransc/ast/ast.kt @@ -743,6 +743,10 @@ class AstMethod constructor( fun getParamsWithAnnotations(args: List) = methodType.args.zip(args).map { AstArgumentCallWithAnnotations(it.first, parameterAnnotationsList[it.first.index], it.second) } fun getParamsWithAnnotationsBox(args: List) = methodType.args.zip(args).map { AstArgumentCallWithAnnotations(it.first, parameterAnnotationsList.getOrNull(it.first.index), it.second.value) } + val relooperEnabled: Boolean? by lazy { + annotationsList.getTyped()?.value + } + init { if (id < 0) { println("Invalid method id: $id") diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index ecf15585..fb01a8db 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -25,7 +25,7 @@ import com.jtransc.graph.RelooperException // @TODO: Use AstBuilder to make it more readable class GotosFeature : AstMethodFeature() { override fun remove(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody { - if (settings.relooper) { + if (method.relooperEnabled ?: settings.relooper) { try { return removeRelooper(body, settings, types) ?: removeMachineState(body, types) } catch (t: Throwable) { diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java new file mode 100644 index 00000000..73459cfb --- /dev/null +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -0,0 +1,35 @@ +package relooper; + +import com.jtransc.annotation.JTranscRelooper; +import com.jtransc.io.JTranscConsole; + +public class RelooperTest { + static public void main(String[] args) { + JTranscConsole.log("RelooperTest:"); + JTranscConsole.log(simpleIf(0, 1)); + JTranscConsole.log(simpleIf(1, 0)); + JTranscConsole.log(simpleIf(0, 0)); + + JTranscConsole.log(composedIf(0, 1)); + JTranscConsole.log(composedIf(1, 0)); + JTranscConsole.log(composedIf(0, 0)); + } + + //@JTranscRelooper + static public int simpleIf(int a, int b) { + if (a < b) { + return -1; + } else { + return +1; + } + } + + @JTranscRelooper + static public int composedIf(int a, int b) { + if (a < b && a >= 0) { + return -1; + } else { + return +1; + } + } +} diff --git a/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt b/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt index c8b62c01..0d2c7e8b 100644 --- a/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt +++ b/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt @@ -56,6 +56,7 @@ import jtransc.staticinit.StaticInitTest2 import org.junit.Assert import org.junit.Ignore import org.junit.Test +import relooper.RelooperTest import testservice.test.ServiceLoaderTest import testservice.test.TestServiceJs2 @@ -69,6 +70,8 @@ class JsTest : _Base() { @Test fun testSideEffects() = testClass(Params(clazz = SideEffectsTest::class.java, minimize = false, log = false, treeShaking = true, debug = true)) + @Test fun testRelooper() = testClass(Params(clazz = RelooperTest::class.java, minimize = false, log = false, treeShaking = true, debug = true)) + @Test fun testBig() = testClass(Params(clazz = BigTest::class.java, minimize = false, log = false)) @Test fun testBigMin() = testClass(Params(clazz = BigTest::class.java, minimize = true, log = false)) //@Test fun testBigIO() = testClass(Params(clazz = BigIOTest::class.java, minimize = true, log = false, treeShaking = true)) From 565962a7992ed7029f2ce54a9d776ea8a224c616 Mon Sep 17 00:00:00 2001 From: soywiz Date: Sun, 14 Jan 2018 22:21:26 +0100 Subject: [PATCH 02/60] Some unfinished work --- .../jtransc/annotation/JTranscRelooper.java | 2 + jtransc-core/src/com/jtransc/ast/ast.kt | 6 +-- .../ast/feature/method/GotosFeature.kt | 6 +-- .../src/com/jtransc/graph/Relooper.kt | 40 +++++++++++++------ .../src/relooper/RelooperTest.java | 25 +++++++++--- 5 files changed, 54 insertions(+), 25 deletions(-) diff --git a/jtransc-annotations/src/com/jtransc/annotation/JTranscRelooper.java b/jtransc-annotations/src/com/jtransc/annotation/JTranscRelooper.java index f750a61d..c28a80a7 100644 --- a/jtransc-annotations/src/com/jtransc/annotation/JTranscRelooper.java +++ b/jtransc-annotations/src/com/jtransc/annotation/JTranscRelooper.java @@ -12,4 +12,6 @@ @Target(ElementType.METHOD) public @interface JTranscRelooper { boolean value() default true; + boolean debug() default false; + int method() default 0; // 1 = experimental method } diff --git a/jtransc-core/src/com/jtransc/ast/ast.kt b/jtransc-core/src/com/jtransc/ast/ast.kt index 3a56766e..68f31c03 100644 --- a/jtransc-core/src/com/jtransc/ast/ast.kt +++ b/jtransc-core/src/com/jtransc/ast/ast.kt @@ -743,9 +743,9 @@ class AstMethod constructor( fun getParamsWithAnnotations(args: List) = methodType.args.zip(args).map { AstArgumentCallWithAnnotations(it.first, parameterAnnotationsList[it.first.index], it.second) } fun getParamsWithAnnotationsBox(args: List) = methodType.args.zip(args).map { AstArgumentCallWithAnnotations(it.first, parameterAnnotationsList.getOrNull(it.first.index), it.second.value) } - val relooperEnabled: Boolean? by lazy { - annotationsList.getTyped()?.value - } + val relooper by lazy { annotationsList.getTyped() } + val relooperEnabled: Boolean? by lazy { relooper?.value } + val relooperDebug by lazy { relooper?.debug ?: false } init { if (id < 0) { diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index fb01a8db..a824d2c7 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -27,7 +27,7 @@ class GotosFeature : AstMethodFeature() { override fun remove(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody { if (method.relooperEnabled ?: settings.relooper) { try { - return removeRelooper(body, settings, types) ?: removeMachineState(body, types) + return removeRelooper(method, body, settings, types) ?: removeMachineState(body, types) } catch (t: Throwable) { t.printStackTrace() return removeMachineState(body, types) @@ -37,7 +37,7 @@ class GotosFeature : AstMethodFeature() { } } - private fun removeRelooper(body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody? { + private fun removeRelooper(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody? { class BasicBlock(var index: Int) { var node: Relooper.Node? = null val stms = arrayListOf() @@ -105,7 +105,7 @@ class GotosFeature : AstMethodFeature() { } } - val relooper = Relooper(types) + val relooper = Relooper(types, "$method", method.relooperDebug) for (n in bblist) { n.node = relooper.node(n.stms) //println("NODE(${n.index}): ${n.stms}") diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index ec8a248d..7a2a0bec 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -3,29 +3,33 @@ package com.jtransc.graph import com.jtransc.ast.* import java.util.* -class Relooper(val types: AstTypes) { +class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boolean = false) { class Graph - class Node(val types: AstTypes, val body: List) { + class Node(val types: AstTypes, val index: Int, val body: List) { var next: Node? = null - val edges = arrayListOf() - val possibleNextNodes: List get() = listOf(next).filterNotNull() + edges.map { it.dst } + val srcEdges = arrayListOf() + val dstEdges = arrayListOf() + val possibleNextNodes: List get() = listOf(next).filterNotNull() + dstEdges.map { it.dst } - override fun toString(): String = dump(types, body.stm()).toString().trim() + override fun toString(): String = "L$index: " + dump(types, body.stm()).toString().trim() + "EDGES: $dstEdges. SRC_EDGES: ${srcEdges.size} NEXT: L${next?.index}" } - class Edge(val dst: Node, val cond: AstExpr) { - override fun toString(): String = "IF ($cond) goto $dst;" + class Edge(val types: AstTypes, val current: Node, val dst: Node, val cond: AstExpr) { + //override fun toString(): String = "IF (${cond.dump(types)}) goto L${dst.index}; else goto L${current.next?.index};" + override fun toString(): String = "IF (${cond.dump(types)}) goto L${dst.index};" } - fun node(body: List): Node = Node(types, body) - fun node(body: AstStm): Node = Node(types, listOf(body)) + var lastIndex = 0 + fun node(body: List): Node = Node(types, lastIndex++, body) + fun node(body: AstStm): Node = Node(types, lastIndex++, listOf(body)) fun edge(a: Node, b: Node) { a.next = b } fun edge(a: Node, b: Node, cond: AstExpr) { - a.edges += Edge(b, cond) + a.dstEdges += Edge(types, a, b, cond) + b.srcEdges += Edge(types, a, b, cond) } private fun prepare(entry: Node): List { @@ -39,13 +43,17 @@ class Relooper(val types: AstTypes) { } else { node.next = exit } - for (edge in node.edges) explore(edge.dst) + for (edge in node.dstEdges) explore(edge.dst) } explore(entry) explored += exit return explored.toList() } + inline private fun trace(msg: () -> String) { + if (debug) println(msg()) + } + fun render(entry: Node): AstStm? { val nodes = prepare(entry) val graph = graphList(nodes.map { @@ -56,16 +64,22 @@ class Relooper(val types: AstTypes) { //graph.dump() //println("----------") + trace { "Rendering $name" } + if (graph.hasCycles()) { //noImpl("acyclic!") //println("cyclic!") + trace { "Do not render $name" } return null } - val graph2 = graph.tarjanStronglyConnectedComponentsAlgorithm() val entry2 = graph2.findComponentIndexWith(entry) + if (debug) { + println("--") + } + //graph2.outputEdges scgraph = graph2 @@ -88,7 +102,7 @@ class Relooper(val types: AstTypes) { lateinit var lookup: AcyclicDigraphLookup> private fun getEdge(from: Node, to: Node): Edge? { - return from.edges.firstOrNull() { it.dst == to } + return from.dstEdges.firstOrNull() { it.dst == to } } private fun renderInternal(node: Int, endnode: Int): List { diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index 73459cfb..f4e18f5c 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -10,12 +10,16 @@ static public void main(String[] args) { JTranscConsole.log(simpleIf(1, 0)); JTranscConsole.log(simpleIf(0, 0)); - JTranscConsole.log(composedIf(0, 1)); - JTranscConsole.log(composedIf(1, 0)); - JTranscConsole.log(composedIf(0, 0)); + JTranscConsole.log(composedIfAnd(0, 1)); + JTranscConsole.log(composedIfAnd(1, 0)); + JTranscConsole.log(composedIfAnd(0, 0)); + + JTranscConsole.log(composedIfOr(0, 1)); + JTranscConsole.log(composedIfOr(1, 0)); + JTranscConsole.log(composedIfOr(0, 0)); } - //@JTranscRelooper + @JTranscRelooper(false) static public int simpleIf(int a, int b) { if (a < b) { return -1; @@ -24,12 +28,21 @@ static public int simpleIf(int a, int b) { } } - @JTranscRelooper - static public int composedIf(int a, int b) { + @JTranscRelooper(false) + static public int composedIfAnd(int a, int b) { if (a < b && a >= 0) { return -1; } else { return +1; } } + + @JTranscRelooper(value = true, debug = true) + static public int composedIfOr(int a, int b) { + if (a < b || a >= 0) { + return -1; + } else { + return +1; + } + } } From 8092d1c461820fc7911754c9cc5effb44ca57ae0 Mon Sep 17 00:00:00 2001 From: soywiz Date: Mon, 15 Jan 2018 15:33:11 +0100 Subject: [PATCH 03/60] Not working: Some work on new relooper --- jtransc-core/src/com/jtransc/ast/ast_body.kt | 10 +- jtransc-core/src/com/jtransc/ast/ast_dump.kt | 1 + .../src/com/jtransc/graph/Relooper.kt | 229 ++++++++++++++++-- .../src/com/jtransc/graph/Relooper2.kt | 39 +++ .../src/com/jtransc/graph/StrongComponent.kt | 22 +- .../test/com/jtransc/graph/Relooper2Test.kt | 45 ++++ .../test/com/jtransc/graph/RelooperTest.kt | 28 ++- .../src/relooper/RelooperTest.java | 21 +- 8 files changed, 365 insertions(+), 30 deletions(-) create mode 100644 jtransc-core/src/com/jtransc/graph/Relooper2.kt create mode 100644 jtransc-core/test/com/jtransc/graph/Relooper2Test.kt diff --git a/jtransc-core/src/com/jtransc/ast/ast_body.kt b/jtransc-core/src/com/jtransc/ast/ast_body.kt index 00162a37..a306b847 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_body.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_body.kt @@ -208,6 +208,12 @@ sealed class AstStm : AstElement, Cloneable { val iter = iter.box } + // Basic back jump + class DO_WHILE(iter: AstStm, cond: AstExpr) : AstStm() { + val cond = cond.box + val iter = iter.box + } + class RETURN(retval: AstExpr) : AstStm() { val retval = retval.box } @@ -364,7 +370,9 @@ abstract class AstExpr : AstElement, Cloneable { //var ahead: Boolean = false } - class RAW(override val type: AstType, val content: String) : AstExpr() + class RAW(override val type: AstType, val content: String) : AstExpr() { + override fun toString(): String = "RAW($content)" + } class PARAM(val argument: AstArgument) : LocalExpr() { override val name: String get() = argument.name diff --git a/jtransc-core/src/com/jtransc/ast/ast_dump.kt b/jtransc-core/src/com/jtransc/ast/ast_dump.kt index 1b575a39..2c82543d 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_dump.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_dump.kt @@ -136,6 +136,7 @@ fun dump(types: AstTypes, expr: AstExpr?): String { is AstExpr.INVOKE_DYNAMIC_METHOD -> { "invokeDynamic(${expr.extraArgCount}, ${expr.methodInInterfaceRef}, ${expr.methodToConvertRef})(${expr.startArgs.map { dump(types, it) }.joinToString(", ")})" } + is AstExpr.RAW -> "${expr.content}" else -> noImpl("$expr") } } diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 7a2a0bec..69c8ef5f 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -1,33 +1,34 @@ package com.jtransc.graph import com.jtransc.ast.* +import com.jtransc.error.invalidOp +import com.jtransc.text.INDENTS import java.util.* class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boolean = false) { class Graph class Node(val types: AstTypes, val index: Int, val body: List) { - var next: Node? = null + //var next: Node? = null val srcEdges = arrayListOf() val dstEdges = arrayListOf() - val possibleNextNodes: List get() = listOf(next).filterNotNull() + dstEdges.map { it.dst } + val dstEdgesButNext get() = dstEdges.filter { it.cond != null } + val possibleNextNodes: List get() = dstEdges.map { it.dst } - override fun toString(): String = "L$index: " + dump(types, body.stm()).toString().trim() + "EDGES: $dstEdges. SRC_EDGES: ${srcEdges.size} NEXT: L${next?.index}" + val next get() = dstEdges.firstOrNull { it.cond == null }?.dst + + override fun toString(): String = "L$index: " + dump(types, body.stm()).toString().trim() + "EDGES: $dstEdges. SRC_EDGES: ${srcEdges.size}" } - class Edge(val types: AstTypes, val current: Node, val dst: Node, val cond: AstExpr) { + class Edge(val types: AstTypes, val src: Node, val dst: Node, val cond: AstExpr? = null) { //override fun toString(): String = "IF (${cond.dump(types)}) goto L${dst.index}; else goto L${current.next?.index};" - override fun toString(): String = "IF (${cond.dump(types)}) goto L${dst.index};" + override fun toString(): String = if (cond != null) "IF (${cond.dump(types)}) goto L${dst.index};" else "goto L${dst.index};" } var lastIndex = 0 fun node(body: List): Node = Node(types, lastIndex++, body) fun node(body: AstStm): Node = Node(types, lastIndex++, listOf(body)) - fun edge(a: Node, b: Node) { - a.next = b - } - - fun edge(a: Node, b: Node, cond: AstExpr) { + fun edge(a: Node, b: Node, cond: AstExpr? = null) { a.dstEdges += Edge(types, a, b, cond) b.srcEdges += Edge(types, a, b, cond) } @@ -41,7 +42,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo if (node.next != null) { explore(node.next!!) } else { - node.next = exit + if (node != exit) edge(node, exit) } for (edge in node.dstEdges) explore(edge.dst) } @@ -66,16 +67,28 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo trace { "Rendering $name" } - if (graph.hasCycles()) { - //noImpl("acyclic!") - //println("cyclic!") - trace { "Do not render $name" } - return null - } + //if (graph.hasCycles()) { + // //noImpl("acyclic!") + // //println("cyclic!") + // trace { "Do not render $name" } + // return null + //} val graph2 = graph.tarjanStronglyConnectedComponentsAlgorithm() + + val result = renderComponents(graph2, entry, null, RenderContext(), level = 0) + + println(result) + println("render!") + val entry2 = graph2.findComponentIndexWith(entry) + //val inputs0 = graph2.components[0].getExternalInputs() + val entries = graph2.components[1].getEntryPoints() + val inputs1 = graph2.components[1].getExternalInputsEdges() + val outputs1 = graph2.components[1].getExternalOutputsEdges() + //val inputs2 = graph2.components[2].getExternalInputs() + if (debug) { println("--") } @@ -97,6 +110,152 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo // @TODO: strong components } + class RenderContext { + var lastId = 0 + val loopStarts = hashMapOf() + val loopEnds = hashMapOf() + fun allocName() = "loop${lastId++}" + } + + companion object { + fun AstExpr.dump() = this.dump(AstTypes(com.jtransc.gen.TargetName("js"))) + fun AstStm.dump() = this.dump(AstTypes(com.jtransc.gen.TargetName("js"))) + } + + interface Res + data class Stm(val body: List) : Res { + override fun toString(): String = body.map { it.dump() }.joinToString("") + } + + data class Stms(val stms: List) : Res { + override fun toString(): String = "{ ${stms.joinToString(" ")}}" + } + + data class DoWhile(val name: String, val body: Res, val cond: AstExpr) : Res { + override fun toString(): String = "$name: do { $body } while(${cond.dump()});" + } + + data class If(val cond: AstExpr, val tbody: Res, val fbody: Res? = null) : Res { + override fun toString(): String { + return if (fbody != null) { + "if (${cond.dump()}) $tbody else $fbody" + } else { + "if (${cond.dump()}) $tbody" + } + } + } + + data class Continue(val name: String) : Res { + override fun toString(): String = "continue $name;" + } + + data class Break(val name: String) : Res { + override fun toString(): String = "break $name;" + } + + /** + * The process consists in: + * - Separate the graph in Strong Components + * - Each strong component represents a loop + * - Each strong component should have a single entry and a single exit (modulo breaking/continuing other loops) in a reductible graph + * - That entry/exit delimits the loop + * - Inside strong components, all links should be internal, or external referencing the beginning/end of this or other loops. + * - Internal links to that component represents ifs, while external links represents, break or continue to specific loops + * - Each loop/strong component should be splitted into smaller strong components after removing links to the beginning of the loop to detect inner loops + */ + fun renderComponents(g: StrongComponentGraph, entry: Node, exit: Node?, ctx: RenderContext, level: Int): Res { + val indent = INDENTS[level] + val out = arrayListOf() + var node: Node? = entry + loop@ while (node != null && node != exit) { + var component = g.findComponentWith(node) + val isMultiNodeLoop = component.isMultiNodeLoop() + val isSingleNodeLoop = component.isSingleNodeLoop() + + // Loop + if (isMultiNodeLoop || isSingleNodeLoop) { + println("$indent- Detected node loop csize=${component.size} : $node") + val outs = component.getExternalOutputsNodes() + val outsNotInContext = outs.filter { it !in ctx.loopStarts } + if (outsNotInContext.size != 1) { + println("ASSERTION FAILED! outsNotInContext.size != ${outsNotInContext.size} : $node") + invalidOp + } + + val entryNode = node + val exitNode = outsNotInContext.first() + + val loopName = ctx.allocName() + + println("$indent:: ${entryNode.index} - ${exitNode.index}") + println("$indent:: ${component}") + + ctx.loopStarts[entryNode] = loopName + ctx.loopEnds[exitNode] = loopName + + out += DoWhile( + loopName, + if (isSingleNodeLoop) { + Stm(node.body) // @TODO: Here we should add ifs with breaks, and then convert put the condition there if possible + } else { + renderComponents(component.split(entryNode, exitNode), entryNode, exitNode, ctx, level = level + 1) + } + , + true.lit + ) + + ctx.loopEnds -= exitNode + ctx.loopStarts -= entryNode + + node = exitNode + } + // Not a loop + else { + println("$indent- Detected no loop : $node") + when (node.dstEdges.size) { + 0 -> { + println("$indent- Last node") + out += Stm(node.body) + break@loop + } + 1 -> { + println("$indent- Node continuing") + out += Stm(node.body) + } + } + for (e in node.dstEdgesButNext) { + val loopStart = ctx.loopStarts[e.dst] + val loopEnd = ctx.loopEnds[e.dst] + when { + loopStart != null -> out += If(e.cond!!, Continue(loopStart)) + loopEnd != null -> out += If(e.cond!!, Break(loopEnd)) + else -> TODO() + } + } + node = node.next + } + } + return if (out.size == 1) out.first() else Stms(out) + } + + fun StrongComponent.isMultiNodeLoop(): Boolean { + return (size > 1) + } + + fun StrongComponent.isSingleNodeLoop(): Boolean { + return (size == 1 && nodes[0].dstEdges.any { it.dst == nodes[0] }) + } + + fun StrongComponent.isLoop(): Boolean { + return isMultiNodeLoop() || isSingleNodeLoop() + } + + fun StrongComponent.split(entry: Node, exit: Node): StrongComponentGraph { + val parent = this + val splitted = parent.graph.tarjanStronglyConnectedComponentsAlgorithm { src, dst -> dst != entry.index } + return splitted + } + lateinit var processedCount: IntArray lateinit var scgraph: Digraph> lateinit var lookup: AcyclicDigraphLookup> @@ -131,8 +290,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val common = lookup.common(targets) val branches = targets.map { branch -> renderInternal(branch, common) } - val edge = nodeDsts.map { getEdge(nodeSrc, it) }.filterNotNull().first() - val cond = edge.cond + val edge = nodeDsts.map { getEdge(nodeSrc, it) }.filterNotNull().filter { it?.cond != null }.first() + val cond = edge.cond!! // IF if (common in targets) { @@ -158,6 +317,38 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } return stms } + + fun StrongComponent.getExternalInputsEdges(): List { + val edges = arrayListOf() + for (node in this.nodes) { + for (srcEdge in node.srcEdges) { + if (srcEdge.src !in this) edges += srcEdge + } + } + return edges + } + + fun StrongComponent.getExternalOutputsEdges(): List { + val edges = arrayListOf() + for (node in this.nodes) { + for (dstEdge in node.dstEdges) { + if (dstEdge.dst !in this) edges += dstEdge + } + } + return edges + } + + fun StrongComponent.getExternalInputsNodes(): List { + return this.getExternalInputsEdges().map { it.src }.distinct() + } + + fun StrongComponent.getExternalOutputsNodes(): List { + return this.getExternalOutputsEdges().map { it.dst }.distinct() + } + + fun StrongComponent.getEntryPoints(): List { + return this.getExternalInputsEdges().map { it.dst }.distinct() + } } class RelooperException(message: String) : RuntimeException(message) \ No newline at end of file diff --git a/jtransc-core/src/com/jtransc/graph/Relooper2.kt b/jtransc-core/src/com/jtransc/graph/Relooper2.kt new file mode 100644 index 00000000..75c48e35 --- /dev/null +++ b/jtransc-core/src/com/jtransc/graph/Relooper2.kt @@ -0,0 +1,39 @@ +package com.jtransc.graph + +import com.jtransc.ast.AstTypes + +class Relooper2(val types: AstTypes) { + inner class Node( + val body: TBody + ) { + val incoming = arrayListOf() + val outgoing = arrayListOf() + } + + inner class Component(val nodes: Set) + + inner class Edge(val src: Node, val dst: Node, val cond: TCond? = null) + + interface Stm + inner class Simple(val body: TBody) : Stm + inner class If(val cond: TCond, val tbody: Stm, val fbody: Stm?) : Stm + inner class While(val name: String, val cond: TCond, val tbody: Stm) : Stm + inner class DoWhile(val name: String, val tbody: Stm, val cond: TCond) : Stm + inner class Continue(val name: String) : Stm + inner class Break(val name: String) : Stm + + fun node(body: TBody): Node { + return Node(body) + } + + fun edge(src: Node, dst: Node, cond: TCond? = null): Edge { + return Edge(src, dst, cond).apply { + dst.incoming += this + src.outgoing += this + } + } + + fun render(node: Node): Stm { + TODO() + } +} \ No newline at end of file diff --git a/jtransc-core/src/com/jtransc/graph/StrongComponent.kt b/jtransc-core/src/com/jtransc/graph/StrongComponent.kt index 7e432ab6..505ca536 100644 --- a/jtransc-core/src/com/jtransc/graph/StrongComponent.kt +++ b/jtransc-core/src/com/jtransc/graph/StrongComponent.kt @@ -6,18 +6,22 @@ import java.util.* // G = Graph // V = Vertices (Nodes) // E = Edges (E) -fun Digraph.tarjanStronglyConnectedComponentsAlgorithm() = StrongComponentGraph(this, TarjanStronglyConnectedComponentsAlgorithm(this).calculate()) +//fun Digraph.tarjanStronglyConnectedComponentsAlgorithm(filterNode: (node: Int) -> Boolean = { true }, filterEdge: (src: Int, dst: Int) -> Boolean = { _, _ -> true }) = StrongComponentGraph(this, TarjanStronglyConnectedComponentsAlgorithm(this, filterNode, filterEdge).calculate()) +fun Digraph.tarjanStronglyConnectedComponentsAlgorithm(filterEdge: (src: Int, dst: Int) -> Boolean = { _, _ -> true }) = StrongComponentGraph(this, TarjanStronglyConnectedComponentsAlgorithm(this, filterEdge).calculate()) // A strong component list is a disjoint set : https://en.wikipedia.org/wiki/Disjoint-set_data_structure class StrongComponent(val scgraph: StrongComponentGraph, val indices: LinkedHashSet) { val graph: Digraph = scgraph.graph val nodes: List = graph.toNodes(indices) + private val nodesSet = nodes.toSet() //val inp: List> get() = scgraph.getInNodes(this) val out: List> get() = scgraph.getOutNodes(this) + val size get() = nodes.size + operator fun contains(node: T) = node in nodesSet override fun toString() = graph.toNodes(indices).toString() } -data class StrongComponentEdge(val scgraph: StrongComponentGraph, val fromNode:Int, val fromSC:StrongComponent, val toNode:Int, val toSC:StrongComponent) { +data class StrongComponentEdge(val scgraph: StrongComponentGraph, val id: Int, val fromNode:Int, val fromSC:StrongComponent, val toNode:Int, val toSC:StrongComponent) { val graph: Digraph = scgraph.graph val fromNodeNode = graph.nodes[fromNode] val toNodeNode = graph.nodes[toNode] @@ -47,6 +51,8 @@ class StrongComponentGraph(val graph: Digraph, componentsData: List(val graph: Digraph, componentsData: List List>.toNodes(graph: Digraph): List> { return this.map { graph.toNodes(it.indices) } } -private class TarjanStronglyConnectedComponentsAlgorithm(val graph: DigraphSimple) { +private class TarjanStronglyConnectedComponentsAlgorithm(val graph: DigraphSimple, val filter: (src: Int, dst: Int) -> Boolean = { _, _ -> true }) { val indices = IntArray(graph.size) { UNDEFINED } val lowlinks = IntArray(graph.size) { UNDEFINED } val onStackList = BooleanArray(graph.size) { false } + val successors = Array?>(graph.size) { null } // @TODO: Use type aliases when kotlin accept them! var Int.index: Int get() = indices[this]; set(value) { indices[this] = value } var Int.lowlink: Int get() = lowlinks[this]; set(value) { lowlinks[this] = value } var Int.onStack: Boolean get() = onStackList[this]; set(value) { onStackList[this] = value } - fun Int.successors(): List = graph.getOut(this) + fun Int.successors(): List { + if (successors[this] == null) { + successors[this] = graph.getOut(this).filter { filter(this, it) } + } + return successors[this]!! + } var index = 0 var S = Stack() diff --git a/jtransc-core/test/com/jtransc/graph/Relooper2Test.kt b/jtransc-core/test/com/jtransc/graph/Relooper2Test.kt new file mode 100644 index 00000000..24e8537c --- /dev/null +++ b/jtransc-core/test/com/jtransc/graph/Relooper2Test.kt @@ -0,0 +1,45 @@ +package com.jtransc.graph + +import com.jtransc.ast.AstTypes +import com.jtransc.gen.TargetName +import org.junit.Test + +class Relooper2Test { + val types = AstTypes(TargetName("js")) + val relooper = Relooper2(types) + + /* + @Test + fun name() { + // A -> B -> C --> D + // /|\___/ + val A = relooper.node("A") + val B = relooper.node("B") + val C = relooper.node("C") + val D = relooper.node("D") + relooper.edge(A, B, "loop") + } + */ + + @Test + fun name2() { + // START -> B -> C -> D -> END + // /|\ * | + // |_________/ + val START = relooper.node("START") + val B = relooper.node("B") + val C = relooper.node("C") + val D = relooper.node("D") + val END = relooper.node("END") + relooper.edge(START, B) + relooper.edge(B, C) + relooper.edge(C, D) + relooper.edge(D, END) + relooper.edge(D, B, "condLoopOut") + relooper.edge(D, END, "condLoopOutExit") + relooper.edge(C, C, "condLoopIn") + relooper.edge(C, END, "condLoopExit") + + relooper.render(START) + } +} \ No newline at end of file diff --git a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt index 5338f762..ec86a1a6 100644 --- a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt +++ b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt @@ -1,17 +1,14 @@ package com.jtransc.graph import com.jtransc.ast.* -import com.jtransc.ast.optimize.optimize -import com.jtransc.ast.dump import com.jtransc.gen.TargetName -import org.junit.Assert import org.junit.Test class RelooperTest { val types = AstTypes(TargetName("js")) val relooper = Relooper(types) - private fun stmt(name:String) = types.build2 { SET(INT.local(name), 1.lit) } + private fun stmt(name: String): AstStm = AstType.INT.local(name).setTo(1.lit) /* @Test fun testIf() { @@ -68,4 +65,27 @@ class RelooperTest { //Assert.assertEquals("{ a = 1; if ((a == 1)) { b = 1; } else { c = 1; } d = 1; }", dump(relooper.render(A)).toString(doIndent = false).trim()) } */ + + @Test + fun testDoubleWhile() { + // A -> B -> C -> D -> E + // /|\ * | + // |_________/ + val A = relooper.node(stmt("A")) + val B = relooper.node(stmt("B")) + val C = relooper.node(stmt("C")) + val D = relooper.node(stmt("D")) + val E = relooper.node(stmt("E")) + relooper.edge(A, B) + relooper.edge(B, C) + relooper.edge(C, D) + relooper.edge(D, E) + relooper.edge(D, B, AstExpr.RAW(AstType.BOOL, "condLoopOutContinue")) + relooper.edge(D, E, AstExpr.RAW(AstType.BOOL, "condLoopOutBreak")) + relooper.edge(C, C, AstExpr.RAW(AstType.BOOL, "condLoopInContinue")) + //relooper.edge(C, E, AstExpr.RAW(AstType.BOOL, "condLoopOutBreak")) + val result = relooper.render(A) + //println(dump(relooper.render(A)).toString()) + //Assert.assertEquals("{ a = 1; if ((a == 1)) { b = 1; } else { c = 1; } d = 1; }", dump(relooper.render(A)).toString(doIndent = false).trim()) + } } \ No newline at end of file diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index f4e18f5c..119cccc1 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -17,6 +17,8 @@ static public void main(String[] args) { JTranscConsole.log(composedIfOr(0, 1)); JTranscConsole.log(composedIfOr(1, 0)); JTranscConsole.log(composedIfOr(0, 0)); + + simpleDoWhile(0, 5); } @JTranscRelooper(false) @@ -37,7 +39,7 @@ static public int composedIfAnd(int a, int b) { } } - @JTranscRelooper(value = true, debug = true) + @JTranscRelooper(value = true, debug = false) static public int composedIfOr(int a, int b) { if (a < b || a >= 0) { return -1; @@ -45,4 +47,21 @@ static public int composedIfOr(int a, int b) { return +1; } } + + @JTranscRelooper(value = true, debug = true) + static public int simpleDoWhile(int a, int b) { + b++; + + do { + if (a % 2 == 0) { + do { + JTranscConsole.log(a); + a++; + } while (a < b); + } + a++; + } while (a < b); + + return b; + } } From 6e7aba899f854ef3c76b3a6a78332b480a155de8 Mon Sep 17 00:00:00 2001 From: soywiz Date: Mon, 15 Jan 2018 15:36:19 +0100 Subject: [PATCH 04/60] Small fix in relooper --- jtransc-core/src/com/jtransc/graph/Relooper.kt | 2 +- jtransc-core/test/com/jtransc/graph/RelooperTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 69c8ef5f..00cdadbb 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -176,7 +176,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo if (isMultiNodeLoop || isSingleNodeLoop) { println("$indent- Detected node loop csize=${component.size} : $node") val outs = component.getExternalOutputsNodes() - val outsNotInContext = outs.filter { it !in ctx.loopStarts } + val outsNotInContext = outs.filter { it !in ctx.loopStarts && it !in ctx.loopEnds } if (outsNotInContext.size != 1) { println("ASSERTION FAILED! outsNotInContext.size != ${outsNotInContext.size} : $node") invalidOp diff --git a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt index ec86a1a6..7762bbfa 100644 --- a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt +++ b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt @@ -83,7 +83,7 @@ class RelooperTest { relooper.edge(D, B, AstExpr.RAW(AstType.BOOL, "condLoopOutContinue")) relooper.edge(D, E, AstExpr.RAW(AstType.BOOL, "condLoopOutBreak")) relooper.edge(C, C, AstExpr.RAW(AstType.BOOL, "condLoopInContinue")) - //relooper.edge(C, E, AstExpr.RAW(AstType.BOOL, "condLoopOutBreak")) + relooper.edge(C, E, AstExpr.RAW(AstType.BOOL, "condLoopOutBreak")) val result = relooper.render(A) //println(dump(relooper.render(A)).toString()) //Assert.assertEquals("{ a = 1; if ((a == 1)) { b = 1; } else { c = 1; } d = 1; }", dump(relooper.render(A)).toString(doIndent = false).trim()) From 30c3c28379758c5fc4849a5b7658a53f5be57924 Mon Sep 17 00:00:00 2001 From: soywiz Date: Mon, 15 Jan 2018 16:10:16 +0100 Subject: [PATCH 05/60] More work on relooper --- .../src/com/jtransc/graph/Relooper.kt | 73 ++++++++++++------- .../test/com/jtransc/graph/RelooperTest.kt | 1 + 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 00cdadbb..382bf1f3 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -138,9 +138,9 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo data class If(val cond: AstExpr, val tbody: Res, val fbody: Res? = null) : Res { override fun toString(): String { return if (fbody != null) { - "if (${cond.dump()}) $tbody else $fbody" + "if (${cond.dump()}) { $tbody } else { $fbody }" } else { - "if (${cond.dump()}) $tbody" + "if (${cond.dump()}) { $tbody }" } } } @@ -196,7 +196,10 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo out += DoWhile( loopName, if (isSingleNodeLoop) { - Stm(node.body) // @TODO: Here we should add ifs with breaks, and then convert put the condition there if possible + val out2 = arrayListOf() + renderNoLoops(g, out2, node, ctx, level) + //Stm(node.body) // @TODO: Here we should add ifs with breaks, and then convert put the condition there if possible + Stms(out2) } else { renderComponents(component.split(entryNode, exitNode), entryNode, exitNode, ctx, level = level + 1) } @@ -211,33 +214,53 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } // Not a loop else { - println("$indent- Detected no loop : $node") - when (node.dstEdges.size) { - 0 -> { - println("$indent- Last node") - out += Stm(node.body) - break@loop - } - 1 -> { - println("$indent- Node continuing") - out += Stm(node.body) - } - } - for (e in node.dstEdgesButNext) { - val loopStart = ctx.loopStarts[e.dst] - val loopEnd = ctx.loopEnds[e.dst] - when { - loopStart != null -> out += If(e.cond!!, Continue(loopStart)) - loopEnd != null -> out += If(e.cond!!, Break(loopEnd)) - else -> TODO() - } - } - node = node.next + node = renderNoLoops(g, out, node, ctx, level = level) } } return if (out.size == 1) out.first() else Stms(out) } + fun renderNoLoops(g: StrongComponentGraph, out: ArrayList, node: Node, ctx: RenderContext, level: Int): Node? { + val indent = INDENTS[level] + println("$indent- Detected no loop : $node") + when (node.dstEdges.size) { + 0, 1 -> { + if (node.dstEdges.size == 0) { + println("$indent- Last node") + } else { + println("$indent- Node continuing") + } + out += Stm(node.body) + if (node.dstEdges.size == 0) return null + } + 2 -> { + println("$indent- Node IF (and else?)") + val ifBody = node.next + val endOfIf = node.dstEdgesButNext.firstOrNull() ?: invalidOp("Expected conditional!") + val endOfIdNode = endOfIf.dst + out += If( + endOfIf.cond!!, + renderComponents(g, ifBody!!, endOfIdNode, ctx, level = level + 1) + ) + return endOfIdNode + } + else -> { + //TODO() + } + } + + for (e in node.dstEdgesButNext) { + val loopStart = ctx.loopStarts[e.dst] + val loopEnd = ctx.loopEnds[e.dst] + when { + loopStart != null -> out += If(e.cond!!, Continue(loopStart)) + loopEnd != null -> out += If(e.cond!!, Break(loopEnd)) + else -> TODO() + } + } + return node.next + } + fun StrongComponent.isMultiNodeLoop(): Boolean { return (size > 1) } diff --git a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt index 7762bbfa..09429aff 100644 --- a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt +++ b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt @@ -84,6 +84,7 @@ class RelooperTest { relooper.edge(D, E, AstExpr.RAW(AstType.BOOL, "condLoopOutBreak")) relooper.edge(C, C, AstExpr.RAW(AstType.BOOL, "condLoopInContinue")) relooper.edge(C, E, AstExpr.RAW(AstType.BOOL, "condLoopOutBreak")) + relooper.edge(A, E, AstExpr.RAW(AstType.BOOL, "condToAvoidLoop")) val result = relooper.render(A) //println(dump(relooper.render(A)).toString()) //Assert.assertEquals("{ a = 1; if ((a == 1)) { b = 1; } else { c = 1; } d = 1; }", dump(relooper.render(A)).toString(doIndent = false).trim()) From ad07e44e1cda49892f6ba5f49c819688e6e3df13 Mon Sep 17 00:00:00 2001 From: soywiz Date: Mon, 15 Jan 2018 16:38:55 +0100 Subject: [PATCH 06/60] Some more work on relooper --- .../src/com/jtransc/graph/Relooper.kt | 179 ++++++------------ .../src/com/jtransc/ds/collectionutils.kt | 2 + 2 files changed, 63 insertions(+), 118 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 382bf1f3..0d75bec6 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -1,9 +1,11 @@ package com.jtransc.graph import com.jtransc.ast.* +import com.jtransc.ds.Queue import com.jtransc.error.invalidOp import com.jtransc.text.INDENTS import java.util.* +import kotlin.collections.LinkedHashSet class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boolean = false) { class Graph @@ -67,54 +69,51 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo trace { "Rendering $name" } - //if (graph.hasCycles()) { - // //noImpl("acyclic!") - // //println("cyclic!") - // trace { "Do not render $name" } - // return null - //} - val graph2 = graph.tarjanStronglyConnectedComponentsAlgorithm() - - val result = renderComponents(graph2, entry, null, RenderContext(), level = 0) + val result = renderComponents(graph2, entry, null, RenderContext(graph2.graph), level = 0) println(result) println("render!") - val entry2 = graph2.findComponentIndexWith(entry) - - //val inputs0 = graph2.components[0].getExternalInputs() - val entries = graph2.components[1].getEntryPoints() - val inputs1 = graph2.components[1].getExternalInputsEdges() - val outputs1 = graph2.components[1].getExternalOutputsEdges() - //val inputs2 = graph2.components[2].getExternalInputs() - - if (debug) { - println("--") - } - - //graph2.outputEdges - - scgraph = graph2 - lookup = scgraph.assertAcyclic().createCommonDescendantLookup() - processedCount = IntArray(scgraph.size) - - return renderInternal(entry2, -1).stm() - /* - println(entry) - println(entry2) - */ - - //graph.dump() - //println(graph) - // @TODO: strong components + TODO() } - class RenderContext { + class RenderContext(val graph: Digraph) { var lastId = 0 val loopStarts = hashMapOf() val loopEnds = hashMapOf() + val rendered = LinkedHashSet() fun allocName() = "loop${lastId++}" + + // @TODO: Optimize performance! And maybe cache? + fun getNodeSuccessorsLinkedSet(a: Node, checkRendered: Boolean = false): Set { + val visited = LinkedHashSet() + if (!checkRendered) visited += rendered + val set = LinkedHashSet() + val queue = Queue() + queue.queue(a) + while (queue.hasMore) { + val item = queue.dequeue() + if (item in visited) continue + visited += item + set += item + for (edge in item.dstEdges) { + queue.queue(edge.dst) + } + } + return set + } + + // @TODO: Optimize performance! + fun findCommonSuccessorNotRendered(a: Node, b: Node): Node? { + val checkRendered = true + val aSet = getNodeSuccessorsLinkedSet(a, checkRendered) + val bSet = getNodeSuccessorsLinkedSet(b, checkRendered) + for (item in bSet) { + if (item in aSet) return item + } + return null + } } companion object { @@ -168,7 +167,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val out = arrayListOf() var node: Node? = entry loop@ while (node != null && node != exit) { - var component = g.findComponentWith(node) + ctx.rendered += node + val component = g.findComponentWith(node) val isMultiNodeLoop = component.isMultiNodeLoop() val isSingleNodeLoop = component.isSingleNodeLoop() @@ -235,14 +235,27 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } 2 -> { println("$indent- Node IF (and else?)") - val ifBody = node.next + val ifBody = node.next!! val endOfIf = node.dstEdgesButNext.firstOrNull() ?: invalidOp("Expected conditional!") - val endOfIdNode = endOfIf.dst - out += If( - endOfIf.cond!!, - renderComponents(g, ifBody!!, endOfIdNode, ctx, level = level + 1) - ) - return endOfIdNode + val endOfIfNode = endOfIf.dst + val common = ctx.findCommonSuccessorNotRendered(ifBody, endOfIfNode) + + // IF + if (common == endOfIfNode) { + out += If( + endOfIf.cond!!, + renderComponents(g, ifBody, endOfIfNode, ctx, level = level + 1) + ) + } + // IF+ELSE + else { + out += If( + endOfIf.cond!!, + renderComponents(g, ifBody, endOfIfNode, ctx, level = level + 1), + renderComponents(g, endOfIfNode, common, ctx, level = level + 1) + ) + } + return endOfIfNode } else -> { //TODO() @@ -261,17 +274,9 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo return node.next } - fun StrongComponent.isMultiNodeLoop(): Boolean { - return (size > 1) - } - - fun StrongComponent.isSingleNodeLoop(): Boolean { - return (size == 1 && nodes[0].dstEdges.any { it.dst == nodes[0] }) - } - - fun StrongComponent.isLoop(): Boolean { - return isMultiNodeLoop() || isSingleNodeLoop() - } + fun StrongComponent.isMultiNodeLoop(): Boolean = (size > 1) + fun StrongComponent.isSingleNodeLoop(): Boolean = (size == 1 && nodes[0].dstEdges.any { it.dst == nodes[0] }) + //fun StrongComponent.isLoop(): Boolean = isMultiNodeLoop() || isSingleNodeLoop() fun StrongComponent.split(entry: Node, exit: Node): StrongComponentGraph { val parent = this @@ -279,68 +284,6 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo return splitted } - lateinit var processedCount: IntArray - lateinit var scgraph: Digraph> - lateinit var lookup: AcyclicDigraphLookup> - - private fun getEdge(from: Node, to: Node): Edge? { - return from.dstEdges.firstOrNull() { it.dst == to } - } - - private fun renderInternal(node: Int, endnode: Int): List { - processedCount[node]++ - if (processedCount[node] > 4) { - //println("Processed several!") - throw RelooperException("Node processed several times!") - } - if (node == endnode) return listOf() - - val stms = arrayListOf() - stms += scgraph.getNode(node).nodes.flatMap { it.body } - - //node.scgraph.dump() - - val nodeNode = scgraph.getNode(node) - val targets = scgraph.getOut(node) - - val nodeSrc = nodeNode.nodes.last() - val nodeDsts = targets.map { scgraph.getNode(it).nodes.first() } - - when (targets.size) { - 0 -> Unit - 1 -> stms += renderInternal(targets.first(), endnode) - 2 -> { - val common = lookup.common(targets) - val branches = targets.map { branch -> renderInternal(branch, common) } - - val edge = nodeDsts.map { getEdge(nodeSrc, it) }.filterNotNull().filter { it?.cond != null }.first() - val cond = edge.cond!! - - // IF - if (common in targets) { - //val type1 = targets.indexOfFirst { it != common } - //stms += AstStm.IF(cond.not(), AstStmUtils.stms(branches[type1])) - stms += AstStm.IF(cond.not(), branches[0].stm()) - } - // IF-ELSE - else { - stms += AstStm.IF_ELSE(cond.not(), branches[0].stm(), branches[1].stm()) - } - stms += renderInternal(common, endnode) - } - else -> { - val common = lookup.common(targets) - val branches = targets.map { branch -> renderInternal(branch, common) } - - println(branches) - println("COMMON: $common") - //println(outNode.next) - //println(outNode.possibleNextNodes.size) - } - } - return stms - } - fun StrongComponent.getExternalInputsEdges(): List { val edges = arrayListOf() for (node in this.nodes) { diff --git a/jtransc-utils/src/com/jtransc/ds/collectionutils.kt b/jtransc-utils/src/com/jtransc/ds/collectionutils.kt index 9eacef9b..e84d07cf 100644 --- a/jtransc-utils/src/com/jtransc/ds/collectionutils.kt +++ b/jtransc-utils/src/com/jtransc/ds/collectionutils.kt @@ -49,6 +49,8 @@ class Queue() : Iterable { val hasMore: Boolean get() = data.isNotEmpty() val length: Int get() = data.size + val size: Int get() = data.size + //val hasMore get() = size > 0 fun queue(value: T): T { data.addFirst(value) From 39d89b75a67b55b0c83a3c3fa224c20c30a3d5cf Mon Sep 17 00:00:00 2001 From: soywiz Date: Mon, 15 Jan 2018 17:11:50 +0100 Subject: [PATCH 07/60] Some more work on relooper --- jtransc-core/src/com/jtransc/ast/ast_body.kt | 12 +-- jtransc-core/src/com/jtransc/ast/ast_dump.kt | 31 +++++-- .../ast/feature/method/GotosFeature.kt | 6 +- ...deterministicParameterEvaluationFeature.kt | 3 +- .../src/com/jtransc/graph/Relooper.kt | 93 +++++-------------- .../reflection/MetaReflectionJTranscPlugin.kt | 4 +- .../test/com/jtransc/graph/RelooperTest.kt | 29 ++++++ 7 files changed, 90 insertions(+), 88 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/ast_body.kt b/jtransc-core/src/com/jtransc/ast/ast_body.kt index a306b847..e6a980a3 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_body.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_body.kt @@ -203,13 +203,13 @@ sealed class AstStm : AstElement, Cloneable { val sfalse = sfalse.box } - class WHILE(cond: AstExpr, iter: AstStm) : AstStm() { + class WHILE(val name: String, cond: AstExpr, iter: AstStm) : AstStm() { val cond = cond.box val iter = iter.box } // Basic back jump - class DO_WHILE(iter: AstStm, cond: AstExpr) : AstStm() { + class DO_WHILE(val name: String, iter: AstStm, cond: AstExpr) : AstStm() { val cond = cond.box val iter = iter.box } @@ -233,8 +233,8 @@ sealed class AstStm : AstElement, Cloneable { val catch = catch.box } - class BREAK() : AstStm() - class CONTINUE() : AstStm() + class BREAK(val name: String) : AstStm() + class CONTINUE(val name: String) : AstStm() // SwitchFeature class SWITCH(subject: AstExpr, default: AstStm, cases: List, AstStm>>) : AstStm() { @@ -890,8 +890,8 @@ class AstBuilder2(types: AstTypes, val ctx: AstBuilderBodyCtx) : BuilderBase(typ return IfElseBuilder(IF, IF_INDEX, stms, types, ctx) } - inline fun WHILE(cond: AstExpr, callback: AstBuilder2.() -> Unit) { - stms += AstStm.WHILE(cond, AstBuilder2(types, ctx).apply(callback).genstm()) + inline fun WHILE(name: String, cond: AstExpr, callback: AstBuilder2.() -> Unit) { + stms += AstStm.WHILE(name, cond, AstBuilder2(types, ctx).apply(callback).genstm()) } inline fun FOR(local: AstLocal, start: Int, until: Int, callback: AstBuilder2.() -> Unit) { diff --git a/jtransc-core/src/com/jtransc/ast/ast_dump.kt b/jtransc-core/src/com/jtransc/ast/ast_dump.kt index 2c82543d..4b6b5ef2 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_dump.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_dump.kt @@ -3,7 +3,6 @@ package com.jtransc.ast import com.jtransc.error.invalidOp import com.jtransc.error.noImpl import com.jtransc.text.Indenter -import com.jtransc.text.Indenter.Companion //fun AstBody.dump() = dump(this) @@ -18,8 +17,21 @@ fun dump(types: AstTypes, body: AstBody): Indenter { } } -fun dump(types: AstTypes, expr: AstStm.Box?): Indenter { - return dump(types, expr?.value) +fun dump(types: AstTypes, stm: AstStm.Box?): Indenter { + return dump(types, stm?.value) +} + +fun dumpCollapse(types: AstTypes, stm: AstStm.Box?): Indenter { + val s = stm?.value + if (s is AstStm.STMS) { + return Indenter { + for (ss in s.stmsUnboxed) { + line(dumpCollapse(types, ss.box)) + } + } + } else { + return dump(types, s) + } } fun dump(types: AstTypes, stm: AstStm?): Indenter { @@ -60,12 +72,17 @@ fun dump(types: AstTypes, stm: AstStm?): Indenter { is AstStm.THROW -> line("throw ${dump(types, stm.exception)};") is AstStm.IF -> line("if (${dump(types, stm.cond)})") { line(dump(types, stm.strue)) } is AstStm.IF_ELSE -> { - line("if (${dump(types, stm.cond)})") { line(dump(types, stm.strue)) } - line("else") { line(dump(types, stm.sfalse)) } + line("if (${dump(types, stm.cond)})") { line(dumpCollapse(types, stm.strue)) } + line("else") { line(dumpCollapse(types, stm.sfalse)) } } is AstStm.WHILE -> { line("while (${dump(types, stm.cond)})") { - line(dump(types, stm.iter)) + line(dumpCollapse(types, stm.iter)) + } + } + is AstStm.DO_WHILE -> { + line("do", after2 = " while (${dump(types, stm.cond)});") { + line(dumpCollapse(types, stm.iter)) } } is AstStm.SWITCH -> { @@ -95,6 +112,8 @@ fun AstExpr?.exprDump(types: AstTypes) = dump(types, this) fun List.dump(types: AstTypes) = dump(types, this.stm()) fun AstStm.dump(types: AstTypes) = dump(types, this) +fun AstStm.dumpCollapse(types: AstTypes) = dumpCollapse(types, this.box) + fun AstExpr.dump(types: AstTypes) = dump(types, this) fun dump(types: AstTypes, expr: AstExpr?): String { diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index a824d2c7..9415797c 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -179,7 +179,7 @@ class GotosFeature : AstMethodFeature() { fun simulateGotoLabel(index: Int) = listOf( gotostate.setTo(index.lit), - AstStm.CONTINUE() + AstStm.CONTINUE("loop") ) fun simulateGotoLabel(label: AstLabel) = simulateGotoLabel(getStateFromLabel(label)) @@ -236,7 +236,7 @@ class GotosFeature : AstMethodFeature() { val plainWhile = listOf( - AstStm.WHILE(true.lit, + AstStm.WHILE("loop", true.lit, AstStm.SWITCH(gotostate, AstStm.NOP("no default"), cases) ), extraReturn() @@ -260,7 +260,7 @@ class GotosFeature : AstMethodFeature() { } listOf( - AstStm.WHILE(true.lit, + AstStm.WHILE("tryLoop", true.lit, AstStm.TRY_CATCH(plainWhile, stms( checkTraps.stms, AstStm.RETHROW() diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt index d2026b0f..94d35389 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt @@ -41,7 +41,8 @@ class UndeterministicParameterEvaluationFeature : AstMethodFeature() { is AstStm.MONITOR_ENTER -> out += AstStm.MONITOR_ENTER(stm.expr.processExpr(out, self = false)) is AstStm.MONITOR_EXIT -> out += AstStm.MONITOR_EXIT(stm.expr.processExpr(out, self = false)) is AstStm.THROW -> out += AstStm.THROW(stm.exception.processExpr(out, self = false)) - is AstStm.WHILE -> out += AstStm.WHILE(stm.cond.processExpr(out, self = false), stm.iter.value) + is AstStm.WHILE -> out += AstStm.WHILE(stm.name, stm.cond.processExpr(out, self = false), stm.iter.value) + is AstStm.DO_WHILE -> out += AstStm.DO_WHILE(stm.name, stm.iter.value, stm.cond.processExpr(out, self = false)) is AstStm.SET_LOCAL -> out += AstStm.SET_LOCAL(stm.local, stm.expr.processExpr(out), true) is AstStm.SET_ARRAY -> { // Like this to keep order diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 0d75bec6..b6c6058c 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -57,25 +57,9 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo if (debug) println(msg()) } - fun render(entry: Node): AstStm? { - val nodes = prepare(entry) - val graph = graphList(nodes.map { - //println("$it -> ${it.possibleNextNodes}") - it to it.possibleNextNodes - }) - //println("----------") - //graph.dump() - //println("----------") - + fun render(entry: Node): AstStm { trace { "Rendering $name" } - - val graph2 = graph.tarjanStronglyConnectedComponentsAlgorithm() - val result = renderComponents(graph2, entry, null, RenderContext(graph2.graph), level = 0) - - println(result) - println("render!") - - TODO() + return renderComponents(graphList(prepare(entry).map { it to it.possibleNextNodes }).tarjanStronglyConnectedComponentsAlgorithm(), entry) } class RenderContext(val graph: Digraph) { @@ -121,37 +105,6 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo fun AstStm.dump() = this.dump(AstTypes(com.jtransc.gen.TargetName("js"))) } - interface Res - data class Stm(val body: List) : Res { - override fun toString(): String = body.map { it.dump() }.joinToString("") - } - - data class Stms(val stms: List) : Res { - override fun toString(): String = "{ ${stms.joinToString(" ")}}" - } - - data class DoWhile(val name: String, val body: Res, val cond: AstExpr) : Res { - override fun toString(): String = "$name: do { $body } while(${cond.dump()});" - } - - data class If(val cond: AstExpr, val tbody: Res, val fbody: Res? = null) : Res { - override fun toString(): String { - return if (fbody != null) { - "if (${cond.dump()}) { $tbody } else { $fbody }" - } else { - "if (${cond.dump()}) { $tbody }" - } - } - } - - data class Continue(val name: String) : Res { - override fun toString(): String = "continue $name;" - } - - data class Break(val name: String) : Res { - override fun toString(): String = "break $name;" - } - /** * The process consists in: * - Separate the graph in Strong Components @@ -162,9 +115,9 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo * - Internal links to that component represents ifs, while external links represents, break or continue to specific loops * - Each loop/strong component should be splitted into smaller strong components after removing links to the beginning of the loop to detect inner loops */ - fun renderComponents(g: StrongComponentGraph, entry: Node, exit: Node?, ctx: RenderContext, level: Int): Res { - val indent = INDENTS[level] - val out = arrayListOf() + fun renderComponents(g: StrongComponentGraph, entry: Node, exit: Node? = null, ctx: RenderContext = RenderContext(g.graph), level: Int = 0): AstStm { + val indent by lazy { INDENTS[level] } + val out = arrayListOf() var node: Node? = entry loop@ while (node != null && node != exit) { ctx.rendered += node @@ -174,11 +127,11 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo // Loop if (isMultiNodeLoop || isSingleNodeLoop) { - println("$indent- Detected node loop csize=${component.size} : $node") + trace { "$indent- Detected node loop csize=${component.size} : $node" } val outs = component.getExternalOutputsNodes() val outsNotInContext = outs.filter { it !in ctx.loopStarts && it !in ctx.loopEnds } if (outsNotInContext.size != 1) { - println("ASSERTION FAILED! outsNotInContext.size != ${outsNotInContext.size} : $node") + trace { "ASSERTION FAILED! outsNotInContext.size != ${outsNotInContext.size} : $node" } invalidOp } @@ -187,19 +140,19 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val loopName = ctx.allocName() - println("$indent:: ${entryNode.index} - ${exitNode.index}") - println("$indent:: ${component}") + trace { "$indent:: ${entryNode.index} - ${exitNode.index}" } + trace { "$indent:: ${component}" } ctx.loopStarts[entryNode] = loopName ctx.loopEnds[exitNode] = loopName - out += DoWhile( + out += AstStm.DO_WHILE( loopName, if (isSingleNodeLoop) { - val out2 = arrayListOf() + val out2 = arrayListOf() renderNoLoops(g, out2, node, ctx, level) //Stm(node.body) // @TODO: Here we should add ifs with breaks, and then convert put the condition there if possible - Stms(out2) + out2.stms } else { renderComponents(component.split(entryNode, exitNode), entryNode, exitNode, ctx, level = level + 1) } @@ -217,24 +170,24 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo node = renderNoLoops(g, out, node, ctx, level = level) } } - return if (out.size == 1) out.first() else Stms(out) + return out.stms } - fun renderNoLoops(g: StrongComponentGraph, out: ArrayList, node: Node, ctx: RenderContext, level: Int): Node? { + fun renderNoLoops(g: StrongComponentGraph, out: ArrayList, node: Node, ctx: RenderContext, level: Int): Node? { val indent = INDENTS[level] - println("$indent- Detected no loop : $node") + trace { "$indent- Detected no loop : $node" } + out += node.body.stms when (node.dstEdges.size) { 0, 1 -> { if (node.dstEdges.size == 0) { - println("$indent- Last node") + trace { "$indent- Last node" } } else { - println("$indent- Node continuing") + trace { "$indent- Node continuing" } } - out += Stm(node.body) if (node.dstEdges.size == 0) return null } 2 -> { - println("$indent- Node IF (and else?)") + trace { "$indent- Node IF (and else?)" } val ifBody = node.next!! val endOfIf = node.dstEdgesButNext.firstOrNull() ?: invalidOp("Expected conditional!") val endOfIfNode = endOfIf.dst @@ -242,14 +195,14 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo // IF if (common == endOfIfNode) { - out += If( + out += AstStm.IF( endOfIf.cond!!, renderComponents(g, ifBody, endOfIfNode, ctx, level = level + 1) ) } // IF+ELSE else { - out += If( + out += AstStm.IF_ELSE( endOfIf.cond!!, renderComponents(g, ifBody, endOfIfNode, ctx, level = level + 1), renderComponents(g, endOfIfNode, common, ctx, level = level + 1) @@ -266,8 +219,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val loopStart = ctx.loopStarts[e.dst] val loopEnd = ctx.loopEnds[e.dst] when { - loopStart != null -> out += If(e.cond!!, Continue(loopStart)) - loopEnd != null -> out += If(e.cond!!, Break(loopEnd)) + loopStart != null -> out += AstStm.IF(e.cond!!, AstStm.CONTINUE(loopStart)) + loopEnd != null -> out += AstStm.IF(e.cond!!, AstStm.BREAK(loopEnd)) else -> TODO() } } diff --git a/jtransc-core/src/com/jtransc/plugin/reflection/MetaReflectionJTranscPlugin.kt b/jtransc-core/src/com/jtransc/plugin/reflection/MetaReflectionJTranscPlugin.kt index c5d2b33d..2660de47 100644 --- a/jtransc-core/src/com/jtransc/plugin/reflection/MetaReflectionJTranscPlugin.kt +++ b/jtransc-core/src/com/jtransc/plugin/reflection/MetaReflectionJTranscPlugin.kt @@ -664,7 +664,6 @@ class MetaReflectionJTranscPlugin : JTranscPlugin() { } } - // ProgramReflectionClass.dynamicSet if (program.contains(ProgramReflection.DynamicSet::class.java.fqname)) { val dynamicSetClass: AstClass = program[ProgramReflection.DynamicSet::class.java.fqname] @@ -694,7 +693,8 @@ class MetaReflectionJTranscPlugin : JTranscPlugin() { } else { STM(AstStm.SET_FIELD_INSTANCE(field.ref, objParam.expr.castTo(field.containingClass.astType), expr)) } - AstStm.BREAK() + //AstStm.BREAK(switchName) + AstStm.RETURN_VOID() } currentIndex++ } diff --git a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt index 09429aff..9b063db7 100644 --- a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt +++ b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt @@ -2,6 +2,7 @@ package com.jtransc.graph import com.jtransc.ast.* import com.jtransc.gen.TargetName +import org.junit.Assert.assertEquals import org.junit.Test class RelooperTest { @@ -66,6 +67,8 @@ class RelooperTest { } */ + fun String.normalizeMulti() = this.trimIndent().trim().lines().map { it.trimEnd() }.joinToString("\n") + @Test fun testDoubleWhile() { // A -> B -> C -> D -> E @@ -86,6 +89,32 @@ class RelooperTest { relooper.edge(C, E, AstExpr.RAW(AstType.BOOL, "condLoopOutBreak")) relooper.edge(A, E, AstExpr.RAW(AstType.BOOL, "condToAvoidLoop")) val result = relooper.render(A) + assertEquals(""" + A = 1; + if (condToAvoidLoop) { + do { + B = 1; + do { + C = 1; + if (condLoopInContinue) { + continue; + } + if (condLoopOutBreak) { + break; + } + } while (true); + D = 1; + if (condLoopOutContinue) { + continue; + } + if (condLoopOutBreak) { + break; + } + } while (true); + } + E = 1; + NOP(empty stm) + """.normalizeMulti(), result.dumpCollapse(types).toString().normalizeMulti()) //println(dump(relooper.render(A)).toString()) //Assert.assertEquals("{ a = 1; if ((a == 1)) { b = 1; } else { c = 1; } d = 1; }", dump(relooper.render(A)).toString(doIndent = false).trim()) } From ba7e03dede0d955bc45290c1ae438dd90262a467 Mon Sep 17 00:00:00 2001 From: soywiz Date: Mon, 15 Jan 2018 17:23:33 +0100 Subject: [PATCH 08/60] Some Relooper adjustments --- .../src/com/jtransc/graph/Relooper.kt | 12 +++++---- .../test/com/jtransc/graph/RelooperTest.kt | 26 ++++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index b6c6058c..58cfa5e2 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -152,7 +152,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val out2 = arrayListOf() renderNoLoops(g, out2, node, ctx, level) //Stm(node.body) // @TODO: Here we should add ifs with breaks, and then convert put the condition there if possible - out2.stms + out2.stmsWoNops } else { renderComponents(component.split(entryNode, exitNode), entryNode, exitNode, ctx, level = level + 1) } @@ -170,13 +170,15 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo node = renderNoLoops(g, out, node, ctx, level = level) } } - return out.stms + return out.stmsWoNops } + val Iterable.stmsWoNops: AstStm get() = this.toList().filter { it !is AstStm.NOP }.stm() + fun renderNoLoops(g: StrongComponentGraph, out: ArrayList, node: Node, ctx: RenderContext, level: Int): Node? { val indent = INDENTS[level] trace { "$indent- Detected no loop : $node" } - out += node.body.stms + out += node.body.stmsWoNops when (node.dstEdges.size) { 0, 1 -> { if (node.dstEdges.size == 0) { @@ -204,11 +206,11 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo else { out += AstStm.IF_ELSE( endOfIf.cond!!, - renderComponents(g, ifBody, endOfIfNode, ctx, level = level + 1), + renderComponents(g, ifBody, common, ctx, level = level + 1), renderComponents(g, endOfIfNode, common, ctx, level = level + 1) ) } - return endOfIfNode + return common } else -> { //TODO() diff --git a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt index 9b063db7..33ae04e1 100644 --- a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt +++ b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt @@ -2,6 +2,7 @@ package com.jtransc.graph import com.jtransc.ast.* import com.jtransc.gen.TargetName +import com.jtransc.text.Indenter import org.junit.Assert.assertEquals import org.junit.Test @@ -11,14 +12,23 @@ class RelooperTest { private fun stmt(name: String): AstStm = AstType.INT.local(name).setTo(1.lit) - /* @Test fun testIf() { val A = relooper.node(stmt("a")) val B = relooper.node(stmt("b")) - relooper.edge(A, B, AstExpr.build { INT.local("a") eq 1.lit }) - Assert.assertEquals("{ a = 1; if ((a == 1)) { b = 1; } NOP }", dump(relooper.render(A)!!.optimize()).toString(doIndent = false).trim()) + val C = relooper.node(stmt("c")) + relooper.edge(A, C, AstType.INT.local("a") eq 1.lit) + relooper.edge(A, B) + relooper.edge(B, C) + assertEquals(""" + a = 1; + if ((a == 1)) { + b = 1; + } + c = 1; + """.normalizeMulti(), relooper.render(A).dumpCollapse(types).normalizeMulti()) } + /* @Test fun testIf2() { val A = relooper.node(stmt("a")) val B = relooper.node(stmt("b")) @@ -67,8 +77,6 @@ class RelooperTest { } */ - fun String.normalizeMulti() = this.trimIndent().trim().lines().map { it.trimEnd() }.joinToString("\n") - @Test fun testDoubleWhile() { // A -> B -> C -> D -> E @@ -113,9 +121,9 @@ class RelooperTest { } while (true); } E = 1; - NOP(empty stm) - """.normalizeMulti(), result.dumpCollapse(types).toString().normalizeMulti()) - //println(dump(relooper.render(A)).toString()) - //Assert.assertEquals("{ a = 1; if ((a == 1)) { b = 1; } else { c = 1; } d = 1; }", dump(relooper.render(A)).toString(doIndent = false).trim()) + """.normalizeMulti(), result.dumpCollapse(types).normalizeMulti()) } + + fun String.normalizeMulti() = this.trimIndent().trim().lines().map { it.trimEnd() }.joinToString("\n") + fun Indenter.normalizeMulti() = this.toString().normalizeMulti() } \ No newline at end of file From 404e005b87c5cd99870de721f935afb3c1ae5b4e Mon Sep 17 00:00:00 2001 From: soywiz Date: Mon, 15 Jan 2018 17:38:14 +0100 Subject: [PATCH 09/60] Some more work on relooper --- jtransc-core/src/com/jtransc/ast/ast_body.kt | 2 + .../src/com/jtransc/graph/Relooper.kt | 12 ++- .../src/com/jtransc/graph/Relooper2.kt | 39 --------- .../test/com/jtransc/graph/Relooper2Test.kt | 45 ---------- .../test/com/jtransc/graph/RelooperTest.kt | 83 ++++++++++++------- 5 files changed, 63 insertions(+), 118 deletions(-) delete mode 100644 jtransc-core/src/com/jtransc/graph/Relooper2.kt delete mode 100644 jtransc-core/test/com/jtransc/graph/Relooper2Test.kt diff --git a/jtransc-core/src/com/jtransc/ast/ast_body.kt b/jtransc-core/src/com/jtransc/ast/ast_body.kt index e6a980a3..7b116619 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_body.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_body.kt @@ -800,6 +800,8 @@ operator fun AstExpr.minus(that: AstExpr) = AstExpr.BINOP(this.type, this, AstBi operator fun AstExpr.times(that: AstExpr) = AstExpr.BINOP(this.type, this, AstBinop.MUL, that) infix fun AstExpr.eq(that: AstExpr) = AstExpr.BINOP(this.type, this, AstBinop.EQ, that) infix fun AstExpr.ne(that: AstExpr) = AstExpr.BINOP(this.type, this, AstBinop.NE, that) +fun AstExpr.inv() = AstExpr.UNOP(AstUnop.INV, this) +//fun AstExpr.not() = AstExpr.UNOP(AstUnop.NOT, this) operator fun AstMethod.invoke(vararg exprs: AstExpr) = AstExpr.CALL_STATIC(this.ref, exprs.toList()) operator fun AstMethodRef.invoke(vararg exprs: AstExpr) = AstExpr.CALL_STATIC(this.ref, exprs.toList()) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 58cfa5e2..71628049 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -116,6 +116,10 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo * - Each loop/strong component should be splitted into smaller strong components after removing links to the beginning of the loop to detect inner loops */ fun renderComponents(g: StrongComponentGraph, entry: Node, exit: Node? = null, ctx: RenderContext = RenderContext(g.graph), level: Int = 0): AstStm { + if (level > 5) { + //throw RelooperException("Too much nesting levels!") + invalidOp("ERROR When Relooping $name (TOO MUCH NESTING LEVELS)") + } val indent by lazy { INDENTS[level] } val out = arrayListOf() var node: Node? = entry @@ -132,7 +136,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val outsNotInContext = outs.filter { it !in ctx.loopStarts && it !in ctx.loopEnds } if (outsNotInContext.size != 1) { trace { "ASSERTION FAILED! outsNotInContext.size != ${outsNotInContext.size} : $node" } - invalidOp + invalidOp("ERROR When Relooping $name (ASSERTION FAILED)") } val entryNode = node @@ -198,7 +202,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo // IF if (common == endOfIfNode) { out += AstStm.IF( - endOfIf.cond!!, + endOfIf.cond!!.not(), renderComponents(g, ifBody, endOfIfNode, ctx, level = level + 1) ) } @@ -206,8 +210,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo else { out += AstStm.IF_ELSE( endOfIf.cond!!, - renderComponents(g, ifBody, common, ctx, level = level + 1), - renderComponents(g, endOfIfNode, common, ctx, level = level + 1) + renderComponents(g, endOfIfNode, common, ctx, level = level + 1), + renderComponents(g, ifBody, common, ctx, level = level + 1) ) } return common diff --git a/jtransc-core/src/com/jtransc/graph/Relooper2.kt b/jtransc-core/src/com/jtransc/graph/Relooper2.kt deleted file mode 100644 index 75c48e35..00000000 --- a/jtransc-core/src/com/jtransc/graph/Relooper2.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.jtransc.graph - -import com.jtransc.ast.AstTypes - -class Relooper2(val types: AstTypes) { - inner class Node( - val body: TBody - ) { - val incoming = arrayListOf() - val outgoing = arrayListOf() - } - - inner class Component(val nodes: Set) - - inner class Edge(val src: Node, val dst: Node, val cond: TCond? = null) - - interface Stm - inner class Simple(val body: TBody) : Stm - inner class If(val cond: TCond, val tbody: Stm, val fbody: Stm?) : Stm - inner class While(val name: String, val cond: TCond, val tbody: Stm) : Stm - inner class DoWhile(val name: String, val tbody: Stm, val cond: TCond) : Stm - inner class Continue(val name: String) : Stm - inner class Break(val name: String) : Stm - - fun node(body: TBody): Node { - return Node(body) - } - - fun edge(src: Node, dst: Node, cond: TCond? = null): Edge { - return Edge(src, dst, cond).apply { - dst.incoming += this - src.outgoing += this - } - } - - fun render(node: Node): Stm { - TODO() - } -} \ No newline at end of file diff --git a/jtransc-core/test/com/jtransc/graph/Relooper2Test.kt b/jtransc-core/test/com/jtransc/graph/Relooper2Test.kt deleted file mode 100644 index 24e8537c..00000000 --- a/jtransc-core/test/com/jtransc/graph/Relooper2Test.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.jtransc.graph - -import com.jtransc.ast.AstTypes -import com.jtransc.gen.TargetName -import org.junit.Test - -class Relooper2Test { - val types = AstTypes(TargetName("js")) - val relooper = Relooper2(types) - - /* - @Test - fun name() { - // A -> B -> C --> D - // /|\___/ - val A = relooper.node("A") - val B = relooper.node("B") - val C = relooper.node("C") - val D = relooper.node("D") - relooper.edge(A, B, "loop") - } - */ - - @Test - fun name2() { - // START -> B -> C -> D -> END - // /|\ * | - // |_________/ - val START = relooper.node("START") - val B = relooper.node("B") - val C = relooper.node("C") - val D = relooper.node("D") - val END = relooper.node("END") - relooper.edge(START, B) - relooper.edge(B, C) - relooper.edge(C, D) - relooper.edge(D, END) - relooper.edge(D, B, "condLoopOut") - relooper.edge(D, END, "condLoopOutExit") - relooper.edge(C, C, "condLoopIn") - relooper.edge(C, END, "condLoopExit") - - relooper.render(START) - } -} \ No newline at end of file diff --git a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt index 33ae04e1..1c665791 100644 --- a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt +++ b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt @@ -21,13 +21,61 @@ class RelooperTest { relooper.edge(B, C) assertEquals(""" a = 1; - if ((a == 1)) { + if ((!(a == 1))) { b = 1; } c = 1; - """.normalizeMulti(), relooper.render(A).dumpCollapse(types).normalizeMulti()) + """.normalizeMulti(), relooper.renderStr(A)) + } + + @Test fun testIfElseExplicitEnd() { + val A = relooper.node(stmt("a")) + val B = relooper.node(stmt("b")) + val C = relooper.node(stmt("c")) + val D = relooper.node(stmt("d")) + relooper.edge(A, B, AstType.INT.local("a") eq 1.lit) + relooper.edge(A, C) + relooper.edge(B, D) + relooper.edge(C, D) + assertEquals(""" + a = 1; + if ((a == 1)) { + b = 1; + } + else { + c = 1; + } + d = 1; + """.normalizeMulti(), relooper.renderStr(A)) } + //@Test fun testDoubleIf() { + // val A = relooper.node(stmt("pre")) + // val B = relooper.node(stmt("if1")) + // val C = relooper.node(stmt("if2")) + // val D = relooper.node(stmt("end")) + // relooper.edge(A, B, AstType.INT.local("pre") eq 1.lit) + // relooper.edge(B, C, AstType.INT.local("if1") eq 1.lit) + // relooper.edge(C, D) + // relooper.edge(B, D) + // assertEquals(""" + // pre = 1; + // if ((pre == 1)) { + // if1 = 1; + // if ((if1 == 1)) { + // if2 = 1; + // } + // else { + // NOP(empty stm) + // } + // end = 1; + // } + // else { + // NOP(empty stm) + // } + // """.normalizeMulti(), relooper.renderStr(A)) + //} + /* @Test fun testIf2() { val A = relooper.node(stmt("a")) @@ -50,31 +98,6 @@ class RelooperTest { Assert.assertEquals("{ a = 1; if ((a == 1)) { b = 1; } else { c = 1; } NOP }", dump(relooper.render(A)).toString(doIndent = false).trim()) } - @Test fun testIfElseExplicitEnd() { - val A = relooper.node(stmt("a")) - val B = relooper.node(stmt("b")) - val C = relooper.node(stmt("c")) - val D = relooper.node(stmt("d")) - relooper.edge(A, B, AstExpr.build { INT.local("a") eq 1.lit }) - relooper.edge(A, C) - relooper.edge(B, D) - relooper.edge(C, D) - Assert.assertEquals("{ a = 1; if ((a == 1)) { b = 1; } else { c = 1; } d = 1; }", dump(relooper.render(A)).toString(doIndent = false).trim()) - } - - @Test fun testDoubleIf() { - val A = relooper.node(stmt("pre")) - val B = relooper.node(stmt("if1")) - val C = relooper.node(stmt("if2")) - val D = relooper.node(stmt("end")) - relooper.edge(A, B, AstExpr.build { INT.local("pre") eq 1.lit }) - relooper.edge(B, C, AstExpr.build { INT.local("if1") eq 1.lit }) - relooper.edge(C, D) - relooper.edge(B, D) - //relooper.edge(C, D) - println(dump(relooper.render(A)).toString()) - //Assert.assertEquals("{ a = 1; if ((a == 1)) { b = 1; } else { c = 1; } d = 1; }", dump(relooper.render(A)).toString(doIndent = false).trim()) - } */ @Test @@ -96,10 +119,9 @@ class RelooperTest { relooper.edge(C, C, AstExpr.RAW(AstType.BOOL, "condLoopInContinue")) relooper.edge(C, E, AstExpr.RAW(AstType.BOOL, "condLoopOutBreak")) relooper.edge(A, E, AstExpr.RAW(AstType.BOOL, "condToAvoidLoop")) - val result = relooper.render(A) assertEquals(""" A = 1; - if (condToAvoidLoop) { + if ((!condToAvoidLoop)) { do { B = 1; do { @@ -121,9 +143,10 @@ class RelooperTest { } while (true); } E = 1; - """.normalizeMulti(), result.dumpCollapse(types).normalizeMulti()) + """.normalizeMulti(), relooper.renderStr(A)) } fun String.normalizeMulti() = this.trimIndent().trim().lines().map { it.trimEnd() }.joinToString("\n") fun Indenter.normalizeMulti() = this.toString().normalizeMulti() + fun Relooper.renderStr(node: Relooper.Node) = render(node).dumpCollapse(types).normalizeMulti() } \ No newline at end of file From 8ca8e229636704fbde86bb2660d86e4a5aed738a Mon Sep 17 00:00:00 2001 From: soywiz Date: Mon, 15 Jan 2018 18:07:56 +0100 Subject: [PATCH 10/60] Some more work on relooper --- .../src/com/jtransc/graph/Relooper.kt | 28 +-- .../test/com/jtransc/graph/RelooperTest.kt | 173 ++++++++++++++---- 2 files changed, 150 insertions(+), 51 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 71628049..d5b4671f 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -18,7 +18,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val next get() = dstEdges.firstOrNull { it.cond == null }?.dst - override fun toString(): String = "L$index: " + dump(types, body.stm()).toString().trim() + "EDGES: $dstEdges. SRC_EDGES: ${srcEdges.size}" + override fun toString(): String = "L$index: " + dump(types, body.stm()).toString().replace('\n', ' ').trim() + " EDGES: $dstEdges. SRC_EDGES: ${srcEdges.size}" } class Edge(val types: AstTypes, val src: Node, val dst: Node, val cond: AstExpr? = null) { @@ -58,8 +58,12 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } fun render(entry: Node): AstStm { + val g = graphList(prepare(entry).map { it to it.possibleNextNodes }) trace { "Rendering $name" } - return renderComponents(graphList(prepare(entry).map { it to it.possibleNextNodes }).tarjanStronglyConnectedComponentsAlgorithm(), entry) + for (n in g.nodes) { + trace { "* $n" } + } + return renderComponents(g.tarjanStronglyConnectedComponentsAlgorithm(), entry) } class RenderContext(val graph: Digraph) { @@ -70,9 +74,10 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo fun allocName() = "loop${lastId++}" // @TODO: Optimize performance! And maybe cache? - fun getNodeSuccessorsLinkedSet(a: Node, checkRendered: Boolean = false): Set { + fun getNodeSuccessorsLinkedSet(a: Node, exit: Node?, checkRendered: Boolean = false): Set { val visited = LinkedHashSet() if (!checkRendered) visited += rendered + if (exit != null) visited += exit val set = LinkedHashSet() val queue = Queue() queue.queue(a) @@ -89,10 +94,11 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } // @TODO: Optimize performance! - fun findCommonSuccessorNotRendered(a: Node, b: Node): Node? { + fun findCommonSuccessorNotRendered(a: Node, b: Node, exit: Node?): Node? { val checkRendered = true - val aSet = getNodeSuccessorsLinkedSet(a, checkRendered) - val bSet = getNodeSuccessorsLinkedSet(b, checkRendered) + //val checkRendered = false + val aSet = getNodeSuccessorsLinkedSet(a, exit, checkRendered) + val bSet = getNodeSuccessorsLinkedSet(b, exit, checkRendered) for (item in bSet) { if (item in aSet) return item } @@ -135,7 +141,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val outs = component.getExternalOutputsNodes() val outsNotInContext = outs.filter { it !in ctx.loopStarts && it !in ctx.loopEnds } if (outsNotInContext.size != 1) { - trace { "ASSERTION FAILED! outsNotInContext.size != ${outsNotInContext.size} : $node" } + trace { "ASSERTION FAILED! outsNotInContext.size != 1 (${outsNotInContext.size}) : $node" } invalidOp("ERROR When Relooping $name (ASSERTION FAILED)") } @@ -154,7 +160,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo loopName, if (isSingleNodeLoop) { val out2 = arrayListOf() - renderNoLoops(g, out2, node, ctx, level) + renderNoLoops(g, out2, node, exitNode, ctx, level) //Stm(node.body) // @TODO: Here we should add ifs with breaks, and then convert put the condition there if possible out2.stmsWoNops } else { @@ -171,7 +177,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } // Not a loop else { - node = renderNoLoops(g, out, node, ctx, level = level) + node = renderNoLoops(g, out, node, exit, ctx, level = level) } } return out.stmsWoNops @@ -179,7 +185,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val Iterable.stmsWoNops: AstStm get() = this.toList().filter { it !is AstStm.NOP }.stm() - fun renderNoLoops(g: StrongComponentGraph, out: ArrayList, node: Node, ctx: RenderContext, level: Int): Node? { + fun renderNoLoops(g: StrongComponentGraph, out: ArrayList, node: Node, exit: Node?, ctx: RenderContext, level: Int): Node? { val indent = INDENTS[level] trace { "$indent- Detected no loop : $node" } out += node.body.stmsWoNops @@ -197,7 +203,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val ifBody = node.next!! val endOfIf = node.dstEdgesButNext.firstOrNull() ?: invalidOp("Expected conditional!") val endOfIfNode = endOfIf.dst - val common = ctx.findCommonSuccessorNotRendered(ifBody, endOfIfNode) + val common = ctx.findCommonSuccessorNotRendered(ifBody, endOfIfNode, exit) // IF if (common == endOfIfNode) { diff --git a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt index 1c665791..19dcfe01 100644 --- a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt +++ b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt @@ -8,36 +8,36 @@ import org.junit.Test class RelooperTest { val types = AstTypes(TargetName("js")) - val relooper = Relooper(types) + val relooper = Relooper(types, debug = true) - private fun stmt(name: String): AstStm = AstType.INT.local(name).setTo(1.lit) - - @Test fun testIf() { - val A = relooper.node(stmt("a")) - val B = relooper.node(stmt("b")) - val C = relooper.node(stmt("c")) - relooper.edge(A, C, AstType.INT.local("a") eq 1.lit) - relooper.edge(A, B) - relooper.edge(B, C) - assertEquals(""" + @Test + fun testIf() = relooperTest { + val A = node("a") + val B = node("b") + val C = node("c") + edge(A, C, AstType.INT.local("a") eq 1.lit) + edge(A, B) + edge(B, C) + A.assertDump(""" a = 1; if ((!(a == 1))) { b = 1; } c = 1; - """.normalizeMulti(), relooper.renderStr(A)) + """) } - @Test fun testIfElseExplicitEnd() { - val A = relooper.node(stmt("a")) - val B = relooper.node(stmt("b")) - val C = relooper.node(stmt("c")) - val D = relooper.node(stmt("d")) - relooper.edge(A, B, AstType.INT.local("a") eq 1.lit) - relooper.edge(A, C) - relooper.edge(B, D) - relooper.edge(C, D) - assertEquals(""" + @Test + fun testIfElseExplicitEnd() = relooperTest { + val A = node("a") + val B = node("b") + val C = node("c") + val D = node("d") + edge(A, B, AstType.INT.local("a") eq 1.lit) + edge(A, C) + edge(B, D) + edge(C, D) + A.assertDump(""" a = 1; if ((a == 1)) { b = 1; @@ -46,7 +46,7 @@ class RelooperTest { c = 1; } d = 1; - """.normalizeMulti(), relooper.renderStr(A)) + """) } //@Test fun testDoubleIf() { @@ -101,25 +101,25 @@ class RelooperTest { */ @Test - fun testDoubleWhile() { + fun testDoubleWhile() = relooperTest { // A -> B -> C -> D -> E // /|\ * | // |_________/ - val A = relooper.node(stmt("A")) - val B = relooper.node(stmt("B")) - val C = relooper.node(stmt("C")) - val D = relooper.node(stmt("D")) - val E = relooper.node(stmt("E")) - relooper.edge(A, B) - relooper.edge(B, C) - relooper.edge(C, D) - relooper.edge(D, E) - relooper.edge(D, B, AstExpr.RAW(AstType.BOOL, "condLoopOutContinue")) - relooper.edge(D, E, AstExpr.RAW(AstType.BOOL, "condLoopOutBreak")) - relooper.edge(C, C, AstExpr.RAW(AstType.BOOL, "condLoopInContinue")) - relooper.edge(C, E, AstExpr.RAW(AstType.BOOL, "condLoopOutBreak")) - relooper.edge(A, E, AstExpr.RAW(AstType.BOOL, "condToAvoidLoop")) - assertEquals(""" + val A = node(stmt("A")) + val B = node(stmt("B")) + val C = node(stmt("C")) + val D = node(stmt("D")) + val E = node(stmt("E")) + edge(A, B) + edge(B, C) + edge(C, D) + edge(D, E) + edge(D, B, AstExpr.RAW(AstType.BOOL, "condLoopOutContinue")) + edge(D, E, AstExpr.RAW(AstType.BOOL, "condLoopOutBreak")) + edge(C, C, AstExpr.RAW(AstType.BOOL, "condLoopInContinue")) + edge(C, E, AstExpr.RAW(AstType.BOOL, "condLoopOutBreak")) + edge(A, E, AstExpr.RAW(AstType.BOOL, "condToAvoidLoop")) + A.assertDump(""" A = 1; if ((!condToAvoidLoop)) { do { @@ -143,10 +143,103 @@ class RelooperTest { } while (true); } E = 1; - """.normalizeMulti(), relooper.renderStr(A)) + """) } + @Test + fun testSmallFunctionWithWhileAndIf() = relooperTest { + val L0 = node("L0") + val L1 = node("L1") + val L2 = node("L2") + val L3 = node("L3") + val L4 = node("L4") + val L5 = node("L5") + val L6 = node("L6") + val L8 = node("L8") + L0.edgeTo(L1) + L1.edgeTo(L2).edgeTo(L3, cond("l1_l3")) + L2.edgeTo(L4) + L3.edgeTo(L6).edgeTo(L1, "l3_l1") + L4.edgeTo(L5).edgeTo(L4, cond("l4_l4")) + L5.edgeTo(L3) + L6.edgeTo(L8) + + //* L0: { lI0 = p0; lI1 = p1; lI1 = (lI1 + 1); }EDGES: [goto L1;]. SRC_EDGES: 0 + //* L1: EDGES: [goto L2;, IF (((lI0 % 2) != 0)) goto L3;]. SRC_EDGES: 2 + //* L2: NOP(empty stm)EDGES: [goto L4;]. SRC_EDGES: 1 + //* L3: { lI0 = (lI0 + 1); }EDGES: [goto L6;, IF ((lI0 < lI1)) goto L1;]. SRC_EDGES: 2 + //* L4: { com.jtransc.io.JTranscConsole.log(lI0); lI0 = (lI0 + 1); }EDGES: [goto L5;, IF ((lI0 < lI1)) goto L4;]. SRC_EDGES: 2 + //* L5: NOP(empty stm)EDGES: [goto L3;]. SRC_EDGES: 1 + //* L6: { return lI1; }EDGES: [goto L8;]. SRC_EDGES: 1 + //* L8: NOP(empty stm)EDGES: []. SRC_EDGES: 1 + + + //@JTranscRelooper(value = true, debug = true) + //static public int simpleDoWhile(int a, int b) { + // b++; + // + // do { + // if (a % 2 == 0) { + // do { + // JTranscConsole.log(a); + // a++; + // } while (a < b); + // } + // a++; + // } while (a < b); + // + // return b; + //} + + L0.assertDump(""" + - + """) + } + + @Test + fun testSimpleDoWhile() = relooperTest { + val L0 = node("L0") + val L1 = node("L1") + val L2 = node("L2") + val L4 = node("L4") + + L0.edgeTo(L1) + L1.edgeTo(L2).edgeTo(L1, "lI0 < lI1") + L2.edgeTo(L4) + + L0.assertDump(""" + - + """) + + //* L0: { lI0 = p0; lI1 = p1; lI1 = (lI1 + 1); } EDGES: [goto L1;]. SRC_EDGES: 0 + //* L1: { lI0 = (lI0 + 1); } EDGES: [goto L2;, IF ((lI0 < lI1)) goto L1;]. SRC_EDGES: 2 + //* L2: { return lI1; } EDGES: [goto L4;]. SRC_EDGES: 1 + //* L4: NOP(empty stm) EDGES: []. SRC_EDGES: 1 + + //@JTranscRelooper(value = true, debug = true) + //static public int simpleDoWhile(int a, int b) { + // b++; + // + // do { + // a++; + // } while (a < b); + // + // return b; + //} + } + + fun Relooper.Node.assertDump(msg: String) { + assertEquals(msg.normalizeMulti(), relooper.renderStr(this)) + } + + inline fun relooperTest(callback: Relooper.() -> Unit): Unit = callback(relooper) + + fun Relooper.Node.edgeTo(other: Relooper.Node, cond: AstExpr? = null): Relooper.Node = this.apply { relooper.edge(this, other, cond) } + fun Relooper.Node.edgeTo(other: Relooper.Node, cond: String): Relooper.Node = this.edgeTo(other, cond.let { cond(it) }) fun String.normalizeMulti() = this.trimIndent().trim().lines().map { it.trimEnd() }.joinToString("\n") fun Indenter.normalizeMulti() = this.toString().normalizeMulti() fun Relooper.renderStr(node: Relooper.Node) = render(node).dumpCollapse(types).normalizeMulti() + fun Relooper.node(name: String) = node(stmt(name)) + private fun stmt(name: String): AstStm = AstType.INT.local(name).setTo(1.lit) + private fun cond(name: String) = AstExpr.RAW(AstType.BOOL, name) } \ No newline at end of file From 3ec897c84d707eccba1a30d686a259500c904058 Mon Sep 17 00:00:00 2001 From: soywiz Date: Mon, 15 Jan 2018 18:38:48 +0100 Subject: [PATCH 11/60] Some more work on relooper --- .../src/com/jtransc/ast/AstTransformer.kt | 7 ++ .../src/com/jtransc/ast/AstVisitor.kt | 6 ++ .../ast/feature/method/GotosFeature.kt | 3 +- .../com/jtransc/gen/common/CommonGenerator.kt | 9 ++ .../src/com/jtransc/graph/Relooper.kt | 102 +++++++++++------- .../test/com/jtransc/graph/RelooperTest.kt | 2 + .../src/com/jtransc/gen/js/JsTarget.kt | 6 +- 7 files changed, 96 insertions(+), 39 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/AstTransformer.kt b/jtransc-core/src/com/jtransc/ast/AstTransformer.kt index 2eb34cea..a28f5146 100644 --- a/jtransc-core/src/com/jtransc/ast/AstTransformer.kt +++ b/jtransc-core/src/com/jtransc/ast/AstTransformer.kt @@ -45,6 +45,7 @@ open class AstTransformer { is AstStm.IF -> transform(stm) is AstStm.IF_ELSE -> transform(stm) is AstStm.WHILE -> transform(stm) + is AstStm.DO_WHILE -> transform(stm) is AstStm.RETURN -> transform(stm) is AstStm.RETURN_VOID -> transform(stm) is AstStm.THROW -> transform(stm) @@ -203,6 +204,12 @@ open class AstTransformer { return stm } + open fun transform(stm: AstStm.DO_WHILE): AstStm { + transform(stm.iter) + transform(stm.cond) + return stm + } + open fun transform(stm: AstStm.RETURN): AstStm { transform(stm.retval) return stm diff --git a/jtransc-core/src/com/jtransc/ast/AstVisitor.kt b/jtransc-core/src/com/jtransc/ast/AstVisitor.kt index c07e1e11..8a2c5549 100644 --- a/jtransc-core/src/com/jtransc/ast/AstVisitor.kt +++ b/jtransc-core/src/com/jtransc/ast/AstVisitor.kt @@ -60,6 +60,7 @@ open class AstVisitor { is AstStm.IF -> visit(stm) is AstStm.IF_ELSE -> visit(stm) is AstStm.WHILE -> visit(stm) + is AstStm.DO_WHILE -> visit(stm) is AstStm.RETURN -> visit(stm) is AstStm.RETURN_VOID -> visit(stm) is AstStm.THROW -> visit(stm) @@ -206,6 +207,11 @@ open class AstVisitor { visit(stm.iter) } + open fun visit(stm: AstStm.DO_WHILE) { + visit(stm.iter) + visit(stm.cond) + } + open fun visit(stm: AstStm.RETURN) { visit(stm.retval) } diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index 9415797c..0f5e11cc 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -25,7 +25,8 @@ import com.jtransc.graph.RelooperException // @TODO: Use AstBuilder to make it more readable class GotosFeature : AstMethodFeature() { override fun remove(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody { - if (method.relooperEnabled ?: settings.relooper) { + //if (method.relooperEnabled ?: settings.relooper) { + if (method.relooperEnabled ?: false) { try { return removeRelooper(method, body, settings, types) ?: removeMachineState(body, types) } catch (t: Throwable) { diff --git a/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt b/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt index 7a88eaf6..0bc4d5a1 100644 --- a/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt +++ b/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt @@ -176,6 +176,7 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { open fun writeClasses(output: SyncVfsFile) { if (SINGLE_FILE) { + output.removeIfExists() if (ADD_UTF8_BOM) { output[outputFileBaseName] = byteArrayOf(0xEF.toByte(), 0xBB.toByte(), 0xBF.toByte()) + genSingleFileClasses(output).toString().toByteArray() } else { @@ -441,6 +442,7 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { is AstStm.SWITCH_GOTO -> genStmSwitchGoto(stm) is AstStm.NOP -> genStmNop(stm) is AstStm.WHILE -> genStmWhile(stm) + is AstStm.DO_WHILE -> genStmDoWhile(stm) is AstStm.IF -> genStmIf(stm) is AstStm.IF_ELSE -> genStmIfElse(stm) is AstStm.RETURN_VOID -> genStmReturnVoid(stm, last) @@ -1346,6 +1348,13 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { } } } + fun genStmDoWhile(stm: AstStm.DO_WHILE) = indent { + flowBlock(FlowKind.WHILE) { + line("do", after2 = " while (${stm.cond.genExpr()});") { + line(stm.iter.genStm()) + } + } + } open fun genStmIf(stm: AstStm.IF) = indent { line("if (${stm.cond.genExpr()})") { line(stm.strue.genStm()) } diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index d5b4671f..421168af 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -8,7 +8,6 @@ import java.util.* import kotlin.collections.LinkedHashSet class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boolean = false) { - class Graph class Node(val types: AstTypes, val index: Int, val body: List) { //var next: Node? = null val srcEdges = arrayListOf() @@ -17,6 +16,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val possibleNextNodes: List get() = dstEdges.map { it.dst } val next get() = dstEdges.firstOrNull { it.cond == null }?.dst + val nextEdge get() = dstEdges.firstOrNull { it.cond == null } override fun toString(): String = "L$index: " + dump(types, body.stm()).toString().replace('\n', ' ').trim() + " EDGES: $dstEdges. SRC_EDGES: ${srcEdges.size}" } @@ -63,7 +63,10 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo for (n in g.nodes) { trace { "* $n" } } - return renderComponents(g.tarjanStronglyConnectedComponentsAlgorithm(), entry) + println("Relooping '$name'...") + val result = renderComponents(g.tarjanStronglyConnectedComponentsAlgorithm(), entry) + println("Relooping '$name'...OK") + return result } class RenderContext(val graph: Digraph) { @@ -137,11 +140,12 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo // Loop if (isMultiNodeLoop || isSingleNodeLoop) { - trace { "$indent- Detected node loop csize=${component.size} : $node" } + trace { "$indent- LOOP csize=${component.size} : $node" } val outs = component.getExternalOutputsNodes() val outsNotInContext = outs.filter { it !in ctx.loopStarts && it !in ctx.loopEnds } + //val outsNotInContext = outs.filter { it !in ctx.loopStarts } if (outsNotInContext.size != 1) { - trace { "ASSERTION FAILED! outsNotInContext.size != 1 (${outsNotInContext.size}) : $node" } + trace { "$indent- ASSERTION FAILED! outsNotInContext.size != 1 (${outsNotInContext.size}) : $node" } invalidOp("ERROR When Relooping $name (ASSERTION FAILED)") } @@ -150,8 +154,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val loopName = ctx.allocName() - trace { "$indent:: ${entryNode.index} - ${exitNode.index}" } - trace { "$indent:: ${component}" } + //trace { "$indent:: ${entryNode.index} - ${exitNode.index}" } + //trace { "$indent:: ${component}" } ctx.loopStarts[entryNode] = loopName ctx.loopEnds[exitNode] = loopName @@ -159,11 +163,13 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo out += AstStm.DO_WHILE( loopName, if (isSingleNodeLoop) { + trace { "$indent- render single node: renderNoLoops" } val out2 = arrayListOf() renderNoLoops(g, out2, node, exitNode, ctx, level) //Stm(node.body) // @TODO: Here we should add ifs with breaks, and then convert put the condition there if possible out2.stmsWoNops } else { + trace { "$indent- render multi node: renderComponents" } renderComponents(component.split(entryNode, exitNode), entryNode, exitNode, ctx, level = level + 1) } , @@ -187,8 +193,20 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo fun renderNoLoops(g: StrongComponentGraph, out: ArrayList, node: Node, exit: Node?, ctx: RenderContext, level: Int): Node? { val indent = INDENTS[level] - trace { "$indent- Detected no loop : $node" } + trace { "$indent- renderNoLoops: Detected no loop : $node" } out += node.body.stmsWoNops + + fun getNodeContinueOrBreak(node: Relooper.Node?): AstStm? { + val loopStart = ctx.loopStarts[node] + val loopEnd = ctx.loopEnds[node] + return when { + loopStart != null -> AstStm.CONTINUE(loopStart) + loopEnd != null -> AstStm.BREAK(loopEnd) + else -> null + } + + } + when (node.dstEdges.size) { 0, 1 -> { if (node.dstEdges.size == 0) { @@ -199,44 +217,56 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo if (node.dstEdges.size == 0) return null } 2 -> { - trace { "$indent- Node IF (and else?)" } - val ifBody = node.next!! - val endOfIf = node.dstEdgesButNext.firstOrNull() ?: invalidOp("Expected conditional!") - val endOfIfNode = endOfIf.dst - val common = ctx.findCommonSuccessorNotRendered(ifBody, endOfIfNode, exit) - - // IF - if (common == endOfIfNode) { - out += AstStm.IF( - endOfIf.cond!!.not(), - renderComponents(g, ifBody, endOfIfNode, ctx, level = level + 1) - ) - } - // IF+ELSE - else { - out += AstStm.IF_ELSE( - endOfIf.cond!!, - renderComponents(g, endOfIfNode, common, ctx, level = level + 1), - renderComponents(g, ifBody, common, ctx, level = level + 1) - ) - } - return common + } else -> { //TODO() } } + var ifsAdded = 0 for (e in node.dstEdgesButNext) { - val loopStart = ctx.loopStarts[e.dst] - val loopEnd = ctx.loopEnds[e.dst] - when { - loopStart != null -> out += AstStm.IF(e.cond!!, AstStm.CONTINUE(loopStart)) - loopEnd != null -> out += AstStm.IF(e.cond!!, AstStm.BREAK(loopEnd)) - else -> TODO() + val breakOrContinue = getNodeContinueOrBreak(e.dst) ?: break + out += AstStm.IF(e.cond!!, breakOrContinue) + ifsAdded++ + } + + if (ifsAdded == node.dstEdgesButNext.size) { + val breakOrContinue = getNodeContinueOrBreak(node.next) + if (breakOrContinue != null) { + out += breakOrContinue } + return node.next + } + + if (node.dstEdges.size != 2) { + TODO() + } + + trace { "$indent- Node IF (and else?)" } + val ifBody = node.next!! + val endOfIf = node.dstEdgesButNext.firstOrNull() ?: invalidOp("Expected conditional!") + val endOfIfNode = endOfIf.dst + val common = ctx.findCommonSuccessorNotRendered(ifBody, endOfIfNode, exit) + + // IF + if (common == endOfIfNode) { + out += AstStm.IF( + endOfIf.cond!!.not(), + renderComponents(g, ifBody, endOfIfNode, ctx, level = level + 1) + ) + } + // IF+ELSE + else { + out += AstStm.IF_ELSE( + endOfIf.cond!!, + renderComponents(g, endOfIfNode, common, ctx, level = level + 1), + renderComponents(g, ifBody, common, ctx, level = level + 1) + ) } - return node.next + return common + + } fun StrongComponent.isMultiNodeLoop(): Boolean = (size > 1) diff --git a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt index 19dcfe01..b5a6dd64 100644 --- a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt +++ b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt @@ -132,6 +132,7 @@ class RelooperTest { if (condLoopOutBreak) { break; } + break; } while (true); D = 1; if (condLoopOutContinue) { @@ -140,6 +141,7 @@ class RelooperTest { if (condLoopOutBreak) { break; } + break; } while (true); } E = 1; diff --git a/jtransc-gen-js/src/com/jtransc/gen/js/JsTarget.kt b/jtransc-gen-js/src/com/jtransc/gen/js/JsTarget.kt index 621ea602..ee5088b6 100644 --- a/jtransc-gen-js/src/com/jtransc/gen/js/JsTarget.kt +++ b/jtransc-gen-js/src/com/jtransc/gen/js/JsTarget.kt @@ -131,6 +131,8 @@ class JsGenerator(injector: Injector) : CommonGenerator(injector) { @Suppress("UNCHECKED_CAST") override fun writeClasses(output: SyncVfsFile) { + output[outputFileBaseName] = "" + output["$outputFileBaseName.map"] = "" val concatFilesTrans = copyFiles(output) val classesIndenter = arrayListOf() @@ -228,13 +230,13 @@ class JsGenerator(injector: Injector) : CommonGenerator(injector) { // Generate source //println("outputFileBaseName:$outputFileBaseName") output[outputFileBaseName] = byteArrayOf(0xEF.toByte(), 0xBB.toByte(), 0xBF.toByte()) + source.toByteArray(Charsets.UTF_8) - if (sourceMap != null) output[outputFileBaseName + ".map"] = sourceMap + if (sourceMap != null) output["$outputFileBaseName.map"] = sourceMap injector.mapInstance(ConfigJavascriptOutput(output[outputFile])) } override fun genSICall(it: AstClass): String { - return "$AWAIT " + "${it.name.targetNameForStatic}" + access("SI", static = true, field = false) + "($JC);" + return "$AWAIT ${it.name.targetNameForStatic}" + access("SI", static = true, field = false) + "($JC);" } override fun genStmTryCatch(stm: AstStm.TRY_CATCH) = indent { From 5ff3685344ce0a78f880505f3282d84f8dd5c592 Mon Sep 17 00:00:00 2001 From: soywiz Date: Mon, 15 Jan 2018 18:46:47 +0100 Subject: [PATCH 12/60] Some more work on relooper --- .../com/jtransc/gen/common/CommonGenerator.kt | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt b/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt index 0bc4d5a1..16771496 100644 --- a/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt +++ b/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt @@ -873,7 +873,7 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { } fun genStmSwitchGoto(stm: AstStm.SWITCH_GOTO): Indenter = indent { - flowBlock(FlowKind.SWITCH) { + flowBlock(FlowKind.SWITCH, "") { line("switch (${stm.subject.genExpr()})") { for ((values, label) in stm.cases) { line("${buildMultipleCase(values)} ${genGoto(label, false)}") @@ -905,8 +905,8 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { open fun actualSetField(stm: AstStm.SET_FIELD_INSTANCE, left: String, right: String): String = "$left = $right;" - open fun genStmContinue(stm: AstStm.CONTINUE) = Indenter("continue;") - open fun genStmBreak(stm: AstStm.BREAK) = Indenter("break;") + open fun genStmContinue(stm: AstStm.CONTINUE) = Indenter("continue ${stm.name};") + open fun genStmBreak(stm: AstStm.BREAK) = Indenter("break ${stm.name};") open fun genStmLabel(stm: AstStm.STM_LABEL): Indenter = Indenter { if (stm.label in trapsByEnd) { for (trap in trapsByEnd[stm.label]!!) line(genStmRawCatch(trap)) @@ -930,18 +930,21 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { enum class FlowKind { SWITCH, WHILE } val flowBlocks = ArrayList() + val flowNames = ArrayList() - inline fun flowBlock(kind: FlowKind, callback: () -> T): T { + inline fun flowBlock(kind: FlowKind, name: String, callback: () -> T): T { try { flowBlocks += kind + flowNames += name return callback() } finally { + flowNames.removeAt(flowNames.size - 1) flowBlocks.removeAt(flowBlocks.size - 1) } } open fun genStmSwitch(stm: AstStm.SWITCH): Indenter = indent { - flowBlock(FlowKind.SWITCH) { + flowBlock(FlowKind.SWITCH, "") { if (stm.cases.isNotEmpty() || !stm.default.value.isEmpty()) { line("switch (${stm.subject.genExpr()})") { for (case in stm.cases) { @@ -1342,15 +1345,19 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { open fun genStmReturnVoid(stm: AstStm.RETURN_VOID, last: Boolean) = Indenter(if (context.method.methodVoidReturnThis) "return " + genExprThis(AstExpr.THIS("Dummy".fqname)) + ";" else "return;") open fun genStmReturnValue(stm: AstStm.RETURN, last: Boolean) = Indenter("return ${stm.retval.genExpr()};") fun genStmWhile(stm: AstStm.WHILE) = indent { - flowBlock(FlowKind.WHILE) { - line("while (${stm.cond.genExpr()})") { + val label = "${stm.name}:" + + flowBlock(FlowKind.WHILE, stm.name) { + line("$label while (${stm.cond.genExpr()})") { line(stm.iter.genStm()) } } } fun genStmDoWhile(stm: AstStm.DO_WHILE) = indent { - flowBlock(FlowKind.WHILE) { - line("do", after2 = " while (${stm.cond.genExpr()});") { + val label = "${stm.name}:" + + flowBlock(FlowKind.WHILE, stm.name) { + line("$label do", after2 = " while (${stm.cond.genExpr()});") { line(stm.iter.genStm()) } } From ecd691b66d924c5fc50a68588325b2d494955138 Mon Sep 17 00:00:00 2001 From: soywiz Date: Mon, 15 Jan 2018 18:59:49 +0100 Subject: [PATCH 13/60] Some more work on relooper --- jtransc-core/src/com/jtransc/ast/ast_body.kt | 3 +- ...deterministicParameterEvaluationFeature.kt | 2 +- .../src/com/jtransc/graph/Relooper.kt | 22 +++++++++++---- .../test/com/jtransc/graph/RelooperTest.kt | 28 +++++++++++++++++-- 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/ast_body.kt b/jtransc-core/src/com/jtransc/ast/ast_body.kt index 7b116619..3e7d9daf 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_body.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_body.kt @@ -3,7 +3,6 @@ package com.jtransc.ast import com.jtransc.ds.cast import com.jtransc.error.invalidOp import com.jtransc.error.noImpl -import com.jtransc.gen.TargetName import kotlin.reflect.KProperty data class AstBody constructor( @@ -209,7 +208,7 @@ sealed class AstStm : AstElement, Cloneable { } // Basic back jump - class DO_WHILE(val name: String, iter: AstStm, cond: AstExpr) : AstStm() { + class DO_WHILE(val name: String, cond: AstExpr, iter: AstStm) : AstStm() { val cond = cond.box val iter = iter.box } diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt index 94d35389..7153be7c 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt @@ -42,7 +42,7 @@ class UndeterministicParameterEvaluationFeature : AstMethodFeature() { is AstStm.MONITOR_EXIT -> out += AstStm.MONITOR_EXIT(stm.expr.processExpr(out, self = false)) is AstStm.THROW -> out += AstStm.THROW(stm.exception.processExpr(out, self = false)) is AstStm.WHILE -> out += AstStm.WHILE(stm.name, stm.cond.processExpr(out, self = false), stm.iter.value) - is AstStm.DO_WHILE -> out += AstStm.DO_WHILE(stm.name, stm.iter.value, stm.cond.processExpr(out, self = false)) + is AstStm.DO_WHILE -> out += AstStm.DO_WHILE(stm.name, stm.cond.processExpr(out, self = false), stm.iter.value) is AstStm.SET_LOCAL -> out += AstStm.SET_LOCAL(stm.local, stm.expr.processExpr(out), true) is AstStm.SET_ARRAY -> { // Like this to keep order diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 421168af..9d63514b 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -160,20 +160,30 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo ctx.loopStarts[entryNode] = loopName ctx.loopEnds[exitNode] = loopName + val optimizedWhile = isSingleNodeLoop && (node.dstEdgesButNext.size == 1) + val cond: AstExpr = if (optimizedWhile) { + node.dstEdgesButNext.first().cond!! + } else { + true.lit + } + + out += AstStm.DO_WHILE( loopName, + cond, if (isSingleNodeLoop) { trace { "$indent- render single node: renderNoLoops" } - val out2 = arrayListOf() - renderNoLoops(g, out2, node, exitNode, ctx, level) - //Stm(node.body) // @TODO: Here we should add ifs with breaks, and then convert put the condition there if possible - out2.stmsWoNops + if (optimizedWhile) { + node.body.stms + } else { + val out2 = arrayListOf() + renderNoLoops(g, out2, node, exitNode, ctx, level) + out2.stmsWoNops + } } else { trace { "$indent- render multi node: renderComponents" } renderComponents(component.split(entryNode, exitNode), entryNode, exitNode, ctx, level = level + 1) } - , - true.lit ) ctx.loopEnds -= exitNode diff --git a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt index b5a6dd64..dee7ae01 100644 --- a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt +++ b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt @@ -194,7 +194,26 @@ class RelooperTest { //} L0.assertDump(""" - - + L0 = 1; + do { + L1 = 1; + if ((!l1_l3)) { + { + L2 = 1; + do { + L4 = 1; + } while (l4_l4); + L5 = 1; + } + } + L3 = 1; + if (l3_l1) { + continue; + } + break; + } while (true); + L6 = 1; + L8 = 1; """) } @@ -210,7 +229,12 @@ class RelooperTest { L2.edgeTo(L4) L0.assertDump(""" - - + L0 = 1; + do { + L1 = 1; + } while (lI0 < lI1); + L2 = 1; + L4 = 1; """) //* L0: { lI0 = p0; lI1 = p1; lI1 = (lI1 + 1); } EDGES: [goto L1;]. SRC_EDGES: 0 From 3f1c6373117d1e786f0ae11590c82ce97546144c Mon Sep 17 00:00:00 2001 From: soywiz Date: Mon, 15 Jan 2018 20:51:18 +0100 Subject: [PATCH 14/60] Some more work on relooper --- .../src/com/jtransc/ast/AstTransformer.kt | 2 +- .../src/com/jtransc/ast/AstVisitor.kt | 2 +- jtransc-core/src/com/jtransc/ast/ast_body.kt | 2 +- jtransc-core/src/com/jtransc/ast/ast_dump.kt | 10 ++-- ...deterministicParameterEvaluationFeature.kt | 2 +- .../com/jtransc/gen/common/CommonGenerator.kt | 2 +- .../src/com/jtransc/graph/Relooper.kt | 51 +++++++++++++------ 7 files changed, 46 insertions(+), 25 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/AstTransformer.kt b/jtransc-core/src/com/jtransc/ast/AstTransformer.kt index a28f5146..f800e126 100644 --- a/jtransc-core/src/com/jtransc/ast/AstTransformer.kt +++ b/jtransc-core/src/com/jtransc/ast/AstTransformer.kt @@ -205,7 +205,7 @@ open class AstTransformer { } open fun transform(stm: AstStm.DO_WHILE): AstStm { - transform(stm.iter) + transform(stm.body) transform(stm.cond) return stm } diff --git a/jtransc-core/src/com/jtransc/ast/AstVisitor.kt b/jtransc-core/src/com/jtransc/ast/AstVisitor.kt index 8a2c5549..52c4bc97 100644 --- a/jtransc-core/src/com/jtransc/ast/AstVisitor.kt +++ b/jtransc-core/src/com/jtransc/ast/AstVisitor.kt @@ -208,7 +208,7 @@ open class AstVisitor { } open fun visit(stm: AstStm.DO_WHILE) { - visit(stm.iter) + visit(stm.body) visit(stm.cond) } diff --git a/jtransc-core/src/com/jtransc/ast/ast_body.kt b/jtransc-core/src/com/jtransc/ast/ast_body.kt index 3e7d9daf..87ef20ff 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_body.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_body.kt @@ -210,7 +210,7 @@ sealed class AstStm : AstElement, Cloneable { // Basic back jump class DO_WHILE(val name: String, cond: AstExpr, iter: AstStm) : AstStm() { val cond = cond.box - val iter = iter.box + val body = iter.box } class RETURN(retval: AstExpr) : AstStm() { diff --git a/jtransc-core/src/com/jtransc/ast/ast_dump.kt b/jtransc-core/src/com/jtransc/ast/ast_dump.kt index 4b6b5ef2..ceb7d8c6 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_dump.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_dump.kt @@ -67,8 +67,8 @@ fun dump(types: AstTypes, stm: AstStm?): Indenter { //line("LINE(${stm.line})") } is AstStm.NOP -> line("NOP(${stm.reason})") - is AstStm.CONTINUE -> line("continue;") - is AstStm.BREAK -> line("break;") + is AstStm.CONTINUE -> line("continue ${stm.name};") + is AstStm.BREAK -> line("break ${stm.name};") is AstStm.THROW -> line("throw ${dump(types, stm.exception)};") is AstStm.IF -> line("if (${dump(types, stm.cond)})") { line(dump(types, stm.strue)) } is AstStm.IF_ELSE -> { @@ -76,13 +76,13 @@ fun dump(types: AstTypes, stm: AstStm?): Indenter { line("else") { line(dumpCollapse(types, stm.sfalse)) } } is AstStm.WHILE -> { - line("while (${dump(types, stm.cond)})") { + line("${stm.name}: while (${dump(types, stm.cond)})") { line(dumpCollapse(types, stm.iter)) } } is AstStm.DO_WHILE -> { - line("do", after2 = " while (${dump(types, stm.cond)});") { - line(dumpCollapse(types, stm.iter)) + line("${stm.name}: do", after2 = " while (${dump(types, stm.cond)});") { + line(dumpCollapse(types, stm.body)) } } is AstStm.SWITCH -> { diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt index 7153be7c..fd8eb456 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt @@ -42,7 +42,7 @@ class UndeterministicParameterEvaluationFeature : AstMethodFeature() { is AstStm.MONITOR_EXIT -> out += AstStm.MONITOR_EXIT(stm.expr.processExpr(out, self = false)) is AstStm.THROW -> out += AstStm.THROW(stm.exception.processExpr(out, self = false)) is AstStm.WHILE -> out += AstStm.WHILE(stm.name, stm.cond.processExpr(out, self = false), stm.iter.value) - is AstStm.DO_WHILE -> out += AstStm.DO_WHILE(stm.name, stm.cond.processExpr(out, self = false), stm.iter.value) + is AstStm.DO_WHILE -> out += AstStm.DO_WHILE(stm.name, stm.cond.processExpr(out, self = false), stm.body.value) is AstStm.SET_LOCAL -> out += AstStm.SET_LOCAL(stm.local, stm.expr.processExpr(out), true) is AstStm.SET_ARRAY -> { // Like this to keep order diff --git a/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt b/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt index 16771496..877a93ee 100644 --- a/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt +++ b/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt @@ -1358,7 +1358,7 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { flowBlock(FlowKind.WHILE, stm.name) { line("$label do", after2 = " while (${stm.cond.genExpr()});") { - line(stm.iter.genStm()) + line(stm.body.genStm()) } } } diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 9d63514b..5617725a 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -98,13 +98,28 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo // @TODO: Optimize performance! fun findCommonSuccessorNotRendered(a: Node, b: Node, exit: Node?): Node? { - val checkRendered = true - //val checkRendered = false + //val checkRendered = true + val checkRendered = false val aSet = getNodeSuccessorsLinkedSet(a, exit, checkRendered) val bSet = getNodeSuccessorsLinkedSet(b, exit, checkRendered) - for (item in bSet) { - if (item in aSet) return item - } + //for (item in bSet) if (item in aSet) return item + //for (item in aSet) if (item in bSet) return item + + val aIt = aSet.iterator() + val bIt = bSet.iterator() + do { + var c = 0 + if (aIt.hasNext()) { + val item = aIt.next() + if (item in bSet) return item + c++ + } + if (bIt.hasNext()) { + val item = bIt.next() + if (item in aSet) return item + c++ + } + } while (c != 0) return null } } @@ -161,11 +176,12 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo ctx.loopEnds[exitNode] = loopName val optimizedWhile = isSingleNodeLoop && (node.dstEdgesButNext.size == 1) - val cond: AstExpr = if (optimizedWhile) { - node.dstEdgesButNext.first().cond!! - } else { - true.lit - } + //val cond: AstExpr = if (optimizedWhile) { + // node.dstEdgesButNext.first().cond!! + //} else { + // true.lit + //} + val cond = true.lit out += AstStm.DO_WHILE( @@ -173,18 +189,18 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo cond, if (isSingleNodeLoop) { trace { "$indent- render single node: renderNoLoops" } - if (optimizedWhile) { - node.body.stms - } else { + //if (optimizedWhile) { + // node.body.stms + //} else { val out2 = arrayListOf() renderNoLoops(g, out2, node, exitNode, ctx, level) out2.stmsWoNops - } + //} } else { trace { "$indent- render multi node: renderComponents" } renderComponents(component.split(entryNode, exitNode), entryNode, exitNode, ctx, level = level + 1) } - ) + ).optimizeDoWhile() ctx.loopEnds -= exitNode ctx.loopStarts -= entryNode @@ -320,6 +336,11 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo fun StrongComponent.getEntryPoints(): List { return this.getExternalInputsEdges().map { it.dst }.distinct() } + + fun AstStm.DO_WHILE.optimizeDoWhile(): AstStm.DO_WHILE { + this.body + return this + } } class RelooperException(message: String) : RuntimeException(message) \ No newline at end of file From 19f8ee1aad6222e22b28a0ac4b1cf05d5bb048bb Mon Sep 17 00:00:00 2001 From: soywiz Date: Mon, 15 Jan 2018 21:03:43 +0100 Subject: [PATCH 15/60] Some more work on relooper --- .../com/jtransc/ast/feature/method/GotosFeature.kt | 4 ++-- jtransc-core/src/com/jtransc/graph/Relooper.kt | 11 ++++++++++- .../src/relooper/RelooperTest.java | 4 ++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index 0f5e11cc..a784163d 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -25,8 +25,8 @@ import com.jtransc.graph.RelooperException // @TODO: Use AstBuilder to make it more readable class GotosFeature : AstMethodFeature() { override fun remove(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody { - //if (method.relooperEnabled ?: settings.relooper) { - if (method.relooperEnabled ?: false) { + if (method.relooperEnabled ?: settings.relooper) { + //if (method.relooperEnabled ?: false) { try { return removeRelooper(method, body, settings, types) ?: removeMachineState(body, types) } catch (t: Throwable) { diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 5617725a..6f8f32ae 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -147,7 +147,11 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val indent by lazy { INDENTS[level] } val out = arrayListOf() var node: Node? = entry + val explored = LinkedHashSet() loop@ while (node != null && node != exit) { + if (node in explored) invalidOp("Already explored") + explored += node + val prevNode = node ctx.rendered += node val component = g.findComponentWith(node) val isMultiNodeLoop = component.isMultiNodeLoop() @@ -211,6 +215,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo else { node = renderNoLoops(g, out, node, exit, ctx, level = level) } + + if (node == prevNode) invalidOp("Infinite loop detected") } return out.stmsWoNops } @@ -338,7 +344,10 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } fun AstStm.DO_WHILE.optimizeDoWhile(): AstStm.DO_WHILE { - this.body + val bodyValue = this.body.value + if (bodyValue is AstStm.STMS) { + + } return this } } diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index 119cccc1..b723ab79 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -21,7 +21,7 @@ static public void main(String[] args) { simpleDoWhile(0, 5); } - @JTranscRelooper(false) + @JTranscRelooper(true) static public int simpleIf(int a, int b) { if (a < b) { return -1; @@ -30,7 +30,7 @@ static public int simpleIf(int a, int b) { } } - @JTranscRelooper(false) + @JTranscRelooper(true) static public int composedIfAnd(int a, int b) { if (a < b && a >= 0) { return -1; From c7ed617f338b6a94b966ba46f1df82ef2c3e4e70 Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 06:47:06 +0100 Subject: [PATCH 16/60] Some more work on relooper --- .../src/com/jtransc/graph/Relooper.kt | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 6f8f32ae..c96d0f6a 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -7,6 +7,29 @@ import com.jtransc.text.INDENTS import java.util.* import kotlin.collections.LinkedHashSet +/** + * Converts a digraph representing the control flow graph of a method into ifs and whiles. + * If we fail doing so because the graph is irreductible or we have a bug, we will fallback to creating + * a state machine as we are already doing. + * + * Fors: + * - Separate the graph in Strong Components + * - Each strong component represents a loop (with potentially other loops inside) + * - Each strong component should have a single entry and a single exit (modulo breaking/continuing other loops) in a reductible graph + * - That entry/exit delimits the loop + * - Inside strong components, all edges should be internal, or external referencing the beginning/end of this or other loops. + * - Internal links to that component represents ifs, while external links represents, break or continue to specific loops + * - Each loop/strong component should be splitted into smaller strong components after removing links to the beginning of the loop to detect inner loops, each split is recursively handled. + * + * Ifs: + * - For each 'if' we have two forward edges. One to enter the if, and other to skip the if. So the conditional edge is the negation of the if. + * - To determine the end of the if, we have to compute the common successor of all the edges + * - If the common successor is the same as the if-skipping edge, we have a plain if, and if not, we have an if-else combination. + * - So we have three nodes that delimits if-elses: entering the condition, skipping the condition, and the common successor. + * + * Switch: + * - TODO (probably we can just create if chains and then generate a switch from it) + */ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boolean = false) { class Node(val types: AstTypes, val index: Int, val body: List) { //var next: Node? = null @@ -80,17 +103,19 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo fun getNodeSuccessorsLinkedSet(a: Node, exit: Node?, checkRendered: Boolean = false): Set { val visited = LinkedHashSet() if (!checkRendered) visited += rendered - if (exit != null) visited += exit val set = LinkedHashSet() val queue = Queue() queue.queue(a) while (queue.hasMore) { val item = queue.dequeue() - if (item in visited) continue - visited += item - set += item - for (edge in item.dstEdges) { - queue.queue(edge.dst) + if (item !in visited) { + set += item + visited += item + if (item != exit) { + for (edge in item.dstEdges) { + queue.queue(edge.dst) + } + } } } return set @@ -129,16 +154,6 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo fun AstStm.dump() = this.dump(AstTypes(com.jtransc.gen.TargetName("js"))) } - /** - * The process consists in: - * - Separate the graph in Strong Components - * - Each strong component represents a loop - * - Each strong component should have a single entry and a single exit (modulo breaking/continuing other loops) in a reductible graph - * - That entry/exit delimits the loop - * - Inside strong components, all links should be internal, or external referencing the beginning/end of this or other loops. - * - Internal links to that component represents ifs, while external links represents, break or continue to specific loops - * - Each loop/strong component should be splitted into smaller strong components after removing links to the beginning of the loop to detect inner loops - */ fun renderComponents(g: StrongComponentGraph, entry: Node, exit: Node? = null, ctx: RenderContext = RenderContext(g.graph), level: Int = 0): AstStm { if (level > 5) { //throw RelooperException("Too much nesting levels!") From 1f5e4492bc971ed595676082f988ad6049089f19 Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 07:03:22 +0100 Subject: [PATCH 17/60] Some more work on relooper --- .../src/com/jtransc/ast/feature/method/GotosFeature.kt | 7 ++++--- jtransc-core/src/com/jtransc/graph/Relooper.kt | 9 +++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index a784163d..28135e2d 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -25,6 +25,7 @@ import com.jtransc.graph.RelooperException // @TODO: Use AstBuilder to make it more readable class GotosFeature : AstMethodFeature() { override fun remove(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody { + //if (false) { if (method.relooperEnabled ?: settings.relooper) { //if (method.relooperEnabled ?: false) { try { @@ -178,9 +179,9 @@ class GotosFeature : AstMethodFeature() { stateStms = arrayListOf() } - fun simulateGotoLabel(index: Int) = listOf( + fun simulateGotoLabel(index: Int, insideCatch: Boolean = false) = listOf( gotostate.setTo(index.lit), - AstStm.CONTINUE("loop") + AstStm.CONTINUE(if (insideCatch) "tryLoop" else "loop") ) fun simulateGotoLabel(label: AstLabel) = simulateGotoLabel(getStateFromLabel(label)) @@ -256,7 +257,7 @@ class GotosFeature : AstMethodFeature() { AstStm.IF( //(gotostate ge AstExpr.LITERAL(startState)) band (gotostate le AstExpr.LITERAL(endState)) band (AstExpr.CAUGHT_EXCEPTION() instanceof trap.exception), (gotostate ge startState.lit) band (gotostate lt endState.lit) band (AstExpr.CAUGHT_EXCEPTION() instanceof trap.exception), - simulateGotoLabel(handlerState).stm() + simulateGotoLabel(handlerState, insideCatch = true).stm() ) } diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index c96d0f6a..235f9dd4 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -29,6 +29,15 @@ import kotlin.collections.LinkedHashSet * * Switch: * - TODO (probably we can just create if chains and then generate a switch from it) + * + * Try-Catch: + * - TODO + * + * Irreductible CFGs: + * - TODO + * - Ideas: Since we need the strong components to have a single entry and a single exit (modulo continuing/exiting other loops), + * we can try to create synthetic edges to enter each strong component, from a single entry point and then skip some parts. + * That should work with custom gotos or await/async implementations. */ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boolean = false) { class Node(val types: AstTypes, val index: Int, val body: List) { From 829393dd5e683e223ca70a883388cbe7f123b2a5 Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 07:19:52 +0100 Subject: [PATCH 18/60] Remove old annotations --- .../src/com/jtransc/annotation/KeepConstructors.java | 11 ----------- .../src/com/jtransc/annotation/KeepFields.java | 11 ----------- .../src/com/jtransc/annotation/KeepMethods.java | 11 ----------- 3 files changed, 33 deletions(-) delete mode 100644 jtransc-annotations/src/com/jtransc/annotation/KeepConstructors.java delete mode 100644 jtransc-annotations/src/com/jtransc/annotation/KeepFields.java delete mode 100644 jtransc-annotations/src/com/jtransc/annotation/KeepMethods.java diff --git a/jtransc-annotations/src/com/jtransc/annotation/KeepConstructors.java b/jtransc-annotations/src/com/jtransc/annotation/KeepConstructors.java deleted file mode 100644 index 2140b5c9..00000000 --- a/jtransc-annotations/src/com/jtransc/annotation/KeepConstructors.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.jtransc.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.CLASS) -@Target({ElementType.TYPE}) -public @interface KeepConstructors { -} diff --git a/jtransc-annotations/src/com/jtransc/annotation/KeepFields.java b/jtransc-annotations/src/com/jtransc/annotation/KeepFields.java deleted file mode 100644 index 091008b5..00000000 --- a/jtransc-annotations/src/com/jtransc/annotation/KeepFields.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.jtransc.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.CLASS) -@Target({ElementType.TYPE}) -public @interface KeepFields { -} diff --git a/jtransc-annotations/src/com/jtransc/annotation/KeepMethods.java b/jtransc-annotations/src/com/jtransc/annotation/KeepMethods.java deleted file mode 100644 index 7a77a4dd..00000000 --- a/jtransc-annotations/src/com/jtransc/annotation/KeepMethods.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.jtransc.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.CLASS) -@Target({ElementType.TYPE}) -public @interface KeepMethods { -} From 887797bbe1891e054782b11d8aa9a51db07bcec1 Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 07:20:17 +0100 Subject: [PATCH 19/60] Output jtransc version with JS for convenience --- jtransc-rt/resources/js/Base.js | 1 + 1 file changed, 1 insertion(+) diff --git a/jtransc-rt/resources/js/Base.js b/jtransc-rt/resources/js/Base.js index e3b6e367..3df68956 100644 --- a/jtransc-rt/resources/js/Base.js +++ b/jtransc-rt/resources/js/Base.js @@ -1,3 +1,4 @@ +// Generated by JTransc {{ JTRANSC_VERSION }} var _global = (typeof window !== "undefined") ? window : global; var DEBUG_VERSION = {{ debug != false }}; From ca3697fee538dd56d279354adab5b66521b16e92 Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 07:20:36 +0100 Subject: [PATCH 20/60] Old comment --- jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index 28135e2d..338b5783 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -140,7 +140,6 @@ class GotosFeature : AstMethodFeature() { fun removeMachineState(body: AstBody, types: AstTypes): AstBody { // @TODO: this should create simple blocks and do analysis like that, instead of creating a gigantic switch - // @TODO: trying to generate whiles, ifs and so on to allow javascript be fast. See relooper paper. var stm = body.stm //val locals = body.locals.toCollection(arrayListOf()) val traps = body.traps.toCollection(arrayListOf()) From 0f6ece9930af772da4e57ddb20555899712bc21d Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 07:21:07 +0100 Subject: [PATCH 21/60] Add a flag for generators to specify if they support labels (most of them do) --- .../com/jtransc/gen/common/CommonGenerator.kt | 41 ++++++++++++++----- .../src/com/jtransc/gen/haxe/HaxeTarget.kt | 1 + 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt b/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt index 877a93ee..890bdd83 100644 --- a/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt +++ b/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt @@ -59,6 +59,7 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { open val floatHasFSuffix = true open val casesWithCommas = false open val optionalDoubleDummyDecimals = false + open val supportsLabels = true open val GENERATE_LINE_NUMBERS = true @@ -905,8 +906,22 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { open fun actualSetField(stm: AstStm.SET_FIELD_INSTANCE, left: String, right: String): String = "$left = $right;" - open fun genStmContinue(stm: AstStm.CONTINUE) = Indenter("continue ${stm.name};") - open fun genStmBreak(stm: AstStm.BREAK) = Indenter("break ${stm.name};") + open fun genStmContinue(stm: AstStm.CONTINUE): Indenter { + if (supportsLabels) { + return Indenter("continue ${stm.name};") + } else { + // @TODO: We have to use a variable do tell how many loops we want to skip + return Indenter("continue;") + } + } + open fun genStmBreak(stm: AstStm.BREAK):Indenter { + if (supportsLabels) { + return Indenter("break ${stm.name};") + } else { + // @TODO: We have to use a variable do tell how many loops we want to skip + return Indenter("break;") + } + } open fun genStmLabel(stm: AstStm.STM_LABEL): Indenter = Indenter { if (stm.label in trapsByEnd) { for (trap in trapsByEnd[stm.label]!!) line(genStmRawCatch(trap)) @@ -1049,17 +1064,17 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { private fun SHIFT_FIX_32(r: Int): Int { if (r < 0) { - return (32 - ((-r) and 0x1F)) and 0x1F; + return (32 - ((-r) and 0x1F)) and 0x1F } else { - return r and 0x1F; + return r and 0x1F } } private fun SHIFT_FIX_64(r: Int): Int { if (r < 0) { - return (64 - ((-r) and 0x3F)) and 0x3F; + return (64 - ((-r) and 0x3F)) and 0x3F } else { - return r and 0x3F; + return r and 0x3F } } @@ -1345,22 +1360,28 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { open fun genStmReturnVoid(stm: AstStm.RETURN_VOID, last: Boolean) = Indenter(if (context.method.methodVoidReturnThis) "return " + genExprThis(AstExpr.THIS("Dummy".fqname)) + ";" else "return;") open fun genStmReturnValue(stm: AstStm.RETURN, last: Boolean) = Indenter("return ${stm.retval.genExpr()};") fun genStmWhile(stm: AstStm.WHILE) = indent { - val label = "${stm.name}:" + val label = if (supportsLabels) "${stm.name}: " else "" flowBlock(FlowKind.WHILE, stm.name) { - line("$label while (${stm.cond.genExpr()})") { + line("${label}while (${stm.cond.genExpr()})") { line(stm.iter.genStm()) } } + if (!supportsLabels) { + // @TODO: We have to put an if here checking a variable to tell how many loops we have to break and whether to break or to continue the last one + } } fun genStmDoWhile(stm: AstStm.DO_WHILE) = indent { - val label = "${stm.name}:" + val label = if (supportsLabels) "${stm.name}: " else "" flowBlock(FlowKind.WHILE, stm.name) { - line("$label do", after2 = " while (${stm.cond.genExpr()});") { + line("${label}do", after2 = " while (${stm.cond.genExpr()});") { line(stm.body.genStm()) } } + if (!supportsLabels) { + // @TODO: We have to put an if here checking a variable to tell how many loops we have to break and whether to break or to continue the last one + } } open fun genStmIf(stm: AstStm.IF) = indent { diff --git a/jtransc-gen-haxe/src/com/jtransc/gen/haxe/HaxeTarget.kt b/jtransc-gen-haxe/src/com/jtransc/gen/haxe/HaxeTarget.kt index 6ff9d68c..fbbe3b0b 100644 --- a/jtransc-gen-haxe/src/com/jtransc/gen/haxe/HaxeTarget.kt +++ b/jtransc-gen-haxe/src/com/jtransc/gen/haxe/HaxeTarget.kt @@ -105,6 +105,7 @@ class HaxeGenerator(injector: Injector) : CommonGenerator(injector) { val MAX_SWITCH_SIZE = 10 override val floatHasFSuffix: Boolean = false override val casesWithCommas = true + override val supportsLabels = false val usingGotoHack = ENABLE_HXCPP_GOTO_HACK && (subtarget in setOf("cpp", "windows", "linux", "mac", "android")) From 61d9b81c67b4151b8b791de30040c92da367a9c8 Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 08:02:38 +0100 Subject: [PATCH 22/60] Optimize DO_WHILE + if continue + break externally --- .../src/com/jtransc/graph/Relooper.kt | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 235f9dd4..e3570915 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -203,12 +203,6 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo ctx.loopStarts[entryNode] = loopName ctx.loopEnds[exitNode] = loopName - val optimizedWhile = isSingleNodeLoop && (node.dstEdgesButNext.size == 1) - //val cond: AstExpr = if (optimizedWhile) { - // node.dstEdgesButNext.first().cond!! - //} else { - // true.lit - //} val cond = true.lit @@ -217,13 +211,9 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo cond, if (isSingleNodeLoop) { trace { "$indent- render single node: renderNoLoops" } - //if (optimizedWhile) { - // node.body.stms - //} else { - val out2 = arrayListOf() - renderNoLoops(g, out2, node, exitNode, ctx, level) - out2.stmsWoNops - //} + val out2 = arrayListOf() + renderNoLoops(g, out2, node, exitNode, ctx, level) + out2.stmsWoNops } else { trace { "$indent- render multi node: renderComponents" } renderComponents(component.split(entryNode, exitNode), entryNode, exitNode, ctx, level = level + 1) @@ -370,9 +360,19 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo fun AstStm.DO_WHILE.optimizeDoWhile(): AstStm.DO_WHILE { val bodyValue = this.body.value if (bodyValue is AstStm.STMS) { - + val stms = bodyValue.stms + if (stms.size >= 2) { + val last = stms[stms.size - 1].value + val plast = stms[stms.size - 2].value + if (last is AstStm.BREAK && plast is AstStm.IF && plast.strue.value is AstStm.CONTINUE) { + return AstStm.DO_WHILE(name, plast.cond.value, stms.map { it.value }.slice(0 until stms.size - 2).stms.box.value) + } + } } return this + // var n = 0; do { if (n++ < 10) continue; } while (false); console.log(n); // NOT WORKING: 1 + // var n = 0; do { if (n++ < 10) continue; break; } while (true); console.log(n); // WORKING: 11 + // var n = 0; do { } while (n++ < 10); console.log(n); // WORKING: 11 } } From f384cd049fe3a72ba3ba1cc01cbaddb5f7f56342 Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 08:18:45 +0100 Subject: [PATCH 23/60] Some more work on relooper --- jtransc-core/src/com/jtransc/ast/ast_dump.kt | 2 +- .../src/relooper/RelooperTest.java | 24 +++++++++++++++---- .../test/com/jtransc/gen/js/JsTest.kt | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/ast_dump.kt b/jtransc-core/src/com/jtransc/ast/ast_dump.kt index ceb7d8c6..0abd908b 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_dump.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_dump.kt @@ -70,7 +70,7 @@ fun dump(types: AstTypes, stm: AstStm?): Indenter { is AstStm.CONTINUE -> line("continue ${stm.name};") is AstStm.BREAK -> line("break ${stm.name};") is AstStm.THROW -> line("throw ${dump(types, stm.exception)};") - is AstStm.IF -> line("if (${dump(types, stm.cond)})") { line(dump(types, stm.strue)) } + is AstStm.IF -> line("if (${dump(types, stm.cond)})") { line(dumpCollapse(types, stm.strue)) } is AstStm.IF_ELSE -> { line("if (${dump(types, stm.cond)})") { line(dumpCollapse(types, stm.strue)) } line("else") { line(dumpCollapse(types, stm.sfalse)) } diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index b723ab79..77bdf4e9 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -6,6 +6,7 @@ public class RelooperTest { static public void main(String[] args) { JTranscConsole.log("RelooperTest:"); + JTranscConsole.log(simpleIf(0, 1)); JTranscConsole.log(simpleIf(1, 0)); JTranscConsole.log(simpleIf(0, 0)); @@ -19,9 +20,10 @@ static public void main(String[] args) { JTranscConsole.log(composedIfOr(0, 0)); simpleDoWhile(0, 5); + simpleWhile(0, 5); } - @JTranscRelooper(true) + @JTranscRelooper static public int simpleIf(int a, int b) { if (a < b) { return -1; @@ -30,7 +32,7 @@ static public int simpleIf(int a, int b) { } } - @JTranscRelooper(true) + @JTranscRelooper static public int composedIfAnd(int a, int b) { if (a < b && a >= 0) { return -1; @@ -39,7 +41,7 @@ static public int composedIfAnd(int a, int b) { } } - @JTranscRelooper(value = true, debug = false) + @JTranscRelooper static public int composedIfOr(int a, int b) { if (a < b || a >= 0) { return -1; @@ -48,7 +50,7 @@ static public int composedIfOr(int a, int b) { } } - @JTranscRelooper(value = true, debug = true) + @JTranscRelooper static public int simpleDoWhile(int a, int b) { b++; @@ -64,4 +66,18 @@ static public int simpleDoWhile(int a, int b) { return b; } + + @JTranscRelooper(debug = true) + static public int simpleWhile(int a, int b) { + b++; + + while (a < b) { + JTranscConsole.log(a); + a++; + } + JTranscConsole.log(a); + JTranscConsole.log(b); + + return b; + } } diff --git a/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt b/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt index 0d2c7e8b..1f627a63 100644 --- a/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt +++ b/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt @@ -70,7 +70,7 @@ class JsTest : _Base() { @Test fun testSideEffects() = testClass(Params(clazz = SideEffectsTest::class.java, minimize = false, log = false, treeShaking = true, debug = true)) - @Test fun testRelooper() = testClass(Params(clazz = RelooperTest::class.java, minimize = false, log = false, treeShaking = true, debug = true)) + @Test fun testRelooperTest() = testClass(Params(clazz = RelooperTest::class.java, minimize = false, log = false, treeShaking = true, debug = true)) @Test fun testBig() = testClass(Params(clazz = BigTest::class.java, minimize = false, log = false)) @Test fun testBigMin() = testClass(Params(clazz = BigTest::class.java, minimize = true, log = false)) From 91b2168defaeaa2a65d6b48fc03361677a3e38b1 Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 09:30:21 +0100 Subject: [PATCH 24/60] Supported white and for loops --- .../src/com/jtransc/graph/Relooper.kt | 9 +++-- .../test/com/jtransc/graph/RelooperTest.kt | 35 +++++++++++++++++++ .../src/com/jtransc/gen/common/_Base.kt | 3 +- .../src/relooper/RelooperTest.java | 10 ++++++ .../test/com/jtransc/gen/js/JsTest.kt | 3 +- 5 files changed, 55 insertions(+), 5 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index e3570915..52347162 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -173,7 +173,10 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo var node: Node? = entry val explored = LinkedHashSet() loop@ while (node != null && node != exit) { - if (node in explored) invalidOp("Already explored") + if (node in explored) { + //invalidOp("Already explored : $node") + break + } explored += node val prevNode = node ctx.rendered += node @@ -215,7 +218,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo renderNoLoops(g, out2, node, exitNode, ctx, level) out2.stmsWoNops } else { - trace { "$indent- render multi node: renderComponents" } + trace { "$indent- render multi node: renderComponents (${entryNode.index} - ${exitNode.index})" } renderComponents(component.split(entryNode, exitNode), entryNode, exitNode, ctx, level = level + 1) } ).optimizeDoWhile() @@ -298,7 +301,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo // IF if (common == endOfIfNode) { out += AstStm.IF( - endOfIf.cond!!.not(), + endOfIf.cond!!.not(), // @TODO: Negate a float comparison problem with NaNs renderComponents(g, ifBody, endOfIfNode, ctx, level = level + 1) ) } diff --git a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt index dee7ae01..28f11b6f 100644 --- a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt +++ b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt @@ -254,6 +254,41 @@ class RelooperTest { //} } + @Test + fun testSimpleWhile() = relooperTest { + val L0 = node("L0") + val L1 = node("L1") + val L2 = node("L2") + val L3 = node("L3") + val L6 = node("L6") + + L0.edgeTo(L1) + L1.edgeTo(L2).edgeTo(L3, "l2_l3") + L2.edgeTo(L1) + L3.edgeTo(L6) + + L0.assertDump(""" + - + """) + + // * L0: { lI0 = p0; lI1 = p1; lI1 = (lI1 + 1); } EDGES: [goto L1;]. SRC_EDGES: 0 + // * L1: EDGES: [goto L2;, IF ((lI0 >= lI1)) goto L3;]. SRC_EDGES: 2 + // * L2: { com.jtransc.io.JTranscConsole.log(lI0); lI0 = (lI0 + 1); } EDGES: [goto L1;]. SRC_EDGES: 1 + // * L3: { com.jtransc.io.JTranscConsole.log(lI0); com.jtransc.io.JTranscConsole.log(lI1); return lI1; } EDGES: [goto L6;]. SRC_EDGES: 2 + // * L6: L6: NOP(empty stm) EDGES: []. SRC_EDGES: 1 + //@JTranscRelooper(debug = true) + //static public int simpleWhile(int a, int b) { + // b++; + // while (a < b) { + // JTranscConsole.log(a); + // a++; + // } + // JTranscConsole.log(a); + // JTranscConsole.log(b); + // return b; + //} + } + fun Relooper.Node.assertDump(msg: String) { assertEquals(msg.normalizeMulti(), relooper.renderStr(this)) } diff --git a/jtransc-gen-common-tests/src/com/jtransc/gen/common/_Base.kt b/jtransc-gen-common-tests/src/com/jtransc/gen/common/_Base.kt index 3eb4838e..e356c536 100644 --- a/jtransc-gen-common-tests/src/com/jtransc/gen/common/_Base.kt +++ b/jtransc-gen-common-tests/src/com/jtransc/gen/common/_Base.kt @@ -111,6 +111,7 @@ open class _Base { val optimize: Boolean? = null, val treeShaking: Boolean? = null, val backend: BuildBackend? = null, + val relooper: Boolean? = null, val configureInjector: Injector.() -> Unit = {}, val target: GenTargetDescriptor? = null, val log: Boolean? = null, @@ -203,7 +204,7 @@ open class _Base { jtranscVersion = JTranscVersion.getVersion(), debug = params.debug ?: DEBUG, optimize = params.optimize ?: OPTIMIZE, - relooper = RELOOPER, + relooper = params.relooper ?: RELOOPER, extra = params.extra ?: mapOf(), analyzer = params.analyze ?: ANALYZER, rtAndRtCore = rtAndCoreFiltered diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index 77bdf4e9..43d51773 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -21,6 +21,7 @@ static public void main(String[] args) { simpleDoWhile(0, 5); simpleWhile(0, 5); + simpleFor(2, 5); } @JTranscRelooper @@ -80,4 +81,13 @@ static public int simpleWhile(int a, int b) { return b; } + + @JTranscRelooper(debug = true) + static public int simpleFor(int a, int b) { + for (int n = 1; n < b; n++) { + JTranscConsole.log(a + n); + } + return b; + } + } diff --git a/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt b/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt index 1f627a63..2b94e5ec 100644 --- a/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt +++ b/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt @@ -70,7 +70,8 @@ class JsTest : _Base() { @Test fun testSideEffects() = testClass(Params(clazz = SideEffectsTest::class.java, minimize = false, log = false, treeShaking = true, debug = true)) - @Test fun testRelooperTest() = testClass(Params(clazz = RelooperTest::class.java, minimize = false, log = false, treeShaking = true, debug = true)) + //@Test fun testRelooperTest() = testClass(Params(clazz = RelooperTest::class.java, minimize = false, log = false, treeShaking = true, debug = true, relooper = false)) + @Test fun testRelooperTest() = testClass(Params(clazz = RelooperTest::class.java, minimize = false, log = false, treeShaking = true, debug = true, relooper = true)) @Test fun testBig() = testClass(Params(clazz = BigTest::class.java, minimize = false, log = false)) @Test fun testBigMin() = testClass(Params(clazz = BigTest::class.java, minimize = true, log = false)) From f1faab9e52b20bac0319e5fc447d216c92621067 Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 09:59:24 +0100 Subject: [PATCH 25/60] Enable relooper in benchmark --- benchmark/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/benchmark/build.gradle b/benchmark/build.gradle index 0cc4b3ec..578d7baf 100644 --- a/benchmark/build.gradle +++ b/benchmark/build.gradle @@ -33,6 +33,7 @@ jtransc { //minimizeNames = true minimizeNames = false treeshaking = true + relooper = true // Call ./gradlew -Pjs_enable_async=true runJs if (rootProject.findProperty("js_enable_async")?.toString()?.toBoolean() ?: false) { From ce878e2bf66a053bd6bea81f310a3875d371707c Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 10:12:49 +0100 Subject: [PATCH 26/60] This PR will start 0.7 branch --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 186c891a..02b930a6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -jtranscVersion=0.6.9-SNAPSHOT +jtranscVersion=0.7.0-SNAPSHOT kotlinVersion=1.2.10 file.encoding=UTF-8 kotlin.incremental=true From 84a323c76cf3e44d5041d1e156f13962656966c7 Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 10:13:10 +0100 Subject: [PATCH 27/60] Inline errors --- jtransc-utils/src/com/jtransc/error/errors.kt | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/jtransc-utils/src/com/jtransc/error/errors.kt b/jtransc-utils/src/com/jtransc/error/errors.kt index f9956b4c..69fad680 100644 --- a/jtransc-utils/src/com/jtransc/error/errors.kt +++ b/jtransc-utils/src/com/jtransc/error/errors.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.jtransc.error class InvalidOperationException(str: String = "Invalid Operation", cause: Throwable? = null) : Exception(str, cause) @@ -26,24 +28,22 @@ class MustOverrideException(str: String = "Must Override") : Exception(str) class DeprecatedException(str: String = "Deprecated") : Exception(str) class UnexpectedException(str: String = "Unexpected") : Exception(str) -val deprecated: Nothing get() = throw MustValidateCodeException() -val mustValidate: Nothing get() = throw NotImplementedException() -val noImpl: Nothing get() = throw NotImplementedException() -val invalidOp: Nothing get() = throw InvalidOperationException() +inline val deprecated: Nothing get() = throw MustValidateCodeException() +inline val mustValidate: Nothing get() = throw NotImplementedException() +inline val noImpl: Nothing get() = throw NotImplementedException() +inline val invalidOp: Nothing get() = throw InvalidOperationException() -fun deprecated(msg:String): Nothing { throw DeprecatedException(msg) } -fun mustValidate(msg:String): Nothing { throw MustValidateCodeException(msg) } -fun noImpl(msg:String): Nothing { throw NotImplementedException(msg) } -fun invalidOp(msg:String, cause: Throwable? = null): Nothing { - throw InvalidOperationException(msg, cause) -} -fun unsupported(msg:String): Nothing { throw UnsupportedOperationException(msg) } -fun invalidArgument(msg:String): Nothing { throw InvalidArgumentException(msg) } -fun unexpected(msg:String): Nothing { throw UnexpectedException(msg) } +inline fun deprecated(msg:String): Nothing = throw DeprecatedException(msg) +inline fun mustValidate(msg:String): Nothing = throw MustValidateCodeException(msg) +inline fun noImpl(msg:String): Nothing = throw NotImplementedException(msg) +inline fun invalidOp(msg:String, cause: Throwable? = null): Nothing = throw InvalidOperationException(msg, cause) +inline fun unsupported(msg:String): Nothing = throw UnsupportedOperationException(msg) +inline fun invalidArgument(msg:String): Nothing = throw InvalidArgumentException(msg) +inline fun unexpected(msg:String): Nothing = throw UnexpectedException(msg) // Warns -fun untestedWarn(msg:String): Unit { println("Untested: $msg") } -fun noImplWarn(msg:String): Unit { println("Not implemented: $msg") } +inline fun untestedWarn(msg:String): Unit { println("Untested: $msg") } +inline fun noImplWarn(msg:String): Unit { println("Not implemented: $msg") } inline fun ignoreErrors(action: () -> Unit) { try { From 009e53d768d65c6e15b8ce35acc4879b01caa497 Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 10:35:10 +0100 Subject: [PATCH 28/60] Added failing test case using relooper + test code generator when debugging --- jtransc-core/src/com/jtransc/ast/ast_dump.kt | 1 + .../src/com/jtransc/graph/Relooper.kt | 37 ++++++++++++--- .../test/com/jtransc/graph/RelooperTest.kt | 46 ++++++++++++++++++- .../src/relooper/RelooperTest.java | 23 +++++++++- .../test/com/jtransc/gen/js/JsTest.kt | 4 +- 5 files changed, 100 insertions(+), 11 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/ast_dump.kt b/jtransc-core/src/com/jtransc/ast/ast_dump.kt index 0abd908b..6f9d70d4 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_dump.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_dump.kt @@ -111,6 +111,7 @@ fun dump(types: AstTypes, expr: AstExpr.Box?): String { fun AstExpr?.exprDump(types: AstTypes) = dump(types, this) fun List.dump(types: AstTypes) = dump(types, this.stm()) +fun List.dumpCollapse(types: AstTypes) = dumpCollapse(types, this.stms.box) fun AstStm.dump(types: AstTypes) = dump(types, this) fun AstStm.dumpCollapse(types: AstTypes) = dumpCollapse(types, this.box) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 52347162..1b465dfa 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -91,13 +91,38 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo fun render(entry: Node): AstStm { val g = graphList(prepare(entry).map { it to it.possibleNextNodes }) - trace { "Rendering $name" } - for (n in g.nodes) { - trace { "* $n" } + if (debug) { + trace { "Rendering $name" } + for (n in g.nodes) { + trace { "* $n" } + } + trace { "// STRUCTURE CODE FOR TESTS:" } + for (n in g.nodes) { + val line = "val L${n.index} = node(\"L${n.index}\")" + val bodyStr = n.body.filter { it !is AstStm.NOP }.dumpCollapse(types).toString(false).replace('\n', ' ').trim() + trace { if (bodyStr.isNotEmpty()) "$line // $bodyStr" else line } + } + for (n in g.nodes) { + if (n.dstEdges.size != 0) { + var out = "L${n.index}" + val conds = arrayListOf() + if (n.next != null) { + out += ".edgeTo(L${n.next!!.index})" + } + for (e in n.dstEdgesButNext) { + out += ".edgeTo(L${e.dst.index}, \"l${e.src.index}_l${e.dst.index}\")" + conds += e.cond?.dump(types) ?: "" + } + trace { + if (conds.isNotEmpty()) "$out // $conds" else out + } + } + } + trace { "// /STRUCTURE CODE FOR TESTS" } } - println("Relooping '$name'...") + //println("Relooping '$name'...") val result = renderComponents(g.tarjanStronglyConnectedComponentsAlgorithm(), entry) - println("Relooping '$name'...OK") + //println("Relooping '$name'...OK") return result } @@ -192,7 +217,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo //val outsNotInContext = outs.filter { it !in ctx.loopStarts } if (outsNotInContext.size != 1) { trace { "$indent- ASSERTION FAILED! outsNotInContext.size != 1 (${outsNotInContext.size}) : $node" } - invalidOp("ERROR When Relooping $name (ASSERTION FAILED)") + invalidOp("ERROR When Relooping '$name' ASSERTION FAILED :: ASSERTION FAILED! outsNotInContext.size != 1 (${outsNotInContext.size}) : $node") } val entryNode = node diff --git a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt index 28f11b6f..77e2921b 100644 --- a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt +++ b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt @@ -8,7 +8,7 @@ import org.junit.Test class RelooperTest { val types = AstTypes(TargetName("js")) - val relooper = Relooper(types, debug = true) + val relooper = Relooper(types, name = "test", debug = true) @Test fun testIf() = relooperTest { @@ -289,6 +289,49 @@ class RelooperTest { //} } + @Test + fun testMixed1() = relooperTest { + val L0 = node("L0") // lA3 = new java.util.ArrayList(); lI4 = 0; lI5 = 0; + val L1 = node("L1") + val L2 = node("L2") + val L3 = node("L3") + val L4 = node("L4") // lA3.add(((java.lang.Object)p0.substring(lI5, lI4))); lI5 = (lI4 + 1); + val L5 = node("L5") // lI4 = (lI4 + 1); + val L6 = node("L6") // NOP(empty stm) + val L9 = node("L9") // lA3.add(((java.lang.Object)p0.substring(lI5))); + val L10 = node("L10") // return ((java.lang.String[])lA3.toArray(((java.lang.Object[])new java.lang.String[lA3.size()]))); + val L12 = node("L12") // NOP(empty stm) + L0.edgeTo(L1) + L1.edgeTo(L2).edgeTo(L3, "l1_l3") // [(lI4 >= p0.length())] + L2.edgeTo(L4).edgeTo(L5, "l2_l5") // [(p0.charAt(lI4) != ((int)p1))] + L3.edgeTo(L9).edgeTo(L10, "l3_l10") // [(lI5 >= p0.length())] + L4.edgeTo(L6).edgeTo(L5, "l4_l5") // [(lA3.size() < (p2 - 1))] + L5.edgeTo(L1) + L6.edgeTo(L3) + L9.edgeTo(L10) + L10.edgeTo(L12) + + L0.assertDump(""" + - + """) + + // @JTranscRelooper(debug = true) + // static private String[] split(String str, char ch, int limit) { + // ArrayList out = new ArrayList(); + // int n = 0; + // int start = 0; + // for (; n < str.length(); n++) { + // if (str.charAt(n) == ch) { + // out.add(str.substring(start, n)); + // start = n + 1; + // if (out.size() >= limit - 1) break; + // } + // } + // if (start < str.length()) out.add(str.substring(start)); + // return out.toArray(new String[out.size()]); + // } + } + fun Relooper.Node.assertDump(msg: String) { assertEquals(msg.normalizeMulti(), relooper.renderStr(this)) } @@ -301,6 +344,7 @@ class RelooperTest { fun Indenter.normalizeMulti() = this.toString().normalizeMulti() fun Relooper.renderStr(node: Relooper.Node) = render(node).dumpCollapse(types).normalizeMulti() fun Relooper.node(name: String) = node(stmt(name)) + fun Relooper.node(name: String, content: String) = node(stmt(name)) private fun stmt(name: String): AstStm = AstType.INT.local(name).setTo(1.lit) private fun cond(name: String) = AstExpr.RAW(AstType.BOOL, name) } \ No newline at end of file diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index 43d51773..6c7a948e 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -3,6 +3,9 @@ import com.jtransc.annotation.JTranscRelooper; import com.jtransc.io.JTranscConsole; +import java.util.ArrayList; +import java.util.Arrays; + public class RelooperTest { static public void main(String[] args) { JTranscConsole.log("RelooperTest:"); @@ -22,6 +25,7 @@ static public void main(String[] args) { simpleDoWhile(0, 5); simpleWhile(0, 5); simpleFor(2, 5); + JTranscConsole.log(Arrays.asList(split("hello world test", ' ', 2))); } @JTranscRelooper @@ -68,7 +72,7 @@ static public int simpleDoWhile(int a, int b) { return b; } - @JTranscRelooper(debug = true) + @JTranscRelooper static public int simpleWhile(int a, int b) { b++; @@ -82,7 +86,7 @@ static public int simpleWhile(int a, int b) { return b; } - @JTranscRelooper(debug = true) + @JTranscRelooper static public int simpleFor(int a, int b) { for (int n = 1; n < b; n++) { JTranscConsole.log(a + n); @@ -90,4 +94,19 @@ static public int simpleFor(int a, int b) { return b; } + @JTranscRelooper(debug = true) + static private String[] split(String str, char ch, int limit) { + ArrayList out = new ArrayList(); + int n = 0; + int start = 0; + for (; n < str.length(); n++) { + if (str.charAt(n) == ch) { + out.add(str.substring(start, n)); + start = n + 1; + if (out.size() >= limit - 1) break; + } + } + if (start < str.length()) out.add(str.substring(start)); + return out.toArray(new String[out.size()]); + } } diff --git a/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt b/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt index 2b94e5ec..9f7a1071 100644 --- a/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt +++ b/jtransc-gen-js/test/com/jtransc/gen/js/JsTest.kt @@ -70,8 +70,8 @@ class JsTest : _Base() { @Test fun testSideEffects() = testClass(Params(clazz = SideEffectsTest::class.java, minimize = false, log = false, treeShaking = true, debug = true)) - //@Test fun testRelooperTest() = testClass(Params(clazz = RelooperTest::class.java, minimize = false, log = false, treeShaking = true, debug = true, relooper = false)) - @Test fun testRelooperTest() = testClass(Params(clazz = RelooperTest::class.java, minimize = false, log = false, treeShaking = true, debug = true, relooper = true)) + @Test fun testRelooperTest() = testClass(Params(clazz = RelooperTest::class.java, minimize = false, log = false, treeShaking = true, debug = true, relooper = false)) + //@Test fun testRelooperTest() = testClass(Params(clazz = RelooperTest::class.java, minimize = false, log = false, treeShaking = true, debug = true, relooper = true)) @Test fun testBig() = testClass(Params(clazz = BigTest::class.java, minimize = false, log = false)) @Test fun testBigMin() = testClass(Params(clazz = BigTest::class.java, minimize = true, log = false)) From dd4dd2ed905c956dc7fe9cb819ff5abc5e8fd79c Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 11:26:48 +0100 Subject: [PATCH 29/60] Remove empty nodes, that were making the strong component contract (to just one exit) to fail --- jtransc-core/src/com/jtransc/ast/ast_dump.kt | 1 + .../src/com/jtransc/graph/Relooper.kt | 97 +++++++++++++++---- .../test/com/jtransc/graph/RelooperTest.kt | 53 +++++----- 3 files changed, 110 insertions(+), 41 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/ast_dump.kt b/jtransc-core/src/com/jtransc/ast/ast_dump.kt index 6f9d70d4..1bd2e575 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_dump.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_dump.kt @@ -26,6 +26,7 @@ fun dumpCollapse(types: AstTypes, stm: AstStm.Box?): Indenter { if (s is AstStm.STMS) { return Indenter { for (ss in s.stmsUnboxed) { + if (ss is AstStm.NOP) continue line(dumpCollapse(types, ss.box)) } } diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 1b465dfa..620131e0 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -4,6 +4,7 @@ import com.jtransc.ast.* import com.jtransc.ds.Queue import com.jtransc.error.invalidOp import com.jtransc.text.INDENTS +import com.jtransc.text.quote import java.util.* import kotlin.collections.LinkedHashSet @@ -50,39 +51,72 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val next get() = dstEdges.firstOrNull { it.cond == null }?.dst val nextEdge get() = dstEdges.firstOrNull { it.cond == null } + fun edgeTo(dst: Node, cond: AstExpr? = null) { + val src = this + val edge = Edge(types, src, dst, cond) + src.dstEdges += edge + dst.srcEdges += edge + } + override fun toString(): String = "L$index: " + dump(types, body.stm()).toString().replace('\n', ' ').trim() + " EDGES: $dstEdges. SRC_EDGES: ${srcEdges.size}" } class Edge(val types: AstTypes, val src: Node, val dst: Node, val cond: AstExpr? = null) { //override fun toString(): String = "IF (${cond.dump(types)}) goto L${dst.index}; else goto L${current.next?.index};" + + fun remove() { + src.dstEdges -= this + dst.srcEdges -= this + } + override fun toString(): String = if (cond != null) "IF (${cond.dump(types)}) goto L${dst.index};" else "goto L${dst.index};" } var lastIndex = 0 - fun node(body: List): Node = Node(types, lastIndex++, body) - fun node(body: AstStm): Node = Node(types, lastIndex++, listOf(body)) + fun node(body: List): Node = Node(types, lastIndex, body.normalize(lastIndex)).apply { lastIndex++ } + fun node(body: AstStm): Node = Node(types, lastIndex, listOf(body).normalize(lastIndex)).apply { lastIndex++ } - fun edge(a: Node, b: Node, cond: AstExpr? = null) { - a.dstEdges += Edge(types, a, b, cond) - b.srcEdges += Edge(types, a, b, cond) + fun List.normalize(index: Int): List { + val out = arrayListOf() + //if (debug && index == 6) println("test") + for (stm in this) { + when (stm) { + is AstStm.STMS -> out += stm.stmsUnboxed.normalize(index) + is AstStm.NOP -> Unit + else -> out += stm + } + } + return out } + fun edge(a: Node, b: Node, cond: AstExpr? = null) = a.edgeTo(b, cond) + private fun prepare(entry: Node): List { val exit = node(listOf()) val explored = LinkedHashSet() + val result = LinkedHashSet() fun explore(node: Node) { if (node in explored) return explored += node - if (node.next != null) { - explore(node.next!!) + + // Remove empty node + if (node.body.isEmpty() && node.dstEdges.size == 1 && node.dstEdgesButNext.isEmpty()) { + val dstNode = node.dstEdges.first().dst + for (e in node.srcEdges.toList()) { + e.remove() + e.src.edgeTo(dstNode, e.cond) + } } else { - if (node != exit) edge(node, exit) + result += node } + + if (node.next == null && node != exit) edge(node, exit) // Add edge to end node + if (node.next != null) explore(node.next!!) for (edge in node.dstEdges) explore(edge.dst) } explore(entry) - explored += exit - return explored.toList() + result += exit + return result.toList() } inline private fun trace(msg: () -> String) { @@ -96,7 +130,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo for (n in g.nodes) { trace { "* $n" } } - trace { "// STRUCTURE CODE FOR TESTS:" } + trace { "// STRUCTURE CODE FOR TESTS START" } for (n in g.nodes) { val line = "val L${n.index} = node(\"L${n.index}\")" val bodyStr = n.body.filter { it !is AstStm.NOP }.dumpCollapse(types).toString(false).replace('\n', ' ').trim() @@ -118,7 +152,29 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } } } - trace { "// /STRUCTURE CODE FOR TESTS" } + trace { "// STRUCTURE CODE FOR TESTS END" } + + trace { "# GRAPHVIZ START - http://viz-js.com/" } + trace { "digraph G {" } + for (n in g.nodes) { + val label = n.body.dumpCollapse(types).toString() + trace { "L${n.index} [label = ${label.quote()}]" } + } + for (n in g.nodes) { + if (n.dstEdges.size != 0) { + for (e in n.dstEdges) { + val label = if (e.cond != null) { + //"l${e.src.index}_l${e.dst.index}" + e.cond.dump(types) + } else { + "" + } + trace { "L${e.src.index} -> L${e.dst.index} [label = ${label.quote()}]" } + } + } + } + trace { "}" } + trace { "# GRAPHVIZ END" } } //println("Relooping '$name'...") val result = renderComponents(g.tarjanStronglyConnectedComponentsAlgorithm(), entry) @@ -193,15 +249,22 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo //throw RelooperException("Too much nesting levels!") invalidOp("ERROR When Relooping $name (TOO MUCH NESTING LEVELS)") } + val indent by lazy { INDENTS[level] } val out = arrayListOf() var node: Node? = entry val explored = LinkedHashSet() + + trace { "$indent- renderComponents: start: L${entry.index}, end: L${exit?.index}" } + + fun List.toLString() = this.map { "L${it.index}" }.toString() + loop@ while (node != null && node != exit) { if (node in explored) { //invalidOp("Already explored : $node") break } + trace { "$indent- Processing L${node?.index}" } explored += node val prevNode = node ctx.rendered += node @@ -217,7 +280,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo //val outsNotInContext = outs.filter { it !in ctx.loopStarts } if (outsNotInContext.size != 1) { trace { "$indent- ASSERTION FAILED! outsNotInContext.size != 1 (${outsNotInContext.size}) : $node" } - invalidOp("ERROR When Relooping '$name' ASSERTION FAILED :: ASSERTION FAILED! outsNotInContext.size != 1 (${outsNotInContext.size}) : $node") + invalidOp("ERROR When Relooping '$name' MULTIPLE EXITS :: NODES${component.nodes.toLString()}, EXITS:${outsNotInContext.toLString()}") } val entryNode = node @@ -241,7 +304,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo trace { "$indent- render single node: renderNoLoops" } val out2 = arrayListOf() renderNoLoops(g, out2, node, exitNode, ctx, level) - out2.stmsWoNops + out2.stmsWithoutNops } else { trace { "$indent- render multi node: renderComponents (${entryNode.index} - ${exitNode.index})" } renderComponents(component.split(entryNode, exitNode), entryNode, exitNode, ctx, level = level + 1) @@ -260,15 +323,15 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo if (node == prevNode) invalidOp("Infinite loop detected") } - return out.stmsWoNops + return out.stmsWithoutNops } - val Iterable.stmsWoNops: AstStm get() = this.toList().filter { it !is AstStm.NOP }.stm() + val Iterable.stmsWithoutNops: AstStm get() = this.toList().filter { it !is AstStm.NOP }.stm() fun renderNoLoops(g: StrongComponentGraph, out: ArrayList, node: Node, exit: Node?, ctx: RenderContext, level: Int): Node? { val indent = INDENTS[level] trace { "$indent- renderNoLoops: Detected no loop : $node" } - out += node.body.stmsWoNops + out += node.body.stmsWithoutNops fun getNodeContinueOrBreak(node: Relooper.Node?): AstStm? { val loopStart = ctx.loopStarts[node] diff --git a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt index 77e2921b..d631eb9e 100644 --- a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt +++ b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt @@ -122,26 +122,26 @@ class RelooperTest { A.assertDump(""" A = 1; if ((!condToAvoidLoop)) { - do { + loop0: do { B = 1; - do { + loop1: do { C = 1; if (condLoopInContinue) { - continue; + continue loop1; } if (condLoopOutBreak) { - break; + break loop0; } - break; + break loop1; } while (true); D = 1; if (condLoopOutContinue) { - continue; + continue loop0; } if (condLoopOutBreak) { - break; + break loop0; } - break; + break loop0; } while (true); } E = 1; @@ -195,23 +195,17 @@ class RelooperTest { L0.assertDump(""" L0 = 1; - do { + loop0: do { L1 = 1; if ((!l1_l3)) { - { - L2 = 1; - do { - L4 = 1; - } while (l4_l4); - L5 = 1; - } + L2 = 1; + loop1: do { + L4 = 1; + } while (l4_l4); + L5 = 1; } L3 = 1; - if (l3_l1) { - continue; - } - break; - } while (true); + } while (l3_l1); L6 = 1; L8 = 1; """) @@ -230,7 +224,7 @@ class RelooperTest { L0.assertDump(""" L0 = 1; - do { + loop0: do { L1 = 1; } while (lI0 < lI1); L2 = 1; @@ -257,7 +251,7 @@ class RelooperTest { @Test fun testSimpleWhile() = relooperTest { val L0 = node("L0") - val L1 = node("L1") + val L1 = node(AstStm.NOP("just_for_if")) val L2 = node("L2") val L3 = node("L3") val L6 = node("L6") @@ -267,8 +261,19 @@ class RelooperTest { L2.edgeTo(L1) L3.edgeTo(L6) + // @TODO: We can remove continue loo0 at the end of any loop. + // @TODO: We can convert `while (true) { if (cond) break; ... }` --> `while (!cond) { ... }` L0.assertDump(""" - - + L0 = 1; + loop0: do { + if (l2_l3) { + break loop0; + } + L2 = 1; + continue loop0; + } while (true); + L3 = 1; + L6 = 1; """) // * L0: { lI0 = p0; lI1 = p1; lI1 = (lI1 + 1); } EDGES: [goto L1;]. SRC_EDGES: 0 From 1679db28e76435cd07d6773d452aabbc50a1d06a Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 11:33:54 +0100 Subject: [PATCH 30/60] Do not create additional exit node if not required --- .../src/com/jtransc/graph/Relooper.kt | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 620131e0..697603df 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -92,9 +92,9 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo fun edge(a: Node, b: Node, cond: AstExpr? = null) = a.edgeTo(b, cond) private fun prepare(entry: Node): List { - val exit = node(listOf()) val explored = LinkedHashSet() val result = LinkedHashSet() + val exitNodes = arrayListOf() fun explore(node: Node) { if (node in explored) return explored += node @@ -110,12 +110,19 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo result += node } - if (node.next == null && node != exit) edge(node, exit) // Add edge to end node + if (node.next == null) exitNodes += node if (node.next != null) explore(node.next!!) for (edge in node.dstEdges) explore(edge.dst) } explore(entry) - result += exit + + // Ensure just one single exit node + if (exitNodes.size != 1) { + val exit = node(listOf()) + for (node in exitNodes) edge(node, exit) + result += exit + } + return result.toList() } @@ -253,19 +260,22 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val indent by lazy { INDENTS[level] } val out = arrayListOf() var node: Node? = entry - val explored = LinkedHashSet() + val locallyExplored = LinkedHashSet() trace { "$indent- renderComponents: start: L${entry.index}, end: L${exit?.index}" } fun List.toLString() = this.map { "L${it.index}" }.toString() loop@ while (node != null && node != exit) { - if (node in explored) { - //invalidOp("Already explored : $node") + if (node in locallyExplored) { + //invalidOp("Already explored locally : $node") break } + if (node in ctx.rendered) { + //invalidOp("Already explored globally : $node") + } trace { "$indent- Processing L${node?.index}" } - explored += node + locallyExplored += node val prevNode = node ctx.rendered += node val component = g.findComponentWith(node) From ae6db5863c86352178aaeb30cfe13a7475a2bd09 Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 11:40:15 +0100 Subject: [PATCH 31/60] Revert previos: we need end node for this to work properly --- .../src/com/jtransc/graph/Relooper.kt | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 697603df..b8efaffe 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -51,11 +51,12 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val next get() = dstEdges.firstOrNull { it.cond == null }?.dst val nextEdge get() = dstEdges.firstOrNull { it.cond == null } - fun edgeTo(dst: Node, cond: AstExpr? = null) { + fun edgeTo(dst: Node, cond: AstExpr? = null): Node { val src = this val edge = Edge(types, src, dst, cond) src.dstEdges += edge dst.srcEdges += edge + return this } override fun toString(): String = "L$index: " + dump(types, body.stm()).toString().replace('\n', ' ').trim() + " EDGES: $dstEdges. SRC_EDGES: ${srcEdges.size}" @@ -91,7 +92,9 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo fun edge(a: Node, b: Node, cond: AstExpr? = null) = a.edgeTo(b, cond) - private fun prepare(entry: Node): List { + data class Prepare(val nodes: List, val entry: Node, val exit: Node) + + private fun prepare(entry: Node): Prepare { val explored = LinkedHashSet() val result = LinkedHashSet() val exitNodes = arrayListOf() @@ -117,13 +120,23 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo explore(entry) // Ensure just one single exit node - if (exitNodes.size != 1) { + //val actualExit = if (exitNodes.size != 1) { + // val exit = node(listOf()) + // for (node in exitNodes) edge(node, exit) + // result += exit + // exit + //} else { + // exitNodes.first() + //} + + val actualExit = run { val exit = node(listOf()) for (node in exitNodes) edge(node, exit) result += exit + exit } - return result.toList() + return Prepare(result.toList(), entry, actualExit) } inline private fun trace(msg: () -> String) { @@ -131,7 +144,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } fun render(entry: Node): AstStm { - val g = graphList(prepare(entry).map { it to it.possibleNextNodes }) + val gresult = prepare(entry) + val g = graphList(gresult.nodes.map { it to it.possibleNextNodes }) if (debug) { trace { "Rendering $name" } for (n in g.nodes) { @@ -184,7 +198,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo trace { "# GRAPHVIZ END" } } //println("Relooping '$name'...") - val result = renderComponents(g.tarjanStronglyConnectedComponentsAlgorithm(), entry) + val result = renderComponents(g.tarjanStronglyConnectedComponentsAlgorithm(), gresult.entry, gresult.exit) //println("Relooping '$name'...OK") return result } @@ -251,7 +265,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo fun AstStm.dump() = this.dump(AstTypes(com.jtransc.gen.TargetName("js"))) } - fun renderComponents(g: StrongComponentGraph, entry: Node, exit: Node? = null, ctx: RenderContext = RenderContext(g.graph), level: Int = 0): AstStm { + fun renderComponents(g: StrongComponentGraph, entry: Node, exit: Node, ctx: RenderContext = RenderContext(g.graph), level: Int = 0): AstStm { if (level > 5) { //throw RelooperException("Too much nesting levels!") invalidOp("ERROR When Relooping $name (TOO MUCH NESTING LEVELS)") @@ -407,8 +421,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo else { out += AstStm.IF_ELSE( endOfIf.cond!!, - renderComponents(g, endOfIfNode, common, ctx, level = level + 1), - renderComponents(g, ifBody, common, ctx, level = level + 1) + renderComponents(g, endOfIfNode, common!!, ctx, level = level + 1), + renderComponents(g, ifBody, common!!, ctx, level = level + 1) ) } return common From 1e4c067ce42efc5be8f08299ef21a0f922223472 Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 11:51:14 +0100 Subject: [PATCH 32/60] Fixed some relooper cases --- .../src/com/jtransc/graph/Relooper.kt | 53 +++++++++++++------ .../test/com/jtransc/graph/RelooperTest.kt | 27 ++++++++-- 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index b8efaffe..7d60c407 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -42,6 +42,7 @@ import kotlin.collections.LinkedHashSet */ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boolean = false) { class Node(val types: AstTypes, val index: Int, val body: List) { + val name = "L$index" //var next: Node? = null val srcEdges = arrayListOf() val dstEdges = arrayListOf() @@ -65,6 +66,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo class Edge(val types: AstTypes, val src: Node, val dst: Node, val cond: AstExpr? = null) { //override fun toString(): String = "IF (${cond.dump(types)}) goto L${dst.index}; else goto L${current.next?.index};" + val condOrTrue get() = cond ?: true.lit + fun remove() { src.dstEdges -= this dst.srcEdges -= this @@ -82,7 +85,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo //if (debug && index == 6) println("test") for (stm in this) { when (stm) { - is AstStm.STMS -> out += stm.stmsUnboxed.normalize(index) + is AstStm.STMS -> out += stm.stmsUnboxed.normalize(index) is AstStm.NOP -> Unit else -> out += stm } @@ -406,23 +409,41 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo trace { "$indent- Node IF (and else?)" } val ifBody = node.next!! - val endOfIf = node.dstEdgesButNext.firstOrNull() ?: invalidOp("Expected conditional!") - val endOfIfNode = endOfIf.dst - val common = ctx.findCommonSuccessorNotRendered(ifBody, endOfIfNode, exit) - - // IF - if (common == endOfIfNode) { - out += AstStm.IF( - endOfIf.cond!!.not(), // @TODO: Negate a float comparison problem with NaNs + val endOfIfEdge = node.dstEdgesButNext.firstOrNull() ?: invalidOp("Expected conditional!") + val endOfIfNode = endOfIfEdge.dst + val common = ctx.findCommonSuccessorNotRendered(ifBody, endOfIfNode, exit = exit) + //?: invalidOp("Not found common node for ${ifBody.name} and ${endOfIfNode.name}!") + + + when (common) { + // IF + endOfIfNode -> out += AstStm.IF( + endOfIfEdge.cond!!.not(), // @TODO: Negate a float comparison problem with NaNs renderComponents(g, ifBody, endOfIfNode, ctx, level = level + 1) ) - } - // IF+ELSE - else { - out += AstStm.IF_ELSE( - endOfIf.cond!!, - renderComponents(g, endOfIfNode, common!!, ctx, level = level + 1), - renderComponents(g, ifBody, common!!, ctx, level = level + 1) + // IF + null -> { + val ifBodyCB = getNodeContinueOrBreak(ifBody) + val endOfIfCB = getNodeContinueOrBreak(endOfIfNode) + when { + ifBodyCB != null && endOfIfCB == null -> { + out += AstStm.IF(endOfIfEdge.condOrTrue.not(), ifBodyCB) + return endOfIfNode + } + ifBodyCB == null && endOfIfCB != null -> { + TODO("ifBodyCB null!") + } + else -> { + TODO("Both null!") + } + } + + } + // IF+ELSE + else -> out += AstStm.IF_ELSE( + endOfIfEdge.cond!!, + renderComponents(g, endOfIfNode, common, ctx, level = level + 1), + renderComponents(g, ifBody, common, ctx, level = level + 1) ) } return common diff --git a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt index d631eb9e..49d4b57c 100644 --- a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt +++ b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt @@ -302,10 +302,10 @@ class RelooperTest { val L3 = node("L3") val L4 = node("L4") // lA3.add(((java.lang.Object)p0.substring(lI5, lI4))); lI5 = (lI4 + 1); val L5 = node("L5") // lI4 = (lI4 + 1); - val L6 = node("L6") // NOP(empty stm) + val L6 = node(AstStm.NOP("L6")) // NOP(empty stm) val L9 = node("L9") // lA3.add(((java.lang.Object)p0.substring(lI5))); val L10 = node("L10") // return ((java.lang.String[])lA3.toArray(((java.lang.Object[])new java.lang.String[lA3.size()]))); - val L12 = node("L12") // NOP(empty stm) + val L12 = node(AstStm.NOP("L12")) // NOP(empty stm) L0.edgeTo(L1) L1.edgeTo(L2).edgeTo(L3, "l1_l3") // [(lI4 >= p0.length())] L2.edgeTo(L4).edgeTo(L5, "l2_l5") // [(p0.charAt(lI4) != ((int)p1))] @@ -317,7 +317,27 @@ class RelooperTest { L10.edgeTo(L12) L0.assertDump(""" - - + L0 = 1; + loop0: do { + L1 = 1; + if (l1_l3) { + break loop0; + } + L2 = 1; + if ((!l2_l5)) { + L4 = 1; + if ((!l4_l5)) { + break loop0; + } + } + L5 = 1; + continue loop0; + } while (true); + L3 = 1; + if ((!l3_l10)) { + L9 = 1; + } + L10 = 1; """) // @JTranscRelooper(debug = true) @@ -343,7 +363,6 @@ class RelooperTest { inline fun relooperTest(callback: Relooper.() -> Unit): Unit = callback(relooper) - fun Relooper.Node.edgeTo(other: Relooper.Node, cond: AstExpr? = null): Relooper.Node = this.apply { relooper.edge(this, other, cond) } fun Relooper.Node.edgeTo(other: Relooper.Node, cond: String): Relooper.Node = this.edgeTo(other, cond.let { cond(it) }) fun String.normalizeMulti() = this.trimIndent().trim().lines().map { it.trimEnd() }.joinToString("\n") fun Indenter.normalizeMulti() = this.toString().normalizeMulti() From 90b2fc1021b5bcec3c78507ac8032897b3f02a20 Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 12:20:18 +0100 Subject: [PATCH 33/60] Some loop optimizations --- .../src/com/jtransc/ast/AstTransformer.kt | 2 +- .../src/com/jtransc/ast/AstVisitor.kt | 2 +- jtransc-core/src/com/jtransc/ast/ast_body.kt | 18 +++-- jtransc-core/src/com/jtransc/ast/ast_dump.kt | 2 +- .../com/jtransc/ast/dependency/analyzer.kt | 2 +- ...deterministicParameterEvaluationFeature.kt | 2 +- .../com/jtransc/gen/common/CommonGenerator.kt | 4 +- .../src/com/jtransc/graph/Relooper.kt | 68 +++++++++++++++---- .../test/com/jtransc/graph/RelooperTest.kt | 27 +++----- 9 files changed, 85 insertions(+), 42 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/AstTransformer.kt b/jtransc-core/src/com/jtransc/ast/AstTransformer.kt index f800e126..789260be 100644 --- a/jtransc-core/src/com/jtransc/ast/AstTransformer.kt +++ b/jtransc-core/src/com/jtransc/ast/AstTransformer.kt @@ -200,7 +200,7 @@ open class AstTransformer { open fun transform(stm: AstStm.WHILE): AstStm { transform(stm.cond) - transform(stm.iter) + transform(stm.body) return stm } diff --git a/jtransc-core/src/com/jtransc/ast/AstVisitor.kt b/jtransc-core/src/com/jtransc/ast/AstVisitor.kt index 52c4bc97..9861e82d 100644 --- a/jtransc-core/src/com/jtransc/ast/AstVisitor.kt +++ b/jtransc-core/src/com/jtransc/ast/AstVisitor.kt @@ -204,7 +204,7 @@ open class AstVisitor { open fun visit(stm: AstStm.WHILE) { visit(stm.cond) - visit(stm.iter) + visit(stm.body) } open fun visit(stm: AstStm.DO_WHILE) { diff --git a/jtransc-core/src/com/jtransc/ast/ast_body.kt b/jtransc-core/src/com/jtransc/ast/ast_body.kt index 87ef20ff..8fc2fca0 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_body.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_body.kt @@ -202,15 +202,15 @@ sealed class AstStm : AstElement, Cloneable { val sfalse = sfalse.box } - class WHILE(val name: String, cond: AstExpr, iter: AstStm) : AstStm() { + class WHILE(val name: String, cond: AstExpr, body: AstStm) : AstStm() { val cond = cond.box - val iter = iter.box + val body = body.box } // Basic back jump - class DO_WHILE(val name: String, cond: AstExpr, iter: AstStm) : AstStm() { + class DO_WHILE(val name: String, cond: AstExpr, body: AstStm) : AstStm() { val cond = cond.box - val body = iter.box + val body = body.box } class RETURN(retval: AstExpr) : AstStm() { @@ -277,6 +277,11 @@ fun AstStm.isEmpty() = when (this) { else -> false } +fun AstStm.isNop(): Boolean = this is AstStm.NOP + +fun AstStm.isContinue(name: String): Boolean = this is AstStm.CONTINUE && this.name == name +fun AstStm.isBreak(name: String): Boolean = this is AstStm.BREAK && this.name == name + fun AstStm.isSingleStm(): Boolean { if (this is AstStm.STMS) return (this.stms.count() == 1) && this.stms.last().value.isSingleStm() return true @@ -287,6 +292,8 @@ fun AstStm.lastStm(): AstStm { return this } +val Iterable.unboxed get() = this.map { it.value } + fun AstStm.expand(): List { return when (this) { is AstStm.STMS -> this.stms.flatMap { it.value.expand() } @@ -590,6 +597,9 @@ fun List.stm() = when (this.size) { } fun AstExpr.Box.isPure(): Boolean = this.value.isPure() +fun AstExpr.Box.isLiteral(value: Any?): Boolean = this.value.isLiteral(value) + +fun AstExpr.isLiteral(value: Any?): Boolean = (this is AstExpr.LITERAL) && this.value == value fun AstExpr.isPure(): Boolean = when (this) { is AstExpr.ARRAY_ACCESS -> this.array.isPure() && this.index.isPure() // Can cause null pointer/out of bounds diff --git a/jtransc-core/src/com/jtransc/ast/ast_dump.kt b/jtransc-core/src/com/jtransc/ast/ast_dump.kt index 1bd2e575..3be53034 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_dump.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_dump.kt @@ -78,7 +78,7 @@ fun dump(types: AstTypes, stm: AstStm?): Indenter { } is AstStm.WHILE -> { line("${stm.name}: while (${dump(types, stm.cond)})") { - line(dumpCollapse(types, stm.iter)) + line(dumpCollapse(types, stm.body)) } } is AstStm.DO_WHILE -> { diff --git a/jtransc-core/src/com/jtransc/ast/dependency/analyzer.kt b/jtransc-core/src/com/jtransc/ast/dependency/analyzer.kt index c4e702d6..5748b31b 100644 --- a/jtransc-core/src/com/jtransc/ast/dependency/analyzer.kt +++ b/jtransc-core/src/com/jtransc/ast/dependency/analyzer.kt @@ -256,7 +256,7 @@ object AstDependencyAnalyzer { } is AstStm.WHILE -> { flow() - ana(stm.cond); ana(stm.iter) + ana(stm.cond); ana(stm.body) } else -> noImpl("Not implemented STM $stm") diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt index fd8eb456..65d41598 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/UndeterministicParameterEvaluationFeature.kt @@ -41,7 +41,7 @@ class UndeterministicParameterEvaluationFeature : AstMethodFeature() { is AstStm.MONITOR_ENTER -> out += AstStm.MONITOR_ENTER(stm.expr.processExpr(out, self = false)) is AstStm.MONITOR_EXIT -> out += AstStm.MONITOR_EXIT(stm.expr.processExpr(out, self = false)) is AstStm.THROW -> out += AstStm.THROW(stm.exception.processExpr(out, self = false)) - is AstStm.WHILE -> out += AstStm.WHILE(stm.name, stm.cond.processExpr(out, self = false), stm.iter.value) + is AstStm.WHILE -> out += AstStm.WHILE(stm.name, stm.cond.processExpr(out, self = false), stm.body.value) is AstStm.DO_WHILE -> out += AstStm.DO_WHILE(stm.name, stm.cond.processExpr(out, self = false), stm.body.value) is AstStm.SET_LOCAL -> out += AstStm.SET_LOCAL(stm.local, stm.expr.processExpr(out), true) is AstStm.SET_ARRAY -> { diff --git a/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt b/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt index 890bdd83..09baa42b 100644 --- a/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt +++ b/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt @@ -772,7 +772,7 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { indent { line(genBody2WithFeatures2(method, body)) } - line("} finally{") + line("} finally {") indent { lineMonitorExit() } @@ -1364,7 +1364,7 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { flowBlock(FlowKind.WHILE, stm.name) { line("${label}while (${stm.cond.genExpr()})") { - line(stm.iter.genStm()) + line(stm.body.genStm()) } } if (!supportsLabels) { diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 7d60c407..4012f566 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -268,7 +268,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo fun AstStm.dump() = this.dump(AstTypes(com.jtransc.gen.TargetName("js"))) } - fun renderComponents(g: StrongComponentGraph, entry: Node, exit: Node, ctx: RenderContext = RenderContext(g.graph), level: Int = 0): AstStm { + fun renderComponents(g: StrongComponentGraph, entry: Node, exit: Node?, ctx: RenderContext = RenderContext(g.graph), level: Int = 0): AstStm { if (level > 5) { //throw RelooperException("Too much nesting levels!") invalidOp("ERROR When Relooping $name (TOO MUCH NESTING LEVELS)") @@ -324,7 +324,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val cond = true.lit - out += AstStm.DO_WHILE( + out += AstStm.WHILE( loopName, cond, if (isSingleNodeLoop) { @@ -336,7 +336,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo trace { "$indent- render multi node: renderComponents (${entryNode.index} - ${exitNode.index})" } renderComponents(component.split(entryNode, exitNode), entryNode, exitNode, ctx, level = level + 1) } - ).optimizeDoWhile() + ).optimizeWhile() ctx.loopEnds -= exitNode ctx.loopStarts -= entryNode @@ -354,6 +354,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } val Iterable.stmsWithoutNops: AstStm get() = this.toList().filter { it !is AstStm.NOP }.stm() + val Iterable.stmsWithoutNopsList: List get() = this.toList().filter { it !is AstStm.NOP } fun renderNoLoops(g: StrongComponentGraph, out: ArrayList, node: Node, exit: Node?, ctx: RenderContext, level: Int): Node? { val indent = INDENTS[level] @@ -434,7 +435,16 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo TODO("ifBodyCB null!") } else -> { - TODO("Both null!") + //TODO("Both null!") + + // Maybe an if-chain that was not optimized? And this will generate repeated branches! + trace { "$indent- WARNING: Maybe an if-chain that was not optimized? And this will generate repeated branches!" } + + out += AstStm.IF_ELSE( + endOfIfEdge.cond!!, + renderComponents(g, endOfIfNode, common, ctx, level = level + 1), + renderComponents(g, ifBody, common, ctx, level = level + 1) + ) } } @@ -493,18 +503,50 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo return this.getExternalInputsEdges().map { it.dst }.distinct() } - fun AstStm.DO_WHILE.optimizeDoWhile(): AstStm.DO_WHILE { + fun AstStm.DO_WHILE.optimizeWhile(): AstStm { + if (this.cond.isLiteral(true)) { + return AstStm.WHILE(this.name, this.cond.value, this.body.value) + } else { + return this + } + } + + fun AstStm.WHILE.optimizeWhile(): AstStm { + if (!this.cond.isLiteral(true)) return this // Can't optimize! + + val loopName = this.name val bodyValue = this.body.value - if (bodyValue is AstStm.STMS) { - val stms = bodyValue.stms - if (stms.size >= 2) { - val last = stms[stms.size - 1].value - val plast = stms[stms.size - 2].value - if (last is AstStm.BREAK && plast is AstStm.IF && plast.strue.value is AstStm.CONTINUE) { - return AstStm.DO_WHILE(name, plast.cond.value, stms.map { it.value }.slice(0 until stms.size - 2).stms.box.value) - } + @Suppress("FoldInitializerAndIfToElvis") + if (bodyValue !is AstStm.STMS) return this + + val stms = bodyValue.stms.unboxed.stmsWithoutNopsList.map { it.box } + + val last = stms.lastOrNull() + if (last != null) { + val lastValue = last.value + if (lastValue.isContinue(loopName)) { + //lastValue.box.replaceWith(AstStm.NOP("optimized")) + return AstStm.WHILE(loopName, cond.value, AstStm.STMS(stms.dropLast(1).unboxed, true)).optimizeWhile() + } + } + + val first = stms.firstOrNull() + if (first != null) { + val firstValue = first.value + if (firstValue is AstStm.IF && firstValue.strue.value.isBreak(loopName)) { + //firstValue.box.replaceWith(AstStm.NOP("optimized")) + return AstStm.WHILE(loopName, firstValue.cond.value.not(), AstStm.STMS(stms.drop(1).unboxed, true)).optimizeWhile() } } + + if (stms.size >= 2) { + val last = stms[stms.size - 1].value + val plast = stms[stms.size - 2].value + if (last is AstStm.BREAK && plast is AstStm.IF && plast.strue.value.isContinue(loopName)) { + return AstStm.DO_WHILE(loopName, plast.cond.value, stms.map { it.value }.slice(0 until stms.size - 2).stms.box.value) + } + } + return this // var n = 0; do { if (n++ < 10) continue; } while (false); console.log(n); // NOT WORKING: 1 // var n = 0; do { if (n++ < 10) continue; break; } while (true); console.log(n); // WORKING: 11 diff --git a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt index 49d4b57c..75b6b761 100644 --- a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt +++ b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt @@ -122,9 +122,9 @@ class RelooperTest { A.assertDump(""" A = 1; if ((!condToAvoidLoop)) { - loop0: do { + loop0: while (true) { B = 1; - loop1: do { + loop1: while (true) { C = 1; if (condLoopInContinue) { continue loop1; @@ -133,7 +133,7 @@ class RelooperTest { break loop0; } break loop1; - } while (true); + } D = 1; if (condLoopOutContinue) { continue loop0; @@ -142,7 +142,7 @@ class RelooperTest { break loop0; } break loop0; - } while (true); + } } E = 1; """) @@ -265,13 +265,9 @@ class RelooperTest { // @TODO: We can convert `while (true) { if (cond) break; ... }` --> `while (!cond) { ... }` L0.assertDump(""" L0 = 1; - loop0: do { - if (l2_l3) { - break loop0; - } + loop0: while ((!l2_l3)) { L2 = 1; - continue loop0; - } while (true); + } L3 = 1; L6 = 1; """) @@ -297,7 +293,7 @@ class RelooperTest { @Test fun testMixed1() = relooperTest { val L0 = node("L0") // lA3 = new java.util.ArrayList(); lI4 = 0; lI5 = 0; - val L1 = node("L1") + val L1 = node(AstStm.NOP("L1")) val L2 = node("L2") val L3 = node("L3") val L4 = node("L4") // lA3.add(((java.lang.Object)p0.substring(lI5, lI4))); lI5 = (lI4 + 1); @@ -318,11 +314,7 @@ class RelooperTest { L0.assertDump(""" L0 = 1; - loop0: do { - L1 = 1; - if (l1_l3) { - break loop0; - } + loop0: while ((!l1_l3)) { L2 = 1; if ((!l2_l5)) { L4 = 1; @@ -331,8 +323,7 @@ class RelooperTest { } } L5 = 1; - continue loop0; - } while (true); + } L3 = 1; if ((!l3_l10)) { L9 = 1; From fa640a7678e37d114165a77d4a3c75a5bab921f8 Mon Sep 17 00:00:00 2001 From: soywiz Date: Tue, 16 Jan 2018 12:30:15 +0100 Subject: [PATCH 34/60] Updated version --- benchmark/gradle.properties | 2 +- jtransc-core/src/com/jtransc/graph/Relooper.kt | 4 ++-- .../example-gradle-multi/gradle.properties | 2 +- jtransc-main-run/example-gradle/gradle.properties | 2 +- jtransc-main-run/pom.xml | 2 +- jtransc-maven-plugin/example/pom.xml | 4 ++-- .../jtransc-maven-plugin/plugin-help.xml | 2 +- .../jtransc-maven-plugin/pom.properties | 2 +- .../maven/com.jtransc/jtransc-maven-plugin/pom.xml | 2 +- .../resources/META-INF/maven/plugin.xml | 14 +++++++------- .../src/com/jtransc/JTranscVersion.java | 2 +- 11 files changed, 19 insertions(+), 19 deletions(-) diff --git a/benchmark/gradle.properties b/benchmark/gradle.properties index 442b3e39..09b63735 100644 --- a/benchmark/gradle.properties +++ b/benchmark/gradle.properties @@ -1 +1 @@ -jtranscVersion=0.6.9-SNAPSHOT +jtranscVersion=0.7.0-SNAPSHOT diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 4012f566..bbac80a2 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -353,8 +353,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo return out.stmsWithoutNops } - val Iterable.stmsWithoutNops: AstStm get() = this.toList().filter { it !is AstStm.NOP }.stm() - val Iterable.stmsWithoutNopsList: List get() = this.toList().filter { it !is AstStm.NOP } + val Iterable.stmsWithoutNopsList: List get() = this.filter { it !is AstStm.NOP } + val Iterable.stmsWithoutNops: AstStm get() = this.stmsWithoutNopsList.stm() fun renderNoLoops(g: StrongComponentGraph, out: ArrayList, node: Node, exit: Node?, ctx: RenderContext, level: Int): Node? { val indent = INDENTS[level] diff --git a/jtransc-main-run/example-gradle-multi/gradle.properties b/jtransc-main-run/example-gradle-multi/gradle.properties index 930505e5..8ee60c78 100644 --- a/jtransc-main-run/example-gradle-multi/gradle.properties +++ b/jtransc-main-run/example-gradle-multi/gradle.properties @@ -1 +1 @@ -jtranscVersion=0.6.9-SNAPSHOT +jtranscVersion=0.7.0-SNAPSHOT diff --git a/jtransc-main-run/example-gradle/gradle.properties b/jtransc-main-run/example-gradle/gradle.properties index 442b3e39..09b63735 100644 --- a/jtransc-main-run/example-gradle/gradle.properties +++ b/jtransc-main-run/example-gradle/gradle.properties @@ -1 +1 @@ -jtranscVersion=0.6.9-SNAPSHOT +jtranscVersion=0.7.0-SNAPSHOT diff --git a/jtransc-main-run/pom.xml b/jtransc-main-run/pom.xml index d2f9f305..0fcd1a6b 100644 --- a/jtransc-main-run/pom.xml +++ b/jtransc-main-run/pom.xml @@ -22,7 +22,7 @@ 4.0.0 com.jtransc - 0.6.9-SNAPSHOT + 0.7.0-SNAPSHOT jtransc-main-run diff --git a/jtransc-maven-plugin/example/pom.xml b/jtransc-maven-plugin/example/pom.xml index 5bc6c30f..f362df2f 100644 --- a/jtransc-maven-plugin/example/pom.xml +++ b/jtransc-maven-plugin/example/pom.xml @@ -43,7 +43,7 @@ com.jtransc jtransc-rt-core - 0.6.9-SNAPSHOT + 0.7.0-SNAPSHOT @@ -53,7 +53,7 @@ com.jtransc jtransc-maven-plugin - 0.6.9-SNAPSHOT + 0.7.0-SNAPSHOT js:js:program.js example.Test diff --git a/jtransc-maven-plugin/resources/META-INF/maven/com.jtransc/jtransc-maven-plugin/plugin-help.xml b/jtransc-maven-plugin/resources/META-INF/maven/com.jtransc/jtransc-maven-plugin/plugin-help.xml index 949b03a4..7b7c11f1 100644 --- a/jtransc-maven-plugin/resources/META-INF/maven/com.jtransc/jtransc-maven-plugin/plugin-help.xml +++ b/jtransc-maven-plugin/resources/META-INF/maven/com.jtransc/jtransc-maven-plugin/plugin-help.xml @@ -7,7 +7,7 @@ Maven plugin for JVM AOT compiler currently generating Haxe, with initial focus on kotlin and games. com.jtransc jtransc-maven-plugin - 0.6.9-SNAPSHOT + 0.7.0-SNAPSHOT jtransc diff --git a/jtransc-maven-plugin/resources/META-INF/maven/com.jtransc/jtransc-maven-plugin/pom.properties b/jtransc-maven-plugin/resources/META-INF/maven/com.jtransc/jtransc-maven-plugin/pom.properties index 2c9bf00b..b2262198 100644 --- a/jtransc-maven-plugin/resources/META-INF/maven/com.jtransc/jtransc-maven-plugin/pom.properties +++ b/jtransc-maven-plugin/resources/META-INF/maven/com.jtransc/jtransc-maven-plugin/pom.properties @@ -1,5 +1,5 @@ #Generated by Apache Maven #Thu May 26 02:15:01 CEST 2016 -version=0.6.9-SNAPSHOT +version=0.7.0-SNAPSHOT groupId=com.jtransc artifactId=jtransc-maven-plugin diff --git a/jtransc-maven-plugin/resources/META-INF/maven/com.jtransc/jtransc-maven-plugin/pom.xml b/jtransc-maven-plugin/resources/META-INF/maven/com.jtransc/jtransc-maven-plugin/pom.xml index e91a9a57..75314788 100644 --- a/jtransc-maven-plugin/resources/META-INF/maven/com.jtransc/jtransc-maven-plugin/pom.xml +++ b/jtransc-maven-plugin/resources/META-INF/maven/com.jtransc/jtransc-maven-plugin/pom.xml @@ -20,7 +20,7 @@ com.jtransc jtransc - 0.6.9-SNAPSHOT + 0.7.0-SNAPSHOT ../pom.xml diff --git a/jtransc-maven-plugin/resources/META-INF/maven/plugin.xml b/jtransc-maven-plugin/resources/META-INF/maven/plugin.xml index 15504b57..4238a5c4 100644 --- a/jtransc-maven-plugin/resources/META-INF/maven/plugin.xml +++ b/jtransc-maven-plugin/resources/META-INF/maven/plugin.xml @@ -7,7 +7,7 @@ Maven plugin for JVM AOT compiler currently generating Haxe, with initial focus on kotlin and games. com.jtransc jtransc-maven-plugin - 0.6.9-SNAPSHOT + 0.7.0-SNAPSHOT jtransc false true @@ -296,37 +296,37 @@ com.jtransc jtransc-core jar - 0.6.9-SNAPSHOT + 0.7.0-SNAPSHOT com.jtransc jtransc-utils jar - 0.6.9-SNAPSHOT + 0.7.0-SNAPSHOT com.jtransc jtransc-asm jar - 0.6.9-SNAPSHOT + 0.7.0-SNAPSHOT com.jtransc jtransc-rt-core jar - 0.6.9-SNAPSHOT + 0.7.0-SNAPSHOT com.jtransc jtransc-main jar - 0.6.9-SNAPSHOT + 0.7.0-SNAPSHOT com.jtransc jtransc-gen-haxe jar - 0.6.9-SNAPSHOT + 0.7.0-SNAPSHOT org.ow2.asm diff --git a/jtransc-rt-core/src/com/jtransc/JTranscVersion.java b/jtransc-rt-core/src/com/jtransc/JTranscVersion.java index 8e7aff86..635d6b61 100644 --- a/jtransc-rt-core/src/com/jtransc/JTranscVersion.java +++ b/jtransc-rt-core/src/com/jtransc/JTranscVersion.java @@ -3,7 +3,7 @@ import com.jtransc.annotation.JTranscSync; public class JTranscVersion { - static private final String version = "0.6.9-SNAPSHOT"; + static private final String version = "0.7.0-SNAPSHOT"; @JTranscSync static public String getVersion() { From bc1c6466290bb20e188d1e29cac255f1e037ecd1 Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 10:39:37 +0100 Subject: [PATCH 35/60] Detects if && chains --- jtransc-core/src/com/jtransc/ast/ast_body.kt | 3 ++ .../src/com/jtransc/graph/Relooper.kt | 50 +++++++++++++++++-- .../src/relooper/RelooperTest.java | 4 +- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/ast_body.kt b/jtransc-core/src/com/jtransc/ast/ast_body.kt index 8fc2fca0..5d36f9d4 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_body.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_body.kt @@ -567,9 +567,12 @@ abstract class AstExpr : AstElement, Cloneable { infix fun lt(that: AstExpr) = AstExpr.BINOP(AstType.BOOL, this, AstBinop.LT, that) infix fun le(that: AstExpr) = AstExpr.BINOP(AstType.BOOL, this, AstBinop.LE, that) infix fun band(that: AstExpr) = AstExpr.BINOP(AstType.BOOL, this, AstBinop.BAND, that) + infix fun bor(that: AstExpr) = AstExpr.BINOP(AstType.BOOL, this, AstBinop.BOR, that) infix fun and(that: AstExpr) = AstExpr.BINOP(this.type, this, AstBinop.AND, that) infix fun instanceof(that: AstType.REF) = AstExpr.INSTANCE_OF(this, that) + //infix fun AND(that: AstExpr) = AstExpr.BINOP(this.type, this, AstBinop.AND, that) + class TERNARY(val cond: AstExpr, val etrue: AstExpr, val efalse: AstExpr, val types: AstTypes) : AstExpr() { override val type: AstType = types.unify(etrue.type, efalse.type) } diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index bbac80a2..972ee06b 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -49,8 +49,13 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val dstEdgesButNext get() = dstEdges.filter { it.cond != null } val possibleNextNodes: List get() = dstEdges.map { it.dst } - val next get() = dstEdges.firstOrNull { it.cond == null }?.dst val nextEdge get() = dstEdges.firstOrNull { it.cond == null } + var next: Node? + get() = nextEdge?.dst + set(value) { + nextEdge?.remove() + if (value != null) edgeTo(value) + } fun edgeTo(dst: Node, cond: AstExpr? = null): Node { val src = this @@ -97,11 +102,16 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo data class Prepare(val nodes: List, val entry: Node, val exit: Node) + /** + * - Remove empty nodes + * - Combine nodes with && and || + * - Create a single artificial exit node + */ private fun prepare(entry: Node): Prepare { val explored = LinkedHashSet() val result = LinkedHashSet() val exitNodes = arrayListOf() - fun explore(node: Node) { + fun explore(node: Node): Unit { if (node in explored) return explored += node @@ -112,10 +122,42 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo e.remove() e.src.edgeTo(dstNode, e.cond) } - } else { - result += node + explore(dstNode) + return + } + + // This node may be a || or a && + if (node.body.isEmpty() && node.srcEdges.size == 1 && node.srcEdges[0].src.next == node && node.dstEdges.size == 2) { + val prev = node.srcEdges[0].src + + if (prev.dstEdges.size == 2) { + val prevNext = prev.next!!; assert(prevNext == node) + val currNext = node.next!! + + val prevCond = prev.dstEdgesButNext.first() + val currCond = node.dstEdgesButNext.first() + + val prevCondNode = prevCond.dst + val currCondNode = currCond.dst + + // OR (after negating, acts as an && in code) + //* L0: EDGES: [goto L1;, IF ((p0 >= p1)) goto L2;]. SRC_EDGES: 0 + //* L1: NOP(empty stm) EDGES: [goto L3;, IF ((p0 < 0)) goto L2;]. SRC_EDGES: 1 + if (prevCondNode == currCondNode) { + prevCond.remove() + prev.edgeTo(currCond.dst, prevCond.condOrTrue bor currCond.condOrTrue) + prev.next = currNext + //println("-------") + explore(currCond.dst) + explore(currNext) + return + } else { + //TODO() + } + } } + result += node if (node.next == null) exitNodes += node if (node.next != null) explore(node.next!!) for (edge in node.dstEdges) explore(edge.dst) diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index 6c7a948e..0edb34af 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -37,7 +37,7 @@ static public int simpleIf(int a, int b) { } } - @JTranscRelooper + @JTranscRelooper(debug = true) static public int composedIfAnd(int a, int b) { if (a < b && a >= 0) { return -1; @@ -94,7 +94,7 @@ static public int simpleFor(int a, int b) { return b; } - @JTranscRelooper(debug = true) + @JTranscRelooper static private String[] split(String str, char ch, int limit) { ArrayList out = new ArrayList(); int n = 0; From 26b5b0d160a89cc86c081689a00f2146bde8b561 Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 11:16:05 +0100 Subject: [PATCH 36/60] Detects if || chains --- .../src/com/jtransc/graph/Relooper.kt | 110 ++++++++++++------ 1 file changed, 73 insertions(+), 37 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 972ee06b..ca619253 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -66,6 +66,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } override fun toString(): String = "L$index: " + dump(types, body.stm()).toString().replace('\n', ' ').trim() + " EDGES: $dstEdges. SRC_EDGES: ${srcEdges.size}" + fun isEmpty(): Boolean = body.isEmpty() } class Edge(val types: AstTypes, val src: Node, val dst: Node, val cond: AstExpr? = null) { @@ -102,29 +103,39 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo data class Prepare(val nodes: List, val entry: Node, val exit: Node) - /** - * - Remove empty nodes - * - Combine nodes with && and || - * - Create a single artificial exit node - */ - private fun prepare(entry: Node): Prepare { - val explored = LinkedHashSet() - val result = LinkedHashSet() - val exitNodes = arrayListOf() - fun explore(node: Node): Unit { - if (node in explored) return - explored += node + private fun Node.removeEmptyNodes(): Node { + var nentry = this + val processed = LinkedHashSet() + fun explore(node: Node) { + if (node in processed) return + processed += node - // Remove empty node if (node.body.isEmpty() && node.dstEdges.size == 1 && node.dstEdgesButNext.isEmpty()) { val dstNode = node.dstEdges.first().dst for (e in node.srcEdges.toList()) { e.remove() e.src.edgeTo(dstNode, e.cond) } + if (nentry == node) { + nentry = dstNode + } explore(dstNode) - return + } else { + for (edge in node.dstEdges.toList()) { + explore(edge.dst) + } } + } + explore(nentry) + return nentry + } + + private fun Node.combineBooleanOpsEdges(): Node { + val entry = this + val processed = LinkedHashSet() + fun explore(node: Node) { + if (node in processed) return + processed += node // This node may be a || or a && if (node.body.isEmpty() && node.srcEdges.size == 1 && node.srcEdges[0].src.next == node && node.dstEdges.size == 2) { @@ -140,7 +151,11 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val prevCondNode = prevCond.dst val currCondNode = currCond.dst - // OR (after negating, acts as an && in code) + if (debug) { + println("!!") + } + + // && in the original code //* L0: EDGES: [goto L1;, IF ((p0 >= p1)) goto L2;]. SRC_EDGES: 0 //* L1: NOP(empty stm) EDGES: [goto L3;, IF ((p0 < 0)) goto L2;]. SRC_EDGES: 1 if (prevCondNode == currCondNode) { @@ -151,44 +166,65 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo explore(currCond.dst) explore(currNext) return - } else { - //TODO() + } + // || in the original code + //* L0: EDGES: [goto L1;, IF ((p0 < p1)) goto L2;]. SRC_EDGES: 0 + //* L1: NOP(empty stm) EDGES: [IF ((p0 < 0)) goto L4;, goto L2;]. SRC_EDGES: 1 + else if (prevCondNode == currNext) { + prevCond.remove() + prev.edgeTo(currNext, prevCond.condOrTrue bor currCond.condOrTrue.not()) + prev.next = currCond.dst + //println("-------") + explore(currCond.dst) + explore(currNext) + return + } + // None + else { + } } } - result += node - if (node.next == null) exitNodes += node - if (node.next != null) explore(node.next!!) for (edge in node.dstEdges) explore(edge.dst) } explore(entry) + return entry + } - // Ensure just one single exit node - //val actualExit = if (exitNodes.size != 1) { - // val exit = node(listOf()) - // for (node in exitNodes) edge(node, exit) - // result += exit - // exit - //} else { - // exitNodes.first() - //} - - val actualExit = run { - val exit = node(listOf()) - for (node in exitNodes) edge(node, exit) - result += exit - exit + /** + * - Create a single artificial exit node + * - Creates a list of nodes for the graph + */ + private fun prepare(entry: Node): Prepare { + val exit = node(listOf()) + val explored = LinkedHashSet() + val result = LinkedHashSet() + fun explore(node: Node): Unit { + if (node in explored) return + explored += node + result += node + if (node.next == null) { + edge(node, exit) + } + if (node.next != null) explore(node.next!!) + for (edge in node.dstEdges) explore(edge.dst) } + explore(entry) + result += exit - return Prepare(result.toList(), entry, actualExit) + return Prepare(result.toList(), entry, exit) } inline private fun trace(msg: () -> String) { if (debug) println(msg()) } - fun render(entry: Node): AstStm { + fun render(rentry: Node): AstStm { + val entry = rentry + .removeEmptyNodes() + .combineBooleanOpsEdges() + val gresult = prepare(entry) val g = graphList(gresult.nodes.map { it to it.possibleNextNodes }) if (debug) { From 2932f7efcee4712ea6aabd2467657350da027729 Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 11:16:25 +0100 Subject: [PATCH 37/60] Small fixes --- .../src/com/jtransc/ast/feature/method/GotosFeature.kt | 1 + jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt | 2 +- jtransc-gen-common-tests/src/relooper/RelooperTest.java | 2 +- jtransc-gen-js/src/com/jtransc/gen/js/JsTarget.kt | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index 338b5783..95bf70f5 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -31,6 +31,7 @@ class GotosFeature : AstMethodFeature() { try { return removeRelooper(method, body, settings, types) ?: removeMachineState(body, types) } catch (t: Throwable) { + System.err.println("Not relooping $method because of exception!:") t.printStackTrace() return removeMachineState(body, types) } diff --git a/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt b/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt index 09baa42b..2d58163c 100644 --- a/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt +++ b/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt @@ -177,7 +177,7 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { open fun writeClasses(output: SyncVfsFile) { if (SINGLE_FILE) { - output.removeIfExists() + output[outputFileBaseName].removeIfExists() if (ADD_UTF8_BOM) { output[outputFileBaseName] = byteArrayOf(0xEF.toByte(), 0xBB.toByte(), 0xBF.toByte()) + genSingleFileClasses(output).toString().toByteArray() } else { diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index 0edb34af..db20d9fb 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -37,7 +37,7 @@ static public int simpleIf(int a, int b) { } } - @JTranscRelooper(debug = true) + @JTranscRelooper static public int composedIfAnd(int a, int b) { if (a < b && a >= 0) { return -1; diff --git a/jtransc-gen-js/src/com/jtransc/gen/js/JsTarget.kt b/jtransc-gen-js/src/com/jtransc/gen/js/JsTarget.kt index ee5088b6..58d0d96a 100644 --- a/jtransc-gen-js/src/com/jtransc/gen/js/JsTarget.kt +++ b/jtransc-gen-js/src/com/jtransc/gen/js/JsTarget.kt @@ -131,8 +131,8 @@ class JsGenerator(injector: Injector) : CommonGenerator(injector) { @Suppress("UNCHECKED_CAST") override fun writeClasses(output: SyncVfsFile) { - output[outputFileBaseName] = "" - output["$outputFileBaseName.map"] = "" + output[outputFileBaseName].remove() + output["$outputFileBaseName.map"].remove() val concatFilesTrans = copyFiles(output) val classesIndenter = arrayListOf() From 2f13dd0980571e40048372f8a8673329cab500d9 Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 11:17:15 +0100 Subject: [PATCH 38/60] ConcurrentModification fix --- jtransc-core/src/com/jtransc/graph/Relooper.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index ca619253..b7c26862 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -186,7 +186,9 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } } - for (edge in node.dstEdges) explore(edge.dst) + for (edge in node.dstEdges.toList()) { + explore(edge.dst) + } } explore(entry) return entry From fea7cf9a8acbe308435f244e18ae20bf6445002f Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 11:22:07 +0100 Subject: [PATCH 39/60] Convert recursive functions into normal queues --- .../src/com/jtransc/graph/Relooper.kt | 61 ++++++++++--------- .../src/com/jtransc/ds/collectionutils.kt | 2 + 2 files changed, 34 insertions(+), 29 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index b7c26862..0d55e9a5 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -106,8 +106,11 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo private fun Node.removeEmptyNodes(): Node { var nentry = this val processed = LinkedHashSet() - fun explore(node: Node) { - if (node in processed) return + val queue = Queue() + queue.queue(nentry) + while (queue.hasMore) { + val node = queue.dequeue() + if (node in processed) continue processed += node if (node.body.isEmpty() && node.dstEdges.size == 1 && node.dstEdgesButNext.isEmpty()) { @@ -119,22 +122,24 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo if (nentry == node) { nentry = dstNode } - explore(dstNode) + queue.queue(dstNode) } else { - for (edge in node.dstEdges.toList()) { - explore(edge.dst) + for (edge in node.dstEdges) { + queue.queue(edge.dst) } } } - explore(nentry) return nentry } private fun Node.combineBooleanOpsEdges(): Node { val entry = this val processed = LinkedHashSet() - fun explore(node: Node) { - if (node in processed) return + val queue = Queue() + queue.queue(entry) + while (queue.hasMore) { + val node = queue.dequeue() + if (node in processed) continue processed += node // This node may be a || or a && @@ -151,10 +156,6 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val prevCondNode = prevCond.dst val currCondNode = currCond.dst - if (debug) { - println("!!") - } - // && in the original code //* L0: EDGES: [goto L1;, IF ((p0 >= p1)) goto L2;]. SRC_EDGES: 0 //* L1: NOP(empty stm) EDGES: [goto L3;, IF ((p0 < 0)) goto L2;]. SRC_EDGES: 1 @@ -163,9 +164,9 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo prev.edgeTo(currCond.dst, prevCond.condOrTrue bor currCond.condOrTrue) prev.next = currNext //println("-------") - explore(currCond.dst) - explore(currNext) - return + queue(currCond.dst) + queue(currNext) + continue } // || in the original code //* L0: EDGES: [goto L1;, IF ((p0 < p1)) goto L2;]. SRC_EDGES: 0 @@ -175,9 +176,9 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo prev.edgeTo(currNext, prevCond.condOrTrue bor currCond.condOrTrue.not()) prev.next = currCond.dst //println("-------") - explore(currCond.dst) - explore(currNext) - return + queue(currCond.dst) + queue(currNext) + continue } // None else { @@ -186,11 +187,10 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } } - for (edge in node.dstEdges.toList()) { - explore(edge.dst) + for (edge in node.dstEdges) { + queue(edge.dst) } } - explore(entry) return entry } @@ -200,21 +200,24 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo */ private fun prepare(entry: Node): Prepare { val exit = node(listOf()) - val explored = LinkedHashSet() + val processed = LinkedHashSet() val result = LinkedHashSet() - fun explore(node: Node): Unit { - if (node in explored) return - explored += node + + val queue = Queue() + queue.queue(entry) + while (queue.hasMore) { + val node = queue.dequeue() + if (node in processed) continue + processed += node + result += node if (node.next == null) { edge(node, exit) } - if (node.next != null) explore(node.next!!) - for (edge in node.dstEdges) explore(edge.dst) + if (node.next != null) queue(node.next!!) + for (edge in node.dstEdges) queue(edge.dst) } - explore(entry) result += exit - return Prepare(result.toList(), entry, exit) } diff --git a/jtransc-utils/src/com/jtransc/ds/collectionutils.kt b/jtransc-utils/src/com/jtransc/ds/collectionutils.kt index e84d07cf..85bd4752 100644 --- a/jtransc-utils/src/com/jtransc/ds/collectionutils.kt +++ b/jtransc-utils/src/com/jtransc/ds/collectionutils.kt @@ -52,6 +52,8 @@ class Queue() : Iterable { val size: Int get() = data.size //val hasMore get() = size > 0 + operator fun invoke(value: T): T = queue(value) + fun queue(value: T): T { data.addFirst(value) return value From 0a1611f12f8b661e28b6a5ff2a4a7983b65f82b3 Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 11:45:12 +0100 Subject: [PATCH 40/60] Optimize IF_ELSE with return on both sides --- jtransc-core/src/com/jtransc/ast/ast_body.kt | 11 +++++++++++ .../src/com/jtransc/graph/Relooper.kt | 19 ++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/ast_body.kt b/jtransc-core/src/com/jtransc/ast/ast_body.kt index 5d36f9d4..8fb0ed6e 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_body.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_body.kt @@ -277,10 +277,21 @@ fun AstStm.isEmpty() = when (this) { else -> false } +fun AstStm.stripNopsAndLines(): AstStm { + if (this is AstStm.STMS) { + val st = this.stms.filter { !it.value.isNopOrLine() } + return if (st.size == 1) st[0].value else st.unboxed.stms + } + return this +} + fun AstStm.isNop(): Boolean = this is AstStm.NOP +fun AstStm.isLine(): Boolean = this is AstStm.LINE +fun AstStm.isNopOrLine(): Boolean = isNop() || isLine() fun AstStm.isContinue(name: String): Boolean = this is AstStm.CONTINUE && this.name == name fun AstStm.isBreak(name: String): Boolean = this is AstStm.BREAK && this.name == name +fun AstStm.isReturnValue(): Boolean = this is AstStm.RETURN fun AstStm.isSingleStm(): Boolean { if (this is AstStm.STMS) return (this.stms.count() == 1) && this.stms.last().value.isSingleStm() diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 0d55e9a5..cb14358a 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -436,8 +436,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo return out.stmsWithoutNops } - val Iterable.stmsWithoutNopsList: List get() = this.filter { it !is AstStm.NOP } - val Iterable.stmsWithoutNops: AstStm get() = this.stmsWithoutNopsList.stm() + val Iterable.stmsWithoutNopsAndLineList: List get() = this.filter { !it.isNopOrLine() } + val Iterable.stmsWithoutNops: AstStm get() = this.stmsWithoutNopsAndLineList.stm() fun renderNoLoops(g: StrongComponentGraph, out: ArrayList, node: Node, exit: Node?, ctx: RenderContext, level: Int): Node? { val indent = INDENTS[level] @@ -527,7 +527,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo endOfIfEdge.cond!!, renderComponents(g, endOfIfNode, common, ctx, level = level + 1), renderComponents(g, ifBody, common, ctx, level = level + 1) - ) + ).optimizeIfElse() } } @@ -537,7 +537,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo endOfIfEdge.cond!!, renderComponents(g, endOfIfNode, common, ctx, level = level + 1), renderComponents(g, ifBody, common, ctx, level = level + 1) - ) + ).optimizeIfElse() } return common @@ -602,7 +602,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo @Suppress("FoldInitializerAndIfToElvis") if (bodyValue !is AstStm.STMS) return this - val stms = bodyValue.stms.unboxed.stmsWithoutNopsList.map { it.box } + val stms = bodyValue.stms.unboxed.stmsWithoutNopsAndLineList.map { it.box } val last = stms.lastOrNull() if (last != null) { @@ -635,6 +635,15 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo // var n = 0; do { if (n++ < 10) continue; break; } while (true); console.log(n); // WORKING: 11 // var n = 0; do { } while (n++ < 10); console.log(n); // WORKING: 11 } + + fun AstStm.IF_ELSE.optimizeIfElse(): AstStm { + val st = this.strue.value.stripNopsAndLines() + val sf = this.sfalse.value.stripNopsAndLines() + if ((st is AstStm.RETURN) && (sf is AstStm.RETURN)) { + return AstStm.RETURN(AstExpr.TERNARY(this.cond.value, st.retval.value, sf.retval.value, types)) + } + return this + } } class RelooperException(message: String) : RuntimeException(message) \ No newline at end of file From d1bf5d40c7dcca296011a98f72e1f3d2308a247a Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 11:58:35 +0100 Subject: [PATCH 41/60] Optimize some IF_ELSE to act as guard clauses --- benchmark/build.gradle | 4 ++-- jtransc-core/src/com/jtransc/ast/ast_body.kt | 8 ++++--- .../src/com/jtransc/graph/Relooper.kt | 19 +++++++++++++-- .../src/relooper/RelooperTest.java | 23 +++++++++++++++++++ 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/benchmark/build.gradle b/benchmark/build.gradle index 578d7baf..13605773 100644 --- a/benchmark/build.gradle +++ b/benchmark/build.gradle @@ -30,8 +30,8 @@ dependencies { } jtransc { - //minimizeNames = true - minimizeNames = false + minimizeNames = true + //minimizeNames = false treeshaking = true relooper = true diff --git a/jtransc-core/src/com/jtransc/ast/ast_body.kt b/jtransc-core/src/com/jtransc/ast/ast_body.kt index 8fb0ed6e..1283392c 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_body.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_body.kt @@ -277,10 +277,12 @@ fun AstStm.isEmpty() = when (this) { else -> false } -fun AstStm.stripNopsAndLines(): AstStm { +fun AstStm.normalizeWithoutNopsOrLines(): AstStm { if (this is AstStm.STMS) { - val st = this.stms.filter { !it.value.isNopOrLine() } - return if (st.size == 1) st[0].value else st.unboxed.stms + val st = this.stms.unboxed + .flatMap { if (it is AstStm.STMS) it.stmsUnboxed else listOf(it) } + .filter { !it.isNopOrLine() } + return if (st.size == 1) st[0] else st.stms } return this } diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index cb14358a..8c4dc844 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -637,11 +637,26 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } fun AstStm.IF_ELSE.optimizeIfElse(): AstStm { - val st = this.strue.value.stripNopsAndLines() - val sf = this.sfalse.value.stripNopsAndLines() + val cond = this.cond + val st = this.strue.value.normalizeWithoutNopsOrLines() + val sf = this.sfalse.value.normalizeWithoutNopsOrLines() if ((st is AstStm.RETURN) && (sf is AstStm.RETURN)) { return AstStm.RETURN(AstExpr.TERNARY(this.cond.value, st.retval.value, sf.retval.value, types)) } + // Guard clause! + if ((st is AstStm.RETURN)) { + return listOf( + AstStm.IF(cond.value, st), + sf + ).stms + } + // Guard clause! + if ((sf is AstStm.RETURN)) { + return listOf( + AstStm.IF(cond.value.not(), sf), + st + ).stms + } return this } } diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index db20d9fb..f58c393a 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -3,6 +3,7 @@ import com.jtransc.annotation.JTranscRelooper; import com.jtransc.io.JTranscConsole; +import java.nio.ShortBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -26,6 +27,7 @@ static public void main(String[] args) { simpleWhile(0, 5); simpleFor(2, 5); JTranscConsole.log(Arrays.asList(split("hello world test", ' ', 2))); + bufferEquals(ShortBuffer.allocate(1), ShortBuffer.allocate(1)); } @JTranscRelooper @@ -109,4 +111,25 @@ static private String[] split(String str, char ch, int limit) { if (start < str.length()) out.add(str.substring(start)); return out.toArray(new String[out.size()]); } + @JTranscRelooper(debug = true) + static private boolean bufferEquals(ShortBuffer t, Object other) { + if (!(other instanceof ShortBuffer)) { + return false; + } + ShortBuffer otherBuffer = (ShortBuffer) other; + + if (t.remaining() != otherBuffer.remaining()) { + return false; + } + + int myPosition = t.position(); + int otherPosition = otherBuffer.position(); + boolean equalSoFar = true; + //while (equalSoFar && (myPosition < t.limit())) { + // equalSoFar = t.get(myPosition++) == otherBuffer.get(otherPosition++); + //} + + return equalSoFar; + } + } From 7dd53ef8c2fa59fff754e04c6e4a6c8472f0ab8a Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 15:27:56 +0100 Subject: [PATCH 42/60] Kotlin 1.2.20 --- gradle.properties | 2 +- jtransc-maven-plugin/example/pom.xml | 2 +- jtransc-maven-plugin/resources/META-INF/maven/plugin.xml | 8 ++++---- jtransc-utils/src/com/jtransc/KotlinVersion.kt | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/gradle.properties b/gradle.properties index 02b930a6..af8df78f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ jtranscVersion=0.7.0-SNAPSHOT -kotlinVersion=1.2.10 +kotlinVersion=1.2.20 file.encoding=UTF-8 kotlin.incremental=true org.gradle.daemon=true diff --git a/jtransc-maven-plugin/example/pom.xml b/jtransc-maven-plugin/example/pom.xml index f362df2f..cf1b6541 100644 --- a/jtransc-maven-plugin/example/pom.xml +++ b/jtransc-maven-plugin/example/pom.xml @@ -29,7 +29,7 @@ org.jetbrains.kotlin kotlin-stdlib - 1.2.10 + 1.2.20 1.2.10 + 1.2.20 org.jetbrains.kotlin kotlin-runtime jar - 1.2.10 + 1.2.20 com.jtransc @@ -536,13 +536,13 @@ org.eclipse.aether aether-api jar - 1.2.10 + 1.2.20 org.eclipse.aether aether-util jar - 1.2.10 + 1.2.20 org.apache.maven diff --git a/jtransc-utils/src/com/jtransc/KotlinVersion.kt b/jtransc-utils/src/com/jtransc/KotlinVersion.kt index 9b1bb932..43d2f639 100644 --- a/jtransc-utils/src/com/jtransc/KotlinVersion.kt +++ b/jtransc-utils/src/com/jtransc/KotlinVersion.kt @@ -16,4 +16,4 @@ package com.jtransc -val KotlinVersion = "1.2.10" \ No newline at end of file +val KotlinVersion = "1.2.20" \ No newline at end of file From f39d87c4fb70321a1edad06652e884427f24295f Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 16:22:37 +0100 Subject: [PATCH 43/60] Fixed a problematic bug related to indices not matching in Relooper --- .../src/com/jtransc/graph/Relooper.kt | 33 +++++++++++-------- .../src/com/jtransc/graph/StrongComponent.kt | 14 +++++++- .../src/relooper/RelooperTest.java | 19 +++++++++-- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 8c4dc844..a6daca39 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -284,7 +284,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo trace { "# GRAPHVIZ END" } } //println("Relooping '$name'...") - val result = renderComponents(g.tarjanStronglyConnectedComponentsAlgorithm(), gresult.entry, gresult.exit) + val result = renderComponents(null, g.tarjanStronglyConnectedComponentsAlgorithm(), gresult.entry, gresult.exit) //println("Relooping '$name'...OK") return result } @@ -319,6 +319,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } // @TODO: Optimize performance! + // @TODO: We should use the parent Strong Component to determine reachable nodes. fun findCommonSuccessorNotRendered(a: Node, b: Node, exit: Node?): Node? { //val checkRendered = true val checkRendered = false @@ -351,7 +352,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo fun AstStm.dump() = this.dump(AstTypes(com.jtransc.gen.TargetName("js"))) } - fun renderComponents(g: StrongComponentGraph, entry: Node, exit: Node?, ctx: RenderContext = RenderContext(g.graph), level: Int = 0): AstStm { + fun renderComponents(pg: StrongComponentGraph? = null, g: StrongComponentGraph, entry: Node, exit: Node?, ctx: RenderContext = RenderContext(g.graph), level: Int = 0): AstStm { if (level > 5) { //throw RelooperException("Too much nesting levels!") invalidOp("ERROR When Relooping $name (TOO MUCH NESTING LEVELS)") @@ -362,7 +363,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo var node: Node? = entry val locallyExplored = LinkedHashSet() - trace { "$indent- renderComponents: start: L${entry.index}, end: L${exit?.index}" } + trace { "$indent- renderComponents: start: L${entry.index}, end: L${exit?.index}, strong=${g.components.size}" } fun List.toLString() = this.map { "L${it.index}" }.toString() @@ -396,6 +397,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val entryNode = node val exitNode = outsNotInContext.first() + trace { "$indent- LOOP --> entry: ${entryNode.name}, exit: ${exitNode.name}" } + val loopName = ctx.allocName() //trace { "$indent:: ${entryNode.index} - ${exitNode.index}" } @@ -413,11 +416,15 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo if (isSingleNodeLoop) { trace { "$indent- render single node: renderNoLoops" } val out2 = arrayListOf() - renderNoLoops(g, out2, node, exitNode, ctx, level) + renderNoLoops(pg, g, out2, node, exitNode, ctx, level) out2.stmsWithoutNops } else { trace { "$indent- render multi node: renderComponents (${entryNode.index} - ${exitNode.index})" } - renderComponents(component.split(entryNode, exitNode), entryNode, exitNode, ctx, level = level + 1) + val splitComponent = component.split(entryNode, exitNode) + if (splitComponent == g) { + invalidOp("Couldn't split strong component for some reason") + } + renderComponents(g, splitComponent, entryNode, exitNode, ctx, level = level + 1) } ).optimizeWhile() @@ -428,7 +435,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } // Not a loop else { - node = renderNoLoops(g, out, node, exit, ctx, level = level) + node = renderNoLoops(pg, g, out, node, exit, ctx, level = level) } if (node == prevNode) invalidOp("Infinite loop detected") @@ -439,7 +446,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val Iterable.stmsWithoutNopsAndLineList: List get() = this.filter { !it.isNopOrLine() } val Iterable.stmsWithoutNops: AstStm get() = this.stmsWithoutNopsAndLineList.stm() - fun renderNoLoops(g: StrongComponentGraph, out: ArrayList, node: Node, exit: Node?, ctx: RenderContext, level: Int): Node? { + fun renderNoLoops(pg: StrongComponentGraph?, g: StrongComponentGraph, out: ArrayList, node: Node, exit: Node?, ctx: RenderContext, level: Int): Node? { val indent = INDENTS[level] trace { "$indent- renderNoLoops: Detected no loop : $node" } out += node.body.stmsWithoutNops @@ -503,7 +510,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo // IF endOfIfNode -> out += AstStm.IF( endOfIfEdge.cond!!.not(), // @TODO: Negate a float comparison problem with NaNs - renderComponents(g, ifBody, endOfIfNode, ctx, level = level + 1) + renderComponents(pg, g, ifBody, endOfIfNode, ctx, level = level + 1) ) // IF null -> { @@ -525,8 +532,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo out += AstStm.IF_ELSE( endOfIfEdge.cond!!, - renderComponents(g, endOfIfNode, common, ctx, level = level + 1), - renderComponents(g, ifBody, common, ctx, level = level + 1) + renderComponents(pg, g, endOfIfNode, common, ctx, level = level + 1), + renderComponents(pg, g, ifBody, common, ctx, level = level + 1) ).optimizeIfElse() } } @@ -535,8 +542,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo // IF+ELSE else -> out += AstStm.IF_ELSE( endOfIfEdge.cond!!, - renderComponents(g, endOfIfNode, common, ctx, level = level + 1), - renderComponents(g, ifBody, common, ctx, level = level + 1) + renderComponents(pg, g, endOfIfNode, common, ctx, level = level + 1), + renderComponents(pg, g, ifBody, common, ctx, level = level + 1) ).optimizeIfElse() } return common @@ -550,7 +557,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo fun StrongComponent.split(entry: Node, exit: Node): StrongComponentGraph { val parent = this - val splitted = parent.graph.tarjanStronglyConnectedComponentsAlgorithm { src, dst -> dst != entry.index } + val splitted = parent.graph.tarjanStronglyConnectedComponentsAlgorithm { src, dst -> dst != entry } return splitted } diff --git a/jtransc-core/src/com/jtransc/graph/StrongComponent.kt b/jtransc-core/src/com/jtransc/graph/StrongComponent.kt index 505ca536..ab0cbb59 100644 --- a/jtransc-core/src/com/jtransc/graph/StrongComponent.kt +++ b/jtransc-core/src/com/jtransc/graph/StrongComponent.kt @@ -7,7 +7,10 @@ import java.util.* // V = Vertices (Nodes) // E = Edges (E) //fun Digraph.tarjanStronglyConnectedComponentsAlgorithm(filterNode: (node: Int) -> Boolean = { true }, filterEdge: (src: Int, dst: Int) -> Boolean = { _, _ -> true }) = StrongComponentGraph(this, TarjanStronglyConnectedComponentsAlgorithm(this, filterNode, filterEdge).calculate()) -fun Digraph.tarjanStronglyConnectedComponentsAlgorithm(filterEdge: (src: Int, dst: Int) -> Boolean = { _, _ -> true }) = StrongComponentGraph(this, TarjanStronglyConnectedComponentsAlgorithm(this, filterEdge).calculate()) +fun Digraph.tarjanStronglyConnectedComponentsAlgorithm(filterEdge: (src: T, dst: T) -> Boolean = { _, _ -> true }): StrongComponentGraph { + val g = this + return StrongComponentGraph(this, TarjanStronglyConnectedComponentsAlgorithm(this, { srcIndex, dstIndex -> filterEdge(g.getNode(srcIndex), g.getNode(dstIndex)) }).calculate()) +} // A strong component list is a disjoint set : https://en.wikipedia.org/wiki/Disjoint-set_data_structure class StrongComponent(val scgraph: StrongComponentGraph, val indices: LinkedHashSet) { @@ -19,6 +22,10 @@ class StrongComponent(val scgraph: StrongComponentGraph, val indices: Link val size get() = nodes.size operator fun contains(node: T) = node in nodesSet override fun toString() = graph.toNodes(indices).toString() + + override fun equals(other: Any?): Boolean { + return (other is StrongComponent<*>) && nodes == other.nodes + } } data class StrongComponentEdge(val scgraph: StrongComponentGraph, val id: Int, val fromNode:Int, val fromSC:StrongComponent, val toNode:Int, val toSC:StrongComponent) { @@ -43,6 +50,11 @@ class StrongComponentGraph(val graph: Digraph, componentsData: List) && components == other.components + } + + private fun addEdge(edge: StrongComponentEdge) { val fromSCIndex = nodeIndices[edge.fromSC]!! val toSCIndex = nodeIndices[edge.toSC]!! diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index f58c393a..548ec847 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -28,6 +28,8 @@ static public void main(String[] args) { simpleFor(2, 5); JTranscConsole.log(Arrays.asList(split("hello world test", ' ', 2))); bufferEquals(ShortBuffer.allocate(1), ShortBuffer.allocate(1)); + + JTranscConsole.log(demo(true, true, false)); } @JTranscRelooper @@ -111,6 +113,7 @@ static private String[] split(String str, char ch, int limit) { if (start < str.length()) out.add(str.substring(start)); return out.toArray(new String[out.size()]); } + @JTranscRelooper(debug = true) static private boolean bufferEquals(ShortBuffer t, Object other) { if (!(other instanceof ShortBuffer)) { @@ -125,11 +128,21 @@ static private boolean bufferEquals(ShortBuffer t, Object other) { int myPosition = t.position(); int otherPosition = otherBuffer.position(); boolean equalSoFar = true; - //while (equalSoFar && (myPosition < t.limit())) { - // equalSoFar = t.get(myPosition++) == otherBuffer.get(otherPosition++); - //} + while (equalSoFar && (myPosition < t.limit())) { + equalSoFar = t.get(myPosition++) == otherBuffer.get(otherPosition++); + } return equalSoFar; } + @JTranscRelooper + static private boolean demo(boolean a, boolean b, boolean c) { + boolean result = true; + while (a && b != c) { + a = !b; + result = ((a ^ c) == b); + } + + return result; + } } From 3bd4e7ac79d0f93cdefa00567ad36e9c555c539e Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 16:28:00 +0100 Subject: [PATCH 44/60] org.gradle.caching=true --- gradle.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gradle.properties b/gradle.properties index af8df78f..a9d70cfd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,3 +5,5 @@ kotlin.incremental=true org.gradle.daemon=true org.gradle.daemon.idleTimeout=3600000 org.gradle.jvmargs=-Xmx512m -XX:MaxPermSize=128m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +kotlin.incremental.usePreciseJavaTracking=true +org.gradle.caching=true From e7627df90d3b7bc255cc192db365fb61dc5babd5 Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 16:28:17 +0100 Subject: [PATCH 45/60] Throw statements can be also guard clauses --- jtransc-core/src/com/jtransc/graph/Relooper.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index a6daca39..70fe45db 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -651,14 +651,14 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo return AstStm.RETURN(AstExpr.TERNARY(this.cond.value, st.retval.value, sf.retval.value, types)) } // Guard clause! - if ((st is AstStm.RETURN)) { + if ((st is AstStm.RETURN) || (st is AstStm.THROW)) { return listOf( AstStm.IF(cond.value, st), sf ).stms } // Guard clause! - if ((sf is AstStm.RETURN)) { + if ((sf is AstStm.RETURN) || (sf is AstStm.THROW)) { return listOf( AstStm.IF(cond.value.not(), sf), st From 8e80373327b2ff2658da69d6f99d970c97e3e7d8 Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 16:42:25 +0100 Subject: [PATCH 46/60] Remove exit node cycle --- .../src/com/jtransc/graph/Relooper.kt | 11 ++++++++--- .../src/relooper/RelooperTest.java | 19 ++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 70fe45db..147696e4 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -42,6 +42,7 @@ import kotlin.collections.LinkedHashSet */ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boolean = false) { class Node(val types: AstTypes, val index: Int, val body: List) { + var tag = "" val name = "L$index" //var next: Node? = null val srcEdges = arrayListOf() @@ -201,6 +202,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo private fun prepare(entry: Node): Prepare { val exit = node(listOf()) val processed = LinkedHashSet() + processed += exit val result = LinkedHashSet() val queue = Queue() @@ -212,12 +214,13 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo result += node if (node.next == null) { - edge(node, exit) + node.edgeTo(exit) } if (node.next != null) queue(node.next!!) for (edge in node.dstEdges) queue(edge.dst) } result += exit + exit.tag = "exit" return Prepare(result.toList(), entry, exit) } @@ -231,6 +234,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo .combineBooleanOpsEdges() val gresult = prepare(entry) + val g = graphList(gresult.nodes.map { it to it.possibleNextNodes }) if (debug) { trace { "Rendering $name" } @@ -265,7 +269,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo trace { "digraph G {" } for (n in g.nodes) { val label = n.body.dumpCollapse(types).toString() - trace { "L${n.index} [label = ${label.quote()}]" } + val tag = n.tag + trace { "L${n.index} [label = ${"$label$tag".quote()}]" } } for (n in g.nodes) { if (n.dstEdges.size != 0) { @@ -363,7 +368,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo var node: Node? = entry val locallyExplored = LinkedHashSet() - trace { "$indent- renderComponents: start: L${entry.index}, end: L${exit?.index}, strong=${g.components.size}" } + trace { "$indent- renderComponents: start: L${entry.index}, end: L${exit?.index}, parentStrong=${pg?.components?.map { it.nodes.size }}, strong=${g.components.map { it.nodes.size }}" } fun List.toLString() = this.map { "L${it.index}" }.toString() diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index 548ec847..c8331949 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -30,6 +30,18 @@ static public void main(String[] args) { bufferEquals(ShortBuffer.allocate(1), ShortBuffer.allocate(1)); JTranscConsole.log(demo(true, true, false)); + + JTranscConsole.log(isDigit('0')); + JTranscConsole.log(isDigit('5')); + JTranscConsole.log(isDigit('9')); + JTranscConsole.log(isDigit('a')); + JTranscConsole.log(isDigit('f')); + JTranscConsole.log(isDigit('A')); + JTranscConsole.log(isDigit('E')); + JTranscConsole.log(isDigit('-')); + JTranscConsole.log(isDigit('g')); + JTranscConsole.log(isDigit('G')); + JTranscConsole.log(isDigit('z')); } @JTranscRelooper @@ -114,7 +126,7 @@ static private String[] split(String str, char ch, int limit) { return out.toArray(new String[out.size()]); } - @JTranscRelooper(debug = true) + @JTranscRelooper static private boolean bufferEquals(ShortBuffer t, Object other) { if (!(other instanceof ShortBuffer)) { return false; @@ -145,4 +157,9 @@ static private boolean demo(boolean a, boolean b, boolean c) { return result; } + + @JTranscRelooper(debug = true) + static public boolean isDigit(char c) { + return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); + } } From 29f92a87b3e0fd26813832c377029c91233d5c5c Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 17:17:46 +0100 Subject: [PATCH 47/60] Detect some ifs leading to the exit point (return and throws) --- .../src/com/jtransc/graph/Relooper.kt | 47 +++++++++++++++++-- .../src/relooper/RelooperTest.java | 44 ++++++++++++++++- 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 147696e4..df2ed145 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -42,6 +42,7 @@ import kotlin.collections.LinkedHashSet */ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boolean = false) { class Node(val types: AstTypes, val index: Int, val body: List) { + var exitNode = false var tag = "" val name = "L$index" //var next: Node? = null @@ -83,6 +84,22 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo override fun toString(): String = if (cond != null) "IF (${cond.dump(types)}) goto L${dst.index};" else "goto L${dst.index};" } + fun Node.locateExitNode(): Node { + val processed = hashSetOf() + val queue = Queue() + queue(this) + while (queue.hasMore) { + val node = queue.dequeue() + if (node in processed) continue + processed += node + if (node.dstEdges.isEmpty()) { + return node + } + for (e in node.dstEdges) queue.queue(e.dst) + } + invalidOp("Can't find exit node") + } + var lastIndex = 0 fun node(body: List): Node = Node(types, lastIndex, body.normalize(lastIndex)).apply { lastIndex++ } fun node(body: AstStm): Node = Node(types, lastIndex, listOf(body).normalize(lastIndex)).apply { lastIndex++ } @@ -202,6 +219,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo private fun prepare(entry: Node): Prepare { val exit = node(listOf()) val processed = LinkedHashSet() + exit.exitNode = true processed += exit val result = LinkedHashSet() @@ -214,6 +232,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo result += node if (node.next == null) { + node.exitNode = true node.edgeTo(exit) } if (node.next != null) queue(node.next!!) @@ -358,7 +377,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } fun renderComponents(pg: StrongComponentGraph? = null, g: StrongComponentGraph, entry: Node, exit: Node?, ctx: RenderContext = RenderContext(g.graph), level: Int = 0): AstStm { - if (level > 5) { + if (level > 6) { //throw RelooperException("Too much nesting levels!") invalidOp("ERROR When Relooping $name (TOO MUCH NESTING LEVELS)") } @@ -392,11 +411,16 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo if (isMultiNodeLoop || isSingleNodeLoop) { trace { "$indent- LOOP csize=${component.size} : $node" } val outs = component.getExternalOutputsNodes() - val outsNotInContext = outs.filter { it !in ctx.loopStarts && it !in ctx.loopEnds } + var outsNotInContext = outs.filter { it !in ctx.loopStarts && it !in ctx.loopEnds } + val outsNotInContext2 = outsNotInContext.filter { !it.exitNode } //val outsNotInContext = outs.filter { it !in ctx.loopStarts } if (outsNotInContext.size != 1) { - trace { "$indent- ASSERTION FAILED! outsNotInContext.size != 1 (${outsNotInContext.size}) : $node" } - invalidOp("ERROR When Relooping '$name' MULTIPLE EXITS :: NODES${component.nodes.toLString()}, EXITS:${outsNotInContext.toLString()}") + if (outsNotInContext2.isEmpty()) { + outsNotInContext = listOf(node.locateExitNode()) + } else { + trace { "$indent- ASSERTION FAILED! outsNotInContext.size != 1 (${outsNotInContext.size}) : $node" } + invalidOp("ERROR When Relooping '$name' MULTIPLE EXITS :: NODES${component.nodes.toLString()}, EXITS:${outsNotInContext.toLString()}") + } } val entryNode = node @@ -484,6 +508,21 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } } + val firstCondEdge = node.dstEdgesButNext.firstOrNull() + val firstCondNode = firstCondEdge?.dst + + // Guard clause (either return or throw) + if (node.dstEdgesButNext.size == 1 && node.next != null && node.next!!.exitNode) { + out += AstStm.IF(firstCondEdge!!.cond!!.not(), node.next!!.body.stm()) + return node.dstEdgesButNext.first().dst + } + + // Guard clause (either return or throw) + if (node.dstEdgesButNext.size == 1 && node.next != null && firstCondNode?.exitNode == true) { + out += AstStm.IF(firstCondEdge.cond!!, firstCondNode.body.stm()) + return node.next + } + var ifsAdded = 0 for (e in node.dstEdgesButNext) { val breakOrContinue = getNodeContinueOrBreak(e.dst) ?: break diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index c8331949..0ccb885c 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -1,8 +1,10 @@ package relooper; import com.jtransc.annotation.JTranscRelooper; +import com.jtransc.annotation.JTranscSync; import com.jtransc.io.JTranscConsole; +import java.nio.DoubleBuffer; import java.nio.ShortBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -42,6 +44,10 @@ static public void main(String[] args) { JTranscConsole.log(isDigit('g')); JTranscConsole.log(isDigit('G')); JTranscConsole.log(isDigit('z')); + + //JTranscConsole.log(dbCompareTo(DoubleBuffer.allocate(1), DoubleBuffer.allocate(1))); + JTranscConsole.log(sequals("a", "a")); + JTranscConsole.log(sequals("a", "b")); } @JTranscRelooper @@ -158,8 +164,44 @@ static private boolean demo(boolean a, boolean b, boolean c) { return result; } - @JTranscRelooper(debug = true) + @JTranscRelooper static public boolean isDigit(char c) { return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); } + + //@JTranscRelooper + //static public int dbCompareTo(DoubleBuffer base, DoubleBuffer otherBuffer) { + // int compareRemaining = (base.remaining() < otherBuffer.remaining()) ? base.remaining() + // : otherBuffer.remaining(); + // int thisPos = base.position(); + // int otherPos = otherBuffer.position(); + // double thisDouble, otherDouble; + // while (compareRemaining > 0) { + // thisDouble = base.get(thisPos); + // otherDouble = otherBuffer.get(otherPos); + // // checks for double and NaN inequality + // if ((thisDouble != otherDouble) + // && ((thisDouble == thisDouble) || (otherDouble == otherDouble))) { + // return thisDouble < otherDouble ? -1 : 1; + // } + // thisPos++; + // otherPos++; + // compareRemaining--; + // } + // return base.remaining() - otherBuffer.remaining(); + //} + + @JTranscRelooper(debug = true) + static private boolean sequals(String l, String r) { + //noinspection StringEquality + if (l == r) return true; + if (l == null) return false; + if (r == null) return false; + if (l.length() != r.length()) return false; + if (l.hashCode() != r.hashCode()) return false; + final int len = l.length(); + for (int n = 0; n < len; n++) if (l.charAt(n) != r.charAt(n)) return false; + return true; + } + } From 77077353be53ef88fe6772fd820c4eee85863ca2 Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 17:44:08 +0100 Subject: [PATCH 48/60] Prepare to support switches in relooper --- .../ast/feature/method/GotosFeature.kt | 36 +++++++++++-------- .../src/relooper/RelooperTest.java | 27 ++++++++++++-- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index 95bf70f5..a1fe0d68 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -40,18 +40,22 @@ class GotosFeature : AstMethodFeature() { } } - private fun removeRelooper(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody? { - class BasicBlock(var index: Int) { - var node: Relooper.Node? = null - val stms = arrayListOf() - var next: BasicBlock? = null - var condExpr: AstExpr? = null - var ifNext: BasicBlock? = null + class BasicBlock(var index: Int) { + var node: Relooper.Node? = null + val stms = arrayListOf() + var next: BasicBlock? = null + val edges = arrayListOf() + //var condExpr: AstExpr? = null + //var ifNext: BasicBlock? = null - //val targets by lazy { (listOf(next, ifNext) + (switchNext?.values ?: listOf())).filterNotNull() } + //val targets by lazy { (listOf(next, ifNext) + (switchNext?.values ?: listOf())).filterNotNull() } - override fun toString(): String = "BasicBlock($index)" - } + override fun toString(): String = "BasicBlock($index)" + } + + class BBEdge(val cond: AstExpr, val next: BasicBlock) + + private fun removeRelooper(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody? { val entryStm = body.stm as? AstStm.STMS ?: return null // Not relooping single statements @@ -88,12 +92,13 @@ class GotosFeature : AstMethodFeature() { is AstStm.IF_GOTO -> { val prev = current current = createBB() - prev.condExpr = stm.cond.value - prev.ifNext = getBBForLabel(stm.label) + prev.edges += BBEdge(stm.cond.value, getBBForLabel(stm.label)) prev.next = current } is AstStm.SWITCH_GOTO -> { - // Not handled switches yet! + //val prev = current + //current = createBB() + return null } is AstStm.RETURN, is AstStm.THROW, is AstStm.RETHROW -> { @@ -117,9 +122,10 @@ class GotosFeature : AstMethodFeature() { } for (n in bblist) { val next = n.next - val ifNext = n.ifNext if (next != null) relooper.edge(n.node!!, next.node!!) - if (n.condExpr != null && ifNext != null) relooper.edge(n.node!!, ifNext.node!!, n.condExpr!!) + for (edge in n.edges) { + relooper.edge(n.node!!, edge.next.node!!, edge.cond) + } } try { diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index 0ccb885c..f1b2b41b 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -48,6 +48,12 @@ static public void main(String[] args) { //JTranscConsole.log(dbCompareTo(DoubleBuffer.allocate(1), DoubleBuffer.allocate(1))); JTranscConsole.log(sequals("a", "a")); JTranscConsole.log(sequals("a", "b")); + + JTranscConsole.log(myswitch(0)); + JTranscConsole.log(myswitch(1)); + JTranscConsole.log(myswitch(2)); + JTranscConsole.log(myswitch(3)); + JTranscConsole.log(myswitch(4)); } @JTranscRelooper @@ -191,7 +197,7 @@ static public boolean isDigit(char c) { // return base.remaining() - otherBuffer.remaining(); //} - @JTranscRelooper(debug = true) + @JTranscRelooper static private boolean sequals(String l, String r) { //noinspection StringEquality if (l == r) return true; @@ -204,4 +210,21 @@ static private boolean sequals(String l, String r) { return true; } -} + @JTranscRelooper(debug = true) + static private boolean myswitch(int a) { + switch (a) { + case 0: + JTranscConsole.log("0"); + break; + case 1: + JTranscConsole.log("1"); + case 2: + JTranscConsole.log("2"); + break; + case 3: + return false; + + } + return true; + } +} \ No newline at end of file From a083765f0ac28813dd0523fe4de7e2e540f0c240 Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 17:53:32 +0100 Subject: [PATCH 49/60] Some more work on supporting switch on relooper --- benchmark/build.gradle | 4 ++-- .../ast/feature/method/GotosFeature.kt | 21 ++++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/benchmark/build.gradle b/benchmark/build.gradle index 13605773..578d7baf 100644 --- a/benchmark/build.gradle +++ b/benchmark/build.gradle @@ -30,8 +30,8 @@ dependencies { } jtransc { - minimizeNames = true - //minimizeNames = false + //minimizeNames = true + minimizeNames = false treeshaking = true relooper = true diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index a1fe0d68..5e869ead 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -76,6 +76,7 @@ class GotosFeature : AstMethodFeature() { val entry = createBB() var current = entry + var switchId = 0 for (stmBox in stms) { val stm = stmBox.value when (stm) { @@ -96,10 +97,24 @@ class GotosFeature : AstMethodFeature() { prev.next = current } is AstStm.SWITCH_GOTO -> { - //val prev = current - //current = createBB() + val prev = current + current = createBB() - return null + val switchLocal = if (stm.subject.value is AstExpr.LOCAL) { + val switchLocal = AstType.INT.local("switch${switchId++}") + current.stms += switchLocal.setTo(stm.subject.value) + switchLocal + } else { + stm.subject.value + } + current.next = getBBForLabel(stm.default) + for ((keys, label) in stm.cases) { + val caseLabel = getBBForLabel(label) + for (key in keys) { + prev.edges += BBEdge(switchLocal eq key.lit, caseLabel) + } + } + prev.next = current } is AstStm.RETURN, is AstStm.THROW, is AstStm.RETHROW -> { current.stms += stm From 4b04237799cebfac47255c42df6abf3ee915539a Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 18:02:37 +0100 Subject: [PATCH 50/60] First switch implementation with ifs --- .../src/com/jtransc/graph/Relooper.kt | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index df2ed145..883da8e2 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -344,7 +344,9 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo // @TODO: Optimize performance! // @TODO: We should use the parent Strong Component to determine reachable nodes. - fun findCommonSuccessorNotRendered(a: Node, b: Node, exit: Node?): Node? { + fun findCommonSuccessorNotRendered(a: Node?, b: Node?, exit: Node?): Node? { + if (a == null) return b + if (b == null) return a //val checkRendered = true val checkRendered = false val aSet = getNodeSuccessorsLinkedSet(a, exit, checkRendered) @@ -369,6 +371,10 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } while (c != 0) return null } + + fun findCommonSuccessorNotRendered(nodes: List, exit: Node?): Node? { + return nodes.reduce { acc, node -> findCommonSuccessorNotRendered(acc!!, node!!, exit) } + } } companion object { @@ -539,7 +545,26 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } if (node.dstEdges.size != 2) { - TODO() + val common = ctx.findCommonSuccessorNotRendered(node.dstEdges.map { it.dst }, exit) + //out += AstStm.SWITCH() + + var base = if (node.next != null) { + renderComponents(pg, g, node.next!!, common, ctx, level = level + 1) + } else { + AstStm.NOP("") + } + + for (e in node.dstEdgesButNext) { + base = AstStm.IF_ELSE( + e.condOrTrue, + renderComponents(pg, g, e.dst, common, ctx, level = level + 1), + base + ) + } + + out += base + + return common } trace { "$indent- Node IF (and else?)" } @@ -547,16 +572,16 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val endOfIfEdge = node.dstEdgesButNext.firstOrNull() ?: invalidOp("Expected conditional!") val endOfIfNode = endOfIfEdge.dst val common = ctx.findCommonSuccessorNotRendered(ifBody, endOfIfNode, exit = exit) - //?: invalidOp("Not found common node for ${ifBody.name} and ${endOfIfNode.name}!") + //?: invalidOp("Not found common node for ${ifBody.name} and ${endOfIfNode.name}!") when (common) { - // IF + // IF endOfIfNode -> out += AstStm.IF( endOfIfEdge.cond!!.not(), // @TODO: Negate a float comparison problem with NaNs renderComponents(pg, g, ifBody, endOfIfNode, ctx, level = level + 1) ) - // IF + // IF null -> { val ifBodyCB = getNodeContinueOrBreak(ifBody) val endOfIfCB = getNodeContinueOrBreak(endOfIfNode) @@ -583,7 +608,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } } - // IF+ELSE + // IF+ELSE else -> out += AstStm.IF_ELSE( endOfIfEdge.cond!!, renderComponents(pg, g, endOfIfNode, common, ctx, level = level + 1), From 9cbd1bb535687c78b4dae1109fd20a565ab6778e Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 18:13:05 +0100 Subject: [PATCH 51/60] Compact else-if chains to reduce indentation --- .../src/com/jtransc/gen/common/CommonGenerator.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt b/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt index 2d58163c..62b2d20b 100644 --- a/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt +++ b/jtransc-core/src/com/jtransc/gen/common/CommonGenerator.kt @@ -1390,7 +1390,16 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { open fun genStmIfElse(stm: AstStm.IF_ELSE) = indent { line("if (${stm.cond.genExpr()})") { line(stm.strue.genStm()) } - line("else") { line(stm.sfalse.genStm()) } + var sfalse: AstStm = stm.sfalse.value + while (true) { + if (sfalse is AstStm.IF_ELSE) { + line("else if (${sfalse.cond.genExpr()})") { line((sfalse as AstStm.IF_ELSE).strue.genStm()) } + sfalse = sfalse.sfalse.value + } else { + line("else") { line(sfalse.genStm()) } + break + } + } } open val allowAssignItself = false From b98197fb888f3c95eecf2e0cfc6f28097f626858 Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 18:46:34 +0100 Subject: [PATCH 52/60] Disable switch in relooper temporarily --- jtransc-core/src/com/jtransc/ast/ast_dump.kt | 3 ++ .../ast/feature/method/GotosFeature.kt | 52 +++++++++++++------ .../src/relooper/RelooperTest.java | 7 +-- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/ast_dump.kt b/jtransc-core/src/com/jtransc/ast/ast_dump.kt index 3be53034..7623e379 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_dump.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_dump.kt @@ -157,6 +157,9 @@ fun dump(types: AstTypes, expr: AstExpr?): String { is AstExpr.INVOKE_DYNAMIC_METHOD -> { "invokeDynamic(${expr.extraArgCount}, ${expr.methodInInterfaceRef}, ${expr.methodToConvertRef})(${expr.startArgs.map { dump(types, it) }.joinToString(", ")})" } + is AstExpr.CONCAT_STRING -> { + "''" + expr.args.map { it.dump(types) }.joinToString { "+" } + } is AstExpr.RAW -> "${expr.content}" else -> noImpl("$expr") } diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index 5e869ead..9b771e1a 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -41,7 +41,8 @@ class GotosFeature : AstMethodFeature() { } class BasicBlock(var index: Int) { - var node: Relooper.Node? = null + lateinit var node: Relooper.Node + var isSwitch = false val stms = arrayListOf() var next: BasicBlock? = null val edges = arrayListOf() @@ -77,29 +78,39 @@ class GotosFeature : AstMethodFeature() { val entry = createBB() var current = entry var switchId = 0 + var prev: BasicBlock? = current for (stmBox in stms) { val stm = stmBox.value + + fun setPrevNextTo(that: BasicBlock?) { + //if (!prev.isSwitch) { + prev?.next = that + //} + } + when (stm) { is AstStm.STM_LABEL -> { - val prev = current current = getBBForLabel(stm.label) - prev.next = current + setPrevNextTo(current) + prev = current } is AstStm.GOTO -> { - val prev = current current = createBB() - prev.next = getBBForLabel(stm.label) + setPrevNextTo(getBBForLabel(stm.label)) + prev = null } is AstStm.IF_GOTO -> { - val prev = current current = createBB() - prev.edges += BBEdge(stm.cond.value, getBBForLabel(stm.label)) - prev.next = current + prev?.edges?.add(BBEdge(stm.cond.value, getBBForLabel(stm.label))) + setPrevNextTo(current) + prev = current } is AstStm.SWITCH_GOTO -> { - val prev = current + return null current = createBB() + //val endNode = createBB() + val switchLocal = if (stm.subject.value is AstExpr.LOCAL) { val switchLocal = AstType.INT.local("switch${switchId++}") current.stms += switchLocal.setTo(stm.subject.value) @@ -107,23 +118,30 @@ class GotosFeature : AstMethodFeature() { } else { stm.subject.value } + current.isSwitch = true current.next = getBBForLabel(stm.default) + //current.next!!.next = endNode for ((keys, label) in stm.cases) { val caseLabel = getBBForLabel(label) + //caseLabel.next = endNode for (key in keys) { - prev.edges += BBEdge(switchLocal eq key.lit, caseLabel) + current.edges += BBEdge(switchLocal eq key.lit, caseLabel) } } - prev.next = current + setPrevNextTo(current) + //prev = endNode + //prev = current + prev = null } is AstStm.RETURN, is AstStm.THROW, is AstStm.RETHROW -> { current.stms += stm - val prev = current current = createBB() - prev.next = null + setPrevNextTo(null) + prev = current } else -> { current.stms += stm + prev = current } } } @@ -137,16 +155,16 @@ class GotosFeature : AstMethodFeature() { } for (n in bblist) { val next = n.next - if (next != null) relooper.edge(n.node!!, next.node!!) + if (next != null) n.node.edgeTo(next.node) for (edge in n.edges) { - relooper.edge(n.node!!, edge.next.node!!, edge.cond) + n.node.edgeTo(edge.next.node, edge.cond) } } try { - val render = relooper.render(bblist[0].node!!) + val render = relooper.render(bblist[0].node) val bodyGotos = if (settings.optimize) { - render?.optimize(body.flags) + render.optimize(body.flags) } else { render } diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index f1b2b41b..88f45be8 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -212,14 +212,15 @@ static private boolean sequals(String l, String r) { @JTranscRelooper(debug = true) static private boolean myswitch(int a) { + JTranscConsole.log("myswitch: " + a); switch (a) { case 0: - JTranscConsole.log("0"); + JTranscConsole.log(0); break; case 1: - JTranscConsole.log("1"); + JTranscConsole.log(1); case 2: - JTranscConsole.log("2"); + JTranscConsole.log(2); break; case 3: return false; From 2e84278da1969ed22be1d3de8ade55ce92dc0f9b Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 19:40:51 +0100 Subject: [PATCH 53/60] Some work on fixing switches with a new/better conversor to basic Stms to Relooper graph --- .../ast/feature/method/GotosFeature.kt | 192 +++++++----------- .../src/com/jtransc/graph/Relooper.kt | 5 +- 2 files changed, 75 insertions(+), 122 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index 9b771e1a..a98c8d43 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -27,7 +27,7 @@ class GotosFeature : AstMethodFeature() { override fun remove(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody { //if (false) { if (method.relooperEnabled ?: settings.relooper) { - //if (method.relooperEnabled ?: false) { + //if (method.relooperEnabled ?: false) { try { return removeRelooper(method, body, settings, types) ?: removeMachineState(body, types) } catch (t: Throwable) { @@ -40,142 +40,92 @@ class GotosFeature : AstMethodFeature() { } } - class BasicBlock(var index: Int) { - lateinit var node: Relooper.Node - var isSwitch = false - val stms = arrayListOf() - var next: BasicBlock? = null - val edges = arrayListOf() - //var condExpr: AstExpr? = null - //var ifNext: BasicBlock? = null - - //val targets by lazy { (listOf(next, ifNext) + (switchNext?.values ?: listOf())).filterNotNull() } - - override fun toString(): String = "BasicBlock($index)" - } - - class BBEdge(val cond: AstExpr, val next: BasicBlock) - private fun removeRelooper(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody? { - val entryStm = body.stm as? AstStm.STMS ?: return null // Not relooping single statements if (body.traps.isNotEmpty()) return null // Not relooping functions with traps by the moment - - val stms = entryStm.stms - val bblist = arrayListOf() - val bbs = hashMapOf() - fun createBB(): BasicBlock { - val bb = BasicBlock(bblist.size) - bblist += bb - return bb - } - - fun getBBForLabel(label: AstLabel): BasicBlock { - return bbs.getOrPut(label) { createBB() } - } - - val entry = createBB() - var current = entry - var switchId = 0 - var prev: BasicBlock? = current - for (stmBox in stms) { - val stm = stmBox.value - - fun setPrevNextTo(that: BasicBlock?) { - //if (!prev.isSwitch) { - prev?.next = that - //} + val stms = entryStm.stmsUnboxed + val labelToIndex = stms.withIndex().filter { it.value is AstStm.STM_LABEL }.map { (it.value as AstStm.STM_LABEL).label to it.index }.toMap() + val relooper = Relooper(types, "$method", method.relooperDebug) + val tswitchLocal = AstType.INT.local("_switchId") + + val nodesByIndex = hashMapOf() + fun render(index: Int): Relooper.Node { + println("$index: ${stms[index]}") + if (index in nodesByIndex) return nodesByIndex[index]!! + val out = arrayListOf() + val node = relooper.node(out).apply { + nodesByIndex[index] = this } - - when (stm) { - is AstStm.STM_LABEL -> { - current = getBBForLabel(stm.label) - setPrevNextTo(current) - prev = current - } - is AstStm.GOTO -> { - current = createBB() - setPrevNextTo(getBBForLabel(stm.label)) - prev = null - } - is AstStm.IF_GOTO -> { - current = createBB() - prev?.edges?.add(BBEdge(stm.cond.value, getBBForLabel(stm.label))) - setPrevNextTo(current) - prev = current - } - is AstStm.SWITCH_GOTO -> { - return null - current = createBB() - - //val endNode = createBB() - - val switchLocal = if (stm.subject.value is AstExpr.LOCAL) { - val switchLocal = AstType.INT.local("switch${switchId++}") - current.stms += switchLocal.setTo(stm.subject.value) - switchLocal - } else { - stm.subject.value + loop@for (i in index until stms.size) { + val stm = stms[i] + nodesByIndex[i] = node + + when (stm) { + is AstStm.LINE -> Unit + is AstStm.NOP -> Unit + is AstStm.STM_LABEL -> { + if (i == index) { + Unit + } else { + nodesByIndex.remove(i) + node.edgeTo(render(i)) + break@loop + } + } + is AstStm.RETURN, is AstStm.THROW -> { + out += stm + break@loop } - current.isSwitch = true - current.next = getBBForLabel(stm.default) - //current.next!!.next = endNode - for ((keys, label) in stm.cases) { - val caseLabel = getBBForLabel(label) - //caseLabel.next = endNode - for (key in keys) { - current.edges += BBEdge(switchLocal eq key.lit, caseLabel) + is AstStm.GOTO -> { + node.edgeTo(render(labelToIndex[stm.label]!!)) + break@loop + } + is AstStm.IF_GOTO -> { + for (n in i until stms.size) { + val stm = stms[n] + if (stm is AstStm.IF_GOTO) { + node.edgeTo(render(labelToIndex[stm.label]!!), stm.cond.value) + } else { + node.edgeTo(render(i + 1)) + break@loop + } } + break@loop } - setPrevNextTo(current) - //prev = endNode - //prev = current - prev = null - } - is AstStm.RETURN, is AstStm.THROW, is AstStm.RETHROW -> { - current.stms += stm - current = createBB() - setPrevNextTo(null) - prev = current - } - else -> { - current.stms += stm - prev = current - } - } - } + is AstStm.SWITCH_GOTO -> { + val switchLocal = if (stm.subject.value is AstExpr.LOCAL) { + out += tswitchLocal.setTo(stm.subject.value) + tswitchLocal + } else { + stm.subject.value + } - val relooper = Relooper(types, "$method", method.relooperDebug) - for (n in bblist) { - n.node = relooper.node(n.stms) - //println("NODE(${n.index}): ${n.stms}") - //if (n.next != null) println(" -> ${n.next}") - //if (n.ifNext != null) println(" -> ${n.ifNext} [${n.condExpr}]") - } - for (n in bblist) { - val next = n.next - if (next != null) n.node.edgeTo(next.node) - for (edge in n.edges) { - n.node.edgeTo(edge.next.node, edge.cond) + node.edgeTo(render(labelToIndex[stm.default]!!)) + for ((keys, label) in stm.cases) { + val branch = render(labelToIndex[label]!!) + for (key in keys) { + node.edgeTo(branch, switchLocal eq key.lit) + } + } + break@loop + } + else -> { + out += stm + } + } } + return node } try { - val render = relooper.render(bblist[0].node) - val bodyGotos = if (settings.optimize) { - render.optimize(body.flags) - } else { - render - } - return body.copy( - stm = bodyGotos ?: return null - ) + val render = relooper.render(render(0)) + val bodyGotos = if (settings.optimize) render.optimize(body.flags) else render + return body.copy(stm = bodyGotos) } catch (e: RelooperException) { - //println("RelooperException: ${e.message}") + println("RelooperException: ${e.message}") return null } - //return AstBody(relooper.render(bblist[0].node!!) ?: return null, body.locals, body.traps) } fun removeMachineState(body: AstBody, types: AstTypes): AstBody { diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 883da8e2..b010f16d 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -101,7 +101,10 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } var lastIndex = 0 - fun node(body: List): Node = Node(types, lastIndex, body.normalize(lastIndex)).apply { lastIndex++ } + //fun node(body: List): Node = Node(types, lastIndex, body.normalize(lastIndex)).apply { lastIndex++ } + //fun node(body: AstStm): Node = Node(types, lastIndex, listOf(body).normalize(lastIndex)).apply { lastIndex++ } + + fun node(body: List): Node = Node(types, lastIndex, body).apply { lastIndex++ } fun node(body: AstStm): Node = Node(types, lastIndex, listOf(body).normalize(lastIndex)).apply { lastIndex++ } fun List.normalize(index: Int): List { From d1b3f3763349e04b5a3e4ed108652d8636604cc5 Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 20:38:19 +0100 Subject: [PATCH 54/60] switch fixes --- .../ast/feature/method/GotosFeature.kt | 202 ++++++++++++++++-- .../src/com/jtransc/graph/Relooper.kt | 43 +++- .../src/relooper/RelooperTest.java | 5 +- 3 files changed, 216 insertions(+), 34 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index a98c8d43..7db42c1c 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -41,6 +41,149 @@ class GotosFeature : AstMethodFeature() { } private fun removeRelooper(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody? { + //return removeRelooper1(method, body, settings, types) + return removeRelooper2(method, body, settings, types) + } + + class BasicBlock(var index: Int) { + lateinit var node: Relooper.Node + var isSwitch = false + val stms = arrayListOf() + var next: BasicBlock? = null + val edges = arrayListOf() + //var condExpr: AstExpr? = null + //var ifNext: BasicBlock? = null + + //val targets by lazy { (listOf(next, ifNext) + (switchNext?.values ?: listOf())).filterNotNull() } + + override fun toString(): String = "BasicBlock($index)" + } + + class BBEdge(val cond: AstExpr, val next: BasicBlock) + + private fun removeRelooper1(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody? { + + val entryStm = body.stm as? AstStm.STMS ?: return null + // Not relooping single statements + if (body.traps.isNotEmpty()) return null // Not relooping functions with traps by the moment + + val stms = entryStm.stms + val bblist = arrayListOf() + val bbs = hashMapOf() + fun createBB(): BasicBlock { + val bb = BasicBlock(bblist.size) + bblist += bb + return bb + } + + fun getBBForLabel(label: AstLabel): BasicBlock { + return bbs.getOrPut(label) { createBB() } + } + + val entry = createBB() + var current = entry + var switchId = 0 + var prev: BasicBlock? = current + for (stmBox in stms) { + val stm = stmBox.value + + fun setPrevNextTo(that: BasicBlock?) { + //if (!prev.isSwitch) { + prev?.next = that + //} + } + + when (stm) { + is AstStm.STM_LABEL -> { + current = getBBForLabel(stm.label) + setPrevNextTo(current) + prev = current + } + is AstStm.GOTO -> { + current = createBB() + setPrevNextTo(getBBForLabel(stm.label)) + prev = null + } + is AstStm.IF_GOTO -> { + current = createBB() + prev?.edges?.add(BBEdge(stm.cond.value, getBBForLabel(stm.label))) + setPrevNextTo(current) + prev = current + } + is AstStm.SWITCH_GOTO -> { + return null + current = createBB() + + //val endNode = createBB() + + val switchLocal = if (stm.subject.value is AstExpr.LOCAL) { + val switchLocal = AstType.INT.local("switch${switchId++}") + current.stms += switchLocal.setTo(stm.subject.value) + switchLocal + } else { + stm.subject.value + } + current.isSwitch = true + current.next = getBBForLabel(stm.default) + //current.next!!.next = endNode + for ((keys, label) in stm.cases) { + val caseLabel = getBBForLabel(label) + //caseLabel.next = endNode + for (key in keys) { + current.edges += BBEdge(switchLocal eq key.lit, caseLabel) + } + } + setPrevNextTo(current) + //prev = endNode + //prev = current + prev = null + } + is AstStm.RETURN, is AstStm.THROW, is AstStm.RETHROW -> { + current.stms += stm + current = createBB() + setPrevNextTo(null) + prev = current + } + else -> { + current.stms += stm + prev = current + } + } + } + + val relooper = Relooper(types, "$method", method.relooperDebug) + for (n in bblist) { + n.node = relooper.node(n.stms) + //println("NODE(${n.index}): ${n.stms}") + //if (n.next != null) println(" -> ${n.next}") + //if (n.ifNext != null) println(" -> ${n.ifNext} [${n.condExpr}]") + } + for (n in bblist) { + val next = n.next + if (next != null) n.node.edgeTo(next.node) + for (edge in n.edges) { + n.node.edgeTo(edge.next.node, edge.cond) + } + } + + try { + val render = relooper.render(bblist[0].node) + val bodyGotos = if (settings.optimize) { + render.optimize(body.flags) + } else { + render + } + return body.copy( + stm = bodyGotos ?: return null + ) + } catch (e: RelooperException) { + //println("RelooperException: ${e.message}") + return null + } + //return AstBody(relooper.render(bblist[0].node!!) ?: return null, body.locals, body.traps) + } + + private fun removeRelooper2(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody? { val entryStm = body.stm as? AstStm.STMS ?: return null // Not relooping single statements if (body.traps.isNotEmpty()) return null // Not relooping functions with traps by the moment @@ -49,26 +192,35 @@ class GotosFeature : AstMethodFeature() { val relooper = Relooper(types, "$method", method.relooperDebug) val tswitchLocal = AstType.INT.local("_switchId") + fun AstLabel.index(): Int = labelToIndex[this]!! + val nodesByIndex = hashMapOf() + + fun Relooper.Node.edgeToOrMerge(that: Relooper.Node, cond: AstExpr? = null) { + val edge = this.tryGetEdgeTo(that) + if (edge != null) { + edge.remove() + this.edgeTo(that, if (cond != null) (edge.condOrTrue bor cond) else edge.cond) + } else { + this.edgeTo(that, cond) + } + } + fun render(index: Int): Relooper.Node { - println("$index: ${stms[index]}") + //println("$index: ${stms[index]}") if (index in nodesByIndex) return nodesByIndex[index]!! val out = arrayListOf() val node = relooper.node(out).apply { nodesByIndex[index] = this } - loop@for (i in index until stms.size) { + loop@ for (i in index until stms.size) { val stm = stms[i] - nodesByIndex[i] = node + //if (out.isEmpty()) nodesByIndex[i] = node when (stm) { - is AstStm.LINE -> Unit - is AstStm.NOP -> Unit + is AstStm.LINE, is AstStm.NOP -> Unit is AstStm.STM_LABEL -> { - if (i == index) { - Unit - } else { - nodesByIndex.remove(i) + if (i != index) { node.edgeTo(render(i)) break@loop } @@ -78,21 +230,29 @@ class GotosFeature : AstMethodFeature() { break@loop } is AstStm.GOTO -> { - node.edgeTo(render(labelToIndex[stm.label]!!)) + node.edgeTo(render(stm.label.index())) break@loop } is AstStm.IF_GOTO -> { - for (n in i until stms.size) { - val stm = stms[n] - if (stm is AstStm.IF_GOTO) { - node.edgeTo(render(labelToIndex[stm.label]!!), stm.cond.value) - } else { - node.edgeTo(render(i + 1)) - break@loop - } - } + node.edgeTo(render(i + 1)) + node.edgeTo(render(stm.label.index()), stm.cond.value) break@loop } + //is AstStm.IF_GOTO, is AstStm.GOTO -> { + // for (n in i until stms.size) { + // val stm = stms[n] + // if (stm is AstStm.IF_GOTO) { + // node.edgeToOrMerge(render(stm.label.index()), stm.cond.value) + // } else if (stm is AstStm.GOTO) { + // node.edgeToOrMerge(render(stm.label.index())) + // break@loop + // } else { + // node.edgeTo(render(n)) + // break@loop + // } + // } + // break@loop + //} is AstStm.SWITCH_GOTO -> { val switchLocal = if (stm.subject.value is AstExpr.LOCAL) { out += tswitchLocal.setTo(stm.subject.value) @@ -101,9 +261,9 @@ class GotosFeature : AstMethodFeature() { stm.subject.value } - node.edgeTo(render(labelToIndex[stm.default]!!)) + node.edgeTo(render(stm.default.index())) for ((keys, label) in stm.cases) { - val branch = render(labelToIndex[label]!!) + val branch = render(label.index()) for (key in keys) { node.edgeTo(branch, switchLocal eq key.lit) } diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index b010f16d..97d4f322 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -6,6 +6,7 @@ import com.jtransc.error.invalidOp import com.jtransc.text.INDENTS import com.jtransc.text.quote import java.util.* +import kotlin.collections.ArrayList import kotlin.collections.LinkedHashSet /** @@ -41,7 +42,7 @@ import kotlin.collections.LinkedHashSet * That should work with custom gotos or await/async implementations. */ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boolean = false) { - class Node(val types: AstTypes, val index: Int, val body: List) { + inner class Node(val index: Int, val body: ArrayList) { var exitNode = false var tag = "" val name = "L$index" @@ -59,9 +60,16 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo if (value != null) edgeTo(value) } + init { + trace { "node: ${this.name}" } + } + + fun tryGetEdgeTo(dst: Node) = this.dstEdges.firstOrNull { it.dst == dst } + fun edgeTo(dst: Node, cond: AstExpr? = null): Node { + trace { "edge: ${this.name} -> ${dst.name}" } val src = this - val edge = Edge(types, src, dst, cond) + val edge = Edge(src, dst, cond) src.dstEdges += edge dst.srcEdges += edge return this @@ -71,7 +79,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo fun isEmpty(): Boolean = body.isEmpty() } - class Edge(val types: AstTypes, val src: Node, val dst: Node, val cond: AstExpr? = null) { + inner class Edge(val src: Node, val dst: Node, val cond: AstExpr? = null) { //override fun toString(): String = "IF (${cond.dump(types)}) goto L${dst.index}; else goto L${current.next?.index};" val condOrTrue get() = cond ?: true.lit @@ -104,8 +112,8 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo //fun node(body: List): Node = Node(types, lastIndex, body.normalize(lastIndex)).apply { lastIndex++ } //fun node(body: AstStm): Node = Node(types, lastIndex, listOf(body).normalize(lastIndex)).apply { lastIndex++ } - fun node(body: List): Node = Node(types, lastIndex, body).apply { lastIndex++ } - fun node(body: AstStm): Node = Node(types, lastIndex, listOf(body).normalize(lastIndex)).apply { lastIndex++ } + fun node(body: ArrayList): Node = Node(lastIndex, body).apply { lastIndex++ } + fun node(body: AstStm): Node = node(ArrayList(listOf(body).normalize(lastIndex))) fun List.normalize(index: Int): List { val out = arrayListOf() @@ -129,11 +137,12 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val processed = LinkedHashSet() val queue = Queue() queue.queue(nentry) - while (queue.hasMore) { + loop@while (queue.hasMore) { val node = queue.dequeue() if (node in processed) continue processed += node + // Combine an empty node that just links to another node if (node.body.isEmpty() && node.dstEdges.size == 1 && node.dstEdgesButNext.isEmpty()) { val dstNode = node.dstEdges.first().dst for (e in node.srcEdges.toList()) { @@ -144,10 +153,22 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo nentry = dstNode } queue.queue(dstNode) - } else { - for (edge in node.dstEdges) { - queue.queue(edge.dst) - } + continue@loop + } + + // Combine a non-empty node that references just a node and that node is just referenced by that one node + //while (node.dstEdgesButNext.isEmpty() && node.next != null && node.next!!.srcEdges.size == 1 && node.next!!.srcEdges.first().src == node) { + // val next = node.next!! + // node.body += next.body + // node.next = next.next + // for (e in next.dstEdges.toList()) { + // e.remove() + // node.edgeTo(e.dst, e.cond) + // } + //} + + for (edge in node.dstEdges) { + queue.queue(edge.dst) } } return nentry @@ -220,7 +241,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo * - Creates a list of nodes for the graph */ private fun prepare(entry: Node): Prepare { - val exit = node(listOf()) + val exit = node(arrayListOf()) val processed = LinkedHashSet() exit.exitNode = true processed += exit diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index 88f45be8..b108d8e2 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -74,7 +74,7 @@ static public int composedIfAnd(int a, int b) { } } - @JTranscRelooper + @JTranscRelooper(debug = true) static public int composedIfOr(int a, int b) { if (a < b || a >= 0) { return -1; @@ -170,6 +170,7 @@ static private boolean demo(boolean a, boolean b, boolean c) { return result; } + //@JTranscRelooper(debug = true) @JTranscRelooper static public boolean isDigit(char c) { return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); @@ -210,7 +211,7 @@ static private boolean sequals(String l, String r) { return true; } - @JTranscRelooper(debug = true) + @JTranscRelooper static private boolean myswitch(int a) { JTranscConsole.log("myswitch: " + a); switch (a) { From 34521b81bfe828cad97ea3040c07472db82ba49f Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 21:17:05 +0100 Subject: [PATCH 55/60] Some switch fixes --- .../ast/feature/method/GotosFeature.kt | 169 ------------------ .../src/com/jtransc/graph/Relooper.kt | 29 +-- .../src/relooper/RelooperTest.java | 6 +- 3 files changed, 20 insertions(+), 184 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index 7db42c1c..af7977d6 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -41,149 +41,6 @@ class GotosFeature : AstMethodFeature() { } private fun removeRelooper(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody? { - //return removeRelooper1(method, body, settings, types) - return removeRelooper2(method, body, settings, types) - } - - class BasicBlock(var index: Int) { - lateinit var node: Relooper.Node - var isSwitch = false - val stms = arrayListOf() - var next: BasicBlock? = null - val edges = arrayListOf() - //var condExpr: AstExpr? = null - //var ifNext: BasicBlock? = null - - //val targets by lazy { (listOf(next, ifNext) + (switchNext?.values ?: listOf())).filterNotNull() } - - override fun toString(): String = "BasicBlock($index)" - } - - class BBEdge(val cond: AstExpr, val next: BasicBlock) - - private fun removeRelooper1(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody? { - - val entryStm = body.stm as? AstStm.STMS ?: return null - // Not relooping single statements - if (body.traps.isNotEmpty()) return null // Not relooping functions with traps by the moment - - val stms = entryStm.stms - val bblist = arrayListOf() - val bbs = hashMapOf() - fun createBB(): BasicBlock { - val bb = BasicBlock(bblist.size) - bblist += bb - return bb - } - - fun getBBForLabel(label: AstLabel): BasicBlock { - return bbs.getOrPut(label) { createBB() } - } - - val entry = createBB() - var current = entry - var switchId = 0 - var prev: BasicBlock? = current - for (stmBox in stms) { - val stm = stmBox.value - - fun setPrevNextTo(that: BasicBlock?) { - //if (!prev.isSwitch) { - prev?.next = that - //} - } - - when (stm) { - is AstStm.STM_LABEL -> { - current = getBBForLabel(stm.label) - setPrevNextTo(current) - prev = current - } - is AstStm.GOTO -> { - current = createBB() - setPrevNextTo(getBBForLabel(stm.label)) - prev = null - } - is AstStm.IF_GOTO -> { - current = createBB() - prev?.edges?.add(BBEdge(stm.cond.value, getBBForLabel(stm.label))) - setPrevNextTo(current) - prev = current - } - is AstStm.SWITCH_GOTO -> { - return null - current = createBB() - - //val endNode = createBB() - - val switchLocal = if (stm.subject.value is AstExpr.LOCAL) { - val switchLocal = AstType.INT.local("switch${switchId++}") - current.stms += switchLocal.setTo(stm.subject.value) - switchLocal - } else { - stm.subject.value - } - current.isSwitch = true - current.next = getBBForLabel(stm.default) - //current.next!!.next = endNode - for ((keys, label) in stm.cases) { - val caseLabel = getBBForLabel(label) - //caseLabel.next = endNode - for (key in keys) { - current.edges += BBEdge(switchLocal eq key.lit, caseLabel) - } - } - setPrevNextTo(current) - //prev = endNode - //prev = current - prev = null - } - is AstStm.RETURN, is AstStm.THROW, is AstStm.RETHROW -> { - current.stms += stm - current = createBB() - setPrevNextTo(null) - prev = current - } - else -> { - current.stms += stm - prev = current - } - } - } - - val relooper = Relooper(types, "$method", method.relooperDebug) - for (n in bblist) { - n.node = relooper.node(n.stms) - //println("NODE(${n.index}): ${n.stms}") - //if (n.next != null) println(" -> ${n.next}") - //if (n.ifNext != null) println(" -> ${n.ifNext} [${n.condExpr}]") - } - for (n in bblist) { - val next = n.next - if (next != null) n.node.edgeTo(next.node) - for (edge in n.edges) { - n.node.edgeTo(edge.next.node, edge.cond) - } - } - - try { - val render = relooper.render(bblist[0].node) - val bodyGotos = if (settings.optimize) { - render.optimize(body.flags) - } else { - render - } - return body.copy( - stm = bodyGotos ?: return null - ) - } catch (e: RelooperException) { - //println("RelooperException: ${e.message}") - return null - } - //return AstBody(relooper.render(bblist[0].node!!) ?: return null, body.locals, body.traps) - } - - private fun removeRelooper2(method: AstMethod, body: AstBody, settings: AstBuildSettings, types: AstTypes): AstBody? { val entryStm = body.stm as? AstStm.STMS ?: return null // Not relooping single statements if (body.traps.isNotEmpty()) return null // Not relooping functions with traps by the moment @@ -196,16 +53,6 @@ class GotosFeature : AstMethodFeature() { val nodesByIndex = hashMapOf() - fun Relooper.Node.edgeToOrMerge(that: Relooper.Node, cond: AstExpr? = null) { - val edge = this.tryGetEdgeTo(that) - if (edge != null) { - edge.remove() - this.edgeTo(that, if (cond != null) (edge.condOrTrue bor cond) else edge.cond) - } else { - this.edgeTo(that, cond) - } - } - fun render(index: Int): Relooper.Node { //println("$index: ${stms[index]}") if (index in nodesByIndex) return nodesByIndex[index]!! @@ -215,7 +62,6 @@ class GotosFeature : AstMethodFeature() { } loop@ for (i in index until stms.size) { val stm = stms[i] - //if (out.isEmpty()) nodesByIndex[i] = node when (stm) { is AstStm.LINE, is AstStm.NOP -> Unit @@ -238,21 +84,6 @@ class GotosFeature : AstMethodFeature() { node.edgeTo(render(stm.label.index()), stm.cond.value) break@loop } - //is AstStm.IF_GOTO, is AstStm.GOTO -> { - // for (n in i until stms.size) { - // val stm = stms[n] - // if (stm is AstStm.IF_GOTO) { - // node.edgeToOrMerge(render(stm.label.index()), stm.cond.value) - // } else if (stm is AstStm.GOTO) { - // node.edgeToOrMerge(render(stm.label.index())) - // break@loop - // } else { - // node.edgeTo(render(n)) - // break@loop - // } - // } - // break@loop - //} is AstStm.SWITCH_GOTO -> { val switchLocal = if (stm.subject.value is AstExpr.LOCAL) { out += tswitchLocal.setTo(stm.subject.value) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 97d4f322..989efb3b 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -5,9 +5,6 @@ import com.jtransc.ds.Queue import com.jtransc.error.invalidOp import com.jtransc.text.INDENTS import com.jtransc.text.quote -import java.util.* -import kotlin.collections.ArrayList -import kotlin.collections.LinkedHashSet /** * Converts a digraph representing the control flow graph of a method into ifs and whiles. @@ -44,6 +41,7 @@ import kotlin.collections.LinkedHashSet class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boolean = false) { inner class Node(val index: Int, val body: ArrayList) { var exitNode = false + var exitFinalNode = false var tag = "" val name = "L$index" //var next: Node? = null @@ -137,7 +135,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val processed = LinkedHashSet() val queue = Queue() queue.queue(nentry) - loop@while (queue.hasMore) { + loop@ while (queue.hasMore) { val node = queue.dequeue() if (node in processed) continue processed += node @@ -244,6 +242,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val exit = node(arrayListOf()) val processed = LinkedHashSet() exit.exitNode = true + exit.exitFinalNode = true processed += exit val result = LinkedHashSet() @@ -353,13 +352,13 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo queue.queue(a) while (queue.hasMore) { val item = queue.dequeue() - if (item !in visited) { - set += item - visited += item - if (item != exit) { - for (edge in item.dstEdges) { - queue.queue(edge.dst) - } + if (item in visited) continue + if (item.exitFinalNode) continue + set += item + visited += item + if (item != exit) { + for (edge in item.dstEdges) { + queue.queue(edge.dst) } } } @@ -397,7 +396,9 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } fun findCommonSuccessorNotRendered(nodes: List, exit: Node?): Node? { - return nodes.reduce { acc, node -> findCommonSuccessorNotRendered(acc!!, node!!, exit) } + return nodes.reduce { acc, node -> + findCommonSuccessorNotRendered(acc!!, node!!, exit) ?: acc ?: node + } } } @@ -588,6 +589,10 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo out += base + if (debug) { + println("----") + } + return common } diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index b108d8e2..8505f7c5 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -74,7 +74,7 @@ static public int composedIfAnd(int a, int b) { } } - @JTranscRelooper(debug = true) + @JTranscRelooper static public int composedIfOr(int a, int b) { if (a < b || a >= 0) { return -1; @@ -211,7 +211,7 @@ static private boolean sequals(String l, String r) { return true; } - @JTranscRelooper + @JTranscRelooper(debug = true) static private boolean myswitch(int a) { JTranscConsole.log("myswitch: " + a); switch (a) { @@ -225,8 +225,8 @@ static private boolean myswitch(int a) { break; case 3: return false; - } + JTranscConsole.log("end myswitch"); return true; } } \ No newline at end of file From 10a5ed4c9abd0f67c0c743069450ebfd6ddf585a Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 22:09:43 +0100 Subject: [PATCH 56/60] Proper switch working (common node finding still requires some work to produce much less code) --- .../ast/feature/method/GotosFeature.kt | 15 ++++-- .../src/relooper/RelooperTest.java | 49 ++++++++++++++++++- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index af7977d6..e5451358 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -47,7 +47,7 @@ class GotosFeature : AstMethodFeature() { val stms = entryStm.stmsUnboxed val labelToIndex = stms.withIndex().filter { it.value is AstStm.STM_LABEL }.map { (it.value as AstStm.STM_LABEL).label to it.index }.toMap() val relooper = Relooper(types, "$method", method.relooperDebug) - val tswitchLocal = AstType.INT.local("_switchId") + val tswitchLocal = AstType.INT.local("_switchId").local fun AstLabel.index(): Int = labelToIndex[this]!! @@ -85,18 +85,23 @@ class GotosFeature : AstMethodFeature() { break@loop } is AstStm.SWITCH_GOTO -> { - val switchLocal = if (stm.subject.value is AstExpr.LOCAL) { + fun tswitchLocalGen() = tswitchLocal.local + fun subGen() = stm.subject.value + + val switchLocal = if (stm.subject.value !is AstExpr.LocalExpr) { + stm as AstStm.SWITCH_GOTO out += tswitchLocal.setTo(stm.subject.value) - tswitchLocal + ::tswitchLocalGen } else { - stm.subject.value + ::subGen } + stm as AstStm.SWITCH_GOTO node.edgeTo(render(stm.default.index())) for ((keys, label) in stm.cases) { val branch = render(label.index()) for (key in keys) { - node.edgeTo(branch, switchLocal eq key.lit) + node.edgeTo(branch, switchLocal() eq key.lit) } } break@loop diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index 8505f7c5..31c27701 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -54,6 +54,8 @@ static public void main(String[] args) { JTranscConsole.log(myswitch(2)); JTranscConsole.log(myswitch(3)); JTranscConsole.log(myswitch(4)); + + JTranscConsole.log(switchCase(new Term(), 0, new Term())); } @JTranscRelooper @@ -211,7 +213,7 @@ static private boolean sequals(String l, String r) { return true; } - @JTranscRelooper(debug = true) + @JTranscRelooper static private boolean myswitch(int a) { JTranscConsole.log("myswitch: " + a); switch (a) { @@ -229,4 +231,49 @@ static private boolean myswitch(int a) { JTranscConsole.log("end myswitch"); return true; } + + @JTranscRelooper(debug = true) + static private int switchCase(Term target, int distance, Term theFirst) { + int type; + Term next; + boolean eat; + switch (target.type) { + case Term.CHAR: + case Term.BITSET: + case Term.BITSET2: + type = Term.FIND; + break; + case Term.REG: + case Term.REG_I: + case Term.GROUP_IN: + type = Term.FINDREG; + break; + default: + throw new IllegalArgumentException("wrong target type: " + target.type); + } + //target = target; + //distance = distance; + if (target == theFirst) { + next = target.next; + eat = true; //eat the next + } else { + next = theFirst; + eat = false; + } + return type + (eat ? 1 : 0) + next.type; + } + + static class Term { + public int type; + public Term next; + + static final int CHAR = 0; + static final int BITSET = 1; + static final int BITSET2 = 2; + static final int REG = 6; + static final int REG_I = 7; + static final int FIND = 8; + static final int FINDREG = 9; + static final int GROUP_IN = 15; + } } \ No newline at end of file From 4159fcfe9c4bbc297091efe0ab05f31b157b54f0 Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 22:18:48 +0100 Subject: [PATCH 57/60] Prefer break/continue over return/throw as guard clauses --- jtransc-core/src/com/jtransc/graph/Relooper.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index 989efb3b..c585c83e 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -544,13 +544,15 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo // Guard clause (either return or throw) if (node.dstEdgesButNext.size == 1 && node.next != null && node.next!!.exitNode) { - out += AstStm.IF(firstCondEdge!!.cond!!.not(), node.next!!.body.stm()) + val breakOrContinue = getNodeContinueOrBreak(node.next) + out += AstStm.IF(firstCondEdge!!.cond!!.not(), breakOrContinue ?: node.next!!.body.stm()) return node.dstEdgesButNext.first().dst } // Guard clause (either return or throw) if (node.dstEdgesButNext.size == 1 && node.next != null && firstCondNode?.exitNode == true) { - out += AstStm.IF(firstCondEdge.cond!!, firstCondNode.body.stm()) + val breakOrContinue = getNodeContinueOrBreak(firstCondNode) + out += AstStm.IF(firstCondEdge.cond!!, breakOrContinue ?: firstCondNode.body.stm()) return node.next } From 28699bc297197622afd9fe0117a92264130c297d Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 17 Jan 2018 22:37:57 +0100 Subject: [PATCH 58/60] Proper generate switch --- jtransc-core/src/com/jtransc/ast/ast_body.kt | 2 ++ .../ast/feature/method/GotosFeature.kt | 4 +-- .../src/com/jtransc/graph/Relooper.kt | 34 ++++++++++++++++--- .../src/relooper/RelooperTest.java | 1 + 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/jtransc-core/src/com/jtransc/ast/ast_body.kt b/jtransc-core/src/com/jtransc/ast/ast_body.kt index 1283392c..4c94f680 100644 --- a/jtransc-core/src/com/jtransc/ast/ast_body.kt +++ b/jtransc-core/src/com/jtransc/ast/ast_body.kt @@ -614,8 +614,10 @@ fun List.stm() = when (this.size) { fun AstExpr.Box.isPure(): Boolean = this.value.isPure() fun AstExpr.Box.isLiteral(value: Any?): Boolean = this.value.isLiteral(value) +fun AstExpr.Box.isLiteral(): Boolean = this.value.isLiteral() fun AstExpr.isLiteral(value: Any?): Boolean = (this is AstExpr.LITERAL) && this.value == value +fun AstExpr.isLiteral(): Boolean = (this is AstExpr.LITERAL) fun AstExpr.isPure(): Boolean = when (this) { is AstExpr.ARRAY_ACCESS -> this.array.isPure() && this.index.isPure() // Can cause null pointer/out of bounds diff --git a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index e5451358..799a4afa 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -97,11 +97,11 @@ class GotosFeature : AstMethodFeature() { } stm as AstStm.SWITCH_GOTO - node.edgeTo(render(stm.default.index())) + node.tedgeTo(render(stm.default.index())).apply { caseEdge = true } for ((keys, label) in stm.cases) { val branch = render(label.index()) for (key in keys) { - node.edgeTo(branch, switchLocal() eq key.lit) + node.tedgeTo(branch, switchLocal() eq key.lit).apply { caseEdge = true } } } break@loop diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index c585c83e..b5b7eb0b 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -62,22 +62,23 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo trace { "node: ${this.name}" } } - fun tryGetEdgeTo(dst: Node) = this.dstEdges.firstOrNull { it.dst == dst } - - fun edgeTo(dst: Node, cond: AstExpr? = null): Node { + fun tedgeTo(dst: Node, cond: AstExpr? = null): Edge { trace { "edge: ${this.name} -> ${dst.name}" } val src = this val edge = Edge(src, dst, cond) src.dstEdges += edge dst.srcEdges += edge - return this + return edge } + fun edgeTo(dst: Node, cond: AstExpr? = null): Node = this.apply { tedgeTo(dst, cond) } + override fun toString(): String = "L$index: " + dump(types, body.stm()).toString().replace('\n', ' ').trim() + " EDGES: $dstEdges. SRC_EDGES: ${srcEdges.size}" fun isEmpty(): Boolean = body.isEmpty() } inner class Edge(val src: Node, val dst: Node, val cond: AstExpr? = null) { + var caseEdge = false //override fun toString(): String = "IF (${cond.dump(types)}) goto L${dst.index}; else goto L${current.next?.index};" val condOrTrue get() = cond ?: true.lit @@ -575,6 +576,31 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo val common = ctx.findCommonSuccessorNotRendered(node.dstEdges.map { it.dst }, exit) //out += AstStm.SWITCH() + // Suitable for a switch + if (node.dstEdges.all { it.caseEdge }) { + //println("IN A SWITCH!") + + val firstCond = node.dstEdgesButNext.first().cond as AstExpr.BINOP + + val keyToNode = node.dstEdgesButNext.map { + val cond = it.cond as AstExpr.BINOP + val value = (cond.right.value as AstExpr.LITERAL).valueAsInt + value to it.dst + } + + val nodeToKeys = keyToNode.groupBy { it.second }.mapValues { it.value.map { it.first } } + + out += AstStm.SWITCH( + firstCond.left.value, + renderComponents(pg, g, node.next!!, common, ctx, level = level + 1), + nodeToKeys.map { + it.value to renderComponents(pg, g, it.key, common, ctx, level = level + 1) + } + ) + + return common + } + var base = if (node.next != null) { renderComponents(pg, g, node.next!!, common, ctx, level = level + 1) } else { diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index 31c27701..f7d39577 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -222,6 +222,7 @@ static private boolean myswitch(int a) { break; case 1: JTranscConsole.log(1); + case 6: case 2: JTranscConsole.log(2); break; From 6ef715c2003afd52335fac7fd94dc1e9a3f8a08b Mon Sep 17 00:00:00 2001 From: soywiz Date: Thu, 18 Jan 2018 07:51:40 +0100 Subject: [PATCH 59/60] Fix floating point comparisons --- .../src/com/jtransc/graph/Relooper.kt | 13 ++++++------ .../src/relooper/RelooperTest.java | 21 ++++++++++++++++--- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index b5b7eb0b..5482a5ad 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -500,6 +500,7 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } if (node == prevNode) invalidOp("Infinite loop detected") + //if (node?.exitFinalNode == true) break } return out.stmsWithoutNops } @@ -657,9 +658,9 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo trace { "$indent- WARNING: Maybe an if-chain that was not optimized? And this will generate repeated branches!" } out += AstStm.IF_ELSE( - endOfIfEdge.cond!!, - renderComponents(pg, g, endOfIfNode, common, ctx, level = level + 1), - renderComponents(pg, g, ifBody, common, ctx, level = level + 1) + endOfIfEdge.cond!!.not(), + renderComponents(pg, g, ifBody, common, ctx, level = level + 1), + renderComponents(pg, g, endOfIfNode, common, ctx, level = level + 1) ).optimizeIfElse() } } @@ -667,9 +668,9 @@ class Relooper(val types: AstTypes, val name: String = "unknown", val debug: Boo } // IF+ELSE else -> out += AstStm.IF_ELSE( - endOfIfEdge.cond!!, - renderComponents(pg, g, endOfIfNode, common, ctx, level = level + 1), - renderComponents(pg, g, ifBody, common, ctx, level = level + 1) + endOfIfEdge.cond!!.not(), + renderComponents(pg, g, ifBody, common, ctx, level = level + 1), + renderComponents(pg, g, endOfIfNode, common, ctx, level = level + 1) ).optimizeIfElse() } return common diff --git a/jtransc-gen-common-tests/src/relooper/RelooperTest.java b/jtransc-gen-common-tests/src/relooper/RelooperTest.java index f7d39577..01b9a04e 100644 --- a/jtransc-gen-common-tests/src/relooper/RelooperTest.java +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -1,10 +1,8 @@ package relooper; import com.jtransc.annotation.JTranscRelooper; -import com.jtransc.annotation.JTranscSync; import com.jtransc.io.JTranscConsole; -import java.nio.DoubleBuffer; import java.nio.ShortBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -56,6 +54,11 @@ static public void main(String[] args) { JTranscConsole.log(myswitch(4)); JTranscConsole.log(switchCase(new Term(), 0, new Term())); + + floatComparisons(0f); + floatComparisons(1f); + floatComparisons(Float.NaN); + floatComparisons(-Float.NaN); } @JTranscRelooper @@ -233,7 +236,19 @@ static private boolean myswitch(int a) { return true; } - @JTranscRelooper(debug = true) + @JTranscRelooper + @SuppressWarnings("ConstantConditions") + static private void floatComparisons(float v) { + JTranscConsole.log("floatComparisons:" + v); + JTranscConsole.log(v < Float.NaN); + JTranscConsole.log(v > Float.NaN); + JTranscConsole.log(v <= Float.NaN); + JTranscConsole.log(v >= Float.NaN); + JTranscConsole.log(v == Float.NaN); + JTranscConsole.log(v != Float.NaN); + } + + @JTranscRelooper static private int switchCase(Term target, int distance, Term theFirst) { int type; Term next; From a8ebb761ff32eaebbdb66a7752ec01db5de92e03 Mon Sep 17 00:00:00 2001 From: soywiz Date: Thu, 18 Jan 2018 08:20:03 +0100 Subject: [PATCH 60/60] Update badges to represent master instead of last build --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bd44c1ae..ae632bdf 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,8 @@ JTRANSC ![JTransc](extra/logo-256.png) [![Maven Version](https://img.shields.io/github/tag/jtransc/jtransc.svg?style=flat&label=maven)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22jtransc-maven-plugin%22) -[![Build Status](https://secure.travis-ci.org/jtransc/jtransc.svg)](http://travis-ci.org/jtransc/jtransc) -[![Build status](https://ci.appveyor.com/api/projects/status/qnd0g966t1b54q4a?svg=true)](https://ci.appveyor.com/project/soywiz/jtransc) -[![Code coverage](https://codecov.io/gh/jtransc/jtransc/branch/master/graph/badge.svg)](https://codecov.io/gh/jtransc/jtransc) +[![Build Status](https://secure.travis-ci.org/jtransc/jtransc.svg?branch=master)](http://travis-ci.org/jtransc/jtransc) +[![Build status](https://ci.appveyor.com/api/projects/status/qnd0g966t1b54q4a?svg=true&branch=master)](https://ci.appveyor.com/project/soywiz/jtransc) [![gitter](https://img.shields.io/gitter/room/jtransc/general.svg)](https://gitter.im/jtransc/general) # Documentation