this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

internal/core/adt: streamline task equality checking

This is needed for the new pushdown code.

For partially completed values we must check for
task equality to disambiguated them. We made a
few changes here:

1. The code was duplicated. We now check it in
equalPartialNode, but drop the identical checks in
appendDisjunct.

2. We replace the logic with equalTasks. This does
a nested loop, so this increases the time complexity.
It is arguably a bit more robust and flexible, though,
as the previous code relied, perhaps mistakenly, on
the alignment of tasks.

The main purpose of doing this is to get rid of the
taskPos field, though. Doing so will allow us in the
future let the process method only process required
tasks, instead of being forced to process all tasks.

Signed-off-by: Marcel van Lohuizen <mpvl@gmail.com>
Change-Id: Ia4592adea54c6c18c830b7e719b879dceba5db3c
Reviewed-on: https://cue.gerrithub.io/c/cue-lang/cue/+/1236060
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>

+66 -29
+44 -26
internal/core/adt/disjunct2.go
··· 744 744 // check uniqueness 745 745 // TODO: if a node is not finalized, we could check that the parent 746 746 // (overlayed) closeContexts are identical. 747 - outer: 748 747 for _, xn := range a { 749 748 // TODO: for some reason, r may already have been added to dst in some 750 749 // cases, so we need to check for this. ··· 756 755 // Partial node 757 756 758 757 if !equalPartialNode(xn.ctx, x.node, xn.node) { 759 - continue outer 760 - } 761 - if len(xn.tasks) != xn.taskPos || len(x.tasks) != x.taskPos { 762 - if len(xn.tasks) != len(x.tasks) { 763 - continue 764 - } 765 - } 766 - for i, t := range xn.tasks[xn.taskPos:] { 767 - s := x.tasks[i] 768 - if s.x != t.x { 769 - continue outer 770 - } 758 + continue 771 759 } 760 + 772 761 vx, okx := nx.(Value) 773 762 ny := xv.BaseValue 774 763 if ny == nil || isCyclePlaceholder(ny) { ··· 776 765 } 777 766 vy, oky := ny.(Value) 778 767 if okx && oky && !Equal(ctx, vx, vy, CheckStructural) { 779 - continue outer 768 + continue 780 769 781 770 } 782 771 } else { 783 772 // Complete nodes. 784 773 if !Equal(ctx, xn.node.DerefValue(), x.node.DerefValue(), CheckStructural) { 785 - continue outer 774 + continue 786 775 } 787 776 } 788 777 ··· 798 787 return append(a, x) 799 788 } 800 789 790 + // equalTasks reports whether the unfinished tasks in x and y are equal based on 791 + // their expression value. Clearly, this is O(n^2). In our testing repo the 792 + // maximum number of tasks is 101, although usually the number is much smaller. 793 + // If this becomes a bottleneck, could make the task list stack based and keep 794 + // separate queues ready and processed tasks. An unequal number of ready tasks 795 + // would automatically mean inequality, and by sorting the lists we could 796 + // achieve O(n log n) complexity. 797 + func equalTasks(x, y *nodeContext) bool { 798 + inner1: 799 + for _, t := range x.tasks { 800 + if t.state != taskREADY { 801 + continue 802 + } 803 + for _, tt := range y.tasks { 804 + if t.x == tt.x { 805 + continue inner1 806 + } 807 + } 808 + return false 809 + } 810 + 811 + inner2: 812 + for _, t := range y.tasks { 813 + if t.state != taskREADY { 814 + continue 815 + } 816 + for _, tt := range x.tasks { 817 + if t.x == tt.x { 818 + continue inner2 819 + } 820 + } 821 + return false 822 + } 823 + 824 + return true 825 + } 826 + 801 827 func equalPartialNode(ctx *OpContext, x, y *Vertex) bool { 802 828 nx := x.state 803 829 ny := y.state ··· 875 901 if len(x.checks) != len(y.checks) { 876 902 return false 877 903 } 878 - if len(x.tasks) != x.taskPos || len(y.tasks) != y.taskPos { 879 - if len(x.tasks) != len(y.tasks) { 880 - return false 881 - } 904 + 905 + if !equalTasks(x, y) { 906 + return false 882 907 } 883 908 884 909 if !isEqualValue(x.ctx, x.lowerBound, y.lowerBound) { ··· 892 917 for i, c := range x.checks { 893 918 d := y.checks[i] 894 919 if !Equal(x.ctx, c.x.(Value), d.x.(Value), CheckStructural) { 895 - return false 896 - } 897 - } 898 - 899 - for i, t := range x.tasks[x.taskPos:] { 900 - s := y.tasks[i] 901 - if s.x != t.x { 902 920 return false 903 921 } 904 922 }
+20
internal/core/adt/sched.go
··· 315 315 } 316 316 } 317 317 318 + // allTasksStarted reports whether all tasks have started processing. 319 + func allTasksStarted(x *nodeContext) bool { 320 + for _, t := range x.tasks { 321 + if t.state == taskREADY { 322 + return false 323 + } 324 + } 325 + return true 326 + } 327 + 328 + // allTasksFinished reports whether all tasks have completed processing. 329 + func allTasksFinished(x *nodeContext) bool { 330 + for _, t := range x.tasks { 331 + if t.state < taskSUCCESS { 332 + return false 333 + } 334 + } 335 + return true 336 + } 337 + 318 338 // cloneInto initializes the state of dst to be the same as s. 319 339 // 320 340 // NOTE: this is deliberately not a pointer receiver: this approach allows
+2 -3
internal/core/adt/unify.go
··· 253 253 254 254 if n.node.ArcType != ArcPending && 255 255 n.meets(allAncestorsProcessed) && 256 - len(n.tasks) == n.taskPos { 256 + allTasksFinished(n) { 257 257 n.signal(arcTypeKnown) 258 258 } 259 259 ··· 546 546 n.signal(allAncestorsProcessed) 547 547 } 548 548 549 - if len(n.scheduler.tasks) != n.scheduler.taskPos { 550 - // TODO: do we need any more requirements here? 549 + if !allTasksStarted(n) { 551 550 const needs = valueKnown | fieldConjunctsKnown 552 551 553 552 n.process(needs, mode)