2022-09-19 17:41:43 +00:00
|
|
|
package prog8.codegen.intermediate
|
2022-08-21 15:21:29 +00:00
|
|
|
|
2022-09-19 17:41:43 +00:00
|
|
|
import prog8.intermediate.*
|
|
|
|
|
|
|
|
internal class IRPeepholeOptimizer(private val irprog: IRProgram) {
|
2022-08-21 15:21:29 +00:00
|
|
|
fun optimize() {
|
2022-09-19 16:09:56 +00:00
|
|
|
irprog.blocks.asSequence().flatMap { it.subroutines }.forEach { sub ->
|
2022-09-30 00:47:33 +00:00
|
|
|
joinChunks(sub)
|
2022-08-28 14:43:15 +00:00
|
|
|
sub.chunks.forEach { chunk ->
|
2022-09-08 20:59:13 +00:00
|
|
|
// we don't optimize Inline Asm chunks here.
|
|
|
|
if(chunk is IRCodeChunk) {
|
|
|
|
do {
|
2022-10-04 20:54:14 +00:00
|
|
|
val indexedInstructions = chunk.instructions.withIndex()
|
2022-09-26 17:46:44 +00:00
|
|
|
.filter { it.value is IRInstruction }
|
|
|
|
.map { IndexedValue(it.index, it.value as IRInstruction) }
|
2022-09-08 20:59:13 +00:00
|
|
|
val changed = removeNops(chunk, indexedInstructions)
|
|
|
|
|| removeDoubleLoadsAndStores(chunk, indexedInstructions) // TODO not yet implemented
|
|
|
|
|| removeUselessArithmetic(chunk, indexedInstructions)
|
|
|
|
|| removeWeirdBranches(chunk, indexedInstructions)
|
|
|
|
|| removeDoubleSecClc(chunk, indexedInstructions)
|
|
|
|
|| cleanupPushPop(chunk, indexedInstructions)
|
|
|
|
// TODO other optimizations:
|
|
|
|
// more complex optimizations such as unused registers
|
|
|
|
} while (changed)
|
|
|
|
}
|
2022-08-25 19:02:18 +00:00
|
|
|
}
|
2022-08-21 15:21:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-30 00:47:33 +00:00
|
|
|
private fun joinChunks(sub: IRSubroutine) {
|
|
|
|
/*
|
|
|
|
Subroutine contains a list of chunks.
|
|
|
|
Some can be joined into one.
|
|
|
|
TODO: this has to be changed later...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if(sub.chunks.isEmpty())
|
|
|
|
return
|
|
|
|
|
|
|
|
fun mayJoin(previous: IRCodeChunkBase, chunk: IRCodeChunkBase): Boolean {
|
|
|
|
if(previous is IRCodeChunk && chunk is IRCodeChunk) {
|
|
|
|
return true
|
|
|
|
|
|
|
|
// TODO: only if all instructions are non-branching, allow it to join?
|
|
|
|
// return !chunk.lines.filterIsInstance<IRInstruction>().any {it.opcode in OpcodesThatBranch }
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
val chunks = mutableListOf<IRCodeChunkBase>()
|
|
|
|
chunks += sub.chunks[0]
|
|
|
|
for(ix in 1 until sub.chunks.size) {
|
|
|
|
if(mayJoin(chunks.last(), sub.chunks[ix]))
|
2022-10-04 20:54:14 +00:00
|
|
|
chunks.last().instructions += sub.chunks[ix].instructions
|
2022-09-30 00:47:33 +00:00
|
|
|
else
|
|
|
|
chunks += sub.chunks[ix]
|
|
|
|
}
|
|
|
|
sub.chunks.clear()
|
|
|
|
sub.chunks += chunks
|
|
|
|
}
|
|
|
|
|
2022-09-26 17:46:44 +00:00
|
|
|
private fun cleanupPushPop(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
2022-08-21 15:21:29 +00:00
|
|
|
// push followed by pop to same target, or different target->replace with load
|
|
|
|
var changed = false
|
|
|
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
2022-09-19 17:41:43 +00:00
|
|
|
if(ins.opcode== Opcode.PUSH) {
|
2022-10-04 20:54:14 +00:00
|
|
|
if(idx < chunk.instructions.size-1) {
|
|
|
|
val insAfter = chunk.instructions[idx+1] as? IRInstruction
|
2022-09-26 17:46:44 +00:00
|
|
|
if(insAfter!=null && insAfter.opcode == Opcode.POP) {
|
|
|
|
if(ins.reg1==insAfter.reg1) {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions.removeAt(idx)
|
|
|
|
chunk.instructions.removeAt(idx)
|
2022-08-21 15:21:29 +00:00
|
|
|
} else {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions[idx] = IRInstruction(Opcode.LOADR, ins.type, reg1=insAfter.reg1, reg2=ins.reg1)
|
|
|
|
chunk.instructions.removeAt(idx+1)
|
2022-08-21 15:21:29 +00:00
|
|
|
}
|
|
|
|
changed = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return changed
|
|
|
|
}
|
|
|
|
|
2022-09-26 17:46:44 +00:00
|
|
|
private fun removeDoubleSecClc(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
2022-08-21 15:21:29 +00:00
|
|
|
// double sec, clc
|
|
|
|
// sec+clc or clc+sec
|
|
|
|
var changed = false
|
|
|
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
2022-09-19 17:41:43 +00:00
|
|
|
if(ins.opcode== Opcode.SEC || ins.opcode== Opcode.CLC) {
|
2022-10-04 20:54:14 +00:00
|
|
|
if(idx < chunk.instructions.size-1) {
|
|
|
|
val insAfter = chunk.instructions[idx+1] as? IRInstruction
|
2022-09-26 17:46:44 +00:00
|
|
|
if(insAfter?.opcode == ins.opcode) {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions.removeAt(idx)
|
2022-08-21 15:21:29 +00:00
|
|
|
changed = true
|
|
|
|
}
|
2022-09-26 17:46:44 +00:00
|
|
|
else if(ins.opcode== Opcode.SEC && insAfter?.opcode== Opcode.CLC) {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions.removeAt(idx)
|
2022-08-21 15:21:29 +00:00
|
|
|
changed = true
|
|
|
|
}
|
2022-09-26 17:46:44 +00:00
|
|
|
else if(ins.opcode== Opcode.CLC && insAfter?.opcode== Opcode.SEC) {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions.removeAt(idx)
|
2022-08-21 15:21:29 +00:00
|
|
|
changed = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return changed
|
|
|
|
}
|
|
|
|
|
2022-09-26 17:46:44 +00:00
|
|
|
private fun removeWeirdBranches(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
2022-08-21 15:21:29 +00:00
|
|
|
var changed = false
|
|
|
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
2022-09-19 17:41:43 +00:00
|
|
|
val labelSymbol = ins.labelSymbol
|
|
|
|
if(ins.opcode== Opcode.JUMP && labelSymbol!=null) {
|
2022-09-30 13:27:03 +00:00
|
|
|
// remove jump/branch to label immediately below
|
2022-10-04 20:54:14 +00:00
|
|
|
if(idx < chunk.instructions.size-1) {
|
|
|
|
val label = chunk.instructions[idx+1] as? IRCodeLabel
|
2022-09-26 17:03:54 +00:00
|
|
|
if(label?.name == labelSymbol) {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions.removeAt(idx)
|
2022-08-21 15:21:29 +00:00
|
|
|
changed = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-09-30 13:27:03 +00:00
|
|
|
// remove useless RETURN
|
|
|
|
if(ins.opcode == Opcode.RETURN && idx>0) {
|
2022-10-04 20:54:14 +00:00
|
|
|
val previous = chunk.instructions[idx-1] as? IRInstruction
|
2022-09-30 13:27:03 +00:00
|
|
|
if(previous?.opcode in setOf(Opcode.JUMP, Opcode.JUMPA, Opcode.RETURN)) {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions.removeAt(idx)
|
2022-09-30 13:27:03 +00:00
|
|
|
changed = true
|
|
|
|
}
|
|
|
|
}
|
2022-08-21 15:21:29 +00:00
|
|
|
}
|
|
|
|
return changed
|
|
|
|
}
|
|
|
|
|
2022-09-26 17:46:44 +00:00
|
|
|
private fun removeUselessArithmetic(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
2022-08-21 15:21:29 +00:00
|
|
|
// note: this is hard to solve for the non-immediate instructions atm because the values are loaded into registers first
|
|
|
|
var changed = false
|
|
|
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
|
|
|
when (ins.opcode) {
|
|
|
|
Opcode.DIV, Opcode.DIVS, Opcode.MUL, Opcode.MOD -> {
|
|
|
|
if (ins.value == 1) {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions.removeAt(idx)
|
2022-08-21 15:21:29 +00:00
|
|
|
changed = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Opcode.ADD, Opcode.SUB -> {
|
|
|
|
if (ins.value == 1) {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions[idx] = IRInstruction(
|
2022-08-21 15:21:29 +00:00
|
|
|
if (ins.opcode == Opcode.ADD) Opcode.INC else Opcode.DEC,
|
|
|
|
ins.type,
|
|
|
|
ins.reg1
|
|
|
|
)
|
|
|
|
changed = true
|
|
|
|
} else if (ins.value == 0) {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions.removeAt(idx)
|
2022-08-21 15:21:29 +00:00
|
|
|
changed = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Opcode.AND -> {
|
|
|
|
if (ins.value == 0) {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = 0)
|
2022-08-21 15:21:29 +00:00
|
|
|
changed = true
|
2022-09-30 13:27:03 +00:00
|
|
|
} else if (ins.value == 255 && ins.type == IRDataType.BYTE) {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions.removeAt(idx)
|
2022-08-21 15:21:29 +00:00
|
|
|
changed = true
|
2022-09-30 13:27:03 +00:00
|
|
|
} else if (ins.value == 65535 && ins.type == IRDataType.WORD) {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions.removeAt(idx)
|
2022-08-21 15:21:29 +00:00
|
|
|
changed = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Opcode.OR -> {
|
|
|
|
if (ins.value == 0) {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions.removeAt(idx)
|
2022-08-21 15:21:29 +00:00
|
|
|
changed = true
|
2022-09-30 13:27:03 +00:00
|
|
|
} else if ((ins.value == 255 && ins.type == IRDataType.BYTE) || (ins.value == 65535 && ins.type == IRDataType.WORD)) {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, value = ins.value)
|
2022-08-21 15:21:29 +00:00
|
|
|
changed = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Opcode.XOR -> {
|
|
|
|
if (ins.value == 0) {
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions.removeAt(idx)
|
2022-08-21 15:21:29 +00:00
|
|
|
changed = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else -> {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return changed
|
|
|
|
}
|
|
|
|
|
2022-09-26 17:46:44 +00:00
|
|
|
private fun removeNops(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
2022-08-21 15:21:29 +00:00
|
|
|
var changed = false
|
|
|
|
indexedInstructions.reversed().forEach { (idx, ins) ->
|
|
|
|
if (ins.opcode == Opcode.NOP) {
|
|
|
|
changed = true
|
2022-10-04 20:54:14 +00:00
|
|
|
chunk.instructions.removeAt(idx)
|
2022-08-21 15:21:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return changed
|
|
|
|
}
|
|
|
|
|
2022-09-26 17:46:44 +00:00
|
|
|
private fun removeDoubleLoadsAndStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
2022-08-21 15:21:29 +00:00
|
|
|
var changed = false
|
|
|
|
indexedInstructions.forEach { (idx, ins) ->
|
|
|
|
|
|
|
|
// TODO: detect multiple loads to the same target registers, only keep first (if source is not I/O memory)
|
|
|
|
// TODO: detect multiple stores to the same target, only keep first (if target is not I/O memory)
|
|
|
|
// TODO: detect multiple float ffrom/fto to the same target, only keep first
|
|
|
|
// TODO: detect multiple sequential rnd with same reg1, only keep one
|
|
|
|
// TODO: detect subsequent same xors/nots/negs, remove the pairs completely as they cancel out
|
|
|
|
// TODO: detect multiple same ands, ors; only keep first
|
|
|
|
// TODO: (hard) detect multiple registers being assigned the same value (and not changed) - use only 1 of them
|
|
|
|
// ...
|
|
|
|
}
|
|
|
|
return changed
|
|
|
|
}
|
|
|
|
}
|