1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
package eval

import (
	"testing"
	"unsafe"
)

// TestCurryEnv2Layout pins the two structural facts the fusion rests on: that
// the pair really is one object's worth of memory, and that a frame carved out
// of it is laid out exactly as newEnv would have laid it out.
//
// The size assertion is not decoration. The fusion is only free because two
// 48-byte frames come to 96, which is a Go size class; if Env grows by a word
// the pair lands in the 112 class and the saving is spent on padding. That
// would not break anything, so nothing else would catch it.
func TestCurryEnv2Layout(t *testing.T) {
	if got, want := unsafe.Sizeof(Env{}), uintptr(48); got != want {
		t.Errorf("sizeof(Env) = %d, want %d — re-check that two frames still "+
			"land in a size class before keeping curryEnv2", got, want)
	}
	if got, want := unsafe.Sizeof(curryEnv2{}), 2*unsafe.Sizeof(Env{}); got != want {
		t.Errorf("sizeof(curryEnv2) = %d, want %d (no padding between frames)", got, want)
	}

	var ce curryEnv2
	up := newEnv(&EvalState{}, nil, 1)
	ce.outer.initOne(&EvalState{}, up, VInt{V: 1})
	ce.inner.initOne(&EvalState{}, &ce.outer, VInt{V: 2})

	if ce.inner.Up != &ce.outer {
		t.Errorf("inner.Up = %p, want %p", ce.inner.Up, &ce.outer)
	}
	if ce.outer.Up != up {
		t.Errorf("outer.Up = %p, want %p", ce.outer.Up, up)
	}

	// Each frame must use its *own* inline array. Writing through Values and
	// reading back through inline is what catches a frame whose slice points
	// at its neighbour — which would be invisible in ordinary evaluation until
	// two arguments silently aliased.
	ce.outer.Values[0] = VInt{V: 10}
	ce.inner.Values[0] = VInt{V: 20}
	if got := ce.outer.inline[0].(VInt).V; got != 10 {
		t.Errorf("outer write did not land in outer.inline: got %d", got)
	}
	if got := ce.inner.inline[0].(VInt).V; got != 20 {
		t.Errorf("inner write did not land in inner.inline: got %d", got)
	}
}

// TestInitOneMatchesNewEnv checks that a carved frame is indistinguishable
// from an allocated one, which is the whole contract of initOne.
//
// Note what this deliberately does *not* claim to pin: the three-index slice
// in initOne is untestable while inline is exactly one element, because
// inline[:1] already has capacity one. Deleting the cap today changes nothing
// observable, so no test can catch it — the guard is the comment there, and
// this test would only begin to bite if inline were ever widened.
func TestInitOneMatchesNewEnv(t *testing.T) {
	sWant := &EvalState{}
	up := newEnv(sWant, nil, 1)

	want := newEnv(sWant, up, 1)
	want.Values[0] = VInt{V: 7}

	sGot := &EvalState{}
	var got Env
	got.initOne(sGot, up, VInt{V: 7})

	if got.Up != want.Up {
		t.Errorf("Up = %p, want %p", got.Up, want.Up)
	}
	if len(got.Values) != len(want.Values) || cap(got.Values) != cap(want.Values) {
		t.Errorf("Values len/cap = %d/%d, want %d/%d",
			len(got.Values), cap(got.Values), len(want.Values), cap(want.Values))
	}
	if got.Values[0] != want.Values[0] {
		t.Errorf("Values[0] = %v, want %v", got.Values[0], want.Values[0])
	}
	// The slice must alias the frame's own array, not some other allocation.
	if &got.Values[0] != &got.inline[0] {
		t.Error("Values does not alias the frame's inline array")
	}
	// Indistinguishable includes "counts the same". NrEnvs counts frames and
	// not allocations, so a carved frame must contribute exactly what an
	// allocated one does — otherwise curryEnv2 silently halves the count for
	// the 1.98M calls it fuses, which is the failure mode the counter exists to
	// detect and would be invisible in every other assertion above. sWant made
	// two frames (up and want), sGot one.
	if sGot.Stats.NrEnvs != 1 || sGot.Stats.NrValuesInEnvs != 1 {
		t.Errorf("initOne counted NrEnvs=%d NrValuesInEnvs=%d, want 1 and 1",
			sGot.Stats.NrEnvs, sGot.Stats.NrValuesInEnvs)
	}
	if sWant.Stats.NrEnvs != 2 || sWant.Stats.NrValuesInEnvs != 2 {
		t.Errorf("newEnv counted NrEnvs=%d NrValuesInEnvs=%d, want 2 and 2",
			sWant.Stats.NrEnvs, sWant.Stats.NrValuesInEnvs)
	}
}

// TestCurryEnv2CountsTwoFrames pins the arithmetic that the fusion makes easy
// to get wrong: curryEnv2 is one *allocation* holding two *frames*, and NrEnvs
// counts frames, because that is what C++ nrEnvs counts (eval-inline.hh:102,
// one increment per allocEnv, and C++ allocates the two separately).
//
// Counting the pair once would report gonix building half the environments Lix
// builds while binding exactly the same names — wrong in the direction that
// flatters gonix, which is the direction nothing else flags. It is the same
// trap as `NrFunctionCalls += 2` beside it, and that one was found only by the
// disagreement with C++, i.e. after the fact.
func TestCurryEnv2CountsTwoFrames(t *testing.T) {
	s := &EvalState{}
	var ce curryEnv2
	ce.outer.initOne(s, nil, VInt{V: 1})
	ce.inner.initOne(s, &ce.outer, VInt{V: 2})

	if s.Stats.NrEnvs != 2 {
		t.Errorf("NrEnvs = %d, want 2 — one allocation, but two frames", s.Stats.NrEnvs)
	}
	if s.Stats.NrValuesInEnvs != 2 {
		t.Errorf("NrValuesInEnvs = %d, want 2", s.Stats.NrValuesInEnvs)
	}
}

