Skip to content

Commit 7e4dff2

Browse files
committed
internal/core/adt: delay stringifying nodes in errors via fmt.Stringer
Particularly given that OpContext.Str ends up calling debug.NodeString, which is relatively expensive to call when creating many errors. Thousands of errors can easily be created when disjunctions are used. Two of the projects which still see high memory usage see a reduction of about 3% in allocated objects or space. Automata sees a mild saving in CPU cost, as it spent 3% of CPU there. │ old │ new │ │ sec/op │ sec/op vs base │ VetAutomata 551.1m ± 3% 525.1m ± 4% -4.72% (p=0.034 n=8+10) │ old │ new │ │ B/op │ B/op vs base │ VetAutomata 627.1Mi ± 0% 609.6Mi ± 0% -2.78% (p=0.000 n=8+10) │ old │ new │ │ allocs/op │ allocs/op vs base │ VetAutomata 4.442M ± 0% 4.436M ± 0% -0.14% (p=0.000 n=8+10) │ old │ new │ │ sec/op │ sec/op vs base │ VetCaascad 4.482 ± 2% 4.427 ± 2% ~ (p=0.721 n=8) │ old │ new │ │ B/op │ B/op vs base │ VetCaascad 3.318Gi ± 0% 3.302Gi ± 0% -0.50% (p=0.000 n=8) │ old │ new │ │ allocs/op │ allocs/op vs base │ VetCaascad 12.85M ± 0% 12.49M ± 0% -2.80% (p=0.000 n=8) Move OpContext.Str to OpContext.String, and add the new fmt.Stringer implementation in place of the old OpContext.Str. This way, the majority of callers, which just need to stringify a node for formatting inside an error via %s, can stay as they are. It also makes sense for the longer method name to be the one that does the work upfront. Note that some error messages change due to the use of a shallow copy and the delayed printing of the copied value. We used to print the value upfront, before it was fully evaluated, whereas we print it a bit later now, once e.g. `1 & 1` has become `1`, and `if true` has been removed. Updates #3334. Signed-off-by: Daniel Martí <[email protected]> Change-Id: Iea539dbf6a449f7e1268729c5d44266481c018bc Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1217254 Reviewed-by: Marcel van Lohuizen <[email protected]> TryBot-Result: CUEcueckoo <[email protected]> Unity-Result: CUE porcuepine <[email protected]>
1 parent 2adf5e9 commit 7e4dff2

File tree

7 files changed

+40
-38
lines changed

7 files changed

+40
-38
lines changed

