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.
This commit is contained in:
Grigorii Kirgizov 2020-02-28 19:45:25 +03:00
parent a9af387816
commit ccd3ed6d45
8 changed files with 63 additions and 42 deletions

View File

@ -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
}

View File

@ -35,4 +35,9 @@ interface RuleMatchEx : RuleMatch {
// TODO better be an inline extension fun
fun forEachReplaced(action: (Occurrence) -> Unit)
}
}
fun RuleMatch.allHeads() = (matchHeadKept().asSequence() + matchHeadReplaced().asSequence()) as Sequence<Occurrence>
fun RuleMatch.allStored() = allHeads().all { it.stored }

View File

@ -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<Occurrence>
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 }
}

View File

@ -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<FeedbackStatus>("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<FeedbackStatus>("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,

View File

@ -49,7 +49,7 @@ internal class ExecutionQueue(
private val postponedMatches: MutableMap<Id<Occurrence>, List<RuleMatchEx>> = HashMap()
private val execQueue: Queue<ExecPos> =
PriorityQueue<ExecPos>(1 + journalIndex.size / 2) { // just an estimate
PriorityQueue<ExecPos>(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
}

View File

@ -39,11 +39,16 @@ interface MatchJournal : MutableIterable<MatchJournal.Chunk>, 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.

View File

@ -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
}
fun RuleMatch.justifications(): Justifications = justsOf().also { allJss ->
this.allHeads().forEach { allJss.addAll(it.justifications()) }
}

View File

@ -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.
}
}