Skip to content

Commit

Permalink
prototype #1
Browse files Browse the repository at this point in the history
  • Loading branch information
dimonchik0036 committed Jan 12, 2024
1 parent 84727f8 commit 6139587
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 63 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2024 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.analysis.low.level.api.fir.file.builder

enum class EnterResult {
CYCLE,
SUCCESS,
;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,16 @@

package org.jetbrains.kotlin.analysis.low.level.api.fir.file.builder

import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.registry.Registry
import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.LLFirLazyResolveContractChecker
import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.llFirSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.checkCanceled
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.errorWithFirSpecificEntries
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.lockWithPCECheck
import org.jetbrains.kotlin.fir.FirElementWithResolveState
import org.jetbrains.kotlin.fir.declarations.*
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater
import java.util.concurrent.locks.ReentrantLock
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

/**
* Keyed locks provider.
Expand All @@ -42,6 +37,8 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh
phase: FirResolvePhase,
action: () -> Unit,
) {
if (!implicitPhaseLockEnabled) return action()

val lock = when (phase) {
FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE -> implicitTypesLock
else -> null
Expand Down Expand Up @@ -109,17 +106,7 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh
action: () -> Unit,
) {
checker.lazyResolveToPhaseInside(phase) {
target.withCriticalSection(toPhase = phase, updatePhase = updatePhase, action = action)
}
}

inline fun withJumpingLock(
target: FirElementWithResolveState,
phase: FirResolvePhase,
action: () -> Unit,
) {
checker.lazyResolveToPhaseInside(phase, isJumpingPhase = true) {
target.withCriticalSection(toPhase = phase, updatePhase = true, action = action)
target.withNonJumpingLock(toPhase = phase, updatePhase = updatePhase, action = action)
}
}

Expand All @@ -143,7 +130,7 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh
* - If some other thread tries to resolve current [FirElementWithResolveState], it changes `resolveState` and puts the barrier there. Then it awaits on it until the initial thread which hold the lock finishes its job.
* - This way, no barrier is used in a case when no contention arise.
*/
private inline fun FirElementWithResolveState.withCriticalSection(
private inline fun FirElementWithResolveState.withNonJumpingLock(
toPhase: FirResolvePhase,
updatePhase: Boolean,
action: () -> Unit,
Expand Down Expand Up @@ -172,7 +159,7 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh
}

is FirResolvedToPhaseState -> {
if (!tryLock(toPhase, stateSnapshot)) continue
if (!tryNonJumpingLock(toPhase, stateSnapshot)) continue

var exceptionOccurred = false
try {
Expand All @@ -182,11 +169,15 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh
throw e
} finally {
val newPhase = if (updatePhase && !exceptionOccurred) toPhase else stateSnapshot.resolvePhase
unlock(toPhase = newPhase)
nonJumpingUnlock(toPhase = newPhase)
}

return
}

is FirInProcessOfResolvingToJumpingPhaseState -> {
errorWithFirSpecificEntries("$stateSnapshot state are not allowed to be inside non-jumping lock", fir = this)
}
}
}
}
Expand All @@ -201,30 +192,135 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh
toPhase: FirResolvePhase,
stateSnapshot: FirResolveState,
) {
val latch = CountDownLatch(1)
val newState = FirInProcessOfResolvingToPhaseStateWithBarrier(toPhase, latch)
val newState = FirInProcessOfResolvingToPhaseStateWithBarrier(toPhase)
resolveStateFieldUpdater.compareAndSet(this, stateSnapshot, newState)
}

