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 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) { 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/gradle.properties b/gradle.properties index 186c891a..a9d70cfd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,9 @@ -jtranscVersion=0.6.9-SNAPSHOT -kotlinVersion=1.2.10 +jtranscVersion=0.7.0-SNAPSHOT +kotlinVersion=1.2.20 file.encoding=UTF-8 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 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..c28a80a7 --- /dev/null +++ b/jtransc-annotations/src/com/jtransc/annotation/JTranscRelooper.java @@ -0,0 +1,17 @@ +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; + boolean debug() default false; + int method() default 0; // 1 = experimental method +} 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 { -} diff --git a/jtransc-core/src/com/jtransc/ast/AstTransformer.kt b/jtransc-core/src/com/jtransc/ast/AstTransformer.kt index 2eb34cea..789260be 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) @@ -199,7 +200,13 @@ open class AstTransformer { open fun transform(stm: AstStm.WHILE): AstStm { transform(stm.cond) - transform(stm.iter) + transform(stm.body) + return stm + } + + open fun transform(stm: AstStm.DO_WHILE): AstStm { + 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 c07e1e11..9861e82d 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) @@ -203,7 +204,12 @@ 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) { + visit(stm.body) + visit(stm.cond) } open fun visit(stm: AstStm.RETURN) { diff --git a/jtransc-core/src/com/jtransc/ast/ast.kt b/jtransc-core/src/com/jtransc/ast/ast.kt index 6d54c4b5..68f31c03 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 relooper by lazy { annotationsList.getTyped() } + val relooperEnabled: Boolean? by lazy { relooper?.value } + val relooperDebug by lazy { relooper?.debug ?: false } + init { if (id < 0) { println("Invalid method id: $id") diff --git a/jtransc-core/src/com/jtransc/ast/ast_body.kt b/jtransc-core/src/com/jtransc/ast/ast_body.kt index 00162a37..4c94f680 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( @@ -203,9 +202,15 @@ sealed class AstStm : AstElement, Cloneable { val sfalse = sfalse.box } - class WHILE(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, body: AstStm) : AstStm() { + val cond = cond.box + val body = body.box } class RETURN(retval: AstExpr) : AstStm() { @@ -227,8 +232,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() { @@ -272,6 +277,24 @@ fun AstStm.isEmpty() = when (this) { else -> false } +fun AstStm.normalizeWithoutNopsOrLines(): AstStm { + if (this is AstStm.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 +} + +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() return true @@ -282,6 +305,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() } @@ -364,7 +389,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 @@ -553,9 +580,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) } @@ -583,6 +613,11 @@ 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 @@ -792,6 +827,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()) @@ -882,8 +919,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 1b575a39..7623e379 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,22 @@ 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) { + if (ss is AstStm.NOP) continue + line(dumpCollapse(types, ss.box)) + } + } + } else { + return dump(types, s) + } } fun dump(types: AstTypes, stm: AstStm?): Indenter { @@ -55,17 +68,22 @@ 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 -> line("if (${dump(types, stm.cond)})") { line(dumpCollapse(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("${stm.name}: while (${dump(types, stm.cond)})") { + line(dumpCollapse(types, stm.body)) + } + } + is AstStm.DO_WHILE -> { + line("${stm.name}: do", after2 = " while (${dump(types, stm.cond)});") { + line(dumpCollapse(types, stm.body)) } } is AstStm.SWITCH -> { @@ -94,7 +112,10 @@ 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) + fun AstExpr.dump(types: AstTypes) = dump(types, this) fun dump(types: AstTypes, expr: AstExpr?): String { @@ -136,6 +157,10 @@ 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/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/GotosFeature.kt b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt index ecf15585..799a4afa 100644 --- a/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt +++ b/jtransc-core/src/com/jtransc/ast/feature/method/GotosFeature.kt @@ -25,10 +25,13 @@ 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 (false) { + if (method.relooperEnabled ?: settings.relooper) { + //if (method.relooperEnabled ?: false) { try { - return removeRelooper(body, settings, types) ?: removeMachineState(body, types) + 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) } @@ -37,108 +40,92 @@ class GotosFeature : AstMethodFeature() { } } - private fun removeRelooper(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 - - //val targets by lazy { (listOf(next, ifNext) + (switchNext?.values ?: listOf())).filterNotNull() } - - override fun toString(): String = "BasicBlock($index)" - } - + 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.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").local - val stms = entryStm.stms - val bblist = arrayListOf() - val bbs = hashMapOf() - fun createBB(): BasicBlock { - val bb = BasicBlock(bblist.size) - bblist += bb - return bb - } + fun AstLabel.index(): Int = labelToIndex[this]!! - fun getBBForLabel(label: AstLabel): BasicBlock { - return bbs.getOrPut(label) { createBB() } - } + val nodesByIndex = hashMapOf() - val entry = createBB() - var current = entry - for (stmBox in stms) { - val stm = stmBox.value - when (stm) { - is AstStm.STM_LABEL -> { - val prev = current - current = getBBForLabel(stm.label) - prev.next = current - } - is AstStm.GOTO -> { - val prev = current - current = createBB() - prev.next = getBBForLabel(stm.label) - } - is AstStm.IF_GOTO -> { - val prev = current - current = createBB() - prev.condExpr = stm.cond.value - prev.ifNext = getBBForLabel(stm.label) - prev.next = current - } - is AstStm.SWITCH_GOTO -> { - // Not handled switches yet! - return null - } - is AstStm.RETURN, is AstStm.THROW, is AstStm.RETHROW -> { - current.stms += stm - val prev = current - current = createBB() - prev.next = null - } - else -> { - current.stms += stm - } + 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 } - } + loop@ for (i in index until stms.size) { + val stm = stms[i] + + when (stm) { + is AstStm.LINE, is AstStm.NOP -> Unit + is AstStm.STM_LABEL -> { + if (i != index) { + node.edgeTo(render(i)) + break@loop + } + } + is AstStm.RETURN, is AstStm.THROW -> { + out += stm + break@loop + } + is AstStm.GOTO -> { + node.edgeTo(render(stm.label.index())) + break@loop + } + is AstStm.IF_GOTO -> { + node.edgeTo(render(i + 1)) + node.edgeTo(render(stm.label.index()), stm.cond.value) + break@loop + } + is AstStm.SWITCH_GOTO -> { + 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) + ::tswitchLocalGen + } else { + ::subGen + } - val relooper = Relooper(types) - 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 - 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!!) + stm as AstStm.SWITCH_GOTO + 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.tedgeTo(branch, switchLocal() eq key.lit).apply { caseEdge = true } + } + } + 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 { // @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()) @@ -177,9 +164,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() + AstStm.CONTINUE(if (insideCatch) "tryLoop" else "loop") ) fun simulateGotoLabel(label: AstLabel) = simulateGotoLabel(getStateFromLabel(label)) @@ -236,7 +223,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() @@ -255,12 +242,12 @@ 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() ) } 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..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,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.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 -> { // 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 7a88eaf6..62b2d20b 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 @@ -176,6 +177,7 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { open fun writeClasses(output: SyncVfsFile) { if (SINGLE_FILE) { + output[outputFileBaseName].removeIfExists() if (ADD_UTF8_BOM) { output[outputFileBaseName] = byteArrayOf(0xEF.toByte(), 0xBB.toByte(), 0xBF.toByte()) + genSingleFileClasses(output).toString().toByteArray() } else { @@ -441,6 +443,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) @@ -769,7 +772,7 @@ abstract class CommonGenerator(val injector: Injector) : IProgramTemplate { indent { line(genBody2WithFeatures2(method, body)) } - line("} finally{") + line("} finally {") indent { lineMonitorExit() } @@ -871,7 +874,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)}") @@ -903,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;") - open fun genStmBreak(stm: AstStm.BREAK) = Indenter("break;") + 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)) @@ -928,18 +945,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) { @@ -1044,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 } } @@ -1340,11 +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 { - flowBlock(FlowKind.WHILE) { - line("while (${stm.cond.genExpr()})") { - line(stm.iter.genStm()) + val label = if (supportsLabels) "${stm.name}: " else "" + + flowBlock(FlowKind.WHILE, stm.name) { + line("${label}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 + } + } + fun genStmDoWhile(stm: AstStm.DO_WHILE) = indent { + val label = if (supportsLabels) "${stm.name}: " else "" + + flowBlock(FlowKind.WHILE, stm.name) { + 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 { @@ -1353,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 diff --git a/jtransc-core/src/com/jtransc/graph/Relooper.kt b/jtransc-core/src/com/jtransc/graph/Relooper.kt index ec8a248d..5482a5ad 100644 --- a/jtransc-core/src/com/jtransc/graph/Relooper.kt +++ b/jtransc-core/src/com/jtransc/graph/Relooper.kt @@ -1,148 +1,797 @@ package com.jtransc.graph import com.jtransc.ast.* -import java.util.* +import com.jtransc.ds.Queue +import com.jtransc.error.invalidOp +import com.jtransc.text.INDENTS +import com.jtransc.text.quote -class Relooper(val types: AstTypes) { - class Graph - class Node(val types: AstTypes, val body: List) { - var next: Node? = null - val edges = arrayListOf() - val possibleNextNodes: List get() = listOf(next).filterNotNull() + edges.map { it.dst } +/** + * 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) + * + * 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) { + 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 + val srcEdges = arrayListOf() + val dstEdges = arrayListOf() + val dstEdgesButNext get() = dstEdges.filter { it.cond != null } + val possibleNextNodes: List get() = dstEdges.map { it.dst } - override fun toString(): String = dump(types, body.stm()).toString().trim() - } + val nextEdge get() = dstEdges.firstOrNull { it.cond == null } + var next: Node? + get() = nextEdge?.dst + set(value) { + nextEdge?.remove() + if (value != null) edgeTo(value) + } + + init { + trace { "node: ${this.name}" } + } + + 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 edge + } - class Edge(val dst: Node, val cond: AstExpr) { - override fun toString(): String = "IF ($cond) goto $dst;" + 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() } - fun node(body: List): Node = Node(types, body) - fun node(body: AstStm): Node = Node(types, listOf(body)) + 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 - fun edge(a: Node, b: Node) { - a.next = b + 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};" } - fun edge(a: Node, b: Node, cond: AstExpr) { - a.edges += Edge(b, cond) + 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") } - private fun prepare(entry: Node): List { - val exit = node(listOf()) - val explored = LinkedHashSet() - fun explore(node: Node) { - if (node in explored) return - explored += node - if (node.next != null) { - explore(node.next!!) - } else { - node.next = exit + 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++ } + + 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() + //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 } - for (edge in node.edges) explore(edge.dst) } - explore(entry) - explored += exit - return explored.toList() + return out } - 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 edge(a: Node, b: Node, cond: AstExpr? = null) = a.edgeTo(b, cond) - if (graph.hasCycles()) { - //noImpl("acyclic!") - //println("cyclic!") - return null + data class Prepare(val nodes: List, val entry: Node, val exit: Node) + + private fun Node.removeEmptyNodes(): Node { + var nentry = this + val processed = LinkedHashSet() + val queue = Queue() + queue.queue(nentry) + 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()) { + e.remove() + e.src.edgeTo(dstNode, e.cond) + } + if (nentry == node) { + nentry = dstNode + } + queue.queue(dstNode) + 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 + } + private fun Node.combineBooleanOpsEdges(): Node { + val entry = this + val processed = LinkedHashSet() + val queue = Queue() + queue.queue(entry) + while (queue.hasMore) { + val node = queue.dequeue() + if (node in processed) continue + processed += node - val graph2 = graph.tarjanStronglyConnectedComponentsAlgorithm() - val entry2 = graph2.findComponentIndexWith(entry) + // 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 - //graph2.outputEdges + if (prev.dstEdges.size == 2) { + val prevNext = prev.next!!; assert(prevNext == node) + val currNext = node.next!! - scgraph = graph2 - lookup = scgraph.assertAcyclic().createCommonDescendantLookup() - processedCount = IntArray(scgraph.size) + val prevCond = prev.dstEdgesButNext.first() + val currCond = node.dstEdgesButNext.first() - return renderInternal(entry2, -1).stm() - /* - println(entry) - println(entry2) - */ + val prevCondNode = prevCond.dst + val currCondNode = currCond.dst - //graph.dump() - //println(graph) - // @TODO: strong components + // && 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) { + prevCond.remove() + prev.edgeTo(currCond.dst, prevCond.condOrTrue bor currCond.condOrTrue) + prev.next = currNext + //println("-------") + queue(currCond.dst) + queue(currNext) + continue + } + // || 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("-------") + queue(currCond.dst) + queue(currNext) + continue + } + // None + else { + + } + } + } + + for (edge in node.dstEdges) { + queue(edge.dst) + } + } + return entry } - lateinit var processedCount: IntArray - lateinit var scgraph: Digraph> - lateinit var lookup: AcyclicDigraphLookup> + /** + * - Create a single artificial exit node + * - Creates a list of nodes for the graph + */ + private fun prepare(entry: Node): Prepare { + val exit = node(arrayListOf()) + val processed = LinkedHashSet() + exit.exitNode = true + exit.exitFinalNode = true + processed += exit + val result = LinkedHashSet() - private fun getEdge(from: Node, to: Node): Edge? { - return from.edges.firstOrNull() { it.dst == to } + 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) { + node.exitNode = true + 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) + } + + inline private fun trace(msg: () -> String) { + if (debug) println(msg()) } - private fun renderInternal(node: Int, endnode: Int): List { - processedCount[node]++ - if (processedCount[node] > 4) { - //println("Processed several!") - throw RelooperException("Node processed several times!") + 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) { + trace { "Rendering $name" } + for (n in g.nodes) { + trace { "* $n" } + } + 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() + 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 END" } + + trace { "# GRAPHVIZ START - http://viz-js.com/" } + trace { "digraph G {" } + for (n in g.nodes) { + val label = n.body.dumpCollapse(types).toString() + val tag = n.tag + trace { "L${n.index} [label = ${"$label$tag".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" } } - if (node == endnode) return listOf() + //println("Relooping '$name'...") + val result = renderComponents(null, g.tarjanStronglyConnectedComponentsAlgorithm(), gresult.entry, gresult.exit) + //println("Relooping '$name'...OK") + return result + } - val stms = arrayListOf() - stms += scgraph.getNode(node).nodes.flatMap { it.body } + class RenderContext(val graph: Digraph) { + var lastId = 0 + val loopStarts = hashMapOf() + val loopEnds = hashMapOf() + val rendered = LinkedHashSet() + fun allocName() = "loop${lastId++}" - //node.scgraph.dump() + // @TODO: Optimize performance! And maybe cache? + fun getNodeSuccessorsLinkedSet(a: Node, exit: 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 + if (item.exitFinalNode) continue + set += item + visited += item + if (item != exit) { + for (edge in item.dstEdges) { + queue.queue(edge.dst) + } + } + } + return set + } - val nodeNode = scgraph.getNode(node) - val targets = scgraph.getOut(node) + // @TODO: Optimize performance! + // @TODO: We should use the parent Strong Component to determine reachable nodes. + 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) + val bSet = getNodeSuccessorsLinkedSet(b, exit, checkRendered) + //for (item in bSet) if (item in aSet) return item + //for (item in aSet) if (item in bSet) return item - val nodeSrc = nodeNode.nodes.last() - val nodeDsts = targets.map { scgraph.getNode(it).nodes.first() } + 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 + } - 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) } + fun findCommonSuccessorNotRendered(nodes: List, exit: Node?): Node? { + return nodes.reduce { acc, node -> + findCommonSuccessorNotRendered(acc!!, node!!, exit) ?: acc ?: node + } + } + } + + companion object { + fun AstExpr.dump() = this.dump(AstTypes(com.jtransc.gen.TargetName("js"))) + fun AstStm.dump() = this.dump(AstTypes(com.jtransc.gen.TargetName("js"))) + } + + fun renderComponents(pg: StrongComponentGraph? = null, g: StrongComponentGraph, entry: Node, exit: Node?, ctx: RenderContext = RenderContext(g.graph), level: Int = 0): AstStm { + if (level > 6) { + //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 locallyExplored = LinkedHashSet() + + 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 }}" } - val edge = nodeDsts.map { getEdge(nodeSrc, it) }.filterNotNull().first() - val cond = edge.cond + fun List.toLString() = this.map { "L${it.index}" }.toString() - // 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()) + loop@ while (node != null && node != exit) { + 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}" } + locallyExplored += node + val prevNode = node + ctx.rendered += node + val component = g.findComponentWith(node) + val isMultiNodeLoop = component.isMultiNodeLoop() + val isSingleNodeLoop = component.isSingleNodeLoop() + + // Loop + if (isMultiNodeLoop || isSingleNodeLoop) { + trace { "$indent- LOOP csize=${component.size} : $node" } + val outs = component.getExternalOutputsNodes() + 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) { + 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()}") + } } - // IF-ELSE - else { - stms += AstStm.IF_ELSE(cond.not(), branches[0].stm(), branches[1].stm()) + + 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}" } + //trace { "$indent:: ${component}" } + + ctx.loopStarts[entryNode] = loopName + ctx.loopEnds[exitNode] = loopName + + val cond = true.lit + + + out += AstStm.WHILE( + loopName, + cond, + if (isSingleNodeLoop) { + trace { "$indent- render single node: renderNoLoops" } + val out2 = arrayListOf() + renderNoLoops(pg, g, out2, node, exitNode, ctx, level) + out2.stmsWithoutNops + } else { + trace { "$indent- render multi node: renderComponents (${entryNode.index} - ${exitNode.index})" } + 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() + + ctx.loopEnds -= exitNode + ctx.loopStarts -= entryNode + + node = exitNode + } + // Not a loop + else { + node = renderNoLoops(pg, g, out, node, exit, ctx, level = level) + } + + if (node == prevNode) invalidOp("Infinite loop detected") + //if (node?.exitFinalNode == true) break + } + return out.stmsWithoutNops + } + + val Iterable.stmsWithoutNopsAndLineList: List get() = this.filter { !it.isNopOrLine() } + val Iterable.stmsWithoutNops: AstStm get() = this.stmsWithoutNopsAndLineList.stm() + + 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 + + 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) { + trace { "$indent- Last node" } + } else { + trace { "$indent- Node continuing" } } - stms += renderInternal(common, endnode) + if (node.dstEdges.size == 0) return null + } + 2 -> { + } else -> { - val common = lookup.common(targets) - val branches = targets.map { branch -> renderInternal(branch, common) } + //TODO() + } + } + + 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) { + 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) { + val breakOrContinue = getNodeContinueOrBreak(firstCondNode) + out += AstStm.IF(firstCondEdge.cond!!, breakOrContinue ?: firstCondNode.body.stm()) + return node.next + } - println(branches) - println("COMMON: $common") - //println(outNode.next) - //println(outNode.possibleNextNodes.size) + var ifsAdded = 0 + for (e in node.dstEdgesButNext) { + 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) { + 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 { + 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 + + if (debug) { + println("----") + } + + return common + } + + trace { "$indent- Node IF (and else?)" } + val ifBody = node.next!! + 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(pg, g, ifBody, endOfIfNode, 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!") + + // 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!!.not(), + renderComponents(pg, g, ifBody, common, ctx, level = level + 1), + renderComponents(pg, g, endOfIfNode, common, ctx, level = level + 1) + ).optimizeIfElse() + } + } + + } + // IF+ELSE + else -> out += AstStm.IF_ELSE( + endOfIfEdge.cond!!.not(), + renderComponents(pg, g, ifBody, common, ctx, level = level + 1), + renderComponents(pg, g, endOfIfNode, common, ctx, level = level + 1) + ).optimizeIfElse() + } + return common + + + } + + 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 + val splitted = parent.graph.tarjanStronglyConnectedComponentsAlgorithm { src, dst -> dst != entry } + return splitted + } + + 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() + } + + 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 + @Suppress("FoldInitializerAndIfToElvis") + if (bodyValue !is AstStm.STMS) return this + + val stms = bodyValue.stms.unboxed.stmsWithoutNopsAndLineList.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 + // var n = 0; do { } while (n++ < 10); console.log(n); // WORKING: 11 + } + + fun AstStm.IF_ELSE.optimizeIfElse(): AstStm { + 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) || (st is AstStm.THROW)) { + return listOf( + AstStm.IF(cond.value, st), + sf + ).stms + } + // Guard clause! + if ((sf is AstStm.RETURN) || (sf is AstStm.THROW)) { + return listOf( + AstStm.IF(cond.value.not(), sf), + st + ).stms } - return stms + return this } } diff --git a/jtransc-core/src/com/jtransc/graph/StrongComponent.kt b/jtransc-core/src/com/jtransc/graph/StrongComponent.kt index 7e432ab6..ab0cbb59 100644 --- a/jtransc-core/src/com/jtransc/graph/StrongComponent.kt +++ b/jtransc-core/src/com/jtransc/graph/StrongComponent.kt @@ -6,18 +6,29 @@ 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: 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) { 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() + + override fun equals(other: Any?): Boolean { + return (other is StrongComponent<*>) && nodes == other.nodes + } } -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] @@ -39,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]!! @@ -47,6 +63,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/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 5338f762..75b6b761 100644 --- a/jtransc-core/test/com/jtransc/graph/RelooperTest.kt +++ b/jtransc-core/test/com/jtransc/graph/RelooperTest.kt @@ -1,26 +1,82 @@ 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 com.jtransc.text.Indenter +import org.junit.Assert.assertEquals import org.junit.Test class RelooperTest { val types = AstTypes(TargetName("js")) - val relooper = Relooper(types) + val relooper = Relooper(types, name = "test", debug = true) - private fun stmt(name:String) = types.build2 { SET(INT.local(name), 1.lit) } + @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; + """) + } - /* - @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()) + @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; + } + else { + c = 1; + } + d = 1; + """) } + //@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")) val B = relooper.node(stmt("b")) @@ -42,30 +98,268 @@ 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 testDoubleWhile() = relooperTest { + // A -> B -> C -> D -> E + // /|\ * | + // |_________/ + 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)) { + loop0: while (true) { + B = 1; + loop1: while (true) { + C = 1; + if (condLoopInContinue) { + continue loop1; + } + if (condLoopOutBreak) { + break loop0; + } + break loop1; + } + D = 1; + if (condLoopOutContinue) { + continue loop0; + } + if (condLoopOutBreak) { + break loop0; + } + break loop0; + } + } + E = 1; + """) } - @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 + 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(""" + L0 = 1; + loop0: do { + L1 = 1; + if ((!l1_l3)) { + L2 = 1; + loop1: do { + L4 = 1; + } while (l4_l4); + L5 = 1; + } + L3 = 1; + } while (l3_l1); + L6 = 1; + L8 = 1; + """) } - */ + + @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 = 1; + loop0: do { + L1 = 1; + } while (lI0 < lI1); + L2 = 1; + L4 = 1; + """) + + //* 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; + //} + } + + @Test + fun testSimpleWhile() = relooperTest { + val L0 = node("L0") + val L1 = node(AstStm.NOP("just_for_if")) + 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) + + // @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: while ((!l2_l3)) { + L2 = 1; + } + L3 = 1; + L6 = 1; + """) + + // * 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; + //} + } + + @Test + fun testMixed1() = relooperTest { + val L0 = node("L0") // lA3 = new java.util.ArrayList(); lI4 = 0; lI5 = 0; + 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); + val L5 = node("L5") // lI4 = (lI4 + 1); + 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(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))] + 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(""" + L0 = 1; + loop0: while ((!l1_l3)) { + L2 = 1; + if ((!l2_l5)) { + L4 = 1; + if ((!l4_l5)) { + break loop0; + } + } + L5 = 1; + } + L3 = 1; + if ((!l3_l10)) { + L9 = 1; + } + L10 = 1; + """) + + // @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)) + } + + inline fun relooperTest(callback: Relooper.() -> Unit): Unit = callback(relooper) + + 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)) + 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/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 new file mode 100644 index 00000000..01b9a04e --- /dev/null +++ b/jtransc-gen-common-tests/src/relooper/RelooperTest.java @@ -0,0 +1,295 @@ +package relooper; + +import com.jtransc.annotation.JTranscRelooper; +import com.jtransc.io.JTranscConsole; + +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.Arrays; + +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(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)); + + simpleDoWhile(0, 5); + simpleWhile(0, 5); + 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)); + + 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')); + + //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)); + + JTranscConsole.log(switchCase(new Term(), 0, new Term())); + + floatComparisons(0f); + floatComparisons(1f); + floatComparisons(Float.NaN); + floatComparisons(-Float.NaN); + } + + @JTranscRelooper + static public int simpleIf(int a, int b) { + if (a < b) { + return -1; + } else { + return +1; + } + } + + @JTranscRelooper + static public int composedIfAnd(int a, int b) { + if (a < b && a >= 0) { + return -1; + } else { + return +1; + } + } + + @JTranscRelooper + static public int composedIfOr(int a, int b) { + if (a < b || a >= 0) { + return -1; + } else { + return +1; + } + } + + @JTranscRelooper + 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; + } + + @JTranscRelooper + static public int simpleWhile(int a, int b) { + b++; + + while (a < b) { + JTranscConsole.log(a); + a++; + } + JTranscConsole.log(a); + JTranscConsole.log(b); + + return b; + } + + @JTranscRelooper + static public int simpleFor(int a, int b) { + for (int n = 1; n < b; n++) { + JTranscConsole.log(a + n); + } + return b; + } + + @JTranscRelooper + 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()]); + } + + @JTranscRelooper + 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; + } + + @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; + } + + //@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 + 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; + } + + @JTranscRelooper + static private boolean myswitch(int a) { + JTranscConsole.log("myswitch: " + a); + switch (a) { + case 0: + JTranscConsole.log(0); + break; + case 1: + JTranscConsole.log(1); + case 6: + case 2: + JTranscConsole.log(2); + break; + case 3: + return false; + } + JTranscConsole.log("end myswitch"); + return 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; + 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 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")) 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..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,6 +131,8 @@ class JsGenerator(injector: Injector) : CommonGenerator(injector) { @Suppress("UNCHECKED_CAST") override fun writeClasses(output: SyncVfsFile) { + output[outputFileBaseName].remove() + output["$outputFileBaseName.map"].remove() 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 { 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..9f7a1071 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,9 @@ 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 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)) 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..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 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..aad28071 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 @@ -284,49 +284,49 @@ org.jetbrains.kotlin kotlin-stdlib jar - 1.2.10 + 1.2.20 org.jetbrains.kotlin kotlin-runtime jar - 1.2.10 + 1.2.20 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 @@ -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-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() { 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 }}; 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 diff --git a/jtransc-utils/src/com/jtransc/ds/collectionutils.kt b/jtransc-utils/src/com/jtransc/ds/collectionutils.kt index 9eacef9b..85bd4752 100644 --- a/jtransc-utils/src/com/jtransc/ds/collectionutils.kt +++ b/jtransc-utils/src/com/jtransc/ds/collectionutils.kt @@ -49,6 +49,10 @@ 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 + + operator fun invoke(value: T): T = queue(value) fun queue(value: T): T { data.addFirst(value) 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 {