[Wasm] Fix unit issues

- Materialize unit when its value is actually needed.
- Special-case Unit_getInstance return type at codegen. It should be a
  proper Unit object instead of a "void"
This commit is contained in:
Svyatoslav Kuzmich
2021-05-24 18:06:26 +03:00
committed by TeamCityServer
parent 20ddaa9ebf
commit b79719d6f5
45 changed files with 289 additions and 141 deletions
@@ -328,6 +328,12 @@ private val wasmNullSpecializationLowering = makeWasmModulePhase(
description = "Specialize assigning Nothing? values to other types."
)
private val unitMaterializationPass = makeWasmModulePhase(
{ context -> UnitMaterializationPass(context) },
name = "UnitLoweringPass",
description = "Materialize unit instances"
)
private val staticMembersLoweringPhase = makeWasmModulePhase(
::StaticMembersLowering,
name = "StaticMembersLowering",
@@ -499,5 +505,6 @@ val wasmPhases = NamedCompilerPhase(
wasmThrowDebugLoweringPhase then
staticMembersLoweringPhase then
wasmNullSpecializationLowering then
unitMaterializationPass then
validateIrAfterLowering
)
@@ -20,10 +20,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.types.*
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.getInlineClassBackingField
import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.wasm.ir.*
@@ -40,6 +37,13 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV
error("Unexpected element of type ${element::class}")
}
val unitGetInstance by lazy { backendContext.mapping.objectToGetInstanceFunction[irBuiltIns.unitClass.owner]!! }
override fun visitGetObjectValue(expression: IrGetObjectValue) {
require(expression.symbol == irBuiltIns.unitClass)
body.buildCall(context.referenceFunction(unitGetInstance.symbol))
}
override fun visitTypeOperator(expression: IrTypeOperatorCall) {
require(expression.operator == IrTypeOperator.REINTERPRET_CAST) { "Other types of casts must be lowered" }
generateExpression(expression.argument)
@@ -329,11 +333,7 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV
statementToWasmInstruction(it)
}
if (expression.type != irBuiltIns.unitType) {
generateExpression(statements.last() as IrExpression)
} else {
statementToWasmInstruction(statements.last())
}
generateExpression(statements.last() as IrExpression)
}
override fun visitBreak(jump: IrBreak) {
@@ -345,18 +345,17 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV
}
override fun visitReturn(expression: IrReturn) {
generateExpression(expression.value)
// FIXME: Hack for "returning" Unit from functions with generic return type.
// Common case -- lambdas returning unit.
if (expression.value.type == irBuiltIns.unitType &&
expression.returnTargetSymbol.owner.returnType(backendContext) != irBuiltIns.unitType
if (
expression.value.type == irBuiltIns.unitType &&
expression.returnTargetSymbol.owner.returnType(backendContext) == irBuiltIns.unitType
) {
val irReturnType = expression.returnTargetSymbol.owner.returnType(backendContext)
statementToWasmInstruction(expression.value)
} else {
generateExpression(expression.value)
}
if (irReturnType != irBuiltIns.unitType) {
generateDefaultInitializerForType(context.transformType(irReturnType), body)
}
if (context.irFunction is IrConstructor) {
body.buildGetLocal(context.referenceLocal(0))
}
// Handle complex exported parameters.
@@ -371,25 +370,6 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV
}
override fun visitWhen(expression: IrWhen) {
if (expression.type == irBuiltIns.unitType) {
var ifCount = 0
for (branch in expression.branches) {
if (!isElseBranch(branch)) {
generateExpression(branch.condition)
body.buildIf(label = null, resultType = null)
statementToWasmInstruction(branch.result)
body.buildElse()
ifCount++
} else {
statementToWasmInstruction(branch.result)
break
}
}
repeat(ifCount) { body.buildEnd() }
return
}
val resultType = context.transformBlockResultType(expression.type)
var ifCount = 0
for (branch in expression.branches) {
@@ -489,10 +469,47 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV
return
}
generateExpression(statement as IrExpression)
if (statement is IrContainerExpression) {
statement.statements.forEach { it ->
statementToWasmInstruction(it)
}
} else if (statement is IrWhen) {
var ifCount = 0
for (branch in statement.branches) {
if (!isElseBranch(branch)) {
generateExpression(branch.condition)
body.buildIf(label = null, resultType = null)
statementToWasmInstruction(branch.result)
body.buildElse()
ifCount++
} else {
statementToWasmInstruction(branch.result)
break
}
}
if (statement.type != irBuiltIns.unitType && statement.type != irBuiltIns.nothingType) {
body.buildInstr(WasmOp.DROP)
repeat(ifCount) { body.buildEnd() }
} else {
generateExpression(statement as IrExpression)
var needDrop = true
if (statement.type == irBuiltIns.nothingType)
needDrop = false
if (statement is IrSetValue || statement is IrBreakContinue || statement is IrSetField || statement is IrLoop || statement is IrDelegatingConstructorCall) {
needDrop = false
}
if (statement is IrCall || statement is IrConstructorCall) {
val unitGetInstanceCall = statement is IrCall && statement.symbol.owner == unitGetInstance
if (statement.type == irBuiltIns.unitType && !unitGetInstanceCall) {
needDrop = false
}
}
if (needDrop) {
body.buildInstr(WasmOp.DROP)
}
}
}
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.backend.js.utils.findUnitGetInstanceFunction
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
@@ -29,6 +30,8 @@ class DeclarationGenerator(val context: WasmModuleCodegenContext) : IrElementVis
private val backendContext: WasmBackendContext = context.backendContext
private val irBuiltIns: IrBuiltIns = backendContext.irBuiltIns
private val unitGetInstanceFunction: IrSimpleFunction by lazy { backendContext.findUnitGetInstanceFunction() }
override fun visitElement(element: IrElement) {
error("Unexpected element of type ${element::class}")
}
@@ -84,8 +87,18 @@ class DeclarationGenerator(val context: WasmModuleCodegenContext) : IrElementVis
}
},
resultTypes = listOfNotNull(
resultType.let {
if (importedName != null && it is WasmRefNullType) WasmEqRef else it
run {
val type = if (declaration == unitGetInstanceFunction) {
// Unit_getInstance returns true Unit reference instead of "void"
context.transformType(declaration.returnType)
} else {
resultType
}
if (importedName != null && type is WasmRefNullType)
WasmEqRef
else
type
}
)
)
@@ -14,9 +14,12 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irNot
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
import org.jetbrains.kotlin.backend.wasm.ir2wasm.erasedUpperBound
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.backend.js.utils.findUnitGetInstanceFunction
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrTypeParameter
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrTypeOperator
@@ -35,6 +38,7 @@ class WasmTypeOperatorLowering(val context: WasmBackendContext) : FileLoweringPa
class WasmBaseTypeOperatorTransformer(val context: WasmBackendContext) : IrElementTransformerVoidWithContext() {
private val symbols = context.wasmSymbols
private val builtIns = context.irBuiltIns
private val unitGetInstance = context.findUnitGetInstanceFunction()
private lateinit var builder: DeclarationIrBuilder
@@ -45,7 +49,7 @@ class WasmBaseTypeOperatorTransformer(val context: WasmBackendContext) : IrEleme
return when (expression.operator) {
IrTypeOperator.IMPLICIT_CAST -> lowerImplicitCast(expression)
IrTypeOperator.IMPLICIT_DYNAMIC_CAST -> error("Dynamic casts are not supported in Wasm backend")
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> expression.argument
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> builder.irComposite(resultType = builtIns.unitType) { +expression.argument }
IrTypeOperator.IMPLICIT_INTEGER_COERCION -> lowerIntegerCoercion(expression)
IrTypeOperator.IMPLICIT_NOTNULL -> lowerImplicitCast(expression)
IrTypeOperator.INSTANCEOF -> lowerInstanceOf(expression, inverted = false)
@@ -57,6 +61,19 @@ class WasmBaseTypeOperatorTransformer(val context: WasmBackendContext) : IrEleme
}
}
override fun visitVariable(declaration: IrVariable): IrStatement {
// Some IR passes, notable for-loops-lowering assumes implicit cast during variable initialization
val initializer = declaration.initializer
if (initializer != null &&
initializer.type != declaration.type
) {
builder = context.createIrBuilder(currentScope!!.scope.scopeOwnerSymbol).at(declaration)
declaration.initializer = narrowType(initializer.type, declaration.type, initializer)
}
return super.visitVariable(declaration)
}
private fun lowerInstanceOf(
expression: IrTypeOperatorCall,
inverted: Boolean
@@ -135,10 +152,7 @@ class WasmBaseTypeOperatorTransformer(val context: WasmBackendContext) : IrEleme
private fun generateTypeCheckNonNull(argument: IrExpression, toType: IrType): IrExpression {
assert(!toType.isMarkedNullable())
return when {
toType.isNothing() -> builder.irComposite(resultType = builtIns.booleanType) {
+(argument)
+builder.irFalse()
}
toType.isNothing() -> builder.irFalse()
toType.isTypeParameter() -> generateTypeCheckWithTypeParameter(argument, toType)
toType.isInterface() -> generateIsInterface(argument, toType)
else -> generateIsSubClass(argument, toType)
@@ -281,16 +295,10 @@ class WasmBaseTypeOperatorTransformer(val context: WasmBackendContext) : IrEleme
val fromTypeErased = fromType.erasedType
val toTypeErased = toType.erasedType
if (fromTypeErased.isSubtypeOfClass(toTypeErased.classOrNull!!)) {
return builder.irComposite {
+argument
+builder.irTrue()
}
return builder.irTrue()
}
if (!toTypeErased.isSubtypeOfClass(fromTypeErased.classOrNull!!)) {
return builder.irComposite {
+argument
+builder.irFalse()
}
return builder.irFalse()
}
return builder.irCall(symbols.refTest).apply {