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