[K/N] Use suspend function desugaring lowerings.
This commit is contained in:
committed by
Space Team
parent
8886e1b8b4
commit
817dba8564
@@ -20,6 +20,7 @@ interface Mapping {
|
|||||||
val capturedConstructors: Delegate<IrConstructor, IrConstructor>
|
val capturedConstructors: Delegate<IrConstructor, IrConstructor>
|
||||||
val reflectedNameAccessor: Delegate<IrClass, IrSimpleFunction>
|
val reflectedNameAccessor: Delegate<IrClass, IrSimpleFunction>
|
||||||
val suspendFunctionsToFunctionWithContinuations: Delegate<IrSimpleFunction, IrSimpleFunction>
|
val suspendFunctionsToFunctionWithContinuations: Delegate<IrSimpleFunction, IrSimpleFunction>
|
||||||
|
val functionWithContinuationsToSuspendFunctions: Delegate<IrSimpleFunction, IrSimpleFunction>
|
||||||
|
|
||||||
abstract class Delegate<K : IrDeclaration, V> {
|
abstract class Delegate<K : IrDeclaration, V> {
|
||||||
abstract operator fun get(key: K): V?
|
abstract operator fun get(key: K): V?
|
||||||
@@ -77,6 +78,7 @@ open class DefaultMapping(delegateFactory: DelegateFactory = DefaultDelegateFact
|
|||||||
override val capturedConstructors: Mapping.Delegate<IrConstructor, IrConstructor> = delegateFactory.newDeclarationToDeclarationMapping()
|
override val capturedConstructors: Mapping.Delegate<IrConstructor, IrConstructor> = delegateFactory.newDeclarationToDeclarationMapping()
|
||||||
override val reflectedNameAccessor: Mapping.Delegate<IrClass, IrSimpleFunction> = delegateFactory.newDeclarationToDeclarationMapping()
|
override val reflectedNameAccessor: Mapping.Delegate<IrClass, IrSimpleFunction> = delegateFactory.newDeclarationToDeclarationMapping()
|
||||||
override val suspendFunctionsToFunctionWithContinuations: Mapping.Delegate<IrSimpleFunction, IrSimpleFunction> = delegateFactory.newDeclarationToDeclarationMapping()
|
override val suspendFunctionsToFunctionWithContinuations: Mapping.Delegate<IrSimpleFunction, IrSimpleFunction> = delegateFactory.newDeclarationToDeclarationMapping()
|
||||||
|
override val functionWithContinuationsToSuspendFunctions: Mapping.Delegate<IrSimpleFunction, IrSimpleFunction> = delegateFactory.newDeclarationToDeclarationMapping()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <V : Any> KMutableProperty0<V?>.getOrPut(fn: () -> V) = this.get() ?: fn().also {
|
fun <V : Any> KMutableProperty0<V?>.getOrPut(fn: () -> V) = this.get() ?: fn().also {
|
||||||
|
|||||||
+5
-107
@@ -6,8 +6,6 @@
|
|||||||
package org.jetbrains.kotlin.backend.common.lower
|
package org.jetbrains.kotlin.backend.common.lower
|
||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.*
|
import org.jetbrains.kotlin.backend.common.*
|
||||||
import org.jetbrains.kotlin.backend.common.descriptors.synthesizedName
|
|
||||||
import org.jetbrains.kotlin.backend.common.ir.*
|
|
||||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
@@ -19,7 +17,6 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl
|
|||||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||||
import org.jetbrains.kotlin.ir.types.*
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
|
|
||||||
import org.jetbrains.kotlin.ir.util.*
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
import org.jetbrains.kotlin.ir.visitors.*
|
import org.jetbrains.kotlin.ir.visitors.*
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
@@ -33,9 +30,6 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
|||||||
protected abstract val stateMachineMethodName: Name
|
protected abstract val stateMachineMethodName: Name
|
||||||
protected abstract fun getCoroutineBaseClass(function: IrFunction): IrClassSymbol
|
protected abstract fun getCoroutineBaseClass(function: IrFunction): IrClassSymbol
|
||||||
protected abstract fun nameForCoroutineClass(function: IrFunction): Name
|
protected abstract fun nameForCoroutineClass(function: IrFunction): Name
|
||||||
protected abstract fun IrBuilderWithScope.launchSuspendFunctionWithGivenContinuation(
|
|
||||||
symbol: IrSimpleFunctionSymbol, superQualifierSymbol: IrClassSymbol?, dispatchReceiver: IrExpression,
|
|
||||||
arguments: List<IrExpression>, continuation: IrExpression) : IrExpression
|
|
||||||
|
|
||||||
protected abstract fun buildStateMachine(
|
protected abstract fun buildStateMachine(
|
||||||
stateMachineFunction: IrFunction,
|
stateMachineFunction: IrFunction,
|
||||||
@@ -57,106 +51,6 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
|||||||
markSuspendLambdas(irFile)
|
markSuspendLambdas(irFile)
|
||||||
buildCoroutines(irFile)
|
buildCoroutines(irFile)
|
||||||
transformCallableReferencesToSuspendLambdas(irFile)
|
transformCallableReferencesToSuspendLambdas(irFile)
|
||||||
addMissingSupertypesToSuspendFunctionImplementingClasses(irFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addMissingSupertypesToSuspendFunctionImplementingClasses(irFile: IrFile) {
|
|
||||||
irFile.acceptChildrenVoid(object : IrElementVisitorVoid {
|
|
||||||
override fun visitElement(element: IrElement) {
|
|
||||||
// Don't need to iterate through children. All local classes are already moved to the top level by this moment.
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitClass(declaration: IrClass) {
|
|
||||||
addMissingSupertypes(declaration)
|
|
||||||
declaration.acceptChildrenVoid(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun addMissingSupertypes(clazz: IrClass) {
|
|
||||||
val suspendFunctionTypes = getAllSubstitutedSupertypes(clazz).filter {
|
|
||||||
// SuspendFunction class is some hack in old Kotlin/Native compiler versions.
|
|
||||||
// It's not used now, but is considered as SuspendFunction-like class in isSuspendFunction util,
|
|
||||||
// if found in old klib. We need just to ignore it.
|
|
||||||
it.isSuspendFunction() && it.classOrNull?.owner?.name?.toString() != "SuspendFunction"
|
|
||||||
}.toSet()
|
|
||||||
|
|
||||||
for (suspendFunctionType in suspendFunctionTypes) {
|
|
||||||
val suspendFunctionClassSymbol = suspendFunctionType.classOrNull ?: continue
|
|
||||||
val suspendFunction = suspendFunctionClassSymbol.owner.simpleFunctions().single {
|
|
||||||
it.name == OperatorNameConventions.INVOKE
|
|
||||||
}
|
|
||||||
|
|
||||||
val invokeFunction = clazz.simpleFunctions().single {
|
|
||||||
it.name == OperatorNameConventions.INVOKE && it.overrides(suspendFunction)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invokeFunction.modality == Modality.ABSTRACT) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
val suspendFunctionArity = suspendFunction.valueParameters.size
|
|
||||||
val functionClassSymbol = symbols.functionN(suspendFunctionArity + 1)
|
|
||||||
val functionSymbol = functionClassSymbol.owner.simpleFunctions().single {
|
|
||||||
it.name == OperatorNameConventions.INVOKE
|
|
||||||
}.symbol
|
|
||||||
|
|
||||||
val functionClassTypeArguments = suspendFunctionType.arguments.mapIndexed { index, argument ->
|
|
||||||
val type = (argument as IrTypeProjection).type
|
|
||||||
if (index == suspendFunctionArity) continuationClassSymbol.typeWith(type) else type
|
|
||||||
} + context.irBuiltIns.anyNType
|
|
||||||
|
|
||||||
val functionType = functionClassSymbol.typeWith(functionClassTypeArguments)
|
|
||||||
|
|
||||||
clazz.superTypes += functionType
|
|
||||||
|
|
||||||
context.irFactory.buildFun {
|
|
||||||
startOffset = SYNTHETIC_OFFSET
|
|
||||||
endOffset = SYNTHETIC_OFFSET
|
|
||||||
origin = DECLARATION_ORIGIN_COROUTINE_IMPL
|
|
||||||
name = OperatorNameConventions.INVOKE
|
|
||||||
visibility = DescriptorVisibilities.PROTECTED
|
|
||||||
returnType = context.irBuiltIns.anyNType
|
|
||||||
}.apply {
|
|
||||||
parent = clazz
|
|
||||||
clazz.declarations += this
|
|
||||||
|
|
||||||
typeParameters = invokeFunction.typeParameters.map { parameter ->
|
|
||||||
parameter.copyToWithoutSuperTypes(this, origin = DECLARATION_ORIGIN_COROUTINE_IMPL)
|
|
||||||
.apply { superTypes += parameter.superTypes }
|
|
||||||
}
|
|
||||||
|
|
||||||
valueParameters = invokeFunction.valueParameters
|
|
||||||
.mapIndexed { index, parameter ->
|
|
||||||
parameter.copyTo(this, DECLARATION_ORIGIN_COROUTINE_IMPL, index)
|
|
||||||
}
|
|
||||||
valueParameters += buildValueParameter(this) {
|
|
||||||
index = valueParameters.size
|
|
||||||
type = context.irBuiltIns.anyNType
|
|
||||||
name = "completion".synthesizedName
|
|
||||||
}
|
|
||||||
|
|
||||||
this.createDispatchReceiverParameter()
|
|
||||||
|
|
||||||
overriddenSymbols += functionSymbol
|
|
||||||
|
|
||||||
val thisReceiver = dispatchReceiverParameter!!
|
|
||||||
|
|
||||||
val irBuilder = context.createIrBuilder(symbol, startOffset, endOffset)
|
|
||||||
body = irBuilder.irBlockBody(startOffset, endOffset) {
|
|
||||||
+irReturn(
|
|
||||||
launchSuspendFunctionWithGivenContinuation(
|
|
||||||
invokeFunction.symbol,
|
|
||||||
superQualifierSymbol = invokeFunction.parentAsClass.symbol, // Call non-virtually.
|
|
||||||
irGet(thisReceiver),
|
|
||||||
valueParameters.dropLast(1).map { irGet(it) },
|
|
||||||
irGet(valueParameters.last())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildCoroutines(irFile: IrFile) {
|
private fun buildCoroutines(irFile: IrFile) {
|
||||||
@@ -433,7 +327,9 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
|||||||
)
|
)
|
||||||
|
|
||||||
val suspendInvokeFunction =
|
val suspendInvokeFunction =
|
||||||
suspendFunctionClass!!.simpleFunctions().single { it.name == OperatorNameConventions.INVOKE }
|
suspendFunctionClass!!.simpleFunctions().single { it.name == OperatorNameConventions.INVOKE }.let {
|
||||||
|
context.mapping.functionWithContinuationsToSuspendFunctions[it] ?: it
|
||||||
|
}
|
||||||
|
|
||||||
buildInvokeMethod(
|
buildInvokeMethod(
|
||||||
functionInvokeFunction = suspendInvokeFunction,
|
functionInvokeFunction = suspendInvokeFunction,
|
||||||
@@ -722,3 +618,5 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
|
|||||||
private val CREATE_IDENTIFIER = Name.identifier("create")
|
private val CREATE_IDENTIFIER = Name.identifier("create")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -226,7 +226,7 @@ abstract class SingleAbstractMethodLowering(val context: CommonBackendContext) :
|
|||||||
body = context.createIrBuilder(symbol).irBlockBody {
|
body = context.createIrBuilder(symbol).irBlockBody {
|
||||||
+irReturn(
|
+irReturn(
|
||||||
irCall(
|
irCall(
|
||||||
wrappedFunctionClass.functions.single { it.name == OperatorNameConventions.INVOKE }.symbol,
|
getSuspendFunctionWithoutContinuation(wrappedFunctionClass.functions.single { it.name == OperatorNameConventions.INVOKE }).symbol,
|
||||||
originalSuperMethod.returnType
|
originalSuperMethod.returnType
|
||||||
).apply {
|
).apply {
|
||||||
dispatchReceiver = irGetField(irGet(dispatchReceiverParameter!!), field)
|
dispatchReceiver = irGetField(irGet(dispatchReceiverParameter!!), field)
|
||||||
|
|||||||
+5
-3
@@ -88,12 +88,14 @@ private fun transformSuspendFunction(context: CommonBackendContext, function: Ir
|
|||||||
|
|
||||||
fun IrSimpleFunction.getOrCreateFunctionWithContinuationStub(context: CommonBackendContext): IrSimpleFunction {
|
fun IrSimpleFunction.getOrCreateFunctionWithContinuationStub(context: CommonBackendContext): IrSimpleFunction {
|
||||||
return context.mapping.suspendFunctionsToFunctionWithContinuations.getOrPut(this) {
|
return context.mapping.suspendFunctionsToFunctionWithContinuations.getOrPut(this) {
|
||||||
createSuspendFunctionStub(context)
|
createSuspendFunctionStub(context).also {
|
||||||
|
context.mapping.functionWithContinuationsToSuspendFunctions[it] = this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrSimpleFunction.createSuspendFunctionStub(context: CommonBackendContext): IrSimpleFunction {
|
private fun IrSimpleFunction.createSuspendFunctionStub(context: CommonBackendContext): IrSimpleFunction {
|
||||||
require(this.isSuspend)
|
require(this.isSuspend) { "$fqNameWhenAvailable should be a suspend function to create version with contunation" }
|
||||||
return factory.buildFun {
|
return factory.buildFun {
|
||||||
updateFrom(this@createSuspendFunctionStub)
|
updateFrom(this@createSuspendFunctionStub)
|
||||||
isSuspend = false
|
isSuspend = false
|
||||||
@@ -103,9 +105,9 @@ private fun IrSimpleFunction.createSuspendFunctionStub(context: CommonBackendCon
|
|||||||
}.also { function ->
|
}.also { function ->
|
||||||
function.parent = parent
|
function.parent = parent
|
||||||
|
|
||||||
function.annotations += annotations
|
|
||||||
function.metadata = metadata
|
function.metadata = metadata
|
||||||
|
|
||||||
|
function.copyAnnotationsFrom(this)
|
||||||
function.copyAttributes(this)
|
function.copyAttributes(this)
|
||||||
function.copyTypeParametersFrom(this)
|
function.copyTypeParametersFrom(this)
|
||||||
val substitutionMap = makeTypeParameterSubstitutionMap(this, function)
|
val substitutionMap = makeTypeParameterSubstitutionMap(this, function)
|
||||||
|
|||||||
+8
-2
@@ -2,6 +2,7 @@ package org.jetbrains.kotlin.backend.konan
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.*
|
import org.jetbrains.kotlin.backend.common.*
|
||||||
import org.jetbrains.kotlin.backend.common.lower.*
|
import org.jetbrains.kotlin.backend.common.lower.*
|
||||||
|
import org.jetbrains.kotlin.backend.common.lower.coroutines.AddContinuationToNonLocalSuspendFunctionsLowering
|
||||||
import org.jetbrains.kotlin.backend.common.lower.inline.FunctionInlining
|
import org.jetbrains.kotlin.backend.common.lower.inline.FunctionInlining
|
||||||
import org.jetbrains.kotlin.backend.common.lower.inline.LocalClassesExtractionFromInlineFunctionsLowering
|
import org.jetbrains.kotlin.backend.common.lower.inline.LocalClassesExtractionFromInlineFunctionsLowering
|
||||||
import org.jetbrains.kotlin.backend.common.lower.inline.LocalClassesInInlineFunctionsLowering
|
import org.jetbrains.kotlin.backend.common.lower.inline.LocalClassesInInlineFunctionsLowering
|
||||||
@@ -383,8 +384,13 @@ internal val varargPhase = makeKonanFileLoweringPhase(
|
|||||||
prerequisite = setOf(functionReferencePhase, defaultParameterExtentPhase, interopPhase, functionsWithoutBoundCheck)
|
prerequisite = setOf(functionReferencePhase, defaultParameterExtentPhase, interopPhase, functionsWithoutBoundCheck)
|
||||||
)
|
)
|
||||||
|
|
||||||
internal val coroutinesPhase = makeKonanFileLoweringPhase(
|
internal val coroutinesPhase = makeKonanFileOpPhase(
|
||||||
::NativeSuspendFunctionsLowering,
|
{ context, irFile ->
|
||||||
|
NativeSuspendFunctionsLowering(context).lower(irFile)
|
||||||
|
AddContinuationToNonLocalSuspendFunctionsLowering(context).lower(irFile)
|
||||||
|
NativeAddContinuationToFunctionCallsLowering(context).lower(irFile)
|
||||||
|
AddFunctionSupertypeToSuspendFunctionLowering(context).lower(irFile)
|
||||||
|
},
|
||||||
name = "Coroutines",
|
name = "Coroutines",
|
||||||
description = "Coroutines lowering",
|
description = "Coroutines lowering",
|
||||||
prerequisite = setOf(localFunctionsPhase, finallyBlocksPhase, kotlinNothingValueExceptionPhase)
|
prerequisite = setOf(localFunctionsPhase, finallyBlocksPhase, kotlinNothingValueExceptionPhase)
|
||||||
|
|||||||
+9
-1
@@ -356,6 +356,7 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context) {
|
|||||||
val interfaceVTableEntries: List<IrSimpleFunction> by lazy {
|
val interfaceVTableEntries: List<IrSimpleFunction> by lazy {
|
||||||
require(irClass.isInterface)
|
require(irClass.isInterface)
|
||||||
irClass.simpleFunctions()
|
irClass.simpleFunctions()
|
||||||
|
.map { it.getLoweredVersion() }
|
||||||
.filter { f ->
|
.filter { f ->
|
||||||
f.isOverridable && f.bridgeTarget == null
|
f.isOverridable && f.bridgeTarget == null
|
||||||
&& (f.isReal || f.overriddenSymbols.any { f.needBridgeTo(it.owner) })
|
&& (f.isReal || f.overriddenSymbols.any { f.needBridgeTo(it.owner) })
|
||||||
@@ -503,8 +504,15 @@ internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normally, function should be already replaced. But if the function come from LazyIr, it can be not replaced.
|
||||||
|
*/
|
||||||
|
fun IrSimpleFunction.getLoweredVersion() = context.mapping.suspendFunctionsToFunctionWithContinuations[this] ?: this
|
||||||
|
|
||||||
private val overridableOrOverridingMethods: List<IrSimpleFunction>
|
private val overridableOrOverridingMethods: List<IrSimpleFunction>
|
||||||
get() = irClass.simpleFunctions().filter { it.isOverridableOrOverrides && it.bridgeTarget == null }
|
get() = irClass.simpleFunctions()
|
||||||
|
.map {it.getLoweredVersion() }
|
||||||
|
.filter { it.isOverridableOrOverrides && it.bridgeTarget == null }
|
||||||
|
|
||||||
private val IrFunction.uniqueName get() = computeFunctionName()
|
private val IrFunction.uniqueName get() = computeFunctionName()
|
||||||
}
|
}
|
||||||
|
|||||||
-2
@@ -324,8 +324,6 @@ internal class KonanSymbols(
|
|||||||
|
|
||||||
override val returnIfSuspended = internalFunction("returnIfSuspended")
|
override val returnIfSuspended = internalFunction("returnIfSuspended")
|
||||||
|
|
||||||
val coroutineLaunchpad = internalFunction("coroutineLaunchpad")
|
|
||||||
|
|
||||||
override val suspendCoroutineUninterceptedOrReturn = internalFunction("suspendCoroutineUninterceptedOrReturn")
|
override val suspendCoroutineUninterceptedOrReturn = internalFunction("suspendCoroutineUninterceptedOrReturn")
|
||||||
|
|
||||||
private val coroutinesIntrinsicsPackage =
|
private val coroutinesIntrinsicsPackage =
|
||||||
|
|||||||
+1
-14
@@ -72,7 +72,6 @@ internal enum class IntrinsicType {
|
|||||||
// Coroutines
|
// Coroutines
|
||||||
GET_CONTINUATION,
|
GET_CONTINUATION,
|
||||||
RETURN_IF_SUSPENDED,
|
RETURN_IF_SUSPENDED,
|
||||||
COROUTINE_LAUNCHPAD,
|
|
||||||
// Interop
|
// Interop
|
||||||
INTEROP_READ_BITS,
|
INTEROP_READ_BITS,
|
||||||
INTEROP_WRITE_BITS,
|
INTEROP_WRITE_BITS,
|
||||||
@@ -160,6 +159,7 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv
|
|||||||
* processes it. Otherwise it returns null.
|
* processes it. Otherwise it returns null.
|
||||||
*/
|
*/
|
||||||
fun tryEvaluateSpecialCall(callSite: IrFunctionAccessExpression, resultSlot: LLVMValueRef?): LLVMValueRef? {
|
fun tryEvaluateSpecialCall(callSite: IrFunctionAccessExpression, resultSlot: LLVMValueRef?): LLVMValueRef? {
|
||||||
|
resultSlot.let{}
|
||||||
val function = callSite.symbol.owner
|
val function = callSite.symbol.owner
|
||||||
if (!function.isTypedIntrinsic) {
|
if (!function.isTypedIntrinsic) {
|
||||||
return null
|
return null
|
||||||
@@ -184,18 +184,6 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv
|
|||||||
)
|
)
|
||||||
codegen.theUnitInstanceRef.llvm
|
codegen.theUnitInstanceRef.llvm
|
||||||
}
|
}
|
||||||
IntrinsicType.COROUTINE_LAUNCHPAD -> {
|
|
||||||
val suspendFunctionCall = callSite.getValueArgument(0) as IrCall
|
|
||||||
val continuation = environment.evaluateExpression(callSite.getValueArgument(1)!!, null)
|
|
||||||
val suspendFunction = suspendFunctionCall.symbol.owner
|
|
||||||
assert(suspendFunction.isSuspend) { "Call to a suspend function expected but was ${suspendFunction.dump()}" }
|
|
||||||
environment.evaluateCall(suspendFunction,
|
|
||||||
environment.evaluateExplicitArgs(suspendFunctionCall) + listOf(continuation),
|
|
||||||
environment.calculateLifetime(suspendFunctionCall),
|
|
||||||
suspendFunctionCall.superQualifierSymbol?.owner,
|
|
||||||
resultSlot,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,7 +258,6 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv
|
|||||||
reportNonLoweredIntrinsic(intrinsicType)
|
reportNonLoweredIntrinsic(intrinsicType)
|
||||||
IntrinsicType.INIT_INSTANCE,
|
IntrinsicType.INIT_INSTANCE,
|
||||||
IntrinsicType.OBJC_INIT_BY,
|
IntrinsicType.OBJC_INIT_BY,
|
||||||
IntrinsicType.COROUTINE_LAUNCHPAD,
|
|
||||||
IntrinsicType.OBJC_GET_SELECTOR,
|
IntrinsicType.OBJC_GET_SELECTOR,
|
||||||
IntrinsicType.IMMUTABLE_BLOB ->
|
IntrinsicType.IMMUTABLE_BLOB ->
|
||||||
reportSpecialIntrinsic(intrinsicType)
|
reportSpecialIntrinsic(intrinsicType)
|
||||||
|
|||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* 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.backend.konan.lower
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||||
|
import org.jetbrains.kotlin.backend.common.lower.coroutines.getOrCreateFunctionWithContinuationStub
|
||||||
|
import org.jetbrains.kotlin.backend.konan.Context
|
||||||
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||||
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This lowering is a hack, to provide compatibility with leaked jvm implementation detail.
|
||||||
|
*
|
||||||
|
* On jvm SuspendFunctionN<Args, Ret> is implicitly implementing FunctionN+1<Args, Continuation, Any(?)>,
|
||||||
|
* and visa versa, and it can be used for reusing continuation objects for performance improvements.
|
||||||
|
*
|
||||||
|
* Also, it is used in startCoroutineUninterceptedOrReturn intrinsic.
|
||||||
|
*
|
||||||
|
* So we are adding corresponding FunctionN+1 into supper types.
|
||||||
|
* At the current point, lowered suspend function signature is overriding its invoke method.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
internal class AddFunctionSupertypeToSuspendFunctionLowering(val context: Context) : FileLoweringPass {
|
||||||
|
override fun lower(irFile: IrFile) {
|
||||||
|
irFile.acceptChildrenVoid(object : IrElementVisitorVoid {
|
||||||
|
override fun visitElement(element: IrElement) {
|
||||||
|
// Don't need to iterate through children. All local classes are already moved to the top level by this moment.
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitClass(declaration: IrClass) {
|
||||||
|
addMissingSupertypes(declaration)
|
||||||
|
declaration.acceptChildrenVoid(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrSimpleFunction.getLowered() = if (isSuspend)
|
||||||
|
getOrCreateFunctionWithContinuationStub(context)
|
||||||
|
else
|
||||||
|
this
|
||||||
|
|
||||||
|
private fun IrClass.getInvokeFunction() = simpleFunctions().single {
|
||||||
|
it.name == OperatorNameConventions.INVOKE
|
||||||
|
}.getLowered()
|
||||||
|
|
||||||
|
private fun addOverride(clazz: IrClass, alreadyOverridden: IrType, toOverride: IrType) {
|
||||||
|
val alreadyOverriddenFunction = alreadyOverridden.classOrNull!!.owner.getInvokeFunction()
|
||||||
|
val functionToOverride = toOverride.classOrNull!!.owner.getInvokeFunction()
|
||||||
|
val invokeFunction = clazz.simpleFunctions().single { it.overrides(alreadyOverriddenFunction) }
|
||||||
|
if (invokeFunction.modality == Modality.ABSTRACT) return
|
||||||
|
clazz.superTypes += toOverride
|
||||||
|
invokeFunction.overriddenSymbols += functionToOverride.symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun addMissingSupertypes(clazz: IrClass) {
|
||||||
|
val suspendFunctionSuperTypes = getAllSubstitutedSupertypes(clazz).filter {
|
||||||
|
// SuspendFunction class is some hack in old Kotlin/Native compiler versions.
|
||||||
|
// It's not used now, but is considered as SuspendFunction-like class in isSuspendFunction util,
|
||||||
|
// if found in old klib. We need just to ignore it.
|
||||||
|
it.isSuspendFunction() && it.classOrNull?.owner?.name?.toString() != "SuspendFunction"
|
||||||
|
}.toSet()
|
||||||
|
|
||||||
|
val continuationClassSymbol = context.ir.symbols.continuationClass
|
||||||
|
|
||||||
|
fun IrSimpleType.getClassAt(index: Int) = (this.arguments.getOrNull(index) as? IrTypeProjection)?.type?.classOrNull
|
||||||
|
|
||||||
|
val functionWithContinuationSuperTypes = getAllSubstitutedSupertypes(clazz).filter {
|
||||||
|
it.isFunction() &&
|
||||||
|
it.getClassAt(it.arguments.size - 2) == continuationClassSymbol
|
||||||
|
}.toSet()
|
||||||
|
|
||||||
|
for (suspendFunctionType in suspendFunctionSuperTypes) {
|
||||||
|
val functionClassTypeArguments = suspendFunctionType.arguments.mapIndexed { index, argument ->
|
||||||
|
val type = (argument as IrTypeProjection).type
|
||||||
|
if (index == suspendFunctionType.arguments.indices.last) {
|
||||||
|
continuationClassSymbol.typeWith(type)
|
||||||
|
} else {
|
||||||
|
type
|
||||||
|
}
|
||||||
|
} + context.irBuiltIns.anyNType
|
||||||
|
|
||||||
|
val functionType = context.ir.symbols.functionN(functionClassTypeArguments.size - 1).typeWith(functionClassTypeArguments)
|
||||||
|
|
||||||
|
addOverride(clazz, suspendFunctionType, functionType)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (functionType in functionWithContinuationSuperTypes) {
|
||||||
|
val suspendFunctionClassTypeArguments = functionType.arguments.dropLast(1).mapIndexed { index, argument ->
|
||||||
|
val type = (argument as IrTypeProjection).type
|
||||||
|
if (index == functionType.arguments.indices.last - 1) {
|
||||||
|
require(type.classOrNull == continuationClassSymbol)
|
||||||
|
when (val typeArgument = (type as IrSimpleType).arguments.single()) {
|
||||||
|
is IrTypeProjection -> typeArgument.type
|
||||||
|
else -> context.irBuiltIns.anyNType
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val suspendFunctionType = context.ir.symbols.suspendFunctionN(suspendFunctionClassTypeArguments.size - 1).typeWith(suspendFunctionClassTypeArguments)
|
||||||
|
addOverride(clazz, functionType, suspendFunctionType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+5
-14
@@ -171,8 +171,6 @@ internal class FunctionReferenceLowering(val context: Context) : FileLoweringPas
|
|||||||
|
|
||||||
private val irBuiltIns = context.irBuiltIns
|
private val irBuiltIns = context.irBuiltIns
|
||||||
private val symbols = context.ir.symbols
|
private val symbols = context.ir.symbols
|
||||||
private val getContinuationSymbol = symbols.getContinuation
|
|
||||||
private val continuationClassSymbol = getContinuationSymbol.owner.returnType.classifierOrFail as IrClassSymbol
|
|
||||||
private val startOffset = functionReference.startOffset
|
private val startOffset = functionReference.startOffset
|
||||||
private val endOffset = functionReference.endOffset
|
private val endOffset = functionReference.endOffset
|
||||||
private val referencedFunction = functionReference.symbol.owner
|
private val referencedFunction = functionReference.symbol.owner
|
||||||
@@ -268,18 +266,8 @@ internal class FunctionReferenceLowering(val context: Context) : FileLoweringPas
|
|||||||
superTypes += suspendFunctionClass.typeWith(functionParameterAndReturnTypes)
|
superTypes += suspendFunctionClass.typeWith(functionParameterAndReturnTypes)
|
||||||
} else {
|
} else {
|
||||||
functionClass = (if (isKFunction) symbols.kFunctionN(numberOfParameters) else symbols.functionN(numberOfParameters)).owner
|
functionClass = (if (isKFunction) symbols.kFunctionN(numberOfParameters) else symbols.functionN(numberOfParameters)).owner
|
||||||
|
suspendFunctionClass = null
|
||||||
superTypes += functionClass.typeWith(functionParameterAndReturnTypes)
|
superTypes += functionClass.typeWith(functionParameterAndReturnTypes)
|
||||||
val lastParameterType = unboundFunctionParameters.lastOrNull()?.type
|
|
||||||
if (lastParameterType?.classifierOrNull != continuationClassSymbol)
|
|
||||||
suspendFunctionClass = null
|
|
||||||
else {
|
|
||||||
lastParameterType as IrSimpleType
|
|
||||||
// If the last parameter is Continuation<> inherit from SuspendFunction.
|
|
||||||
suspendFunctionClass = symbols.suspendFunctionN(numberOfParameters - 1).owner
|
|
||||||
val suspendFunctionClassTypeParameters = functionParameterTypes.dropLast(1) +
|
|
||||||
(lastParameterType.arguments.single().typeOrNull ?: irBuiltIns.anyNType)
|
|
||||||
superTypes += suspendFunctionClass.symbol.typeWith(suspendFunctionClassTypeParameters)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (functionClass != null)
|
if (functionClass != null)
|
||||||
@@ -440,7 +428,10 @@ internal class FunctionReferenceLowering(val context: Context) : FileLoweringPas
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildInvokeMethod(superFunction: IrSimpleFunction): IrSimpleFunction {
|
private fun buildInvokeMethod(superFunction: IrSimpleFunction) =
|
||||||
|
buildInvokeMethodImpl(context.mapping.functionWithContinuationsToSuspendFunctions[superFunction] ?: superFunction)
|
||||||
|
|
||||||
|
private fun buildInvokeMethodImpl(superFunction: IrSimpleFunction): IrSimpleFunction {
|
||||||
return IrFunctionImpl(
|
return IrFunctionImpl(
|
||||||
startOffset, endOffset,
|
startOffset, endOffset,
|
||||||
DECLARATION_ORIGIN_FUNCTION_REFERENCE_IMPL,
|
DECLARATION_ORIGIN_FUNCTION_REFERENCE_IMPL,
|
||||||
|
|||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* 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.backend.konan.lower
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.lower.coroutines.AbstractAddContinuationToFunctionCallsLowering
|
||||||
|
import org.jetbrains.kotlin.backend.konan.Context
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
|
import org.jetbrains.kotlin.ir.util.overrides
|
||||||
|
|
||||||
|
internal class NativeAddContinuationToFunctionCallsLowering(override val context: Context) : AbstractAddContinuationToFunctionCallsLowering() {
|
||||||
|
/*
|
||||||
|
* In complex cases suspend functions are converted to state-machine class with invokeSuspend method.
|
||||||
|
* In that case continuation is an object itself
|
||||||
|
* In simple cases, function is left as is, and receives continuation as its last parameter
|
||||||
|
* We should handle both cases here
|
||||||
|
*/
|
||||||
|
override fun IrSimpleFunction.getContinuationParameter() = when {
|
||||||
|
overrides(context.ir.symbols.invokeSuspendFunction.owner) -> dispatchReceiverParameter!!
|
||||||
|
else -> {
|
||||||
|
valueParameters.lastOrNull().also {
|
||||||
|
require(origin == IrDeclarationOrigin.LOWERED_SUSPEND_FUNCTION) { "Continuation parameter only exists in lowered suspend functions, but function origin is $origin" }
|
||||||
|
require(it != null && it.origin == IrDeclarationOrigin.CONTINUATION) { "Continuation parameter is expected to be last one" }
|
||||||
|
}!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.common.ScopeWithIr
|
|||||||
import org.jetbrains.kotlin.backend.common.lower.SingleAbstractMethodLowering
|
import org.jetbrains.kotlin.backend.common.lower.SingleAbstractMethodLowering
|
||||||
import org.jetbrains.kotlin.backend.konan.Context
|
import org.jetbrains.kotlin.backend.konan.Context
|
||||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
|
import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall
|
||||||
import org.jetbrains.kotlin.ir.types.IrType
|
import org.jetbrains.kotlin.ir.types.IrType
|
||||||
import org.jetbrains.kotlin.ir.types.classOrNull
|
import org.jetbrains.kotlin.ir.types.classOrNull
|
||||||
@@ -24,4 +25,8 @@ internal class NativeSingleAbstractMethodLowering(context: Context) : SingleAbst
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val IrType.needEqualsHashCodeMethods get() = true
|
override val IrType.needEqualsHashCodeMethods get() = true
|
||||||
|
|
||||||
|
override fun getSuspendFunctionWithoutContinuation(function: IrSimpleFunction) = function.let {
|
||||||
|
context.mapping.functionWithContinuationsToSuspendFunctions[it] ?: it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
-13
@@ -59,19 +59,6 @@ internal class NativeSuspendFunctionsLowering(ctx: Context): AbstractSuspendFunc
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun IrBuilderWithScope.launchSuspendFunctionWithGivenContinuation(
|
|
||||||
symbol: IrSimpleFunctionSymbol, superQualifierSymbol: IrClassSymbol?, dispatchReceiver: IrExpression,
|
|
||||||
arguments: List<IrExpression>, continuation: IrExpression
|
|
||||||
) = irCall(this@NativeSuspendFunctionsLowering.context.ir.symbols.coroutineLaunchpad).apply {
|
|
||||||
putValueArgument(0, irCall(symbol.owner, superQualifierSymbol = superQualifierSymbol).apply {
|
|
||||||
this.dispatchReceiver = dispatchReceiver
|
|
||||||
arguments.forEachIndexed { index, irExpression ->
|
|
||||||
putValueArgument(index, irExpression)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
putValueArgument(1, continuation)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun buildStateMachine(stateMachineFunction: IrFunction,
|
override fun buildStateMachine(stateMachineFunction: IrFunction,
|
||||||
transformingFunction: IrFunction,
|
transformingFunction: IrFunction,
|
||||||
argumentToPropertiesMap: Map<IrValueParameter, IrField>) {
|
argumentToPropertiesMap: Map<IrValueParameter, IrField>) {
|
||||||
|
|||||||
-5
@@ -714,11 +714,6 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
|
|||||||
is IrCall -> when (value.symbol) {
|
is IrCall -> when (value.symbol) {
|
||||||
getContinuationSymbol -> continuationOverride ?: getContinuation().value
|
getContinuationSymbol -> continuationOverride ?: getContinuation().value
|
||||||
|
|
||||||
symbols.coroutineLaunchpad -> getNode(
|
|
||||||
value.getValueArgument(0)!!,
|
|
||||||
continuationOverride = expressionToEdge(value.getValueArgument(1)!!).node
|
|
||||||
).value
|
|
||||||
|
|
||||||
in arrayGetSymbols -> {
|
in arrayGetSymbols -> {
|
||||||
val actualCallee = value.actualCallee
|
val actualCallee = value.actualCallee
|
||||||
|
|
||||||
|
|||||||
-3
@@ -234,7 +234,6 @@ internal object FileInitializersOptimization {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val executeImplSymbol = context.ir.symbols.executeImpl
|
private val executeImplSymbol = context.ir.symbols.executeImpl
|
||||||
private val coroutineLaunchpadSymbol = context.ir.symbols.coroutineLaunchpad
|
|
||||||
private val getContinuationSymbol = context.ir.symbols.getContinuation
|
private val getContinuationSymbol = context.ir.symbols.getContinuation
|
||||||
|
|
||||||
private var dummySet = mutableSetOf<IrFunctionAccessExpression>()
|
private var dummySet = mutableSetOf<IrFunctionAccessExpression>()
|
||||||
@@ -506,8 +505,6 @@ internal object FileInitializersOptimization {
|
|||||||
return processExecuteImpl(expression, data)
|
return processExecuteImpl(expression, data)
|
||||||
if (expression.symbol == getContinuationSymbol)
|
if (expression.symbol == getContinuationSymbol)
|
||||||
return data
|
return data
|
||||||
if (expression.symbol == coroutineLaunchpadSymbol)
|
|
||||||
return processCoroutineLaunchpad(expression, data)
|
|
||||||
if (!expression.isVirtualCall)
|
if (!expression.isVirtualCall)
|
||||||
return processCall(expression, expression.actualCallee, data)
|
return processCall(expression, expression.actualCallee, data)
|
||||||
val devirtualizedCallSite = virtualCallSites[expression] ?: return data
|
val devirtualizedCallSite = virtualCallSites[expression] ?: return data
|
||||||
|
|||||||
+33
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
package codegen.function.unreachable_statement_after_return
|
package codegen.function.unreachable_statement_after_return
|
||||||
|
import kotlin.coroutines.*
|
||||||
|
|
||||||
fun test1(): Any? {
|
fun test1(): Any? {
|
||||||
return 1
|
return 1
|
||||||
@@ -25,10 +26,42 @@ fun test4(): Int {
|
|||||||
42
|
42
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun test5(): Any? {
|
||||||
|
return 5
|
||||||
|
42
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun test6(): Int? {
|
||||||
|
return 6
|
||||||
|
42
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun test7(): Any {
|
||||||
|
return 7
|
||||||
|
42
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun test8(): Int {
|
||||||
|
return 8
|
||||||
|
42
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun <T> (suspend () -> T).runCoroutine() : T {
|
||||||
|
var result : Any? = null
|
||||||
|
startCoroutine(Continuation(EmptyCoroutineContext) { result = it.getOrThrow() })
|
||||||
|
return result as T
|
||||||
|
}
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
println(test1())
|
println(test1())
|
||||||
println(test2())
|
println(test2())
|
||||||
println(test3())
|
println(test3())
|
||||||
println(test4())
|
println(test4())
|
||||||
|
|
||||||
|
println(::test5.runCoroutine())
|
||||||
|
println(::test6.runCoroutine())
|
||||||
|
println(::test7.runCoroutine())
|
||||||
|
println(::test8.runCoroutine())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
@@ -2,3 +2,7 @@
|
|||||||
2
|
2
|
||||||
3
|
3
|
||||||
4
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
|||||||
@@ -25,6 +25,3 @@ internal inline suspend fun getCoroutineContext(): CoroutineContext =
|
|||||||
@TypedIntrinsic(IntrinsicType.RETURN_IF_SUSPENDED)
|
@TypedIntrinsic(IntrinsicType.RETURN_IF_SUSPENDED)
|
||||||
@PublishedApi
|
@PublishedApi
|
||||||
internal external suspend fun <T> returnIfSuspended(@Suppress("UNUSED_PARAMETER") argument: Any?): T
|
internal external suspend fun <T> returnIfSuspended(@Suppress("UNUSED_PARAMETER") argument: Any?): T
|
||||||
|
|
||||||
@TypedIntrinsic(IntrinsicType.COROUTINE_LAUNCHPAD)
|
|
||||||
internal external fun coroutineLaunchpad(suspendFunctionCall: Any?, continuation: Continuation<*>): Any?
|
|
||||||
@@ -61,7 +61,6 @@ class IntrinsicType {
|
|||||||
// Coroutines
|
// Coroutines
|
||||||
const val GET_CONTINUATION = "GET_CONTINUATION"
|
const val GET_CONTINUATION = "GET_CONTINUATION"
|
||||||
const val RETURN_IF_SUSPENDED = "RETURN_IF_SUSPENDED"
|
const val RETURN_IF_SUSPENDED = "RETURN_IF_SUSPENDED"
|
||||||
const val COROUTINE_LAUNCHPAD = "COROUTINE_LAUNCHPAD"
|
|
||||||
|
|
||||||
// Interop
|
// Interop
|
||||||
const val INTEROP_READ_PRIMITIVE = "INTEROP_READ_PRIMITIVE"
|
const val INTEROP_READ_PRIMITIVE = "INTEROP_READ_PRIMITIVE"
|
||||||
|
|||||||
Reference in New Issue
Block a user