From d99473fe4d41bcf1461de84274cc52add219bfd9 Mon Sep 17 00:00:00 2001 From: Igor Laevsky Date: Fri, 20 Aug 2021 14:37:36 +0300 Subject: [PATCH] WASM: Canonicalize catches without finally blocks --- .../kotlin/backend/wasm/WasmLoweringPhases.kt | 8 ++ .../wasm/lower/TryCatchCanonicalization.kt | 104 ++++++++++++++++++ .../kotlin/ir/builders/ExpressionHelpers.kt | 3 + .../tcbInEliminatedCondition.kt | 2 - .../box/enum/initEntriesInCompanionObject.kt | 1 - .../codegen/box/enum/initEntriesInValueOf.kt | 1 - .../unreachableUninitializedProperty.kt | 2 - 7 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/TryCatchCanonicalization.kt diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt index 7b0d9f3c914..1576bde81bd 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt @@ -298,6 +298,13 @@ private val returnableBlockLoweringPhase = makeWasmModulePhase( prerequisite = setOf(functionInliningPhase) ) +private val tryCatchCanonicalization = makeWasmModulePhase( + ::TryCatchCanonicalization, + name = "TryCatchCanonicalization", + description = "Transforms try/catch statements into canonical form supported by the wasm codegen", + prerequisite = setOf(functionInliningPhase) +) + private val bridgesConstructionPhase = makeWasmModulePhase( ::WasmBridgesConstruction, name = "BridgesConstruction", @@ -461,6 +468,7 @@ val wasmPhases = NamedCompilerPhase( stringConstructorLowering then returnableBlockLoweringPhase then + tryCatchCanonicalization then forLoopsLoweringPhase then propertyAccessorInlinerLoweringPhase then diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/TryCatchCanonicalization.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/TryCatchCanonicalization.kt new file mode 100644 index 00000000000..9ce7d7ebcd9 --- /dev/null +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/lower/TryCatchCanonicalization.kt @@ -0,0 +1,104 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.backend.wasm.lower + +import org.jetbrains.kotlin.backend.common.FileLoweringPass +import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext +import org.jetbrains.kotlin.backend.common.lower.* +import org.jetbrains.kotlin.backend.wasm.WasmBackendContext +import org.jetbrains.kotlin.backend.wasm.utils.isCanonical +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.builders.declarations.buildVariable +import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrTry +import org.jetbrains.kotlin.ir.visitors.* +import org.jetbrains.kotlin.name.Name + +// This pass transforms try/catch statements into a simple canonical form which is easily mapped into wasm instruction set. +// From this: +// try { +// ...exprs +// } catch (e: Foo) { +// ...exprs +// } catch (e: Bar) { +// ...exprs +// } finally { +// ...exprs +// } +// We get this: +// try { +// ...exprs +// } catch (e: Throwable) { +// when (e) { +// is Foo -> ...exprs +// is Bar -> ...exprs +// } +// } +// TODO: Describe finally transformation + +internal class TryCatchCanonicalization(private val ctx: WasmBackendContext) : FileLoweringPass { + override fun lower(irFile: IrFile) { + irFile.transformChildrenVoid(CatchMerger(ctx)) + + irFile.acceptVoid(object : IrElementVisitorVoid { + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitTry(aTry: IrTry) { + check(aTry.isCanonical(ctx.irBuiltIns)) { "Found non canonical try/catch $aTry" } + } + }) + } +} + +internal class CatchMerger(private val ctx: WasmBackendContext): IrElementTransformerVoidWithContext() { + override fun visitTry(aTry: IrTry): IrExpression { + // First, handle all nested constructs + aTry.transformChildrenVoid(this) + + // Nothing to do + if (aTry.catches.isEmpty() || + aTry.catches.singleOrNull()?.catchParameter?.symbol?.owner?.type == ctx.irBuiltIns.throwableType) + return aTry + + ctx.createIrBuilder(currentScope!!.scope.scopeOwnerSymbol).run { + val newCatchParameter = buildVariable( + currentScope!!.scope.getLocalDeclarationParent(), + startOffset, + endOffset, + IrDeclarationOrigin.CATCH_PARAMETER, + Name.identifier("merged_catch_param"), + ctx.irBuiltIns.throwableType + ) + + val newCatchBody = irBlock(aTry) { + +irWhen( + aTry.type, + aTry.catches.map { + irBranch( + irIs(irGet(newCatchParameter), it.catchParameter.type), + irBlock(it.result) { + it.catchParameter.initializer = irImplicitCast(irGet(newCatchParameter), it.catchParameter.type) + it.catchParameter.origin = IrDeclarationOrigin.DEFINED + +it.catchParameter + +it.result + } + ) + } + irElseBranch(irThrow(irGet(newCatchParameter))) + ) + } + + val newCatch = irCatch(newCatchParameter, newCatchBody) + + return irTry(aTry.type, aTry.tryResult, listOf(newCatch), aTry.finallyExpression) + } + } +} + diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt index 69980747fb6..7d619a66e25 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt @@ -336,6 +336,9 @@ fun IrBuilderWithScope.irVararg(elementType: IrType, values: List) fun IrBuilderWithScope.irRawFunctionReferefence(type: IrType, symbol: IrFunctionSymbol) = IrRawFunctionReferenceImpl(startOffset, endOffset, type, symbol) +fun IrBuilderWithScope.irTry(type: IrType, tryResult: IrExpression, catches: List, finallyExpression: IrExpression?) = + IrTryImpl(startOffset, endOffset, type, tryResult, catches, finallyExpression) + inline fun IrBuilderWithScope.irBlock( startOffset: Int = this.startOffset, endOffset: Int = this.endOffset, diff --git a/compiler/testData/codegen/box/controlStructures/tcbInEliminatedCondition.kt b/compiler/testData/codegen/box/controlStructures/tcbInEliminatedCondition.kt index 0b5b4a02fc0..46d212b927c 100644 --- a/compiler/testData/codegen/box/controlStructures/tcbInEliminatedCondition.kt +++ b/compiler/testData/codegen/box/controlStructures/tcbInEliminatedCondition.kt @@ -1,5 +1,3 @@ -// IGNORE_BACKEND: WASM - fun foo() {} inline fun test1(v: Int) { diff --git a/compiler/testData/codegen/box/enum/initEntriesInCompanionObject.kt b/compiler/testData/codegen/box/enum/initEntriesInCompanionObject.kt index cab7240130a..876963fb292 100644 --- a/compiler/testData/codegen/box/enum/initEntriesInCompanionObject.kt +++ b/compiler/testData/codegen/box/enum/initEntriesInCompanionObject.kt @@ -1,5 +1,4 @@ // IGNORE_BACKEND: JS -// IGNORE_BACKEND: WASM var l = "" diff --git a/compiler/testData/codegen/box/enum/initEntriesInValueOf.kt b/compiler/testData/codegen/box/enum/initEntriesInValueOf.kt index cab7240130a..876963fb292 100644 --- a/compiler/testData/codegen/box/enum/initEntriesInValueOf.kt +++ b/compiler/testData/codegen/box/enum/initEntriesInValueOf.kt @@ -1,5 +1,4 @@ // IGNORE_BACKEND: JS -// IGNORE_BACKEND: WASM var l = "" diff --git a/compiler/testData/codegen/box/properties/unreachableUninitializedProperty.kt b/compiler/testData/codegen/box/properties/unreachableUninitializedProperty.kt index 7376c78fb5c..e4579096e3f 100644 --- a/compiler/testData/codegen/box/properties/unreachableUninitializedProperty.kt +++ b/compiler/testData/codegen/box/properties/unreachableUninitializedProperty.kt @@ -1,5 +1,3 @@ -// IGNORE_BACKEND: WASM -// WASM_MUTE_REASON: EXCEPTIONS_NOT_IMPLEMENTED // WITH_RUNTIME // KT-44496