From ccd3ed6d4528f2387c8fa904696e62832ab9a719 Mon Sep 17 00:00:00 2001 From: Grigorii Kirgizov Date: Fri, 28 Feb 2020 19:45:25 +0300 Subject: [PATCH] Add tracking of last justifying MatchChunk during program eval to avoid searching for it in Journal Such machinery is needed inside ControllerImpl.processBody to provide nearest relevant match to feedback handling. Also required for a fix for MPSCR-47. Also enables back one assertion in incremental processing on rm stage. --- .../mps/logic/reactor/core/Controller.kt | 3 +- .../mps/logic/reactor/core/RuleMatchEx.kt | 7 +++- .../core/internal/ConstraintsProcessing.kt | 24 ++++++------ .../reactor/core/internal/ControllerImpl.kt | 37 ++++++++++++------- .../reactor/core/internal/ExecutionQueue.kt | 5 ++- .../reactor/core/internal/MatchJournal.kt | 11 ++++-- .../reactor/core/internal/MatchJournalImpl.kt | 15 ++++---- reactor/Test/test/RulesHelper.kt | 3 +- 8 files changed, 63 insertions(+), 42 deletions(-) diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt index 328ea966..73b89ce6 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/Controller.kt @@ -17,6 +17,7 @@ package jetbrains.mps.logic.reactor.core import jetbrains.mps.logic.reactor.core.internal.FeedbackStatus +import jetbrains.mps.logic.reactor.core.internal.MatchJournal import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation import jetbrains.mps.logic.reactor.evaluation.StoreView @@ -54,6 +55,6 @@ interface Controller { * its status allowed the operation. * The returned status captures the result of evaluating the rule's body. */ - fun processBody(match: RuleMatchEx, inStatus: FeedbackStatus): FeedbackStatus + fun processBody(match: RuleMatchEx, parent: MatchJournal.MatchChunk, inStatus: FeedbackStatus): FeedbackStatus } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatchEx.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatchEx.kt index 3aedca25..8397aa93 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatchEx.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/RuleMatchEx.kt @@ -35,4 +35,9 @@ interface RuleMatchEx : RuleMatch { // TODO better be an inline extension fun fun forEachReplaced(action: (Occurrence) -> Unit) -} \ No newline at end of file +} + + +fun RuleMatch.allHeads() = (matchHeadKept().asSequence() + matchHeadReplaced().asSequence()) as Sequence + +fun RuleMatch.allStored() = allHeads().all { it.stored } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ConstraintsProcessing.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ConstraintsProcessing.kt index 6820543e..b79a3f4e 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ConstraintsProcessing.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ConstraintsProcessing.kt @@ -58,7 +58,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di private data class MatchCandidate(val rule: Rule, val occChunk: MatchJournal.OccChunk) - fun reactivate(controller: Controller, activeOcc: Occurrence): FeedbackStatus { + fun reactivate(controller: Controller, activeOcc: Occurrence, parent: MatchJournal.MatchChunk): FeedbackStatus { assert(activeOcc.stored) // Forget that occ was seen. Otherwise it will be @@ -68,7 +68,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di trace.reactivateIncremental(activeOcc) - return processActivated(controller, activeOcc, FeedbackStatus.NORMAL()) + return processActivated(controller, activeOcc, parent, FeedbackStatus.NORMAL()) } /** @@ -118,11 +118,11 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di // We removed the match, so need to reactivate all still valid occurrences from the head // by definition of Chunk and principal rule, all occurrences from the head are principal - val matchedOccs = chunk.match.allHeads() as Iterable + val matchedOccs = chunk.match.allHeads().asIterable() val validOccs = matchedOccs.filter { occ -> !occ.justifiedByAny(justificationRoots) } -// assert(matchedOccs.all { it.isPrincipal() }) + assert(matchedOccs.all { it.isPrincipal() }) execQueue.offerAll(validOccs) } @@ -213,7 +213,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di * Calls the controller to process matches (if any) that were triggered. * This method may be called at most once for a fresh state frame. */ - fun processActivated(controller: Controller, active: Occurrence, inStatus: FeedbackStatus) : FeedbackStatus { + fun processActivated(controller: Controller, active: Occurrence, parent: MatchJournal.MatchChunk, inStatus: FeedbackStatus) : FeedbackStatus { push() assert(active.alive) @@ -237,7 +237,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di if (isFront() || !active.isPrincipal()) { matches } else { - assert( matches.all { ispec.isPrincipal(it.rule()) } ) + assert( matches.all { it.isPrincipal() } ) execQueue.postponeFutureMatches(matches) } @@ -252,7 +252,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di // TODO: paranoid check. should be isAlive() instead // FIXME: move this check elsewhere if (status.operational && active.stored && match.allStored()) - processMatch(controller, match, status) + processMatch(controller, match, parent, status) else status } @@ -279,7 +279,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di private inline fun FeedbackStatus.then(action: (FeedbackStatus) -> FeedbackStatus) : FeedbackStatus = if (operational) action(this) else this - private fun processMatch(controller: Controller, match: RuleMatchEx, inStatus: FeedbackStatus) : FeedbackStatus = + private fun processMatch(controller: Controller, match: RuleMatchEx, parent: MatchJournal.MatchChunk, inStatus: FeedbackStatus) : FeedbackStatus = controller.offerMatch(match, inStatus) .let { when (it) { @@ -300,7 +300,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di accept(controller, match) } .then { - controller.processBody(match, it) + controller.processBody(match, parent, it) } .also { trace.finish(match) } @@ -341,9 +341,7 @@ internal class ConstraintsProcessing(private var dispatchingFront: Dispatcher.Di } + private fun RuleMatch.isPrincipal() = ispec.isPrincipal(this.rule()) + private fun Occurrence.isPrincipal() = ispec.isPrincipal(this.constraint()) - - private fun RuleMatch.allHeads() = matchHeadKept() + matchHeadReplaced() - - private fun RuleMatch.allStored() = allHeads().all { co -> (co as Occurrence).stored } } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt index 72d795ba..c8cc4173 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ControllerImpl.kt @@ -47,7 +47,7 @@ internal class ControllerImpl ( }) trace.activate(occ) - val status = processing.processActivated(this, active, NORMAL()) + val status = processing.processActivated(this, active, processing.initialChunk(), NORMAL()) if (status is FAILED) { throw status.failure.failureCause() } @@ -77,7 +77,7 @@ internal class ControllerImpl ( // FIXME noLogicalContext val context = Context(NORMAL(), noLogicalContext, null, trace) - activateConstraint(constraint, processing.justifications(), context) + activateConstraint(constraint, processing.initialChunk(), justsCopy(processing.justifications()), context) return context.currentStatus() } @@ -102,7 +102,8 @@ internal class ControllerImpl ( profiler.profile("reactivate_${occ.constraint.symbol()}") { trace.reactivate(occ) - processing.processActivated(this, occ, NORMAL()) + // FIXME: propagate parent MatchChunk through call to reactivate() + processing.processActivated(this, occ, processing.parentChunk(), NORMAL()) } @@ -135,7 +136,7 @@ internal class ControllerImpl ( return context.currentStatus() } - override fun processBody(match: RuleMatchEx, inStatus: FeedbackStatus) : FeedbackStatus { + override fun processBody(match: RuleMatchEx, parent: MatchJournal.MatchChunk, inStatus: FeedbackStatus) : FeedbackStatus { val context = Context(inStatus, match.logicalContext(), match.rule().uniqueTag(), trace) val altIt = match.rule().bodyAlternation().iterator() @@ -153,15 +154,22 @@ internal class ControllerImpl ( } val savedPos = processing.currentPos() + var newParent: MatchJournal.MatchChunk = parent - val currentJusts = processing.justifications() + if (match.isPrincipal()) { + // This match corresponds to the last added chunk + assert( (savedPos.chunk as? MatchJournal.MatchChunk)?.match === match ) + newParent = savedPos.chunk as MatchJournal.MatchChunk + } + + val justifications = processing.justifications() for (item in body) { val itemOk = when (item) { is Constraint -> { // track justifications only for principal constraints - val justs = if (ispec.isPrincipal(item)) currentJusts else emptyJustifications() - activateConstraint(item, justs, context) + val js = if (item.isPrincipal()) justsCopy(justifications) else emptyJustifications() + activateConstraint(item, newParent, js, context) } is Predicate -> tellPredicate(item, context) else -> throw IllegalArgumentException("unknown item ${item}") @@ -170,7 +178,7 @@ internal class ControllerImpl ( if (itemOk) { context.withStatus { status -> if (status.feedback?.alreadyHandled() == false) { - status.feedback.handle(match, processing.ancestorMatch().match, supervisor) + status.feedback.handle(match, newParent.match, supervisor) } } @@ -191,7 +199,7 @@ internal class ControllerImpl ( // if failure can be handled here then recover } else if (status.feedback?.alreadyHandled() == false - && status.failure.handle(match, processing.ancestorMatch().match, supervisor)) { + && status.failure.handle(match, newParent.match, supervisor)) { status.recover() @@ -218,15 +226,15 @@ internal class ControllerImpl ( return context.currentStatus() } - private fun activateConstraint(constraint: Constraint, justifications: Justifications, context: Context) : Boolean { + private fun activateConstraint(constraint: Constraint, parent: MatchJournal.MatchChunk, justifications: Justifications, context: Context) : Boolean { val args = supervisor.instantiateArguments(constraint.arguments(), context.logicalContext, context) return context.eval { status -> profiler.profile("activate_${constraint.symbol()}") { - constraint.occurrence(logicalStateObservable(), args, processing.nextEvidence(), justsCopy(justifications), context.logicalContext, context.ruleUniqueTag).let { occ -> + constraint.occurrence(logicalStateObservable(), args, processing.nextEvidence(), justifications, context.logicalContext, context.ruleUniqueTag).let { occ -> trace.activate(occ) - processing.processActivated(this, occ, status) + processing.processActivated(this, occ, parent, status) } } @@ -268,7 +276,10 @@ internal class ControllerImpl ( it.first.patternPredicates(it.second.arguments()) }.toList() - private fun RuleMatch.allStored() = (matchHeadKept() + matchHeadReplaced()).all { co -> (co as Occurrence).stored } + + private fun RuleMatch.isPrincipal() = ispec.isPrincipal(this.rule()) + + private fun Occurrence.isPrincipal() = ispec.isPrincipal(this.constraint()) inner private class Context(inStatus: FeedbackStatus, diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ExecutionQueue.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ExecutionQueue.kt index e39fc689..36cf2950 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ExecutionQueue.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/ExecutionQueue.kt @@ -49,7 +49,7 @@ internal class ExecutionQueue( private val postponedMatches: MutableMap, List> = HashMap() private val execQueue: Queue = - PriorityQueue(1 + journalIndex.size / 2) { // just an estimate + PriorityQueue(1 + journalIndex.size / 8) { // just an estimate lhs, rhs -> journalIndex.compare(lhs.pos, rhs.pos) } @@ -74,7 +74,8 @@ internal class ExecutionQueue( // If the occurrence is still in the store after replay (i.e. if it's valid to activate it) if (execPos.activeOcc.stored) { - status = processing.reactivate(controller, execPos.activeOcc) + // fixme: parentChunk can also be tracked + status = processing.reactivate(controller, execPos.activeOcc, processing.parentChunk()) // Leave journal processing as it was at the point of failure if (!status.operational) return status } diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt index 2377172c..41f0245a 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournal.kt @@ -39,11 +39,16 @@ interface MatchJournal : MutableIterable, EvidenceSource { fun logActivation(occ: Occurrence): OccChunk? /** - * Returns nearest MatchChunk which justifies current chunk. + * Returns nearest [MatchChunk] which justifies current chunk. * If current chunk is [MatchChunk], then it is returned. - * May return initial chunk. For initial chunk case returns it. + * May return initial chunk. */ - fun ancestorMatch(): MatchChunk + fun parentChunk(): MatchChunk + + /** + * Returns the initial chunk which is always present in history + */ + fun initialChunk(): MatchChunk /** * Current position at the journal. diff --git a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt index 957f4a35..cbc8be52 100644 --- a/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt +++ b/reactor/Core/src/jetbrains/mps/logic/reactor/core/internal/MatchJournalImpl.kt @@ -99,7 +99,9 @@ internal open class MatchJournalImpl( return added } - override fun ancestorMatch(): MatchChunk { + override fun initialChunk(): MatchChunk = this.first() as MatchChunk + + override fun parentChunk(): MatchChunk { if (current is MatchChunk) { return current as MatchChunk } @@ -111,7 +113,7 @@ internal open class MatchJournalImpl( return prev } } - return hist.first() as MatchChunk // initial chunk + return initialChunk() } override fun currentPos(): MatchJournal.Pos = current.toPos() @@ -333,9 +335,6 @@ internal open class MatchJournalImpl( fun MatchJournal.justifications() = this.currentPos().chunk.justifications() -fun RuleMatch.justifications(): Justifications { - val res: Justifications = justsOf() - this.matchHeadKept().forEach { res.addAll( (it as Occurrence).justifications() ) } - this.matchHeadReplaced().forEach { res.addAll( (it as Occurrence).justifications() ) } - return res -} \ No newline at end of file +fun RuleMatch.justifications(): Justifications = justsOf().also { allJss -> + this.allHeads().forEach { allJss.addAll(it.justifications()) } +} diff --git a/reactor/Test/test/RulesHelper.kt b/reactor/Test/test/RulesHelper.kt index f27d3653..7756f2bf 100644 --- a/reactor/Test/test/RulesHelper.kt +++ b/reactor/Test/test/RulesHelper.kt @@ -1,5 +1,6 @@ import jetbrains.mps.logic.reactor.core.* import jetbrains.mps.logic.reactor.core.internal.FeedbackStatus +import jetbrains.mps.logic.reactor.core.internal.MatchJournal import jetbrains.mps.logic.reactor.evaluation.PredicateInvocation import jetbrains.mps.logic.reactor.evaluation.StoreView import jetbrains.mps.logic.reactor.logical.Logical @@ -237,7 +238,7 @@ class MockController : Controller { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } - override fun processBody(match: RuleMatchEx, inStatus: FeedbackStatus): FeedbackStatus { + override fun processBody(match: RuleMatchEx, parent: MatchJournal.MatchChunk, inStatus: FeedbackStatus): FeedbackStatus { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } }