[JS IR BE] Inline classes lowering

This commit is contained in:
Svyatoslav Kuzmich
2018-10-31 17:39:02 +03:00
parent 4866c2b36a
commit 370b04df35
11 changed files with 798 additions and 11 deletions
@@ -250,6 +250,9 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
val jsCharSequenceLength = getInternalFunction("charSequenceLength")
val jsCharSequenceSubSequence = getInternalFunction("charSequenceSubSequence")
val jsBoxIntrinsic = getInternalFunction("boxIntrinsic")
val jsUnboxIntrinsic = getInternalFunction("unboxIntrinsic")
// Helpers:
private fun getInternalFunction(name: String) =
@@ -9,6 +9,7 @@ import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.backend.common.*
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.lower.*
import org.jetbrains.kotlin.backend.common.lower.InlineClassLowering
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
@@ -130,6 +131,11 @@ private fun JsIrBackendContext.lower(moduleFragment: IrModuleFragment, dependenc
constructorRedirectorLowering.runOnFilesPostfix(moduleFragment)
}
InlineClassLowering(this).apply {
inlineClassDeclarationLowering.runOnFilesPostfix(moduleFragment)
inlineClassUsageLowering.lower(moduleFragment)
}
AutoboxingTransformer(this).lower(moduleFragment)
ClassReferenceLowering(this).lower(moduleFragment)
PrimitiveCompanionLowering(this).lower(moduleFragment)
@@ -152,4 +158,4 @@ private fun FunctionLoweringPass.runOnFilesPostfix(moduleFragment: IrModuleFragm
moduleFragment.files.forEach { runOnFilePostfix(it) }
private fun ClassLoweringPass.runOnFilesPostfix(moduleFragment: IrModuleFragment) =
moduleFragment.files.forEach { runOnFilePostfix(it) }
moduleFragment.files.forEach { runOnFilePostfix(it) }
@@ -0,0 +1,111 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. 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.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.lower.AbstractValueUsageTransformer
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.isNothing
import org.jetbrains.kotlin.ir.types.makeNotNull
import org.jetbrains.kotlin.ir.util.getInlinedClass
import org.jetbrains.kotlin.ir.util.isInlined
// Copied and adapted from Kotlin/Native
class AutoboxingTransformer(val context: JsIrBackendContext) : AbstractValueUsageTransformer(context.irBuiltIns), FileLoweringPass {
override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid()
}
override fun IrExpression.useAs(type: IrType): IrExpression {
val actualType = when (this) {
is IrCall -> {
if (this.symbol.owner.let { it is IrSimpleFunction && it.isSuspend }) {
irBuiltIns.anyNType
} else {
try {
this.symbol.owner.returnType
} catch (e: kotlin.UninitializedPropertyAccessException) {
// TODO: Fix lateinit return types
this.type
}
}
}
is IrGetField -> this.symbol.owner.type
is IrTypeOperatorCall -> when (this.operator) {
IrTypeOperator.IMPLICIT_INTEGER_COERCION ->
// TODO: is it a workaround for inconsistent IR?
this.typeOperand
IrTypeOperator.CAST, IrTypeOperator.IMPLICIT_CAST -> context.irBuiltIns.anyNType
else -> this.type
}
is IrGetValue -> {
val value = this.symbol.owner
if (value is IrValueParameter && value.isDispatchReceiver) {
irBuiltIns.anyNType
} else {
this.type
}
}
else -> this.type
}
// TODO: Default parameters are passed as nulls and they need not to be unboxed. Fix this
if (actualType.makeNotNull().isNothing())
return this
val expectedType = type
val actualInlinedClass = actualType.getInlinedClass()
val expectedInlinedClass = expectedType.getInlinedClass()
val function = when {
actualInlinedClass == null && expectedInlinedClass == null -> return this
actualInlinedClass != null && expectedInlinedClass == null -> context.intrinsics.jsBoxIntrinsic
actualInlinedClass == null && expectedInlinedClass != null -> context.intrinsics.jsUnboxIntrinsic
else -> return this
}
return JsIrBuilder.buildCall(
function,
expectedType,
typeArguments = listOf(actualType, expectedType)
).also {
it.putValueArgument(0, this)
}
}
override fun IrExpression.useAsVarargElement(expression: IrVararg): IrExpression {
return this.useAs(
if (this.type.isInlined())
irBuiltIns.anyNType
else
expression.varargElementType
)
}
private val IrValueParameter.isDispatchReceiver: Boolean
get() {
val parent = this.parent
if (parent is IrClass)
return true
if (parent is IrFunction && parent.dispatchReceiverParameter == this)
return true
return false
}
}
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.IrTypeProjection
import org.jetbrains.kotlin.ir.types.toIrType
import org.jetbrains.kotlin.ir.util.isInlined
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
@@ -357,6 +358,12 @@ class CallableReferenceLowering(val context: JsIrBackendContext) : FileLoweringP
return returnStatements
}
private fun IrType.boxIfInlined() = if (isInlined()) {
context.irBuiltIns.anyNType
} else {
this
}
private fun generateSignatureForClosure(
callable: IrFunction,
factory: IrFunction,
@@ -387,7 +394,7 @@ class CallableReferenceLowering(val context: JsIrBackendContext) : FileLoweringP
callable.dispatchReceiverParameter?.let { dispatch ->
if (reference.dispatchReceiver == null) {
result.add(JsIrBuilder.buildValueParameter(dispatch.name, result.size, dispatch.type).also { it.parent = closure })
result.add(JsIrBuilder.buildValueParameter(dispatch.name, result.size, dispatch.type.boxIfInlined()).also { it.parent = closure })
} else {
// do not add dispatch receiver in result signature if it is bound
capturedParams--
@@ -396,7 +403,7 @@ class CallableReferenceLowering(val context: JsIrBackendContext) : FileLoweringP
callable.extensionReceiverParameter?.let { ext ->
if (reference.extensionReceiver == null) {
result.add(JsIrBuilder.buildValueParameter(ext.name, result.size, ext.type).also { it.parent = closure })
result.add(JsIrBuilder.buildValueParameter(ext.name, result.size, ext.type.boxIfInlined()).also { it.parent = closure })
} else {
// the same as for dispatch
capturedParams--
@@ -406,7 +413,7 @@ class CallableReferenceLowering(val context: JsIrBackendContext) : FileLoweringP
for ((index, param) in (result.size until arity).zip(callable.valueParameters.drop(capturedParams))) {
val type = if (index < functionSignature.size) functionSignature[index] else param.type
val paramName = param.name.run { if (!isSpecial) identifier else "p$index" }
result += JsIrBuilder.buildValueParameter(paramName, result.size, type).also { it.parent = closure }
result += JsIrBuilder.buildValueParameter(paramName, result.size, type.boxIfInlined()).also { it.parent = closure }
}
return result
@@ -492,7 +499,7 @@ class CallableReferenceLowering(val context: JsIrBackendContext) : FileLoweringP
val boundParamSymbols = factoryFunction.valueParameters.map { it.symbol }
val unboundParamDeclarations = generateSignatureForClosure(declaration, factoryFunction, closureFunction, reference, arity)
val unboundParamSymbols = unboundParamDeclarations.map { it.symbol }
val returnType = declaration.returnType
val returnType = declaration.returnType.boxIfInlined()
closureFunction.valueParameters += unboundParamDeclarations
closureFunction.returnType = returnType
@@ -540,4 +547,4 @@ class CallableReferenceLowering(val context: JsIrBackendContext) : FileLoweringP
return closureFunction
}
}
}
@@ -38,14 +38,18 @@ class SecondaryCtorLowering(val context: JsIrBackendContext) {
val constructorProcessorLowering = object : DeclarationContainerLoweringPass {
override fun lower(irDeclarationContainer: IrDeclarationContainer) {
irDeclarationContainer.declarations.filterIsInstance<IrClass>().forEach { lowerClass(it) }
irDeclarationContainer.declarations.filterIsInstance<IrClass>().forEach {
if (!it.isInline) // Inline classes are lowered separately
lowerClass(it)
}
}
}
val constructorRedirectorLowering = object : DeclarationContainerLoweringPass {
override fun lower(irDeclarationContainer: IrDeclarationContainer) {
if (irDeclarationContainer is IrClass) {
updateConstructorDeclarations(irDeclarationContainer)
if (!irDeclarationContainer.isInline) // Inline classes are lowered separately
updateConstructorDeclarations(irDeclarationContainer)
}
for (it in irDeclarationContainer.declarations) {
it.accept(CallsiteRedirectionTransformer(), null)
@@ -8,12 +8,14 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
import org.jetbrains.kotlin.ir.types.IrDynamicType
import org.jetbrains.kotlin.ir.util.isFunctionTypeOrSubtype
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -63,6 +65,12 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
}
override fun visitGetField(expression: IrGetField, context: JsGenerationContext): JsExpression {
if (expression.symbol.isBound) {
val fieldParent = expression.symbol.owner.parent
if (fieldParent is IrClass && fieldParent.isInline) {
return expression.receiver!!.accept(this, context)
}
}
val fieldName = context.getNameForSymbol(expression.symbol)
return JsNameRef(fieldName, expression.receiver?.accept(this, context))
}
@@ -100,6 +108,15 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
val thisRef =
if (fromPrimary) JsThisRef() else context.getNameForSymbol(context.currentFunction!!.valueParameters.last().symbol).makeRef()
val arguments = translateCallArguments(expression, context)
val constructor = expression.symbol.owner
if (constructor.parentAsClass.isInline) {
assert(constructor.isPrimary) {
"Delegation to secondary inline constructors must be lowered into simple function calls"
}
return JsBinaryOperation(JsBinaryOperator.ASG, thisRef, arguments.single())
}
return JsInvocation(callFuncRef, listOf(thisRef) + arguments)
}
@@ -127,7 +144,19 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
}
return if (symbol is IrConstructorSymbol) {
JsNew(context.getNameForSymbol(symbol).makeRef(), arguments)
// Inline class primary constructor takes a single value of to
// initialize underlying property.
// TODO: Support initialization block
val klass = symbol.owner.parentAsClass
if (klass.isInline) {
assert(symbol.owner.isPrimary) {
"Inline class secondary constructors must be lowered into static methods"
}
// Argument value constructs unboxed inline class instance
arguments.single()
} else {
JsNew(context.getNameForSymbol(symbol).makeRef(), arguments)
}
} else {
val symbolName = context.getNameForSymbol(symbol)
val ref = if (jsDispatchReceiver != null) JsNameRef(symbolName, jsDispatchReceiver) else JsNameRef(symbolName)
@@ -157,4 +186,4 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
return simpleFunction.name == OperatorNameConventions.INVOKE && receiverType.isFunctionTypeOrSubtype()
}
}
}
@@ -8,10 +8,13 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.util.getInlineClassBackingField
import org.jetbrains.kotlin.ir.util.getInlinedClass
import org.jetbrains.kotlin.js.backend.ast.*
typealias IrCallTransformer = (IrCall, context: JsGenerationContext) -> JsExpression
@@ -183,6 +186,21 @@ class JsIntrinsicTransformers(backendContext: JsIrBackendContext) {
JsNew(JsNameRef("${prefix}Array"), translateCallArguments(call, context))
}
}
add(intrinsics.jsBoxIntrinsic) { call: IrCall, context ->
val arg = translateCallArguments(call, context).single()
val inlineClass = call.getTypeArgument(0)!!.getInlinedClass()!!
val constructor = inlineClass.declarations.filterIsInstance<IrConstructor>().single { it.isPrimary }
JsNew(context.getNameForSymbol(constructor.symbol).makeRef(), listOf(arg))
}
add(intrinsics.jsUnboxIntrinsic) { call: IrCall, context ->
val arg = translateCallArguments(call, context).single()
val inlineClass = call.getTypeArgument(1)!!.getInlinedClass()!!
val field = getInlineClassBackingField(inlineClass)
val fieldName = context.getNameForSymbol(field.symbol)
JsNameRef(fieldName, arg)
}
}
}