From 124d0b167faa88ed8a96d8e3f332700317871ddc Mon Sep 17 00:00:00 2001 From: TarCV Date: Sat, 18 Apr 2020 17:15:50 +0300 Subject: [PATCH 1/9] Update to latest Kotlin --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cd3c135..5f4a317 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'org.jetbrains.kotlin.multiplatform' version '1.3.31' + id 'org.jetbrains.kotlin.multiplatform' version '1.3.72' id 'com.github.johnrengelman.shadow' version '5.0.0' } From 1bf2c119de69ee3762dff610aedf9967fad5bb6b Mon Sep 17 00:00:00 2001 From: TarCV Date: Sun, 19 Apr 2020 02:36:15 +0300 Subject: [PATCH 2/9] Bot commands should not be converted to literal nodes --- .../tarcv/zandronum/debotc/BotCommand.kt | 2 +- .../tarcv/zandronum/debotc/DataHeaders.kt | 77 ++-- .../tarcv/zandronum/debotc/Decompiler.kt | 371 +++++++++--------- .../github/tarcv/zandronum/debotc/Nodes.kt | 17 +- .../zandronum/debotc/CounterexamplesTest.kt | 26 +- 5 files changed, 284 insertions(+), 209 deletions(-) diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt index 5efd09d..7aa87b6 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt @@ -124,7 +124,7 @@ enum class BotCommand( RETURNVAL_VOID -> DONT_PUSHES_TO_STACK } - return FunctionNode(readableName, args, addsTo) + return FunctionNode(readableName, args, addsTo, hasNonStackDeps = true) } } diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt index 28f46a0..fe96383 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt @@ -235,85 +235,113 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_ASSIGNGLOBALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} = ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + "\$global${command.arguments[0]} = ${stackArgs[0]};" + } } }, DH_ADDGLOBALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} += ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + "\$global${command.arguments[0]} += ${stackArgs[0]};" + } } }, DH_SUBGLOBALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} -= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + "\$global${command.arguments[0]} -= ${stackArgs[0]};" + } } }, DH_MULGLOBALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} *= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + "\$global${command.arguments[0]} *= ${stackArgs[0]};" + } } }, DH_DIVGLOBALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} /= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + "\$global${command.arguments[0]} /= ${stackArgs[0]};" + } } }, DH_MODGLOBALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$global${command.arguments[0]} %= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + "\$global${command.arguments[0]} %= ${stackArgs[0]};" + } } }, DH_INCLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(0, DONT_PUSHES_TO_STACK) { "\$local${command.arguments[0]}++;" } + return CustomStackConsumingNode(0, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { + "\$local${command.arguments[0]}++;" + } } }, DH_DECLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(0, DONT_PUSHES_TO_STACK) { "\$local${command.arguments[0]}--;" } + return CustomStackConsumingNode(0, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { + "\$local${command.arguments[0]}--;" + } } }, DH_ASSIGNLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} = ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + "\$local${command.arguments[0]} = ${stackArgs[0]};" + } } }, DH_ADDLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} += ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + "\$local${command.arguments[0]} += ${stackArgs[0]};" + } } }, DH_SUBLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} -= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + "\$local${command.arguments[0]} -= ${stackArgs[0]};" + } } }, DH_MULLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} *= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + "\$local${command.arguments[0]} *= ${stackArgs[0]};" + } } }, DH_DIVLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} /= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + "\$local${command.arguments[0]} /= ${stackArgs[0]};" + } } }, DH_MODLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> "\$local${command.arguments[0]} %= ${stackArgs[0]};" } + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + "\$local${command.arguments[0]} %= ${stackArgs[0]};" + } } }, DH_CASEGOTO(2) { @@ -332,7 +360,7 @@ enum class DataHeaders(requiredArgs: Int = 0) }, DH_INCGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}]++;" } @@ -340,56 +368,56 @@ enum class DataHeaders(requiredArgs: Int = 0) }, DH_DECGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}]--;" } } }, DH_ASSIGNGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] = ${stackArgs[1]};" } } }, DH_ADDGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] += ${stackArgs[1]};" } } }, DH_SUBGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] -= ${stackArgs[1]};" } } }, DH_MULGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] *= ${stackArgs[1]};" } } }, DH_DIVGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] /= ${stackArgs[1]};" } } }, DH_MODGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK) { stackArgs -> + return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] %= ${stackArgs[1]};" } } }, DH_PUSHGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(1, ADDS_TO_NORMAL_STACK) { stackArgs -> + return CustomStackConsumingNode(1, ADDS_TO_NORMAL_STACK, hasNonStackDeps = true) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}]" } } @@ -425,7 +453,8 @@ enum class DataHeaders(requiredArgs: Int = 0) }, DH_ARRAYSET{ override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return FunctionNode("memset", consumesNormalStack(3), DONT_PUSHES_TO_STACK) + return FunctionNode("memset", consumesNormalStack(3), DONT_PUSHES_TO_STACK, + hasNonStackDeps = true) } }, diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt index e01f023..7887a24 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt @@ -89,166 +89,6 @@ class Decompiler { } } - private fun compactAndStringifyNodes(rootNode: BaseNode): String { - var scriptText = "" - - var wasAtLeastOneChange = false - - wasAtLeastOneChange = optimizeWhile(rootNode) { node -> - recoverComplexNodes(node) - } || wasAtLeastOneChange - - wasAtLeastOneChange = optimizeWhile(rootNode) { node -> - packToTextNodes(node) - } || wasAtLeastOneChange - - assert(rootNode.outputs.size == 1) - assert(rootNode.nextNode.inputs.size == 1) - assert(rootNode.nextNode.outputs.size == 0 || rootNode.nextNode.outputs.size == 1) - if (rootNode.nextNode.outputs.size == 1) { - scriptText = (rootNode.nextNode as TextNode).asText.replace(Regex("^", MULTILINE), "\t\t") - assert(rootNode.nextNode.nextNode is EndNode) - } else { - assert(rootNode.nextNode is EndNode) - } - - return scriptText - } - - private fun packToTextNodes(node: BaseNode): Boolean { - var changed = false - - changed = packPairsToTextNodes(node) || changed - - changed = convertToTextNodes(node) || changed - - changed = removeUnusedLabel(node) || changed - - changed = removeUnusedLiterals(node) || changed - - changed = packIfBlockToText(node) || changed - - changed = packSwitchBlockToText(node) || changed - - return changed - } - - private fun packIfBlockToText(node: BaseNode): Boolean { - var changed = false - - node.outputs.copy().forEach { nextNode -> - if (nextNode.outputs.size == 2) { - var condition: String - var mainBranch: BaseNode - var elseBranch: BaseNode - val endingLabel: BaseNode - - when (nextNode) { - is IfNotGotoNode -> { - mainBranch = nextNode.nextNode - elseBranch = nextNode.jumpTargetNode - condition = nextNode.condition.argument.toString() - } - is IfGotoNode -> { - mainBranch = nextNode.jumpTargetNode - elseBranch = nextNode.nextNode - condition = invertCondition(nextNode.condition.argument.toString()) - } - else -> return@forEach - } - - if (mainBranch is TextNode - && canBeBranchingEnd(elseBranch) - && mainBranch.nextNode == elseBranch - ) { - endingLabel = elseBranch - } else if (canBeBranchingEnd(mainBranch) - && elseBranch is TextNode - && mainBranch == elseBranch.nextNode) { - endingLabel = mainBranch - mainBranch = elseBranch - elseBranch = endingLabel - condition = invertCondition(condition) - } else if (mainBranch is TextNode - && elseBranch is TextNode - && mainBranch.nextNode == elseBranch.nextNode - && canBeBranchingEnd(mainBranch.nextNode)) { - endingLabel = mainBranch.nextNode as LabelNode - } else { - return@forEach - } - - changed = true - - val elseBranchText = if (elseBranch != endingLabel) { - " else {${lineSeparator}${elseBranch.asText.indent()}${lineSeparator}}" - } else "" - val text = "if ($condition) {${lineSeparator}" + - "${mainBranch.asText.indent()}${lineSeparator}" + - "}$elseBranchText" - val newNode = TextNode(text) - - newNode.nextNode = endingLabel - ArrayList(nextNode.inputs).forEach { - it.outputs.replace(nextNode, newNode) - } - if (endingLabel.inputs.contains(nextNode)) { - nextNode.outputs.replace(endingLabel, nullNode) - } - if (endingLabel.inputs.contains(mainBranch)) { - endingLabel.inEdgeFrom[mainBranch].destroy() - } - if (endingLabel.inputs.contains(elseBranch)) { - endingLabel.inEdgeFrom[elseBranch].destroy() - } - } - } - - return changed - } - - private fun recoverComplexNodes(node: BaseNode): Boolean { - var changed = false - - changed = tryLiteralizeNextNode(node) || changed - - changed = joinNextLiteralNodes(node) || changed - - changed = inlineStackArgs(node) || changed - - changed = cleanupLiteralNode(node) || changed - - changed = joinNextSwitchNodes(node) || changed - - changed = removeUnusedLabel(node) || changed - - return changed - } - - private fun cleanupLiteralNode(node: BaseNode): Boolean { - var changed = false - - if (node.outputs.size > 0) { - val nextNode = node.nextNode - if (nextNode is LiteralNode) { - val returns = nextNode.returns() - val notConsumedReturns = returns.filter { !it.consumed } - if (returns.size != notConsumedReturns.size) { - changed = true - - if (notConsumedReturns.isNotEmpty()) { - val newNode = LiteralNode(prototypesFromReturns(notConsumedReturns)) - replaceNode(nextNode, newNode) - } else { - cutNode(nextNode) - } - } - } - } - - return changed - } - class NodeHolder( val nodes: ArrayList = ArrayList(), private var prevNode: BaseNode? = null @@ -326,14 +166,6 @@ class Decompiler { return nodeHolder.nodes } - private fun invertCondition(condition: String): String { - return if (Regex("^!\\(.+\\)$").matches(condition)) { - condition.substring(2, condition.length - 1) - } else { - "!($condition)" - } - } - fun parse(data0: UByteArray) { if (alreadyParsed) { throw IllegalStateException("parse() can be called only once") @@ -485,6 +317,180 @@ class Decompiler { states = ArrayList() states.add(State()) } + + companion object { + private fun compactAndStringifyNodes(rootNode: BaseNode): String { + var scriptText = "" + + compactNodes(rootNode) + + assert(rootNode.outputs.size == 1) + assert(rootNode.nextNode.inputs.size == 1) + assert(rootNode.nextNode.outputs.size == 0 || rootNode.nextNode.outputs.size == 1) + if (rootNode.nextNode.outputs.size == 1) { + scriptText = (rootNode.nextNode as TextNode).asText.replace(Regex("^", MULTILINE), "\t\t") + assert(rootNode.nextNode.nextNode is EndNode) + } else { + assert(rootNode.nextNode is EndNode) + } + + return scriptText + } + + fun compactNodes(rootNode: BaseNode) { + var wasAtLeastOneChange = false + + wasAtLeastOneChange = optimizeWhile(rootNode) { node -> + recoverComplexNodes(node) + } || wasAtLeastOneChange + + wasAtLeastOneChange = optimizeWhile(rootNode) { node -> + packToTextNodes(node) + } || wasAtLeastOneChange + } + + private fun packToTextNodes(node: BaseNode): Boolean { + var changed = false + + changed = packPairsToTextNodes(node) || changed + + changed = convertToTextNodes(node) || changed + + changed = removeUnusedLabel(node) || changed + + changed = removeUnusedLiterals(node) || changed + + changed = packIfBlockToText(node) || changed + + changed = packSwitchBlockToText(node) || changed + + return changed + } + + private fun packIfBlockToText(node: BaseNode): Boolean { + var changed = false + + node.outputs.copy().forEach { nextNode -> + if (nextNode.outputs.size == 2) { + var condition: String + var mainBranch: BaseNode + var elseBranch: BaseNode + val endingLabel: BaseNode + + when (nextNode) { + is IfNotGotoNode -> { + mainBranch = nextNode.nextNode + elseBranch = nextNode.jumpTargetNode + condition = nextNode.condition.argument.toString() + } + is IfGotoNode -> { + mainBranch = nextNode.jumpTargetNode + elseBranch = nextNode.nextNode + condition = invertCondition(nextNode.condition.argument.toString()) + } + else -> return@forEach + } + + if (mainBranch is TextNode + && canBeBranchingEnd(elseBranch) + && mainBranch.nextNode == elseBranch + ) { + endingLabel = elseBranch + } else if (canBeBranchingEnd(mainBranch) + && elseBranch is TextNode + && mainBranch == elseBranch.nextNode) { + endingLabel = mainBranch + mainBranch = elseBranch + elseBranch = endingLabel + condition = invertCondition(condition) + } else if (mainBranch is TextNode + && elseBranch is TextNode + && mainBranch.nextNode == elseBranch.nextNode + && canBeBranchingEnd(mainBranch.nextNode)) { + endingLabel = mainBranch.nextNode as LabelNode + } else { + return@forEach + } + + changed = true + + val elseBranchText = if (elseBranch != endingLabel) { + " else {${lineSeparator}${elseBranch.asText.indent()}${lineSeparator}}" + } else "" + val text = "if ($condition) {${lineSeparator}" + + "${mainBranch.asText.indent()}${lineSeparator}" + + "}$elseBranchText" + val newNode = TextNode(text) + + newNode.nextNode = endingLabel + ArrayList(nextNode.inputs).forEach { + it.outputs.replace(nextNode, newNode) + } + if (endingLabel.inputs.contains(nextNode)) { + nextNode.outputs.replace(endingLabel, nullNode) + } + if (endingLabel.inputs.contains(mainBranch)) { + endingLabel.inEdgeFrom[mainBranch].destroy() + } + if (endingLabel.inputs.contains(elseBranch)) { + endingLabel.inEdgeFrom[elseBranch].destroy() + } + } + } + + return changed + } + + private fun recoverComplexNodes(node: BaseNode): Boolean { + var changed = false + + changed = tryLiteralizeNextNode(node) || changed + + changed = joinNextLiteralNodes(node) || changed + + changed = inlineStackArgs(node) || changed + + changed = cleanupLiteralNode(node) || changed + + changed = joinNextSwitchNodes(node) || changed + + changed = removeUnusedLabel(node) || changed + + return changed + } + + private fun cleanupLiteralNode(node: BaseNode): Boolean { + var changed = false + + if (node.outputs.size > 0) { + val nextNode = node.nextNode + if (nextNode is LiteralNode) { + val returns = nextNode.returns() + val notConsumedReturns = returns.filter { !it.consumed } + if (returns.size != notConsumedReturns.size) { + changed = true + + if (notConsumedReturns.isNotEmpty()) { + val newNode = LiteralNode(prototypesFromReturns(notConsumedReturns)) + replaceNode(nextNode, newNode) + } else { + cutNode(nextNode) + } + } + } + } + + return changed + } + + private fun invertCondition(condition: String): String { + return if (Regex("^!\\(.+\\)$").matches(condition)) { + condition.substring(2, condition.length - 1) + } else { + "!($condition)" + } + } + } } const val MAX_NUM_GLOBAL_EVENTS = 32 @@ -542,7 +548,12 @@ class Data constructor( private set } -val changingAddTos = StackChangingNode.AddsTo.values().filter { it != DONT_PUSHES_TO_STACK } +val changingAddTos = StackChangingNode.AddsTo.values().filter { + when(it) { + DONT_PUSHES_TO_STACK -> false + ADDS_TO_NORMAL_STACK, ADDS_TO_STRING_STACK -> true + } +} fun inlineStackArgs(node: BaseNode): Boolean { var changed = false @@ -614,23 +625,25 @@ private fun tryLiteralizeNextNode(node: BaseNode): Boolean { if (node.outputs.size > 0) { node.outputs.forEach { nextNode -> - if (nextNode is StackChangingNode && nextNode.outputs.size == 1) { + // We can only replace function nodes (nodes returning values) with literal + // as far as are not dangerous (has no side effects) + if (nextNode is StackChangingNode && nextNode !is LiteralNode + && nextNode.outputs.size == 1 && !nextNode.hasNonStackDeps) { val allArgsAreStatic = nextNode.arguments.all { it.argument is StackChangingNode.LiteralArgument } if (allArgsAreStatic) { - // We can only replace function nodes (nodes returning values) with literal - // as far as all dangerous commands are statements (don't return any values) - assert(BotCommand.BOTCMD_DELAY.returnType == BotCommandReturnType.RETURNVAL_VOID) + // Avoid any dangerous nodes that are not marked as having non-stack dependencies + // Such dangerous commands are usually statements (don't return any values) val returns = nextNode.returns() - val replacingNode = when (returns.count { it.addsTo != DONT_PUSHES_TO_STACK }) { - 0 -> { - if (returns.size != 1) throw AssertionError() - CommandNode(returns[0].addsTo.asText() + returns[0].value) + val changingReturnsCount = returns.count { + when(it.addsTo) { + DONT_PUSHES_TO_STACK -> false + ADDS_TO_NORMAL_STACK, ADDS_TO_STRING_STACK -> true } - else -> LiteralNode(nextNode.returns().map { it.value to it.addsTo }) } - if (nextNode::class != replacingNode::class) { + if (changingReturnsCount != 0) { + val replacingNode = LiteralNode(nextNode.returns().map { it.value to it.addsTo }) replaceNode(nextNode, replacingNode) changed = true } diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt index a943dc3..8e61663 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt @@ -23,6 +23,11 @@ abstract class BaseNode(open val asText: String, outputNum: Int) { val inEdgeFrom = EdgesFrom() val outEdgeTo = EdgesTo() + /** + * Does it access anything besides stacks? E.g. does it change variable(s), changes or requests world state + */ + open val hasNonStackDeps: Boolean = false + fun addInput(node: BaseNode) { assert(!_inputs.contains(node) || this == nullNode || node == nullNode) _inputs.add(node) @@ -148,7 +153,9 @@ val nullNode = NullNode() class BeginNode: BaseNode("(BEGIN)", 1) class EndNode: BaseNode("(END)", 0) -open class CommandNode(asText: String): BaseNode(asText.tryAppendSemicolon(), 1) +open class CommandNode(asText: String): BaseNode(asText.tryAppendSemicolon(), 1) { + override val hasNonStackDeps: Boolean = true +} class TextNode(asText: String): BaseNode(asText, 1) @@ -278,11 +285,17 @@ abstract class StackChangingNode( class CustomStackConsumingNode( numArgs: Int, addsTo: AddsTo, + override val hasNonStackDeps: Boolean = false, private val transformer: (List) -> String ) : StackChangingNode(consumesNormalStack(numArgs), arrayOf(ReturnPrototype(addsTo, { arguments -> transformer(arguments) }))) -open class FunctionNode(val name: String, arguments: List, addsTo: AddsTo) +open class FunctionNode( + val name: String, + arguments: List, + addsTo: AddsTo, + override val hasNonStackDeps: Boolean = false +) : StackChangingNode( arguments, arrayOf(ReturnPrototype(addsTo, { _ -> "$name(${arguments.joinToString { it.toString() }})" })) diff --git a/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt b/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt index bcc62e3..dc8f72f 100644 --- a/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt +++ b/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt @@ -1,5 +1,7 @@ package com.github.tarcv.zandronum.debotc +import com.github.tarcv.zandronum.debotc.Decompiler.Companion.compactNodes +import com.github.tarcv.zandronum.debotc.StackChangingNode.AddsTo import kotlin.test.Test /** @@ -11,9 +13,9 @@ class CounterexamplesTest { fun setupNodes(): BeginNode { val rootNode = BeginNode() rootNode - .attachNode(FunctionNode("IsEnemyVisible", emptyList(), StackChangingNode.AddsTo.ADDS_TO_NORMAL_STACK)) - .attachNode(FunctionNode("ClearEnemy", emptyList(), StackChangingNode.AddsTo.DONT_PUSHES_TO_STACK)) - .attachNode(FunctionNode("Function", StackChangingNode.consumesNormalStack(1), StackChangingNode.AddsTo.ADDS_TO_NORMAL_STACK)) + .attachNode(FunctionNode("IsEnemyVisible", emptyList(), AddsTo.ADDS_TO_NORMAL_STACK)) + .attachNode(FunctionNode("ClearEnemy", emptyList(), AddsTo.DONT_PUSHES_TO_STACK)) + .attachNode(FunctionNode("Function", StackChangingNode.consumesNormalStack(1), AddsTo.ADDS_TO_NORMAL_STACK)) .attachNode(EndNode()) return rootNode } @@ -29,4 +31,22 @@ class CounterexamplesTest { val expectedStructureRoot = setupNodes() assertIsSameStructure(expectedStructureRoot, rootNode) } + + @Test + fun testNoInlinesAcrossStatements2() { + val rootNode = BeginNode() + rootNode + .attachNode(FunctionNode("Roam", emptyList(), AddsTo.ADDS_TO_NORMAL_STACK, hasNonStackDeps = true)) + .attachNode(DropStackNode("Drop")) + .attachNode(FunctionNode("Function", emptyList(), AddsTo.DONT_PUSHES_TO_STACK)) + .attachNode(EndNode()) + compactNodes(rootNode) + + val expectedStructureRoot = BeginNode() + expectedStructureRoot + .attachNode(TextNode("Roam();${lineSeparator}// dropped 'Roam()';${lineSeparator}Function();")) + .attachNode(EndNode()) + + assertIsSameStructure(expectedStructureRoot, rootNode) + } } \ No newline at end of file From 9bc2c2f11222b76494bcbd93d649c350501d8ead Mon Sep 17 00:00:00 2001 From: TarCV Date: Sun, 19 Apr 2020 15:09:16 +0300 Subject: [PATCH 3/9] Inline LiteralNode-like dangerous nodes --- .../tarcv/zandronum/debotc/Decompiler.kt | 180 +++++++++--------- .../github/tarcv/zandronum/debotc/Nodes.kt | 27 ++- 2 files changed, 114 insertions(+), 93 deletions(-) diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt index 7887a24..52bbfb3 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt @@ -4,6 +4,7 @@ import com.github.tarcv.zandronum.debotc.BotCommand.NUM_BOTCMDS import com.github.tarcv.zandronum.debotc.DataHeaders.* import com.github.tarcv.zandronum.debotc.LiteralNode.Companion.consumedMarker import com.github.tarcv.zandronum.debotc.StackChangingNode.AddsTo.* +import com.github.tarcv.zandronum.debotc.StackChangingNode.Companion.filterIsStackChanging import kotlin.collections.ArrayList import kotlin.collections.HashSet import kotlin.text.RegexOption.* @@ -548,76 +549,77 @@ class Data constructor( private set } -val changingAddTos = StackChangingNode.AddsTo.values().filter { - when(it) { - DONT_PUSHES_TO_STACK -> false - ADDS_TO_NORMAL_STACK, ADDS_TO_STRING_STACK -> true - } -} +val changingAddTos = StackChangingNode.AddsTo.values().filterIsStackChanging() fun inlineStackArgs(node: BaseNode): Boolean { - var changed = false + if (node.outputs.size != 1) return false + if (node !is StackChangingNode) return false - if (node.outputs.size == 1 && node is LiteralNode) { - val nextNode = node.outputs[0] - if (nextNode.inputs.size == 1 && nextNode is ConsumesStack) { - val nextNodeArguments = nextNode.arguments.filter { it.argument is StackChangingNode.StackArgument } - if (nextNodeArguments.isNotEmpty()) { - - val outputsByAddTo = changingAddTos - .map { changingAddTo -> - changingAddTo to node.returns() - .filter { it.addsTo == changingAddTo } - .filter { !it.consumed } - .asReversed() - } - .toMap() + val inlinableNonStackDeps = node.hasNonStackDeps && + !node.outputs[0].hasNonStackDeps && + node.returns().map { it.addsTo }.filterIsStackChanging().size == 1 && + node.arguments.isEmpty() + if (node !is LiteralNode && !inlinableNonStackDeps) return false - val hasAllArgs = nextNodeArguments.all { holder -> - holder.argument.let { - if (it is StackChangingNode.StackArgument) { - outputsByAddTo[it.addsTo]!!.size > it.depth - } else { - true - } - } - } + val nextNode = node.outputs[0] - if (hasAllArgs) { - assert(node.arguments.isEmpty()) // LiteralNodes have no dependencies - changed = true + if (nextNode.inputs.size != 1 || nextNode !is ConsumesStack) return false - val maxConsumedDepths = changingAddTos.map { it to -1 }.toMap().toMutableMap() - - nextNodeArguments.forEach { holder -> - holder.argument = holder.argument.let { - if (it is StackChangingNode.StackArgument) { - val compatibleOutputs = outputsByAddTo[it.addsTo]!! - val inlinedReturn = compatibleOutputs[it.depth] - maxConsumedDepths[it.addsTo] = max(maxConsumedDepths[it.addsTo]!!, it.depth) - node.markReturnAsConsumed(inlinedReturn.index) - StackChangingNode.LiteralArgument(inlinedReturn.value) - } else { - it - } - } - } - changingAddTos.forEach { changingAddTo -> - val allReturns = node.returns() - .filter { it.addsTo == changingAddTo } - .asReversed() - if (allReturns.isNotEmpty()) { - if (allReturns.subList(0, maxConsumedDepths[changingAddTo]!! + 1).any { !it.consumed }) - throw AssertionError() - } - } - assert(nextNode.arguments.all { it.argument is StackChangingNode.LiteralArgument }) + val nextNodeArguments = nextNode.arguments.filter { it.argument is StackChangingNode.StackArgument } + if (nextNodeArguments.isEmpty()) return false - tryLiteralizeNextNode(node) - } + val outputsByAddTo = changingAddTos + .map { changingAddTo -> + changingAddTo to node.returns() + .filter { it.addsTo == changingAddTo } + .filter { !it.consumed } + .asReversed() + } + .toMap() + + val hasAllArgs = nextNodeArguments.all { holder -> + holder.argument.let { + if (it is StackChangingNode.StackArgument) { + outputsByAddTo[it.addsTo]!!.size > it.depth + } else { + true } } } - return changed + + if (!hasAllArgs) return false + + assert(node.arguments.isEmpty()) // LiteralNodes have no dependencies + + val maxConsumedDepths = changingAddTos.map { it to -1 }.toMap().toMutableMap() + + nextNodeArguments.forEach { holder -> + holder.argument = holder.argument.let { + if (it is StackChangingNode.StackArgument) { + val compatibleOutputs = outputsByAddTo[it.addsTo]!! + val inlinedReturn = compatibleOutputs[it.depth] + maxConsumedDepths[it.addsTo] = max(maxConsumedDepths[it.addsTo]!!, it.depth) + node.markReturnAsConsumed(inlinedReturn.index) + StackChangingNode.LiteralArgument(inlinedReturn.value) + } else { + it + } + } + } + changingAddTos.forEach { changingAddTo -> + val allReturns = node.returns() + .filter { it.addsTo == changingAddTo } + .asReversed() + if (allReturns.isNotEmpty()) { + if (allReturns.subList(0, maxConsumedDepths[changingAddTo]!! + 1).any { !it.consumed }) + throw AssertionError() + } + } + nextNode.hasNonStackDeps = nextNode.hasNonStackDeps || node.hasNonStackDeps + + assert(nextNode.arguments.all { it.argument is StackChangingNode.LiteralArgument }) + + tryLiteralizeNextNode(node) + return true } private fun tryLiteralizeNextNode(node: BaseNode): Boolean { @@ -685,33 +687,33 @@ fun joinNextLiteralNodes(nodeBeforeLiteral: BaseNode): Boolean { nodeBeforeLiteral.outputs .forEach { node -> if (node.outputs.size > 0) { - val nextNode = node.nextNode - if (node is LiteralNode && node.outputs.size == 1 - && nextNode is LiteralNode && node.inputs.size == 1) { - - val literalPairs = ArrayList>() - literalPairs.addAll(prototypesFromReturns(node.returns())) - literalPairs.addAll(prototypesFromReturns(nextNode.returns())) - - val newNode = LiteralNode(literalPairs) - val oldInputs = ArrayList(node.inputs) - val oldNextNodeOutputs = ArrayList(nextNode.outputs.copy()) - replaceNode(nextNode, newNode) - cutNode(node) - - assert(oldInputs.all { - !it.outputs.contains(node) - && !it.outputs.contains(nextNode) - && it.outputs.contains(newNode) - }) - assert(oldNextNodeOutputs.all { - !it.inputs.contains(node) - && !it.inputs.contains(nextNode) - && it.inputs.contains(newNode) - }) + val nextNode = node.nextNode + if (node is LiteralNode && node.outputs.size == 1 + && nextNode is LiteralNode && node.inputs.size == 1) { + + val literalPairs = ArrayList>() + literalPairs.addAll(prototypesFromReturns(node.returns())) + literalPairs.addAll(prototypesFromReturns(nextNode.returns())) + + val newNode = LiteralNode(literalPairs) + val oldInputs = ArrayList(node.inputs) + val oldNextNodeOutputs = ArrayList(nextNode.outputs.copy()) + replaceNode(nextNode, newNode) + cutNode(node) + + assert(oldInputs.all { + !it.outputs.contains(node) + && !it.outputs.contains(nextNode) + && it.outputs.contains(newNode) + }) + assert(oldNextNodeOutputs.all { + !it.inputs.contains(node) + && !it.inputs.contains(nextNode) + && it.inputs.contains(newNode) + }) - changed = true - } + changed = true + } } } return changed @@ -766,8 +768,8 @@ fun recurse( fun convertNodeToText(nextNode: BaseNode): String { assert(nextNode !is LabelNode) - assert(!nextNode.asText.contains("stack[")) - assert(nextNode.asText.trim() != ";") +// TODO: assert(!nextNode.asText.contains("stack[")) +// TODO: assert(nextNode.asText.trim() != ";") return nextNode.asText } diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt index 8e61663..ae9aab1 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt @@ -26,7 +26,7 @@ abstract class BaseNode(open val asText: String, outputNum: Int) { /** * Does it access anything besides stacks? E.g. does it change variable(s), changes or requests world state */ - open val hasNonStackDeps: Boolean = false + open var hasNonStackDeps: Boolean = false fun addInput(node: BaseNode) { assert(!_inputs.contains(node) || this == nullNode || node == nullNode) @@ -154,7 +154,7 @@ class BeginNode: BaseNode("(BEGIN)", 1) class EndNode: BaseNode("(END)", 0) open class CommandNode(asText: String): BaseNode(asText.tryAppendSemicolon(), 1) { - override val hasNonStackDeps: Boolean = true + override var hasNonStackDeps: Boolean = true } class TextNode(asText: String): BaseNode(asText, 1) @@ -279,13 +279,28 @@ abstract class StackChangingNode( } return args } + + fun Array.filterIsStackChanging(): List { + return filter(::isStackChanging) + } + + fun Iterable.filterIsStackChanging(): List { + return filter(::isStackChanging) + } + + private fun isStackChanging(it: AddsTo): Boolean { + return when (it) { + DONT_PUSHES_TO_STACK -> false + ADDS_TO_NORMAL_STACK, ADDS_TO_STRING_STACK -> true + } + } } } class CustomStackConsumingNode( numArgs: Int, addsTo: AddsTo, - override val hasNonStackDeps: Boolean = false, + override var hasNonStackDeps: Boolean = false, private val transformer: (List) -> String ) : StackChangingNode(consumesNormalStack(numArgs), arrayOf(ReturnPrototype(addsTo, { arguments -> transformer(arguments) }))) @@ -294,7 +309,7 @@ open class FunctionNode( val name: String, arguments: List, addsTo: AddsTo, - override val hasNonStackDeps: Boolean = false + override var hasNonStackDeps: Boolean = false ) : StackChangingNode( arguments, @@ -311,6 +326,10 @@ class OperatorNode(val name: String, numArgs: Int) }) ) +/** + * Node containing only constants and inlined expressions to be pushed on a stack. + * Contents inside a literal node might be inlined or reordered + */ class LiteralNode(pairs: List>) : StackChangingNode( emptyList(), From c935a660a2de7bec4300f62120be1f562dc2e58b Mon Sep 17 00:00:00 2001 From: TarCV Date: Mon, 20 Apr 2020 01:37:06 +0300 Subject: [PATCH 4/9] Also allow inlines when all arguments are known --- .../tarcv/zandronum/debotc/Decompiler.kt | 70 ++++++++----------- .../github/tarcv/zandronum/debotc/Nodes.kt | 10 ++- 2 files changed, 37 insertions(+), 43 deletions(-) diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt index 52bbfb3..387b200 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt @@ -554,14 +554,13 @@ fun inlineStackArgs(node: BaseNode): Boolean { if (node.outputs.size != 1) return false if (node !is StackChangingNode) return false + val nextNode = node.outputs[0] val inlinableNonStackDeps = node.hasNonStackDeps && - !node.outputs[0].hasNonStackDeps && + !nextNode.hasNonStackDeps && node.returns().map { it.addsTo }.filterIsStackChanging().size == 1 && - node.arguments.isEmpty() + node.arguments.none { it.argument !is StackChangingNode.LiteralArgument } if (node !is LiteralNode && !inlinableNonStackDeps) return false - val nextNode = node.outputs[0] - if (nextNode.inputs.size != 1 || nextNode !is ConsumesStack) return false val nextNodeArguments = nextNode.arguments.filter { it.argument is StackChangingNode.StackArgument } @@ -576,47 +575,31 @@ fun inlineStackArgs(node: BaseNode): Boolean { } .toMap() - val hasAllArgs = nextNodeArguments.all { holder -> - holder.argument.let { - if (it is StackChangingNode.StackArgument) { - outputsByAddTo[it.addsTo]!!.size > it.depth - } else { - true - } - } - } + assert(node.arguments.none { it.argument !is StackChangingNode.LiteralArgument }) - if (!hasAllArgs) return false + var depthAdjustment = 0 + nextNodeArguments + .filter { it.argument is StackChangingNode.StackArgument } + .sortedBy { (it.argument as StackChangingNode.StackArgument).depth } + .forEach { holder -> + holder.argument = holder.argument.let { + val argument = it as StackChangingNode.StackArgument - assert(node.arguments.isEmpty()) // LiteralNodes have no dependencies - - val maxConsumedDepths = changingAddTos.map { it to -1 }.toMap().toMutableMap() + val compatibleOutputs = outputsByAddTo.getValue(argument.addsTo) + if (compatibleOutputs.size <= argument.depth) { + return@let argument.withDepth(argument.depth - depthAdjustment) + } - nextNodeArguments.forEach { holder -> - holder.argument = holder.argument.let { - if (it is StackChangingNode.StackArgument) { - val compatibleOutputs = outputsByAddTo[it.addsTo]!! - val inlinedReturn = compatibleOutputs[it.depth] - maxConsumedDepths[it.addsTo] = max(maxConsumedDepths[it.addsTo]!!, it.depth) - node.markReturnAsConsumed(inlinedReturn.index) - StackChangingNode.LiteralArgument(inlinedReturn.value) - } else { - it - } - } - } - changingAddTos.forEach { changingAddTo -> - val allReturns = node.returns() - .filter { it.addsTo == changingAddTo } - .asReversed() - if (allReturns.isNotEmpty()) { - if (allReturns.subList(0, maxConsumedDepths[changingAddTo]!! + 1).any { !it.consumed }) - throw AssertionError() - } + // arg value is known + val inlinedReturn = compatibleOutputs[argument.depth] + assert(!inlinedReturn.consumed) + node.markReturnAsConsumed(inlinedReturn.index) + depthAdjustment += 1 + StackChangingNode.LiteralArgument(inlinedReturn.value) + } } - nextNode.hasNonStackDeps = nextNode.hasNonStackDeps || node.hasNonStackDeps - assert(nextNode.arguments.all { it.argument is StackChangingNode.LiteralArgument }) + nextNode.hasNonStackDeps = nextNode.hasNonStackDeps || node.hasNonStackDeps tryLiteralizeNextNode(node) return true @@ -770,7 +753,12 @@ fun convertNodeToText(nextNode: BaseNode): String { assert(nextNode !is LabelNode) // TODO: assert(!nextNode.asText.contains("stack[")) // TODO: assert(nextNode.asText.trim() != ";") - return nextNode.asText + val nodeText = nextNode.asText + return if (nextNode !is TextNode) { + "[${nextNode::class.simpleName}(dng=${nextNode.hasNonStackDeps})]" + nodeText + } else { + nodeText + } } fun convertToTextNodes(node: BaseNode): Boolean { diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt index ae9aab1..09accf9 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt @@ -247,6 +247,8 @@ abstract class StackChangingNode( interface StackArgument { val addsTo: AddsTo val depth: Int + + fun withDepth(depth: Int): Argument } class NormalStackArgument(override val depth: Int): Argument(), StackArgument { @@ -257,12 +259,16 @@ abstract class StackChangingNode( override val addsTo: AddsTo = ADDS_TO_NORMAL_STACK override fun toString(): String = "stack[$depth]" + + override fun withDepth(depth: Int) = NormalStackArgument(depth) } class StringStackArgument(override val depth: Int): Argument(), StackArgument { init { assert(depth >= 0) } + override fun withDepth(depth: Int) = StringStackArgument(depth) + override val addsTo: AddsTo = ADDS_TO_STRING_STACK override fun toString(): String = "stringStack[$depth]" @@ -342,10 +348,10 @@ class LiteralNode(pairs: List>) } } -class DropStackNode(override val asText: String) +class DropStackNode : StackChangingNode( listOf(createNormalStackArgument(0)), - arrayOf(ReturnPrototype(DONT_PUSHES_TO_STACK, { arguments -> "// dropped '${arguments[0]}'" })) + arrayOf(ReturnPrototype(DONT_PUSHES_TO_STACK, { arguments -> "${arguments[0]} // result ignored" })) ) open class LabelNode(byte: Int) : BaseNode("label$byte", 1) From be4c48a13de88593972c132a2114129e43e10d6b Mon Sep 17 00:00:00 2001 From: TarCV Date: Mon, 20 Apr 2020 22:06:56 +0300 Subject: [PATCH 5/9] Refactor hasNonStackDeps property --- .../tarcv/zandronum/debotc/BotCommand.kt | 3 +- .../tarcv/zandronum/debotc/DataHeaders.kt | 57 ++++++------ .../tarcv/zandronum/debotc/Decompiler.kt | 14 ++- .../github/tarcv/zandronum/debotc/Nodes.kt | 89 ++++++++++++++----- 4 files changed, 102 insertions(+), 61 deletions(-) diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt index 7aa87b6..6df551f 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/BotCommand.kt @@ -1,5 +1,6 @@ package com.github.tarcv.zandronum.debotc +import com.github.tarcv.zandronum.debotc.BaseNode.HasNonStackDeps.NON_STACK_DEPS import com.github.tarcv.zandronum.debotc.BotCommandReturnType.* import com.github.tarcv.zandronum.debotc.StackChangingNode.AddsTo.* import com.github.tarcv.zandronum.debotc.StackChangingNode.ArgumentHolder @@ -124,7 +125,7 @@ enum class BotCommand( RETURNVAL_VOID -> DONT_PUSHES_TO_STACK } - return FunctionNode(readableName, args, addsTo, hasNonStackDeps = true) + return FunctionNode(readableName, args, addsTo, NON_STACK_DEPS) } } diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt index fe96383..ddb0424 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt @@ -1,5 +1,6 @@ package com.github.tarcv.zandronum.debotc +import com.github.tarcv.zandronum.debotc.BaseNode.HasNonStackDeps.NON_STACK_DEPS import com.github.tarcv.zandronum.debotc.StackChangingNode.AddsTo.* import com.github.tarcv.zandronum.debotc.StackChangingNode.ArgumentHolder.Companion.createNormalStackArgument import com.github.tarcv.zandronum.debotc.StackChangingNode.Companion.consumesNormalStack @@ -235,7 +236,7 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_ASSIGNGLOBALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> "\$global${command.arguments[0]} = ${stackArgs[0]};" } } @@ -243,7 +244,7 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_ADDGLOBALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> "\$global${command.arguments[0]} += ${stackArgs[0]};" } } @@ -251,7 +252,7 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_SUBGLOBALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> "\$global${command.arguments[0]} -= ${stackArgs[0]};" } } @@ -259,7 +260,7 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_MULGLOBALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> "\$global${command.arguments[0]} *= ${stackArgs[0]};" } } @@ -267,7 +268,7 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_DIVGLOBALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> "\$global${command.arguments[0]} /= ${stackArgs[0]};" } } @@ -275,7 +276,7 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_MODGLOBALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> "\$global${command.arguments[0]} %= ${stackArgs[0]};" } } @@ -283,7 +284,7 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_INCLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(0, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { + return CustomStackConsumingNode(0, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { "\$local${command.arguments[0]}++;" } } @@ -291,7 +292,7 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_DECLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(0, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { + return CustomStackConsumingNode(0, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { "\$local${command.arguments[0]}--;" } } @@ -299,7 +300,7 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_ASSIGNLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> "\$local${command.arguments[0]} = ${stackArgs[0]};" } } @@ -307,7 +308,7 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_ADDLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> "\$local${command.arguments[0]} += ${stackArgs[0]};" } } @@ -315,7 +316,7 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_SUBLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> "\$local${command.arguments[0]} -= ${stackArgs[0]};" } } @@ -323,7 +324,7 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_MULLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> "\$local${command.arguments[0]} *= ${stackArgs[0]};" } } @@ -331,7 +332,7 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_DIVLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> "\$local${command.arguments[0]} /= ${stackArgs[0]};" } } @@ -339,7 +340,7 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_MODLOCALVAR(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> "\$local${command.arguments[0]} %= ${stackArgs[0]};" } } @@ -360,7 +361,7 @@ enum class DataHeaders(requiredArgs: Int = 0) }, DH_INCGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}]++;" } @@ -368,56 +369,56 @@ enum class DataHeaders(requiredArgs: Int = 0) }, DH_DECGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}]--;" } } }, DH_ASSIGNGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] = ${stackArgs[1]};" } } }, DH_ADDGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] += ${stackArgs[1]};" } } }, DH_SUBGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] -= ${stackArgs[1]};" } } }, DH_MULGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] *= ${stackArgs[1]};" } } }, DH_DIVGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] /= ${stackArgs[1]};" } } }, DH_MODGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}] %= ${stackArgs[1]};" } } }, DH_PUSHGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { - return CustomStackConsumingNode(1, ADDS_TO_NORMAL_STACK, hasNonStackDeps = true) { stackArgs -> + return CustomStackConsumingNode(1, ADDS_TO_NORMAL_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) "\$globalArray${command.arguments[0]}[${stackArgs[0]}]" } } @@ -427,8 +428,8 @@ enum class DataHeaders(requiredArgs: Int = 0) return object : StackChangingNode( consumesNormalStack(2), arrayOf( - ReturnPrototype(ADDS_TO_NORMAL_STACK, {arguments -> "${arguments[1]}"}), - ReturnPrototype(ADDS_TO_NORMAL_STACK, {arguments -> "${arguments[0]}"}) + ReturnPrototype(ADDS_TO_NORMAL_STACK) { arguments -> "${arguments[1]}"}, + ReturnPrototype(ADDS_TO_NORMAL_STACK) { arguments -> "${arguments[0]}"} ) ) { override val asText: String @@ -441,8 +442,8 @@ enum class DataHeaders(requiredArgs: Int = 0) return object : StackChangingNode( consumesNormalStack(1), arrayOf( - ReturnPrototype(ADDS_TO_NORMAL_STACK, {arguments -> "${arguments[0]}"}), - ReturnPrototype(ADDS_TO_NORMAL_STACK, {arguments -> "${arguments[0]}"}) + ReturnPrototype(ADDS_TO_NORMAL_STACK) { arguments -> "${arguments[0]}"}, + ReturnPrototype(ADDS_TO_NORMAL_STACK) { arguments -> "${arguments[0]}"} ) ) { @@ -454,7 +455,7 @@ enum class DataHeaders(requiredArgs: Int = 0) DH_ARRAYSET{ override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return FunctionNode("memset", consumesNormalStack(3), DONT_PUSHES_TO_STACK, - hasNonStackDeps = true) + NON_STACK_DEPS) } }, diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt index 387b200..8fdd34b 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt @@ -1,5 +1,6 @@ package com.github.tarcv.zandronum.debotc +import com.github.tarcv.zandronum.debotc.BaseNode.HasNonStackDeps.NON_STACK_DEPS import com.github.tarcv.zandronum.debotc.BotCommand.NUM_BOTCMDS import com.github.tarcv.zandronum.debotc.DataHeaders.* import com.github.tarcv.zandronum.debotc.LiteralNode.Companion.consumedMarker @@ -555,8 +556,8 @@ fun inlineStackArgs(node: BaseNode): Boolean { if (node !is StackChangingNode) return false val nextNode = node.outputs[0] - val inlinableNonStackDeps = node.hasNonStackDeps && - !nextNode.hasNonStackDeps && + val inlinableNonStackDeps = node.hasNonStackDeps == NON_STACK_DEPS && + nextNode.hasNonStackDeps != NON_STACK_DEPS && node.returns().map { it.addsTo }.filterIsStackChanging().size == 1 && node.arguments.none { it.argument !is StackChangingNode.LiteralArgument } if (node !is LiteralNode && !inlinableNonStackDeps) return false @@ -613,7 +614,7 @@ private fun tryLiteralizeNextNode(node: BaseNode): Boolean { // We can only replace function nodes (nodes returning values) with literal // as far as are not dangerous (has no side effects) if (nextNode is StackChangingNode && nextNode !is LiteralNode - && nextNode.outputs.size == 1 && !nextNode.hasNonStackDeps) { + && nextNode.outputs.size == 1 && nextNode.hasNonStackDeps != NON_STACK_DEPS) { val allArgsAreStatic = nextNode.arguments.all { it.argument is StackChangingNode.LiteralArgument } if (allArgsAreStatic) { @@ -753,12 +754,7 @@ fun convertNodeToText(nextNode: BaseNode): String { assert(nextNode !is LabelNode) // TODO: assert(!nextNode.asText.contains("stack[")) // TODO: assert(nextNode.asText.trim() != ";") - val nodeText = nextNode.asText - return if (nextNode !is TextNode) { - "[${nextNode::class.simpleName}(dng=${nextNode.hasNonStackDeps})]" + nodeText - } else { - nodeText - } + return nextNode.asText } fun convertToTextNodes(node: BaseNode): Boolean { diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt index 09accf9..5231b05 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt @@ -1,5 +1,7 @@ package com.github.tarcv.zandronum.debotc +import com.github.tarcv.zandronum.debotc.BaseNode.HasNonStackDeps.NON_STACK_DEPS +import com.github.tarcv.zandronum.debotc.BaseNode.HasNonStackDeps.SAFE_DEPS import com.github.tarcv.zandronum.debotc.StackChangingNode.* import com.github.tarcv.zandronum.debotc.StackChangingNode.AddsTo.* import com.github.tarcv.zandronum.debotc.StackChangingNode.ArgumentHolder.Companion.createLiteralArgument @@ -26,7 +28,11 @@ abstract class BaseNode(open val asText: String, outputNum: Int) { /** * Does it access anything besides stacks? E.g. does it change variable(s), changes or requests world state */ - open var hasNonStackDeps: Boolean = false + enum class HasNonStackDeps { + SAFE_DEPS, + NON_STACK_DEPS + } + abstract val hasNonStackDeps: HasNonStackDeps fun addInput(node: BaseNode) { assert(!_inputs.contains(node) || this == nullNode || node == nullNode) @@ -147,17 +153,26 @@ abstract class BaseNode(open val asText: String, outputNum: Int) { } // Hack to avoid immutability in Kotlin Native -class NullNode: BaseNode("NULL", 0) { } +class NullNode: BaseNode("NULL", 0) { + override val hasNonStackDeps = SAFE_DEPS +} val nullNode = NullNode() -class BeginNode: BaseNode("(BEGIN)", 1) -class EndNode: BaseNode("(END)", 0) +class BeginNode: BaseNode("(BEGIN)", 1) { + override val hasNonStackDeps = SAFE_DEPS +} +class EndNode: BaseNode("(END)", 0) { + override val hasNonStackDeps = SAFE_DEPS +} -open class CommandNode(asText: String): BaseNode(asText.tryAppendSemicolon(), 1) { - override var hasNonStackDeps: Boolean = true +class CommandNode(asText: String): BaseNode(asText.tryAppendSemicolon(), 1) { + override var hasNonStackDeps = NON_STACK_DEPS } -class TextNode(asText: String): BaseNode(asText, 1) +class TextNode(asText: String): BaseNode(asText, 1) { + // hasNonStackDeps is never actually used for TextNode, so hardcode to 'true' to make it future-proof + override val hasNonStackDeps = NON_STACK_DEPS +} interface ConsumesStack { val arguments: List @@ -201,19 +216,21 @@ abstract class StackChangingNode( fun returns(): List { return returnPrototypes.mapIndexed { i, it -> - Return(i, it.addsTo, it.returnTransform(arguments)) + Return(i, it.addsTo, it.returnTransform(arguments), it.hasNonStackDeps) } } - class ReturnPrototype ( - val addsTo: AddsTo, - val returnTransform: (arguments: List) -> String, - val consumed: Boolean = false + class ReturnPrototype( + val addsTo: AddsTo, + val hasNonStackDeps: HasNonStackDeps = SAFE_DEPS, + val consumed: Boolean = false, + val returnTransform: (arguments: List) -> String ) inner class Return constructor( val index: Int, val addsTo: AddsTo, - val value: String + val value: String, + val hasNonStackDeps: HasNonStackDeps ) { val consumed: Boolean get() = isReturnConsumed(index) @@ -224,6 +241,23 @@ abstract class StackChangingNode( abstract class Argument + override val hasNonStackDeps: HasNonStackDeps + get() { + val hasNonStackReturns = returns() + .filter { !it.consumed } + .any { it.hasNonStackDeps == NON_STACK_DEPS } + if (hasNonStackReturns) return NON_STACK_DEPS + + val hasNonStackArgs = arguments + .map { holder -> holder.argument } + .any { + it is LiteralArgument && it.hasNonStackDeps == NON_STACK_DEPS + } + if (hasNonStackArgs) return NON_STACK_DEPS + + return SAFE_DEPS + } + class ArgumentHolder( var argument: Argument ) { @@ -273,7 +307,7 @@ abstract class StackChangingNode( override fun toString(): String = "stringStack[$depth]" } - class LiteralArgument(val value: String): Argument() { + class LiteralArgument(val value: String, val hasNonStackDeps: HasNonStackDeps = SAFE_DEPS): Argument() { override fun toString(): String = value } @@ -306,28 +340,30 @@ abstract class StackChangingNode( class CustomStackConsumingNode( numArgs: Int, addsTo: AddsTo, - override var hasNonStackDeps: Boolean = false, + hasNonStackDeps: HasNonStackDeps = SAFE_DEPS, private val transformer: (List) -> String ) -: StackChangingNode(consumesNormalStack(numArgs), arrayOf(ReturnPrototype(addsTo, { arguments -> transformer(arguments) }))) +: StackChangingNode(consumesNormalStack(numArgs), arrayOf(ReturnPrototype(addsTo, hasNonStackDeps) { arguments -> transformer(arguments) })) open class FunctionNode( val name: String, arguments: List, addsTo: AddsTo, - override var hasNonStackDeps: Boolean = false + hasNonStackDeps: HasNonStackDeps = SAFE_DEPS ) : StackChangingNode( arguments, - arrayOf(ReturnPrototype(addsTo, { _ -> "$name(${arguments.joinToString { it.toString() }})" })) + arrayOf(ReturnPrototype(addsTo, hasNonStackDeps) { _ -> + "$name(${arguments.joinToString { it.toString() }})" + }) ) class OperatorNode(val name: String, numArgs: Int) : StackChangingNode( consumesNormalStack(numArgs), arrayOf(when (numArgs) { - 2 -> ReturnPrototype(ADDS_TO_NORMAL_STACK, { arguments -> "(" + arguments[0] + " " + name + " " + arguments[1] + ")" }) - 1 -> ReturnPrototype(ADDS_TO_NORMAL_STACK, { arguments -> "(" + name + arguments[0] + ")" }) + 2 -> ReturnPrototype(ADDS_TO_NORMAL_STACK) { arguments -> "(" + arguments[0] + " " + name + " " + arguments[1] + ")" } + 1 -> ReturnPrototype(ADDS_TO_NORMAL_STACK) { arguments -> "(" + name + arguments[0] + ")" } else -> throw AssertionError() }) ) @@ -339,7 +375,7 @@ class OperatorNode(val name: String, numArgs: Int) class LiteralNode(pairs: List>) : StackChangingNode( emptyList(), - pairs.map {ReturnPrototype(it.second, { _ -> it.first }, it.first == consumedMarker) }.toTypedArray() + pairs.map {ReturnPrototype(it.second, consumed = it.first == consumedMarker) { _ -> it.first } }.toTypedArray() ) { constructor(value: String, addsTo: AddsTo) : this(listOf(value to addsTo)) @@ -351,16 +387,20 @@ class LiteralNode(pairs: List>) class DropStackNode : StackChangingNode( listOf(createNormalStackArgument(0)), - arrayOf(ReturnPrototype(DONT_PUSHES_TO_STACK, { arguments -> "${arguments[0]} // result ignored" })) + arrayOf(ReturnPrototype(DONT_PUSHES_TO_STACK) { arguments -> "${arguments[0]} // result ignored" }) ) -open class LabelNode(byte: Int) : BaseNode("label$byte", 1) +open class LabelNode(byte: Int) : BaseNode("label$byte", 1) { + override val hasNonStackDeps: HasNonStackDeps = SAFE_DEPS +} interface JumpingNode { var jumpTargetNode: BaseNode } open class AbstractGotoNode(val targetByte: Int) : BaseNode("goto label$targetByte", 2), JumpingNode { + override val hasNonStackDeps: HasNonStackDeps = SAFE_DEPS + override var jumpTargetNode: BaseNode get() = outputs[1] set(value) { @@ -426,6 +466,9 @@ class FullSwitchNode( } } + // No need to check if it actually has such deps, so better safe than sorry + override val hasNonStackDeps: HasNonStackDeps = NON_STACK_DEPS + override val arguments: List get() = listOf(conditionTarget) From 7787ea5c620b35ac57c9c4faeea1cf55f076f4c7 Mon Sep 17 00:00:00 2001 From: TarCV Date: Tue, 21 Apr 2020 00:00:31 +0300 Subject: [PATCH 6/9] Rework how dangerous nodes are handled --- .../tarcv/zandronum/debotc/DataHeaders.kt | 12 +++------ .../tarcv/zandronum/debotc/Decompiler.kt | 26 +++++++++---------- .../github/tarcv/zandronum/debotc/Nodes.kt | 23 +++++++--------- .../tarcv/zandronum/debotc/CompactTest.kt | 2 +- .../zandronum/debotc/CounterexamplesTest.kt | 5 ++-- 5 files changed, 30 insertions(+), 38 deletions(-) diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt index ddb0424..f426d71 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt @@ -431,10 +431,7 @@ enum class DataHeaders(requiredArgs: Int = 0) ReturnPrototype(ADDS_TO_NORMAL_STACK) { arguments -> "${arguments[1]}"}, ReturnPrototype(ADDS_TO_NORMAL_STACK) { arguments -> "${arguments[0]}"} ) - ) { - override val asText: String - get() = "(swap last stack items)" - } + ) {} } }, DH_DUP{ @@ -446,10 +443,7 @@ enum class DataHeaders(requiredArgs: Int = 0) ReturnPrototype(ADDS_TO_NORMAL_STACK) { arguments -> "${arguments[0]}"} ) - ) { - override val asText: String - get() = "(duplicate stack item)" - } + ) {} } }, DH_ARRAYSET{ @@ -469,5 +463,5 @@ enum class DataHeaders(requiredArgs: Int = 0) } private fun dropFromStack(): BaseNode { - return DropStackNode("// item dropped from stack") + return DropStackNode() } diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt index 8fdd34b..3c5c8ff 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt @@ -9,7 +9,6 @@ import com.github.tarcv.zandronum.debotc.StackChangingNode.Companion.filterIsSta import kotlin.collections.ArrayList import kotlin.collections.HashSet import kotlin.text.RegexOption.* -import kotlin.math.max @ExperimentalUnsignedTypes class Decompiler { @@ -118,7 +117,6 @@ class Decompiler { ) val gotos = ArrayList() - val terminatingCommands = ArrayList() val labels = HashMap() val nodeHolder = NodeHolder() @@ -136,8 +134,6 @@ class Decompiler { nodeHolder.add(newNode) if (newNode is AbstractGotoNode) { gotos.add(newNode) - } else if (newNode is TerminatingFunctionNode) { - terminatingCommands.add(newNode) } if (event.commands.lastIndex == index && labelPositions.contains(code.positionAfter)) { @@ -160,10 +156,6 @@ class Decompiler { replaceGotoWithEdge(it) } } - terminatingCommands.forEach { - it.jumpTargetNode = endNode - replaceGotoWithEdge(it) - } return nodeHolder.nodes } @@ -557,7 +549,6 @@ fun inlineStackArgs(node: BaseNode): Boolean { val nextNode = node.outputs[0] val inlinableNonStackDeps = node.hasNonStackDeps == NON_STACK_DEPS && - nextNode.hasNonStackDeps != NON_STACK_DEPS && node.returns().map { it.addsTo }.filterIsStackChanging().size == 1 && node.arguments.none { it.argument !is StackChangingNode.LiteralArgument } if (node !is LiteralNode && !inlinableNonStackDeps) return false @@ -580,6 +571,17 @@ fun inlineStackArgs(node: BaseNode): Boolean { var depthAdjustment = 0 nextNodeArguments + .takeWhile { holder -> + if (node.hasNonStackDeps != NON_STACK_DEPS) return@takeWhile true + + holder.argument.let { argument -> + if (argument !is StackChangingNode.LiteralArgument) { + true + } else { + argument.hasNonStackDeps != NON_STACK_DEPS + } + } + } .filter { it.argument is StackChangingNode.StackArgument } .sortedBy { (it.argument as StackChangingNode.StackArgument).depth } .forEach { holder -> @@ -596,12 +598,10 @@ fun inlineStackArgs(node: BaseNode): Boolean { assert(!inlinedReturn.consumed) node.markReturnAsConsumed(inlinedReturn.index) depthAdjustment += 1 - StackChangingNode.LiteralArgument(inlinedReturn.value) + StackChangingNode.LiteralArgument(inlinedReturn.value, inlinedReturn.hasNonStackDeps) } } - nextNode.hasNonStackDeps = nextNode.hasNonStackDeps || node.hasNonStackDeps - tryLiteralizeNextNode(node) return true } @@ -629,7 +629,7 @@ private fun tryLiteralizeNextNode(node: BaseNode): Boolean { } } if (changingReturnsCount != 0) { - val replacingNode = LiteralNode(nextNode.returns().map { it.value to it.addsTo }) + val replacingNode = LiteralNode(prototypesFromReturns(nextNode.returns())) replaceNode(nextNode, replacingNode) changed = true } diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt index 5231b05..71eb0c7 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt @@ -178,6 +178,11 @@ interface ConsumesStack { val arguments: List } +/** + * Node that pushes its returns to the stack. + * Note: such nodes are completely defined by their returnPrototypes and args, + * they can't have any special asText implementations in implementing classes. + */ abstract class StackChangingNode( override val arguments: List, private val returnPrototypes: Array, @@ -236,16 +241,17 @@ abstract class StackChangingNode( get() = isReturnConsumed(index) } - override val asText: String + final override val asText: String get() = returnsAsText(this) abstract class Argument override val hasNonStackDeps: HasNonStackDeps get() { - val hasNonStackReturns = returns() - .filter { !it.consumed } - .any { it.hasNonStackDeps == NON_STACK_DEPS } + val unconsumedReturns = returns().filter { !it.consumed } + if (unconsumedReturns.isEmpty()) return SAFE_DEPS // no unconsumed returns means this node is empty and thus safe + + val hasNonStackReturns = unconsumedReturns.any { it.hasNonStackDeps == NON_STACK_DEPS } if (hasNonStackReturns) return NON_STACK_DEPS val hasNonStackArgs = arguments @@ -408,15 +414,6 @@ open class AbstractGotoNode(val targetByte: Int) : BaseNode("goto label$targetBy } } -class TerminatingFunctionNode(name: String, arguments: List, addsTo: AddsTo) - : FunctionNode(name, arguments, addsTo), JumpingNode { - override var jumpTargetNode: BaseNode - get() = outputs[1] - set(value) { - outputs[1] = value - } -} - class GotoNode(targetByte: Int): AbstractGotoNode(targetByte) open class IfGotoNode(val condition: ArgumentHolder, targetByte: Int) diff --git a/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CompactTest.kt b/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CompactTest.kt index f10cad3..1355230 100644 --- a/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CompactTest.kt +++ b/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CompactTest.kt @@ -198,7 +198,7 @@ class CompactTest { .attachNode(switchNode) .attachNode(caseGotoNode2) .attachNode(caseGotoNode3) - .attachNode(DropStackNode("default")) + .attachNode(DropStackNode()) .attachNode(endLabelNode) .attachNode(EndNode()) diff --git a/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt b/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt index dc8f72f..dcbedcd 100644 --- a/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt +++ b/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt @@ -1,5 +1,6 @@ package com.github.tarcv.zandronum.debotc +import com.github.tarcv.zandronum.debotc.BaseNode.HasNonStackDeps.NON_STACK_DEPS import com.github.tarcv.zandronum.debotc.Decompiler.Companion.compactNodes import com.github.tarcv.zandronum.debotc.StackChangingNode.AddsTo import kotlin.test.Test @@ -36,8 +37,8 @@ class CounterexamplesTest { fun testNoInlinesAcrossStatements2() { val rootNode = BeginNode() rootNode - .attachNode(FunctionNode("Roam", emptyList(), AddsTo.ADDS_TO_NORMAL_STACK, hasNonStackDeps = true)) - .attachNode(DropStackNode("Drop")) + .attachNode(FunctionNode("Roam", emptyList(), AddsTo.ADDS_TO_NORMAL_STACK, NON_STACK_DEPS)) + .attachNode(DropStackNode()) .attachNode(FunctionNode("Function", emptyList(), AddsTo.DONT_PUSHES_TO_STACK)) .attachNode(EndNode()) compactNodes(rootNode) From 079ad2c4fb57051716acc25e4127341f2a76ae22 Mon Sep 17 00:00:00 2001 From: TarCV Date: Tue, 21 Apr 2020 00:00:44 +0300 Subject: [PATCH 7/9] Avoid double semicolons --- .../tarcv/zandronum/debotc/DataHeaders.kt | 44 +++++++++---------- .../github/tarcv/zandronum/debotc/Nodes.kt | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt index f426d71..cb06546 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/DataHeaders.kt @@ -237,7 +237,7 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> - "\$global${command.arguments[0]} = ${stackArgs[0]};" + "\$global${command.arguments[0]} = ${stackArgs[0]}" } } }, @@ -245,7 +245,7 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> - "\$global${command.arguments[0]} += ${stackArgs[0]};" + "\$global${command.arguments[0]} += ${stackArgs[0]}" } } }, @@ -253,7 +253,7 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> - "\$global${command.arguments[0]} -= ${stackArgs[0]};" + "\$global${command.arguments[0]} -= ${stackArgs[0]}" } } }, @@ -261,7 +261,7 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> - "\$global${command.arguments[0]} *= ${stackArgs[0]};" + "\$global${command.arguments[0]} *= ${stackArgs[0]}" } } }, @@ -269,7 +269,7 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> - "\$global${command.arguments[0]} /= ${stackArgs[0]};" + "\$global${command.arguments[0]} /= ${stackArgs[0]}" } } }, @@ -277,7 +277,7 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineGlobalVariable(command.arguments[0]) return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> - "\$global${command.arguments[0]} %= ${stackArgs[0]};" + "\$global${command.arguments[0]} %= ${stackArgs[0]}" } } }, @@ -285,7 +285,7 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) return CustomStackConsumingNode(0, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { - "\$local${command.arguments[0]}++;" + "\$local${command.arguments[0]}++" } } }, @@ -293,7 +293,7 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) return CustomStackConsumingNode(0, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { - "\$local${command.arguments[0]}--;" + "\$local${command.arguments[0]}--" } } }, @@ -301,7 +301,7 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> - "\$local${command.arguments[0]} = ${stackArgs[0]};" + "\$local${command.arguments[0]} = ${stackArgs[0]}" } } }, @@ -309,7 +309,7 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> - "\$local${command.arguments[0]} += ${stackArgs[0]};" + "\$local${command.arguments[0]} += ${stackArgs[0]}" } } }, @@ -317,7 +317,7 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> - "\$local${command.arguments[0]} -= ${stackArgs[0]};" + "\$local${command.arguments[0]} -= ${stackArgs[0]}" } } }, @@ -325,7 +325,7 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> - "\$local${command.arguments[0]} *= ${stackArgs[0]};" + "\$local${command.arguments[0]} *= ${stackArgs[0]}" } } }, @@ -333,7 +333,7 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> - "\$local${command.arguments[0]} /= ${stackArgs[0]};" + "\$local${command.arguments[0]} /= ${stackArgs[0]}" } } }, @@ -341,7 +341,7 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { vmState.defineStateVariable(command.arguments[0]) return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> - "\$local${command.arguments[0]} %= ${stackArgs[0]};" + "\$local${command.arguments[0]} %= ${stackArgs[0]}" } } }, @@ -363,7 +363,7 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) - "\$globalArray${command.arguments[0]}[${stackArgs[0]}]++;" + "\$globalArray${command.arguments[0]}[${stackArgs[0]}]++" } } }, @@ -371,49 +371,49 @@ enum class DataHeaders(requiredArgs: Int = 0) override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return CustomStackConsumingNode(1, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) - "\$globalArray${command.arguments[0]}[${stackArgs[0]}]--;" } + "\$globalArray${command.arguments[0]}[${stackArgs[0]}]--" } } }, DH_ASSIGNGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) - "\$globalArray${command.arguments[0]}[${stackArgs[0]}] = ${stackArgs[1]};" } + "\$globalArray${command.arguments[0]}[${stackArgs[0]}] = ${stackArgs[1]}" } } }, DH_ADDGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) - "\$globalArray${command.arguments[0]}[${stackArgs[0]}] += ${stackArgs[1]};" } + "\$globalArray${command.arguments[0]}[${stackArgs[0]}] += ${stackArgs[1]}" } } }, DH_SUBGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) - "\$globalArray${command.arguments[0]}[${stackArgs[0]}] -= ${stackArgs[1]};" } + "\$globalArray${command.arguments[0]}[${stackArgs[0]}] -= ${stackArgs[1]}" } } }, DH_MULGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) - "\$globalArray${command.arguments[0]}[${stackArgs[0]}] *= ${stackArgs[1]};" } + "\$globalArray${command.arguments[0]}[${stackArgs[0]}] *= ${stackArgs[1]}" } } }, DH_DIVGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) - "\$globalArray${command.arguments[0]}[${stackArgs[0]}] /= ${stackArgs[1]};" } + "\$globalArray${command.arguments[0]}[${stackArgs[0]}] /= ${stackArgs[1]}" } } }, DH_MODGLOBALARRAY(1) { override fun processAndCreateNode(command: Command, vmState: VmState): BaseNode { return CustomStackConsumingNode(2, DONT_PUSHES_TO_STACK, NON_STACK_DEPS) { stackArgs -> vmState.defineGlobalArray(command.arguments[0]) - "\$globalArray${command.arguments[0]}[${stackArgs[0]}] %= ${stackArgs[1]};" } + "\$globalArray${command.arguments[0]}[${stackArgs[0]}] %= ${stackArgs[1]}" } } }, DH_PUSHGLOBALARRAY(1) { diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt index 71eb0c7..6ff08ae 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt @@ -393,7 +393,7 @@ class LiteralNode(pairs: List>) class DropStackNode : StackChangingNode( listOf(createNormalStackArgument(0)), - arrayOf(ReturnPrototype(DONT_PUSHES_TO_STACK) { arguments -> "${arguments[0]} // result ignored" }) + arrayOf(ReturnPrototype(DONT_PUSHES_TO_STACK) { arguments -> "${arguments[0]} /* result ignored */" }) ) open class LabelNode(byte: Int) : BaseNode("label$byte", 1) { From 52a9b70653614b4175cd56a43f29e005388878fd Mon Sep 17 00:00:00 2001 From: TarCV Date: Wed, 22 Apr 2020 02:57:27 +0300 Subject: [PATCH 8/9] Reenable convertNodeToText assertions --- .../kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt index 3c5c8ff..b0a4e2e 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Decompiler.kt @@ -752,8 +752,8 @@ fun recurse( fun convertNodeToText(nextNode: BaseNode): String { assert(nextNode !is LabelNode) -// TODO: assert(!nextNode.asText.contains("stack[")) -// TODO: assert(nextNode.asText.trim() != ";") + assert(!nextNode.asText.contains("stack[")) + assert(nextNode.asText.trim() != ";") return nextNode.asText } From 801d1cab11c5b472b022b01f86e41608056f7053 Mon Sep 17 00:00:00 2001 From: TarCV Date: Wed, 22 Apr 2020 03:06:19 +0300 Subject: [PATCH 9/9] Fix tests and hasNonStackDeps for If*GotoNodes --- .../kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt | 6 ++++++ .../github/tarcv/zandronum/debotc/CounterexamplesTest.kt | 2 +- .../kotlin/com/github/tarcv/zandronum/debotc/Utils.kt | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt index 6ff08ae..6da62db 100644 --- a/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt +++ b/src/commonMain/kotlin/com/github/tarcv/zandronum/debotc/Nodes.kt @@ -422,6 +422,9 @@ open class IfGotoNode(val condition: ArgumentHolder, targetByte: Int) override val arguments: List get() = listOf(condition) + // No need to check if it actually has such deps, so better safe than sorry + override val hasNonStackDeps: HasNonStackDeps = NON_STACK_DEPS + override val asText: String get() = "if (${condition.argument}) ${super.asText}" } @@ -431,6 +434,9 @@ class IfNotGotoNode(val condition: ArgumentHolder, targetByte: Int) override val arguments: List get() = listOf(condition) + // No need to check if it actually has such deps, so better safe than sorry + override val hasNonStackDeps: HasNonStackDeps = NON_STACK_DEPS + override val asText: String get() = "if (! $condition) ${super.asText}" } diff --git a/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt b/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt index dcbedcd..b9e2121 100644 --- a/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt +++ b/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/CounterexamplesTest.kt @@ -45,7 +45,7 @@ class CounterexamplesTest { val expectedStructureRoot = BeginNode() expectedStructureRoot - .attachNode(TextNode("Roam();${lineSeparator}// dropped 'Roam()';${lineSeparator}Function();")) + .attachNode(TextNode("Roam() /* result ignored */;${lineSeparator}Function();")) .attachNode(EndNode()) assertIsSameStructure(expectedStructureRoot, rootNode) diff --git a/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/Utils.kt b/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/Utils.kt index a554d4d..316d445 100644 --- a/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/Utils.kt +++ b/src/jvmTest/kotlin/com/github/tarcv/zandronum/debotc/Utils.kt @@ -15,6 +15,9 @@ class SplitNode(vararg secondBranchNodes: BaseNode): BaseNode("Split", 2) { } nodeJoiningBothBranches = secondBranchNodes.last() } + + // No need to check if it actually has such deps, so better safe than sorry + override val hasNonStackDeps: HasNonStackDeps = HasNonStackDeps.NON_STACK_DEPS } fun assertIsSameStructure(expectedStructureRoot: BeginNode, rootNode: BeginNode) {