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 )
251
252 var featureToOpcodes = map[string][]string{
253
254
255
256 "popcnt": {"popcntq", "popcntl", "popcnt"},
257 "bmi1": {
258 "andnq", "andnl", "andn",
259 "blsiq", "blsil", "blsi",
260 "blsmskq", "blsmskl", "blsmsk",
261 "blsrq", "blsrl", "blsr",
262 "tzcntq", "tzcntl", "tzcnt",
263 },
264 "bmi2": {
265 "sarxq", "sarxl", "sarx",
266 "shlxq", "shlxl", "shlx",
267 "shrxq", "shrxl", "shrx",
268 },
269 "sse41": {
270 "roundsd",
271 "pinsrq", "pinsrl", "pinsrd", "pinsrb", "pinsr",
272 "pextrq", "pextrl", "pextrd", "pextrb", "pextr",
273 "pminsb", "pminsd", "pminuw", "pminud",
274 "pmaxsb", "pmaxsd", "pmaxuw", "pmaxud",
275 "pmovzxbw", "pmovzxbd", "pmovzxbq", "pmovzxwd", "pmovzxwq", "pmovzxdq",
276 "pmovsxbw", "pmovsxbd", "pmovsxbq", "pmovsxwd", "pmovsxwq", "pmovsxdq",
277 "pblendvb",
278 },
279 "fma": {"vfmadd231sd"},
280 "movbe": {"movbeqq", "movbeq", "movbell", "movbel", "movbe"},
281 "lzcnt": {"lzcntq", "lzcntl", "lzcnt"},
282 }
283
284
285 func TestPopCnt(t *testing.T) {
286 for _, tt := range []struct {
287 x uint64
288 want int
289 }{
290 {0b00001111, 4},
291 {0b00001110, 3},
292 {0b00001100, 2},
293 {0b00000000, 0},
294 } {
295 if got := bits.OnesCount64(tt.x); got != tt.want {
296 t.Errorf("OnesCount64(%#x) = %d, want %d", tt.x, got, tt.want)
297 }
298 if got := bits.OnesCount32(uint32(tt.x)); got != tt.want {
299 t.Errorf("OnesCount32(%#x) = %d, want %d", tt.x, got, tt.want)
300 }
301 }
302 }
303
304
305 func TestAndNot(t *testing.T) {
306 for _, tt := range []struct {
307 x, y, want uint64
308 }{
309 {0b00001111, 0b00000011, 0b1100},
310 {0b00001111, 0b00001100, 0b0011},
311 {0b00000000, 0b00000000, 0b0000},
312 } {
313 if got := tt.x &^ tt.y; got != tt.want {
314 t.Errorf("%#x &^ %#x = %#x, want %#x", tt.x, tt.y, got, tt.want)
315 }
316 if got := uint32(tt.x) &^ uint32(tt.y); got != uint32(tt.want) {
317 t.Errorf("%#x &^ %#x = %#x, want %#x", tt.x, tt.y, got, tt.want)
318 }
319 }
320 }
321
322
323 func TestBLSI(t *testing.T) {
324 for _, tt := range []struct {
325 x, want uint64
326 }{
327 {0b00001111, 0b001},
328 {0b00001110, 0b010},
329 {0b00001100, 0b100},
330 {0b11000110, 0b010},
331 {0b00000000, 0b000},
332 } {
333 if got := tt.x & -tt.x; got != tt.want {
334 t.Errorf("%#x & (-%#x) = %#x, want %#x", tt.x, tt.x, got, tt.want)
335 }
336 if got := uint32(tt.x) & -uint32(tt.x); got != uint32(tt.want) {
337 t.Errorf("%#x & (-%#x) = %#x, want %#x", tt.x, tt.x, got, tt.want)
338 }
339 }
340 }
341
342
343 func TestBLSMSK(t *testing.T) {
344 for _, tt := range []struct {
345 x, want uint64
346 }{
347 {0b00001111, 0b001},
348 {0b00001110, 0b011},
349 {0b00001100, 0b111},
350 {0b11000110, 0b011},
351 {0b00000000, 1<<64 - 1},
352 } {
353 if got := tt.x ^ (tt.x - 1); got != tt.want {
354 t.Errorf("%#x ^ (%#x-1) = %#x, want %#x", tt.x, tt.x, got, tt.want)
355 }
356 if got := uint32(tt.x) ^ (uint32(tt.x) - 1); got != uint32(tt.want) {
357 t.Errorf("%#x ^ (%#x-1) = %#x, want %#x", tt.x, tt.x, got, uint32(tt.want))
358 }
359 }
360 }
361
362
363 func TestBLSR(t *testing.T) {
364 for _, tt := range []struct {
365 x, want uint64
366 }{
367 {0b00001111, 0b00001110},
368 {0b00001110, 0b00001100},
369 {0b00001100, 0b00001000},
370 {0b11000110, 0b11000100},
371 {0b00000000, 0b00000000},
372 } {
373 if got := tt.x & (tt.x - 1); got != tt.want {
374 t.Errorf("%#x & (%#x-1) = %#x, want %#x", tt.x, tt.x, got, tt.want)
375 }
376 if got := uint32(tt.x) & (uint32(tt.x) - 1); got != uint32(tt.want) {
377 t.Errorf("%#x & (%#x-1) = %#x, want %#x", tt.x, tt.x, got, tt.want)
378 }
379 }
380 }
381
382 func TestTrailingZeros(t *testing.T) {
383 for _, tt := range []struct {
384 x uint64
385 want int
386 }{
387 {0b00001111, 0},
388 {0b00001110, 1},
389 {0b00001100, 2},
390 {0b00001000, 3},
391 {0b00000000, 64},
392 } {
393 if got := bits.TrailingZeros64(tt.x); got != tt.want {
394 t.Errorf("TrailingZeros64(%#x) = %d, want %d", tt.x, got, tt.want)
395 }
396 want := tt.want
397 if want == 64 {
398 want = 32
399 }
400 if got := bits.TrailingZeros32(uint32(tt.x)); got != want {
401 t.Errorf("TrailingZeros64(%#x) = %d, want %d", tt.x, got, want)
402 }
403 }
404 }
405
406 func TestRound(t *testing.T) {
407 for _, tt := range []struct {
408 x, want float64
409 }{
410 {1.4, 1},
411 {1.5, 2},
412 {1.6, 2},
413 {2.4, 2},
414 {2.5, 2},
415 {2.6, 3},
416 } {
417 if got := math.RoundToEven(tt.x); got != tt.want {
418 t.Errorf("RoundToEven(%f) = %f, want %f", tt.x, got, tt.want)
419 }
420 }
421 }
422
423 func TestFMA(t *testing.T) {
424 for _, tt := range []struct {
425 x, y, z, want float64
426 }{
427 {2, 3, 4, 10},
428 {3, 4, 5, 17},
429 } {
430 if got := math.FMA(tt.x, tt.y, tt.z); got != tt.want {
431 t.Errorf("FMA(%f,%f,%f) = %f, want %f", tt.x, tt.y, tt.z, got, tt.want)
432 }
433 }
434 }
435
View as plain text