Skip to content

Commit

Permalink
internal/core/adt: some optimizations for pattern constraints
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
mpvl committed Feb 8, 2021
1 parent 25ba1d9 commit 48d255a
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 65 deletions.
20 changes: 13 additions & 7 deletions internal/core/adt/closed.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ func Accept(ctx *OpContext, n *Vertex, f Feature) (found, required bool) {
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
Expand All @@ -319,11 +321,12 @@ func Accept(ctx *OpContext, n *Vertex, f Feature) (found, required bool) {
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 {
Expand Down Expand Up @@ -444,7 +447,7 @@ func getScratch(ctx *OpContext, s *closeInfo) *closeStats {
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
Expand All @@ -464,21 +467,24 @@ func verifyArc(ctx *OpContext, s *StructInfo, f Feature, label string) bool {
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
}
}
Expand Down
14 changes: 9 additions & 5 deletions internal/core/adt/composite.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ type ID int32
// 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 {
Expand Down Expand Up @@ -488,11 +491,12 @@ func Unwrap(v Value) Value {
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 {
Expand Down
55 changes: 55 additions & 0 deletions internal/core/adt/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ func (o *StructLit) Init() {
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
Expand Down Expand Up @@ -617,6 +622,56 @@ func (x *BoundValue) validate(c *OpContext, y Value) *Bottom {
}
}

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
Expand Down
87 changes: 34 additions & 53 deletions internal/core/adt/optional.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ outer:
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
Expand All @@ -51,7 +56,7 @@ outer:
// 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))
Expand All @@ -77,67 +82,43 @@ outer:
}
}

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
}

m := getMatcher(c, env, v)
if m == nil {
return false
}
func matchBulk(c *OpContext, env *Environment, x *BulkOptionalField, f Feature, label Value) bool {
v := env.evalCached(c, x.Filter)

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)

default:
return newPatternMatcher(c, env, v)
}
}

type fieldMatcher interface {
Match(c *OpContext, env *Environment, f Feature) bool
}
return x.K&StringKind != 0

type typeMatcher Kind
case *BoundValue:
switch x.Kind() {
case StringKind:
if label == nil {
label = f.ToValue(c)
}
str := label.(*String).Str
return x.validateStr(c, str)

func (m typeMatcher) Match(c *OpContext, env *Environment, f Feature) bool {
switch f.Typ() {
case StringLabel:
return Kind(m)&StringKind != 0
case IntKind:
return x.validateInt(c, int64(f.Index()))
}
}

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)
}

0 comments on commit 48d255a

Please sign in to comment.