Skip to content

Commit d03090c

Browse files
authored
Add CFG DSL (#294)
1 parent deeaae6 commit d03090c

File tree

18 files changed

+1517
-5
lines changed

18 files changed

+1517
-5
lines changed
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright 2022 UnitTestBot contributors (utbot.org)
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jacodb.ets.dsl
18+
19+
import org.jacodb.ets.utils.IdentityHashSet
20+
import java.util.IdentityHashMap
21+
22+
data class Block(
23+
val id: Int,
24+
val statements: List<BlockStmt>,
25+
)
26+
27+
data class BlockCfg(
28+
val blocks: List<Block>,
29+
val successors: Map<Int, List<Int>>,
30+
)
31+
32+
fun Program.toBlockCfg(): BlockCfg {
33+
val labelToNode: MutableMap<String, Node> = hashMapOf()
34+
val targets: MutableSet<Node> = IdentityHashSet()
35+
36+
fun findLabels(nodes: List<Node>) {
37+
if (nodes.lastOrNull() is Label) {
38+
error("Label at the end of the block: $nodes")
39+
}
40+
for ((stmt, next) in nodes.zipWithNext()) {
41+
if (stmt is Label) {
42+
check(next !is Label) { "Two labels in a row: $stmt, $next" }
43+
check(next !is Goto) { "Label followed by goto: $stmt, $next" }
44+
check(stmt.name !in labelToNode) { "Duplicate label: ${stmt.name}" }
45+
labelToNode[stmt.name] = next
46+
}
47+
}
48+
for (node in nodes) {
49+
if (node is If) {
50+
findLabels(node.thenBranch)
51+
findLabels(node.elseBranch)
52+
}
53+
if (node is Goto) {
54+
targets += labelToNode[node.targetLabel] ?: error("Unknown label: ${node.targetLabel}")
55+
}
56+
}
57+
}
58+
59+
findLabels(nodes)
60+
61+
val blocks: MutableList<Block> = mutableListOf()
62+
val successors: MutableMap<Int, List<Int>> = hashMapOf()
63+
val stmtToBlock: MutableMap<BlockStmt, Int> = IdentityHashMap()
64+
val nodeToStmt: MutableMap<Node, BlockStmt> = IdentityHashMap()
65+
66+
fun buildBlocks(nodes: List<Node>): Pair<Int, Int>? {
67+
if (nodes.isEmpty()) return null
68+
69+
lateinit var currentBlock: MutableList<BlockStmt>
70+
71+
fun newBlock(): Block {
72+
currentBlock = mutableListOf()
73+
val block = Block(blocks.size, currentBlock)
74+
blocks += block
75+
return block
76+
}
77+
78+
var block = newBlock()
79+
val firstBlockId = block.id
80+
81+
for (node in nodes) {
82+
if (node is Label) continue
83+
84+
if (node in targets && currentBlock.isNotEmpty()) {
85+
block.statements.forEach { stmtToBlock[it] = block.id }
86+
val prevBlock = block
87+
block = newBlock()
88+
successors[prevBlock.id] = listOf(block.id)
89+
}
90+
91+
if (node !is Goto) {
92+
val stmt = when (node) {
93+
Nop -> BlockNop
94+
is Assign -> BlockAssign(node.target, node.expr)
95+
is Return -> BlockReturn(node.expr)
96+
is If -> BlockIf(node.condition)
97+
else -> error("Unexpected node: $node")
98+
}
99+
nodeToStmt[node] = stmt
100+
currentBlock += stmt
101+
}
102+
103+
if (node is If) {
104+
block.statements.forEach { stmtToBlock[it] = block.id }
105+
val ifBlock = block
106+
block = newBlock()
107+
108+
val thenBlocks = buildBlocks(node.thenBranch)
109+
val elseBlocks = buildBlocks(node.elseBranch)
110+
111+
when {
112+
thenBlocks != null && elseBlocks != null -> {
113+
val (thenStart, thenEnd) = thenBlocks
114+
val (elseStart, elseEnd) = elseBlocks
115+
successors[ifBlock.id] = listOf(thenStart, elseStart) // (true, false) branches
116+
when (blocks[thenEnd].statements.lastOrNull()) {
117+
is BlockReturn -> {}
118+
is BlockIf -> error("Unexpected if statement at the end of the block")
119+
else -> successors[thenEnd] = listOf(block.id)
120+
}
121+
when (blocks[elseEnd].statements.lastOrNull()) {
122+
is BlockReturn -> {}
123+
is BlockIf -> error("Unexpected if statement at the end of the block")
124+
else -> successors[elseEnd] = listOf(block.id)
125+
}
126+
}
127+
128+
thenBlocks != null -> {
129+
val (thenStart, thenEnd) = thenBlocks
130+
successors[ifBlock.id] = listOf(thenStart, block.id) // (true, false) branches
131+
when (blocks[thenEnd].statements.lastOrNull()) {
132+
is BlockReturn -> {}
133+
is BlockIf -> error("Unexpected if statement at the end of the block")
134+
else -> successors[thenEnd] = listOf(block.id)
135+
}
136+
}
137+
138+
elseBlocks != null -> {
139+
val (elseStart, elseEnd) = elseBlocks
140+
successors[ifBlock.id] = listOf(block.id, elseStart) // (true, false) branches
141+
when (blocks[elseEnd].statements.lastOrNull()) {
142+
is BlockReturn -> {}
143+
is BlockIf -> error("Unexpected if statement at the end of the block")
144+
else -> successors[elseEnd] = listOf(block.id)
145+
}
146+
}
147+
148+
else -> {
149+
successors[ifBlock.id] = listOf(block.id)
150+
}
151+
}
152+
} else if (node is Goto) {
153+
val targetNode = labelToNode[node.targetLabel] ?: error("Unknown label: ${node.targetLabel}")
154+
val target = nodeToStmt[targetNode] ?: error("No statement for $targetNode")
155+
val targetBlockId = stmtToBlock[target] ?: error("No block for $target")
156+
successors[block.id] = listOf(targetBlockId)
157+
block.statements.forEach { stmtToBlock[it] = block.id }
158+
block = newBlock()
159+
} else if (node is Return) {
160+
successors[block.id] = emptyList()
161+
break
162+
}
163+
}
164+
165+
block.statements.forEach { stmtToBlock[it] = block.id }
166+
val lastBlockId = block.id
167+
168+
return Pair(firstBlockId, lastBlockId)
169+
}
170+
171+
buildBlocks(nodes)
172+
173+
return BlockCfg(blocks, successors)
174+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2022 UnitTestBot contributors (utbot.org)
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jacodb.ets.dsl
18+
19+
sealed interface BlockStmt
20+
21+
data object BlockNop : BlockStmt
22+
23+
data class BlockAssign(
24+
val target: Local,
25+
val expr: Expr,
26+
) : BlockStmt
27+
28+
data class BlockReturn(
29+
val expr: Expr,
30+
) : BlockStmt
31+
32+
data class BlockIf(
33+
val condition: Expr,
34+
) : BlockStmt
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2022 UnitTestBot contributors (utbot.org)
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jacodb.ets.dsl
18+
19+
import org.jacodb.ets.utils.view
20+
21+
private fun main() {
22+
val prog = program {
23+
assign(local("i"), param(0))
24+
25+
ifStmt(gt(local("i"), const(10.0))) {
26+
ifStmt(eq(local("i"), const(42.0))) {
27+
ret(local("i"))
28+
`else` {
29+
assign(local("i"), const(10.0))
30+
}
31+
}
32+
nop()
33+
}
34+
35+
label("loop")
36+
ifStmt(gt(local("i"), const(0.0))) {
37+
assign(local("i"), sub(local("i"), const(1.0)))
38+
goto("loop")
39+
`else` {
40+
ret(local("i"))
41+
}
42+
}
43+
44+
ret(const(42.0)) // unreachable
45+
}
46+
47+
val doView = false
48+
49+
println("PROGRAM:")
50+
println("-----")
51+
println(prog.toText())
52+
println("-----")
53+
54+
println("=== PROGRAM:")
55+
println(prog.toDot())
56+
if (doView) view(prog.toDot(), name = "program")
57+
58+
val blockCfg = prog.toBlockCfg()
59+
println("=== BLOCK CFG:")
60+
println(blockCfg.toDot())
61+
if (doView) view(blockCfg.toDot(), name = "block")
62+
63+
val linearCfg = blockCfg.linearize()
64+
println("=== LINEARIZED CFG:")
65+
println(linearCfg.toDot())
66+
if (doView) view(linearCfg.toDot(), name = "linear")
67+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2022 UnitTestBot contributors (utbot.org)
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jacodb.ets.dsl
18+
19+
sealed interface Expr
20+
21+
data class Local(val name: String) : Expr {
22+
override fun toString() = name
23+
}
24+
25+
data class Parameter(val index: Int) : Expr {
26+
override fun toString() = "param($index)"
27+
}
28+
29+
object ThisRef : Expr {
30+
override fun toString() = "this"
31+
}
32+
33+
data class Constant(val value: Double) : Expr {
34+
override fun toString() = "const($value)"
35+
}
36+
37+
enum class BinaryOperator {
38+
AND, OR,
39+
EQ, NEQ, LT, LTE, GT, GTE,
40+
ADD, SUB, MUL, DIV
41+
}
42+
43+
data class BinaryExpr(
44+
val operator: BinaryOperator,
45+
val left: Expr,
46+
val right: Expr,
47+
) : Expr {
48+
override fun toString() = "${operator.name.lowercase()}($left, $right)"
49+
}
50+
51+
enum class UnaryOperator {
52+
NOT, NEG
53+
}
54+
55+
data class UnaryExpr(
56+
val operator: UnaryOperator,
57+
val expr: Expr,
58+
) : Expr {
59+
override fun toString() = "${operator.name.lowercase()}($expr)"
60+
}

0 commit comments

Comments
 (0)