[LL FIR] split LLFirLockProvider on read and write API

^KT-56550
This commit is contained in:
Dmitrii Gridin
2023-05-08 14:59:05 +02:00
committed by Space Team
parent 76df0f9ad1
commit 13dc4c8014
3 changed files with 66 additions and 15 deletions
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.file.builder
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.LLFirLazyResolveContractChecker import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.LLFirLazyResolveContractChecker
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.checkCanceled import org.jetbrains.kotlin.analysis.low.level.api.fir.util.checkCanceled
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.checkPhase
import org.jetbrains.kotlin.analysis.low.level.api.fir.util.lockWithPCECheck import org.jetbrains.kotlin.analysis.low.level.api.fir.util.lockWithPCECheck
import org.jetbrains.kotlin.fir.FirElementWithResolveState import org.jetbrains.kotlin.fir.FirElementWithResolveState
import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.*
@@ -68,13 +67,47 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh
} }
} }
inline fun withLock( /**
* Locks an a [FirElementWithResolveState] to resolve from `phase - 1` to [phase] and
* then updates the [FirElementWithResolveState.resolveState] to a [phase].
* Does nothing if [target] already has at least [phase] phase.
*
* [action] will be executed once if [target] is not yet resolved to [phase] phase.
*
* @see withReadLock
*/
inline fun withWriteLock(
target: FirElementWithResolveState, target: FirElementWithResolveState,
phase: FirResolvePhase, phase: FirResolvePhase,
action: () -> Unit action: () -> Unit
) {
withLock(target, phase, updatePhase = true, action)
}
/**
* Locks an a [FirElementWithResolveState] to read something required for [phase].
* Does nothing if [target] already has at least [phase] phase.
*
* [action] will be executed once if [target] is not yet resolved to [phase] phase.
*
* @see withWriteLock
*/
inline fun withReadLock(
target: FirElementWithResolveState,
phase: FirResolvePhase,
action: () -> Unit
) {
withLock(target, phase, updatePhase = false, action)
}
private inline fun withLock(
target: FirElementWithResolveState,
phase: FirResolvePhase,
updatePhase: Boolean,
action: () -> Unit
) { ) {
checker.lazyResolveToPhaseInside(phase) { checker.lazyResolveToPhaseInside(phase) {
target.withCriticalSection(phase, action) target.withCriticalSection(toPhase = phase, updatePhase = updatePhase, action = action)
} }
} }
@@ -84,20 +117,23 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh
action: () -> Unit action: () -> Unit
) { ) {
checker.lazyResolveToPhaseInside(phase, isJumpingPhase = true) { checker.lazyResolveToPhaseInside(phase, isJumpingPhase = true) {
target.withCriticalSection(phase, action) target.withCriticalSection(toPhase = phase, updatePhase = true, action = action)
} }
} }
/** /**
* Locks an a [FirElementWithResolveState] to resolve from `toPhase - 1` to [toPhase] and then updates the [FirElementWithResolveState.resolveState] to a [toPhase]. * Locks an a [FirElementWithResolveState] to resolve from `toPhase - 1` to [toPhase] and
* then updates the [FirElementWithResolveState.resolveState] to a [toPhase] if [updatePhase] is **true**.
*
* [updatePhase] == false means that we want to read some data under a lock.
* *
* If [FirElementWithResolveState] is already at least at [toPhase], does nothing. * If [FirElementWithResolveState] is already at least at [toPhase], does nothing.
* *
* Otherwise: * Otherwise:
* - Marks [FirElementWithResolveState] as in a process of resovle * - Marks [FirElementWithResolveState] as in a process of resovle
* - performs the resolve by calling [action] * - performs the resolve by calling [action]
* - updates the resolve phase to [toPhase] * - updates the resolve phase to [toPhase] if [updatePhase] is **true**.
* - notifies other threads waiting on the same lock that the declaration is already resolved by this thread, so other thread can continue its execution. * - notifies other threads waiting on the same lock that the declaration is already resolved by this thread, so other threads can continue its execution.
* *
* *
* Contention handling: * Contention handling:
@@ -107,6 +143,7 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh
*/ */
private inline fun FirElementWithResolveState.withCriticalSection( private inline fun FirElementWithResolveState.withCriticalSection(
toPhase: FirResolvePhase, toPhase: FirResolvePhase,
updatePhase: Boolean,
action: () -> Unit action: () -> Unit
) { ) {
while (true) { while (true) {
@@ -118,25 +155,29 @@ internal class LLFirLockProvider(private val checker: LLFirLazyResolveContractCh
// already resolved by some other thread // already resolved by some other thread
return return
} }
when (stateSnapshot) { when (stateSnapshot) {
is FirInProcessOfResolvingToPhaseStateWithoutBarrier -> { is FirInProcessOfResolvingToPhaseStateWithoutBarrier -> {
// some thread is resolving the phase, so we wait until it finishes // some thread is resolving the phase, so we wait until it finishes
trySettingBarrier(toPhase, stateSnapshot) trySettingBarrier(toPhase, stateSnapshot)
continue continue
} }
is FirInProcessOfResolvingToPhaseStateWithBarrier -> { is FirInProcessOfResolvingToPhaseStateWithBarrier -> {
// some thread is waiting on a barrier as the declaration is beeing resovled, so we try too // some thread is waiting on a barrier as the declaration is being resolved, so we try too
if (!waitOnBarrier(stateSnapshot)) continue waitOnBarrier(stateSnapshot)
checkPhase(toPhase) continue
return
} }
is FirResolvedToPhaseState -> { is FirResolvedToPhaseState -> {
if (!tryLock(toPhase, stateSnapshot)) continue if (!tryLock(toPhase, stateSnapshot)) continue
try { try {
action() action()
} finally { } finally {
unlock(toPhase) val newPhase = if (updatePhase) toPhase else stateSnapshot.resolvePhase
unlock(toPhase = newPhase)
} }
return return
} }
} }
@@ -201,7 +242,7 @@ private val shouldRetryFlag: ThreadLocal<Boolean> = ThreadLocal.withInitial { fa
private val LOG = Logger.getInstance(LLFirLockProvider::class.java) private val LOG = Logger.getInstance(LLFirLockProvider::class.java)
/** /**
* Retry the `action` calculation with a new FIR session if session passed to [LLFirLockProvider.withLock] turns to be invalid. * Retry the `action` calculation with a new FIR session if session passed to [LLFirLockProvider.withWriteLock] turns to be invalid.
* This is a temporary solution to fix inconsistent analysis state in common cases of idempotent analysis. * This is a temporary solution to fix inconsistent analysis state in common cases of idempotent analysis.
* The right solution would be to modify the FIR tree after the analysis is done, so the tree will always be in consistent state. * The right solution would be to modify the FIR tree after the analysis is done, so the tree will always be in consistent state.
*/ */
@@ -146,7 +146,7 @@ internal class LLFirModuleLazyDeclarationResolver(val moduleComponents: LLFirMod
if (target.resolvePhase >= FirResolvePhase.IMPORTS) return if (target.resolvePhase >= FirResolvePhase.IMPORTS) return
val firFile = target.getContainingFile() ?: return val firFile = target.getContainingFile() ?: return
if (firFile.resolvePhase >= FirResolvePhase.IMPORTS) return if (firFile.resolvePhase >= FirResolvePhase.IMPORTS) return
moduleComponents.globalResolveComponents.lockProvider.withLock(firFile, FirResolvePhase.IMPORTS) { moduleComponents.globalResolveComponents.lockProvider.withWriteLock(firFile, FirResolvePhase.IMPORTS) {
resolveFileToImportsWithoutLock(firFile) resolveFileToImportsWithoutLock(firFile)
} }
} }
@@ -113,10 +113,20 @@ internal abstract class LLFirTargetResolver(
if (isJumpingPhase) { if (isJumpingPhase) {
lockProvider.withJumpingLock(target, resolverPhase, action) lockProvider.withJumpingLock(target, resolverPhase, action)
} else { } else {
lockProvider.withLock(target, resolverPhase, action) lockProvider.withWriteLock(target, resolverPhase, action)
} }
} }
/**
* Execute action under a declaration lock.
* [action] will be executed only once in case of successful lock.
* If some another thread is already resolved [target] declaration to [resolverPhase] then [action] won't be executed.
*/
protected inline fun withReadLock(target: FirElementWithResolveState, action: () -> Unit) {
checkThatResolvedAtLeastToPreviousPhase(target)
lockProvider.withReadLock(target, resolverPhase, action)
}
private fun checkThatResolvedAtLeastToPreviousPhase(target: FirElementWithResolveState) { private fun checkThatResolvedAtLeastToPreviousPhase(target: FirElementWithResolveState) {
when (val previousPhase = resolverPhase.previous) { when (val previousPhase = resolverPhase.previous) {
FirResolvePhase.IMPORTS -> {} FirResolvePhase.IMPORTS -> {}