[WASM] Remove unreachable code from compiled wasm

This commit is contained in:
Igor Yakovlev
2022-08-10 20:13:17 +02:00
committed by teamcity
parent c2d4a2cfb5
commit 03c500d9e9
5 changed files with 91 additions and 37 deletions
@@ -7,9 +7,9 @@ package org.jetbrains.kotlin.backend.wasm.ir2wasm
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.expressions.IrLoop
import org.jetbrains.kotlin.ir.symbols.IrReturnableBlockSymbol
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.wasm.ir.WasmExpressionBuilder
import org.jetbrains.kotlin.wasm.ir.WasmInstr
import org.jetbrains.kotlin.wasm.ir.WasmLocal
enum class LoopLabelType { BREAK, CONTINUE }
@@ -23,6 +23,9 @@ interface WasmFunctionCodegenContext : WasmBaseCodegenContext {
fun referenceLocal(index: Int): WasmLocal
fun referenceLocal(type: SyntheticLocalType): WasmLocal
fun defineNonLocalReturnLevel(block: IrReturnableBlockSymbol, level: Int)
fun referenceNonLocalReturnLevel(block: IrReturnableBlockSymbol): Int
fun defineLoopLevel(irLoop: IrLoop, labelType: LoopLabelType, level: Int)
fun referenceLoopLevel(irLoop: IrLoop, labelType: LoopLabelType): Int
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.expressions.IrLoop
import org.jetbrains.kotlin.ir.symbols.IrReturnableBlockSymbol
import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.wasm.ir.*
@@ -29,6 +30,7 @@ class WasmFunctionCodegenContextImpl(
private val wasmLocals = LinkedHashMap<IrValueSymbol, WasmLocal>()
private val wasmSyntheticLocals = LinkedHashMap<SyntheticLocalType, WasmLocal>()
private val loopLevels = LinkedHashMap<Pair<IrLoop, LoopLabelType>, Int>()
private val nonLocalReturnLevels = LinkedHashMap<IrReturnableBlockSymbol, Int>()
override fun defineLocal(irValueDeclaration: IrValueSymbol) {
assert(irValueDeclaration !in wasmLocals) { "Redefinition of local" }
@@ -70,6 +72,14 @@ class WasmFunctionCodegenContextImpl(
}
}
override fun defineNonLocalReturnLevel(block: IrReturnableBlockSymbol, level: Int) {
nonLocalReturnLevels[block] = level
}
override fun referenceNonLocalReturnLevel(block: IrReturnableBlockSymbol): Int {
return nonLocalReturnLevels.getValue(block)
}
override fun defineLoopLevel(irLoop: IrLoop, labelType: LoopLabelType, level: Int) {
val loopKey = Pair(irLoop, labelType)
assert(loopKey !in loopLevels) { "Redefinition of loop" }
@@ -29,29 +29,4 @@ fun WasmModule.calculateIds() {
memories.calculateIds(startIndex = importedMemories.size)
tables.calculateIds(startIndex = importedTables.size)
tags.calculateIds(startIndex = importedTags.size)
}
// This is used to perform simple peephole-like optimizations from the WasmExpressionBuilder.
// Takes two adjacent instructions and returns an array with 0, 1 or 2 instructions to replace the original ones.
// Returns null if no action is required.
fun foldWasmInstructions(prev: WasmInstr?, next: WasmInstr): List<WasmInstr>? {
if (prev == null)
return null
// Unreachable is not needed after another unreachable or return
if (next.operator == WasmOp.UNREACHABLE && prev.operator in listOf(WasmOp.UNREACHABLE, WasmOp.RETURN))
return listOf(prev)
if (next.operator == WasmOp.DROP) {
// simple pure instruction + drop -> nothing
if (prev.operator == WasmOp.GET_UNIT || prev.operator == WasmOp.REF_NULL)
return listOf()
// return + drop -> return
if (prev.operator == WasmOp.RETURN)
return listOf(prev)
}
return null
}
}
@@ -60,8 +60,8 @@ abstract class WasmExpressionBuilder {
}
fun buildBlock(resultType: WasmType? = null): Int {
buildInstr(WasmOp.BLOCK, WasmImmediate.BlockType.Value(resultType))
numberOfNestedBlocks++
buildInstr(WasmOp.BLOCK, WasmImmediate.BlockType.Value(resultType))
return numberOfNestedBlocks
}
@@ -5,20 +5,86 @@
package org.jetbrains.kotlin.wasm.ir
private fun WasmOp.isOutCfgNode() = when (this) {
WasmOp.UNREACHABLE, WasmOp.RETURN, WasmOp.THROW, WasmOp.RETHROW, WasmOp.BR, WasmOp.BR_TABLE -> true
else -> false
}
private fun WasmOp.isInCfgNode() = when (this) {
WasmOp.ELSE, WasmOp.CATCH, WasmOp.CATCH_ALL -> true
else -> false
}
private fun WasmOp.pureStacklessInstruction() = when (this) {
WasmOp.GET_UNIT, WasmOp.REF_NULL, WasmOp.I32_CONST, WasmOp.I64_CONST, WasmOp.F32_CONST, WasmOp.F64_CONST, WasmOp.LOCAL_GET, WasmOp.GLOBAL_GET -> true
else -> false
}
class WasmIrExpressionBuilder(
val expression: MutableList<WasmInstr>
) : WasmExpressionBuilder() {
override fun buildInstr(op: WasmOp, vararg immediates: WasmImmediate) {
val nextInstr = WasmInstr(op, immediates.toList())
val foldedInstrs = foldWasmInstructions(expression.lastOrNull(), nextInstr)
private val lastInstr: WasmInstr?
get() = expression.lastOrNull()
private var eatEverythingUntilLevel: Int? = null
if (foldedInstrs == null) {
expression += nextInstr
} else {
expression.removeLastOrNull()
expression += foldedInstrs
private fun addInstruction(op: WasmOp, immediates: Array<out WasmImmediate>) {
val newInstruction = WasmInstr(op, immediates.toList())
expression.add(newInstruction)
}
private fun getCurrentEatLevel(op: WasmOp): Int? {
val eatLevel = eatEverythingUntilLevel ?: return null
if (numberOfNestedBlocks == eatLevel && op.isInCfgNode()) {
eatEverythingUntilLevel = null
return null
}
if (numberOfNestedBlocks < eatLevel) {
eatEverythingUntilLevel = null
return null
}
return eatLevel
}
override fun buildInstr(op: WasmOp, vararg immediates: WasmImmediate) {
val currentEatUntil = getCurrentEatLevel(op)
if (currentEatUntil != null) {
if (currentEatUntil <= numberOfNestedBlocks) return
} else {
if (op.isOutCfgNode()) {
eatEverythingUntilLevel = numberOfNestedBlocks
addInstruction(op, immediates)
return
}
}
val lastInstruction = lastInstr
if (lastInstruction == null) {
addInstruction(op, immediates)
return
}
val lastOperator = lastInstruction.operator
// droppable instructions + drop/unreachable -> nothing
if ((op == WasmOp.DROP || op == WasmOp.UNREACHABLE) && lastOperator.pureStacklessInstruction()) {
expression.removeLast()
return
}
// local set and local get for the same argument -> local tee
if (lastOperator == WasmOp.LOCAL_SET && op == WasmOp.LOCAL_GET) {
val localSetNumber = (lastInstruction.immediates.firstOrNull() as? WasmImmediate.LocalIdx)?.value
if (localSetNumber != null) {
val localGetNumber = (immediates.firstOrNull() as? WasmImmediate.LocalIdx)?.value
if (localGetNumber == localSetNumber) {
expression.removeLast()
addInstruction(WasmOp.LOCAL_TEE, immediates)
return
}
}
}
addInstruction(op, immediates)
}
override var numberOfNestedBlocks: Int = 0
@@ -32,4 +98,4 @@ inline fun buildWasmExpression(body: WasmExpressionBuilder.() -> Unit): MutableL
val res = mutableListOf<WasmInstr>()
WasmIrExpressionBuilder(res).body()
return res
}
}