cuelang.org/go@v0.13.0/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 (
    18  	"maps"
    19  	"slices"
    20  )
    21  
    22  // This file implements a Vertex overlay. This is used by the disjunction
    23  // algorithm to fork an existing Vertex value without modifying the original.
    24  //
    25  // At the moment, the forked value is a complete copy of the original.
    26  // The copy points to the original to keep track of pointer equivalence.
    27  // Conversely, while a copy is evaluated, the value of which it is a copy
    28  // references the copy. Dereferencing will then take care that the copy is used
    29  // during evaluation.
    30  //
    31  //   nodeContext (main)  <-
    32  //   - deref               \
    33  //     |                    \
    34  //     |  nodeContext (d1)  | <-
    35  //     \  - overlays -------/   \
    36  //      \                        \
    37  //       ->   nodeContext (d2)    |
    38  //            - overlays --------/
    39  //
    40  // TODO: implement dereferencing
    41  // TODO(perf): implement copy on write: instead of copying the entire tree, we
    42  // could get by with only copying arcs to that are modified in the copy.
    43  
    44  func newOverlayContext(ctx *OpContext) *overlayContext {
    45  	return &overlayContext{
    46  		ctx: ctx,
    47  
    48  		// TODO(perf): take a map from a pool of maps and reuse.
    49  		vertexMap: make(map[*Vertex]*Vertex),
    50  	}
    51  }
    52  
    53  // An overlayContext keeps track of copied vertices, closeContexts, and tasks.
    54  // This allows different passes to know which of each were created, without
    55  // having to walk the entire tree.
    56  type overlayContext struct {
    57  	ctx *OpContext
    58  
    59  	// vertices holds the original, non-overlay vertices. The overlay for a
    60  	// vertex v can be obtained by looking up v.cc.overlay.src.
    61  	vertices []*Vertex
    62  
    63  	// vertexMap maps Vertex values of an originating node to the ones copied
    64  	// for this overlayContext. This is used to update the Vertex values in
    65  	// Environment values.
    66  	vertexMap vertexMap
    67  
    68  	// confMap maps envComprehension values to the ones copied for this
    69  	// overlayContext.
    70  	compMap map[*envComprehension]*envComprehension
    71  }
    72  
    73  type vertexMap map[*Vertex]*Vertex
    74  
    75  // overlayFrom is used to store overlay information in the OpContext. This
    76  // is used for dynamic resolution of vertices, which prevents data structures
    77  // from having to be copied in the overlay.
    78  //
    79  // TODO(perf): right now this is only used for resolving vertices in
    80  // comprehensions. We could also use this for resolving environments, though.
    81  // Furthermore, we could used the "cleared" vertexMaps on this stack to avoid
    82  // allocating memory.
    83  //
    84  // NOTE: using a stack globally in OpContext is not very principled, as we
    85  // may be evaluating nested evaluations of different disjunctions. However,
    86  // in practice this just results in more work: as the vertices should not
    87  // overlap, there will be no cycles.
    88  type overlayFrame struct {
    89  	vertexMap vertexMap
    90  	root      *Vertex
    91  }
    92  
    93  func (c *OpContext) pushOverlay(v *Vertex, m vertexMap) {
    94  	c.overlays = append(c.overlays, overlayFrame{m, v})
    95  }
    96  
    97  func (c *OpContext) popOverlay() {
    98  	c.overlays = c.overlays[:len(c.overlays)-1]
    99  }
   100  
   101  func (c *OpContext) deref(v *Vertex) *Vertex {
   102  	for i := len(c.overlays) - 1; i >= 0; i-- {
   103  		f := c.overlays[i]
   104  		if f.root == v {
   105  			continue
   106  		}
   107  		if x, ok := f.vertexMap[v]; ok {
   108  			return x
   109  		}
   110  	}
   111  	return v
   112  }
   113  
   114  // deref reports a replacement of v or v itself if such a replacement does not
   115  // exists. It computes the transitive closure of the replacement graph.
   116  // TODO(perf): it is probably sufficient to only replace one level. But we need
   117  // to prove this to be sure. Until then, we keep the code as is.
   118  //
   119  // This function does a simple cycle check. As every overlayContext adds only
   120  // new Vertex nodes and only entries from old to new nodes are created, this
   121  // should never happen. But just in case we will panic instead of hang in such
   122  // situations.
   123  func (m vertexMap) deref(v *Vertex) *Vertex {
   124  	for i := 0; ; i++ {
   125  		x, ok := m[v]
   126  		if !ok {
   127  			break
   128  		}
   129  		v = x
   130  
   131  		if i > len(m) {
   132  			panic("cycle detected in vertexMap")
   133  		}
   134  	}
   135  	return v
   136  }
   137  
   138  // cloneRoot clones the Vertex in which disjunctions are defined to allow
   139  // inserting selected disjuncts into a new Vertex.
   140  func (ctx *overlayContext) cloneRoot(root *nodeContext) *nodeContext {
   141  	maps.Copy(ctx.vertexMap, root.vertexMap)
   142  
   143  	// Clone all vertices that need to be cloned to support the overlay.
   144  	v := ctx.cloneVertex(root.node)
   145  	v.IsDisjunct = true
   146  	v.state.vertexMap = ctx.vertexMap
   147  
   148  	for _, v := range ctx.vertices {
   149  		v = v.overlay
   150  
   151  		n := v.state
   152  		if n == nil {
   153  			continue
   154  		}
   155  
   156  		// The group of the root closeContext should point to the Conjuncts field
   157  		// of the Vertex. As we already allocated the group, we use that allocation,
   158  		// but "move" it to v.Conjuncts.
   159  		// TODO: Is this ever necessary? It is certainly necessary to rewrite
   160  		// environments from inserted disjunction values, but expressions that
   161  		// were already added will typically need to be recomputed and recreated
   162  		// anyway. We add this in to be a bit defensive and reinvestigate once we
   163  		// have more aggressive structure sharing implemented
   164  		for i, c := range v.Conjuncts {
   165  			v.Conjuncts[i].Env = ctx.derefDisjunctsEnv(c.Env)
   166  		}
   167  
   168  		for _, t := range n.tasks {
   169  			ctx.rewriteComprehension(t)
   170  		}
   171  	}
   172  
   173  	return v.state
   174  }
   175  
   176  // unlinkOverlay unlinks helper pointers. This should be done after the
   177  // evaluation of a disjunct is complete. Keeping the linked pointers around
   178  // will allow for dereferencing a vertex to its overlay, which, in turn,
   179  // allows a disjunct to refer to parents vertices of the disjunct that
   180  // recurse into the disjunct.
   181  //
   182  // TODO(perf): consider using generation counters.
   183  func (ctx *overlayContext) unlinkOverlay() {
   184  	for _, v := range ctx.vertices {
   185  		v.overlay = nil
   186  	}
   187  }
   188  
   189  // cloneVertex copies the contents of x into a new Vertex.
   190  //
   191  // It copies all Arcs, Conjuncts, and Structs, recursively.
   192  //
   193  // TODO(perf): it would probably be faster to copy vertices on demand. But this
   194  // is more complicated and it would be worth measuring how much of a performance
   195  // benefit this gives. More importantly, we should first implement the filter
   196  // to eliminate disjunctions pre-copy based on discriminator fields and what
   197  // have you. This is not unlikely to eliminate
   198  func (ctx *overlayContext) cloneVertex(x *Vertex) *Vertex {
   199  	if x.overlay != nil {
   200  		return x.overlay
   201  	}
   202  
   203  	v := &Vertex{}
   204  	*v = *x
   205  	ctx.vertexMap[x] = v
   206  
   207  	x.overlay = v
   208  
   209  	ctx.vertices = append(ctx.vertices, x)
   210  
   211  	v.Conjuncts = slices.Clone(v.Conjuncts)
   212  
   213  	if a := x.Arcs; len(a) > 0 {
   214  		// TODO(perf): reuse buffer.
   215  		v.Arcs = make([]*Vertex, len(a))
   216  		for i, arc := range a {
   217  			// TODO(perf): reuse when finalized.
   218  			arc := ctx.cloneVertex(arc)
   219  			v.Arcs[i] = arc
   220  			arc.Parent = v
   221  		}
   222  	}
   223  
   224  	v.Structs = slices.Clone(v.Structs)
   225  
   226  	if pc := v.PatternConstraints; pc != nil {
   227  		npc := &Constraints{Allowed: pc.Allowed}
   228  		v.PatternConstraints = npc
   229  
   230  		npc.Pairs = make([]PatternConstraint, len(pc.Pairs))
   231  		for i, p := range pc.Pairs {
   232  			npc.Pairs[i] = PatternConstraint{
   233  				Pattern:    p.Pattern,
   234  				Constraint: ctx.cloneVertex(p.Constraint),
   235  			}
   236  		}
   237  	}
   238  
   239  	if v.state != nil {
   240  		v.state = ctx.cloneNodeContext(x.state)
   241  		v.state.node = v
   242  
   243  		ctx.cloneScheduler(v.state, x.state)
   244  	}
   245  
   246  	return v
   247  }
   248  
   249  // derefDisjunctsEnv creates a new env for each Environment in the Up chain with
   250  // each Environment where Vertex is "from" to one where Vertex is "to".
   251  //
   252  // TODO(perf): we could, instead, just look up the mapped vertex in
   253  // OpContext.Up. This would avoid us having to copy the Environments for each
   254  // disjunct. This requires quite a bit of plumbing, though, so we leave it as
   255  // is until this proves to be a performance issue.
   256  func (ctx *overlayContext) derefDisjunctsEnv(env *Environment) *Environment {
   257  	if env == nil {
   258  		return nil
   259  	}
   260  	up := ctx.derefDisjunctsEnv(env.Up)
   261  	to := ctx.vertexMap.deref(env.Vertex)
   262  	if up != env.Up || env.Vertex != to {
   263  		env = &Environment{
   264  			Up:           up,
   265  			Vertex:       to,
   266  			DynamicLabel: env.DynamicLabel,
   267  		}
   268  	}
   269  	return env
   270  }
   271  
   272  func (ctx *overlayContext) cloneNodeContext(n *nodeContext) *nodeContext {
   273  	n.node.getState(ctx.ctx) // ensure state is initialized.
   274  
   275  	d := n.ctx.newNodeContext(n.node)
   276  	d.underlying = n.underlying
   277  	if n.underlying == nil {
   278  		panic("unexpected nil underlying")
   279  	}
   280  
   281  	d.refCount++
   282  
   283  	d.ctx = n.ctx
   284  	d.node = n.node
   285  
   286  	d.nodeContextState = n.nodeContextState
   287  
   288  	d.arcMap = append(d.arcMap, n.arcMap...)
   289  	d.checks = append(d.checks, n.checks...)
   290  	d.sharedIDs = append(d.sharedIDs, n.sharedIDs...)
   291  
   292  	d.reqDefIDs = append(d.reqDefIDs, n.reqDefIDs...)
   293  	d.replaceIDs = append(d.replaceIDs, n.replaceIDs...)
   294  	d.conjunctInfo = append(d.conjunctInfo, n.conjunctInfo...)
   295  
   296  	// TODO: do we need to add cyclicConjuncts? Typically, cyclicConjuncts
   297  	// gets cleared at the end of a unify call. There are cases, however, where
   298  	// this is possible. We should decide whether cyclicConjuncts should be
   299  	// forced to be processed in the parent node, or that we allow it to be
   300  	// copied to the disjunction. By taking no action here, we assume it is
   301  	// processed in the parent node. Investigate whether this always will lead
   302  	// to correct results.
   303  	// d.cyclicConjuncts = append(d.cyclicConjuncts, n.cyclicConjuncts...)
   304  
   305  	if len(n.disjunctions) > 0 {
   306  		// Do not clone cc in disjunctions, as it is identified by underlying.
   307  		// We only need to clone the cc in disjunctCCs.
   308  		for _, x := range n.disjunctions {
   309  			x.env = ctx.derefDisjunctsEnv(x.env)
   310  			d.disjunctions = append(d.disjunctions, x)
   311  		}
   312  	}
   313  
   314  	return d
   315  }
   316  
   317  func (ctx *overlayContext) cloneScheduler(dst, src *nodeContext) {
   318  	ss := &src.scheduler
   319  	ds := &dst.scheduler
   320  
   321  	ds.state = ss.state
   322  	ds.completed = ss.completed
   323  	ds.needs = ss.needs
   324  	ds.provided = ss.provided
   325  	ds.counters = ss.counters
   326  
   327  	ss.blocking = ss.blocking[:0]
   328  
   329  	for _, t := range ss.tasks {
   330  		switch t.state {
   331  		case taskWAITING:
   332  			// Do not unblock previously blocked tasks, unless they are
   333  			// associated with this node.
   334  			// TODO: an edge case is when a task is blocked on another node
   335  			// within the same disjunction. We could solve this by associating
   336  			// each nodeContext with a unique ID (like a generation counter) for
   337  			// the disjunction.
   338  			if t.node != src || t.blockedOn != ss {
   339  				break
   340  			}
   341  			t.defunct = true
   342  			t := ctx.cloneTask(t, ds, ss)
   343  			ds.tasks = append(ds.tasks, t)
   344  			ds.blocking = append(ds.blocking, t)
   345  			ctx.ctx.blocking = append(ctx.ctx.blocking, t)
   346  
   347  		case taskREADY:
   348  			t.defunct = true
   349  			t := ctx.cloneTask(t, ds, ss)
   350  			ds.tasks = append(ds.tasks, t)
   351  
   352  		case taskRUNNING:
   353  			if t.run == handleDisjunctions {
   354  				continue
   355  			}
   356  
   357  			t.defunct = true
   358  			t := ctx.cloneTask(t, ds, ss)
   359  			t.state = taskREADY
   360  			ds.tasks = append(ds.tasks, t)
   361  		}
   362  	}
   363  }
   364  
   365  func (ctx *overlayContext) cloneTask(t *task, dst, src *scheduler) *task {
   366  	if t.node != src.node {
   367  		panic("misaligned node")
   368  	}
   369  
   370  	id := t.id
   371  
   372  	env := ctx.derefDisjunctsEnv(t.env)
   373  
   374  	// TODO(perf): alloc from buffer.
   375  	d := &task{
   376  		run:            t.run,
   377  		state:          t.state,
   378  		completes:      t.completes,
   379  		unblocked:      t.unblocked,
   380  		blockCondition: t.blockCondition,
   381  		err:            t.err,
   382  		env:            env,
   383  		x:              t.x,
   384  		id:             id,
   385  
   386  		node: dst.node,
   387  
   388  		// These are rewritten after everything is cloned when all vertices are
   389  		// known.
   390  		comp: t.comp,
   391  		leaf: t.leaf,
   392  	}
   393  
   394  	if t.blockedOn != nil {
   395  		if t.blockedOn != src {
   396  			panic("invalid scheduler")
   397  		}
   398  		d.blockedOn = dst
   399  	}
   400  
   401  	return d
   402  }
   403  
   404  func (ctx *overlayContext) rewriteComprehension(t *task) {
   405  	if t.comp != nil {
   406  		t.comp = ctx.mapComprehensionContext(t.comp)
   407  	}
   408  
   409  	t.leaf = ctx.mapComprehension(t.leaf)
   410  }
   411  
   412  func (ctx *overlayContext) mapComprehension(c *Comprehension) *Comprehension {
   413  	if c == nil {
   414  		return nil
   415  	}
   416  	cc := *c
   417  	cc.comp = ctx.mapComprehensionContext(cc.comp)
   418  	cc.arc = ctx.ctx.deref(cc.arc)
   419  	cc.parent = ctx.mapComprehension(cc.parent)
   420  	return &cc
   421  }
   422  
   423  func (ctx *overlayContext) mapComprehensionContext(ec *envComprehension) *envComprehension {
   424  	if ec == nil {
   425  		return nil
   426  	}
   427  
   428  	if ctx.compMap == nil {
   429  		ctx.compMap = make(map[*envComprehension]*envComprehension)
   430  	}
   431  
   432  	if ctx.compMap[ec] == nil {
   433  		x := &envComprehension{
   434  			comp:    ec.comp,
   435  			structs: ec.structs,
   436  			vertex:  ctx.ctx.deref(ec.vertex),
   437  		}
   438  		ctx.compMap[ec] = x
   439  		ec = x
   440  	}
   441  
   442  	return ec
   443  }