| // Copyright 2019 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package test | |
| import ( | |
| "testing" | |
| ) | |
| var glob = 3 | |
| var globp *int64 | |
| // Testing compilation of arch.ZeroRange of various sizes. | |
| // By storing a pointer to an int64 output param in a global, the compiler must | |
| // ensure that output param is allocated on the heap. Also, since there is a | |
| // defer, the pointer to each output param must be zeroed in the prologue (see | |
| // plive.go:epilogue()). So, we will get a block of one or more stack slots that | |
| // need to be zeroed. Hence, we are testing compilation completes successfully when | |
| // zerorange calls of various sizes (8-136 bytes) are generated. We are not | |
| // testing runtime correctness (which is hard to do for the current uses of | |
| // ZeroRange). | |
| func TestZeroRange(t *testing.T) { | |
| testZeroRange8(t) | |
| testZeroRange16(t) | |
| testZeroRange32(t) | |
| testZeroRange64(t) | |
| testZeroRange136(t) | |
| } | |
| func testZeroRange8(t *testing.T) (r int64) { | |
| defer func() { | |
| glob = 4 | |
| }() | |
| globp = &r | |
| return | |
| } | |
| func testZeroRange16(t *testing.T) (r, s int64) { | |
| defer func() { | |
| glob = 4 | |
| }() | |
| globp = &r | |
| globp = &s | |
| return | |
| } | |
| func testZeroRange32(t *testing.T) (r, s, t2, u int64) { | |
| defer func() { | |
| glob = 4 | |
| }() | |
| globp = &r | |
| globp = &s | |
| globp = &t2 | |
| globp = &u | |
| return | |
| } | |
| func testZeroRange64(t *testing.T) (r, s, t2, u, v, w, x, y int64) { | |
| defer func() { | |
| glob = 4 | |
| }() | |
| globp = &r | |
| globp = &s | |
| globp = &t2 | |
| globp = &u | |
| globp = &v | |
| globp = &w | |
| globp = &x | |
| globp = &y | |
| return | |
| } | |
| func testZeroRange136(t *testing.T) (r, s, t2, u, v, w, x, y, r1, s1, t1, u1, v1, w1, x1, y1, z1 int64) { | |
| defer func() { | |
| glob = 4 | |
| }() | |
| globp = &r | |
| globp = &s | |
| globp = &t2 | |
| globp = &u | |
| globp = &v | |
| globp = &w | |
| globp = &x | |
| globp = &y | |
| globp = &r1 | |
| globp = &s1 | |
| globp = &t1 | |
| globp = &u1 | |
| globp = &v1 | |
| globp = &w1 | |
| globp = &x1 | |
| globp = &y1 | |
| globp = &z1 | |
| return | |
| } | |
| type S struct { | |
| x [2]uint64 | |
| p *uint64 | |
| y [2]uint64 | |
| q uint64 | |
| } | |
| type M struct { | |
| x [8]uint64 | |
| p *uint64 | |
| y [8]uint64 | |
| q uint64 | |
| } | |
| type L struct { | |
| x [4096]uint64 | |
| p *uint64 | |
| y [4096]uint64 | |
| q uint64 | |
| } | |
| //go:noinline | |
| func triggerZerorangeLarge(f, g, h uint64) (rv0 uint64) { | |
| ll := L{p: &f} | |
| da := f | |
| rv0 = f + g + h | |
| defer func(dl L, i uint64) { | |
| rv0 += dl.q + i | |
| }(ll, da) | |
| return rv0 | |
| } | |
| //go:noinline | |
| func triggerZerorangeMedium(f, g, h uint64) (rv0 uint64) { | |
| ll := M{p: &f} | |
| rv0 = f + g + h | |
| defer func(dm M, i uint64) { | |
| rv0 += dm.q + i | |
| }(ll, f) | |
| return rv0 | |
| } | |
| //go:noinline | |
| func triggerZerorangeSmall(f, g, h uint64) (rv0 uint64) { | |
| ll := S{p: &f} | |
| rv0 = f + g + h | |
| defer func(ds S, i uint64) { | |
| rv0 += ds.q + i | |
| }(ll, f) | |
| return rv0 | |
| } | |
| // This test was created as a follow up to issue #45372, to help | |
| // improve coverage of the compiler's arch-specific "zerorange" | |
| // function, which is invoked to zero out ambiguously live portions of | |
| // the stack frame in certain specific circumstances. | |
| // | |
| // In the current compiler implementation, for zerorange to be | |
| // invoked, we need to have an ambiguously live variable that needs | |
| // zeroing. One way to trigger this is to have a function with an | |
| // open-coded defer, where the opendefer function has an argument that | |
| // contains a pointer (this is what's used below). | |
| // | |
| // At the moment this test doesn't do any specific checking for | |
| // code sequence, or verification that things were properly set to zero, | |
| // this seems as though it would be too tricky and would result | |
| // in a "brittle" test. | |
| // | |
| // The small/medium/large scenarios below are inspired by the amd64 | |
| // implementation of zerorange, which generates different code | |
| // depending on the size of the thing that needs to be zeroed out | |
| // (I've verified at the time of the writing of this test that it | |
| // exercises the various cases). | |
| // | |
| func TestZerorange45372(t *testing.T) { | |
| if r := triggerZerorangeLarge(101, 303, 505); r != 1010 { | |
| t.Errorf("large: wanted %d got %d", 1010, r) | |
| } | |
| if r := triggerZerorangeMedium(101, 303, 505); r != 1010 { | |
| t.Errorf("medium: wanted %d got %d", 1010, r) | |
| } | |
| if r := triggerZerorangeSmall(101, 303, 505); r != 1010 { | |
| t.Errorf("small: wanted %d got %d", 1010, r) | |
| } | |
| } |