cue/testdata/builtins/validators.txtar

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -555,15 +555,6 @@ Result:
555555
diff old new
556556
--- old
557557
+++ new
558-
@@ -31,7 +31,7 @@
559-
./issue3418.cue:10:16
560-
./issue3418.cue:10:18
561-
./issue3418.cue:11:5
562-
-issue3474.structValidator.failAfter.A: invalid value {C:true,B*:if true true} (does not satisfy struct.MaxFields(1)): len(fields) > MaxFields(1) (2 > 1):
563-
+issue3474.structValidator.failAfter.A: invalid value {C:true,B:true} (does not satisfy struct.MaxFields(1)): len(fields) > MaxFields(1) (2 > 1):
564-
./issue3474.cue:12:5
565-
./issue3474.cue:12:22
566-
./issue3474.cue:13:5
567558
@@ -51,6 +51,11 @@
568559
./issue3474.cue:53:5
569560
./issue3474.cue:53:12
@@ -589,15 +580,6 @@ diff old new
589580
_a: (_|_){
590581
// [incomplete] issue2098.incomplete1._a: invalid value [] (does not satisfy list.MinItems(1)): len(list) < MinItems(1) (0 < 1):
591582
// ./in.cue:112:6
592-
@@ -209,7 +212,7 @@
593-
failAfter: (_|_){
594-
// [eval]
595-
A: (_|_){
596-
- // [eval] issue3474.structValidator.failAfter.A: invalid value {C:true,B*:if true true} (does not satisfy struct.MaxFields(1)): len(fields) > MaxFields(1) (2 > 1):
597-
+ // [eval] issue3474.structValidator.failAfter.A: invalid value {C:true,B:true} (does not satisfy struct.MaxFields(1)): len(fields) > MaxFields(1) (2 > 1):
598-
// ./issue3474.cue:12:5
599-
// ./issue3474.cue:12:22
600-
// ./issue3474.cue:13:5
601583
@@ -270,6 +273,11 @@
602584
// ./issue3474.cue:53:5
603585
// ./issue3474.cue:53:12
@@ -677,7 +659,7 @@ issue3418.t4.x: invalid value "foo" (does not satisfy matchN): conflicting value
677659
./issue3418.cue:10:16
678660
./issue3418.cue:10:18
679661
./issue3418.cue:11:5
680-
issue3474.structValidator.failAfter.A: invalid value {C:true,B*:if true true} (does not satisfy struct.MaxFields(1)): len(fields) > MaxFields(1) (2 > 1):
662+
issue3474.structValidator.failAfter.A: invalid value {C:true,B:true} (does not satisfy struct.MaxFields(1)): len(fields) > MaxFields(1) (2 > 1):
681663
./issue3474.cue:12:5
682664
./issue3474.cue:12:22
683665
./issue3474.cue:13:5
@@ -855,7 +837,7 @@ Result:
855837
failAfter: (_|_){
856838
// [eval]
857839
A: (_|_){
858-
// [eval] issue3474.structValidator.failAfter.A: invalid value {C:true,B*:if true true} (does not satisfy struct.MaxFields(1)): len(fields) > MaxFields(1) (2 > 1):
840+
// [eval] issue3474.structValidator.failAfter.A: invalid value {C:true,B:true} (does not satisfy struct.MaxFields(1)): len(fields) > MaxFields(1) (2 > 1):
859841
// ./issue3474.cue:12:5
860842
// ./issue3474.cue:12:22
861843
// ./issue3474.cue:13:5

cue/testdata/definitions/fields.txtar

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,7 @@ diff old new
866866
- ./validators.cue:18:6
867867
- ./validators.cue:23:5
868868
./validators.cue:24:5
869-
-validator.open.data: invalid value {trailingComma:_|_(validator.open.data.trailingComma: field not allowed),#x:{trailingComma?:"all"}} (does not satisfy matchN): 0 matched, expected 1:
869+
-validator.open.data: invalid value {trailingComma:_|_(validator.open.data.trailingComma: field not allowed),#x:_|_(validator.open.data.trailingComma: field not allowed)} (does not satisfy matchN): 0 matched, expected 1:
870870
- ./validators.cue:10:3
871871
- ./validators.cue:10:10
872872
- ./validators.cue:13:8
@@ -1206,7 +1206,7 @@ diff old new
12061206
}
12071207
}
12081208
- data: (_|_){
1209-
- // [eval] validator.open.data: invalid value {trailingComma:_|_(validator.open.data.trailingComma: field not allowed),#x:{trailingComma?:"all"}} (does not satisfy matchN): 0 matched, expected 1:
1209+
- // [eval] validator.open.data: invalid value {trailingComma:_|_(validator.open.data.trailingComma: field not allowed),#x:_|_(validator.open.data.trailingComma: field not allowed)} (does not satisfy matchN): 0 matched, expected 1:
12101210
- // ./validators.cue:10:3
12111211
- // ./validators.cue:10:10
12121212
- // ./validators.cue:13:8
@@ -1318,7 +1318,7 @@ validator.keepClosed.x.c: field not allowed:
13181318
./validators.cue:18:6
13191319
./validators.cue:23:5
13201320
./validators.cue:24:5
1321-
validator.open.data: invalid value {trailingComma:_|_(validator.open.data.trailingComma: field not allowed),#x:{trailingComma?:"all"}} (does not satisfy matchN): 0 matched, expected 1:
1321+
validator.open.data: invalid value {trailingComma:_|_(validator.open.data.trailingComma: field not allowed),#x:_|_(validator.open.data.trailingComma: field not allowed)} (does not satisfy matchN): 0 matched, expected 1:
13221322
./validators.cue:10:3
13231323
./validators.cue:10:10
13241324
./validators.cue:13:8
@@ -1811,7 +1811,7 @@ Result:
18111811
}
18121812
}
18131813
data: (_|_){
1814-
// [eval] validator.open.data: invalid value {trailingComma:_|_(validator.open.data.trailingComma: field not allowed),#x:{trailingComma?:"all"}} (does not satisfy matchN): 0 matched, expected 1:
1814+
// [eval] validator.open.data: invalid value {trailingComma:_|_(validator.open.data.trailingComma: field not allowed),#x:_|_(validator.open.data.trailingComma: field not allowed)} (does not satisfy matchN): 0 matched, expected 1:
18151815
// ./validators.cue:10:3
18161816
// ./validators.cue:10:10
18171817
// ./validators.cue:13:8

cue/testdata/disjunctions/elimination.txtar

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1842,15 +1842,6 @@ diff old new
18421842
disambiguateClosed: (struct){
18431843
b: (#struct){ |((#struct){
18441844
x: (bool){ true }
1845-
@@ -181,7 +127,7 @@
1846-
x: ((null|struct)){ |((struct){
1847-
a: (struct){
1848-
b: (_|_){
1849-
- // [incomplete] nestedNonMonotonic.incomplete.b.n2.p1.x.a.b: invalid value {c:1 & 1 & 1,d:1 & 1 & 1} (does not satisfy struct.MinFields(3)): len(fields) < MinFields(3) (2 < 3):
1850-
+ // [incomplete] nestedNonMonotonic.incomplete.b.n2.p1.x.a.b: invalid value {c:1,d:1} (does not satisfy struct.MinFields(3)): len(fields) < MinFields(3) (2 < 3):
1851-
// ./in.cue:138:15
1852-
// ./in.cue:124:15
1853-
// ./in.cue:125:12
18541845
@@ -349,10 +295,6 @@
18551846
#type: (#struct){
18561847
fieldName: ((string|struct)){ |((string){ string }, (#struct){
@@ -2905,7 +2896,7 @@ Result:
29052896
x: ((null|struct)){ |((struct){
29062897
a: (struct){
29072898
b: (_|_){
2908-
// [incomplete] nestedNonMonotonic.incomplete.b.n2.p1.x.a.b: invalid value {c:1 & 1 & 1,d:1 & 1 & 1} (does not satisfy struct.MinFields(3)): len(fields) < MinFields(3) (2 < 3):
2899+
// [incomplete] nestedNonMonotonic.incomplete.b.n2.p1.x.a.b: invalid value {c:1,d:1} (does not satisfy struct.MinFields(3)): len(fields) < MinFields(3) (2 < 3):
29092900
// ./in.cue:138:15
29102901
// ./in.cue:124:15
29112902
// ./in.cue:125:12

cue/types_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2890,7 +2890,7 @@ func TestValueLookup(t *testing.T) {
28902890
t.Errorf("exists: got %v; want %v", got, tc.notExists)
28912891
}
28922892

2893-
got := cue.ValueCtx(v).Str(cue.ValueVertex(v))
2893+
got := cue.ValueCtx(v).String(cue.ValueVertex(v))
28942894
if tc.str == "" {
28952895
t.Fatalf("str empty, got %q", got)
28962896
}

internal/core/adt/context.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,14 +1448,26 @@ func (c *OpContext) newList(src ast.Node, parent *Vertex) *Vertex {
14481448
return c.newInlineVertex(parent, &ListMarker{})
14491449
}
14501450

1451-
// Str reports a debug string of x.
1452-
func (c *OpContext) Str(x Node) string {
1451+
// String reports a string of x, for use in errors or debugging.
1452+
// Use [OpContext.Str] instead for %s format arguments, as it delays the work.
1453+
func (c *OpContext) String(x Node) string {
14531454
if c.Format == nil {
14541455
return fmt.Sprintf("%T", x)
14551456
}
14561457
return c.Format(c.Runtime, x)
14571458
}
14581459

1460+
type stringerFunc func() string
1461+
1462+
func (f stringerFunc) String() string { return f() }
1463+
1464+
// Str reports a string of x via a [fmt.Stringer], for use in errors or debugging.
1465+
func (c *OpContext) Str(x Node) fmt.Stringer {
1466+
return stringerFunc(func() string {
1467+
return c.String(x)
1468+
})
1469+
}
1470+
14591471
// NewList returns a new list for the given values.
14601472
func (c *OpContext) NewList(values ...Value) *Vertex {
14611473
// TODO: consider making this a literal list instead.

internal/core/adt/errors.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,21 @@ func (c *OpContext) NewPosf(p token.Pos, format string, args ...interface{}) *Va
380380
switch x := arg.(type) {
381381
case Node:
382382
a = appendNodePositions(a, x)
383+
// Wrap nodes in a [fmt.Stringer] which delays the call to
384+
// [OpContext.Str] until the error needs to be rendered.
385+
// This helps avoid work, as in many cases,
386+
// errors are created but never shown to the user.
387+
//
388+
// A Vertex will set an error as its BaseValue via a Bottom node,
389+
// which might be this error we are creating.
390+
// Using the Vertex directly could then lead to endless recursion.
391+
// Make a shallow copy to avoid that.
392+
if v, ok := x.(*Vertex); ok {
393+
// TODO(perf): we could join this allocation with the creation
394+
// of the stringer below.
395+
vcopy := *v
396+
x = &vcopy
397+
}
383398
args[i] = c.Str(x)
384399
case ast.Node:
385400
// TODO: ideally the core evaluator should not depend on higher

internal/core/adt/expr.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1897,6 +1897,8 @@ func validateWithBuiltin(call *CallContext) *Bottom {
18971897
return b
18981898
}
18991899
// failed:
1900+
// TODO(mvdan): building this buffer should be part of the error format and arguments,
1901+
// e.g. any logic needed here can be wrapped in an [fmt.Stringer].
19001902
var buf bytes.Buffer
19011903
buf.WriteString(b.qualifiedName(c))
19021904

@@ -1909,7 +1911,7 @@ func validateWithBuiltin(call *CallContext) *Bottom {
19091911
if i > 0 {
19101912
_, _ = buf.WriteString(", ")
19111913
}
1912-
buf.WriteString(c.Str(a))
1914+
buf.WriteString(c.String(a))
19131915
}
19141916
buf.WriteString(")")
19151917
}

0 commit comments

Comments
 (0)