JVM_IR. Proper lower toArray functions

This commit is contained in:
Mikhael Bogdanov
2019-11-18 16:49:12 +01:00
parent 6a18ef1417
commit 742c3a228a
6 changed files with 259 additions and 138 deletions
@@ -484,7 +484,8 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
/// We assume that IR code that reaches us has been checked for correctness at the frontend. /// We assume that IR code that reaches us has been checked for correctness at the frontend.
/// This function needs to single out those cases where Java accessibility rules differ from Kotlin's. /// This function needs to single out those cases where Java accessibility rules differ from Kotlin's.
val declarationRaw = owner as IrDeclarationWithVisibility val symbolOwner = owner
val declarationRaw = symbolOwner as IrDeclarationWithVisibility
val declaration = val declaration =
(declarationRaw as? IrSimpleFunction)?.resolveFakeOverride() (declarationRaw as? IrSimpleFunction)?.resolveFakeOverride()
?: (declarationRaw as? IrField)?.resolveFakeOverride() ?: declarationRaw ?: (declarationRaw as? IrField)?.resolveFakeOverride() ?: declarationRaw
@@ -495,6 +496,14 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
// `internal` maps to public and requires no accessor. // `internal` maps to public and requires no accessor.
if (!withSuper && !declaration.visibility.isPrivate && !declaration.visibility.isProtected) return true if (!withSuper && !declaration.visibility.isPrivate && !declaration.visibility.isProtected) return true
//`toArray` is always accessible cause mapped to public functions
if (symbolOwner is IrSimpleFunction && (symbolOwner.isNonGenericToArray() || symbolOwner.isGenericToArray(context))) {
val parent = symbolOwner.parent
if (parent is IrClass && parent.isCollectionSubClass()) {
return true
}
}
// If local variables are accessible by Kotlin rules, they also are by Java rules. // If local variables are accessible by Kotlin rules, they also are by Java rules.
val symbolDeclarationContainer = (declaration.parent as? IrDeclarationContainer) as? IrElement ?: return true val symbolDeclarationContainer = (declaration.parent as? IrDeclarationContainer) as? IrElement ?: return true
val contextDeclarationContainer = (currentScope!!.irElement as IrDeclaration).getAccessContext(withSuper) ?: return false val contextDeclarationContainer = (currentScope!!.irElement as IrDeclaration).getAccessContext(withSuper) ?: return false
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
@@ -18,7 +19,6 @@ import org.jetbrains.kotlin.ir.builders.irBlockBody
import org.jetbrains.kotlin.ir.builders.irCall import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irReturn import org.jetbrains.kotlin.ir.builders.irReturn
import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
@@ -35,7 +35,9 @@ import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.defaultType import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.isClass import org.jetbrains.kotlin.ir.util.isClass
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.types.Variance import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.utils.DFS import org.jetbrains.kotlin.utils.DFS
internal val toArrayPhase = makeIrFilePhase( internal val toArrayPhase = makeIrFilePhase(
@@ -46,173 +48,162 @@ internal val toArrayPhase = makeIrFilePhase(
private class ToArrayLowering(private val context: JvmBackendContext) : ClassLoweringPass { private class ToArrayLowering(private val context: JvmBackendContext) : ClassLoweringPass {
override fun lower(irClass: IrClass) { override fun lower(irClass: IrClass) {
if (irClass.isJvmInterface || !irClass.isDirectCollectionSubClass()) return if (irClass.isJvmInterface || !irClass.isCollectionSubClass()) return
val irBuiltIns = context.irBuiltIns val irBuiltIns = context.irBuiltIns
val symbols = context.ir.symbols val symbols = context.ir.symbols
val toArrayName = Name.identifier("toArray") val toArrayName = Name.identifier("toArray")
val genericToArray = irClass.declarations.find { it.isGenericToArray() } val genericToArray = irClass.declarations.filterIsInstance<IrSimpleFunction>().find { it.isGenericToArray(context) }
val nonGenericToArray = irClass.declarations.find { it.isNonGenericToArray() } val nonGenericToArray = irClass.declarations.filterIsInstance<IrSimpleFunction>().find { it.isNonGenericToArray() }
val isDirectCollectionSubClass = irClass.isDirectCollectionSubClass()
if (genericToArray == null) { if (genericToArray == null) {
val typeParameterDescriptor = WrappedTypeParameterDescriptor() if (isDirectCollectionSubClass) {
val typeParameter = IrTypeParameterImpl( val typeParameterDescriptor = WrappedTypeParameterDescriptor()
UNDEFINED_OFFSET, UNDEFINED_OFFSET, val typeParameter = IrTypeParameterImpl(
JvmLoweredDeclarationOrigin.TO_ARRAY,
IrTypeParameterSymbolImpl(typeParameterDescriptor),
Name.identifier("T"),
index = 0,
variance = Variance.INVARIANT,
isReified = false
).apply {
typeParameterDescriptor.bind(this)
superTypes.add(irBuiltIns.anyNType)
}
val substitutedArrayType = irBuiltIns.arrayClass.typeWith(typeParameter.defaultType)
val functionDescriptor = WrappedSimpleFunctionDescriptor()
val irFunction = IrFunctionImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
JvmLoweredDeclarationOrigin.TO_ARRAY,
IrSimpleFunctionSymbolImpl(functionDescriptor),
toArrayName,
Visibilities.PUBLIC,
Modality.OPEN,
returnType = substitutedArrayType,
isInline = false,
isExternal = false,
isTailrec = false,
isSuspend = false,
isExpect = false,
isFakeOverride = false
)
functionDescriptor.bind(irFunction)
irFunction.parent = irClass
typeParameter.parent = irFunction
irFunction.typeParameters.add(typeParameter)
val dispatchReceiverParameterDescriptor = WrappedValueParameterDescriptor()
irFunction.dispatchReceiverParameter = IrValueParameterImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
JvmLoweredDeclarationOrigin.TO_ARRAY,
IrValueParameterSymbolImpl(dispatchReceiverParameterDescriptor),
Name.special("<this>"),
index = -1,
type = irClass.defaultType,
varargElementType = null,
isCrossinline = false,
isNoinline = false
).apply {
parent = irFunction
}
val valueParameterDescriptor = WrappedValueParameterDescriptor()
irFunction.valueParameters.add(
IrValueParameterImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, UNDEFINED_OFFSET, UNDEFINED_OFFSET,
JvmLoweredDeclarationOrigin.TO_ARRAY, JvmLoweredDeclarationOrigin.TO_ARRAY,
IrValueParameterSymbolImpl(valueParameterDescriptor), IrTypeParameterSymbolImpl(typeParameterDescriptor),
Name.identifier("array"), Name.identifier("T"),
index = 0, index = 0,
variance = Variance.INVARIANT,
isReified = false
).apply {
typeParameterDescriptor.bind(this)
superTypes.add(irBuiltIns.anyNType)
}
val substitutedArrayType = irBuiltIns.arrayClass.typeWith(typeParameter.defaultType)
val functionDescriptor = WrappedSimpleFunctionDescriptor()
val irFunction = IrFunctionImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
JvmLoweredDeclarationOrigin.TO_ARRAY,
IrSimpleFunctionSymbolImpl(functionDescriptor),
toArrayName,
Visibilities.PUBLIC,
Modality.OPEN,
returnType = substitutedArrayType,
isInline = false,
isExternal = false,
isTailrec = false,
isSuspend = false,
isExpect = false,
isFakeOverride = false
)
functionDescriptor.bind(irFunction)
irFunction.parent = irClass
typeParameter.parent = irFunction
irFunction.typeParameters.add(typeParameter)
val dispatchReceiverParameterDescriptor = WrappedValueParameterDescriptor()
irFunction.dispatchReceiverParameter = IrValueParameterImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
JvmLoweredDeclarationOrigin.TO_ARRAY,
IrValueParameterSymbolImpl(dispatchReceiverParameterDescriptor),
Name.special("<this>"),
index = -1,
type = irClass.defaultType,
varargElementType = null, varargElementType = null,
type = substitutedArrayType,
isCrossinline = false, isCrossinline = false,
isNoinline = false isNoinline = false
).apply { ).apply {
valueParameterDescriptor.bind(this)
parent = irFunction parent = irFunction
} }
) val valueParameterDescriptor = WrappedValueParameterDescriptor()
irFunction.valueParameters.add(
IrValueParameterImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
JvmLoweredDeclarationOrigin.TO_ARRAY,
IrValueParameterSymbolImpl(valueParameterDescriptor),
Name.identifier("array"),
index = 0,
varargElementType = null,
type = substitutedArrayType,
isCrossinline = false,
isNoinline = false
).apply {
valueParameterDescriptor.bind(this)
parent = irFunction
}
)
irFunction.body = context.createIrBuilder(irFunction.symbol).irBlockBody { irFunction.body = context.createIrBuilder(irFunction.symbol).irBlockBody {
+irReturn( +irReturn(
irCall(symbols.genericToArray, symbols.genericToArray.owner.returnType).apply { irCall(symbols.genericToArray, symbols.genericToArray.owner.returnType).apply {
putValueArgument( putValueArgument(
0, 0,
IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, irFunction.dispatchReceiverParameter!!.symbol) IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, irFunction.dispatchReceiverParameter!!.symbol)
) )
putValueArgument(1, IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, irFunction.valueParameters[0].symbol)) putValueArgument(1, IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, irFunction.valueParameters[0].symbol))
}) })
}
irClass.declarations.add(irFunction)
} }
irClass.declarations.add(irFunction)
} else { } else {
//TODO patch visibility (genericToArray as IrFunctionImpl).visibility = Visibilities.PUBLIC
} }
if (nonGenericToArray == null) { if (nonGenericToArray == null) {
val functionDescriptor = WrappedSimpleFunctionDescriptor() if (isDirectCollectionSubClass) {
val irFunction = IrFunctionImpl( val functionDescriptor = WrappedSimpleFunctionDescriptor()
UNDEFINED_OFFSET, UNDEFINED_OFFSET, val irFunction = IrFunctionImpl(
JvmLoweredDeclarationOrigin.TO_ARRAY, UNDEFINED_OFFSET, UNDEFINED_OFFSET,
IrSimpleFunctionSymbolImpl(functionDescriptor), JvmLoweredDeclarationOrigin.TO_ARRAY,
toArrayName, IrSimpleFunctionSymbolImpl(functionDescriptor),
Visibilities.PUBLIC, toArrayName,
Modality.OPEN, Visibilities.PUBLIC,
returnType = irBuiltIns.arrayClass.typeWith(irBuiltIns.anyNType), Modality.OPEN,
isInline = false, returnType = irBuiltIns.arrayClass.typeWith(irBuiltIns.anyNType),
isExternal = false, isInline = false,
isTailrec = false, isExternal = false,
isSuspend = false, isTailrec = false,
isExpect = false, isSuspend = false,
isFakeOverride = false isExpect = false,
) isFakeOverride = false
functionDescriptor.bind(irFunction) )
irFunction.parent = irClass functionDescriptor.bind(irFunction)
irFunction.parent = irClass
val dispatchReceiverParameterDescriptor = WrappedValueParameterDescriptor() val dispatchReceiverParameterDescriptor = WrappedValueParameterDescriptor()
irFunction.dispatchReceiverParameter = IrValueParameterImpl( irFunction.dispatchReceiverParameter = IrValueParameterImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, UNDEFINED_OFFSET, UNDEFINED_OFFSET,
JvmLoweredDeclarationOrigin.TO_ARRAY, JvmLoweredDeclarationOrigin.TO_ARRAY,
IrValueParameterSymbolImpl(dispatchReceiverParameterDescriptor), IrValueParameterSymbolImpl(dispatchReceiverParameterDescriptor),
Name.special("<this>"), Name.special("<this>"),
index = -1, index = -1,
type = irClass.defaultType, type = irClass.defaultType,
varargElementType = null, varargElementType = null,
isCrossinline = false, isCrossinline = false,
isNoinline = false isNoinline = false
).apply { ).apply {
parent = irFunction parent = irFunction
}
irFunction.body = context.createIrBuilder(irFunction.symbol).irBlockBody {
+irReturn(
irCall(symbols.nonGenericToArray, symbols.nonGenericToArray.owner.returnType).apply {
putValueArgument(
0,
IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, irFunction.dispatchReceiverParameter!!.symbol)
)
})
}
irClass.declarations.add(irFunction)
} }
irFunction.body = context.createIrBuilder(irFunction.symbol).irBlockBody {
+irReturn(
irCall(symbols.nonGenericToArray, symbols.nonGenericToArray.owner.returnType).apply {
putValueArgument(
0,
IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, irFunction.dispatchReceiverParameter!!.symbol)
)
})
}
irClass.declarations.add(irFunction)
} else { } else {
//TODO patch visibility (nonGenericToArray as IrFunctionImpl).visibility = Visibilities.PUBLIC
} }
} }
private fun IrDeclaration.isGenericToArray(): Boolean {
if (this !is IrSimpleFunction) return false
val signature = context.methodSignatureMapper.mapAsmMethod(this)
return signature.toString() == "toArray([Ljava/lang/Object;)[Ljava/lang/Object;"
}
private fun IrDeclaration.isNonGenericToArray(): Boolean {
if (this !is IrSimpleFunction) return false
if (this.name.asString() != "toArray") return false
if (typeParameters.isNotEmpty() || valueParameters.isNotEmpty()) return false
if (!returnType.isArray()) return false
return true
}
} }
private val IrClass.superClasses private val IrClass.superClasses
get() = superTypes.mapNotNull { it.getClass() } get() = superTypes.mapNotNull { it.getClass() }
// Have to check by name, since irBuiltins is unreliable. // Have to check by name, since irBuiltins is unreliable.
private fun IrClass.isCollectionSubClass() = internal fun IrClass.isCollectionSubClass() =
DFS.ifAny(listOf(this), IrClass::superClasses) { it.defaultType.isCollection() } DFS.ifAny(listOf(this), IrClass::superClasses) { it.defaultType.isCollection() }
// If this class inherits from another Kotlin class that implements Collection, it already has toArray. // If this class inherits from another Kotlin class that implements Collection, it already has toArray.
@@ -220,3 +211,23 @@ private fun IrClass.isDirectCollectionSubClass() =
isCollectionSubClass() && !superClasses.any { isCollectionSubClass() && !superClasses.any {
it.isClass && it.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB && it.isCollectionSubClass() it.isClass && it.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB && it.isCollectionSubClass()
} }
internal fun IrSimpleFunction.isGenericToArray(context: JvmBackendContext): Boolean {
if (name.asString() != "toArray") return false
if (typeParameters.size != 1 || valueParameters.size != 1 || extensionReceiverParameter != null) return false
val paramType = valueParameters[0].type
if (!returnType.isArray() || !paramType.isArray()) return false
val elementType = typeParameters[0].defaultType
val expectedType = context.ir.symbols.array.typeWith(elementType)
return expectedType == paramType && expectedType == returnType
}
internal fun IrSimpleFunction.isNonGenericToArray(): Boolean {
if (name.asString() != "toArray") return false
if (typeParameters.isNotEmpty() || valueParameters.isNotEmpty() || extensionReceiverParameter != null) return false
return returnType.isArray()
}
@@ -1,5 +1,4 @@
// IGNORE_BACKEND_FIR: JVM_IR // IGNORE_BACKEND_FIR: JVM_IR
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM // TARGET_BACKEND: JVM
// WITH_RUNTIME // WITH_RUNTIME
@@ -0,0 +1,66 @@
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: SingletonCollection.kt
package test
open class SingletonCollection<T>(val value: T) : AbstractCollection<T>() {
override val size = 1
override fun iterator(): Iterator<T> = listOf(value).iterator()
protected override final fun toArray(): Array<Any?> =
arrayOf<Any?>(value)
protected override final fun <E> toArray(a: Array<E>): Array<E> {
a[0] = value as E
return a
}
}
open class SingletonCollection2<T>(val value: T) : AbstractCollection<T>() {
override val size = 1
override fun iterator(): Iterator<T> = listOf(value).iterator()
}
// FILE: DerivedSingletonCollection.kt
package test2
import test.*
class DerivedSingletonCollection<T>(value: T) : SingletonCollection<T>(value) {
fun test() = object {
fun test() = toArray()
}.test()
fun <E> test(a: Array<E>) = object {
fun test() = toArray(a)
}.test()
}
class DerivedSingletonCollection2<T>(value: T) : SingletonCollection2<T>(value) {
fun test() = object {
fun test() = toArray()
}.test()
fun <E> test(a: Array<E>) = object {
fun test() = toArray(a)
}.test()
}
// @test/SingletonCollection.class:
// 0 access\$
// 2 public final toArray
// 0 \.toArray
// @test/SingletonCollection2.class:
// 0 access\$
// 0 toArray
// @test2/DerivedSingletonCollection.class:
// 0 access\$
// 0 toArray
// @test2/DerivedSingletonCollection2.class:
// 0 access\$
// 0 toArray
@@ -4061,6 +4061,24 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
} }
} }
@TestMetadata("compiler/testData/codegen/bytecodeText/toArray")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ToArray extends AbstractBytecodeTextTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
}
public void testAllFilesPresentInToArray() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/toArray"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("noAccessorForToArray.kt")
public void testNoAccessorForToArray() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/toArray/noAccessorForToArray.kt");
}
}
@TestMetadata("compiler/testData/codegen/bytecodeText/unsignedTypes") @TestMetadata("compiler/testData/codegen/bytecodeText/unsignedTypes")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -3979,6 +3979,24 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
} }
} }
@TestMetadata("compiler/testData/codegen/bytecodeText/toArray")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ToArray extends AbstractIrBytecodeTextTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath);
}
public void testAllFilesPresentInToArray() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/toArray"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true);
}
@TestMetadata("noAccessorForToArray.kt")
public void testNoAccessorForToArray() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/toArray/noAccessorForToArray.kt");
}
}
@TestMetadata("compiler/testData/codegen/bytecodeText/unsignedTypes") @TestMetadata("compiler/testData/codegen/bytecodeText/unsignedTypes")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)