[K/N] Support of @Volatile annotation

^KT-54944
This commit is contained in:
Pavel Kunyavskiy
2022-11-14 17:08:28 +01:00
committed by Space Team
parent a29c418b63
commit 9dea349752
20 changed files with 631 additions and 295 deletions
@@ -50,6 +50,10 @@ import kotlin.LazyThreadSafetyMode.PUBLICATION
internal class NativeMapping : DefaultMapping() {
data class BridgeKey(val target: IrSimpleFunction, val bridgeDirections: BridgeDirections)
enum class AtomicFunctionType {
COMPARE_AND_SWAP, COMPARE_AND_SET, GET_AND_SET, GET_AND_ADD;
}
data class AtomicFunctionKey(val field: IrField, val type: AtomicFunctionType)
val outerThisFields = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrField>()
val enumValueGetters = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrFunction>()
@@ -62,6 +66,8 @@ internal class NativeMapping : DefaultMapping() {
val boxFunctions = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrSimpleFunction>()
val unboxFunctions = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrSimpleFunction>()
val loweredInlineClassConstructors = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrConstructor, IrSimpleFunction>()
val volatileFieldToAtomicFunction = mutableMapOf<AtomicFunctionKey, IrSimpleFunction>()
val functionToVolatileField = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrSimpleFunction, IrField>()
}
internal class Context(
@@ -25,6 +25,7 @@ object KonanFqNames {
val cancellationException = FqName("kotlin.coroutines.cancellation.CancellationException")
val threadLocal = FqName("kotlin.native.concurrent.ThreadLocal")
val sharedImmutable = FqName("kotlin.native.concurrent.SharedImmutable")
val volatile = FqName("kotlin.concurrent.Volatile")
val frozen = FqName("kotlin.native.internal.Frozen")
val frozenLegacyMM = FqName("kotlin.native.internal.FrozenLegacyMM")
val leakDetectorCandidate = FqName("kotlin.native.internal.LeakDetectorCandidate")
@@ -274,6 +274,13 @@ internal val tailrecPhase = makeKonanFileLoweringPhase(
prerequisite = setOf(localFunctionsPhase)
)
internal val volatilePhase = makeKonanFileLoweringPhase(
::VolatileFieldsLowering,
name = "VolatileFields",
description = "Volatile fields processing",
prerequisite = setOf(localFunctionsPhase)
)
internal val defaultParameterExtentPhase = makeKonanFileOpPhase(
{ context, irFile ->
KonanDefaultArgumentStubGenerator(context).lower(irFile)
@@ -335,7 +342,8 @@ internal val testProcessorPhase = makeKonanFileOpPhase(
internal val delegationPhase = makeKonanFileLoweringPhase(
{ PropertyDelegationLowering(it.generationState) },
name = "Delegation",
description = "Delegation lowering"
description = "Delegation lowering",
prerequisite = setOf(volatilePhase)
)
internal val functionReferencePhase = makeKonanFileLoweringPhase(
@@ -165,6 +165,7 @@ internal val allLoweringsPhase = SameTypeNamedCompilerPhase(
enumConstructorsPhase,
initializersPhase,
localFunctionsPhase,
volatilePhase,
tailrecPhase,
defaultParameterExtentPhase,
innerClassPhase,
@@ -99,6 +99,7 @@ internal abstract class KonanSymbols(
val symbolName = topLevelClass(RuntimeNames.symbolNameAnnotation)
val filterExceptions = topLevelClass(RuntimeNames.filterExceptions)
val exportForCppRuntime = topLevelClass(RuntimeNames.exportForCppRuntime)
val typedIntrinsic = topLevelClass(RuntimeNames.typedIntrinsicAnnotation)
val objCMethodImp = symbolTable.referenceClass(descriptorsLookup.interopBuiltIns.objCMethodImp)
@@ -415,6 +416,8 @@ internal abstract class KonanSymbols(
val sharedImmutable = topLevelClass(KonanFqNames.sharedImmutable)
val volatile = topLevelClass(KonanFqNames.volatile)
val eagerInitialization = topLevelClass(KonanFqNames.eagerInitialization)
private fun topLevelClass(fqName: FqName): IrClassSymbol = irBuiltIns.findClass(fqName.shortName(), fqName.parent())!!
@@ -560,21 +560,22 @@ internal abstract class FunctionGenerationContext(
fun param(index: Int): LLVMValueRef = LLVMGetParam(this.function, index)!!
fun load(address: LLVMValueRef, name: String = "", memoryOrder: LLVMAtomicOrdering? = null): LLVMValueRef {
fun load(address: LLVMValueRef, name: String = "",
memoryOrder: LLVMAtomicOrdering? = null, alignment: Int? = null
): LLVMValueRef {
val value = LLVMBuildLoad(builder, address, name)!!
if (memoryOrder != null) {
LLVMSetOrdering(value, memoryOrder)
}
memoryOrder?.let { LLVMSetOrdering(value, it) }
alignment?.let { LLVMSetAlignment(value, it) }
// Use loadSlot() API for that.
assert(!isObjectRef(value))
return value
}
fun loadSlot(address: LLVMValueRef, isVar: Boolean, resultSlot: LLVMValueRef? = null, name: String = "", memoryOrder: LLVMAtomicOrdering? = null): LLVMValueRef {
fun loadSlot(address: LLVMValueRef, isVar: Boolean, resultSlot: LLVMValueRef? = null, name: String = "",
memoryOrder: LLVMAtomicOrdering? = null, alignment: Int? = null): LLVMValueRef {
val value = LLVMBuildLoad(builder, address, name)!!
if (memoryOrder != null) {
LLVMSetOrdering(value, memoryOrder)
}
memoryOrder?.let { LLVMSetOrdering(value, it) }
alignment?.let { LLVMSetAlignment(value, it) }
if (isObjectRef(value) && isVar) {
val slot = resultSlot ?: alloca(LLVMTypeOf(value), variableLocation = null)
storeStackRef(value, slot)
@@ -582,8 +583,10 @@ internal abstract class FunctionGenerationContext(
return value
}
fun store(value: LLVMValueRef, ptr: LLVMValueRef) {
LLVMBuildStore(builder, value, ptr)
fun store(value: LLVMValueRef, ptr: LLVMValueRef, memoryOrder: LLVMAtomicOrdering? = null, alignment: Int? = null) {
val store = LLVMBuildStore(builder, value, ptr)
memoryOrder?.let { LLVMSetOrdering(store, it) }
alignment?.let { LLVMSetAlignment(store, it) }
}
fun storeHeapRef(value: LLVMValueRef, ptr: LLVMValueRef) {
@@ -594,12 +597,12 @@ internal abstract class FunctionGenerationContext(
updateRef(value, ptr, onStack = true)
}
fun storeAny(value: LLVMValueRef, ptr: LLVMValueRef, onStack: Boolean) = if (isObjectRef(value)) {
if (onStack) storeStackRef(value, ptr) else storeHeapRef(value, ptr)
null
} else {
LLVMBuildStore(builder, value, ptr)
fun storeAny(value: LLVMValueRef, ptr: LLVMValueRef, onStack: Boolean, isVolatile: Boolean = false, alignment: Int? = null) {
when {
isObjectRef(value) -> updateRef(value, ptr, onStack, isVolatile, alignment)
else -> store(value, ptr, if (isVolatile) LLVMAtomicOrdering.LLVMAtomicOrderingSequentiallyConsistent else null, alignment)
}
}
fun freeze(value: LLVMValueRef, exceptionHandler: ExceptionHandler) {
if (isObjectRef(value))
@@ -618,14 +621,21 @@ internal abstract class FunctionGenerationContext(
call(llvm.updateReturnRefFunction, listOf(address, value))
}
private fun updateRef(value: LLVMValueRef, address: LLVMValueRef, onStack: Boolean) {
private fun updateRef(value: LLVMValueRef, address: LLVMValueRef, onStack: Boolean,
isVolatile: Boolean = false, alignment: Int? = null) {
require(alignment == null || alignment == runtime.pointerAlignment)
if (onStack) {
require(!isVolatile) { "Stack ref update can't be volatile"}
if (context.memoryModel == MemoryModel.STRICT)
store(value, address)
else
call(llvm.updateStackRefFunction, listOf(address, value))
} else {
call(llvm.updateHeapRefFunction, listOf(address, value))
if (isVolatile && context.memoryModel == MemoryModel.EXPERIMENTAL) {
call(llvm.UpdateVolatileHeapRef, listOf(address, value))
} else {
call(llvm.updateHeapRefFunction, listOf(address, value))
}
}
}
@@ -1349,7 +1359,7 @@ internal abstract class FunctionGenerationContext(
}
addPhiIncoming(slotsPhi!!, prologueBb to slots)
memScoped {
slotToVariableLocation.forEach { slot, variable ->
slotToVariableLocation.forEach { (slot, variable) ->
val expr = longArrayOf(DwarfOp.DW_OP_plus_uconst.value,
runtime.pointerSize * slot.toLong()).toCValues()
DIInsertDeclaration(
@@ -457,6 +457,12 @@ internal class Llvm(private val generationState: NativeGenerationState, val modu
val Kotlin_processFieldInMark by lazyRtFunction
val Kotlin_processEmptyObjectInMark by lazyRtFunction
val UpdateVolatileHeapRef by lazyRtFunction
val CompareAndSetVolatileHeapRef by lazyRtFunction
val CompareAndSwapVolatileHeapRef by lazyRtFunction
val GetAndSetVolatileHeapRef by lazyRtFunction
val tlsMode by lazy {
when (target) {
KonanTarget.WASM32,
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.backend.konan.llvm.objc.genObjCSelector
import org.jetbrains.kotlin.backend.konan.reportCompilationError
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
@@ -90,7 +91,16 @@ internal enum class IntrinsicType {
INTEROP_FUNPTR_INVOKE,
INTEROP_MEMORY_COPY,
// Worker
WORKER_EXECUTE
WORKER_EXECUTE,
// Atomics
COMPARE_AND_SET_FIELD,
COMPARE_AND_SWAP_FIELD,
GET_AND_SET_FIELD,
GET_AND_ADD_FIELD,
COMPARE_AND_SET,
COMPARE_AND_SWAP,
GET_AND_SET,
GET_AND_ADD,
}
internal enum class ConstantConstructorIntrinsicType {
@@ -117,6 +127,8 @@ internal interface IntrinsicGeneratorEnvironment {
fun evaluateExplicitArgs(expression: IrFunctionAccessExpression): List<LLVMValueRef>
fun evaluateExpression(value: IrExpression, resultSlot: LLVMValueRef?): LLVMValueRef
fun getObjectFieldPointer(thisRef: LLVMValueRef, field: IrField): LLVMValueRef
}
internal fun tryGetIntrinsicType(callSite: IrFunctionAccessExpression): IntrinsicType? =
@@ -242,6 +254,10 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv
IntrinsicType.INTEROP_MEMORY_COPY -> emitMemoryCopy(callSite, args)
IntrinsicType.IS_EXPERIMENTAL_MM -> emitIsExperimentalMM()
IntrinsicType.THE_UNIT_INSTANCE -> theUnitInstanceRef.llvm
IntrinsicType.COMPARE_AND_SET -> emitCompareAndSet(callSite, args)
IntrinsicType.COMPARE_AND_SWAP -> emitCompareAndSwap(callSite, args, resultSlot)
IntrinsicType.GET_AND_SET -> emitGetAndSet(callSite, args, resultSlot)
IntrinsicType.GET_AND_ADD -> emitGetAndAdd(callSite, args)
IntrinsicType.GET_CONTINUATION,
IntrinsicType.RETURN_IF_SUSPENDED,
IntrinsicType.INTEROP_BITS_TO_FLOAT,
@@ -253,7 +269,11 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv
IntrinsicType.INTEROP_CONVERT,
IntrinsicType.ENUM_VALUES,
IntrinsicType.ENUM_VALUE_OF,
IntrinsicType.WORKER_EXECUTE ->
IntrinsicType.WORKER_EXECUTE,
IntrinsicType.COMPARE_AND_SET_FIELD,
IntrinsicType.COMPARE_AND_SWAP_FIELD,
IntrinsicType.GET_AND_SET_FIELD,
IntrinsicType.GET_AND_ADD_FIELD ->
reportNonLoweredIntrinsic(intrinsicType)
IntrinsicType.INIT_INSTANCE,
IntrinsicType.OBJC_INIT_BY,
@@ -292,6 +312,65 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv
private fun FunctionGenerationContext.emitIsExperimentalMM(): LLVMValueRef =
llvm.int1(context.memoryModel == MemoryModel.EXPERIMENTAL)
// cmpxcgh llvm instruction return pair. idnex is index of required element of this pair
enum class CmpExchangeMode(val index:Int) {
SWAP(0),
SET(1)
}
private fun FunctionGenerationContext.emitCmpExchange(callSite: IrCall, args: List<LLVMValueRef>, mode: CmpExchangeMode, resultSlot: LLVMValueRef?): LLVMValueRef {
val field = context.mapping.functionToVolatileField[callSite.symbol.owner]!!
require(args.size == 3)
val address = environment.getObjectFieldPointer(args[0], field)
return if (isObjectRef(args[1])) {
require(context.memoryModel == MemoryModel.EXPERIMENTAL)
when (mode) {
CmpExchangeMode.SET -> call(llvm.CompareAndSetVolatileHeapRef, listOf(address, args[1], args[2]))
CmpExchangeMode.SWAP -> call(llvm.CompareAndSwapVolatileHeapRef, listOf(address, args[1], args[2]),
environment.calculateLifetime(callSite), resultSlot = resultSlot)
}
} else {
val cmp = LLVMBuildAtomicCmpXchg(builder, address, args[1], args[2],
LLVMAtomicOrdering.LLVMAtomicOrderingSequentiallyConsistent,
LLVMAtomicOrdering.LLVMAtomicOrderingSequentiallyConsistent,
SingleThread = 0
)!!
LLVMBuildExtractValue(builder, cmp, mode.index, "")!!
}
}
private fun FunctionGenerationContext.emitAtomicRMW(callSite: IrCall, args: List<LLVMValueRef>, op: LLVMAtomicRMWBinOp, resultSlot: LLVMValueRef?): LLVMValueRef {
val field = context.mapping.functionToVolatileField[callSite.symbol.owner]!!
require(args.size == 2)
val address = environment.getObjectFieldPointer(args[0], field)
return if (isObjectRef(args[1])) {
require(op == LLVMAtomicRMWBinOp.LLVMAtomicRMWBinOpXchg)
require(context.memoryModel == MemoryModel.EXPERIMENTAL)
call(llvm.GetAndSetVolatileHeapRef, listOf(address, args[1]),
environment.calculateLifetime(callSite), resultSlot = resultSlot)
} else {
LLVMBuildAtomicRMW(builder, op, address, args[1],
LLVMAtomicOrdering.LLVMAtomicOrderingSequentiallyConsistent,
singleThread = 0
)!!
}
}
private fun FunctionGenerationContext.emitCompareAndSet(callSite: IrCall, args: List<LLVMValueRef>): LLVMValueRef {
return emitCmpExchange(callSite, args, CmpExchangeMode.SET, null)
}
private fun FunctionGenerationContext.emitCompareAndSwap(callSite: IrCall, args: List<LLVMValueRef>, resultSlot: LLVMValueRef?): LLVMValueRef {
return emitCmpExchange(callSite, args, CmpExchangeMode.SWAP, resultSlot)
}
private fun FunctionGenerationContext.emitGetAndSet(callSite: IrCall, args: List<LLVMValueRef>, resultSlot: LLVMValueRef?): LLVMValueRef {
return emitAtomicRMW(callSite, args, LLVMAtomicRMWBinOp.LLVMAtomicRMWBinOpXchg, resultSlot)
}
private fun FunctionGenerationContext.emitGetAndAdd(callSite: IrCall, args: List<LLVMValueRef>): LLVMValueRef {
return emitAtomicRMW(callSite, args, LLVMAtomicRMWBinOp.LLVMAtomicRMWBinOpAdd, null)
}
private fun FunctionGenerationContext.emitGetNativeNullPtr(): LLVMValueRef =
llvm.kNullInt8Ptr
@@ -228,6 +228,9 @@ internal class CodeGeneratorVisitor(val generationState: NativeGenerationState,
override fun evaluateExpression(value: IrExpression, resultSlot: LLVMValueRef?): LLVMValueRef =
this@CodeGeneratorVisitor.evaluateExpression(value, resultSlot)
override fun getObjectFieldPointer(thisRef: LLVMValueRef, field: IrField): LLVMValueRef =
this@CodeGeneratorVisitor.fieldPtrOfClass(thisRef, field)
}
private val intrinsicGenerator = IntrinsicGenerator(intrinsicGeneratorEnvironment)
@@ -1660,35 +1663,46 @@ internal class CodeGeneratorVisitor(val generationState: NativeGenerationState,
private fun evaluateGetField(value: IrGetField, resultSlot: LLVMValueRef?): LLVMValueRef {
context.log { "evaluateGetField : ${ir2string(value)}" }
return if (!value.symbol.owner.isStatic) {
val thisPtr = evaluateExpression(value.receiver!!)
functionGenerationContext.loadSlot(
fieldPtrOfClass(thisPtr, value.symbol.owner), !value.symbol.owner.isFinal, resultSlot)
} else {
assert(value.receiver == null)
if (value.symbol.owner.correspondingPropertySymbol?.owner?.isConst == true) {
evaluateConst(value.symbol.owner.initializer?.expression as IrConst<*>).llvm
} else {
val alignment = when {
value.type.classifierOrNull?.isClassWithFqName(vectorType) == true -> 8
else -> null
}
val order = when {
value.symbol.owner.hasAnnotation(KonanFqNames.volatile) ->
LLVMAtomicOrdering.LLVMAtomicOrderingSequentiallyConsistent
else -> null
}
val fieldAddress = when {
!value.symbol.owner.isStatic -> {
fieldPtrOfClass(evaluateExpression(value.receiver!!), value.symbol.owner)
}
value.symbol.owner.correspondingPropertySymbol?.owner?.isConst == true -> {
// TODO: probably can be removed, as they are inlined.
return evaluateConst(value.symbol.owner.initializer?.expression as IrConst<*>).llvm
}
else -> {
if (context.config.threadsAreAllowed && value.symbol.owner.isGlobalNonPrimitive(context)) {
functionGenerationContext.checkGlobalsAccessible(currentCodeContext.exceptionHandler)
}
val ptr = generationState.llvmDeclarations.forStaticField(value.symbol.owner).storageAddressAccess.getAddress(
functionGenerationContext
)
functionGenerationContext.loadSlot(ptr, !value.symbol.owner.isFinal, resultSlot)
generationState.llvmDeclarations
.forStaticField(value.symbol.owner)
.storageAddressAccess
.getAddress(functionGenerationContext)
}
}.also {
if (value.type.classifierOrNull?.isClassWithFqName(vectorType) == true)
LLVMSetAlignment(it, 8)
}
return functionGenerationContext.loadSlot(
fieldAddress, !value.symbol.owner.isFinal, resultSlot,
memoryOrder = order,
alignment = alignment
)
}
//-------------------------------------------------------------------------//
private fun needMutationCheck(irClass: IrClass): Boolean {
private fun needMutationCheck(irField: IrField): Boolean {
// For now we omit mutation checks on immutable types, as this allows initialization in constructor
// and it is assumed that API doesn't allow to change them.
return context.config.freezing.enableFreezeChecks && !irClass.isFrozen(context)
return context.config.freezing.enableFreezeChecks && !irField.parentAsClass.isFrozen(context) && !irField.hasAnnotation(KonanFqNames.volatile)
}
private fun needLifetimeConstraintsCheck(valueToAssign: LLVMValueRef, irClass: IrClass): Boolean {
@@ -1724,13 +1738,14 @@ internal class CodeGeneratorVisitor(val generationState: NativeGenerationState,
}
val valueToAssign = evaluateExpression(value.value)
val store = if (!value.symbol.owner.isStatic) {
val address: LLVMValueRef
if (!value.symbol.owner.isStatic) {
val thisPtr = evaluateExpression(value.receiver!!)
assert(thisPtr.type == codegen.kObjHeaderPtr) {
LLVMPrintTypeToString(thisPtr.type)?.toKString().toString()
}
val parentAsClass = value.symbol.owner.parentAsClass
if (needMutationCheck(parentAsClass)) {
if (needMutationCheck(value.symbol.owner)) {
functionGenerationContext.call(llvm.mutationCheck,
listOf(functionGenerationContext.bitcast(codegen.kObjHeaderPtr, thisPtr)),
Lifetime.IRRELEVANT, currentCodeContext.exceptionHandler)
@@ -1738,21 +1753,26 @@ internal class CodeGeneratorVisitor(val generationState: NativeGenerationState,
if (needLifetimeConstraintsCheck(valueToAssign, parentAsClass)) {
functionGenerationContext.call(llvm.checkLifetimesConstraint, listOf(thisPtr, valueToAssign))
}
functionGenerationContext.storeAny(valueToAssign, fieldPtrOfClass(thisPtr, value.symbol.owner), false)
address = fieldPtrOfClass(thisPtr, value.symbol.owner)
} else {
assert(value.receiver == null)
val globalAddress = generationState.llvmDeclarations.forStaticField(value.symbol.owner).storageAddressAccess.getAddress(
functionGenerationContext
)
if (context.config.threadsAreAllowed && value.symbol.owner.storageKind(context) == FieldStorageKind.GLOBAL)
functionGenerationContext.checkGlobalsAccessible(currentCodeContext.exceptionHandler)
if (value.symbol.owner.shouldBeFrozen(context) && value.origin != ObjectClassLowering.IrStatementOriginFieldPreInit)
functionGenerationContext.freeze(valueToAssign, currentCodeContext.exceptionHandler)
functionGenerationContext.storeAny(valueToAssign, globalAddress, false)
address = generationState.llvmDeclarations.forStaticField(value.symbol.owner).storageAddressAccess.getAddress(
functionGenerationContext
)
}
if (store != null && value.value.type.classifierOrNull?.isClassWithFqName(vectorType) == true) {
LLVMSetAlignment(store, 8)
val alignment = when {
value.value.type.classifierOrNull?.isClassWithFqName(vectorType) == true -> 8
else -> null
}
functionGenerationContext.storeAny(
valueToAssign, address, false,
isVolatile = value.symbol.owner.hasAnnotation(KonanFqNames.volatile),
alignment = alignment,
)
assert (value.type.isUnit())
return codegen.theUnitInstanceRef.llvm
@@ -2379,8 +2399,7 @@ internal class CodeGeneratorVisitor(val generationState: NativeGenerationState,
val bbExit = basicBlock("label_continue", null)
moveBlockAfterEntry(bbExit)
moveBlockAfterEntry(bbInit)
val state = load(statePtr)
LLVMSetOrdering(state, LLVMAtomicOrdering.LLVMAtomicOrderingAcquire)
val state = load(statePtr, memoryOrder = LLVMAtomicOrdering.LLVMAtomicOrderingAcquire)
condBr(icmpEq(state, llvm.int32(FILE_INITIALIZED)), bbExit, bbInit)
positionAtEnd(bbInit)
call(llvm.callInitGlobalPossiblyLock, listOf(statePtr, initializerPtr),
@@ -0,0 +1,171 @@
/*
* 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.ir.addDispatchReceiver
import org.jetbrains.kotlin.backend.common.lower.*
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.NativeMapping
import org.jetbrains.kotlin.backend.konan.ir.buildSimpleAnnotation
import org.jetbrains.kotlin.backend.konan.llvm.IntrinsicType
import org.jetbrains.kotlin.backend.konan.llvm.tryGetIntrinsicType
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.builders.irString
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.util.capitalizeDecapitalize.*
object IR_DECLARATION_ORIGIN_VOLATILE : IrDeclarationOriginImpl("VOLATILE")
internal class VolatileFieldsLowering(val context: Context) : FileLoweringPass {
private fun buildIntrinsicFunction(irField: IrField, intrinsicType: IntrinsicType, builder: IrSimpleFunction.() -> Unit) = context.irFactory.buildFun {
isExternal = true
origin = IR_DECLARATION_ORIGIN_VOLATILE
name = Name.special("<${intrinsicType.name.decapitalizeSmart()}-${irField.name}>")
startOffset = irField.startOffset
endOffset = irField.endOffset
}.apply {
val parentClass = irField.parents.filterIsInstance<IrClass>().first()
parent = parentClass
addDispatchReceiver {
startOffset = irField.startOffset
endOffset = irField.endOffset
type = parentClass.defaultType
}
builder()
annotations += buildSimpleAnnotation(context.irBuiltIns,
SYNTHETIC_OFFSET, SYNTHETIC_OFFSET,
context.ir.symbols.typedIntrinsic.owner, intrinsicType.name)
}
private fun buildCasFunction(irField: IrField, intrinsicType: IntrinsicType, functionReturnType: IrType) =
buildIntrinsicFunction(irField, intrinsicType ) {
returnType = functionReturnType
addValueParameter {
startOffset = irField.startOffset
endOffset = irField.endOffset
name = Name.identifier("expectedValue")
type = irField.type
}
addValueParameter {
startOffset = irField.startOffset
endOffset = irField.endOffset
name = Name.identifier("newValue")
type = irField.type
}
}
private fun buildAtomicRWMFunction(irField: IrField, intrinsicType: IntrinsicType) =
buildIntrinsicFunction(irField, intrinsicType) {
returnType = irField.type
addValueParameter {
startOffset = irField.startOffset
endOffset = irField.endOffset
name = Name.identifier("value")
type = irField.type
}
}
private inline fun atomicFunction(irField: IrField, type: NativeMapping.AtomicFunctionType, builder: () -> IrSimpleFunction): IrSimpleFunction {
val key = NativeMapping.AtomicFunctionKey(irField, type)
return context.mapping.volatileFieldToAtomicFunction.getOrPut(key) {
builder().also {
context.mapping.functionToVolatileField[it] = irField
}
}
}
private fun compareAndSetFunction(irField: IrField) = atomicFunction(irField, NativeMapping.AtomicFunctionType.COMPARE_AND_SET) {
this.buildCasFunction(irField, IntrinsicType.COMPARE_AND_SET, this.context.irBuiltIns.booleanType)
}
private fun compareAndSwapFunction(irField: IrField) = atomicFunction(irField, NativeMapping.AtomicFunctionType.COMPARE_AND_SWAP) {
this.buildCasFunction(irField, IntrinsicType.COMPARE_AND_SWAP, irField.type)
}
private fun getAndSetFunction(irField: IrField) = atomicFunction(irField, NativeMapping.AtomicFunctionType.GET_AND_SET) {
this.buildAtomicRWMFunction(irField, IntrinsicType.GET_AND_SET)
}
private fun getAndAddFunction(irField: IrField) = atomicFunction(irField, NativeMapping.AtomicFunctionType.GET_AND_ADD) {
this.buildAtomicRWMFunction(irField, IntrinsicType.GET_AND_ADD)
}
private fun IrField.isInteger() = type == context.irBuiltIns.intType ||
type == context.irBuiltIns.longType ||
type == context.irBuiltIns.shortType ||
type == context.irBuiltIns.byteType
override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid(object : IrBuildingTransformer(context) {
override fun visitClass(declaration: IrClass): IrStatement {
declaration.declarations.transformFlat {
when {
it !is IrProperty -> null
it.backingField?.hasAnnotation(KonanFqNames.volatile) != true -> null
else -> {
val field = it.backingField!!
if (field.type.binaryTypeIsReference() && context.memoryModel != MemoryModel.EXPERIMENTAL) {
it.annotations = it.annotations.filterNot { it.symbol.owner.parentAsClass.hasEqualFqName(KonanFqNames.volatile) }
null
} else {
listOfNotNull(it,
compareAndSetFunction(field),
compareAndSwapFunction(field),
getAndSetFunction(field),
if (field.isInteger()) getAndAddFunction(field) else null
)
}
}
}
}
declaration.transformChildrenVoid()
return declaration
}
private fun unsupported(message: String) = builder.irCall(context.ir.symbols.throwIllegalArgumentExceptionWithMessage).apply {
putValueArgument(0, builder.irString(message))
}
private val intrinsicMap = mapOf(
IntrinsicType.COMPARE_AND_SET_FIELD to ::compareAndSetFunction,
IntrinsicType.COMPARE_AND_SWAP_FIELD to ::compareAndSwapFunction,
IntrinsicType.GET_AND_SET_FIELD to ::getAndSetFunction,
IntrinsicType.GET_AND_ADD_FIELD to ::getAndAddFunction,
)
override fun visitCall(expression: IrCall): IrExpression {
expression.transformChildrenVoid(this)
val intrinsicType = tryGetIntrinsicType(expression).takeIf { it in intrinsicMap } ?: return expression
builder.at(expression)
val reference = expression.getValueArgument(0) as? IrPropertyReference
?: return unsupported("Only compile-time known IrProperties supported for $intrinsicType")
val property = reference.symbol.owner
val backingField = property.backingField
if (backingField?.type?.binaryTypeIsReference() == true && context.memoryModel != MemoryModel.EXPERIMENTAL) {
return unsupported("Only primitives are supported for $intrinsicType with legacy memory model")
}
if (backingField?.hasAnnotation(KonanFqNames.volatile) != true) {
return unsupported("Only volatile properties are supported for $intrinsicType")
}
val function = intrinsicMap[intrinsicType]!!(backingField)
return builder.irCall(function).apply {
dispatchReceiver = expression.extensionReceiver
putValueArgument(0, expression.getValueArgument(1))
if (intrinsicType == IntrinsicType.COMPARE_AND_SET_FIELD || intrinsicType == IntrinsicType.COMPARE_AND_SWAP_FIELD) {
putValueArgument(1, expression.getValueArgument(2))
}
}
}
})
}
}
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.backend.konan.llvm.*
internal class ExternalModulesDFG(val allTypes: List<DataFlowIR.Type.Declared>,
val publicTypes: Map<Long, DataFlowIR.Type.Public>,
@@ -258,7 +259,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
context.logMultiple {
+"SYMBOL TABLE:"
symbolTable.classMap.forEach { irClass, type ->
symbolTable.classMap.forEach { (irClass, type) ->
+" DESCRIPTOR: ${irClass.descriptor}"
+" TYPE: $type"
if (type !is DataFlowIR.Type.Declared)
@@ -309,39 +310,61 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
expressions += expression to currentLoop
}
if (expression is IrCall && expression.symbol == initInstanceSymbol) {
// Skip the constructor call as initInstance is handled specially later.
val thiz = expression.getValueArgument(0)!!
val constructorCall = expression.getValueArgument(1)!!
thiz.acceptVoid(this)
constructorCall.acceptChildrenVoid(this)
return
if (expression is IrCall) {
if (expression.symbol == initInstanceSymbol) {
// Skip the constructor call as initInstance is handled specially later.
val thiz = expression.getValueArgument(0)!!
val constructorCall = expression.getValueArgument(1)!!
thiz.acceptVoid(this)
constructorCall.acceptChildrenVoid(this)
return
}
if (expression.symbol == executeImplSymbol) {
// Producer and job of executeImpl are called externally, we need to reflect this somehow.
val producerInvocation = IrCallImpl.fromSymbolDescriptor(expression.startOffset, expression.endOffset,
executeImplProducerInvoke.returnType,
executeImplProducerInvoke.symbol,
executeImplProducerInvoke.symbol.owner.typeParameters.size,
executeImplProducerInvoke.symbol.owner.valueParameters.size,
STATEMENT_ORIGIN_PRODUCER_INVOCATION)
producerInvocation.dispatchReceiver = expression.getValueArgument(2)
expressions += producerInvocation to currentLoop
val jobFunctionReference = expression.getValueArgument(3) as? IrFunctionReference
?: error("A function reference expected")
val jobInvocation = IrCallImpl.fromSymbolDescriptor(expression.startOffset, expression.endOffset,
jobFunctionReference.symbol.owner.returnType,
jobFunctionReference.symbol as IrSimpleFunctionSymbol,
jobFunctionReference.symbol.owner.typeParameters.size,
jobFunctionReference.symbol.owner.valueParameters.size,
STATEMENT_ORIGIN_JOB_INVOCATION)
jobInvocation.putValueArgument(0, producerInvocation)
expressions += jobInvocation to currentLoop
}
val intrinsicType = tryGetIntrinsicType(expression)
if (intrinsicType == IntrinsicType.COMPARE_AND_SET || intrinsicType == IntrinsicType.COMPARE_AND_SWAP) {
expressions += IrSetFieldImpl(
expression.startOffset, expression.endOffset,
context.mapping.functionToVolatileField[expression.symbol.owner]!!.symbol,
expression.dispatchReceiver,
expression.getValueArgument(1)!!,
context.irBuiltIns.unitType
) to currentLoop
}
if (intrinsicType == IntrinsicType.GET_AND_SET) {
expressions += IrSetFieldImpl(
expression.startOffset, expression.endOffset,
context.mapping.functionToVolatileField[expression.symbol.owner]!!.symbol,
expression.dispatchReceiver,
expression.getValueArgument(0)!!,
context.irBuiltIns.unitType
) to currentLoop
}
}
if (expression is IrCall && expression.symbol == executeImplSymbol) {
// Producer and job of executeImpl are called externally, we need to reflect this somehow.
val producerInvocation = IrCallImpl.fromSymbolDescriptor(expression.startOffset, expression.endOffset,
executeImplProducerInvoke.returnType,
executeImplProducerInvoke.symbol,
executeImplProducerInvoke.symbol.owner.typeParameters.size,
executeImplProducerInvoke.symbol.owner.valueParameters.size,
STATEMENT_ORIGIN_PRODUCER_INVOCATION)
producerInvocation.dispatchReceiver = expression.getValueArgument(2)
expressions += producerInvocation to currentLoop
val jobFunctionReference = expression.getValueArgument(3) as? IrFunctionReference
?: error("A function reference expected")
val jobInvocation = IrCallImpl.fromSymbolDescriptor(expression.startOffset, expression.endOffset,
jobFunctionReference.symbol.owner.returnType,
jobFunctionReference.symbol as IrSimpleFunctionSymbol,
jobFunctionReference.symbol.owner.typeParameters.size,
jobFunctionReference.symbol.owner.valueParameters.size,
STATEMENT_ORIGIN_JOB_INVOCATION)
jobInvocation.putValueArgument(0, producerInvocation)
expressions += jobInvocation to currentLoop
}
// TODO: A little bit hacky but it is the simplest solution.
// See ObjC instanceOf code generation for details.