[WASM] Native support of returnable blocks
This commit is contained in:
+2
-10
@@ -144,8 +144,8 @@ private val tailrecLoweringPhase = makeWasmModulePhase(
|
||||
|
||||
private val wasmStringSwitchOptimizerLowering = makeWasmModulePhase(
|
||||
::WasmStringSwitchOptimizerLowering,
|
||||
name = "!!!",
|
||||
description = "!!!"
|
||||
name = "WasmStringSwitchOptimizerLowering",
|
||||
description = "Replace when with constant string cases to binary search by string hashcodes"
|
||||
)
|
||||
|
||||
private val complexExternalDeclarationsToTopLevelFunctionsLowering = makeWasmModulePhase(
|
||||
@@ -403,13 +403,6 @@ private val tryCatchCanonicalization = makeWasmModulePhase(
|
||||
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(
|
||||
::WasmBridgesConstruction,
|
||||
name = "BridgesConstruction",
|
||||
@@ -627,7 +620,6 @@ val wasmPhases = NamedCompilerPhase(
|
||||
unhandledExceptionLowering then
|
||||
|
||||
tryCatchCanonicalization then
|
||||
returnableBlockLoweringPhase then
|
||||
|
||||
forLoopsLoweringPhase then
|
||||
propertyLazyInitLoweringPhase then
|
||||
|
||||
+131
-29
@@ -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.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalUnsignedTypes::class)
|
||||
|
||||
package org.jetbrains.kotlin.backend.wasm.ir2wasm
|
||||
|
||||
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.expressions.*
|
||||
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.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
@@ -30,7 +29,8 @@ import org.jetbrains.kotlin.wasm.ir.*
|
||||
|
||||
class BodyGenerator(
|
||||
val context: WasmFunctionCodegenContext,
|
||||
private val hierarchyDisjointUnions: DisjointUnions<IrClassSymbol>
|
||||
private val hierarchyDisjointUnions: DisjointUnions<IrClassSymbol>,
|
||||
private val isGetUnitFunction: Boolean,
|
||||
) : IrElementVisitorVoid {
|
||||
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.
|
||||
private fun generateExpression(elem: IrElement) {
|
||||
internal fun generateExpression(elem: IrElement) {
|
||||
assert(elem is IrExpression || elem is IrVariable) { "Unsupported statement kind" }
|
||||
|
||||
elem.acceptVoid(this)
|
||||
|
||||
if (elem is IrExpression && elem.type == irBuiltIns.nothingType) {
|
||||
if (elem is IrExpression && elem.type.isNothing()) {
|
||||
body.buildUnreachable()
|
||||
}
|
||||
}
|
||||
@@ -296,7 +296,7 @@ class BodyGenerator(
|
||||
}
|
||||
|
||||
if (tryToGenerateIntrinsicCall(call, function)) {
|
||||
if (function.returnType == irBuiltIns.unitType)
|
||||
if (function.returnType.isUnit())
|
||||
body.buildGetUnit()
|
||||
return
|
||||
}
|
||||
@@ -346,7 +346,7 @@ class BodyGenerator(
|
||||
}
|
||||
|
||||
// Unit types don't cross function boundaries
|
||||
if (function.returnType == irBuiltIns.unitType)
|
||||
if (function.returnType.isUnit())
|
||||
body.buildGetUnit()
|
||||
}
|
||||
|
||||
@@ -498,21 +498,36 @@ class BodyGenerator(
|
||||
val statements = expression.statements
|
||||
|
||||
if (statements.isEmpty()) {
|
||||
if (expression.type == irBuiltIns.unitType)
|
||||
if (expression.type == irBuiltIns.unitType) {
|
||||
body.buildGetUnit()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
statements.dropLast(1).forEach {
|
||||
generateStatement(it)
|
||||
if (expression is IrReturnableBlock) {
|
||||
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,
|
||||
// but the block itself marked with the unit type.
|
||||
if (statements.last() !is IrExpression && expression.type != wasmSymbols.voidType) {
|
||||
body.buildGetUnit()
|
||||
if (expression is IrReturnableBlock) {
|
||||
body.buildEnd()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,13 +541,16 @@ class BodyGenerator(
|
||||
body.buildBr(context.referenceLoopLevel(jump.loop, LoopLabelType.CONTINUE))
|
||||
}
|
||||
|
||||
override fun visitReturn(expression: IrReturn) {
|
||||
if (expression.returnTargetSymbol.owner.returnType(backendContext) == irBuiltIns.unitType &&
|
||||
expression.returnTargetSymbol.owner != unitGetInstance
|
||||
) {
|
||||
private fun visitFunctionReturn(expression: IrReturn) {
|
||||
val returnType = expression.returnTargetSymbol.owner.returnType(backendContext)
|
||||
if (returnType == irBuiltIns.unitType && expression.returnTargetSymbol.owner != unitGetInstance) {
|
||||
generateStatement(expression.value)
|
||||
} else {
|
||||
generateExpression(expression.value)
|
||||
if (isGetUnitFunction) {
|
||||
generateExpression(expression.value)
|
||||
} else {
|
||||
generateWithExpectedType(expression.value, returnType)
|
||||
}
|
||||
}
|
||||
|
||||
if (context.irFunction is IrConstructor) {
|
||||
@@ -542,6 +560,88 @@ class BodyGenerator(
|
||||
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) {
|
||||
if (tryGenerateOptimisedWhen(expression, context.backendContext.wasmSymbols)) {
|
||||
return
|
||||
@@ -555,17 +655,11 @@ class BodyGenerator(
|
||||
if (!isElseBranch(branch)) {
|
||||
generateExpression(branch.condition)
|
||||
body.buildIf(null, resultType)
|
||||
generateExpression(branch.result)
|
||||
if (expression.type == irBuiltIns.nothingType) {
|
||||
body.buildUnreachable()
|
||||
}
|
||||
generateWithExpectedType(branch.result, expression.type)
|
||||
body.buildElse()
|
||||
ifCount++
|
||||
} else {
|
||||
generateExpression(branch.result)
|
||||
if (expression.type == irBuiltIns.nothingType) {
|
||||
body.buildUnreachable()
|
||||
}
|
||||
generateWithExpectedType(branch.result, expression.type)
|
||||
seenElse = true
|
||||
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 (!seenElse && resultType != null) {
|
||||
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() }
|
||||
|
||||
+5
-1
@@ -126,7 +126,11 @@ class DeclarationGenerator(
|
||||
}
|
||||
|
||||
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" }
|
||||
declaration.body?.acceptVoid(bodyBuilder)
|
||||
|
||||
+27
-27
@@ -9,8 +9,8 @@ import org.jetbrains.kotlin.backend.common.IrWhenUtils
|
||||
import org.jetbrains.kotlin.backend.wasm.WasmSymbols
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
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.visitors.acceptVoid
|
||||
import org.jetbrains.kotlin.wasm.ir.*
|
||||
|
||||
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
|
||||
|
||||
val selectorLocal = context.referenceLocal(SyntheticLocalType.TABLE_SWITCH_SELECTOR)
|
||||
subject.accept(this, null)
|
||||
generateExpression(subject)
|
||||
body.buildSetLocal(selectorLocal)
|
||||
|
||||
val resultType = context.transformBlockResultType(expression.type)
|
||||
@@ -71,7 +71,7 @@ internal fun BodyGenerator.tryGenerateOptimisedWhen(expression: IrWhen, symbols:
|
||||
intBranches = intBranches,
|
||||
elseExpression = elseExpression,
|
||||
resultType = resultType,
|
||||
nothingType = symbols.irBuiltIns.nothingType
|
||||
expectedType = expression.type,
|
||||
)
|
||||
} else {
|
||||
createBinaryTable(selectorLocal, intBranches)
|
||||
@@ -83,7 +83,7 @@ internal fun BodyGenerator.tryGenerateOptimisedWhen(expression: IrWhen, symbols:
|
||||
elseExpression = elseExpression,
|
||||
shift = 0,
|
||||
brTable = intBranches.indices.toList(),
|
||||
nothingType = symbols.irBuiltIns.nothingType
|
||||
expectedType = expression.type,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@@ -100,19 +100,12 @@ internal fun BodyGenerator.tryGenerateOptimisedWhen(expression: IrWhen, symbols:
|
||||
elseExpression = elseExpression,
|
||||
shift = minValue,
|
||||
brTable = brTable,
|
||||
nothingType = symbols.irBuiltIns.nothingType
|
||||
expectedType = expression.type,
|
||||
)
|
||||
}
|
||||
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
|
||||
* when (a) {
|
||||
@@ -202,7 +195,7 @@ private fun BodyGenerator.createBinaryTable(
|
||||
intBranches: List<ExtractedWhenBranch<Int>>,
|
||||
elseExpression: IrExpression?,
|
||||
resultType: WasmType?,
|
||||
nothingType: IrType,
|
||||
expectedType: IrType,
|
||||
) {
|
||||
val sortedCaseToBranchIndex = mutableListOf<Pair<Int, IrExpression>>()
|
||||
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 ->
|
||||
val thenBody = { result: IrExpression ->
|
||||
result.safeAccept(this, nothingType)
|
||||
generateWithExpectedType(result, expectedType)
|
||||
body.buildBr(currentBlock)
|
||||
}
|
||||
val elseBody: () -> Unit = {
|
||||
resultType?.let { generateDefaultInitializerForType(it, body) }
|
||||
}
|
||||
createBinaryTable(
|
||||
selectorLocal = selectorLocal,
|
||||
resultType = resultType,
|
||||
resultType = null,
|
||||
sortedCases = sortedCaseToBranchIndex,
|
||||
fromIncl = 0,
|
||||
toExcl = sortedCaseToBranchIndex.size,
|
||||
thenBody = thenBody,
|
||||
elseBody = elseBody
|
||||
elseBody = { }
|
||||
)
|
||||
|
||||
if (resultType != null) {
|
||||
body.buildDrop()
|
||||
if (elseExpression != null) {
|
||||
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?,
|
||||
shift: Int,
|
||||
brTable: List<Int>,
|
||||
nothingType: IrType,
|
||||
expectedType: IrType,
|
||||
) {
|
||||
val baseBlockIndex = body.numberOfNestedBlocks
|
||||
//expressions + else branch + br_table
|
||||
@@ -324,16 +322,18 @@ private fun BodyGenerator.genTableIntSwitch(
|
||||
if (resultType != null) {
|
||||
body.buildDrop()
|
||||
}
|
||||
expression.expression.safeAccept(this, nothingType)
|
||||
generateWithExpectedType(expression.expression, expectedType)
|
||||
|
||||
body.buildBr(baseBlockIndex + 1)
|
||||
body.buildEnd()
|
||||
}
|
||||
|
||||
if (resultType != null) {
|
||||
body.buildDrop()
|
||||
if (elseExpression != null) {
|
||||
if (resultType != null) {
|
||||
body.buildDrop()
|
||||
}
|
||||
generateWithExpectedType(elseExpression, expectedType)
|
||||
}
|
||||
elseExpression?.safeAccept(this, nothingType)
|
||||
|
||||
body.buildEnd()
|
||||
check(baseBlockIndex == body.numberOfNestedBlocks)
|
||||
|
||||
+10
-12
@@ -80,19 +80,17 @@ class UnitToVoidLowering(val context: WasmBackendContext) : FileLoweringPass, Ab
|
||||
if (expr.type == symbols.voidType || expr.type == builtIns.nothingType)
|
||||
return false
|
||||
|
||||
when (expr) {
|
||||
is IrContainerExpression -> {
|
||||
if (expr.statements.isEmpty())
|
||||
return false
|
||||
return shouldVoidify(expr.statements.last())
|
||||
}
|
||||
is IrWhen ->
|
||||
return true
|
||||
is IrTry ->
|
||||
return true
|
||||
return when (expr) {
|
||||
is IrContainerExpression ->
|
||||
expr.statements.isNotEmpty() && expr !is IrReturnableBlock && shouldVoidify(expr.statements.last())
|
||||
|
||||
is IrWhen, is IrTry ->
|
||||
true
|
||||
|
||||
is IrTypeOperatorCall ->
|
||||
return expr.operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT
|
||||
else -> return false
|
||||
expr.operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -5,6 +5,7 @@
|
||||
|
||||
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.backend.js.JsCommonBackendContext
|
||||
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.
|
||||
*/
|
||||
class WasmNullCoercingLowering(context: JsCommonBackendContext) : AbstractValueUsageLowering(context) {
|
||||
class WasmNullCoercingLowering(private val contextx: WasmBackendContext) : AbstractValueUsageLowering(contextx) {
|
||||
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(
|
||||
type,
|
||||
expectedType,
|
||||
listOf(this, IrConstImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, expectedType, IrConstKind.Null, null))
|
||||
)
|
||||
else
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// IGNORE_BACKEND: WASM
|
||||
// WASM_MUTE_REASON: TYPE_ISSUES
|
||||
// WITH_STDLIB
|
||||
// WORKS_WHEN_VALUE_CLASS
|
||||
// LANGUAGE: +ValueClasses
|
||||
|
||||
Reference in New Issue
Block a user