Refactored stuff.
Added control flow information to ISA. In ISA exg is no longer treated as setting a definitive value. Added inspection find dead writes to registers.
This commit is contained in:
parent
6f99c2ffcc
commit
921449cbb8
@ -79,6 +79,8 @@ make it work with JUnit 5. Feel free to use the code (in package ```de.platon42.
|
|||||||
- Bugfix: Added alternate condition code tests `HS (=CC)` and `LO (=CS)`.
|
- Bugfix: Added alternate condition code tests `HS (=CC)` and `LO (=CS)`.
|
||||||
- Performance: Optimized mnemonic lookup.
|
- Performance: Optimized mnemonic lookup.
|
||||||
- Enhancement: Reworked Instruction Documentation provider, now shows condition codes.
|
- Enhancement: Reworked Instruction Documentation provider, now shows condition codes.
|
||||||
|
- Bugfix: In ISA `exg` is no longer treated as setting a definitive value.
|
||||||
|
- New: Added inspection find dead writes to registers!
|
||||||
|
|
||||||
### V0.4 (03-Aug-21)
|
### V0.4 (03-Aug-21)
|
||||||
|
|
||||||
|
14
build.gradle
14
build.gradle
@ -65,6 +65,8 @@ patchPluginXml {
|
|||||||
<li>Bugfix: Added alternate condition code tests HS (=CC) and LO (=CS).
|
<li>Bugfix: Added alternate condition code tests HS (=CC) and LO (=CS).
|
||||||
<li>Performance: Optimized mnemonic lookup.
|
<li>Performance: Optimized mnemonic lookup.
|
||||||
<li>Enhancement: Reworked Instruction Documentation provider, now shows condition codes.
|
<li>Enhancement: Reworked Instruction Documentation provider, now shows condition codes.
|
||||||
|
<li>Bugfix: In ISA exg is no longer treated as setting a definitive value.
|
||||||
|
<li>New: Added inspection find dead writes to registers!
|
||||||
</ul>
|
</ul>
|
||||||
<h4>V0.4 (03-Aug-21)</h4>
|
<h4>V0.4 (03-Aug-21)</h4>
|
||||||
<ul>
|
<ul>
|
||||||
@ -79,18 +81,6 @@ patchPluginXml {
|
|||||||
<li>Bugfix: Macro definitions with colons and without space supported (as found in P61a source).
|
<li>Bugfix: Macro definitions with colons and without space supported (as found in P61a source).
|
||||||
<li>New: When asking for documentation on registers, a code flow analysis is done. Cool stuff!
|
<li>New: When asking for documentation on registers, a code flow analysis is done. Cool stuff!
|
||||||
</ul>
|
</ul>
|
||||||
<h4>V0.3 (28-Jul-21)</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Enhancement: Macro contents are no longer parsed, added syntax highlighting options for macros.
|
|
||||||
<li>Enhancement: Macro definitions are now word and stub indexed, macro calls reference to definition.
|
|
||||||
<li>New: Macro definition refactoring and find usages support.
|
|
||||||
<li>Enhancement: Structural View also shows macro definitions.
|
|
||||||
<li>Bugfix: Missing REPT and ENDR assembler directives added.
|
|
||||||
<li>Cosmetics: Changed or added some icons at various places.
|
|
||||||
<li>Performance: Reference search for global labels and symbols now uses stub index.
|
|
||||||
<li>Compatibility: Restored compatibility with IDE versions < 2021.1.
|
|
||||||
<li>Performance: Optimized lexer.
|
|
||||||
</ul>
|
|
||||||
<p>Full changelog available at <a href="https://github.com/chrisly42/mc68000-asm-plugin#changelog">Github project site</a>.</p>
|
<p>Full changelog available at <a href="https://github.com/chrisly42/mc68000-asm-plugin#changelog">Github project site</a>.</p>
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
|
@ -187,12 +187,12 @@ enum class ConditionCode(val cc: String, val testedCc: Int) {
|
|||||||
|
|
||||||
fun getCcFromName(cc: String) = NAME_TO_CC_MAP[cc.lowercase()]!!
|
fun getCcFromName(cc: String) = NAME_TO_CC_MAP[cc.lowercase()]!!
|
||||||
|
|
||||||
fun getCcFromMnemonic(mnemonic: String) =
|
fun getCcFromMnemonic(originalMnemonic: String, mnemonic: String) =
|
||||||
// handle special case for dbra
|
// handle special case for dbra
|
||||||
if (mnemonic.equals("dbra", ignoreCase = true)) {
|
if (mnemonic.equals("dbra", ignoreCase = true)) {
|
||||||
FALSE
|
FALSE
|
||||||
} else {
|
} else {
|
||||||
NAME_TO_CC_MAP[mnemonic.substring(mnemonic.length - 2).lowercase()]!!
|
NAME_TO_CC_MAP[mnemonic.removePrefix(originalMnemonic.removeSuffix("CC")).lowercase()]!!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,11 +72,13 @@ const val RWM_READ_OPSIZE = 0x800
|
|||||||
const val RWM_READ_B = 0x900
|
const val RWM_READ_B = 0x900
|
||||||
const val RWM_READ_W = 0xb00
|
const val RWM_READ_W = 0xb00
|
||||||
const val RWM_READ_L = 0xf00
|
const val RWM_READ_L = 0xf00
|
||||||
|
const val RWM_READ_SHIFT = 8
|
||||||
|
|
||||||
const val RWM_MODIFY_OPSIZE = 0x880
|
const val RWM_MODIFY_OPSIZE = 0x080
|
||||||
const val RWM_MODIFY_B = 0x990
|
const val RWM_MODIFY_B = 0x090
|
||||||
const val RWM_MODIFY_W = 0xbb0
|
const val RWM_MODIFY_W = 0x0b0
|
||||||
const val RWM_MODIFY_L = 0xff0
|
const val RWM_MODIFY_L = 0x0f0
|
||||||
|
const val RWM_MODIFY_SHIFT = 4
|
||||||
|
|
||||||
const val RWM_OP1_SHIFT = 0
|
const val RWM_OP1_SHIFT = 0
|
||||||
const val RWM_OP2_SHIFT = 12
|
const val RWM_OP2_SHIFT = 12
|
||||||
@ -133,6 +135,7 @@ data class IsaData(
|
|||||||
val isPrivileged: Boolean = false,
|
val isPrivileged: Boolean = false,
|
||||||
val hasOps: Boolean = true,
|
val hasOps: Boolean = true,
|
||||||
val modes: List<AllowedAdrMode> = listOf(AllowedAdrMode()),
|
val modes: List<AllowedAdrMode> = listOf(AllowedAdrMode()),
|
||||||
|
val changesControlFlow: Boolean = false
|
||||||
)
|
)
|
||||||
|
|
||||||
object M68kIsa {
|
object M68kIsa {
|
||||||
@ -389,7 +392,7 @@ object M68kIsa {
|
|||||||
setOf(AddressMode.DATA_REGISTER_DIRECT, AddressMode.ADDRESS_REGISTER_DIRECT),
|
setOf(AddressMode.DATA_REGISTER_DIRECT, AddressMode.ADDRESS_REGISTER_DIRECT),
|
||||||
setOf(AddressMode.DATA_REGISTER_DIRECT, AddressMode.ADDRESS_REGISTER_DIRECT),
|
setOf(AddressMode.DATA_REGISTER_DIRECT, AddressMode.ADDRESS_REGISTER_DIRECT),
|
||||||
OP_SIZE_L,
|
OP_SIZE_L,
|
||||||
modInfo = RWM_SET_OP1_L or RWM_SET_OP2_L
|
modInfo = RWM_MODIFY_OP1_L or RWM_MODIFY_OP2_L // exchanging registers does not set value to a defined state
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -687,13 +690,19 @@ object M68kIsa {
|
|||||||
// Program Control Instructions
|
// Program Control Instructions
|
||||||
IsaData(
|
IsaData(
|
||||||
"bCC", "Branch Conditionally", conditionCodes = conditionCodesBcc,
|
"bCC", "Branch Conditionally", conditionCodes = conditionCodesBcc,
|
||||||
modes = listOf(AllowedAdrMode(setOf(AddressMode.ABSOLUTE_ADDRESS), null, OP_SIZE_SBW, testedCc = cc("-????")))
|
modes = listOf(AllowedAdrMode(setOf(AddressMode.ABSOLUTE_ADDRESS), null, OP_SIZE_SBW, testedCc = cc("-????"))),
|
||||||
|
changesControlFlow = true
|
||||||
|
),
|
||||||
|
IsaData(
|
||||||
|
"bra", "Branch",
|
||||||
|
modes = listOf(AllowedAdrMode(setOf(AddressMode.ABSOLUTE_ADDRESS), null, OP_SIZE_SBW)),
|
||||||
|
changesControlFlow = true
|
||||||
),
|
),
|
||||||
IsaData("bra", "Branch", modes = listOf(AllowedAdrMode(setOf(AddressMode.ABSOLUTE_ADDRESS), null, OP_SIZE_SBW))),
|
|
||||||
IsaData(
|
IsaData(
|
||||||
"bsr",
|
"bsr",
|
||||||
"Branch to Subroutine",
|
"Branch to Subroutine",
|
||||||
modes = listOf(AllowedAdrMode(setOf(AddressMode.ABSOLUTE_ADDRESS), null, OP_SIZE_SBW, modInfo = RWM_MODIFY_STACK))
|
modes = listOf(AllowedAdrMode(setOf(AddressMode.ABSOLUTE_ADDRESS), null, OP_SIZE_SBW, modInfo = RWM_MODIFY_STACK)),
|
||||||
|
changesControlFlow = true
|
||||||
),
|
),
|
||||||
|
|
||||||
IsaData(
|
IsaData(
|
||||||
@ -701,12 +710,8 @@ object M68kIsa {
|
|||||||
"Test Condition, Decrement, and Branch",
|
"Test Condition, Decrement, and Branch",
|
||||||
altMnemonics = listOf("dbra"),
|
altMnemonics = listOf("dbra"),
|
||||||
conditionCodes = conditionCodes,
|
conditionCodes = conditionCodes,
|
||||||
modes = listOf(
|
modes = listOf(AllowedAdrMode(DREG_ONLY, setOf(AddressMode.ABSOLUTE_ADDRESS), OP_SIZE_W, modInfo = RWM_MODIFY_OP1_W, testedCc = cc("-????"))),
|
||||||
AllowedAdrMode(
|
changesControlFlow = true
|
||||||
DREG_ONLY, setOf(AddressMode.ABSOLUTE_ADDRESS), OP_SIZE_W, modInfo = RWM_MODIFY_OP1_W,
|
|
||||||
testedCc = cc("-????")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
IsaData(
|
IsaData(
|
||||||
"sCC", "Set Conditionally", conditionCodes = conditionCodes,
|
"sCC", "Set Conditionally", conditionCodes = conditionCodes,
|
||||||
@ -726,7 +731,8 @@ object M68kIsa {
|
|||||||
AddressMode.PROGRAM_COUNTER_INDIRECT_WITH_INDEX
|
AddressMode.PROGRAM_COUNTER_INDIRECT_WITH_INDEX
|
||||||
), null, OP_UNSIZED
|
), null, OP_UNSIZED
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
changesControlFlow = true
|
||||||
),
|
),
|
||||||
IsaData(
|
IsaData(
|
||||||
"jsr", "Jump to Subroutine",
|
"jsr", "Jump to Subroutine",
|
||||||
@ -741,7 +747,8 @@ object M68kIsa {
|
|||||||
AddressMode.PROGRAM_COUNTER_INDIRECT_WITH_INDEX
|
AddressMode.PROGRAM_COUNTER_INDIRECT_WITH_INDEX
|
||||||
), null, OP_UNSIZED, modInfo = RWM_MODIFY_STACK
|
), null, OP_UNSIZED, modInfo = RWM_MODIFY_STACK
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
changesControlFlow = true
|
||||||
),
|
),
|
||||||
IsaData("nop", "No Operation", hasOps = false, modes = NO_OPS_UNSIZED),
|
IsaData("nop", "No Operation", hasOps = false, modes = NO_OPS_UNSIZED),
|
||||||
|
|
||||||
@ -749,9 +756,14 @@ object M68kIsa {
|
|||||||
"rtr",
|
"rtr",
|
||||||
"Return and Restore",
|
"Return and Restore",
|
||||||
hasOps = false,
|
hasOps = false,
|
||||||
modes = listOf(AllowedAdrMode(size = OP_UNSIZED, modInfo = RWM_MODIFY_STACK, affectedCc = cc("*****")))
|
modes = listOf(AllowedAdrMode(size = OP_UNSIZED, modInfo = RWM_MODIFY_STACK, affectedCc = cc("*****"))),
|
||||||
|
changesControlFlow = true
|
||||||
|
),
|
||||||
|
IsaData(
|
||||||
|
"rts", "Return from Subroutine", hasOps = false,
|
||||||
|
modes = listOf(AllowedAdrMode(size = OP_UNSIZED, modInfo = RWM_MODIFY_STACK)),
|
||||||
|
changesControlFlow = true
|
||||||
),
|
),
|
||||||
IsaData("rts", "Return from Subroutine", hasOps = false, modes = listOf(AllowedAdrMode(size = OP_UNSIZED, modInfo = RWM_MODIFY_STACK))),
|
|
||||||
|
|
||||||
IsaData(
|
IsaData(
|
||||||
"tst", "Test Operand", modes = listOf(
|
"tst", "Test Operand", modes = listOf(
|
||||||
@ -823,7 +835,8 @@ object M68kIsa {
|
|||||||
IsaData("reset", "Reset External Devices", isPrivileged = true, hasOps = false, modes = NO_OPS_UNSIZED),
|
IsaData("reset", "Reset External Devices", isPrivileged = true, hasOps = false, modes = NO_OPS_UNSIZED),
|
||||||
IsaData(
|
IsaData(
|
||||||
"rte", "Return from Exception", isPrivileged = true, hasOps = false,
|
"rte", "Return from Exception", isPrivileged = true, hasOps = false,
|
||||||
modes = listOf(AllowedAdrMode(size = OP_UNSIZED, modInfo = RWM_MODIFY_STACK))
|
modes = listOf(AllowedAdrMode(size = OP_UNSIZED, modInfo = RWM_MODIFY_STACK)),
|
||||||
|
changesControlFlow = true
|
||||||
),
|
),
|
||||||
IsaData(
|
IsaData(
|
||||||
"stop", "Stop", isPrivileged = true,
|
"stop", "Stop", isPrivileged = true,
|
||||||
@ -834,9 +847,13 @@ object M68kIsa {
|
|||||||
"chk", "Check Register Against Bound",
|
"chk", "Check Register Against Bound",
|
||||||
modes = listOf(AllowedAdrMode(ALL_EXCEPT_AREG, DREG_ONLY, OP_SIZE_W, modInfo = RWM_READ_OP1_W or RWM_READ_OP2_W, affectedCc = cc("-*UUU")))
|
modes = listOf(AllowedAdrMode(ALL_EXCEPT_AREG, DREG_ONLY, OP_SIZE_W, modInfo = RWM_READ_OP1_W or RWM_READ_OP2_W, affectedCc = cc("-*UUU")))
|
||||||
),
|
),
|
||||||
IsaData("illegal", "Take Illegal Instruction Trap", hasOps = false, modes = NO_OPS_UNSIZED),
|
IsaData("illegal", "Take Illegal Instruction Trap", hasOps = false, modes = NO_OPS_UNSIZED, changesControlFlow = true),
|
||||||
IsaData("trap", "Trap", modes = listOf(AllowedAdrMode(setOf(AddressMode.IMMEDIATE_DATA), null, OP_UNSIZED))),
|
IsaData("trap", "Trap", modes = listOf(AllowedAdrMode(setOf(AddressMode.IMMEDIATE_DATA), null, OP_UNSIZED)), changesControlFlow = true),
|
||||||
IsaData("trapv", "Trap on Overflow", hasOps = false, modes = NO_OPS_UNSIZED),
|
IsaData(
|
||||||
|
"trapv", "Trap on Overflow", hasOps = false,
|
||||||
|
modes = listOf(AllowedAdrMode(size = OP_UNSIZED, testedCc = cc("---?-"))),
|
||||||
|
changesControlFlow = true
|
||||||
|
),
|
||||||
|
|
||||||
IsaData(
|
IsaData(
|
||||||
"andi", "AND Immediate to Condition Code Register", id = "andi to CCR", altMnemonics = listOf("and"),
|
"andi", "AND Immediate to Condition Code Register", id = "andi to CCR", altMnemonics = listOf("and"),
|
||||||
|
@ -13,8 +13,8 @@ import com.intellij.ui.JBColor
|
|||||||
import de.platon42.intellij.plugins.m68k.asm.*
|
import de.platon42.intellij.plugins.m68k.asm.*
|
||||||
import de.platon42.intellij.plugins.m68k.asm.Register.Companion.getRegFromName
|
import de.platon42.intellij.plugins.m68k.asm.Register.Companion.getRegFromName
|
||||||
import de.platon42.intellij.plugins.m68k.psi.*
|
import de.platon42.intellij.plugins.m68k.psi.*
|
||||||
import de.platon42.intellij.plugins.m68k.psi.M68kAddressModeUtil.getOtherReadWriteModifyRegisters
|
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil.checkIfInstructionUsesRegister
|
||||||
import de.platon42.intellij.plugins.m68k.psi.M68kAddressModeUtil.getReadWriteModifyRegisters
|
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil.evaluateRegisterUse
|
||||||
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil.findExactIsaDataAndAllowedAdrModeForInstruction
|
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil.findExactIsaDataAndAllowedAdrModeForInstruction
|
||||||
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil.getOpSizeOrDefault
|
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil.getOpSizeOrDefault
|
||||||
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil.modifyRwmWithOpsize
|
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil.modifyRwmWithOpsize
|
||||||
@ -64,10 +64,10 @@ class M68kRegisterFlowDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
)
|
)
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
(RWM_SET_L and RWM_SIZE_MASK) and totalRwm.inv()
|
RWM_SIZE_MASK and totalRwm.inv()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(RWM_SET_L and RWM_SIZE_MASK) and ((cursorRwm and RWM_MODIFY_L) ushr 8)
|
RWM_SIZE_MASK and (((cursorRwm and RWM_MODIFY_L) ushr RWM_MODIFY_SHIFT) or ((cursorRwm and RWM_READ_L) ushr RWM_READ_SHIFT))
|
||||||
}
|
}
|
||||||
val initialStatement: M68kStatement = asmInstruction.parent as M68kStatement
|
val initialStatement: M68kStatement = asmInstruction.parent as M68kStatement
|
||||||
val localLabelName = PsiTreeUtil.findChildOfType(initialStatement, M68kLocalLabel::class.java)?.name ?: "-->"
|
val localLabelName = PsiTreeUtil.findChildOfType(initialStatement, M68kLocalLabel::class.java)?.name ?: "-->"
|
||||||
@ -85,7 +85,7 @@ class M68kRegisterFlowDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
backtrace.reverse()
|
backtrace.reverse()
|
||||||
val traceBits = (cursorRwm or (cursorRwm ushr 8)) and RWM_SIZE_MASK
|
val traceBits = (cursorRwm or (cursorRwm ushr RWM_MODIFY_SHIFT) or (cursorRwm ushr RWM_READ_SHIFT)) and RWM_SIZE_MASK
|
||||||
backtrace.addAll(analyseFlow(register, traceBits, false, initialStatement, linesLimit) {
|
backtrace.addAll(analyseFlow(register, traceBits, false, initialStatement, linesLimit) {
|
||||||
PsiTreeUtil.getNextSiblingOfType(
|
PsiTreeUtil.getNextSiblingOfType(
|
||||||
it,
|
it,
|
||||||
@ -210,30 +210,6 @@ class M68kRegisterFlowDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
)
|
)
|
||||||
.children(DocumentationMarkup.SECTION_CONTENT_CELL.child(HtmlChunk.nbsp()))
|
.children(DocumentationMarkup.SECTION_CONTENT_CELL.child(HtmlChunk.nbsp()))
|
||||||
|
|
||||||
private fun evaluateRegisterUse(
|
|
||||||
asmInstruction: M68kAsmInstruction,
|
|
||||||
adrMode: AllowedAdrMode,
|
|
||||||
register: Register
|
|
||||||
): List<Int> {
|
|
||||||
val opSize = getOpSizeOrDefault(asmInstruction.asmOp.opSize, adrMode)
|
|
||||||
|
|
||||||
val rwm1 = modifyRwmWithOpsize((adrMode.modInfo ushr RWM_OP1_SHIFT) and RWM_OP_MASK, opSize)
|
|
||||||
val rwm2 = if (asmInstruction.addressingModeList.size > 1) modifyRwmWithOpsize((adrMode.modInfo ushr RWM_OP2_SHIFT) and RWM_OP_MASK, opSize) else 0
|
|
||||||
return getReadWriteModifyRegisters(asmInstruction.addressingModeList[0], rwm1).asSequence()
|
|
||||||
.plus(getReadWriteModifyRegisters(asmInstruction.addressingModeList.getOrNull(1), rwm2))
|
|
||||||
.plus(getOtherReadWriteModifyRegisters(adrMode.modInfo))
|
|
||||||
.filter { it.first == register }
|
|
||||||
.map { it.second }
|
|
||||||
.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkIfInstructionUsesRegister(instruction: M68kAsmInstruction, register: Register): Boolean {
|
|
||||||
if (instruction.addressingModeList.isEmpty()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return instruction.addressingModeList.any { aml -> getReadWriteModifyRegisters(aml, 0).any { it.first == register } }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCustomDocumentationElement(editor: Editor, file: PsiFile, contextElement: PsiElement?, targetOffset: Int): PsiElement? {
|
override fun getCustomDocumentationElement(editor: Editor, file: PsiFile, contextElement: PsiElement?, targetOffset: Int): PsiElement? {
|
||||||
if (contextElement == null) return null
|
if (contextElement == null) return null
|
||||||
if (contextElement is M68kDataRegister || contextElement is M68kAddressRegister) return contextElement
|
if (contextElement is M68kDataRegister || contextElement is M68kAddressRegister) return contextElement
|
||||||
|
@ -0,0 +1,102 @@
|
|||||||
|
package de.platon42.intellij.plugins.m68k.inspections
|
||||||
|
|
||||||
|
import com.intellij.codeInspection.InspectionManager
|
||||||
|
import com.intellij.codeInspection.ProblemDescriptor
|
||||||
|
import com.intellij.codeInspection.ProblemHighlightType
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import com.intellij.util.SmartList
|
||||||
|
import de.platon42.intellij.plugins.m68k.asm.*
|
||||||
|
import de.platon42.intellij.plugins.m68k.asm.M68kIsa.findMatchingInstructions
|
||||||
|
import de.platon42.intellij.plugins.m68k.psi.M68kAddressModeUtil
|
||||||
|
import de.platon42.intellij.plugins.m68k.psi.M68kAsmInstruction
|
||||||
|
import de.platon42.intellij.plugins.m68k.psi.M68kGlobalLabel
|
||||||
|
import de.platon42.intellij.plugins.m68k.psi.M68kStatement
|
||||||
|
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil
|
||||||
|
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil.checkIfInstructionUsesRegister
|
||||||
|
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil.evaluateRegisterUse
|
||||||
|
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil.findExactIsaDataAndAllowedAdrModeForInstruction
|
||||||
|
import de.platon42.intellij.plugins.m68k.utils.M68kIsaUtil.getConcreteTestedCcFromMnemonic
|
||||||
|
|
||||||
|
class M68kDeadWriteInspection : AbstractBaseM68kLocalInspectionTool() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val DISPLAY_NAME = "Dead writes to registers"
|
||||||
|
|
||||||
|
private const val DEAD_WRITE_MSG = "Register %s is overwritten later without being used"
|
||||||
|
private const val POSSIBLY_DEAD_WRITE_MSG = "Register %s is overwritten later (only CC evaluated?)"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDisplayName() = DISPLAY_NAME
|
||||||
|
|
||||||
|
override fun checkAsmInstruction(asmInstruction: M68kAsmInstruction, manager: InspectionManager, isOnTheFly: Boolean): Array<ProblemDescriptor>? {
|
||||||
|
val asmOp = asmInstruction.asmOp
|
||||||
|
if (asmInstruction.addressingModeList.isEmpty()) return emptyArray()
|
||||||
|
|
||||||
|
val isaDataCandidates = findMatchingInstructions(asmOp.mnemonic)
|
||||||
|
if (isaDataCandidates.isEmpty()) return emptyArray()
|
||||||
|
val (_, adrMode) = findExactIsaDataAndAllowedAdrModeForInstruction(asmInstruction) ?: return emptyArray()
|
||||||
|
|
||||||
|
val opSize = M68kIsaUtil.getOpSizeOrDefault(asmInstruction.asmOp.opSize, adrMode)
|
||||||
|
val rwm1 = M68kIsaUtil.modifyRwmWithOpsize((adrMode.modInfo ushr RWM_OP1_SHIFT) and RWM_OP_MASK, opSize)
|
||||||
|
val rwm2 = if (asmInstruction.addressingModeList.size > 1) M68kIsaUtil.modifyRwmWithOpsize(
|
||||||
|
(adrMode.modInfo ushr RWM_OP2_SHIFT) and RWM_OP_MASK, opSize
|
||||||
|
) else 0
|
||||||
|
val regsWritten = M68kAddressModeUtil.getReadWriteModifyRegisters(asmInstruction.addressingModeList[0], rwm1).asSequence()
|
||||||
|
.plus(M68kAddressModeUtil.getReadWriteModifyRegisters(asmInstruction.addressingModeList.getOrNull(1), rwm2))
|
||||||
|
.plus(M68kAddressModeUtil.getOtherReadWriteModifyRegisters(adrMode.modInfo))
|
||||||
|
.filter { (it.second and RWM_SET_L) > 0 }
|
||||||
|
.distinct()
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
val hints = SmartList<ProblemDescriptor>()
|
||||||
|
for (regPair in regsWritten) {
|
||||||
|
val register = regPair.first
|
||||||
|
val rwm = regPair.second
|
||||||
|
var currStatement = asmInstruction.parent as M68kStatement
|
||||||
|
|
||||||
|
var ccModification = adrMode.affectedCc
|
||||||
|
var ccOverwritten = false
|
||||||
|
var ccTested = false
|
||||||
|
var hasModification = false
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
currStatement = PsiTreeUtil.getNextSiblingOfType(currStatement, M68kStatement::class.java) ?: break
|
||||||
|
val globalLabel = PsiTreeUtil.findChildOfType(currStatement, M68kGlobalLabel::class.java)
|
||||||
|
if (globalLabel != null) break
|
||||||
|
|
||||||
|
val currAsmInstruction = PsiTreeUtil.getChildOfType(currStatement, M68kAsmInstruction::class.java) ?: continue
|
||||||
|
val (isaData, currAdrMode) = findExactIsaDataAndAllowedAdrModeForInstruction(currAsmInstruction) ?: continue
|
||||||
|
if (isaData.changesControlFlow) break
|
||||||
|
val testedCc = getConcreteTestedCcFromMnemonic(currAsmInstruction.asmOp.mnemonic, isaData, adrMode)
|
||||||
|
if (((testedCc and ccModification) > 0) && !ccOverwritten) ccTested = true
|
||||||
|
if (currAdrMode.affectedCc != 0) ccOverwritten = true
|
||||||
|
if (checkIfInstructionUsesRegister(currAsmInstruction, register)) {
|
||||||
|
val totalRwms = evaluateRegisterUse(currAsmInstruction, currAdrMode, register).reduce(Int::or)
|
||||||
|
if (totalRwms and RWM_READ_L > 0) break
|
||||||
|
if (totalRwms and RWM_MODIFY_L > 0) {
|
||||||
|
hasModification = true
|
||||||
|
ccOverwritten = false
|
||||||
|
ccModification = ccModification or currAdrMode.affectedCc
|
||||||
|
}
|
||||||
|
if (totalRwms and RWM_SET_L >= rwm) {
|
||||||
|
if (ccTested && hasModification) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
hints.add(
|
||||||
|
manager.createProblemDescriptor(
|
||||||
|
asmInstruction,
|
||||||
|
asmInstruction,
|
||||||
|
(if (ccTested) POSSIBLY_DEAD_WRITE_MSG else DEAD_WRITE_MSG).format(register.regname),
|
||||||
|
if (ccTested) ProblemHighlightType.WEAK_WARNING else ProblemHighlightType.WARNING,
|
||||||
|
isOnTheFly
|
||||||
|
)
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return hints.toTypedArray()
|
||||||
|
}
|
||||||
|
}
|
@ -43,8 +43,8 @@ object M68kAddressModeUtil {
|
|||||||
is M68kProgramCounterIndirectWithDisplacementOldAddressingMode,
|
is M68kProgramCounterIndirectWithDisplacementOldAddressingMode,
|
||||||
is M68kAbsoluteAddressAddressingMode -> emptyList()
|
is M68kAbsoluteAddressAddressingMode -> emptyList()
|
||||||
|
|
||||||
is M68kAddressRegisterIndirectPostIncAddressingMode -> listOf(Register.getRegFromName(addressingMode.addressRegister.text) to RWM_MODIFY_L)
|
is M68kAddressRegisterIndirectPostIncAddressingMode -> listOf(Register.getRegFromName(addressingMode.addressRegister.text) to (RWM_READ_L or RWM_MODIFY_L))
|
||||||
is M68kAddressRegisterIndirectPreDecAddressingMode -> listOf(Register.getRegFromName(addressingMode.addressRegister.text) to RWM_MODIFY_L)
|
is M68kAddressRegisterIndirectPreDecAddressingMode -> listOf(Register.getRegFromName(addressingMode.addressRegister.text) to (RWM_READ_L or RWM_MODIFY_L))
|
||||||
is M68kWithAddressRegisterIndirect -> {
|
is M68kWithAddressRegisterIndirect -> {
|
||||||
if (addressingMode is M68kWithIndexRegister) {
|
if (addressingMode is M68kWithIndexRegister) {
|
||||||
listOf(
|
listOf(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package de.platon42.intellij.plugins.m68k.utils
|
package de.platon42.intellij.plugins.m68k.utils
|
||||||
|
|
||||||
import de.platon42.intellij.plugins.m68k.asm.*
|
import de.platon42.intellij.plugins.m68k.asm.*
|
||||||
|
import de.platon42.intellij.plugins.m68k.asm.ConditionCode.Companion.getCcFromMnemonic
|
||||||
import de.platon42.intellij.plugins.m68k.psi.M68kAddressModeUtil
|
import de.platon42.intellij.plugins.m68k.psi.M68kAddressModeUtil
|
||||||
import de.platon42.intellij.plugins.m68k.psi.M68kAsmInstruction
|
import de.platon42.intellij.plugins.m68k.psi.M68kAsmInstruction
|
||||||
import de.platon42.intellij.plugins.m68k.psi.M68kSpecialRegisterDirectAddressingMode
|
import de.platon42.intellij.plugins.m68k.psi.M68kSpecialRegisterDirectAddressingMode
|
||||||
@ -34,6 +35,30 @@ object M68kIsaUtil {
|
|||||||
return matchedIsaData.map { it to M68kIsa.findMatchingAddressMode(it.modes, op1, op2, opSize, specialReg) }
|
return matchedIsaData.map { it to M68kIsa.findMatchingAddressMode(it.modes, op1, op2, opSize, specialReg) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkIfInstructionUsesRegister(instruction: M68kAsmInstruction, register: Register): Boolean {
|
||||||
|
if (instruction.addressingModeList.isEmpty()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return instruction.addressingModeList.any { aml -> M68kAddressModeUtil.getReadWriteModifyRegisters(aml, 0).any { it.first == register } }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun evaluateRegisterUse(asmInstruction: M68kAsmInstruction, adrMode: AllowedAdrMode, register: Register): List<Int> {
|
||||||
|
val opSize = getOpSizeOrDefault(asmInstruction.asmOp.opSize, adrMode)
|
||||||
|
|
||||||
|
val rwm1 = modifyRwmWithOpsize((adrMode.modInfo ushr RWM_OP1_SHIFT) and RWM_OP_MASK, opSize)
|
||||||
|
val rwm2 = if (asmInstruction.addressingModeList.size > 1) modifyRwmWithOpsize((adrMode.modInfo ushr RWM_OP2_SHIFT) and RWM_OP_MASK, opSize) else 0
|
||||||
|
return M68kAddressModeUtil.getReadWriteModifyRegisters(asmInstruction.addressingModeList[0], rwm1).asSequence()
|
||||||
|
.plus(M68kAddressModeUtil.getReadWriteModifyRegisters(asmInstruction.addressingModeList.getOrNull(1), rwm2))
|
||||||
|
.plus(M68kAddressModeUtil.getOtherReadWriteModifyRegisters(adrMode.modInfo))
|
||||||
|
.filter { it.first == register }
|
||||||
|
.map { it.second }
|
||||||
|
.distinct()
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getConcreteTestedCcFromMnemonic(mnemonic: String, isaData: IsaData, adrMode: AllowedAdrMode) =
|
||||||
|
if (isaData.conditionCodes.isNotEmpty()) getCcFromMnemonic(isaData.mnemonic, mnemonic).testedCc else adrMode.testedCc
|
||||||
|
|
||||||
fun getOpSizeOrDefault(opSize: Int, adrMode: AllowedAdrMode): Int {
|
fun getOpSizeOrDefault(opSize: Int, adrMode: AllowedAdrMode): Int {
|
||||||
if (opSize == OP_UNSIZED && (adrMode.size != OP_UNSIZED)) {
|
if (opSize == OP_UNSIZED && (adrMode.size != OP_UNSIZED)) {
|
||||||
return if ((adrMode.size and OP_SIZE_W) == OP_SIZE_W) {
|
return if ((adrMode.size and OP_SIZE_W) == OP_SIZE_W) {
|
||||||
|
@ -50,6 +50,9 @@
|
|||||||
<localInspection implementationClass="de.platon42.intellij.plugins.m68k.inspections.M68kSyntaxInspection"
|
<localInspection implementationClass="de.platon42.intellij.plugins.m68k.inspections.M68kSyntaxInspection"
|
||||||
displayName="Assembly instruction validity" groupName="M68k"
|
displayName="Assembly instruction validity" groupName="M68k"
|
||||||
enabledByDefault="true" level="ERROR"/>
|
enabledByDefault="true" level="ERROR"/>
|
||||||
|
<localInspection implementationClass="de.platon42.intellij.plugins.m68k.inspections.M68kDeadWriteInspection"
|
||||||
|
displayName="Dead writes to registers" groupName="M68k"
|
||||||
|
enabledByDefault="true" level="WARNING"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
<actions>
|
<actions>
|
||||||
|
11
src/main/resources/inspectionDescriptions/M68kDeadWrite.html
Normal file
11
src/main/resources/inspectionDescriptions/M68kDeadWrite.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
Finds dead writes to registers, i.e. writes that will not have any effect.
|
||||||
|
|
||||||
|
Issues a weak warning if the instruction affects only condition codes that are later tested.
|
||||||
|
|
||||||
|
Analysis is terminated at the next global label or instruction that reads the register or changes control flow.
|
||||||
|
<!-- tooltip end -->
|
||||||
|
<p>Note: As there is no evaluation of macros right now, the inspection might report some false positives.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -11,6 +11,7 @@ import org.junit.jupiter.api.extension.ExtendWith
|
|||||||
@ExtendWith(LightCodeInsightExtension::class)
|
@ExtendWith(LightCodeInsightExtension::class)
|
||||||
@TestDataPath("src/test/resources/inspections")
|
@TestDataPath("src/test/resources/inspections")
|
||||||
abstract class AbstractInspectionTest : AbstractM68kTest() {
|
abstract class AbstractInspectionTest : AbstractM68kTest() {
|
||||||
|
|
||||||
protected fun assertHighlightings(myFixture: CodeInsightTestFixture, count: Int, snippet: String) {
|
protected fun assertHighlightings(myFixture: CodeInsightTestFixture, count: Int, snippet: String) {
|
||||||
assertThat(myFixture.doHighlighting())
|
assertThat(myFixture.doHighlighting())
|
||||||
.areExactly(count, Condition({ it.description?.contains(snippet) ?: false }, "containing"))
|
.areExactly(count, Condition({ it.description?.contains(snippet) ?: false }, "containing"))
|
||||||
|
@ -0,0 +1,157 @@
|
|||||||
|
package de.platon42.intellij.plugins.m68k.inspections
|
||||||
|
|
||||||
|
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
|
||||||
|
import de.platon42.intellij.jupiter.MyFixture
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class M68kDeadWriteInspectionTest : AbstractInspectionTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
internal fun find_direct_dead_write_to_register(@MyFixture myFixture: CodeInsightTestFixture) {
|
||||||
|
myFixture.enableInspections(M68kDeadWriteInspection::class.java)
|
||||||
|
myFixture.configureByText(
|
||||||
|
"deadwrite.asm", """
|
||||||
|
move.l #123,d1
|
||||||
|
add.l d0,d2
|
||||||
|
moveq.l #123,d1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assertHighlightings(myFixture, 1, "Register d1 is overwritten later without being used")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
internal fun find_direct_dead_write_to_register_with_modification(@MyFixture myFixture: CodeInsightTestFixture) {
|
||||||
|
myFixture.enableInspections(M68kDeadWriteInspection::class.java)
|
||||||
|
myFixture.configureByText(
|
||||||
|
"deadwrite.asm", """
|
||||||
|
move.l #123,d1
|
||||||
|
add.l d0,d1
|
||||||
|
moveq.l #123,d1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assertHighlightings(myFixture, 1, "Register d1 is overwritten later without being used")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
internal fun no_dead_write_to_register_due_to_part_modification(@MyFixture myFixture: CodeInsightTestFixture) {
|
||||||
|
myFixture.enableInspections(M68kDeadWriteInspection::class.java)
|
||||||
|
myFixture.configureByText(
|
||||||
|
"deadwrite.asm", """
|
||||||
|
move.l #123,d1
|
||||||
|
add.b d1,d0
|
||||||
|
moveq.l #123,d1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assertThat(myFixture.doHighlighting()).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
internal fun use_of_condition_code_causes_weak_warning(@MyFixture myFixture: CodeInsightTestFixture) {
|
||||||
|
myFixture.enableInspections(M68kDeadWriteInspection::class.java)
|
||||||
|
myFixture.configureByText(
|
||||||
|
"deadwrite.asm", """
|
||||||
|
move.l #123,d1
|
||||||
|
scc d0
|
||||||
|
moveq.l #123,d1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assertHighlightings(myFixture, 1, "Register d1 is overwritten later (only CC evaluated?)")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
internal fun use_of_conditional_instruction_for_non_conditional_causes_full_warning(@MyFixture myFixture: CodeInsightTestFixture) {
|
||||||
|
myFixture.enableInspections(M68kDeadWriteInspection::class.java)
|
||||||
|
myFixture.configureByText(
|
||||||
|
"deadwrite.asm", """
|
||||||
|
move.l #123,d1
|
||||||
|
st d0
|
||||||
|
moveq.l #123,d1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assertHighlightings(myFixture, 1, "Register d1 is overwritten later without being used")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
internal fun use_of_condition_code_after_modification_causes_no_warning(@MyFixture myFixture: CodeInsightTestFixture) {
|
||||||
|
myFixture.enableInspections(M68kDeadWriteInspection::class.java)
|
||||||
|
myFixture.configureByText(
|
||||||
|
"deadwrite.asm", """
|
||||||
|
move.l #123,d1
|
||||||
|
add.b d0,d1
|
||||||
|
seq .foo
|
||||||
|
moveq.l #123,d1
|
||||||
|
.foo
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assertThat(myFixture.doHighlighting()).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
internal fun use_of_control_flow_causes_no_warning(@MyFixture myFixture: CodeInsightTestFixture) {
|
||||||
|
myFixture.enableInspections(M68kDeadWriteInspection::class.java)
|
||||||
|
myFixture.configureByText(
|
||||||
|
"deadwrite.asm", """
|
||||||
|
move.l #123,d1
|
||||||
|
add.b d0,d1
|
||||||
|
bra.s .foo
|
||||||
|
moveq.l #123,d1
|
||||||
|
.foo
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assertThat(myFixture.doHighlighting()).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
internal fun movem_can_cause_multiple_warnings(@MyFixture myFixture: CodeInsightTestFixture) {
|
||||||
|
myFixture.enableInspections(M68kDeadWriteInspection::class.java)
|
||||||
|
myFixture.configureByText(
|
||||||
|
"deadwrite.asm", """
|
||||||
|
movem.l (a0),d0-d7
|
||||||
|
add.l d0,d1
|
||||||
|
moveq.l #123,d3
|
||||||
|
clr.l #123,d6
|
||||||
|
movem.l (a0),d5-d7
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assertHighlightings(myFixture, 1, "Register d5 is overwritten later without being used")
|
||||||
|
assertHighlightings(myFixture, 1, "Register d6 is overwritten later without being used")
|
||||||
|
assertHighlightings(myFixture, 1, "Register d7 is overwritten later without being used")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
internal fun overwrite_in_same_instruction_with_read_causes_no_warning(@MyFixture myFixture: CodeInsightTestFixture) {
|
||||||
|
myFixture.enableInspections(M68kDeadWriteInspection::class.java)
|
||||||
|
myFixture.configureByText(
|
||||||
|
"deadwrite.asm", """
|
||||||
|
move.w #123,d3
|
||||||
|
move.w (a0,d3.w),d3
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assertThat(myFixture.doHighlighting()).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
internal fun partial_overwrite_in_causes_no_warning(@MyFixture myFixture: CodeInsightTestFixture) {
|
||||||
|
myFixture.enableInspections(M68kDeadWriteInspection::class.java)
|
||||||
|
myFixture.configureByText(
|
||||||
|
"deadwrite.asm", """
|
||||||
|
move.w #123,d3
|
||||||
|
move.b d2,d3
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assertThat(myFixture.doHighlighting()).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
internal fun oversized_overwrite_in_causes_warning(@MyFixture myFixture: CodeInsightTestFixture) {
|
||||||
|
myFixture.enableInspections(M68kDeadWriteInspection::class.java)
|
||||||
|
myFixture.configureByText(
|
||||||
|
"deadwrite.asm", """
|
||||||
|
move.w #123,d3
|
||||||
|
move.l d2,d3
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assertHighlightings(myFixture, 1, "Register d3 is overwritten later without being used")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user