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 }