internal/core/adt: some optimizations for pattern constraints
Along the lines of the optimizations done for Issue #572.
This doesn't improve much, but prevents some allocations
and effect is still noticeable.
Change-Id: I0558e2b3da8710919ddaa1fdbeeb4da0b704d1e1
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/8563
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 3e1e434..9cf590b 100644
--- a/internal/core/adt/closed.go
+++ b/internal/core/adt/closed.go
@@ -310,6 +310,8 @@
ctx.generation++
ctx.todo = nil
+ var optionalTypes OptionalType
+
// 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
@@ -319,11 +321,12 @@
continue
}
markCounts(ctx, s.CloseInfo)
+ optionalTypes |= s.types
}
- str := ""
- if f.IsString() {
- str = f.StringValue(ctx)
+ var str Value
+ if optionalTypes&(HasComplexPattern|HasDynamic) != 0 && f.IsString() {
+ str = f.ToValue(ctx)
}
for _, s := range n.Structs {
@@ -444,7 +447,7 @@
return x
}
-func verifyArc(ctx *OpContext, s *StructInfo, f Feature, label string) bool {
+func verifyArc(ctx *OpContext, s *StructInfo, f Feature, label Value) bool {
isRegular := f.IsRegular()
o := s.StructLit
@@ -464,21 +467,24 @@
return false
}
- if f.IsString() {
+ if len(o.Dynamic) > 0 && f.IsString() {
+ if label == nil && f.IsString() {
+ label = f.ToValue(ctx)
+ }
for _, b := range o.Dynamic {
v := env.evalCached(ctx, b.Key)
s, ok := v.(*String)
if !ok {
continue
}
- if label == s.Str {
+ if label.(*String).Str == s.Str {
return true
}
}
}
for _, b := range o.Bulk {
- if matchBulk(ctx, env, b, f) {
+ if matchBulk(ctx, env, b, f, label) {
return true
}
}
diff --git a/internal/core/adt/composite.go b/internal/core/adt/composite.go
index 7cbaef4..6f19b19 100644
--- a/internal/core/adt/composite.go
+++ b/internal/core/adt/composite.go
@@ -130,6 +130,9 @@
// evalCached is used to look up let expressions. Caching let expressions
// prevents a possible combinatorial explosion.
func (e *Environment) evalCached(c *OpContext, x Expr) Value {
+ if v, ok := x.(Value); ok {
+ return v
+ }
v, ok := e.cache[x]
if !ok {
if e.cache == nil {
@@ -488,11 +491,12 @@
type OptionalType int
const (
- HasField OptionalType = 1 << iota // X: T
- HasDynamic // (X): T or "\(X)": T
- HasPattern // [X]: T
- HasAdditional // ...T
- IsOpen // Defined for all fields
+ HasField OptionalType = 1 << iota // X: T
+ HasDynamic // (X): T or "\(X)": T
+ HasPattern // [X]: T
+ HasComplexPattern // anything but a basic type
+ HasAdditional // ...T
+ IsOpen // Defined for all fields
)
func (v *Vertex) Kind() Kind {
diff --git a/internal/core/adt/expr.go b/internal/core/adt/expr.go
index 12d0b6f..d4130ce 100644
--- a/internal/core/adt/expr.go
+++ b/internal/core/adt/expr.go
@@ -121,6 +121,11 @@
case *BulkOptionalField:
o.Bulk = append(o.Bulk, x)
o.types |= HasPattern
+ switch x.Filter.(type) {
+ case *BasicType, *Top:
+ default:
+ o.types |= HasComplexPattern
+ }
case *Ellipsis:
expr := x.Value
@@ -617,6 +622,56 @@
}
}
+func (x *BoundValue) validateStr(c *OpContext, a string) bool {
+ if str, ok := x.Value.(*String); ok {
+ b := str.Str
+ switch x.Op {
+ case LessEqualOp:
+ return a <= b
+ case LessThanOp:
+ return a < b
+ case GreaterEqualOp:
+ return a >= b
+ case GreaterThanOp:
+ return a > b
+ case EqualOp:
+ return a == b
+ case NotEqualOp:
+ return a != b
+ case MatchOp:
+ return c.regexp(x.Value).MatchString(a)
+ case NotMatchOp:
+ return !c.regexp(x.Value).MatchString(a)
+ }
+ }
+ return x.validate(c, &String{Str: a}) == nil
+}
+
+func (x *BoundValue) validateInt(c *OpContext, a int64) bool {
+ switch n := x.Value.(type) {
+ case *Num:
+ b, err := n.X.Int64()
+ if err != nil {
+ break
+ }
+ switch x.Op {
+ case LessEqualOp:
+ return a <= b
+ case LessThanOp:
+ return a < b
+ case GreaterEqualOp:
+ return a >= b
+ case GreaterThanOp:
+ return a > b
+ case EqualOp:
+ return a == b
+ case NotEqualOp:
+ return a != b
+ }
+ }
+ return x.validate(c, c.NewInt64(a)) == nil
+}
+
// A NodeLink is used during computation to refer to an existing Vertex.
// It is used to signal a potential cycle or reference.
// Note that a NodeLink may be used as a value. This should be taken into
diff --git a/internal/core/adt/optional.go b/internal/core/adt/optional.go
index 8a452c9..64cd4c4 100644
--- a/internal/core/adt/optional.go
+++ b/internal/core/adt/optional.go
@@ -40,6 +40,11 @@
return
}
+ var label Value
+ if o.types&HasComplexPattern != 0 && arc.Label.IsString() {
+ label = arc.Label.ToValue(c)
+ }
+
if len(o.Bulk) > 0 {
bulkEnv := *env
bulkEnv.DynamicLabel = arc.Label
@@ -51,7 +56,7 @@
// if matched && f.additional {
// continue
// }
- if matchBulk(c, env, b, arc.Label) {
+ if matchBulk(c, env, b, arc.Label, label) {
matched = true
info := closeInfo.SpawnSpan(b.Value, ConstraintSpan)
arc.AddConjunct(MakeConjunct(&bulkEnv, b, info))
@@ -77,67 +82,43 @@
}
}
-func matchBulk(c *OpContext, env *Environment, x *BulkOptionalField, f Feature) bool {
- v, ok := c.Evaluate(env, x.Filter)
- if !ok {
- // TODO: handle dynamically
- return false
- }
+func matchBulk(c *OpContext, env *Environment, x *BulkOptionalField, f Feature, label Value) bool {
+ v := env.evalCached(c, x.Filter)
- m := getMatcher(c, env, v)
- if m == nil {
- return false
- }
-
- c.inConstraint++
- ret := m.Match(c, env, f)
- c.inConstraint--
- return ret
-}
-
-func getMatcher(c *OpContext, env *Environment, v Value) fieldMatcher {
- switch f := v.(type) {
+ // Fast-track certain cases.
+ switch x := v.(type) {
case *Top:
- return typeMatcher(TopKind)
+ return true
case *BasicType:
- return typeMatcher(f.K)
+ return x.K&StringKind != 0
- default:
- return newPatternMatcher(c, env, v)
+ case *BoundValue:
+ switch x.Kind() {
+ case StringKind:
+ if label == nil {
+ label = f.ToValue(c)
+ }
+ str := label.(*String).Str
+ return x.validateStr(c, str)
+
+ case IntKind:
+ return x.validateInt(c, int64(f.Index()))
+ }
}
-}
-type fieldMatcher interface {
- Match(c *OpContext, env *Environment, f Feature) bool
-}
-
-type typeMatcher Kind
-
-func (m typeMatcher) Match(c *OpContext, env *Environment, f Feature) bool {
- switch f.Typ() {
- case StringLabel:
- return Kind(m)&StringKind != 0
-
- case IntLabel:
- return Kind(m)&IntKind != 0
+ n := Vertex{}
+ m := MakeRootConjunct(env, v)
+ n.AddConjunct(m)
+ if label == nil {
+ label = f.ToValue(c)
}
- return false
-}
+ n.AddConjunct(MakeRootConjunct(m.Env, label))
-type patternMatcher Conjunct
+ c.inConstraint++
+ n.Finalize(c)
+ c.inConstraint--
-func (m patternMatcher) Match(c *OpContext, env *Environment, f Feature) bool {
- v := Vertex{}
- v.AddConjunct(Conjunct(m))
- label := f.ToValue(c)
- v.AddConjunct(MakeRootConjunct(m.Env, label))
- v.Finalize(c)
- b, _ := v.BaseValue.(*Bottom)
+ b, _ := n.BaseValue.(*Bottom)
return b == nil
}
-
-func newPatternMatcher(ctx *OpContext, env *Environment, x Value) fieldMatcher {
- c := MakeRootConjunct(env, x)
- return patternMatcher(c)
-}