[Wasm] Require location for buildUnreachable and fix all usages

This commit is contained in:
Zalim Bashorov
2022-12-09 18:50:43 +01:00
parent 36d4e29253
commit ce265c049a
4 changed files with 33 additions and 19 deletions
@@ -59,7 +59,8 @@ class BodyGenerator(
elem.acceptVoid(this) elem.acceptVoid(this)
if (elem is IrExpression && elem.type.isNothing()) { if (elem is IrExpression && elem.type.isNothing()) {
body.buildUnreachable() // TODO Ideally, we should generate unreachable only for specific cases and preferable on declaration site.
body.buildUnreachableAfterNothingType()
} }
} }
@@ -437,7 +438,10 @@ class BodyGenerator(
body.buildInstr(WasmOp.CALL_REF, WasmImmediate.TypeIdx(context.referenceFunctionType(function.symbol))) body.buildInstr(WasmOp.CALL_REF, WasmImmediate.TypeIdx(context.referenceFunctionType(function.symbol)))
body.commentGroupEnd() body.commentGroupEnd()
} else { } else {
body.buildUnreachable() // We came here for a call to an interface method which interface is not implemented anywhere,
// so we don't have a slot in the itable and can't generate a correct call,
// and, anyway, the call effectively is unreachable.
body.buildUnreachableForVerifier()
} }
} }
@@ -552,17 +556,11 @@ class BodyGenerator(
val fromType = call.getTypeArgument(0)!! val fromType = call.getTypeArgument(0)!!
if (fromType.isNothing()) { if (fromType.isNothing()) {
body.buildUnreachable() body.buildUnreachableAfterNothingType()
// TODO: Investigate why? // TODO: Investigate why?
return true return true
} }
// Workaround test codegen/box/elvis/nullNullOk.kt
if (fromType.makeNotNull().isNothing()) {
body.buildUnreachable()
return true
}
val toType = call.getTypeArgument(1)!! val toType = call.getTypeArgument(1)!!
val klass: IrClass = backendContext.inlineClassesUtils.getInlinedClass(toType)!! val klass: IrClass = backendContext.inlineClassesUtils.getInlinedClass(toType)!!
val field = getInlineClassBackingField(klass) val field = getInlineClassBackingField(klass)
@@ -694,7 +692,7 @@ class BodyGenerator(
private fun recoverToExpectedType(actualType: IrType, expectedType: IrType) { private fun recoverToExpectedType(actualType: IrType, expectedType: IrType) {
// TYPE -> NOTHING -> FALSE // TYPE -> NOTHING -> FALSE
if (expectedType.isNothing()) { if (expectedType.isNothing()) {
body.buildUnreachable() body.buildUnreachableAfterNothingType()
return return
} }
@@ -724,11 +722,12 @@ class BodyGenerator(
// NOT_NOTHING_TYPE -> NOTHING -> FALSE // NOT_NOTHING_TYPE -> NOTHING -> FALSE
if (expectedTypeErased.isNothing() && !actualTypeErased.isNothing()) { if (expectedTypeErased.isNothing() && !actualTypeErased.isNothing()) {
body.buildUnreachable() body.buildUnreachableAfterNothingType()
return return
} }
// TYPE -> BASE -> TRUE // TYPE -> BASE -> TRUE
// TODO Shouldn't we keep nullability for subtype check?
if (actualClassErased.isSubclassOf(expectedClassErased)) { if (actualClassErased.isSubclassOf(expectedClassErased)) {
return return
} }
@@ -739,7 +738,8 @@ class BodyGenerator(
// PRIMITIVE -> REF -> FALSE // PRIMITIVE -> REF -> FALSE
// REF -> PRIMITIVE -> FALSE // REF -> PRIMITIVE -> FALSE
if (expectedIsPrimitive != actualIsPrimitive) { if (expectedIsPrimitive != actualIsPrimitive) {
body.buildUnreachable() // TODO Shouldn't we throw ICE instead?
body.buildUnreachableForVerifier()
return return
} }
@@ -748,7 +748,7 @@ class BodyGenerator(
if (expectedClassErased.isSubclassOf(actualClassErased)) { if (expectedClassErased.isSubclassOf(actualClassErased)) {
generateRefNullCast(actualTypeErased, expectedTypeErased) generateRefNullCast(actualTypeErased, expectedTypeErased)
} else { } else {
body.buildUnreachable() body.buildUnreachableForVerifier()
} }
} }
} }
@@ -758,7 +758,6 @@ class BodyGenerator(
if (nonLocalReturnSymbol != null) { if (nonLocalReturnSymbol != null) {
generateWithExpectedType(expression.value, nonLocalReturnSymbol.owner.type) generateWithExpectedType(expression.value, nonLocalReturnSymbol.owner.type)
body.buildBr(functionContext.referenceNonLocalReturnLevel(nonLocalReturnSymbol)) body.buildBr(functionContext.referenceNonLocalReturnLevel(nonLocalReturnSymbol))
body.buildUnreachable()
} else { } else {
visitFunctionReturn(expression) visitFunctionReturn(expression)
} }
@@ -798,7 +797,7 @@ class BodyGenerator(
body.buildGetUnit() body.buildGetUnit()
} }
} else { } else {
body.buildUnreachable() error("'When' without else branch and non Unit type: ${expression.type.dumpKotlinLike()}")
} }
} }
@@ -171,7 +171,7 @@ class DeclarationGenerator(
// Add unreachable if function returns something but not as a last instruction. // Add unreachable if function returns something but not as a last instruction.
// We can do a separate lowering which adds explicit returns everywhere instead. // We can do a separate lowering which adds explicit returns everywhere instead.
if (wasmFunctionType.resultTypes.isNotEmpty()) { if (wasmFunctionType.resultTypes.isNotEmpty()) {
exprGen.buildUnreachable() exprGen.buildUnreachableForVerifier()
} }
context.defineFunction(declaration.symbol, function) context.defineFunction(declaration.symbol, function)
@@ -10,6 +10,7 @@ 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.types.isUnit
import org.jetbrains.kotlin.ir.util.dumpKotlinLike
import org.jetbrains.kotlin.ir.util.isElseBranch import org.jetbrains.kotlin.ir.util.isElseBranch
import org.jetbrains.kotlin.wasm.ir.* import org.jetbrains.kotlin.wasm.ir.*
import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocation import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocation
@@ -225,9 +226,10 @@ private fun BodyGenerator.createBinaryTable(
// default else block // default else block
if (resultType != null) { if (resultType != null) {
if (expectedType.isUnit()) { if (expectedType.isUnit()) {
// UnitToVoidLowering may optimize "a code" that execution didn't come here.
body.buildGetUnit() body.buildGetUnit()
} else { } else {
body.buildUnreachable() error("'When' without else branch and non Unit type: ${expectedType.dumpKotlinLike()}")
} }
} }
} }
@@ -36,8 +36,8 @@ abstract class WasmExpressionBuilder {
buildInstr(WasmOp.I32_CONST, location, WasmImmediate.SymbolI32(value)) buildInstr(WasmOp.I32_CONST, location, WasmImmediate.SymbolI32(value))
} }
fun buildUnreachable() { fun buildUnreachable(location: SourceLocation) {
buildInstr(WasmOp.UNREACHABLE) buildInstr(WasmOp.UNREACHABLE, location)
} }
@Suppress("UNUSED_PARAMETER") @Suppress("UNUSED_PARAMETER")
@@ -191,3 +191,16 @@ abstract class WasmExpressionBuilder {
buildInstr(WasmOp.PSEUDO_COMMENT_GROUP_END) buildInstr(WasmOp.PSEUDO_COMMENT_GROUP_END)
} }
} }
fun WasmExpressionBuilder.buildUnreachableForVerifier() {
buildUnreachable(SourceLocation.NoLocation("This instruction should never be reached, but required for wasm verifier"))
}
fun WasmExpressionBuilder.buildUnreachableAfterNothingType() {
buildUnreachable(
SourceLocation.NoLocation(
"The unreachable instruction after an expression with Nothing type to make sure that " +
"execution doesn't come here (or it fails fast if so). It also might be required for wasm verifier."
)
)
}