// TestCurryPairAcceptance pins which shapes take the fused path. The risk here
// is not a wrong answer but a silent loss: a predicate that quietly stops
// matching still evaluates correctly and simply allocates more, so nothing
// else in the suite would notice.
func TestCurryPairAcceptance(t *testing.T) {
	s := NewEvalState(DefaultBuiltinNames)
	two := []Value{VInt{V: 1}, VInt{V: 2}}

	lambdaOf := func(src string) *ExprLambda {
		t.Helper()
		expr, err := NativeParseExprIntoState(s, []byte(src), "/", "<test>")
		if err != nil {
			t.Fatalf("parse %s: %v", src, err)
		}
		lam, ok := expr.(*ExprLambda)
		if !ok {
			t.Fatalf("%s parsed as %T, want *ExprLambda", src, expr)
		}
		return lam
	}

	for _, c := range []struct {
		src  string
		args int
		want bool
	}{
		{`a: b: a`, 2, true},
		{`a: b: c: a`, 2, true},   // deeper chains take it for the first two
		{`a: b: a`, 3, true},      // extra args are handled by the outer loop
		{`a: b: a`, 1, false},     // unsaturated: must stay a partial application
		{`a: a`, 2, false},        // body is not a lambda
		{`{ a }: b: a`, 2, false}, // outer pattern is not simple
		{`a: { b }: b`, 2, false}, // inner pattern is not simple
	} {
		lam := lambdaOf(c.src)
		args := make([]Value, c.args)
		copy(args, two)
		inner, got := curryPair(lam, args)
		if got != c.want {
			t.Errorf("curryPair(%s, %d args) = %v, want %v", c.src, c.args, got, c.want)
		}
		if got && inner != lam.Body {
			t.Errorf("curryPair(%s) returned an inner lambda that is not the body", c.src)
		}
	}
}

// TestCurriedCallSemantics checks that the fused path computes what the
// unfused one does, on the shapes where a layout bug would actually show: the
// two arguments must not alias, the frames must nest in the right order, and
// shadowing must resolve to the inner binding.
//
// Partial application is the case worth stating separately. `f 1` must still
// yield a function, because curryPair refuses it — if it ever accepted an
// unsaturated call it would evaluate the body against a frame holding a nil
// slot, which surfaces as a wrong answer rather than a crash.
func TestCurriedCallSemantics(t *testing.T) {
	s := NewEvalState(DefaultBuiltinNames)
	for _, c := range []struct{ src, want string }{
		{`(a: b: a) 1 2`, `1`},
		{`(a: b: b) 1 2`, `2`},
		{`(a: b: a - b) 10 3`, `7`},
		// Shadowing: the inner binding must win.
		{`(a: a: a) 1 2`, `2`},
		// Three frames: the outer loop continues past the fused pair.
		{`(a: b: c: a - b - c) 10 3 2`, `5`},
		{`(a: b: c: d: d) 1 2 3 4`, `4`},
		// The body reaches through both frames to an enclosing scope.
		{`let z = 100; in (a: b: z - a - b) 1 2`, `97`},
		// Partial application still produces a function, applied later.
		{`let f = a: b: a - b; g = f 10; in g 4`, `6`},
		{`builtins.isFunction ((a: b: a) 1)`, `true`},
		// Laziness is unchanged: an unused argument must not be forced.
		{`(a: b: a) 1 (throw "boom")`, `1`},
		// A curried lambda whose inner pattern is an attrset takes the slow
		// path; check it still works.
		{`(a: { b }: a - b) 10 { b = 4; }`, `6`},
		{`({ a }: b: a - b) { a = 10; } 4`, `6`},
	} {
		if got := evalSrc(t, s, c.src); got != c.want {
			t.Errorf("%s = %s, want %s", c.src, got, c.want)
		}
	}
}

// TestCurriedCallErrors checks that errors still come from the right place.
// The fused path writes arguments into slots without calling Pattern.match, so
// a mistake here would surface as a missing or mislocated error rather than a
// wrong value.
func TestCurriedCallErrors(t *testing.T) {
	s := NewEvalState(DefaultBuiltinNames)
	for _, src := range []string{
		`(a: b: a) 1 2 3`,            // too many arguments: 1 is not callable
		`(a: { b }: b) 1 { c = 2; }`, // inner formals unmatched
		`(a: b: a + b) 1 "x"`,        // type error inside the body
	} {
		expr, err := NativeParseExprIntoState(s, []byte(src), "/", "<test>")
		if err != nil {
			t.Fatalf("parse %s: %v", src, err)
		}
		v, err := s.Eval(expr)
		if err == nil {
			err = s.ForceValue(&v)
		}
		if err == nil {
			t.Errorf("%s: expected an error, got none", src)
		}
	}
}