Skip to content
174 changes: 174 additions & 0 deletions jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* Copyright 2022 UnitTestBot contributors (utbot.org)
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jacodb.ets.dsl

import org.jacodb.ets.utils.IdentityHashSet
import java.util.IdentityHashMap

data class Block(
val id: Int,
val statements: List<BlockStmt>,
)

data class BlockCfg(
val blocks: List<Block>,
val successors: Map<Int, List<Int>>,
)

fun Program.toBlockCfg(): BlockCfg {
val labelToNode: MutableMap<String, Node> = hashMapOf()
val targets: MutableSet<Node> = IdentityHashSet()

fun findLabels(nodes: List<Node>) {
if (nodes.lastOrNull() is Label) {
error("Label at the end of the block: $nodes")

Check warning on line 38 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt#L38

Added line #L38 was not covered by tests
}
for ((stmt, next) in nodes.zipWithNext()) {
if (stmt is Label) {
check(next !is Label) { "Two labels in a row: $stmt, $next" }
check(next !is Goto) { "Label followed by goto: $stmt, $next" }
check(stmt.name !in labelToNode) { "Duplicate label: ${stmt.name}" }
labelToNode[stmt.name] = next

Check warning on line 45 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt#L45

Added line #L45 was not covered by tests
}
}
for (node in nodes) {
if (node is If) {
findLabels(node.thenBranch)
findLabels(node.elseBranch)
}
if (node is Goto) {
targets += labelToNode[node.targetLabel] ?: error("Unknown label: ${node.targetLabel}")
}
}
}

findLabels(nodes)

val blocks: MutableList<Block> = mutableListOf()
val successors: MutableMap<Int, List<Int>> = hashMapOf()
val stmtToBlock: MutableMap<BlockStmt, Int> = IdentityHashMap()
val nodeToStmt: MutableMap<Node, BlockStmt> = IdentityHashMap()

fun buildBlocks(nodes: List<Node>): Pair<Int, Int>? {
if (nodes.isEmpty()) return null

lateinit var currentBlock: MutableList<BlockStmt>

fun newBlock(): Block {
currentBlock = mutableListOf()
val block = Block(blocks.size, currentBlock)
blocks += block
return block
}

var block = newBlock()
val firstBlockId = block.id

for (node in nodes) {
if (node is Label) continue

if (node in targets && currentBlock.isNotEmpty()) {
block.statements.forEach { stmtToBlock[it] = block.id }
val prevBlock = block
block = newBlock()
successors[prevBlock.id] = listOf(block.id)

Check warning on line 88 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt#L85-L88

Added lines #L85 - L88 were not covered by tests
}

if (node !is Goto) {
val stmt = when (node) {
Nop -> BlockNop
is Assign -> BlockAssign(node.target, node.expr)
is Return -> BlockReturn(node.expr)
is If -> BlockIf(node.condition)
else -> error("Unexpected node: $node")

Check warning on line 97 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt#L97

Added line #L97 was not covered by tests
}
nodeToStmt[node] = stmt
currentBlock += stmt
}

if (node is If) {
block.statements.forEach { stmtToBlock[it] = block.id }
val ifBlock = block
block = newBlock()

val thenBlocks = buildBlocks(node.thenBranch)
val elseBlocks = buildBlocks(node.elseBranch)

when {
thenBlocks != null && elseBlocks != null -> {
val (thenStart, thenEnd) = thenBlocks
val (elseStart, elseEnd) = elseBlocks
successors[ifBlock.id] = listOf(thenStart, elseStart) // (true, false) branches
when (blocks[thenEnd].statements.lastOrNull()) {

Check warning on line 116 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt#L113-L116

Added lines #L113 - L116 were not covered by tests
is BlockReturn -> {}
is BlockIf -> error("Unexpected if statement at the end of the block")
else -> successors[thenEnd] = listOf(block.id)

Check warning on line 119 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt#L119

Added line #L119 was not covered by tests
}
when (blocks[elseEnd].statements.lastOrNull()) {

Check warning on line 121 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt#L121

Added line #L121 was not covered by tests
is BlockReturn -> {}
is BlockIf -> error("Unexpected if statement at the end of the block")
else -> successors[elseEnd] = listOf(block.id)

Check warning on line 124 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt#L124

Added line #L124 was not covered by tests
}
}

thenBlocks != null -> {
val (thenStart, thenEnd) = thenBlocks
successors[ifBlock.id] = listOf(thenStart, block.id) // (true, false) branches
when (blocks[thenEnd].statements.lastOrNull()) {
is BlockReturn -> {}
is BlockIf -> error("Unexpected if statement at the end of the block")
else -> successors[thenEnd] = listOf(block.id)
}
}

elseBlocks != null -> {
val (elseStart, elseEnd) = elseBlocks
successors[ifBlock.id] = listOf(block.id, elseStart) // (true, false) branches
when (blocks[elseEnd].statements.lastOrNull()) {

Check warning on line 141 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt#L139-L141

Added lines #L139 - L141 were not covered by tests
is BlockReturn -> {}
is BlockIf -> error("Unexpected if statement at the end of the block")
else -> successors[elseEnd] = listOf(block.id)

Check warning on line 144 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt#L144

Added line #L144 was not covered by tests
}
}

else -> {
successors[ifBlock.id] = listOf(block.id)

Check warning on line 149 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt#L149

Added line #L149 was not covered by tests
}
}
} else if (node is Goto) {
val targetNode = labelToNode[node.targetLabel] ?: error("Unknown label: ${node.targetLabel}")
val target = nodeToStmt[targetNode] ?: error("No statement for $targetNode")
val targetBlockId = stmtToBlock[target] ?: error("No block for $target")
successors[block.id] = listOf(targetBlockId)
block.statements.forEach { stmtToBlock[it] = block.id }
block = newBlock()

Check warning on line 158 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockCfg.kt#L156-L158

Added lines #L156 - L158 were not covered by tests
} else if (node is Return) {
successors[block.id] = emptyList()
break
}
}

block.statements.forEach { stmtToBlock[it] = block.id }
val lastBlockId = block.id

return Pair(firstBlockId, lastBlockId)
}

buildBlocks(nodes)

return BlockCfg(blocks, successors)
}
34 changes: 34 additions & 0 deletions jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/BlockStmt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2022 UnitTestBot contributors (utbot.org)
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jacodb.ets.dsl

