From 03c500d9e9f1db1c9ef3974dc8200ebcfa0c54b3 Mon Sep 17 00:00:00 2001 From: Igor Yakovlev Date: Wed, 10 Aug 2022 20:13:17 +0200 Subject: [PATCH] [WASM] Remove unreachable code from compiled wasm --- .../ir2wasm/WasmFunctionCodegenContext.kt | 5 +- .../ir2wasm/WasmFunctionCodegenContextImpl.kt | 10 +++ .../src/org/jetbrains/kotlin/wasm/ir/Utils.kt | 27 +----- .../kotlin/wasm/ir/WasmExpressionBuilder.kt | 2 +- .../kotlin/wasm/ir/WasmIrExpressionBuilder.kt | 84 +++++++++++++++++-- 5 files changed, 91 insertions(+), 37 deletions(-) diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmFunctionCodegenContext.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmFunctionCodegenContext.kt index bc5acc2af52..1148609a5ab 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmFunctionCodegenContext.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmFunctionCodegenContext.kt @@ -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 diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmFunctionCodegenContextImpl.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmFunctionCodegenContextImpl.kt index 05395d0a373..445b4f06baa 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmFunctionCodegenContextImpl.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmFunctionCodegenContextImpl.kt @@ -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() private val wasmSyntheticLocals = LinkedHashMap() private val loopLevels = LinkedHashMap, Int>() + private val nonLocalReturnLevels = LinkedHashMap() 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" } diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Utils.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Utils.kt index c406f5affde..a0bb9ddf095 100644 --- a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Utils.kt +++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Utils.kt @@ -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? { - 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 -} - +} \ No newline at end of file diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmExpressionBuilder.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmExpressionBuilder.kt index 330ef72f5f6..9261ac34752 100644 --- a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmExpressionBuilder.kt +++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmExpressionBuilder.kt @@ -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 } diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmIrExpressionBuilder.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmIrExpressionBuilder.kt index 0d82e853e13..8e49badf44a 100644 --- a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmIrExpressionBuilder.kt +++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmIrExpressionBuilder.kt @@ -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 ) : 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) { + 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() WasmIrExpressionBuilder(res).body() return res -} +} \ No newline at end of file