1 #!/bin/bash
2 # Phase 6: build tags and allocation instrumentation.
3 #
4 # -tags and -print-allocs are compiler features whose whole point is the
5 # binary they produce, so every check here builds AND runs the result. Both
6 # compilers must behave identically: legacy is the bootstrap reference and
7 # stage4 has to mirror it.
8 set -euo pipefail
9 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10 ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
11 export MOXIEROOT="$ROOT"
12 source "$SCRIPT_DIR/util.sh"
13 14 LEGACY="${LEGACY:-$ROOT/legacy/moxie}"
15 16 echo ""
17 echo "=== Phase 6: build tags and allocation instrumentation ==="
18 19 if [ ! -x "$LEGACY" ]; then
20 log_skip "phase6" "legacy compiler not built at $LEGACY"
21 exit 0
22 fi
23 24 # The legacy compiler is a prebuilt binary and the suite never builds it. A
25 # stale one silently disagrees with its own source (a changed logAlloc
26 # signature showed up as an LLVM verifier failure), so say so instead of
27 # reporting a compiler bug that is not there.
28 if [ -n "$(find "$ROOT/legacy" -name '*.go' -newer "$LEGACY" -print -quit 2>/dev/null)" ]; then
29 log_skip "phase6" "legacy compiler older than its source; run legacy/build.sh"
30 exit 0
31 fi
32 33 # fixture <name> copies a fixture directory out of the tree. In-tree
34 # directories are built in place by the rest of the suite, but these tests
35 # want a scratch copy so a stray build product never lands in the repo.
36 fixture() {
37 local name="$1"
38 local d
39 d="$(tempdir "fx_$name")"
40 rm -rf "$d"; mkdir -p "$d"
41 cp "$SCRIPT_DIR/data/$name"/*.mx "$d/"
42 # A fixture that exercises module manifests carries its own.
43 if [ -f "$SCRIPT_DIR/data/$name/moxie.mod" ]; then
44 cp "$SCRIPT_DIR/data/$name/moxie.mod" "$d/"
45 fi
46 echo "$d"
47 }
48 49 # 6a: the stage4 regexp matcher must agree with the Go regexp legacy uses.
50 # tests/tools/regexp_ref is the reference (it must be Go: it calls Go's
51 # regexp). Cases are a shared file so the two sides cannot drift.
52 name="regexp:table"
53 t0=$(date +%s%N)
54 td="$(tempdir "regexp")"; rm -rf "$td"; mkdir -p "$td"
55 rxdir="$(fixture regexp)"
56 rc=0
57 MOXIEROOT="$MOXIEROOT" "$MOXIE" build -o "$td/rx_moxie" "$rxdir" >"$td/build.log" 2>&1 || rc=$?
58 if [ $rc -ne 0 ]; then
59 log_fail "$name" "stage4 build failed: $(tail -3 "$td/build.log" | tr '\n' ' ')"
60 else
61 "$td/rx_moxie" "$SCRIPT_DIR/data/regexp/cases.txt" >"$td/moxie.txt" 2>&1 || rc=$?
62 if [ $rc -ne 0 ]; then
63 log_fail "$name" "case program exited $rc"
64 elif ! command -v go >/dev/null 2>&1; then
65 log_skip "$name" "go not available for the regexp reference"
66 else
67 # shellcheck disable=SC2086
68 go run "$SCRIPT_DIR/tools/regexp_ref/main.go" "$SCRIPT_DIR/data/regexp/cases.txt" >"$td/go.txt" 2>"$td/go.err" || rc=$?
69 if [ $rc -ne 0 ]; then
70 log_fail "$name" "reference failed: $(tail -2 "$td/go.err" | tr '\n' ' ')"
71 elif cmp -s "$td/go.txt" "$td/moxie.txt"; then
72 t1=$(date +%s%N)
73 log_pass "$name" "$(wc -l < "$td/moxie.txt") cases in $(( (t1 - t0) / 1000000 ))ms"
74 else
75 log_fail "$name" "first difference: $(diff "$td/go.txt" "$td/moxie.txt" | head -3 | tr '\n' ' ')"
76 fi
77 fi
78 fi
79 rm -rf "$td" "$rxdir"
80 81 # 6b: -tags selects a //:build-gated file, in both compilers, including the
82 # comma-separated and negated forms.
83 for cc in "$MOXIE" "$LEGACY"; do
84 label="stage4"
85 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
86 td="$(tempdir "tags_$label")"; rm -rf "$td"; mkdir -p "$td"
87 tdir="$(fixture tags)"
88 for spec in "none:plain" "probe_tag:tagged" "a,probe_tag:tagged" "!probe_tag:plain"; do
89 tagset="${spec%%:*}"
90 want="${spec##*:}"
91 name="tags:$label:${tagset}"
92 args=()
93 [ "$tagset" = "none" ] || args=(-tags "$tagset")
94 rc=0
95 MOXIEROOT="$MOXIEROOT" "$cc" build "${args[@]}" -o "$td/bin" "$tdir" >"$td/build.log" 2>&1 || rc=$?
96 if [ $rc -ne 0 ]; then
97 log_fail "$name" "build failed: $(tail -2 "$td/build.log" | tr '\n' ' ')"
98 continue
99 fi
100 got="$("$td/bin" 2>&1 | head -1)"
101 if [ "$got" = "$want" ]; then
102 log_pass "$name" "ok"
103 else
104 log_fail "$name" "got '$got', want '$want'"
105 fi
106 done
107 rm -rf "$td" "$tdir"
108 done
109 110 # 6c/6d: allocation instrumentation. The site table must list the matched
111 # function, the runtime must count that site, and without the alloc_trace tag
112 # the same call must reach the no-op stub instead.
113 for cc in "$MOXIE" "$LEGACY"; do
114 label="stage4"
115 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
116 117 # instrumented build: counters fire
118 name="allocs:$label:matched"
119 td="$(tempdir "allocs_$label")"; rm -rf "$td"; mkdir -p "$td"
120 adir="$(fixture allocs)"
121 rc=0
122 MOXIEROOT="$MOXIEROOT" "$cc" build -tags alloc_trace -print-allocs allocA \
123 -o "$td/bin" "$adir" >"$td/build.log" 2>&1 || rc=$?
124 if [ $rc -ne 0 ]; then
125 log_fail "$name" "build failed: $(tail -3 "$td/build.log" | tr '\n' ' ')"
126 else
127 msg=""
128 [ -f "$td/bin.alloc_sites.json" ] || msg="no alloc_sites.json"
129 if [ -z "$msg" ]; then
130 sites=$(grep -o '"id":' "$td/bin.alloc_sites.json" | wc -l)
131 [ "$sites" -eq 1 ] || msg="expected 1 site, got $sites"
132 fi
133 if [ -z "$msg" ] && ! grep -q '"name":"[^"]*allocA' "$td/bin.alloc_sites.json"; then
134 msg="site table does not name allocA: $(cat "$td/bin.alloc_sites.json")"
135 fi
136 if [ -z "$msg" ]; then
137 out="$("$td/bin" 2>&1)"
138 got="$(echo "$out" | grep -c 'allocsite .* count=[1-9]')"
139 [ "$got" -ge 1 ] || msg="no site counted: $(echo "$out" | tr '\n' ',')"
140 fi
141 if [ -z "$msg" ]; then
142 log_pass "$name" "$(echo "$out" | grep allocsite | head -1)"
143 else
144 log_fail "$name" "$msg"
145 fi
146 fi
147 148 # matched function is the only thing instrumented
149 name="allocs:$label:selector"
150 rc=0
151 MOXIEROOT="$MOXIEROOT" "$cc" build -tags alloc_trace -print-allocs allocB \
152 -o "$td/binB" "$adir" >"$td/buildB.log" 2>&1 || rc=$?
153 if [ $rc -ne 0 ]; then
154 log_fail "$name" "build failed: $(tail -3 "$td/buildB.log" | tr '\n' ' ')"
155 elif ! grep -q '"name":"[^"]*allocB' "$td/binB.alloc_sites.json"; then
156 log_fail "$name" "site table does not name allocB: $(cat "$td/binB.alloc_sites.json" 2>/dev/null)"
157 else
158 out="$("$td/binB" 2>&1)"
159 line="$(echo "$out" | grep allocsite | head -1)"
160 if [ -n "$line" ] && echo "$line" | grep -q 'bytes=32'; then
161 log_pass "$name" "$line"
162 else
163 log_fail "$name" "expected a 32-byte site, got: $(echo "$out" | tr '\n' ',')"
164 fi
165 fi
166 167 # untagged build: the same call reaches the stub, so nothing is counted
168 name="allocs:$label:untagged"
169 rc=0
170 MOXIEROOT="$MOXIEROOT" "$cc" build -print-allocs allocA \
171 -o "$td/binU" "$adir" >"$td/buildU.log" 2>&1 || rc=$?
172 if [ $rc -ne 0 ]; then
173 log_fail "$name" "build failed: $(tail -3 "$td/buildU.log" | tr '\n' ' ')"
174 else
175 out="$("$td/binU" 2>&1)"
176 if echo "$out" | grep -q allocsite; then
177 log_fail "$name" "untagged build still counted: $(echo "$out" | grep allocsite | head -1)"
178 else
179 log_pass "$name" "no counters without -tags alloc_trace"
180 fi
181 fi
182 183 # an unusable pattern is rejected before anything is compiled
184 name="allocs:$label:bad-pattern"
185 rc=0
186 MOXIEROOT="$MOXIEROOT" "$cc" build -print-allocs 'a(' \
187 -o "$td/binBad" "$adir" >"$td/bad.log" 2>&1 || rc=$?
188 if [ $rc -eq 0 ]; then
189 log_fail "$name" "unbalanced pattern accepted"
190 else
191 log_pass "$name" "rejected"
192 fi
193 194 rm -rf "$td" "$adir"
195 done
196 197 # 6e: package-level float globals keep their initializer in both compilers.
198 # The legacy interp read a constant float with LLVMConstRealGetDouble, whose
199 # double return travels in XMM0 while the purego binding read RAX, so every
200 # package-level float32/float64 variable was silently initialized to zero
201 # (integers return in RAX and were fine).
202 for cc in "$MOXIE" "$LEGACY"; do
203 label="stage4"
204 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
205 name="globalinit:$label"
206 td="$(tempdir "globalinit_$label")"; rm -rf "$td"; mkdir -p "$td"
207 gdir="$(fixture globalinit)"
208 rc=0
209 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$gdir" >"$td/build.log" 2>&1 || rc=$?
210 if [ $rc -ne 0 ]; then
211 log_fail "$name" "build failed: $(tail -3 "$td/build.log" | tr '\n' ' ')"
212 else
213 out="$("$td/bin" 2>&1 || true)"
214 if [ "$(echo "$out" | head -1)" = "ok" ]; then
215 log_pass "$name" "package-level float globals initialized"
216 else
217 log_fail "$name" "got '$(echo "$out" | head -1)', want 'ok'"
218 fi
219 fi
220 rm -rf "$td" "$gdir"
221 done
222 223 # 6f: len()/cap() on a channel report the ring in both compilers. stage4's
224 # len/cap lowering had no channel arm and returned zero, while legacy called
225 # runtime.chanLen/chanCap.
226 for cc in "$MOXIE" "$LEGACY"; do
227 label="stage4"
228 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
229 name="chanlen:$label"
230 td="$(tempdir "chanlen_$label")"; rm -rf "$td"; mkdir -p "$td"
231 cdir="$(fixture chanlen)"
232 rc=0
233 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$cdir" >"$td/build.log" 2>&1 || rc=$?
234 if [ $rc -ne 0 ]; then
235 log_fail "$name" "build failed: $(tail -3 "$td/build.log" | tr '\n' ' ')"
236 else
237 out="$("$td/bin" 2>&1 || true)"
238 if [ "$(echo "$out" | head -1)" = "ok" ]; then
239 log_pass "$name" "channel len/cap report the ring"
240 else
241 log_fail "$name" "got '$(echo "$out" | head -1)', want 'ok'"
242 fi
243 fi
244 rm -rf "$td" "$cdir"
245 done
246 247 # 6g: testing.T.Fatal/Skip abort the test body and the run continues with the
248 # next test. This is the cross-frame recover path: stage4 never armed the defer
249 # frame's JumpPC (call lines are emitted in fragments), and explicit
250 # panic("const") went to the abort-only runtime._panicstr.
251 for cc in "$MOXIE" "$LEGACY"; do
252 label="stage4"
253 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
254 name="fatal:$label"
255 td="$(tempdir "fatal_$label")"; rm -rf "$td"; mkdir -p "$td"
256 fdir="$(fixture fatal)"
257 rc=0
258 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$fdir" >"$td/build.log" 2>&1 || rc=$?
259 if [ $rc -ne 0 ]; then
260 log_fail "$name" "build failed: $(tail -3 "$td/build.log" | tr '\n' ' ')"
261 else
262 out="$("$td/bin" 2>&1 || true)"
263 msg=""
264 if echo "$out" | grep -q "unreachable after Fatal"; then msg="Fatal did not abort the test body"; fi
265 if [ -z "$msg" ] && echo "$out" | grep -q "unreachable after Skip"; then msg="Skip did not abort the test body"; fi
266 if [ -z "$msg" ] && ! echo "$out" | grep -q "second-ran"; then msg="the run did not continue to the next test"; fi
267 if [ -z "$msg" ] && ! echo "$out" | grep -q -e "--- FAIL: TestStopsAtFatal"; then msg="the Fatal test was not reported as failed"; fi
268 if [ -z "$msg" ] && ! echo "$out" | grep -q -e "--- SKIP: TestStopsAtSkip"; then msg="the Skip test was not reported as skipped"; fi
269 if [ -z "$msg" ]; then
270 log_pass "$name" "Fatal/Skip abort and the run continues"
271 else
272 log_fail "$name" "$msg"
273 fi
274 fi
275 rm -rf "$td" "$fdir"
276 done
277 278 # 6h: the compiler's build identity is part of the cache directory, so a stale
279 # binary cannot seed the next compiler's cache with objects it compiled under
280 # the old sources but keyed on the new source hash. Two binaries from the same
281 # tree (same source hash) must use different cache dirs.
282 name="cacheid:stage4"
283 td="$(tempdir cacheid)"; rm -rf "$td"; mkdir -p "$td"
284 cp "$MOXIE" "$td/a"
285 cp "$MOXIE" "$td/b"
286 printf 'x' >> "$td/b"
287 chmod +x "$td/b"
288 ca="$(MOXIEROOT="$MOXIEROOT" "$td/a" env 2>/dev/null | sed -n 's/^CACHE="\(.*\)"$/\1/p' | head -1)"
289 cb="$(MOXIEROOT="$MOXIEROOT" "$td/b" env 2>/dev/null | sed -n 's/^CACHE="\(.*\)"$/\1/p' | head -1)"
290 if [ -z "$ca" ]; then
291 log_fail "$name" "could not read CACHE from mxc env"
292 elif [ -z "$cb" ]; then
293 log_skip "$name" "the modified binary did not run (signed binary?)"
294 elif [ "$ca" = "$cb" ]; then
295 log_fail "$name" "cache dir does not carry the build identity: $ca"
296 else
297 log_pass "$name" "cache dir differs with the binary"
298 fi
299 rm -rf "$td"
300 301 # 6i: untyped int/float constant promotion in both compilers. stage4's
302 # SSA-builder fold used the left (int) operand's type, so `1/2.0` folded to
303 # the int constant 0 and `1/Ln2` truncated - which also mis-sized math/big's
304 # base-10 conversion buffer.
305 for cc in "$MOXIE" "$LEGACY"; do
306 label="stage4"
307 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
308 name="constpromo:$label"
309 td="$(tempdir "constpromo_$label")"; rm -rf "$td"; mkdir -p "$td"
310 pdir="$(fixture constpromo)"
311 rc=0
312 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$pdir" >"$td/build.log" 2>&1 || rc=$?
313 if [ $rc -ne 0 ]; then
314 log_fail "$name" "build failed: $(tail -3 "$td/build.log" | tr '\n' ' ')"
315 else
316 out="$("$td/bin" 2>&1 || true)"
317 if [ "$(echo "$out" | head -1)" = "ok" ]; then
318 log_pass "$name" "untyped int/float promotion"
319 else
320 log_fail "$name" "got '$(echo "$out" | head -1)', want 'ok'"
321 fi
322 fi
323 rm -rf "$td" "$pdir"
324 done
325 326 # 6j: clear() zeroes a slice or a map in both compilers. stage4's builtin
327 # emitter had no clear arm, so the call was dropped: math/big's basicMul
328 # starts with clear(z[0:len(x)+len(y)]) and accumulates into z, so a reused
329 # receiver kept its previous product's digits (Exp(10,20) = 10^20+10^5) and
330 # a 300-digit Text(10) divided by a stale zero and died with SIGFPE.
331 for cc in "$MOXIE" "$LEGACY"; do
332 label="stage4"
333 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
334 name="clearzero:$label"
335 td="$(tempdir "clearzero_$label")"; rm -rf "$td"; mkdir -p "$td"
336 zdir="$(fixture clearzero)"
337 rc=0
338 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$zdir" >"$td/build.log" 2>&1 || rc=$?
339 if [ $rc -ne 0 ]; then
340 log_fail "$name" "build failed: $(tail -3 "$td/build.log" | tr '\n' ' ')"
341 else
342 out="$("$td/bin" 2>&1 || true)"
343 if [ "$(echo "$out" | head -1)" = "ok" ]; then
344 log_pass "$name" "clear zeroes slices and maps"
345 else
346 log_fail "$name" "got '$(echo "$out" | head -1)', want 'ok'"
347 fi
348 fi
349 rm -rf "$td" "$zdir"
350 done
351 352 # 6k: the spawn ring's index word is the parking word. A receiver parked on the
353 # write index must be woken by the publish (no lost wakeup), and a parent
354 # parked on a ring whose child died must unpark, report the closed channel and
355 # not hang. Before this both sides polled with a 1ms sleep.
356 #
357 # The same fixture also pins the discarded-receive ABI: `<-done` with no
358 # assignment passes a nil destination from the legacy compiler (stage4 passes a
359 # scratch), and the runtime wrote the delivered value through it - a
360 # legacy-built spawn program died in memcpy right after its first exchange.
361 # recvStore/recvZero in runtime.mx treat a nil destination as "discard", so
362 # both compilers run this now.
363 for cc in "$MOXIE" "$LEGACY"; do
364 label="stage4"
365 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
366 name="futexpark:$label"
367 td="$(tempdir "futexpark_$label")"; rm -rf "$td"; mkdir -p "$td"
368 fpdir="$(fixture futexpark)"
369 rc=0
370 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$fpdir" >"$td/build.log" 2>&1 || rc=$?
371 if [ $rc -ne 0 ]; then
372 log_fail "$name" "build failed: $(tail -3 "$td/build.log" | tr '\n' ' ')"
373 else
374 # A hang is the failure mode under test, so cap the run.
375 out="$(timeout 60 "$td/bin" 2>&1 || true)"
376 if echo "$out" | grep -q "^wake ok$" && echo "$out" | grep -q "^dead ok$"; then
377 log_pass "$name" "park wakes on publish and unparks on peer death"
378 else
379 log_fail "$name" "got '$(echo "$out" | tr '\n' ' ')'"
380 fi
381 fi
382 rm -rf "$td" "$fpdir"
383 done
384 385 # 6l: untyped float constants are exact rationals, in one package and across
386 # the .mxh boundary. stage4 compared the rounded float64 and exported a folded
387 # constant as its shortest decimal, so `math.MaxFloat64 ==
388 # 1.7976931348623157e308` was true where go/types (legacy) says false.
389 for cc in "$MOXIE" "$LEGACY"; do
390 label="stage4"
391 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
392 name="exactfloat:$label"
393 td="$(tempdir "exactfloat_$label")"; rm -rf "$td"; mkdir -p "$td"
394 edir="$(fixture exactfloat)"
395 rc=0
396 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$edir" >"$td/build.log" 2>&1 || rc=$?
397 if [ $rc -ne 0 ]; then
398 log_fail "$name" "build failed: $(tail -3 "$td/build.log" | tr '\n' ' ')"
399 else
400 out="$("$td/bin" 2>&1 || true)"
401 if [ "$(echo "$out" | head -1)" = "ok" ]; then
402 log_pass "$name" "untyped float constants are exact"
403 else
404 log_fail "$name" "got '$(echo "$out" | head -1)', want 'ok'"
405 fi
406 fi
407 rm -rf "$td" "$edir"
408 done
409 410 # 6m: the coverage join is one implementation (pkg/mxcover) shared by the
411 # mxcover command and `moxie test -cover`, so both print identical text. The
412 # same fixture and expected report are asserted by legacy/cover's Go test for
413 # the legacy harness (legacy's own `moxie test` cannot build these packages in
414 # this tree, so its report is locked by that unit test instead).
415 name="coverreport:stage4"
416 td="$(tempdir "coverreport")"; rm -rf "$td"; mkdir -p "$td"
417 rc=0
418 MOXIEROOT="$MOXIEROOT" "$MOXIE" build -o "$td/mxcover" "$ROOT/cmd/mxcover" >"$td/build.log" 2>&1 || rc=$?
419 if [ $rc -ne 0 ]; then
420 log_fail "$name" "mxcover build failed: $(tail -3 "$td/build.log" | tr '\n' ' ')"
421 else
422 "$td/mxcover" "$SCRIPT_DIR/data/mxcover/sites.json" "$SCRIPT_DIR/data/mxcover/counts" "git.smesh.lol/smesh/" >"$td/got.txt" 2>&1 || rc=$?
423 if [ $rc -ne 0 ]; then
424 log_fail "$name" "mxcover exited $rc: $(tail -2 "$td/got.txt" | tr '\n' ' ')"
425 elif cmp -s "$td/got.txt" "$SCRIPT_DIR/data/mxcover/want.txt"; then
426 log_pass "$name" "report matches the shared fixture"
427 else
428 log_fail "$name" "report differs: $(diff "$SCRIPT_DIR/data/mxcover/want.txt" "$td/got.txt" | head -3 | tr '\n' ' ')"
429 fi
430 fi
431 rm -rf "$td"
432 433 # 6n: the reporter's parser and arithmetic units, run by the compiler that
434 # ships them (`moxie test ./pkg/mxcover`). pkg/mxcover is a package so the
435 # harness can print the same report the command does.
436 name="coverunit:stage4"
437 td="$(tempdir "coverunit")"; rm -rf "$td"; mkdir -p "$td"
438 rc=0
439 out="$(MOXIEROOT="$MOXIEROOT" timeout 900 "$MOXIE" test "$ROOT/pkg/mxcover" 2>&1)" || rc=$?
440 if [ $rc -eq 0 ] && echo "$out" | grep -q "^PASS: "; then
441 log_pass "$name" "$(echo "$out" | grep '^PASS: ')"
442 else
443 log_fail "$name" "$(echo "$out" | tail -3 | tr '\n' ' ')"
444 fi
445 rm -rf "$td"
446 447 # 6o: a shift whose left operand is an untyped constant is typed by its
448 # context, not by the count. stage4 typed `1 << c` (c a byte) as uint8, so the
449 # emitter's overshift select (Go: a count >= the width shifts to zero) zeroed
450 # every count >= 8 - `bytes.TrimRight(s, "\r\n")` never trimmed and the
451 # mediaproxy header loop read past the blank line. legacy was already right.
452 for cc in "$MOXIE" "$LEGACY"; do
453 label="stage4"
454 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
455 name="shiftconst:$label"
456 td="$(tempdir "shiftconst_$label")"; rm -rf "$td"; mkdir -p "$td"
457 sdir="$(fixture shiftconst)"
458 rc=0
459 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$sdir" >"$td/build.log" 2>&1 || rc=$?
460 if [ $rc -ne 0 ]; then
461 log_fail "$name" "build failed: $(tail -3 "$td/build.log" | tr '\n' ' ')"
462 else
463 out="$("$td/bin" 2>&1 || true)"
464 if [ "$(echo "$out" | head -1)" = "ok" ]; then
465 log_pass "$name" "untyped-constant shift takes the context type"
466 else
467 log_fail "$name" "got '$(echo "$out" | head -1)', want 'ok'"
468 fi
469 fi
470 rm -rf "$td" "$sdir"
471 done
472 473 # 6p: a reslice is clamped to its parent instead of producing a negative
474 # length. `s[1:]` on an empty slice was len -1, so the tree's
475 # `for ; len(r) > 0; r = r[1:]` loops left `r` holding a negative length and
476 # the `if len(r) == 0` guards that follow never fired; a truncated REQ filter
477 # then read past the end and killed the relay.
478 for cc in "$MOXIE" "$LEGACY"; do
479 label="stage4"
480 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
481 name="sliceres:$label"
482 td="$(tempdir "sliceres_$label")"; rm -rf "$td"; mkdir -p "$td"
483 srdir="$(fixture sliceres)"
484 rc=0
485 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$srdir" >"$td/build.log" 2>&1 || rc=$?
486 if [ $rc -ne 0 ]; then
487 log_fail "$name" "build failed: $(tail -3 "$td/build.log" | tr '\n' ' ')"
488 else
489 out="$("$td/bin" 2>&1 || true)"
490 if [ "$(echo "$out" | head -1)" = "ok" ]; then
491 log_pass "$name" "a reslice clamps to its parent"
492 else
493 log_fail "$name" "got '$(echo "$out" | head -1)', want 'ok'"
494 fi
495 fi
496 rm -rf "$td" "$srdir"
497 done
498 499 # 6q: the ChaCha20-Poly1305 AEAD must produce the RFC 8439 vector when it is a
500 # dependency of an application, not only when its own package is the build
501 # root. The ported sealGeneric split one allocation into a head and a tail view
502 # (the Go sliceForAppend idiom); Moxie relocates each returned slice on its
503 # own, so the tail came back as a copy and Seal answered all zeros - which is
504 # why Marmot DMs could not decrypt in an application build.
505 for cc in "$MOXIE" "$LEGACY"; do
506 label="stage4"
507 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
508 name="aeaddep:$label"
509 td="$(tempdir "aeaddep_$label")"; rm -rf "$td"; mkdir -p "$td"
510 addir="$(fixture aeaddep)"
511 rc=0
512 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$addir" >"$td/build.log" 2>&1 || rc=$?
513 if [ $rc -ne 0 ]; then
514 log_fail "$name" "build failed: $(tail -3 "$td/build.log" | tr '\n' ' ')"
515 else
516 out="$("$td/bin" 2>&1 || true)"
517 if echo "$out" | grep -q "^ok d3 91$"; then
518 log_pass "$name" "the AEAD known-answer vector holds as a dependency"
519 else
520 log_fail "$name" "got '$(echo "$out" | head -1)', want 'ok d3 91'"
521 fi
522 fi
523 rm -rf "$td" "$addir"
524 done
525 526 # 6r: an untyped `{...}` in a map literal and a field select on a map index.
527 # Both compilers must build the same program and print the same values: stage4
528 # silently dropped the map entry when its value was an untyped composite
529 # literal (buildExpr answered nil), and it refused `m[k].Field` for a
530 # struct-valued map because a map value is not addressable - the key was live
531 # and the value was not there. Legacy builds both, so stage4 has to.
532 for cc in "$MOXIE" "$LEGACY"; do
533 label="stage4"
534 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
535 name="maplit:$label"
536 td="$(tempdir "maplit_$label")"; rm -rf "$td"; mkdir -p "$td"
537 addir="$(fixture maplit)"
538 rc=0
539 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$addir" >"$td/build.log" 2>&1 || rc=$?
540 if [ $rc -ne 0 ]; then
541 log_fail "$name" "build failed: $(tail -3 "$td/build.log" | tr '\n' ' ')"
542 else
543 out="$("$td/bin" 2>&1 || true)"
544 want="nested en = EN LOGIN
545 nested ja = JA LOGIN
546 slice = one,two
547 struct = one,two
548 array = first,second
549 ptr = five,six"
550 if [ "$out" = "$want" ]; then
551 log_pass "$name" "untyped map elements and map-index field selects hold"
552 else
553 log_fail "$name" "got '$(echo "$out" | tr '\n' '|')'"
554 fi
555 fi
556 rm -rf "$td" "$addir"
557 done
558 559 # 6t: a variable declared from an untyped constant takes the constant's default
560 # type. Left untyped, `acc := 0` made the shift in `byte((acc >> bits) & 0x1f)`
561 # context-dependent, and the conversion retyped it to uint8 - an i8 shift on
562 # acc's low byte. Bech32 payloads came out wrong. Legacy types it int32 and is
563 # the reference here.
564 for cc in "$MOXIE" "$LEGACY"; do
565 label="stage4"
566 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
567 name="untypedvar:$label"
568 td="$(tempdir "untypedvar_$label")"; rm -rf "$td"; mkdir -p "$td"
569 uvdir="$(fixture untypedvar)"
570 rc=0
571 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$uvdir" >"$td/build.log" 2>&1 || rc=$?
572 if [ $rc -ne 0 ]; then
573 log_fail "$name" "build failed: $(tail -3 "$td/build.log" | tr '\n' ' ')"
574 else
575 out="$("$td/bin" 2>&1 || true)"
576 if [ "$out" = "groups 1202140204021a13" ]; then
577 log_pass "$name" "a declaration from an untyped constant is int32"
578 else
579 log_fail "$name" "got '$out', want 'groups 1202140204021a13'"
580 fi
581 fi
582 rm -rf "$td" "$uvdir"
583 done
584 585 # 6u: push on an untyped nil must fail loudly in both compilers. Stage4 typed
586 # the call untyped nil, defaulted it to int32, and emitted an i32 where a slice
587 # header belongs - or, in a short declaration, silently built an empty slice.
588 # Legacy has always reported it; the fixture pins both sides to the same error.
589 for cc in "$MOXIE" "$LEGACY"; do
590 label="stage4"
591 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
592 name="pushnil:$label"
593 td="$(tempdir "pushnil_$label")"; rm -rf "$td"; mkdir -p "$td"
594 pndir="$(fixture pushnil)"
595 rc=0
596 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$pndir" >"$td/build.log" 2>&1 || rc=$?
597 if [ $rc -eq 0 ]; then
598 log_fail "$name" "push on an untyped nil compiled"
599 elif grep -q "must be a slice; have untyped nil" "$td/build.log"; then
600 log_pass "$name" "push on an untyped nil is rejected by name"
601 else
602 log_fail "$name" "wrong error: $(tail -2 "$td/build.log" | tr '\n' ' ')"
603 fi
604 rm -rf "$td" "$pndir"
605 done
606 607 # 6v..6x: the three language rules legacy enforces and stage4 did not, each in
608 # its own fixture and inside a real module. Legacy applies user restrictions
609 # only to the main module (p.Module.Main), so a manifest-less directory version
610 # of these programs is accepted by legacy and would prove nothing. Both
611 # compilers must reject each one by name.
612 reject_fixture() {
613 local fx="$1" want="$2"
614 for cc in "$MOXIE" "$LEGACY"; do
615 label="stage4"
616 if [ "$cc" = "$LEGACY" ]; then label="legacy"; fi
617 name="$fx:$label"
618 td="$(tempdir "${fx}_$label")"; rm -rf "$td"; mkdir -p "$td"
619 fxdir="$(fixture "$fx")"
620 rc=0
621 if [ "$cc" = "$LEGACY" ]; then
622 # The legacy loader shells out to the Go toolchain for go/types.
623 PATH="/tmp/moxie-goroot/bin:$PATH" GOROOT=/tmp/moxie-goroot \
624 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$fxdir" >"$td/build.log" 2>&1 || rc=$?
625 else
626 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$fxdir" >"$td/build.log" 2>&1 || rc=$?
627 fi
628 if [ $rc -eq 0 ]; then
629 log_fail "$name" "the program compiled; want rejection"
630 elif grep -q "$want" "$td/build.log"; then
631 log_pass "$name" "rejected: $want"
632 else
633 log_fail "$name" "wrong error: $(tail -2 "$td/build.log" | tr '\n' ' ')"
634 fi
635 rm -rf "$td" "$fxdir"
636 done
637 }
638 639 # 6v: unnamed return values.
640 reject_fixture unnamedret "unnamed return values are not allowed"
641 # 6w: a parenthesised call target.
642 reject_fixture parencall "parenthesized call target is not allowed"
643 # 6x: '+' for text concatenation.
644 reject_fixture plustext "'+' is not allowed for text concatenation"
645 # 6x.1: a package-level var with an initializer.
646 reject_fixture declvar "package-level var with initializer is not allowed"
647 # 6x.2: a named slice type.
648 reject_fixture namedslice "named slice type 'Names' is not allowed"
649 650 # 6y..6z: the no-conflict rule reaches imports. Legacy rejects both shapes
651 # through its own type checker (a redeclared import; a declaration already
652 # declared through the dot import), stage4 through the rule it gained here, so
653 # the two compilers reject for different reasons and the check pins each side's
654 # message rather than pretending they share one.
655 reject_conflict_fixture() {
656 local fx="$1" want_s4="$2" want_legacy="$3"
657 for cc in "$MOXIE" "$LEGACY"; do
658 label="stage4"; want="$want_s4"
659 if [ "$cc" = "$LEGACY" ]; then label="legacy"; want="$want_legacy"; fi
660 name="$fx:$label"
661 td="$(tempdir "${fx}_$label")"; rm -rf "$td"; mkdir -p "$td"
662 fxdir="$(fixture "$fx")"
663 rc=0
664 if [ "$cc" = "$LEGACY" ]; then
665 PATH="/tmp/moxie-goroot/bin:$PATH" GOROOT=/tmp/moxie-goroot \
666 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$fxdir" >"$td/build.log" 2>&1 || rc=$?
667 else
668 MOXIEROOT="$MOXIEROOT" "$cc" build -o "$td/bin" "$fxdir" >"$td/build.log" 2>&1 || rc=$?
669 fi
670 if [ $rc -eq 0 ]; then
671 log_fail "$name" "the program compiled; want rejection"
672 elif grep -q "$want" "$td/build.log"; then
673 log_pass "$name" "rejected: $want"
674 else
675 log_fail "$name" "wrong error: $(tail -2 "$td/build.log" | tr '\n' ' ')"
676 fi
677 rm -rf "$td" "$fxdir"
678 done
679 }
680 681 # 6y: two different imports binding one name in one file.
682 reject_conflict_fixture importconflict \
683 "is imported twice in one file" \
684 "redeclared in this block"
685 # 6z: a declaration colliding with a name a dot import injects.
686 reject_conflict_fixture dotconflict \
687 "collides with an imported package name" \
688 "already declared through dot-import of package math"
689 690 # 6aa: `moxie test` must run the package under test's init(). The harness
691 # concatenates the library and its test files into a synthetic main package and
692 # injects a __varinit call into that main, but it never called init() - so an
693 # init-set global read zero in the package's own test and nowhere else.
694 name="testinit:stage4"
695 td="$(tempdir "testinit")"; rm -rf "$td"; mkdir -p "$td"
696 itdir="$(fixture inittest)"
697 rc=0
698 out="$(MOXIEROOT="$MOXIEROOT" "$MOXIE" test "$itdir" 2>&1)" || rc=$?
699 if [ $rc -eq 0 ] && echo "$out" | grep -q "^PASS: "; then
700 log_pass "$name" "$(echo "$out" | grep '^PASS: ')"
701 else
702 log_fail "$name" "$(echo "$out" | grep -v '^\[mem\]' | tail -3 | tr '\n' ' ')"
703 fi
704 rm -rf "$td" "$itdir"
705 706 # 6ab: a named result without parentheses is a syntax error. The parser used to
707 # recover by swallowing the body, so the function's locals landed at package
708 # scope - every other function using one of those names then reported a
709 # shadowing error - and the only complaint was "unnamed return values", for a
710 # signature that names one. Two bugs behind it: the early file==nil branch
711 # collected the parse errors into an empty loop, and a recovered parse (file
712 # non-nil, errors recorded) was allowed to carry on with a partial tree.
713 reject_conflict_fixture badnamedresult \
714 "named result values require parentheses" \
715 "unexpected name"
716 717 failed=0
718 if [ -f "$STATUSFILE" ]; then
719 failed=$(grep -c FAIL "$STATUSFILE" 2>/dev/null) || failed=0
720 fi
721 if [ "$failed" -gt 0 ]; then
722 echo ""
723 echo "Instrumentation suite: $failed failure(s)"
724 exit 1
725 fi
726