sealed interface BlockStmt

data object BlockNop : BlockStmt

data class BlockAssign(
val target: Local,
val expr: Expr,
) : BlockStmt

data class BlockReturn(
val expr: Expr,
) : BlockStmt

data class BlockIf(
val condition: Expr,
) : BlockStmt
67 changes: 67 additions & 0 deletions jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2022 UnitTestBot contributors (utbot.org)
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jacodb.ets.dsl

import org.jacodb.ets.utils.view

private fun main() {
val prog = program {
assign(local("i"), param(0))

Check warning on line 23 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt#L22-L23

Added lines #L22 - L23 were not covered by tests

ifStmt(gt(local("i"), const(10.0))) {
ifStmt(eq(local("i"), const(42.0))) {
ret(local("i"))
`else` {
assign(local("i"), const(10.0))
}
}
nop()
}

Check warning on line 33 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt#L25-L33

Added lines #L25 - L33 were not covered by tests

label("loop")
ifStmt(gt(local("i"), const(0.0))) {
assign(local("i"), sub(local("i"), const(1.0)))
goto("loop")
`else` {
ret(local("i"))
}
}

Check warning on line 42 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt#L35-L42

Added lines #L35 - L42 were not covered by tests

ret(const(42.0)) // unreachable
}

Check warning on line 45 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt#L44-L45

Added lines #L44 - L45 were not covered by tests

val doView = false

Check warning on line 47 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt#L47

Added line #L47 was not covered by tests

println("PROGRAM:")
println("-----")
println(prog.toText())
println("-----")

Check warning on line 52 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt#L49-L52

Added lines #L49 - L52 were not covered by tests

println("=== PROGRAM:")
println(prog.toDot())
if (doView) view(prog.toDot(), name = "program")

Check warning on line 56 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt#L54-L56

Added lines #L54 - L56 were not covered by tests

val blockCfg = prog.toBlockCfg()
println("=== BLOCK CFG:")
println(blockCfg.toDot())
if (doView) view(blockCfg.toDot(), name = "block")

Check warning on line 61 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt#L58-L61

Added lines #L58 - L61 were not covered by tests

val linearCfg = blockCfg.linearize()
println("=== LINEARIZED CFG:")
println(linearCfg.toDot())
if (doView) view(linearCfg.toDot(), name = "linear")
}

Check warning on line 67 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/DSL.kt#L63-L67

Added lines #L63 - L67 were not covered by tests
60 changes: 60 additions & 0 deletions jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2022 UnitTestBot contributors (utbot.org)
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jacodb.ets.dsl

sealed interface Expr

data class Local(val name: String) : Expr {
override fun toString() = name
}

data class Parameter(val index: Int) : Expr {
override fun toString() = "param($index)"
}

object ThisRef : Expr {
override fun toString() = "this"

Check warning on line 30 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt#L30

Added line #L30 was not covered by tests
}

data class Constant(val value: Double) : Expr {
override fun toString() = "const($value)"
}

enum class BinaryOperator {
AND, OR,
EQ, NEQ, LT, LTE, GT, GTE,
ADD, SUB, MUL, DIV
}

Check warning on line 41 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt#L41

Added line #L41 was not covered by tests

data class BinaryExpr(
val operator: BinaryOperator,
val left: Expr,
val right: Expr,
) : Expr {
override fun toString() = "${operator.name.lowercase()}($left, $right)"
}

enum class UnaryOperator {
NOT, NEG
}

Check warning on line 53 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt#L53

Added line #L53 was not covered by tests

data class UnaryExpr(
val operator: UnaryOperator,
val expr: Expr,

Check warning on line 57 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt#L55-L57

Added lines #L55 - L57 were not covered by tests
) : Expr {
override fun toString() = "${operator.name.lowercase()}($expr)"

Check warning on line 59 in jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt

View check run for this annotation

Codecov / codecov/patch

jacodb-ets/src/main/kotlin/org/jacodb/ets/dsl/Expr.kt#L59

Added line #L59 was not covered by tests
}
Loading