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 }