1
2
3
4
5 package amd64_test
6
7 import (
8 "bufio"
9 "debug/elf"
10 "debug/macho"
11 "fmt"
12 "internal/testenv"
13 "io"
14 "math"
15 "math/bits"
16 "os"
17 "os/exec"
18 "regexp"
19 "runtime"
20 "strconv"
21 "strings"
22 "testing"
23 )
24
25
26
27 func TestGoAMD64v1(t *testing.T) {
28 if runtime.GOARCH != "amd64" {
29 t.Skip("amd64-only test")
30 }
31 if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
32 t.Skip("test only works on elf or macho platforms")
33 }
34 if v := os.Getenv("GOAMD64"); v != "" && v != "v1" {
35
36
37 t.Skip("GOAMD64 already set")
38 }
39 if os.Getenv("TESTGOAMD64V1") != "" {
40 t.Skip("recursive call")
41 }
42
43
44
45 dst, err := os.CreateTemp("", "TestGoAMD64v1")
46 if err != nil {
47 t.Fatalf("failed to create temp file: %v", err)
48 }
49 defer os.Remove(dst.Name())
50 dst.Chmod(0500)
51
52
53 opcodes := map[string]bool{}
54 var features []string
55 for feature, opcodeList := range featureToOpcodes {
56 if runtimeFeatures[feature] {
57 features = append(features, fmt.Sprintf("cpu.%s=off", feature))
58 }
59 for _, op := range opcodeList {
60 opcodes[op] = true
61 }
62 }
63 clobber(t, os.Args[0], dst, opcodes)
64 if err = dst.Close(); err != nil {
65 t.Fatalf("can't close binary: %v", err)
66 }
67
68
69 cmd := exec.Command(dst.Name())
70 testenv.CleanCmdEnv(cmd)
71 cmd.Env = append(cmd.Env, "TESTGOAMD64V1=yes")
72 cmd.Env = append(cmd.Env, fmt.Sprintf("GODEBUG=%s", strings.Join(features, ",")))
73 out, err := cmd.CombinedOutput()
74 if err != nil {
75 t.Fatalf("couldn't execute test: %s", err)
76 }
77
78
79 success := false
80 lines := strings.Split(string(out), "\n")
81 if len(lines) == 2 {
82 success = lines[0] == "PASS" && lines[1] == ""
83 } else if len(lines) == 3 {
84 success = lines[0] == "PASS" &&
85 strings.HasPrefix(lines[1], "coverage") && lines[2] == ""
86 }
87 if !success {
88 t.Fatalf("test reported error: %s lines=%+v", string(out), lines)
89 }
90 }
91
92
93
94 func clobber(t *testing.T, src string, dst *os.File, opcodes map[string]bool) {
95
96 var re *regexp.Regexp
97 var disasm io.Reader
98 if false {
99
100
101 cmd := exec.Command("go", "tool", "objdump", src)
102 var err error
103 disasm, err = cmd.StdoutPipe()
104 if err != nil {
105 t.Fatal(err)
106 }
107 if err := cmd.Start(); err != nil {
108 t.Fatal(err)
109 }
110 re = regexp.MustCompile(`^[^:]*:[-0-9]+\s+0x([0-9a-f]+)\s+([0-9a-f]+)\s+([A-Z]+)`)
111 } else {
112
113
114 cmd := exec.Command("objdump", "-d", src)
115 var err error
116 disasm, err = cmd.StdoutPipe()
117 if err != nil {
118 t.Skipf("can't run test due to missing objdump: %s", err)
119 }
120 if err := cmd.Start(); err != nil {
121 t.Fatal(err)
122 }
123 re = regexp.MustCompile(`^\s*([0-9a-f]+):\s*((?:[0-9a-f][0-9a-f] )+)\s*([a-z0-9]+)`)
124 }
125
126
127 virtualEdits := map[uint64]bool{}
128 scanner := bufio.NewScanner(disasm)
129 for scanner.Scan() {
130 line := scanner.Text()
131 parts := re.FindStringSubmatch(line)
132 if len(parts) == 0 {
133 continue
134 }
135 addr, err := strconv.ParseUint(parts[1], 16, 64)
136 if err != nil {
137 continue
138 }
139 opcode := strings.ToLower(parts[3])
140 if !opcodes[opcode] {
141 continue
142 }
143 t.Logf("clobbering instruction %s", line)
144 n := (len(parts[2]) - strings.Count(parts[2], " ")) / 2
145 for i := 0; i < n; i++ {
146
147
148 virtualEdits[addr+uint64(i)] = true
149 }
150 }
151
152
153 physicalEdits := map[uint64]bool{}
154 if e, err := elf.Open(src); err == nil {
155 for _, sec := range e.Sections {
156 vaddr := sec.Addr
157 paddr := sec.Offset
158 size := sec.Size
159 for a := range virtualEdits {
160 if a >= vaddr && a < vaddr+size {
161 physicalEdits[paddr+(a-vaddr)] = true
162 }
163 }
164 }
165 } else if m, err2 := macho.Open(src); err2 == nil {
166 for _, sec := range m.Sections {
167 vaddr := sec.Addr
168 paddr := uint64(sec.Offset)
169 size := sec.Size
170 for a := range virtualEdits {
171 if a >= vaddr && a < vaddr+size {
172 physicalEdits[paddr+(a-vaddr)] = true
173 }
174 }
175 }
176 } else {
177 t.Log(err)
178 t.Log(err2)
179 t.Fatal("executable format not elf or macho")
180 }
181 if len(virtualEdits) != len(physicalEdits) {
182 t.Fatal("couldn't find an instruction in text sections")
183 }
184
185
186 f, err := os.Open(src)
187 if err != nil {
188 t.Fatal(err)
189 }
190 r := bufio.NewReader(f)
191 w := bufio.NewWriter(dst)
192 a := uint64(0)
193 done := 0
194 for {
195 b, err := r.ReadByte()
196 if err == io.EOF {
197 break
198 }
199 if err != nil {
200 t.Fatal("can't read")
201 }
202 if physicalEdits[a] {
203 b = 0xcc
204 done++
205 }
206 err = w.WriteByte(b)
207 if err != nil {
208 t.Fatal("can't write")
209 }
210 a++
211 }
212 if done != len(physicalEdits) {
213 t.Fatal("physical edits remaining")
214 }
215 w.Flush()
216 f.Close()
217 }
218
219 func setOf(keys ...string) map[string]bool {
220 m := make(map[string]bool, len(keys))
221 for _, key := range keys {
222 m[key] = true
223 }
224 return m
225 }
226
227 var runtimeFeatures = setOf(
228 "adx", "aes", "avx", "avx2", "bmi1", "bmi2", "erms", "fma",
229 "pclmulqdq", "popcnt", "rdtscp", "sse3", "sse41", "sse42", "ssse3",
230 )
231
232 var featureToOpcodes = map[string][]string{
233
234
235
236 "popcnt": {"popcntq", "popcntl", "popcnt"},
237 "bmi1": {"andnq", "andnl", "andn", "blsiq", "blsil", "blsi", "blsmskq", "blsmskl", "blsmsk", "blsrq", "blsrl", "blsr", "tzcntq", "tzcntl", "tzcnt"},
238 "sse41": {"roundsd"},
239 "fma": {"vfmadd231sd"},
240 "movbe": {"movbeqq", "movbeq", "movbell", "movbel", "movbe"},
241 }
242
243
244 func TestPopCnt(t *testing.T) {
245 for _, tt := range []struct {
246 x uint64
247 want int
248 }{
249 {0b00001111, 4},
250 {0b00001110, 3},
251 {0b00001100, 2},
252 {0b00000000, 0},
253 } {
254 if got := bits.OnesCount64(tt.x); got != tt.want {
255 t.Errorf("OnesCount64(%#x) = %d, want %d", tt.x, got, tt.want)
256 }
257 if got := bits.OnesCount32(uint32(tt.x)); got != tt.want {
258 t.Errorf("OnesCount32(%#x) = %d, want %d", tt.x, got, tt.want)
259 }
260 }
261 }
262
263
264 func TestAndNot(t *testing.T) {
265 for _, tt := range []struct {
266 x, y, want uint64
267 }{
268 {0b00001111, 0b00000011, 0b1100},
269 {0b00001111, 0b00001100, 0b0011},
270 {0b00000000, 0b00000000, 0b0000},
271 } {
272 if got := tt.x &^ tt.y; got != tt.want {
273 t.Errorf("%#x &^ %#x = %#x, want %#x", tt.x, tt.y, got, tt.want)
274 }
275 if got := uint32(tt.x) &^ uint32(tt.y); got != uint32(tt.want) {
276 t.Errorf("%#x &^ %#x = %#x, want %#x", tt.x, tt.y, got, tt.want)
277 }
278 }
279 }
280
281
282 func TestBLSI(t *testing.T) {
283 for _, tt := range []struct {
284 x, want uint64
285 }{
286 {0b00001111, 0b001},
287 {0b00001110, 0b010},
288 {0b00001100, 0b100},
289 {0b11000110, 0b010},
290 {0b00000000, 0b000},
291 } {
292 if got := tt.x & -tt.x; got != tt.want {
293 t.Errorf("%#x & (-%#x) = %#x, want %#x", tt.x, tt.x, got, tt.want)
294 }
295 if got := uint32(tt.x) & -uint32(tt.x); got != uint32(tt.want) {
296 t.Errorf("%#x & (-%#x) = %#x, want %#x", tt.x, tt.x, got, tt.want)
297 }
298 }
299 }
300
301
302 func TestBLSMSK(t *testing.T) {
303 for _, tt := range []struct {
304 x, want uint64
305 }{
306 {0b00001111, 0b001},
307 {0b00001110, 0b011},
308 {0b00001100, 0b111},
309 {0b11000110, 0b011},
310 {0b00000000, 1<<64 - 1},
311 } {
312 if got := tt.x ^ (tt.x - 1); got != tt.want {
313 t.Errorf("%#x ^ (%#x-1) = %#x, want %#x", tt.x, tt.x, got, tt.want)
314 }
315 if got := uint32(tt.x) ^ (uint32(tt.x) - 1); got != uint32(tt.want) {
316 t.Errorf("%#x ^ (%#x-1) = %#x, want %#x", tt.x, tt.x, got, uint32(tt.want))
317 }
318 }
319 }
320
321
322 func TestBLSR(t *testing.T) {
323 for _, tt := range []struct {
324 x, want uint64
325 }{
326 {0b00001111, 0b00001110},
327 {0b00001110, 0b00001100},
328 {0b00001100, 0b00001000},
329 {0b11000110, 0b11000100},
330 {0b00000000, 0b00000000},
331 } {
332 if got := tt.x & (tt.x - 1); got != tt.want {
333 t.Errorf("%#x & (%#x-1) = %#x, want %#x", tt.x, tt.x, got, tt.want)
334 }
335 if got := uint32(tt.x) & (uint32(tt.x) - 1); got != uint32(tt.want) {
336 t.Errorf("%#x & (%#x-1) = %#x, want %#x", tt.x, tt.x, got, tt.want)
337 }
338 }
339 }
340
341 func TestTrailingZeros(t *testing.T) {
342 for _, tt := range []struct {
343 x uint64
344 want int
345 }{
346 {0b00001111, 0},
347 {0b00001110, 1},
348 {0b00001100, 2},
349 {0b00001000, 3},
350 {0b00000000, 64},
351 } {
352 if got := bits.TrailingZeros64(tt.x); got != tt.want {
353 t.Errorf("TrailingZeros64(%#x) = %d, want %d", tt.x, got, tt.want)
354 }
355 want := tt.want
356 if want == 64 {
357 want = 32
358 }
359 if got := bits.TrailingZeros32(uint32(tt.x)); got != want {
360 t.Errorf("TrailingZeros64(%#x) = %d, want %d", tt.x, got, want)
361 }
362 }
363 }
364
365 func TestRound(t *testing.T) {
366 for _, tt := range []struct {
367 x, want float64
368 }{
369 {1.4, 1},
370 {1.5, 2},
371 {1.6, 2},
372 {2.4, 2},
373 {2.5, 2},
374 {2.6, 3},
375 } {
376 if got := math.RoundToEven(tt.x); got != tt.want {
377 t.Errorf("RoundToEven(%f) = %f, want %f", tt.x, got, tt.want)
378 }
379 }
380 }
381
382 func TestFMA(t *testing.T) {
383 for _, tt := range []struct {
384 x, y, z, want float64
385 }{
386 {2, 3, 4, 10},
387 {3, 4, 5, 17},
388 } {
389 if got := math.FMA(tt.x, tt.y, tt.z); got != tt.want {
390 t.Errorf("FMA(%f,%f,%f) = %f, want %f", tt.x, tt.y, tt.z, got, tt.want)
391 }
392 }
393 }
394
View as plain text