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