diff --git a/lib/assetsGeneration/assembly/opcodes.schema.json b/lib/assetsGeneration/assembly/opcodes.schema.json index 0836ceb8..eb9378fa 100644 --- a/lib/assetsGeneration/assembly/opcodes.schema.json +++ b/lib/assetsGeneration/assembly/opcodes.schema.json @@ -20,9 +20,8 @@ "additionalProperties": false, "properties": { "code": { - "type": "integer", - "minimum": 0, - "maximum": 63999, + "type": "string", + "pattern": "^0x(f8|f9)?[0-9a-f]{2}$", "description": "Unique byte representation of the opcode." }, "mnemonic": { diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/ControlFlowGraph.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/ControlFlowGraph.kt index 24112900..e3951e76 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/ControlFlowGraph.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/ControlFlowGraph.kt @@ -135,63 +135,63 @@ private fun createBasicBlocks(cfg: ControlFlowGraphBuilder, segment: Instruction var branchType: BranchType var branchLabels: List - when (inst.opcode) { + when (inst.opcode.code) { // Return. - OP_RET -> { + OP_RET.code -> { branchType = BranchType.Return branchLabels = emptyList() } // Unconditional jump. - OP_JMP -> { + OP_JMP.code -> { branchType = BranchType.Jump branchLabels = listOf(inst.args[0].value as Int) } // Conditional jumps. - OP_JMP_ON, - OP_JMP_OFF, + OP_JMP_ON.code, + OP_JMP_OFF.code, -> { branchType = BranchType.ConditionalJump branchLabels = listOf(inst.args[0].value as Int) } - OP_JMP_E, - OP_JMPI_E, - OP_JMP_NE, - OP_JMPI_NE, - OP_UJMP_G, - OP_UJMPI_G, - OP_JMP_G, - OP_JMPI_G, - OP_UJMP_L, - OP_UJMPI_L, - OP_JMP_L, - OP_JMPI_L, - OP_UJMP_GE, - OP_UJMPI_GE, - OP_JMP_GE, - OP_JMPI_GE, - OP_UJMP_LE, - OP_UJMPI_LE, - OP_JMP_LE, - OP_JMPI_LE, + OP_JMP_E.code, + OP_JMPI_E.code, + OP_JMP_NE.code, + OP_JMPI_NE.code, + OP_UJMP_G.code, + OP_UJMPI_G.code, + OP_JMP_G.code, + OP_JMPI_G.code, + OP_UJMP_L.code, + OP_UJMPI_L.code, + OP_JMP_L.code, + OP_JMPI_L.code, + OP_UJMP_GE.code, + OP_UJMPI_GE.code, + OP_JMP_GE.code, + OP_JMPI_GE.code, + OP_UJMP_LE.code, + OP_UJMPI_LE.code, + OP_JMP_LE.code, + OP_JMPI_LE.code, -> { branchType = BranchType.ConditionalJump branchLabels = listOf(inst.args[2].value as Int) } - OP_SWITCH_JMP -> { + OP_SWITCH_JMP.code -> { branchType = BranchType.ConditionalJump branchLabels = inst.args.drop(1).map { it.value as Int } } // Calls. - OP_CALL, - OP_VA_CALL, + OP_CALL.code, + OP_VA_CALL.code, -> { branchType = BranchType.Call branchLabels = listOf(inst.args[0].value as Int) } - OP_SWITCH_CALL -> { + OP_SWITCH_CALL.code -> { branchType = BranchType.Call branchLabels = inst.args.drop(1).map { it.value as Int } } diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetMapDesignations.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetMapDesignations.kt index 037c7529..bf3f6549 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetMapDesignations.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetMapDesignations.kt @@ -16,9 +16,9 @@ fun getMapDesignations( var cfg: ControlFlowGraph? = null for (inst in func0Segment.instructions) { - when (inst.opcode) { - OP_MAP_DESIGNATE, - OP_MAP_DESIGNATE_EX, + when (inst.opcode.code) { + OP_MAP_DESIGNATE.code, + OP_MAP_DESIGNATE_EX.code, -> { if (cfg == null) { cfg = ControlFlowGraph.create(instructionSegments) @@ -47,7 +47,7 @@ fun getMapDesignations( mapDesignations[areaId[0]!!] = variantId[0]!! } - OP_BB_MAP_DESIGNATE -> { + OP_BB_MAP_DESIGNATE.code -> { mapDesignations[inst.args[0].value as Int] = inst.args[2].value as Int } } diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetRegisterValue.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetRegisterValue.kt index c416541b..107c689d 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetRegisterValue.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetRegisterValue.kt @@ -7,17 +7,13 @@ import kotlin.math.min private val logger = KotlinLogging.logger {} -const val MIN_REGISTER_VALUE = MIN_SIGNED_DWORD_VALUE -const val MAX_REGISTER_VALUE = MAX_SIGNED_DWORD_VALUE - /** * Computes the possible values of a register right before a specific instruction. */ fun getRegisterValue(cfg: ControlFlowGraph, instruction: Instruction, register: Int): ValueSet { val block = cfg.getBlockForInstruction(instruction) - return findValues( - Context(), + return RegisterValueFinder().find( mutableSetOf(), block, block.indexOfInstruction(instruction), @@ -25,202 +21,195 @@ fun getRegisterValue(cfg: ControlFlowGraph, instruction: Instruction, register: ) } -private class Context { +private class RegisterValueFinder { var iterations = 0 -} -private fun findValues( - ctx: Context, - path: MutableSet, - block: BasicBlock, - end: Int, - register: Int, -): ValueSet { - if (++ctx.iterations > 100) { - logger.warn { "Too many iterations." } - return ValueSet.ofInterval(MIN_REGISTER_VALUE, MAX_REGISTER_VALUE) - } + fun find( + path: MutableSet, + block: BasicBlock, + end: Int, + register: Int, + ): ValueSet { + if (++iterations > 100) { + logger.warn { "Too many iterations." } + return ValueSet.all() + } - for (i in end - 1 downTo block.start) { - val instruction = block.segment.instructions[i] - val args = instruction.args + for (i in end - 1 downTo block.start) { + val instruction = block.segment.instructions[i] + val args = instruction.args - when (instruction.opcode) { - OP_LET -> { - if (args[0].value == register) { - return findValues(ctx, LinkedHashSet(path), block, i, args[1].value as Int) - } - } - - OP_LETI, - OP_LETB, - OP_LETW, - OP_SYNC_LETI, - -> { - if (args[0].value == register) { - return ValueSet.of(args[1].value as Int) - } - } - - OP_SET -> { - if (args[0].value == register) { - return ValueSet.of(1) - } - } - - OP_CLEAR -> { - if (args[0].value == register) { - return ValueSet.of(0) - } - } - - OP_REV -> { - if (args[0].value == register) { - val prevVals = findValues(ctx, LinkedHashSet(path), block, i, register) - - return if (prevVals.size == 1L && prevVals[0] == 0) { - ValueSet.of(1) - } else if (0 in prevVals) { - ValueSet.ofInterval(0, 1) - } else { - ValueSet.of(0) + when (instruction.opcode.code) { + OP_LET.code -> { + if (args[0].value == register) { + return find(LinkedHashSet(path), block, i, args[1].value as Int) } } - } - OP_ADDI -> { - if (args[0].value == register) { - val prevVals = findValues(ctx, LinkedHashSet(path), block, i, register) - prevVals += args[1].value as Int - return prevVals + OP_LETI.code, + OP_LETB.code, + OP_LETW.code, + OP_SYNC_LETI.code, + -> { + if (args[0].value == register) { + return ValueSet.of(args[1].value as Int) + } } - } - OP_SUBI -> { - if (args[0].value == register) { - val prevVals = findValues(ctx, LinkedHashSet(path), block, i, register) - prevVals -= args[1].value as Int - return prevVals + OP_SET.code -> { + if (args[0].value == register) { + return ValueSet.of(1) + } } - } - OP_MULI -> { - if (args[0].value == register) { - val prevVals = findValues(ctx, LinkedHashSet(path), block, i, register) - prevVals *= args[1].value as Int - return prevVals + OP_CLEAR.code -> { + if (args[0].value == register) { + return ValueSet.of(0) + } } - } - OP_DIVI -> { - if (args[0].value == register) { - val prevVals = findValues(ctx, LinkedHashSet(path), block, i, register) - prevVals /= args[1].value as Int - return prevVals + OP_REV.code -> { + if (args[0].value == register) { + val prevVals = find(LinkedHashSet(path), block, i, register) + + return if (prevVals.size == 1L && prevVals[0] == 0) { + ValueSet.of(1) + } else if (0 in prevVals) { + ValueSet.ofInterval(0, 1) + } else { + ValueSet.of(0) + } + } } - } - OP_IF_ZONE_CLEAR -> { - if (args[0].value == register) { - return ValueSet.ofInterval(0, 1) + OP_ADDI.code -> { + if (args[0].value == register) { + val prevVals = find(LinkedHashSet(path), block, i, register) + prevVals += args[1].value as Int + return prevVals + } } - } - OP_GET_DIFFLVL -> { - if (args[0].value == register) { - return ValueSet.ofInterval(0, 2) + OP_SUBI.code -> { + if (args[0].value == register) { + val prevVals = find(LinkedHashSet(path), block, i, register) + prevVals -= args[1].value as Int + return prevVals + } } - } - OP_GET_SLOTNUMBER -> { - if (args[0].value == register) { - return ValueSet.ofInterval(0, 3) + OP_MULI.code -> { + if (args[0].value == register) { + val prevVals = find(LinkedHashSet(path), block, i, register) + prevVals *= args[1].value as Int + return prevVals + } } - } - OP_GET_RANDOM -> { - if (args[1].value == register) { - // TODO: undefined values. - val min = findValues( - ctx, - LinkedHashSet(path), - block, - i, - args[0].value as Int - ).minOrNull()!! + OP_DIVI.code -> { + if (args[0].value == register) { + val prevVals = find(LinkedHashSet(path), block, i, register) + prevVals /= args[1].value as Int + return prevVals + } + } - val max = max( - findValues( - ctx, + OP_IF_ZONE_CLEAR.code -> { + if (args[0].value == register) { + return ValueSet.ofInterval(0, 1) + } + } + + OP_GET_DIFFLVL.code -> { + if (args[0].value == register) { + return ValueSet.ofInterval(0, 2) + } + } + + OP_GET_SLOTNUMBER.code -> { + if (args[0].value == register) { + return ValueSet.ofInterval(0, 3) + } + } + + OP_GET_RANDOM.code -> { + if (args[1].value == register) { + // TODO: undefined values. + val min = find( LinkedHashSet(path), block, i, - args[0].value as Int + 1 - ).maxOrNull()!!, - min + 1, - ) + args[0].value as Int + ).minOrNull()!! - return ValueSet.ofInterval(min, max - 1) + val max = max( + find( + LinkedHashSet(path), + block, + i, + args[0].value as Int + 1 + ).maxOrNull()!!, + min + 1, + ) + + return ValueSet.ofInterval(min, max - 1) + } } - } - OP_STACK_PUSHM, - OP_STACK_POPM, - -> { - val minReg = args[0].value as Int - val maxReg = args[0].value as Int + args[1].value as Int + OP_STACK_PUSHM.code, + OP_STACK_POPM.code, + -> { + val minReg = args[0].value as Int + val maxReg = args[0].value as Int + args[1].value as Int - if (register in minReg until maxReg) { - return ValueSet.ofInterval(MIN_REGISTER_VALUE, MAX_REGISTER_VALUE) + if (register in minReg until maxReg) { + return ValueSet.all() + } } - } - else -> { - // Assume any other opcodes that write to the register can produce any value. - val params = instruction.opcode.params - val argLen = min(args.size, params.size) + else -> { + // Assume any other opcodes that write to the register can produce any value. + val params = instruction.opcode.params + val argLen = min(args.size, params.size) - for (j in 0 until argLen) { - val param = params[j] + for (j in 0 until argLen) { + val param = params[j] - if (param.type is RegTupRefType) { - val regRef = args[j].value as Int + if (param.type is RegTupRefType) { + val regRef = args[j].value as Int - for ((k, reg_param) in param.type.registerTuples.withIndex()) { - if ((reg_param.access == ParamAccess.Write || - reg_param.access == ParamAccess.ReadWrite) && - regRef + k == register - ) { - return ValueSet.ofInterval( - MIN_REGISTER_VALUE, - MAX_REGISTER_VALUE, - ) + for ((k, reg_param) in param.type.registerTuples.withIndex()) { + if ((reg_param.access == ParamAccess.Write || + reg_param.access == ParamAccess.ReadWrite) && + regRef + k == register + ) { + return ValueSet.all() + } } } } } } } - } - val values = ValueSet.empty() - path.add(block) + val values = ValueSet.empty() + path.add(block) - for (from in block.from) { - // Bail out from loops. - if (from in path) { - values.setInterval(MIN_REGISTER_VALUE, MAX_REGISTER_VALUE) - break + for (from in block.from) { + // Bail out from loops. + if (from in path) { + return ValueSet.all() + } + + values.union(find(LinkedHashSet(path), from, from.end, register)) } - values.union(findValues(ctx, LinkedHashSet(path), from, from.end, register)) - } + // If values is empty at this point, we know nothing ever sets the register's value and it still + // has its initial value of 0. + if (values.isEmpty()) { + values.setValue(0) + } - // If values is empty at this point, we know nothing ever sets the register's value and it still - // has its initial value of 0. - if (values.isEmpty()) { - values.setValue(0) + return values } - - return values } diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetStackValue.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetStackValue.kt index a9a459d3..8a111d5b 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetStackValue.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetStackValue.kt @@ -1,6 +1,103 @@ package world.phantasmal.lib.assembly.dataFlowAnalysis -import world.phantasmal.lib.assembly.Instruction +import mu.KotlinLogging +import world.phantasmal.lib.assembly.* -fun getStackValue(cfg: ControlFlowGraph, instruction: Instruction, position: Int): ValueSet = - TODO() +private val logger = KotlinLogging.logger {} + +/** + * Computes the possible values of a stack element at the nth position from the top right before a + * specific instruction. + */ +fun getStackValue(cfg: ControlFlowGraph, instruction: Instruction, position: Int): ValueSet { + val block = cfg.getBlockForInstruction(instruction) + + return StackValueFinder().find( + mutableSetOf(), + cfg, + block, + block.indexOfInstruction(instruction), + position, + ) +} + +private class StackValueFinder { + var iterations = 0 + + fun find( + path: MutableSet, + cfg: ControlFlowGraph, + block: BasicBlock, + end: Int, + position: Int, + ): ValueSet { + if (++iterations > 100) { + logger.warn { "Too many iterations." } + return ValueSet.all() + } + + var pos = position + + for (i in end - 1 downTo block.start) { + val instruction = block.segment.instructions[i] + + if (instruction.opcode.stack == StackInteraction.Pop) { + pos += instruction.opcode.params.size + continue + } + + val args = instruction.args + + when (instruction.opcode.code) { + OP_ARG_PUSHR.code -> { + if (pos == 0) { + return getRegisterValue(cfg, instruction, args[0].value as Int) + } else { + pos-- + break + } + } + + OP_ARG_PUSHL.code, + OP_ARG_PUSHB.code, + OP_ARG_PUSHW.code, + -> { + if (pos == 0) { + return ValueSet.of(args[0].value as Int) + } else { + pos-- + break + } + } + + OP_ARG_PUSHA.code, + OP_ARG_PUSHO.code, + OP_ARG_PUSHS.code, + -> { + if (pos == 0) { + return ValueSet.all() + } else { + pos-- + break + } + } + + else -> break + } + } + + val values = ValueSet.empty() + path.add(block) + + for (from in block.from) { + // Bail out from loops. + if (from in path) { + return ValueSet.all() + } + + values.union(find(LinkedHashSet(path), cfg, from, from.end, pos)) + } + + return values + } +} diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/ValueSet.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/ValueSet.kt index 3b02b564..5d38aaf7 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/ValueSet.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/ValueSet.kt @@ -8,8 +8,7 @@ import kotlin.math.min */ class ValueSet private constructor(private val intervals: MutableList) : Iterable { val size: Long - get() = - intervals.fold(0L) { acc, i -> acc + i.end - i.start + 1L } + get() = intervals.fold(0L) { acc, i -> acc + i.end - i.start + 1L } operator fun get(i: Int): Int? { var idx = i @@ -162,8 +161,8 @@ class ValueSet private constructor(private val intervals: MutableList) */ operator fun divAssign(s: Int) { for (int in intervals) { - int.start = int.start / s - int.end = int.end / s + int.start /= s + int.end /= s } } @@ -245,6 +244,11 @@ class ValueSet private constructor(private val intervals: MutableList) */ fun empty(): ValueSet = ValueSet(mutableListOf()) + /** + * Returns a [ValueSet] containing all possible Int values. + */ + fun all(): ValueSet = ofInterval(Int.MIN_VALUE, Int.MAX_VALUE) + /** * Returns a [ValueSet] with a single initial [value]. */ diff --git a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/ObjectCode.kt b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/ObjectCode.kt index 219713b4..86c186b4 100644 --- a/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/ObjectCode.kt +++ b/lib/src/commonMain/kotlin/world/phantasmal/lib/fileFormats/quest/ObjectCode.kt @@ -405,9 +405,9 @@ private fun parseInstructionsSegment( var dropThrough = true for (i in instructions.size - 1 downTo 0) { - val opcode = instructions[i].opcode + val opcode = instructions[i].opcode.code - if (opcode == OP_RET || opcode == OP_JMP) { + if (opcode == OP_RET.code || opcode == OP_JMP.code) { dropThrough = false break } diff --git a/lib/src/commonTest/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetRegisterValueTests.kt b/lib/src/commonTest/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetRegisterValueTests.kt index f72dbc6c..75a1e313 100644 --- a/lib/src/commonTest/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetRegisterValueTests.kt +++ b/lib/src/commonTest/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/GetRegisterValueTests.kt @@ -81,14 +81,14 @@ class GetRegisterValueTests { val r0 = getRegisterValue(cfg, im[0].instructions[2], 0) assertEquals(MAX_REGISTER_VALUES_SIZE, r0.size) - assertEquals(MIN_REGISTER_VALUE, r0.minOrNull()) - assertEquals(MAX_REGISTER_VALUE, r0.maxOrNull()) + assertEquals(Int.MIN_VALUE, r0.minOrNull()) + assertEquals(Int.MAX_VALUE, r0.maxOrNull()) val r1 = getRegisterValue(cfg, im[0].instructions[2], 1) assertEquals(MAX_REGISTER_VALUES_SIZE, r1.size) - assertEquals(MIN_REGISTER_VALUE, r1.minOrNull()) - assertEquals(MAX_REGISTER_VALUE, r1.maxOrNull()) + assertEquals(Int.MIN_VALUE, r1.minOrNull()) + assertEquals(Int.MAX_VALUE, r1.maxOrNull()) } @Test diff --git a/lib/src/commonTest/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/ValueSetTests.kt b/lib/src/commonTest/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/ValueSetTests.kt index cd573710..7175b26f 100644 --- a/lib/src/commonTest/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/ValueSetTests.kt +++ b/lib/src/commonTest/kotlin/world/phantasmal/lib/assembly/dataFlowAnalysis/ValueSetTests.kt @@ -61,7 +61,7 @@ class ValueSetTests { fun plusAssign_integer_overflow() { // The set of all integers should stay the same after adding any integer. for (i in Int.MIN_VALUE..Int.MAX_VALUE step 10_000_000) { - val vs = ValueSet.ofInterval(Int.MIN_VALUE, Int.MAX_VALUE) + val vs = ValueSet.all() vs += i assertEquals(1L shl 32, vs.size) @@ -100,7 +100,7 @@ class ValueSetTests { fun minusAssign_integer_underflow() { // The set of all integers should stay the same after subtracting any integer. for (i in Int.MIN_VALUE..Int.MAX_VALUE step 10_000_000) { - val vs = ValueSet.ofInterval(Int.MIN_VALUE, Int.MAX_VALUE) + val vs = ValueSet.all() vs -= i assertEquals(1L shl 32, vs.size)