internal/core/adt: cache evaluation results for dynamic fields

Also cache string index lookup. This is fast, but requires
an RWLock, which can add up.

Issue #572

Change-Id: I782cf52f50aa022b069b98de9d0496775c40e30e
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8562
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/internal/core/adt/closed.go b/internal/core/adt/closed.go
index acf50ac..3e1e434 100644
--- a/internal/core/adt/closed.go
+++ b/internal/core/adt/closed.go
@@ -310,6 +310,10 @@
 	ctx.generation++
 	ctx.todo = nil
 
+	// TODO(perf): more aggressively determine whether a struct is open or
+	// closed: open structs do not have to be checked, yet they can particularly
+	// be the ones with performance isssues, for instanced as a result of
+	// embedded for comprehensions.
 	for _, s := range n.Structs {
 		if !s.useForAccept() {
 			continue
@@ -317,11 +321,16 @@
 		markCounts(ctx, s.CloseInfo)
 	}
 
+	str := ""
+	if f.IsString() {
+		str = f.StringValue(ctx)
+	}
+
 	for _, s := range n.Structs {
 		if !s.useForAccept() {
 			continue
 		}
-		if verifyArc(ctx, s, f) {
+		if verifyArc(ctx, s, f, str) {
 			// Beware: don't add to below expression: this relies on the
 			// side effects of markUp.
 			ok := markUp(ctx, s.closeInfo, 0)
@@ -435,7 +444,7 @@
 	return x
 }
 
-func verifyArc(ctx *OpContext, s *StructInfo, f Feature) bool {
+func verifyArc(ctx *OpContext, s *StructInfo, f Feature, label string) bool {
 	isRegular := f.IsRegular()
 
 	o := s.StructLit
@@ -455,10 +464,16 @@
 		return false
 	}
 
-	for _, b := range o.Dynamic {
-		m := dynamicMatcher{b.Key}
-		if m.Match(ctx, env, f) {
-			return true
+	if f.IsString() {
+		for _, b := range o.Dynamic {
+			v := env.evalCached(ctx, b.Key)
+			s, ok := v.(*String)
+			if !ok {
+				continue
+			}
+			if label == s.Str {
+				return true
+			}
 		}
 	}
 
diff --git a/internal/core/adt/composite.go b/internal/core/adt/composite.go
index b476662..7cbaef4 100644
--- a/internal/core/adt/composite.go
+++ b/internal/core/adt/composite.go
@@ -228,6 +228,8 @@
 	Embedding bool
 }
 
+// TODO(perf): this could be much more aggressive for eliminating structs that
+// are immaterial for closing.
 func (s *StructInfo) useForAccept() bool {
 	if c := s.closeInfo; c != nil {
 		return !c.noCheck
diff --git a/internal/core/adt/optional.go b/internal/core/adt/optional.go
index 05568bd..8a452c9 100644
--- a/internal/core/adt/optional.go
+++ b/internal/core/adt/optional.go
@@ -125,26 +125,6 @@
 	return false
 }
 
-type dynamicMatcher struct {
-	expr Expr
-}
-
-func (m dynamicMatcher) Match(c *OpContext, env *Environment, f Feature) bool {
-	if !f.IsRegular() || !f.IsString() {
-		return false
-	}
-	v, ok := c.Evaluate(env, m.expr)
-	if !ok {
-		return false
-	}
-	s, ok := v.(*String)
-	if !ok {
-		return false
-	}
-	label := f.StringValue(c)
-	return label == s.Str
-}
-
 type patternMatcher Conjunct
 
 func (m patternMatcher) Match(c *OpContext, env *Environment, f Feature) bool {