cuelang.org/go@v0.10.1/internal/core/adt/share.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  //     https://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  // This file contains logic regarding structure sharing.
    18  
    19  // Notes
    20  //
    21  // TODO:
    22  // We may want to consider tracking closedness in parallel to the Vertex
    23  // structure, for instance in a CloseInfo or in a cue.Value itself.
    24  //
    25  //     reg: {}
    26  //     #def: sub: reg
    27  //
    28  // By tracking closedness inside the CloseInfo, we can still share the
    29  // structure and only have to change
    30  //
    31  // Maybe this is okay, though, as #Def itself can be shared, at least.
    32  
    33  func (n *nodeContext) unshare() {
    34  	n.noSharing = true
    35  
    36  	if !n.isShared {
    37  		return
    38  	}
    39  	n.isShared = false
    40  	n.node.IsShared = false
    41  
    42  	v := n.node.BaseValue.(*Vertex)
    43  
    44  	// TODO: the use of cycle for BaseValue is getting increasingly outdated.
    45  	// Find another mechanism once we get rid of the old evaluator.
    46  	n.node.BaseValue = n.origBaseValue
    47  
    48  	n.scheduleVertexConjuncts(n.shared, v, n.sharedID)
    49  
    50  	n.sharedID.cc.decDependent(n.ctx, SHARED, n.node.cc)
    51  	n.sharedID.cc = nil
    52  }
    53  
    54  // finalizeSharing should be called when it is known for sure a node can be
    55  // shared.
    56  func (n *nodeContext) finalizeSharing() {
    57  	if n.sharedID.cc != nil {
    58  		n.sharedID.cc.decDependent(n.ctx, SHARED, n.node.cc)
    59  		n.sharedID.cc = nil
    60  	}
    61  }
    62  
    63  func (n *nodeContext) share(c Conjunct, arc *Vertex, id CloseInfo) {
    64  	if n.isShared {
    65  		panic("already sharing")
    66  	}
    67  	n.origBaseValue = n.node.BaseValue
    68  	n.node.BaseValue = arc
    69  	n.node.IsShared = true
    70  	n.isShared = true
    71  	n.shared = c
    72  	n.sharedID = id
    73  
    74  	// At this point, the node may still be unshared at a later point. For this
    75  	// purpose we need to keep the retain count above zero until all conjuncts
    76  	// have been processed and it is clear that sharing is possible. Delaying
    77  	// such a count should not hurt performance, as a shared node is completed
    78  	// anyway.
    79  	id.cc.incDependent(n.ctx, SHARED, n.node.cc)
    80  }
    81  
    82  func (n *nodeContext) shareIfPossible(c Conjunct, arc *Vertex, id CloseInfo) bool {
    83  	// TODO: have an experiment here to enable or disable structure sharing.
    84  	// return false
    85  	if !n.ctx.Sharing {
    86  		return false
    87  	}
    88  
    89  	if n.noSharing || n.isShared || n.ctx.errs != nil {
    90  		return false
    91  	}
    92  
    93  	// This line is to deal with this case:
    94  	//
    95  	//     reg: {}
    96  	//     #def: sub: reg
    97  	//
    98  	// Ideally we find a different solution, like passing closedness
    99  	// down elsewhere. In fact, as we do this in closeContexts, it probably
   100  	// already works, it will just not be reflected in the debug output.
   101  	// We could fix that by not printing structure shared nodes, which is
   102  	// probably a good idea anyway.
   103  	//
   104  	// TODO: come up with a mechanism to allow this case.
   105  	if n.node.Closed && !arc.Closed {
   106  		return false
   107  	}
   108  
   109  	// Sharing let expressions is not supported and will result in unmarked
   110  	// structural cycles. Processing will still terminate, but printing the
   111  	// result will result in an infinite loop.
   112  	//
   113  	// TODO: allow this case.
   114  	if n.node.Label.IsLet() {
   115  		return false
   116  	}
   117  
   118  	// If an arc is a computed intermediate result and not part of a CUE output,
   119  	// it should not be shared.
   120  	if n.node.nonRooted || arc.nonRooted {
   121  		return false
   122  	}
   123  
   124  	n.share(c, arc, id)
   125  	return true
   126  }
   127  
   128  // Vertex values that are held in BaseValue will be wrapped in the following
   129  // order:
   130  //
   131  //    disjuncts -> (shared | computed | data)
   132  //
   133  // DerefDisjunct
   134  //   - get the current value under computation
   135  //
   136  // DerefValue
   137  //   - get the value the node ultimately represents.
   138  //
   139  
   140  // DerefValue unrolls indirections of Vertex values. These may be introduced,
   141  // for instance, by temporary bindings such as comprehension values.
   142  // It returns v itself if v does not point to another Vertex.
   143  func (v *Vertex) DerefValue() *Vertex {
   144  	for {
   145  		arc, ok := v.BaseValue.(*Vertex)
   146  		if !ok {
   147  			return v
   148  		}
   149  		v = arc
   150  	}
   151  }
   152  
   153  // DerefDisjunct indirects a node that points to a disjunction.
   154  func (v *Vertex) DerefDisjunct() *Vertex {
   155  	for {
   156  		arc, ok := v.BaseValue.(*Vertex)
   157  		if !ok || !arc.IsDisjunct {
   158  			return v
   159  		}
   160  		v = arc
   161  	}
   162  }
   163  
   164  // DerefNonDisjunct indirects a node that points to a disjunction.
   165  func (v *Vertex) DerefNonDisjunct() *Vertex {
   166  	for {
   167  		arc, ok := v.BaseValue.(*Vertex)
   168  		if !ok || arc.IsDisjunct {
   169  			return v
   170  		}
   171  		v = arc
   172  	}
   173  }
   174  
   175  // DerefNonRooted indirects a node that points to a value that is not rooted.
   176  // This includes structure-shared nodes that point to a let field: let fields
   177  // may or may not be part of a struct, and thus should be treated as non-rooted.
   178  func (v *Vertex) DerefNonRooted() *Vertex {
   179  	for {
   180  		arc, ok := v.BaseValue.(*Vertex)
   181  		if !ok || arc.IsDisjunct || (v.IsShared && !arc.Label.IsLet()) {
   182  			return v
   183  		}
   184  		v = arc
   185  	}
   186  }
   187  
   188  // DerefNonShared finds the indirection of an arc that is not the result of
   189  // structure sharing. This is especially relevant when indirecting disjunction
   190  // values.
   191  func (v *Vertex) DerefNonShared() *Vertex {
   192  	if v.state != nil && v.state.isShared {
   193  		return v
   194  	}
   195  	for {
   196  		arc, ok := v.BaseValue.(*Vertex)
   197  		if !ok {
   198  			return v
   199  		}
   200  		v = arc
   201  	}
   202  }