[WASM] Native support of returnable blocks

This commit is contained in:
Igor Yakovlev
2022-08-12 20:31:38 +02:00
committed by teamcity
parent 03c500d9e9
commit 61d6f69bca
7 changed files with 179 additions and 84 deletions
@@ -144,8 +144,8 @@ private val tailrecLoweringPhase = makeWasmModulePhase(
private val wasmStringSwitchOptimizerLowering = makeWasmModulePhase( private val wasmStringSwitchOptimizerLowering = makeWasmModulePhase(
::WasmStringSwitchOptimizerLowering, ::WasmStringSwitchOptimizerLowering,
name = "!!!", name = "WasmStringSwitchOptimizerLowering",
description = "!!!" description = "Replace when with constant string cases to binary search by string hashcodes"
) )
private val complexExternalDeclarationsToTopLevelFunctionsLowering = makeWasmModulePhase( private val complexExternalDeclarationsToTopLevelFunctionsLowering = makeWasmModulePhase(
@@ -403,13 +403,6 @@ private val tryCatchCanonicalization = makeWasmModulePhase(
prerequisite = setOf(functionInliningPhase) prerequisite = setOf(functionInliningPhase)
) )
private val returnableBlockLoweringPhase = makeWasmModulePhase(
::ReturnableBlockLowering,
name = "ReturnableBlockLowering",
description = "Replace returnable block with do-while loop",
prerequisite = setOf(functionInliningPhase)
)
private val bridgesConstructionPhase = makeWasmModulePhase( private val bridgesConstructionPhase = makeWasmModulePhase(
::WasmBridgesConstruction, ::WasmBridgesConstruction,
name = "BridgesConstruction", name = "BridgesConstruction",
@@ -627,7 +620,6 @@ val wasmPhases = NamedCompilerPhase(
unhandledExceptionLowering then unhandledExceptionLowering then
tryCatchCanonicalization then tryCatchCanonicalization then
returnableBlockLoweringPhase then
forLoopsLoweringPhase then forLoopsLoweringPhase then
propertyLazyInitLoweringPhase then propertyLazyInitLoweringPhase then
@@ -3,8 +3,6 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/ */
@file:OptIn(ExperimentalUnsignedTypes::class)
package org.jetbrains.kotlin.backend.wasm.ir2wasm package org.jetbrains.kotlin.backend.wasm.ir2wasm
import org.jetbrains.kotlin.backend.common.ir.returnType import org.jetbrains.kotlin.backend.common.ir.returnType
@@ -21,6 +19,7 @@ import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrReturnableBlockSymbol
import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
@@ -30,7 +29,8 @@ import org.jetbrains.kotlin.wasm.ir.*
class BodyGenerator( class BodyGenerator(
val context: WasmFunctionCodegenContext, val context: WasmFunctionCodegenContext,
private val hierarchyDisjointUnions: DisjointUnions<IrClassSymbol> private val hierarchyDisjointUnions: DisjointUnions<IrClassSymbol>,
private val isGetUnitFunction: Boolean,
) : IrElementVisitorVoid { ) : IrElementVisitorVoid {
val body: WasmExpressionBuilder = context.bodyGen val body: WasmExpressionBuilder = context.bodyGen
@@ -45,12 +45,12 @@ class BodyGenerator(
} }
// Generates code for the given IR element. Leaves something on the stack unless expression was of the type Void. // Generates code for the given IR element. Leaves something on the stack unless expression was of the type Void.
private fun generateExpression(elem: IrElement) { internal fun generateExpression(elem: IrElement) {
assert(elem is IrExpression || elem is IrVariable) { "Unsupported statement kind" } assert(elem is IrExpression || elem is IrVariable) { "Unsupported statement kind" }
elem.acceptVoid(this) elem.acceptVoid(this)
if (elem is IrExpression && elem.type == irBuiltIns.nothingType) { if (elem is IrExpression && elem.type.isNothing()) {
body.buildUnreachable() body.buildUnreachable()
} }
} }
@@ -296,7 +296,7 @@ class BodyGenerator(
} }
if (tryToGenerateIntrinsicCall(call, function)) { if (tryToGenerateIntrinsicCall(call, function)) {
if (function.returnType == irBuiltIns.unitType) if (function.returnType.isUnit())
body.buildGetUnit() body.buildGetUnit()
return return
} }
@@ -346,7 +346,7 @@ class BodyGenerator(
} }
// Unit types don't cross function boundaries // Unit types don't cross function boundaries
if (function.returnType == irBuiltIns.unitType) if (function.returnType.isUnit())
body.buildGetUnit() body.buildGetUnit()
} }
@@ -498,21 +498,36 @@ class BodyGenerator(
val statements = expression.statements val statements = expression.statements
if (statements.isEmpty()) { if (statements.isEmpty()) {
if (expression.type == irBuiltIns.unitType) if (expression.type == irBuiltIns.unitType) {
body.buildGetUnit() body.buildGetUnit()
}
return return
} }
statements.dropLast(1).forEach { if (expression is IrReturnableBlock) {
generateStatement(it) context.defineNonLocalReturnLevel(
expression.symbol,
body.buildBlock(context.transformBlockResultType(expression.type))
)
} }
generateExpression(statements.last()) statements.forEachIndexed { i, statement ->
if (i != statements.lastIndex) {
generateStatement(statement)
} else {
if (statement is IrExpression) {
generateWithExpectedType(statement, expression.type)
} else {
generateStatement(statement)
if (expression.type != wasmSymbols.voidType) {
body.buildGetUnit()
}
}
}
}
// This handles cases where the last statement of a block is declaration which doesn't produce any value, if (expression is IrReturnableBlock) {
// but the block itself marked with the unit type. body.buildEnd()
if (statements.last() !is IrExpression && expression.type != wasmSymbols.voidType) {
body.buildGetUnit()
} }
} }
@@ -526,13 +541,16 @@ class BodyGenerator(
body.buildBr(context.referenceLoopLevel(jump.loop, LoopLabelType.CONTINUE)) body.buildBr(context.referenceLoopLevel(jump.loop, LoopLabelType.CONTINUE))
} }
override fun visitReturn(expression: IrReturn) { private fun visitFunctionReturn(expression: IrReturn) {
if (expression.returnTargetSymbol.owner.returnType(backendContext) == irBuiltIns.unitType && val returnType = expression.returnTargetSymbol.owner.returnType(backendContext)
expression.returnTargetSymbol.owner != unitGetInstance if (returnType == irBuiltIns.unitType && expression.returnTargetSymbol.owner != unitGetInstance) {
) {
generateStatement(expression.value) generateStatement(expression.value)
} else { } else {
generateExpression(expression.value) if (isGetUnitFunction) {
generateExpression(expression.value)
} else {
generateWithExpectedType(expression.value, returnType)
}
} }
if (context.irFunction is IrConstructor) { if (context.irFunction is IrConstructor) {
@@ -542,6 +560,88 @@ class BodyGenerator(
body.buildInstr(WasmOp.RETURN) body.buildInstr(WasmOp.RETURN)
} }
internal fun generateWithExpectedType(expression: IrExpression, expectedType: IrType) {
val actualType = expression.type
if (expectedType == wasmSymbols.voidType) {
generateStatement(expression)
return
}
if (expectedType.isUnit() && !actualType.isUnit()) {
generateStatement(expression)
body.buildGetUnit()
return
}
generateExpression(expression)
recoverToExpectedType(actualType = actualType, expectedType = expectedType)
}
//TODO: This method needed because of IR has type inconsistency. We need to discover why is it and fix
private fun recoverToExpectedType(actualType: IrType, expectedType: IrType) {
// TYPE -> NOTHING -> FALSE
if (expectedType.isNothing()) {
body.buildUnreachable()
return
}
// NOTHING -> TYPE -> TRUE
if (actualType.isNothing()) return
// NOTHING? -> TYPE? -> TRUE
if (actualType.isNullableNothing() && expectedType.isNullable()) return
val expectedClassErased = expectedType.getRuntimeClass(irBuiltIns)
// TYPE -> EXTERNAL -> TRUE
if (expectedClassErased.isExternal) return
val actualClassErased = actualType.getRuntimeClass(irBuiltIns)
val expectedTypeErased = expectedClassErased.defaultType
val actualTypeErased = actualClassErased.defaultType
// TYPE -> TYPE -> TRUE
if (expectedTypeErased == actualTypeErased) return
// NOT_NOTHING_TYPE -> NOTHING -> FALSE
if (expectedTypeErased.isNothing() && !actualTypeErased.isNothing()) {
body.buildUnreachable()
return
}
// TYPE -> BASE -> TRUE
if (actualClassErased.isSubclassOf(expectedClassErased)) {
return
}
val expectedIsPrimitive = expectedTypeErased.isPrimitiveType()
val actualIsPrimitive = actualTypeErased.isPrimitiveType()
// PRIMITIVE -> REF -> FALSE
// REF -> PRIMITIVE -> FALSE
if (expectedIsPrimitive != actualIsPrimitive) {
body.buildUnreachable()
return
}
// REF -> REF -> REF_CAST
if (!expectedIsPrimitive) {
generateRefCast(actualTypeErased, expectedTypeErased)
}
}
override fun visitReturn(expression: IrReturn) {
val nonLocalReturnSymbol = expression.returnTargetSymbol as? IrReturnableBlockSymbol
if (nonLocalReturnSymbol != null) {
generateWithExpectedType(expression.value, nonLocalReturnSymbol.owner.type)
body.buildBr(context.referenceNonLocalReturnLevel(nonLocalReturnSymbol))
body.buildUnreachable()
} else {
visitFunctionReturn(expression)
}
}
override fun visitWhen(expression: IrWhen) { override fun visitWhen(expression: IrWhen) {
if (tryGenerateOptimisedWhen(expression, context.backendContext.wasmSymbols)) { if (tryGenerateOptimisedWhen(expression, context.backendContext.wasmSymbols)) {
return return
@@ -555,17 +655,11 @@ class BodyGenerator(
if (!isElseBranch(branch)) { if (!isElseBranch(branch)) {
generateExpression(branch.condition) generateExpression(branch.condition)
body.buildIf(null, resultType) body.buildIf(null, resultType)
generateExpression(branch.result) generateWithExpectedType(branch.result, expression.type)
if (expression.type == irBuiltIns.nothingType) {
body.buildUnreachable()
}
body.buildElse() body.buildElse()
ifCount++ ifCount++
} else { } else {
generateExpression(branch.result) generateWithExpectedType(branch.result, expression.type)
if (expression.type == irBuiltIns.nothingType) {
body.buildUnreachable()
}
seenElse = true seenElse = true
break break
} }
@@ -575,7 +669,15 @@ class BodyGenerator(
// If it's not exhaustive it must be used as a statement (per kotlin spec) and so the result value of the last else will never be used. // If it's not exhaustive it must be used as a statement (per kotlin spec) and so the result value of the last else will never be used.
if (!seenElse && resultType != null) { if (!seenElse && resultType != null) {
assert(expression.type != irBuiltIns.nothingType) assert(expression.type != irBuiltIns.nothingType)
generateDefaultInitializerForType(resultType, body) if (expression.type.isUnit()) {
if (isGetUnitFunction) {
generateDefaultInitializerForType(resultType, body)
} else {
body.buildGetUnit()
}
} else {
body.buildUnreachable()
}
} }
repeat(ifCount) { body.buildEnd() } repeat(ifCount) { body.buildEnd() }
@@ -126,7 +126,11 @@ class DeclarationGenerator(
} }
val exprGen = functionCodegenContext.bodyGen val exprGen = functionCodegenContext.bodyGen
val bodyBuilder = BodyGenerator(functionCodegenContext, hierarchyDisjointUnions) val bodyBuilder = BodyGenerator(
context = functionCodegenContext,
hierarchyDisjointUnions = hierarchyDisjointUnions,
isGetUnitFunction = declaration == unitGetInstanceFunction
)
require(declaration.body is IrBlockBody) { "Only IrBlockBody is supported" } require(declaration.body is IrBlockBody) { "Only IrBlockBody is supported" }
declaration.body?.acceptVoid(bodyBuilder) declaration.body?.acceptVoid(bodyBuilder)
@@ -9,8 +9,8 @@ import org.jetbrains.kotlin.backend.common.IrWhenUtils
import org.jetbrains.kotlin.backend.wasm.WasmSymbols import org.jetbrains.kotlin.backend.wasm.WasmSymbols
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.isUnit
import org.jetbrains.kotlin.ir.util.isElseBranch import org.jetbrains.kotlin.ir.util.isElseBranch
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.wasm.ir.* import org.jetbrains.kotlin.wasm.ir.*
private class ExtractedWhenCondition<T>(val condition: IrCall, val const: IrConst<T>) private class ExtractedWhenCondition<T>(val condition: IrCall, val const: IrConst<T>)
@@ -58,7 +58,7 @@ internal fun BodyGenerator.tryGenerateOptimisedWhen(expression: IrWhen, symbols:
if (minValue == maxValue) return false if (minValue == maxValue) return false
val selectorLocal = context.referenceLocal(SyntheticLocalType.TABLE_SWITCH_SELECTOR) val selectorLocal = context.referenceLocal(SyntheticLocalType.TABLE_SWITCH_SELECTOR)
subject.accept(this, null) generateExpression(subject)
body.buildSetLocal(selectorLocal) body.buildSetLocal(selectorLocal)
val resultType = context.transformBlockResultType(expression.type) val resultType = context.transformBlockResultType(expression.type)
@@ -71,7 +71,7 @@ internal fun BodyGenerator.tryGenerateOptimisedWhen(expression: IrWhen, symbols:
intBranches = intBranches, intBranches = intBranches,
elseExpression = elseExpression, elseExpression = elseExpression,
resultType = resultType, resultType = resultType,
nothingType = symbols.irBuiltIns.nothingType expectedType = expression.type,
) )
} else { } else {
createBinaryTable(selectorLocal, intBranches) createBinaryTable(selectorLocal, intBranches)
@@ -83,7 +83,7 @@ internal fun BodyGenerator.tryGenerateOptimisedWhen(expression: IrWhen, symbols:
elseExpression = elseExpression, elseExpression = elseExpression,
shift = 0, shift = 0,
brTable = intBranches.indices.toList(), brTable = intBranches.indices.toList(),
nothingType = symbols.irBuiltIns.nothingType expectedType = expression.type,
) )
} }
} else { } else {
@@ -100,19 +100,12 @@ internal fun BodyGenerator.tryGenerateOptimisedWhen(expression: IrWhen, symbols:
elseExpression = elseExpression, elseExpression = elseExpression,
shift = minValue, shift = minValue,
brTable = brTable, brTable = brTable,
nothingType = symbols.irBuiltIns.nothingType expectedType = expression.type,
) )
} }
return true return true
} }
private fun IrExpression.safeAccept(bodyGenerator: BodyGenerator, nothingType: IrType) {
acceptVoid(bodyGenerator)
if (type == nothingType) {
bodyGenerator.body.buildUnreachable()
}
}
/** /**
* Create binary search for when that emit branches index in leafs * Create binary search for when that emit branches index in leafs
* when (a) { * when (a) {
@@ -202,7 +195,7 @@ private fun BodyGenerator.createBinaryTable(
intBranches: List<ExtractedWhenBranch<Int>>, intBranches: List<ExtractedWhenBranch<Int>>,
elseExpression: IrExpression?, elseExpression: IrExpression?,
resultType: WasmType?, resultType: WasmType?,
nothingType: IrType, expectedType: IrType,
) { ) {
val sortedCaseToBranchIndex = mutableListOf<Pair<Int, IrExpression>>() val sortedCaseToBranchIndex = mutableListOf<Pair<Int, IrExpression>>()
intBranches.mapTo(sortedCaseToBranchIndex) { branch -> branch.conditions[0].const.value to branch.expression } intBranches.mapTo(sortedCaseToBranchIndex) { branch -> branch.conditions[0].const.value to branch.expression }
@@ -210,26 +203,31 @@ private fun BodyGenerator.createBinaryTable(
body.buildBlock("when_block", resultType) { currentBlock -> body.buildBlock("when_block", resultType) { currentBlock ->
val thenBody = { result: IrExpression -> val thenBody = { result: IrExpression ->
result.safeAccept(this, nothingType) generateWithExpectedType(result, expectedType)
body.buildBr(currentBlock) body.buildBr(currentBlock)
} }
val elseBody: () -> Unit = {
resultType?.let { generateDefaultInitializerForType(it, body) }
}
createBinaryTable( createBinaryTable(
selectorLocal = selectorLocal, selectorLocal = selectorLocal,
resultType = resultType, resultType = null,
sortedCases = sortedCaseToBranchIndex, sortedCases = sortedCaseToBranchIndex,
fromIncl = 0, fromIncl = 0,
toExcl = sortedCaseToBranchIndex.size, toExcl = sortedCaseToBranchIndex.size,
thenBody = thenBody, thenBody = thenBody,
elseBody = elseBody elseBody = { }
) )
if (resultType != null) { if (elseExpression != null) {
body.buildDrop() generateWithExpectedType(elseExpression, expectedType)
} else {
// default else block
if (resultType != null) {
if (expectedType.isUnit()) {
body.buildGetUnit()
} else {
body.buildUnreachable()
}
}
} }
elseExpression?.safeAccept(this, nothingType)
} }
} }
@@ -299,7 +297,7 @@ private fun BodyGenerator.genTableIntSwitch(
elseExpression: IrExpression?, elseExpression: IrExpression?,
shift: Int, shift: Int,
brTable: List<Int>, brTable: List<Int>,
nothingType: IrType, expectedType: IrType,
) { ) {
val baseBlockIndex = body.numberOfNestedBlocks val baseBlockIndex = body.numberOfNestedBlocks
//expressions + else branch + br_table //expressions + else branch + br_table
@@ -324,16 +322,18 @@ private fun BodyGenerator.genTableIntSwitch(
if (resultType != null) { if (resultType != null) {
body.buildDrop() body.buildDrop()
} }
expression.expression.safeAccept(this, nothingType) generateWithExpectedType(expression.expression, expectedType)
body.buildBr(baseBlockIndex + 1) body.buildBr(baseBlockIndex + 1)
body.buildEnd() body.buildEnd()
} }
if (resultType != null) { if (elseExpression != null) {
body.buildDrop() if (resultType != null) {
body.buildDrop()
}
generateWithExpectedType(elseExpression, expectedType)
} }
elseExpression?.safeAccept(this, nothingType)
body.buildEnd() body.buildEnd()
check(baseBlockIndex == body.numberOfNestedBlocks) check(baseBlockIndex == body.numberOfNestedBlocks)
@@ -80,19 +80,17 @@ class UnitToVoidLowering(val context: WasmBackendContext) : FileLoweringPass, Ab
if (expr.type == symbols.voidType || expr.type == builtIns.nothingType) if (expr.type == symbols.voidType || expr.type == builtIns.nothingType)
return false return false
when (expr) { return when (expr) {
is IrContainerExpression -> { is IrContainerExpression ->
if (expr.statements.isEmpty()) expr.statements.isNotEmpty() && expr !is IrReturnableBlock && shouldVoidify(expr.statements.last())
return false
return shouldVoidify(expr.statements.last()) is IrWhen, is IrTry ->
} true
is IrWhen ->
return true
is IrTry ->
return true
is IrTypeOperatorCall -> is IrTypeOperatorCall ->
return expr.operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT expr.operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT
else -> return false
else -> false
} }
} }
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.backend.wasm.lower package org.jetbrains.kotlin.backend.wasm.lower
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
@@ -22,11 +23,11 @@ import org.jetbrains.kotlin.ir.types.makeNotNull
* *
* Wasm GC doesn't have a nullref type anymore. * Wasm GC doesn't have a nullref type anymore.
*/ */
class WasmNullCoercingLowering(context: JsCommonBackendContext) : AbstractValueUsageLowering(context) { class WasmNullCoercingLowering(private val contextx: WasmBackendContext) : AbstractValueUsageLowering(contextx) {
override fun IrExpression.useExpressionAsType(actualType: IrType, expectedType: IrType): IrExpression = override fun IrExpression.useExpressionAsType(actualType: IrType, expectedType: IrType): IrExpression =
if (actualType.makeNotNull().isNothing() && actualType.isNullable() && !expectedType.makeNotNull().isNothing()) if (actualType.makeNotNull().isNothing() && actualType.isNullable() && !expectedType.makeNotNull().isNothing() && expectedType != contextx.wasmSymbols.voidType)
JsIrBuilder.buildComposite( JsIrBuilder.buildComposite(
type, expectedType,
listOf(this, IrConstImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, expectedType, IrConstKind.Null, null)) listOf(this, IrConstImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, expectedType, IrConstKind.Null, null))
) )
else else
@@ -1,5 +1,3 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: TYPE_ISSUES
// WITH_STDLIB // WITH_STDLIB
// WORKS_WHEN_VALUE_CLASS // WORKS_WHEN_VALUE_CLASS
// LANGUAGE: +ValueClasses // LANGUAGE: +ValueClasses