cuelang.org/go@v0.10.1/internal/core/adt/overlay.go (about)

     1  // Copyright 2024 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package adt
    16  
    17  import "slices"
    18  
    19  // This file implements a Vertex overlay. This is used by the disjunction
    20  // algorithm to fork an existing Vertex value without modifying the original.
    21  //
    22  // At the moment, the forked value is a complete copy of the original.
    23  // The copy points to the original to keep track of pointer equivalence.
    24  // Conversely, while a copy is evaluated, the value of which it is a copy
    25  // references the copy. Dereferencing will then take care that the copy is used
    26  // during evaluation.
    27  //
    28  //   nodeContext (main)  <-
    29  //   - deref               \
    30  //     |                    \
    31  //     |  nodeContext (d1)  | <-
    32  //     \  - overlays -------/   \
    33  //      \                        \
    34  //       ->   nodeContext (d2)    |
    35  //            - overlays --------/
    36  //
    37  // TODO: implement dereferencing
    38  // TODO(perf): implement copy on write: instead of copying the entire tree, we
    39  // could get by with only copying arcs to that are modified in the copy.
    40  
    41  var nextGeneration int
    42  
    43  func newOverlayContext(ctx *OpContext) *overlayContext {
    44  	nextGeneration++
    45  	return &overlayContext{ctx: ctx, generation: nextGeneration}
    46  }
    47  
    48  // An overlayContext keeps track of copied vertices, closeContexts, and tasks.
    49  // This allows different passes to know which of each were created, without
    50  // having to walk the entire tree.
    51  type overlayContext struct {
    52  	ctx *OpContext
    53  
    54  	// generation is used to identify the current overlayContext. All
    55  	// closeContexts created by this overlayContext will have this generation.
    56  	// Whenever a counter of a closedContext is changed, this may only cause
    57  	// a cascade of changes if the generation is the same.
    58  	generation int
    59  
    60  	// closeContexts holds the allocated closeContexts created by allocCC.
    61  	//
    62  	// In the first pass, closeContexts are copied using allocCC. This also
    63  	// walks the parent tree, and allocates copies for ConjunctGroups.
    64  	//
    65  	// In the second pass, initCloneCC can be finalized by initializing each
    66  	// closeContext in this slice.
    67  	//
    68  	// Note that after the copy is completed, the overlay pointer should be
    69  	// deleted.
    70  	closeContexts []*closeContext
    71  
    72  	// vertices holds the original, non-overlay vertices. The overlay for a
    73  	// vertex v can be obtained by looking up v.cc.overlay.src.
    74  	vertices []*Vertex
    75  }
    76  
    77  // cloneRoot clones the a Vertex in which disjunctions are defined to allow
    78  // inserting selected disjuncts into a new Vertex.
    79  func (ctx *overlayContext) cloneRoot(root *nodeContext) *nodeContext {
    80  	// Clone all vertices that need to be cloned to support the overlay.
    81  	v := ctx.cloneVertex(root.node)
    82  	v.IsDisjunct = true
    83  
    84  	// TODO: patch notifications to any node that is within the disjunct to
    85  	// point to the new vertex instead.
    86  
    87  	// Initialize closeContexts: at this point, all closeContexts that need to
    88  	// be cloned have been allocated and stored in closeContexts and can now be
    89  	// initialized.
    90  	for _, cc := range ctx.closeContexts {
    91  		ctx.initCloneCC(cc)
    92  	}
    93  
    94  	// TODO: walk overlay vertices and decrement counters of non-disjunction
    95  	// running tasks?
    96  	// TODO: find a faster way to do this. Walking over vertices would
    97  	// probably be faster.
    98  	for _, cc := range ctx.closeContexts {
    99  		for _, d := range cc.dependencies {
   100  			if d.task == nil {
   101  				// The test case that makes this necessary:
   102  				// #A: ["a" | "b"] | {}
   103  				// #A: ["a" | "b"] | {}
   104  				// b:  #A & ["b"]
   105  				//
   106  				// TODO: invalidate task instead?
   107  				continue
   108  			}
   109  			if d.kind == TASK && d.task.state == taskRUNNING && !d.task.defunct {
   110  				cc.overlay.decDependent(ctx.ctx, TASK, nil)
   111  			}
   112  		}
   113  	}
   114  
   115  	return v.state
   116  }
   117  
   118  // unlinkOverlay unlinks helper pointers. This should be done after the
   119  // evaluation of a disjunct is complete. Keeping the linked pointers around
   120  // will allow for dereferencing a vertex to its overlay, which, in turn,
   121  // allows a disjunct to refer to parents vertices of the disjunct that
   122  // recurse into the disjunct.
   123  //
   124  // TODO(perf): consider using generation counters.
   125  func (ctx *overlayContext) unlinkOverlay() {
   126  	for _, cc := range ctx.closeContexts {
   127  		cc.overlay = nil
   128  	}
   129  }
   130  
   131  // cloneVertex copies the contents of x into a new Vertex.
   132  //
   133  // It copies all Arcs, Conjuncts, and Structs, recursively.
   134  //
   135  // TODO(perf): it would probably be faster to copy vertices on demand. But this
   136  // is more complicated and it would be worth measuring how much of a performance
   137  // benefit this gives. More importantly, we should first implement the filter
   138  // to eliminate disjunctions pre-copy based on discriminator fields and what
   139  // have you. This is not unlikely to eliminate
   140  func (ctx *overlayContext) cloneVertex(x *Vertex) *Vertex {
   141  	xcc := x.rootCloseContext(ctx.ctx) // may be uninitialized for constraints.
   142  	if o := xcc.overlay; o != nil && o.src != nil {
   143  		// This path could happen with structure sharing or user-constructed
   144  		// values.
   145  		return o.src
   146  	}
   147  
   148  	v := &Vertex{}
   149  	*v = *x
   150  
   151  	ctx.vertices = append(ctx.vertices, v)
   152  
   153  	v.cc = ctx.allocCC(x.cc)
   154  
   155  	v.cc.src = v
   156  	v.cc.parentConjuncts = v
   157  	v.Conjuncts = *v.cc.group
   158  
   159  	if a := x.Arcs; len(a) > 0 {
   160  		// TODO(perf): reuse buffer.
   161  		v.Arcs = make([]*Vertex, len(a))
   162  		for i, arc := range a {
   163  			// TODO(perf): reuse when finalized.
   164  			arc := ctx.cloneVertex(arc)
   165  			v.Arcs[i] = arc
   166  			arc.Parent = v
   167  		}
   168  	}
   169  
   170  	v.Structs = slices.Clone(v.Structs)
   171  
   172  	if pc := v.PatternConstraints; pc != nil {
   173  		npc := &Constraints{Allowed: pc.Allowed}
   174  		v.PatternConstraints = npc
   175  
   176  		npc.Pairs = make([]PatternConstraint, len(pc.Pairs))
   177  		for i, p := range pc.Pairs {
   178  			npc.Pairs[i] = PatternConstraint{
   179  				Pattern:    p.Pattern,
   180  				Constraint: ctx.cloneVertex(p.Constraint),
   181  			}
   182  		}
   183  	}
   184  
   185  	if v.state != nil {
   186  		v.state = ctx.cloneNodeContext(x.state)
   187  		v.state.node = v
   188  
   189  		ctx.cloneScheduler(v.state, x.state)
   190  	}
   191  
   192  	return v
   193  }
   194  
   195  func (ctx *overlayContext) cloneNodeContext(n *nodeContext) *nodeContext {
   196  	if !n.node.isInitialized() {
   197  		panic("unexpected uninitialized node")
   198  	}
   199  	d := n.ctx.newNodeContext(n.node)
   200  	d.underlying = n.underlying
   201  	if n.underlying == nil {
   202  		panic("unexpected nil underlying")
   203  	}
   204  
   205  	d.refCount++
   206  
   207  	d.ctx = n.ctx
   208  	d.node = n.node
   209  
   210  	d.nodeContextState = n.nodeContextState
   211  
   212  	d.arcMap = append(d.arcMap, n.arcMap...)
   213  	d.checks = append(d.checks, n.checks...)
   214  
   215  	// TODO: do we need to add cyclicConjuncts? Typically, cyclicConjuncts
   216  	// gets cleared at the end of a unify call. There are cases, however, where
   217  	// this is possible. We should decide whether cyclicConjuncts should be
   218  	// forced to be processed in the parent node, or that we allow it to be
   219  	// copied to the disjunction. By taking no action here, we assume it is
   220  	// processed in the parent node. Investigate whether this always will lead
   221  	// to correct results.
   222  	// d.cyclicConjuncts = append(d.cyclicConjuncts, n.cyclicConjuncts...)
   223  
   224  	if len(n.disjunctions) > 0 {
   225  		for _, de := range n.disjunctions {
   226  			// Do not clone cc, as it is identified by underlying. We only need
   227  			// to clone the cc in disjunctCCs.
   228  			// de.cloneID.cc = ctx.allocCC(de.cloneID.cc)
   229  			d.disjunctions = append(d.disjunctions, de)
   230  		}
   231  		for _, h := range n.disjunctCCs {
   232  			h.cc = ctx.allocCC(h.cc)
   233  			d.disjunctCCs = append(d.disjunctCCs, h)
   234  		}
   235  	}
   236  
   237  	return d
   238  }
   239  
   240  // cloneConjunct prepares a tree of conjuncts for copying by first allocating
   241  // a clone for each closeContext.
   242  func (ctx *overlayContext) copyConjunct(c Conjunct) Conjunct {
   243  	cc := c.CloseInfo.cc
   244  	if cc == nil {
   245  		return c
   246  	}
   247  	// TODO: see if we can avoid this allocation. It seems that this should
   248  	// not be necessary, and evaluation attains correct results without it.
   249  	// Removing this, though, will cause some of the assertions to fail. These
   250  	// assertions are overly strict and could be relaxed, but keeping them as
   251  	// they are makes reasoning about them easier.
   252  	overlay := ctx.allocCC(cc)
   253  	c.CloseInfo.cc = overlay
   254  	return c
   255  }
   256  
   257  // Phase 1: alloc
   258  func (ctx *overlayContext) allocCC(cc *closeContext) *closeContext {
   259  	// TODO(perf): if the original is "done", it can no longer be modified and
   260  	// we can use the original, even if the values will not be correct.
   261  	if cc.overlay != nil {
   262  		return cc.overlay
   263  	}
   264  
   265  	o := &closeContext{generation: ctx.generation}
   266  	cc.overlay = o
   267  	// TODO(evalv3): is it okay to use the same origin in overlays?
   268  	o.origin = cc.origin
   269  
   270  	if cc.parent != nil {
   271  		o.parent = ctx.allocCC(cc.parent)
   272  	}
   273  
   274  	// Copy the conjunct group if it exists.
   275  	if cc.group != nil {
   276  		// Copy the group of conjuncts.
   277  		g := make([]Conjunct, len(*cc.group))
   278  		o.group = (*ConjunctGroup)(&g)
   279  		for i, c := range *cc.group {
   280  			g[i] = ctx.copyConjunct(c)
   281  		}
   282  
   283  		if o.parent != nil {
   284  			// validate invariants
   285  			ca := *cc.parent.group
   286  			if ca[cc.parentIndex].x != cc.group {
   287  				panic("group misaligned")
   288  			}
   289  
   290  			(*o.parent.group)[cc.parentIndex].x = o.group
   291  		}
   292  	}
   293  
   294  	// This must come after allocating the parent so that we can always read
   295  	// the src vertex from the parent during initialization. This assumes that
   296  	// src is set in the root closeContext when cloning a vertex.
   297  	ctx.closeContexts = append(ctx.closeContexts, cc)
   298  
   299  	// needsCloseInSchedule is used as a boolean. The pointer to the original
   300  	// closeContext is just used for reporting purposes.
   301  	if cc.needsCloseInSchedule != nil {
   302  		o.needsCloseInSchedule = ctx.allocCC(cc.needsCloseInSchedule)
   303  	}
   304  
   305  	// We only explicitly tag dependencies of type ARC. Notifications that
   306  	// point within the disjunct overlay will be tagged elsewhere.
   307  	for _, a := range cc.arcs {
   308  		if a.kind == ARC {
   309  			ctx.allocCC(a.cc)
   310  		}
   311  	}
   312  
   313  	return o
   314  }
   315  
   316  func (ctx *overlayContext) initCloneCC(x *closeContext) {
   317  	o := x.overlay
   318  
   319  	if p := x.parent; p != nil {
   320  		o.parent = p.overlay
   321  		o.src = o.parent.src
   322  	}
   323  
   324  	o.origin = x.origin
   325  	o.conjunctCount = x.conjunctCount
   326  	o.disjunctCount = x.disjunctCount
   327  	o.isDef = x.isDef
   328  	o.isDefOrig = x.isDefOrig
   329  	o.hasEllipsis = x.hasEllipsis
   330  	o.hasTop = x.hasTop
   331  	o.hasNonTop = x.hasNonTop
   332  	o.isClosedOnce = x.isClosedOnce
   333  	o.isEmbed = x.isEmbed
   334  	o.isClosed = x.isClosed
   335  	o.isTotal = x.isTotal
   336  	o.done = x.done
   337  	o.isDecremented = x.isDecremented
   338  	o.parentIndex = x.parentIndex
   339  	o.Expr = x.Expr
   340  	o.Patterns = append(o.Patterns, x.Patterns...)
   341  
   342  	// child and next always point to completed closeContexts. Moreover, only
   343  	// fields that are immutable, such as Expr, are used. It is therefore not
   344  	// necessary to use overlays.
   345  	o.child = x.child
   346  	if x.child != nil && x.child.overlay != nil {
   347  		// TODO: there seem to be situations where this is possible after all.
   348  		// See if this is really true, and we should remove this panic, or if
   349  		// this underlies a bug of sorts.
   350  		// panic("unexpected overlay in child")
   351  	}
   352  	o.next = x.next
   353  	if x.next != nil && x.next.overlay != nil {
   354  		panic("unexpected overlay in next")
   355  	}
   356  
   357  	for _, d := range x.dependencies {
   358  		if d.decremented {
   359  			continue
   360  		}
   361  
   362  		if d.dependency.overlay == nil {
   363  			// This dependency is irrelevant for the current overlay. We can
   364  			// eliminate it as long as we decrement the accompanying counter.
   365  			if o.conjunctCount < 2 {
   366  				// This node can only be relevant if it has at least one other
   367  				// dependency. Check that we are not decrementing the counter
   368  				// to 0.
   369  				// TODO: this currently panics for some tests. Disabling does
   370  				// not seem to harm, though. Reconsider whether this is an issue.
   371  				// panic("unexpected conjunctCount: must be at least 2")
   372  			}
   373  			o.conjunctCount--
   374  			continue
   375  		}
   376  
   377  		dep := d.dependency
   378  		if dep.overlay != nil {
   379  			dep = dep.overlay
   380  		}
   381  		o.dependencies = append(o.dependencies, &ccDep{
   382  			dependency:  dep,
   383  			kind:        d.kind,
   384  			decremented: false,
   385  		})
   386  	}
   387  
   388  	switch p := x.parentConjuncts.(type) {
   389  	case *closeContext:
   390  		if p.overlay == nil {
   391  			panic("expected overlay")
   392  		}
   393  		o.parentConjuncts = p.overlay
   394  
   395  	case *Vertex:
   396  		o.parentConjuncts = o.src
   397  	}
   398  
   399  	if o.src == nil {
   400  		// fall back to original vertex.
   401  		// FIXME: this is incorrect, as it may lead to evaluating nodes that
   402  		// are not part of the disjunction with values of the disjunction.
   403  		// TODO: try eliminating EVAL dependencies of arcs that are the parent
   404  		// of the disjunction root.
   405  		o.src = x.src
   406  	}
   407  
   408  	if o.parentConjuncts == nil {
   409  		panic("expected parentConjuncts")
   410  	}
   411  
   412  	for _, a := range x.arcs {
   413  		// If an arc does not have an overlay, we should not decrement the
   414  		// dependency counter. We simply remove the dependency in that case.
   415  		if a.cc.overlay == nil {
   416  			continue
   417  		}
   418  		if a.key.overlay != nil {
   419  			a.key = a.key.overlay // TODO: is this necessary?
   420  		}
   421  		a.cc = a.cc.overlay
   422  		o.arcs = append(o.arcs, a)
   423  	}
   424  
   425  	// NOTE: copying externalDeps is hard and seems unnecessary, as it needs to
   426  	// be resolved in the base anyway.
   427  }
   428  
   429  func (ctx *overlayContext) cloneScheduler(dst, src *nodeContext) {
   430  	ss := &src.scheduler
   431  	ds := &dst.scheduler
   432  
   433  	ds.state = ss.state
   434  	ds.completed = ss.completed
   435  	ds.needs = ss.needs
   436  	ds.provided = ss.provided
   437  	ds.frozen = ss.frozen
   438  	ds.isFrozen = ss.isFrozen
   439  	ds.counters = ss.counters
   440  
   441  	ss.blocking = ss.blocking[:0]
   442  
   443  	for _, t := range ss.tasks {
   444  		switch t.state {
   445  		case taskWAITING:
   446  			// Do not unblock previously blocked tasks, unless they are
   447  			// associated with this node.
   448  			// TODO: an edge case is when a task is blocked on another node
   449  			// within the same disjunction. We could solve this by associating
   450  			// each nodeContext with a unique ID (like a generation counter) for
   451  			// the disjunction.
   452  			if t.node != src || t.blockedOn != ss {
   453  				break
   454  			}
   455  			t.defunct = true
   456  			t := ctx.cloneTask(t, ds, ss)
   457  			ds.tasks = append(ds.tasks, t)
   458  			ds.blocking = append(ds.blocking, t)
   459  			ctx.ctx.blocking = append(ctx.ctx.blocking, t)
   460  
   461  		case taskREADY:
   462  			t.defunct = true
   463  			t := ctx.cloneTask(t, ds, ss)
   464  			ds.tasks = append(ds.tasks, t)
   465  
   466  		case taskRUNNING:
   467  			if t.run != handleResolver {
   468  				// TODO: consider whether this is also necessary for other
   469  				// types of tasks.
   470  				break
   471  			}
   472  
   473  			t.defunct = true
   474  			t := ctx.cloneTask(t, ds, ss)
   475  			t.state = taskREADY
   476  			ds.tasks = append(ds.tasks, t)
   477  		}
   478  	}
   479  }
   480  
   481  func (ctx *overlayContext) cloneTask(t *task, dst, src *scheduler) *task {
   482  	if t.node != src.node {
   483  		panic("misaligned node")
   484  	}
   485  
   486  	id := t.id
   487  	if id.cc != nil {
   488  		id.cc = ctx.allocCC(t.id.cc) // TODO: may be nil for disjunctions.
   489  	}
   490  
   491  	// TODO: alloc from buffer.
   492  	d := &task{
   493  		run:            t.run,
   494  		state:          t.state,
   495  		completes:      t.completes,
   496  		unblocked:      t.unblocked,
   497  		blockCondition: t.blockCondition,
   498  		err:            t.err,
   499  		env:            t.env,
   500  		x:              t.x,
   501  		id:             id,
   502  
   503  		node: dst.node,
   504  
   505  		// TODO: need to copy closeContexts?
   506  		comp: t.comp,
   507  		leaf: t.leaf,
   508  	}
   509  
   510  	if t.blockedOn != nil {
   511  		if t.blockedOn != src {
   512  			panic("invalid scheduler")
   513  		}
   514  		d.blockedOn = dst
   515  	}
   516  
   517  	return d
   518  }