| // Copyright 2016 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. | |
| // This program generates a test to verify that the standard arithmetic | |
| // operators properly handle const cases. The test file should be | |
| // generated with a known working version of go. | |
| // launch with `go run arithConstGen.go` a file called arithConst.go | |
| // will be written into the parent directory containing the tests | |
| package main | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "go/format" | |
| "log" | |
| "strings" | |
| "text/template" | |
| ) | |
| type op struct { | |
| name, symbol string | |
| } | |
| type szD struct { | |
| name string | |
| sn string | |
| u []uint64 | |
| i []int64 | |
| oponly string | |
| } | |
| var szs = []szD{ | |
| {name: "uint64", sn: "64", u: []uint64{0, 1, 4294967296, 0x8000000000000000, 0xffffFFFFffffFFFF}}, | |
| {name: "uint64", sn: "64", u: []uint64{3, 5, 7, 9, 10, 11, 13, 19, 21, 25, 27, 37, 41, 45, 73, 81}, oponly: "mul"}, | |
| {name: "int64", sn: "64", i: []int64{-0x8000000000000000, -0x7FFFFFFFFFFFFFFF, | |
| -4294967296, -1, 0, 1, 4294967296, 0x7FFFFFFFFFFFFFFE, 0x7FFFFFFFFFFFFFFF}}, | |
| {name: "int64", sn: "64", i: []int64{-9, -5, -3, 3, 5, 7, 9, 10, 11, 13, 19, 21, 25, 27, 37, 41, 45, 73, 81}, oponly: "mul"}, | |
| {name: "uint32", sn: "32", u: []uint64{0, 1, 4294967295}}, | |
| {name: "uint32", sn: "32", u: []uint64{3, 5, 7, 9, 10, 11, 13, 19, 21, 25, 27, 37, 41, 45, 73, 81}, oponly: "mul"}, | |
| {name: "int32", sn: "32", i: []int64{-0x80000000, -0x7FFFFFFF, -1, 0, | |
| 1, 0x7FFFFFFF}}, | |
| {name: "int32", sn: "32", i: []int64{-9, -5, -3, 3, 5, 7, 9, 10, 11, 13, 19, 21, 25, 27, 37, 41, 45, 73, 81}, oponly: "mul"}, | |
| {name: "uint16", sn: "16", u: []uint64{0, 1, 65535}}, | |
| {name: "int16", sn: "16", i: []int64{-32768, -32767, -1, 0, 1, 32766, 32767}}, | |
| {name: "uint8", sn: "8", u: []uint64{0, 1, 255}}, | |
| {name: "int8", sn: "8", i: []int64{-128, -127, -1, 0, 1, 126, 127}}, | |
| } | |
| var ops = []op{ | |
| {"add", "+"}, | |
| {"sub", "-"}, | |
| {"div", "/"}, | |
| {"mul", "*"}, | |
| {"lsh", "<<"}, | |
| {"rsh", ">>"}, | |
| {"mod", "%"}, | |
| {"and", "&"}, | |
| {"or", "|"}, | |
| {"xor", "^"}, | |
| } | |
| // compute the result of i op j, cast as type t. | |
| func ansU(i, j uint64, t, op string) string { | |
| var ans uint64 | |
| switch op { | |
| case "+": | |
| ans = i + j | |
| case "-": | |
| ans = i - j | |
| case "*": | |
| ans = i * j | |
| case "/": | |
| if j != 0 { | |
| ans = i / j | |
| } | |
| case "%": | |
| if j != 0 { | |
| ans = i % j | |
| } | |
| case "<<": | |
| ans = i << j | |
| case ">>": | |
| ans = i >> j | |
| case "&": | |
| ans = i & j | |
| case "|": | |
| ans = i | j | |
| case "^": | |
| ans = i ^ j | |
| } | |
| switch t { | |
| case "uint32": | |
| ans = uint64(uint32(ans)) | |
| case "uint16": | |
| ans = uint64(uint16(ans)) | |
| case "uint8": | |
| ans = uint64(uint8(ans)) | |
| } | |
| return fmt.Sprintf("%d", ans) | |
| } | |
| // compute the result of i op j, cast as type t. | |
| func ansS(i, j int64, t, op string) string { | |
| var ans int64 | |
| switch op { | |
| case "+": | |
| ans = i + j | |
| case "-": | |
| ans = i - j | |
| case "*": | |
| ans = i * j | |
| case "/": | |
| if j != 0 { | |
| ans = i / j | |
| } | |
| case "%": | |
| if j != 0 { | |
| ans = i % j | |
| } | |
| case "<<": | |
| ans = i << uint64(j) | |
| case ">>": | |
| ans = i >> uint64(j) | |
| case "&": | |
| ans = i & j | |
| case "|": | |
| ans = i | j | |
| case "^": | |
| ans = i ^ j | |
| } | |
| switch t { | |
| case "int32": | |
| ans = int64(int32(ans)) | |
| case "int16": | |
| ans = int64(int16(ans)) | |
| case "int8": | |
| ans = int64(int8(ans)) | |
| } | |
| return fmt.Sprintf("%d", ans) | |
| } | |
| func main() { | |
| w := new(bytes.Buffer) | |
| fmt.Fprintf(w, "// Code generated by gen/arithConstGen.go. DO NOT EDIT.\n\n") | |
| fmt.Fprintf(w, "package main;\n") | |
| fmt.Fprintf(w, "import \"testing\"\n") | |
| fncCnst1 := template.Must(template.New("fnc").Parse( | |
| `//go:noinline | |
| func {{.Name}}_{{.Type_}}_{{.FNumber}}(a {{.Type_}}) {{.Type_}} { return a {{.Symbol}} {{.Number}} } | |
| `)) | |
| fncCnst2 := template.Must(template.New("fnc").Parse( | |
| `//go:noinline | |
| func {{.Name}}_{{.FNumber}}_{{.Type_}}(a {{.Type_}}) {{.Type_}} { return {{.Number}} {{.Symbol}} a } | |
| `)) | |
| type fncData struct { | |
| Name, Type_, Symbol, FNumber, Number string | |
| } | |
| for _, s := range szs { | |
| for _, o := range ops { | |
| if s.oponly != "" && s.oponly != o.name { | |
| continue | |
| } | |
| fd := fncData{o.name, s.name, o.symbol, "", ""} | |
| // unsigned test cases | |
| if len(s.u) > 0 { | |
| for _, i := range s.u { | |
| fd.Number = fmt.Sprintf("%d", i) | |
| fd.FNumber = strings.ReplaceAll(fd.Number, "-", "Neg") | |
| // avoid division by zero | |
| if o.name != "mod" && o.name != "div" || i != 0 { | |
| // introduce uint64 cast for rhs shift operands | |
| // if they are too large for default uint type | |
| number := fd.Number | |
| if (o.name == "lsh" || o.name == "rsh") && uint64(uint32(i)) != i { | |
| fd.Number = fmt.Sprintf("uint64(%s)", number) | |
| } | |
| fncCnst1.Execute(w, fd) | |
| fd.Number = number | |
| } | |
| fncCnst2.Execute(w, fd) | |
| } | |
| } | |
| // signed test cases | |
| if len(s.i) > 0 { | |
| // don't generate tests for shifts by signed integers | |
| if o.name == "lsh" || o.name == "rsh" { | |
| continue | |
| } | |
| for _, i := range s.i { | |
| fd.Number = fmt.Sprintf("%d", i) | |
| fd.FNumber = strings.ReplaceAll(fd.Number, "-", "Neg") | |
| // avoid division by zero | |
| if o.name != "mod" && o.name != "div" || i != 0 { | |
| fncCnst1.Execute(w, fd) | |
| } | |
| fncCnst2.Execute(w, fd) | |
| } | |
| } | |
| } | |
| } | |
| vrf1 := template.Must(template.New("vrf1").Parse(` | |
| test_{{.Size}}{fn: {{.Name}}_{{.FNumber}}_{{.Type_}}, fnname: "{{.Name}}_{{.FNumber}}_{{.Type_}}", in: {{.Input}}, want: {{.Ans}}},`)) | |
| vrf2 := template.Must(template.New("vrf2").Parse(` | |
| test_{{.Size}}{fn: {{.Name}}_{{.Type_}}_{{.FNumber}}, fnname: "{{.Name}}_{{.Type_}}_{{.FNumber}}", in: {{.Input}}, want: {{.Ans}}},`)) | |
| type cfncData struct { | |
| Size, Name, Type_, Symbol, FNumber, Number string | |
| Ans, Input string | |
| } | |
| for _, s := range szs { | |
| fmt.Fprintf(w, ` | |
| type test_%[1]s%[2]s struct { | |
| fn func (%[1]s) %[1]s | |
| fnname string | |
| in %[1]s | |
| want %[1]s | |
| } | |
| `, s.name, s.oponly) | |
| fmt.Fprintf(w, "var tests_%[1]s%[2]s =[]test_%[1]s {\n\n", s.name, s.oponly) | |
| if len(s.u) > 0 { | |
| for _, o := range ops { | |
| if s.oponly != "" && s.oponly != o.name { | |
| continue | |
| } | |
| fd := cfncData{s.name, o.name, s.name, o.symbol, "", "", "", ""} | |
| for _, i := range s.u { | |
| fd.Number = fmt.Sprintf("%d", i) | |
| fd.FNumber = strings.ReplaceAll(fd.Number, "-", "Neg") | |
| // unsigned | |
| for _, j := range s.u { | |
| if o.name != "mod" && o.name != "div" || j != 0 { | |
| fd.Ans = ansU(i, j, s.name, o.symbol) | |
| fd.Input = fmt.Sprintf("%d", j) | |
| if err := vrf1.Execute(w, fd); err != nil { | |
| panic(err) | |
| } | |
| } | |
| if o.name != "mod" && o.name != "div" || i != 0 { | |
| fd.Ans = ansU(j, i, s.name, o.symbol) | |
| fd.Input = fmt.Sprintf("%d", j) | |
| if err := vrf2.Execute(w, fd); err != nil { | |
| panic(err) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // signed | |
| if len(s.i) > 0 { | |
| for _, o := range ops { | |
| if s.oponly != "" && s.oponly != o.name { | |
| continue | |
| } | |
| // don't generate tests for shifts by signed integers | |
| if o.name == "lsh" || o.name == "rsh" { | |
| continue | |
| } | |
| fd := cfncData{s.name, o.name, s.name, o.symbol, "", "", "", ""} | |
| for _, i := range s.i { | |
| fd.Number = fmt.Sprintf("%d", i) | |
| fd.FNumber = strings.ReplaceAll(fd.Number, "-", "Neg") | |
| for _, j := range s.i { | |
| if o.name != "mod" && o.name != "div" || j != 0 { | |
| fd.Ans = ansS(i, j, s.name, o.symbol) | |
| fd.Input = fmt.Sprintf("%d", j) | |
| if err := vrf1.Execute(w, fd); err != nil { | |
| panic(err) | |
| } | |
| } | |
| if o.name != "mod" && o.name != "div" || i != 0 { | |
| fd.Ans = ansS(j, i, s.name, o.symbol) | |
| fd.Input = fmt.Sprintf("%d", j) | |
| if err := vrf2.Execute(w, fd); err != nil { | |
| panic(err) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| fmt.Fprintf(w, "}\n\n") | |
| } | |
| fmt.Fprint(w, ` | |
| // TestArithmeticConst tests results for arithmetic operations against constants. | |
| func TestArithmeticConst(t *testing.T) { | |
| `) | |
| for _, s := range szs { | |
| fmt.Fprintf(w, `for _, test := range tests_%s%s {`, s.name, s.oponly) | |
| // Use WriteString here to avoid a vet warning about formatting directives. | |
| w.WriteString(`if got := test.fn(test.in); got != test.want { | |
| t.Errorf("%s(%d) = %d, want %d\n", test.fnname, test.in, got, test.want) | |
| } | |
| } | |
| `) | |
| } | |
| fmt.Fprint(w, ` | |
| } | |
| `) | |
| // gofmt result | |
| b := w.Bytes() | |
| src, err := format.Source(b) | |
| if err != nil { | |
| fmt.Printf("%s\n", b) | |
| panic(err) | |
| } | |
| // write to file | |
| err = os.WriteFile("../arithConst_test.go", src, 0666) | |
| if err != nil { | |
| log.Fatalf("can't write output: %v\n", err) | |
| } | |
| } |