private fun FirElementWithResolveState.tryLock(
private fun FirElementWithResolveState.tryNonJumpingLock(
toPhase: FirResolvePhase,
stateSnapshot: FirResolveState,
): Boolean {
val newState = FirInProcessOfResolvingToPhaseStateWithoutBarrier(toPhase)
return resolveStateFieldUpdater.compareAndSet(this, stateSnapshot, newState)
}

private fun FirElementWithResolveState.unlock(toPhase: FirResolvePhase) {
private fun FirElementWithResolveState.nonJumpingUnlock(toPhase: FirResolvePhase) {
when (val stateSnapshotAfter = resolveStateFieldUpdater.getAndSet(this, FirResolvedToPhaseState(toPhase))) {
is FirInProcessOfResolvingToPhaseStateWithoutBarrier -> {}
is FirInProcessOfResolvingToPhaseStateWithBarrier -> {
stateSnapshotAfter.barrier.countDown()
}
is FirResolvedToPhaseState -> {
error("phase is unexpectedly unlocked $stateSnapshotAfter")
is FirResolvedToPhaseState, is FirInProcessOfResolvingToJumpingPhaseState -> {
errorWithFirSpecificEntries("phase is unexpectedly unlocked $stateSnapshotAfter", fir = this)
}
}
}

inline fun withJumpingLock(
target: FirElementWithResolveState,
phase: FirResolvePhase,
session: FirJumpingResolveSession,
actionUnderLock: () -> Unit,
cycleAction: () -> Unit,
) {
checker.lazyResolveToPhaseInside(phase, isJumpingPhase = true) {
target.withJumpingLockImpl(phase, session, actionUnderLock, cycleAction)
}
}

private inline fun FirElementWithResolveState.withJumpingLockImpl(
toPhase: FirResolvePhase,
session: FirJumpingResolveSession,
actionUnderLock: () -> Unit,
cycleAction: () -> Unit,
) {
while (true) {
checkCanceled()

@OptIn(ResolveStateAccess::class)
val stateSnapshot = resolveState
if (stateSnapshot.resolvePhase >= toPhase) {
// already resolved by some other thread
return
}

when (stateSnapshot) {
is FirResolvedToPhaseState -> {
if (!tryJumpingLock(toPhase, stateSnapshot, session)) continue

var exceptionOccurred = false
try {
actionUnderLock()
} catch (e: Throwable) {
exceptionOccurred = true
throw e
} finally {
val newPhase = if (!exceptionOccurred) toPhase else stateSnapshot.resolvePhase
jumpingUnlock(toPhase = newPhase, session)
}

return
}

is FirInProcessOfResolvingToJumpingPhaseState -> {
val previousState = session.states.lastOrNull()
if (previousState != null) {
// Check for multi-thread cycles
// all writes to waitingFor will be consistent, as it is the last write if we have cycle
previousState.waitingFor = stateSnapshot
var next: FirInProcessOfResolvingToJumpingPhaseState? = stateSnapshot
while (next != null) {
if (next === previousState) {
previousState.waitingFor = null

// Do I need it???
// previousState.latch.countDown()

return cycleAction()
}

next = next.waitingFor
}
}

try {
stateSnapshot.latch.await(DEFAULT_LOCKING_INTERVAL, TimeUnit.MILLISECONDS)
} finally {
previousState?.waitingFor = null
}
}

is FirInProcessOfResolvingToPhaseStateWithoutBarrier, is FirInProcessOfResolvingToPhaseStateWithBarrier -> {
errorWithFirSpecificEntries("$stateSnapshot state are not allowed to be inside non-jumping lock", fir = this)
}
}
}
}

private fun FirElementWithResolveState.tryJumpingLock(
toPhase: FirResolvePhase,
stateSnapshot: FirResolveState,
session: FirJumpingResolveSession,
): Boolean {
val newState = FirInProcessOfResolvingToJumpingPhaseState(toPhase)
val isSucceed = resolveStateFieldUpdater.compareAndSet(this, stateSnapshot, newState)
if (!isSucceed) return false

session.states.lastOrNull()?.waitingFor = newState
session.states += newState

return true
}

private fun FirElementWithResolveState.jumpingUnlock(toPhase: FirResolvePhase, session: FirJumpingResolveSession) {
val currentState = session.states.removeLast()
val prevState = session.states.lastOrNull()
require(prevState == null || prevState.waitingFor == currentState)
prevState?.waitingFor = null

resolveStateFieldUpdater.set(this, FirResolvedToPhaseState(toPhase))
currentState.latch.countDown()
}
}

private val resolveStateFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(
Expand All @@ -237,4 +333,8 @@ private val globalLockEnabled: Boolean by lazy(LazyThreadSafetyMode.PUBLICATION)
Registry.`is`("kotlin.parallel.resolve.under.global.lock", false)
}

private val implicitPhaseLockEnabled: Boolean by lazy(LazyThreadSafetyMode.PUBLICATION) {
Registry.`is`("kotlin.implicit.resolve.phase.under.global.lock", false)
}

private const val DEFAULT_LOCKING_INTERVAL = 50L
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ private class LLFirBodyTargetResolver(

// resolve class CFG graph here, to do this we need to have property & init blocks resoled
resolveMembersForControlFlowGraph(target)
performCustomResolveUnderLock(target) {
performCustomResolveUnderNonJumpingWriteLock(target) {
calculateControlFlowGraph(target)
}

Expand All @@ -143,7 +143,7 @@ private class LLFirBodyTargetResolver(

// resolve file CFG graph here, to do this we need to have property blocks resoled
resolveMembersForControlFlowGraph(target)
performCustomResolveUnderLock(target) {
performCustomResolveUnderNonJumpingWriteLock(target) {
calculateControlFlowGraph(target)
}

Expand All @@ -157,7 +157,7 @@ private class LLFirBodyTargetResolver(
}
is FirCodeFragment -> {
resolveCodeFragmentContext(target)
performCustomResolveUnderLock(target) {
performCustomResolveUnderNonJumpingWriteLock(target) {
resolve(target, BodyStateKeepers.CODE_FRAGMENT)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ private class LLFirCompilerRequiredAnnotationsTargetResolver(
lockProvider: LLFirLockProvider,
scopeSession: ScopeSession,
computationSession: LLFirCompilerRequiredAnnotationsComputationSession? = null,
) : LLFirTargetResolver(target, lockProvider, FirResolvePhase.COMPILER_REQUIRED_ANNOTATIONS, isJumpingPhase = false) {
) : LLFirTargetResolver(target, lockProvider, FirResolvePhase.COMPILER_REQUIRED_ANNOTATIONS) {
inner class LLFirCompilerRequiredAnnotationsComputationSession : CompilerRequiredAnnotationsComputationSession() {
override fun resolveAnnotationSymbol(symbol: FirRegularClassSymbol, scopeSession: ScopeSession) {
val regularClass = symbol.fir
Expand Down Expand Up @@ -134,7 +134,7 @@ private class LLFirCompilerRequiredAnnotationsTargetResolver(

// 4. Exit if there are no applicable annotations, so we can just update the phase
if (annotationTransformer.isNothingToResolve()) {
return performCustomResolveUnderLock(target) {
return performCustomResolveUnderNonJumpingWriteLock(target) {
// just update deprecations
annotationTransformer.publishResult(target)
}
Expand All @@ -150,7 +150,7 @@ private class LLFirCompilerRequiredAnnotationsTargetResolver(
annotationTransformer.calculateDeprecations(target)

// 8. Publish result
performCustomResolveUnderLock(target) {
performCustomResolveUnderNonJumpingWriteLock(target) {
annotationTransformer.publishResult(target)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.types.FirImplicitTypeRef
import org.jetbrains.kotlin.fir.util.setMultimapOf
import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry
import org.jetbrains.kotlin.fir.utils.exceptions.withFirSymbolEntry
import org.jetbrains.kotlin.fir.visitors.transformSingle
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
import org.jetbrains.kotlin.utils.exceptions.requireWithAttachment

internal object LLFirImplicitTypesLazyResolver : LLFirLazyResolver(FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE) {
override fun resolve(
Expand All @@ -47,7 +49,7 @@ internal object LLFirImplicitTypesLazyResolver : LLFirLazyResolver(FirResolvePha
}
}

internal class LLImplicitBodyResolveComputationSession : ImplicitBodyResolveComputationSession() {
internal class LLImplicitBodyResolveComputationSession : ImplicitBodyResolveComputationSession(), FirJumpingResolveSession {
/**
* The symbol on which foreign annotations will be postponed
*
Expand Down Expand Up @@ -105,6 +107,17 @@ internal class LLImplicitBodyResolveComputationSession : ImplicitBodyResolveComp
fun postponedSymbols(target: FirCallableDeclaration): Collection<FirBasedSymbol<*>> {
return postponedSymbols[target.symbol]
}

override val states: MutableList<FirInProcessOfResolvingToJumpingPhaseState> = mutableListOf()

private var cycledSymbol: FirCallableSymbol<*>? = null

fun pushCycledSymbol(symbol: FirCallableSymbol<*>) {
requireWithAttachment(cycledSymbol == null, { "Nested recursion is not allowed" })
cycledSymbol = symbol
}

fun popCycledSymbol(): FirCallableSymbol<*>? = cycledSymbol?.also { cycledSymbol = null }
}

internal class LLFirImplicitBodyTargetResolver(
Expand Down Expand Up @@ -139,6 +152,16 @@ internal class LLFirImplicitBodyTargetResolver(
}
}

override val jumpingResolveSession: FirJumpingResolveSession get() = llImplicitBodyResolveComputationSession

override fun handleResolutionCycle(target: FirElementWithResolveState) {
requireWithAttachment(target is FirCallableDeclaration, { "Resolution cycle is supposed to be only for callable declaration" }) {
withFirEntry("target", target)
}

llImplicitBodyResolveComputationSession.pushCycledSymbol(target.symbol)
}

override fun doLazyResolveUnderLock(target: FirElementWithResolveState) {
when {
target is FirCallableDeclaration && target.isCopyCreatedInScope -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,23 @@ internal class LLFirReturnTypeCalculatorWithJump(

val designation = declaration.collectDesignationWithFile().asResolveTarget()
val targetSession = designation.target.llFirSession
val computationSession = implicitBodyResolveComputationSession as LLImplicitBodyResolveComputationSession
val resolver = LLFirImplicitBodyTargetResolver(
designation,
lockProvider = lockProvider,
scopeSession = targetSession.getScopeSession(),
firResolveContextCollector = towerDataContextCollector,
llImplicitBodyResolveComputationSessionParameter = implicitBodyResolveComputationSession as LLImplicitBodyResolveComputationSession,
llImplicitBodyResolveComputationSessionParameter = computationSession,
)

lockProvider.withGlobalPhaseLock(FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE) {
resolver.resolveDesignation()
}

if (computationSession.popCycledSymbol() == declaration.symbol) {
return recursionInImplicitTypeRef()
}

LLFirImplicitTypesLazyResolver.checkIsResolved(designation)
return declaration.returnTypeRef as FirResolvedTypeRef
}
Expand Down
Loading

0 comments on commit 6139587

Please sign in to comment.