| // Copyright 2026 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. |
| |
| package main |
| |
| import ( |
| "bytes" |
| "fmt" |
| "go/format" |
| "log" |
| "math/rand/v2" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "regexp" |
| "slices" |
| "sort" |
| "strings" |
| "text/template" |
| |
| "golang.org/x/arch/arm64/instgen/xmlspec" |
| ) |
| |
| type tmplData struct { |
| EncodingFuncs []funcData |
| Insts []instData |
| InstsByName []instsByName |
| // All possible arg patterns are aggregated here. |
| // They are unique with regard to [Class] and [Elms]. |
| // [Asm] is not considered for uniqueness. |
| UniqueArgs []argData |
| // All possible operand combinations are aggregated here. |
| // They are unique with regard to the list of [Args]. |
| UniqueOpCombs []opCombData |
| Constants []constantData |
| } |
| |
| type instsByName struct { |
| Name string |
| Insts []instData // All these insts share the same GoOp code. |
| } |
| |
| type constantData struct { |
| ConstName string |
| BinName string |
| } |
| |
| type funcData struct { |
| Comments []string |
| Body string |
| Name string |
| } |
| |
| type instData struct { |
| GoOp string |
| FixedBits string |
| Args []argData |
| Ops opCombData |
| Asm string |
| highFeat int |
| } |
| |
| type argData struct { |
| // The global variable name of this arg pattern. |
| Name string |
| Class string |
| Asm string |
| Elms []elmData |
| } |
| |
| type opCombData struct { |
| Name string |
| OpAsms string |
| ArgNames []string |
| BetterName string |
| } |
| |
| type elmData struct { |
| FuncName string |
| EncName string |
| } |
| |
| type e2eData struct { |
| GoOp string |
| Asm string |
| Binary string |
| // highFeat is the feature order (see [operandTypeOrders]), it's used to |
| // sort the e2e test data. |
| highFeat int |
| } |
| |
| const genTmpl = `// Code generated by 'instgen -o=$GOROOT # from go install golang.org/x/arch/arm64/instgen@latest'. DO NOT EDIT. |
| |
| // The following constants are generated from the XML specification. |
| |
| package arm64 |
| |
| // insts are grouped by [goOp]. |
| var insts = [][]instEncoder{ |
| {{- range .InstsByName}} |
| // {{.Name}} |
| { |
| {{- range .Insts}} |
| // {{.Asm}} {{.Ops.OpAsms}} |
| { |
| goOp: {{.GoOp}}, |
| fixedBits: {{.FixedBits}}, |
| args: {{.Ops.BetterName}}, |
| }, |
| {{- end}} |
| }, |
| {{- end}} |
| } |
| |
| {{range .UniqueArgs}} |
| var {{.Name}} = operand{ |
| class: {{.Class}}, elemEncoders: []elemEncoder{ |
| {{- range .Elms}} |
| {{"{"}}{{.FuncName}}, {{.EncName}}{{"}"}}, |
| {{- end}} |
| }, |
| } |
| {{end}} |
| |
| {{range .UniqueOpCombs}} |
| var {{.BetterName}} = []operand{ |
| {{- range .ArgNames}} |
| {{.}}, |
| {{- end}} |
| } |
| {{end}} |
| ` |
| |
| const encodingTmpl = `// Code generated by 'instgen -o=$GOROOT # from go install golang.org/x/arch/arm64/instgen@latest'. |
| |
| package arm64 |
| |
| import "cmd/internal/obj" |
| |
| // stripRawZ first checks if v is a raw register number(0 to 31). |
| // If v is not a raw Z register, it will check if it's a Z register in the ARM64 range: |
| // - if within the range, it returns true, and set v to be the raw Z register. |
| // - otherwise it returns false |
| // If v is a raw register number, it returns true and leaves v unchanged. |
| func stripRawZ(v *uint32) (bool) { |
| if *v >= obj.RBaseARM64 { |
| if !(*v >= REG_Z0 && *v <= REG_Z31) && !(*v >= REG_ZARNG && *v < REG_ZARNGELEM) { |
| return false |
| } |
| } |
| *v = *v & 31 |
| return true |
| } |
| |
| // checkIsR checks if v is a scalar register: |
| // - if v is a raw register number or is within the ARM64 scalar register range, |
| // it returns true. |
| // - otherwise it returns false. |
| func checkIsR(v uint32) (bool) { |
| if v > REG_R31 && v != REG_RSP { |
| return false |
| } |
| return true |
| } |
| |
| const ( |
| enc_NIL component = iota |
| {{- range .Constants}} |
| {{.ConstName}} |
| {{- end}} |
| ) |
| {{- range .EncodingFuncs}} |
| // {{.Name}} is the implementation of the following encoding logic: |
| {{- range .Comments}} |
| // {{.}} |
| {{- end}} |
| func {{.Name}}(v uint32) (uint32, bool) { |
| {{.Body}} |
| } |
| {{- end}} |
| ` |
| |
| const goopsTmpl = `// Code generated by 'instgen -o=$GOROOT # from go install golang.org/x/arch/arm64/instgen@latest'. DO NOT EDIT. |
| |
| package arm64 |
| |
| import "cmd/internal/obj" |
| |
| const ( |
| {{- range .}} |
| {{.}} |
| {{- end}} |
| ALAST |
| ) |
| ` |
| |
| const anamesTmpl = `// Code generated by 'instgen -o=$GOROOT # from go install golang.org/x/arch/arm64/instgen@latest'. DO NOT EDIT. |
| |
| package arm64 |
| |
| var sveAnames = []string{ |
| "SVESTART", |
| {{- range .}} |
| "{{.}}", |
| {{- end}} |
| "LAST", |
| } |
| |
| func init() { |
| Anames = append(Anames, sveAnames...) |
| } |
| ` |
| |
| const e2edataTmpl = `// Code generated by 'instgen -o=$GOROOT # from go install golang.org/x/arch/arm64/instgen@latest'. DO NOT EDIT. |
| |
| #include "../../../../../runtime/textflag.h" |
| |
| TEXT asmtest(SB),DUPOK|NOSPLIT,$-8 |
| {{- range .}} |
| {{.Asm}}{{.Binary}} |
| {{- end}} |
| RET |
| ` |
| |
| var operandTypeOrders = map[string]int{ |
| "AC_ARNG": 0, |
| "AC_PREG": 0, |
| "AC_PREGZ": 0, |
| "AC_PREGZM": 0, |
| "AC_ZREG": 0, |
| "AC_SPZGREG": 1, |
| "AC_VREG": 1, |
| "AC_ARNGIDX": 2, |
| "AC_ZREGIDX": 2, |
| "AC_PREGIDX": 2, |
| "AC_IMM": 3, |
| "AC_REGLIST1": 4, |
| "AC_REGLIST2": 4, |
| "AC_REGLIST3": 4, |
| "AC_REGLIST4": 4, |
| "AC_MEMEXT": 5, |
| "AC_SPECIAL": 6, |
| "AC_MEMOFF": 7, |
| "AC_MEMOFFMULVL": 8, |
| "AC_REGLIST_RANGE": 9, |
| } |
| |
| func readExistingGoOps(aoutPath string) map[string]bool { |
| ops := make(map[string]bool) |
| content, err := os.ReadFile(aoutPath) |
| if err != nil { |
| return ops |
| } |
| re := regexp.MustCompile(`\b(A[A-Z0-9_]+)\b`) |
| matches := re.FindAllStringSubmatch(string(content), -1) |
| for _, m := range matches { |
| ops[m[1]] = true |
| } |
| return ops |
| } |
| |
| func convertToGoID(v string) string { |
| v = strings.ReplaceAll(v, " :: ", "_") |
| v = strings.ReplaceAll(v, "(", "") |
| v = strings.ReplaceAll(v, ")", "") |
| v = strings.ReplaceAll(v, "[", "") |
| v = strings.ReplaceAll(v, "]", "") |
| v = strings.ReplaceAll(v, "#", "c") |
| v = strings.ReplaceAll(v, "-", "_") |
| return v |
| } |
| |
| // formatAndFlush executes the given template and writes it to the given file, |
| // the go file is formatted using gofmt. |
| // If outputDir is empty, it does nothing. |
| func formatAndFlush(outputDir string, filename string, data any, tmpl string) { |
| if outputDir == "" { |
| return |
| } |
| t, err := template.New("template").Parse(tmpl) |
| if err != nil { |
| log.Fatalf("Failed to parse template %s: %v", filename, err) |
| } |
| |
| var buf bytes.Buffer |
| if err := t.Execute(&buf, data); err != nil { |
| log.Fatalf("Failed to execute template %s: %v", filename, err) |
| } |
| |
| formatted := buf.Bytes() |
| if strings.HasSuffix(filename, ".go") { |
| formatted, err = format.Source(formatted) |
| if err != nil { |
| log.Printf("Failed to format generated source: %v", err) |
| formatted = buf.Bytes() |
| } |
| } |
| |
| outputPath := filepath.Join(outputDir, filename) |
| if err := os.WriteFile(outputPath, formatted, 0644); err != nil { |
| log.Fatalf("Failed to write output file %s: %v", outputPath, err) |
| } |
| log.Printf("Generated %s", outputPath) |
| } |
| |
| // Generate generates the Go code for the instruction table. |
| func Generate(insts []*xmlspec.InstructionParsed, outputDir string, genE2E bool) { |
| // process encodingImpls, field them to make the comparison easier |
| for k, v := range encodingImpls { |
| uniformed := strings.Join(strings.Fields(k), " ") |
| encodingImpls[uniformed] = v |
| } |
| |
| // Collect constants |
| encodingFuncs := make(map[string]int) // Map textExp to index |
| encodingFuncComments := make(map[string][]string) |
| |
| // Pre-populate EncodingFuncs |
| sortedEncodings := make([]string, 0, len(xmlspec.AllEncodingDescs)) |
| for k := range xmlspec.AllEncodingDescs { |
| sortedEncodings = append(sortedEncodings, k) |
| } |
| sort.Strings(sortedEncodings) |
| |
| // Create map to store names early |
| funcNames := make(map[int]string) |
| encNames := make(map[int]string) |
| |
| for i, k := range sortedEncodings { |
| encodingFuncs[k] = i + 1 |
| encodingFuncComments[k] = strings.Split(k, "\n") |
| |
| kUniformed := strings.Join(strings.Fields(k), " ") |
| if b, ok := encodingImpls[kUniformed]; ok { |
| funcNames[i+1] = b.name |
| encNames[i+1] = b.encodedIn |
| } else { |
| funcNames[i+1] = fmt.Sprintf("encodeGen%d", i+1) |
| } |
| } |
| |
| // Collect A64Types and A64Feats, and Insts |
| var instsData []instData |
| newGoOps := make(map[string]bool) |
| existingGoOps := readExistingGoOps(filepath.Join(outputDir, "a.out.go")) |
| |
| usedFuncIndices := make(map[int]bool) |
| |
| e2eTestData := make([]e2eData, 0) |
| e2eErrorData := make([]e2eData, 0) |
| for _, inst := range insts { |
| if inst == nil { |
| continue |
| } |
| isAlias := false |
| for _, doc := range inst.DocVars { |
| if doc.Key == "alias_mnemonic" { |
| isAlias = true |
| break |
| } |
| } |
| if isAlias { |
| // Alias instructions are not fully specified in their own XML files, |
| // skip them. |
| continue |
| } |
| |
| for _, iclass := range inst.Classes.Iclass { |
| if !iclass.RegDiagram.Parsed { |
| continue |
| } |
| |
| for _, encodingRaw := range iclass.Encodings { |
| if !encodingRaw.Parsed { |
| continue |
| } |
| |
| if encodingRaw.Alias { |
| // Alias encodings are not fully specified in their own section, |
| // skip them. |
| continue |
| } |
| |
| encodings := xmlspec.SplitInstByRegWidth(encodingRaw) |
| for _, encoding := range encodings { |
| // Filtering logic: |
| // 1. If 0 operands (len <= 1), keep with warning. |
| // 2. If > 0 operands, ALL must be in operandTypeOrders. |
| keep := true |
| if len(encoding.Operands) <= 1 { |
| log.Printf("Warning: instruction with 0 operands kept: %s", encoding.Asm) |
| } else { |
| for _, op := range encoding.Operands[1:] { |
| if _, ok := operandTypeOrders[op.Typ]; !ok { |
| keep = false |
| break |
| } |
| } |
| } |
| |
| if !keep { |
| continue |
| } |
| |
| if !existingGoOps[encoding.GoOp] { |
| newGoOps[encoding.GoOp] = true |
| } else { |
| log.Printf("Warning: goOp %s already exists in a.out.go", encoding.GoOp) |
| } |
| |
| validData, errData := constructInstance(&encoding) |
| e2eTestData = append(e2eTestData, *validData) |
| if errData != nil { |
| e2eErrorData = append(e2eErrorData, *errData) |
| } |
| highFeat := 0 |
| for _, op := range encoding.Operands[1:] { |
| highFeat = max(highFeat, operandTypeOrders[op.Typ]) |
| } |
| instData := instData{ |
| Asm: encoding.GoOp[1:], |
| GoOp: encoding.GoOp, |
| FixedBits: fmt.Sprintf("%#x", encoding.Binary), |
| highFeat: highFeat, |
| } |
| |
| for _, op := range encoding.Operands[1:] { |
| arg := argData{ |
| Asm: op.Name, |
| Class: op.Typ, |
| } |
| if len(op.Elems) > 0 { |
| for _, elm := range op.Elems { |
| idx, ok := encodingFuncs[elm.TextExpWithRanges] |
| if !ok { |
| log.Printf("Warning: encoding func not found for %s", elm.TextExpWithRanges) |
| continue |
| } |
| |
| enc := encNames[idx] |
| if enc == "" { |
| // Fallback |
| sym := xmlspec.EncodingDescsToEncodedIn[elm.TextExpWithRanges] |
| // We actually do not have encodedInMap here yet, so just prefix "enc_" |
| if sym == "" || sym == "nil" { |
| enc = "enc_NIL" |
| } else { |
| enc = fmt.Sprintf("enc_%s", convertToGoID(sym)) |
| } |
| } |
| arg.Elms = append(arg.Elms, elmData{ |
| FuncName: funcNames[idx], |
| EncName: enc, |
| }) |
| usedFuncIndices[idx] = true |
| } |
| } |
| instData.Args = append(instData.Args, arg) |
| } |
| instsData = append(instsData, instData) |
| } |
| } |
| } |
| } |
| |
| // Prepare template data |
| data := tmplData{ |
| Insts: instsData, |
| } |
| |
| // Collect constants |
| encodedInMap := make(map[string]string) // raw string -> constant name |
| var uniqueEncodedIn []string |
| |
| // Collect ALL constants from global map to ensure invariant names (enc_X) |
| for _, v := range xmlspec.EncodingDescsToEncodedIn { |
| if v == "" || v == "nil" { |
| continue |
| } |
| if _, ok := encodedInMap[v]; !ok { |
| encodedInMap[v] = "" |
| uniqueEncodedIn = append(uniqueEncodedIn, v) |
| } |
| } |
| sort.Strings(uniqueEncodedIn) |
| |
| // Identify used constants |
| neededEncodedIn := make(map[string]bool) |
| for k, idx := range encodingFuncs { |
| if usedFuncIndices[idx] { |
| if v := xmlspec.EncodingDescsToEncodedIn[k]; v != "" && v != "nil" { |
| neededEncodedIn[v] = true |
| } |
| } |
| } |
| |
| var constants []constantData |
| for _, v := range uniqueEncodedIn { |
| name := fmt.Sprintf("enc_%s", convertToGoID(v)) |
| encodedInMap[v] = name |
| // Only add to output if used |
| if neededEncodedIn[v] { |
| constants = append(constants, constantData{ |
| ConstName: name, |
| BinName: v, |
| }) |
| } |
| } |
| for i, k := range sortedEncodings { |
| if !usedFuncIndices[i+1] { |
| continue |
| } |
| sym := xmlspec.EncodingDescsToEncodedIn[k] |
| if name, ok := encodedInMap[sym]; ok { |
| sym = name |
| } else { |
| sym = "enc_NIL" |
| } |
| var body string |
| kUniformed := strings.Join(strings.Fields(k), " ") |
| var name string |
| if b, ok := encodingImpls[kUniformed]; ok { |
| body = b.body |
| name = b.name |
| } else { |
| log.Printf("Warning: encoding func not found for\n%s\n", k) |
| body = defaultEncodingTmpl |
| name = fmt.Sprintf("encodeGen%d", i+1) |
| } |
| data.EncodingFuncs = append(data.EncodingFuncs, funcData{ |
| Comments: encodingFuncComments[k], |
| Body: body, |
| Name: name, |
| }) |
| } |
| data.Constants = constants |
| |
| // Now aggregate all arg patterns to unique globals. |
| argMap := make(map[string]bool) |
| var uniqueArgs []argData |
| for i, inst := range instsData { |
| for j, arg := range inst.Args { |
| name := "a_" + arg.Class[3:] // remove AC_ prefix |
| for _, elm := range arg.Elms { |
| name += "_" + strings.TrimPrefix(elm.FuncName, "encode") |
| } |
| instsData[i].Args[j].Name = name |
| arg.Name = name |
| if !argMap[name] { |
| argMap[name] = true |
| uniqueArgs = append(uniqueArgs, arg) |
| } |
| } |
| } |
| data.UniqueArgs = uniqueArgs |
| // Now aggregate all arg lists to unique globals. |
| opCombMap := make(map[string]opCombData) |
| var uniqueOpCombs []opCombData |
| for i, inst := range instsData { |
| name := "oc" |
| argNames := []string{} |
| argAsms := []string{} |
| for _, arg := range inst.Args { |
| name += "_" + arg.Name[2:] // remove a_ prefix |
| for _, elm := range arg.Elms { |
| name += "_" + strings.TrimPrefix(elm.FuncName, "encode") |
| } |
| argNames = append(argNames, arg.Name) |
| argAsms = append(argAsms, arg.Asm) |
| } |
| if ops, ok := opCombMap[name]; ok { |
| instsData[i].Ops = ops |
| } else { |
| ops := opCombData{ |
| Name: name, |
| ArgNames: argNames, |
| OpAsms: strings.Join(argAsms, ", "), |
| } |
| opCombMap[name] = ops |
| uniqueOpCombs = append(uniqueOpCombs, ops) |
| instsData[i].Ops = ops |
| } |
| } |
| data.UniqueOpCombs = uniqueOpCombs |
| |
| // Figure out better names for the slices of asm operand |
| // checkers/encoders. |
| |
| // Goal is to replace an "ugly name" with a sanitized |
| // version of the instruction operand types; however, sometimes |
| // there is more than one ugly name mapping to the same |
| // nominal operands (an ugly name corresponds to an encoding). |
| // Detect this, and for those cases where there's more than |
| // one ugly name for the same operands, append a sequence |
| // number. |
| operandsToName := func(s string) string { |
| s = strings.Replace(s, "<", "", -1) |
| s = strings.Replace(s, ">", "", -1) |
| s = strings.Replace(s, "/", "", -1) |
| s = strings.Replace(s, ",", "_", -1) |
| s = strings.Replace(s, ".", "_", -1) |
| s = strings.Replace(s, " ", "_", -1) |
| s = strings.Replace(s, "|", "", -1) |
| s = strings.Replace(s, "[", "_", -1) |
| s = strings.Replace(s, "]", "_", -1) |
| s = strings.Replace(s, "{", "", -1) |
| s = strings.Replace(s, "}", "", -1) |
| s = strings.Replace(s, "#", "c", -1) |
| s = strings.Replace(s, "-", "_", -1) |
| s = strings.TrimPrefix(s, "_") |
| return s |
| } |
| |
| ot2ugly2Count := make(map[string]map[string]int) |
| otSlice := []string{} // insertion order of operand types |
| uglyInsertionOrder := make(map[string]int) |
| |
| for _, inst := range data.Insts { |
| uglyName := inst.Ops.Name |
| if x := uglyInsertionOrder[uglyName]; x == 0 { |
| uglyInsertionOrder[uglyName] = 1 + len(uglyInsertionOrder) |
| } |
| operandTypes := inst.Ops.OpAsms |
| if ot2ugly2Count[operandTypes] == nil { |
| otSlice = append(otSlice, operandTypes) |
| ot2ugly2Count[operandTypes] = make(map[string]int) |
| } |
| // keep track of how many instructions with this operand type |
| // sequence use the exact same encoding. |
| ot2ugly2Count[operandTypes][uglyName] += 1 |
| } |
| |
| // Map from ugly name to better name |
| ugly2better := make(map[string]string) |
| // There is sometimes only one ugly name for multiple operand type sequences |
| // that have different semantics, we need to assign them version numbers. |
| usedNames := make(map[string]bool) |
| |
| for _, x := range otSlice { |
| // Get a canonical order of ugly names |
| var uglies []string |
| u2c := ot2ugly2Count[x] |
| for c := range u2c { |
| uglies = append(uglies, c) |
| } |
| |
| baseName := operandsToName(x) |
| |
| if len(uglies) > 1 { |
| // if there is more than one, sort into decreasing |
| // frequency order, then sort by insertion order. |
| sort.Slice(uglies, func(i, j int) bool { |
| if c := u2c[uglies[j]] - u2c[uglies[i]]; c != 0 { |
| return c < 0 |
| } |
| return uglyInsertionOrder[uglies[i]] < uglyInsertionOrder[uglies[j]] |
| }) |
| for j, u := range uglies { |
| name := fmt.Sprintf("%s__%d", baseName, j+1) |
| if usedNames[name] { |
| v := 2 |
| for usedNames[fmt.Sprintf("%s_V%d__%d", baseName, v, j+1)] { |
| v++ |
| } |
| name = fmt.Sprintf("%s_V%d__%d", baseName, v, j+1) |
| } |
| ugly2better[u] = name |
| usedNames[name] = true |
| } |
| } else { |
| // if only one, do not add a __N suffix. |
| name := baseName |
| if usedNames[name] { |
| v := 2 |
| for usedNames[fmt.Sprintf("%s_V%d", baseName, v)] { |
| v++ |
| } |
| name = fmt.Sprintf("%s_V%d", baseName, v) |
| } |
| ugly2better[uglies[0]] = name |
| usedNames[name] = true |
| } |
| } |
| |
| // Apply better name translations. |
| for i, inst := range data.Insts { |
| if arg := ugly2better[inst.Ops.Name]; arg != "" { |
| data.Insts[i].Ops.BetterName = arg |
| } else { |
| data.Insts[i].Ops.BetterName = inst.Ops.Name |
| } |
| } |
| |
| for i, uoc := range data.UniqueOpCombs { |
| if arg := ugly2better[uoc.Name]; arg != "" { |
| data.UniqueOpCombs[i].BetterName = arg |
| } else { |
| data.UniqueOpCombs[i].BetterName = uoc.Name |
| } |
| } |
| |
| // Put these two slices into sensible orders. |
| sort.Slice(data.UniqueOpCombs, func(i, j int) bool { |
| return data.UniqueOpCombs[i].BetterName < data.UniqueOpCombs[j].BetterName |
| }) |
| |
| sort.Slice(data.UniqueArgs, func(i, j int) bool { |
| return data.UniqueArgs[i].Name < data.UniqueArgs[j].Name |
| }) |
| |
| // Group instructions by name |
| // Also sort them by their Go mnemonic, stably. |
| slices.SortFunc(data.Insts, func(i, j instData) int { |
| if c := strings.Compare(i.GoOp, j.GoOp); c != 0 { |
| return c |
| } |
| if i.highFeat != j.highFeat { |
| return i.highFeat - j.highFeat |
| } |
| if c := strings.Compare(i.Ops.BetterName, j.Ops.BetterName); c != 0 { |
| return c |
| } |
| if c := strings.Compare(i.Ops.OpAsms, j.Ops.OpAsms); c != 0 { |
| return c |
| } |
| return strings.Compare(i.FixedBits, j.FixedBits) |
| }) |
| ibn := []instsByName{} |
| prevGoOp := "" |
| for _, inst := range data.Insts { |
| if inst.GoOp != prevGoOp { |
| ibn = append(ibn, instsByName{ |
| Name: inst.GoOp[1:], |
| Insts: []instData{}, |
| }) |
| prevGoOp = inst.GoOp |
| } |
| ibn[len(ibn)-1].Insts = append(ibn[len(ibn)-1].Insts, inst) |
| } |
| data.InstsByName = ibn |
| |
| // Generate inst_gen.go |
| formatAndFlush(outputDir, "src/cmd/internal/obj/arm64/inst_gen.go", data, genTmpl) |
| |
| // Generate encoding_gen.go |
| formatAndFlush(outputDir, "src/cmd/internal/obj/arm64/encoding_gen.go", data, encodingTmpl) |
| |
| // Generate goops_gen.go |
| var sortedNewGoOps []string |
| for k := range newGoOps { |
| sortedNewGoOps = append(sortedNewGoOps, k) |
| } |
| sort.Strings(sortedNewGoOps) |
| goopsPrefix := " obj.As = ASVESTART + 1 + iota" |
| sortedNewGoOps[0] += goopsPrefix |
| formatAndFlush(outputDir, "src/cmd/internal/obj/arm64/goops_gen.go", sortedNewGoOps, goopsTmpl) |
| |
| // Generate anames_gen.go |
| for i := range sortedNewGoOps { |
| sortedNewGoOps[i] = strings.TrimSuffix(sortedNewGoOps[i], goopsPrefix) |
| sortedNewGoOps[i] = strings.TrimPrefix(sortedNewGoOps[i], "A") |
| } |
| formatAndFlush(outputDir, "src/cmd/internal/obj/arm64/anames_gen.go", sortedNewGoOps, anamesTmpl) |
| |
| if genE2E { |
| slices.SortStableFunc(e2eTestData, func(i, j e2eData) int { |
| if i.highFeat != j.highFeat { |
| return i.highFeat - j.highFeat |
| } |
| return strings.Compare(i.GoOp, j.GoOp) |
| }) |
| slices.SortStableFunc(e2eErrorData, func(i, j e2eData) int { |
| if i.highFeat != j.highFeat { |
| return i.highFeat - j.highFeat |
| } |
| return strings.Compare(i.GoOp, j.GoOp) |
| }) |
| // Generate arm64sveenc.s |
| formatAndFlush(outputDir, "src/cmd/asm/internal/asm/testdata/arm64sveenc.s", e2eTestData, e2edataTmpl) |
| // Generate arm64sveerror.s |
| formatAndFlush(outputDir, "src/cmd/asm/internal/asm/testdata/arm64sveerror.s", e2eErrorData, e2edataTmpl) |
| } |
| } |
| |
| // Mappings for operand patterns to concrete registers |
| var regMap = map[string]string{ |
| "Zd": "Z", |
| "Zda": "Z", |
| "Zdn": "Z", |
| "Zn": "Z", |
| "Zm": "Z", |
| "Za": "Z", |
| "Zk": "Z", |
| "Zt": "Z", |
| |
| "Pd": "P", |
| "Pdn": "P", |
| "Pdm": "P", |
| "Pn": "P", |
| "Pm": "P", |
| "Pg": "P", |
| "Pt": "P", |
| "PNd": "PN", |
| "PNn": "PN", |
| "PNg": "PN", |
| "Pv": "P", |
| |
| "Vd": "V", |
| "Vn": "V", |
| "Vm": "V", |
| } |
| |
| // Arrangement variables, also includes predication cases. |
| var arrMap = map[string]bool{ |
| "Tb": true, // arrangement variables |
| "T": true, // arrangement variables |
| "ZM": true, // predication variables |
| } |
| |
| // Z, P reg arrangements, used for variable mutation. |
| var sveArr = []string{"B", "H", "S", "D", "Q"} |
| var svePred = []string{"M", "Z"} |
| |
| // V reg arrangements, used for variable mutation. |
| var neonArrGNU = []string{"16B", "1D", "4H", "8H", "2S", "4S", "2D", "1Q"} |
| var neonArrGo = []string{"B16", "D1", "H4", "H8", "S2", "S4", "D2", "Q1"} |
| |
| // assembleGNU assembles the given assembly string using GNU toolchain and returns the hex. |
| func assembleGNU(s string) (string, error) { |
| // Create temp file |
| tmpName := "temp_probe.s" |
| if err := os.WriteFile(tmpName, []byte(s+"\n"), 0644); err != nil { |
| return "", err |
| } |
| defer os.Remove(tmpName) |
| |
| // Run as |
| cmd := exec.Command("aarch64-linux-gnu-as", "-march=all", tmpName, "-o", "temp_probe.o") |
| if out, err := cmd.CombinedOutput(); err != nil { |
| return "", fmt.Errorf("as failed: %s", out) |
| } |
| defer exec.Command("rm", "temp_probe.o").Run() |
| |
| // Run objdump |
| cmd = exec.Command("aarch64-linux-gnu-objdump", "-d", "temp_probe.o") |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| return "", err |
| } |
| |
| // Parse output |
| lines := strings.Split(string(out), "\n") |
| for _, l := range lines { |
| l = strings.TrimSpace(l) |
| // The first text line contains the hex. |
| if strings.Contains(l, "0:") { |
| parts := strings.Fields(l) |
| // parts[0] is offset:, parts[1] is hex |
| if len(parts) >= 2 { |
| if len(parts[1]) == 8 { |
| return parts[1], nil |
| } |
| } |
| } |
| } |
| return "", fmt.Errorf("no hex found") |
| } |
| |
| // reverseHex reverses the byte order of a hex string. |
| // this is required for Go's e2e test. |
| func reverseHex(h string) string { |
| if len(h) != 8 { |
| return h |
| } |
| return h[6:8] + h[4:6] + h[2:4] + h[0:2] |
| } |
| |
| var prefetchOps = []string{ |
| "PLDL1KEEP", "PLDL1STRM", "PLDL2KEEP", "PLDL2STRM", |
| "PLDL3KEEP", "PLDL3STRM", "PSTL1KEEP", "PSTL1STRM", |
| "PSTL2KEEP", "PSTL2STRM", "PSTL3KEEP", "PSTL3STRM", |
| } |
| var vlOps = []string{"VLx2", "VLx4"} |
| |
| // constructInstance takes an [Encoding] and tries to generate a |
| // valid Go assembly instance of it and returns the e2eData for this instance. |
| // The construction is deterministic. |
| // |
| // This function requires the availablity of the GNU toolchain. |
| // Special instructions are elided, they are documented in the comments. |
| // Technically we can skip them early before we do the construction, but |
| // we kept them after the construction for future verification. |
| func constructInstance(enc *xmlspec.EncodingParsed) (*e2eData, *e2eData) { |
| var name string |
| // Try to construct two assembly, one in Go the other in GNU. |
| // The trial succeeds once the GNU one is accepted by the GNU toolchain. |
| rng := rand.New(rand.NewPCG(uint64(42), uint64(1024))) |
| // Try 100 times until we find one that's accepted by the GNU toolchain. |
| // constructInstance does not understand the encoding level constraint, so |
| // it just try until a valid one is found. |
| var highFeat int |
| var gnuErr error |
| var errCase *e2eData |
| var validCase *e2eData |
| for range 100 { |
| goAsmOps := make([]string, 0) |
| gnuAsmOps := make([]string, 0) |
| // Useful for pruning AC_ARNG, AC_PREG, AC_PREGZM, AC_ZREG. |
| // ABS <Zd>.<T>, <Pg>/Z, <Zn>.<T> |
| // the 2 instances of <T> should be exactly the same... |
| // Key is the random number selected. |
| regCache := map[string]int{} |
| arrCache := map[string]int{} |
| immCache := map[string]int{} |
| cachedOrNew := func(cache map[string]int, key string, n int) int { |
| if v, ok := cache[key]; ok { |
| if errCase != nil || len(enc.Operands) == 0 { |
| // Give the error case a chance to be triggered. |
| return v |
| } |
| } |
| v := rng.IntN(n) |
| cache[key] = v |
| return v |
| } |
| for i, op := range enc.Operands { |
| if i == 0 { |
| // First operand is the instruction name. |
| name = op.Name |
| continue |
| } |
| opName := op.Name |
| if _, ok := operandTypeOrders[op.Typ]; !ok { |
| log.Fatalf("Unsupported operand type in constructInstance: %s, enc: %s", op.Typ, enc.String()) |
| } |
| highFeat = max(highFeat, operandTypeOrders[op.Typ]) |
| generateSVERegOp := func(opName string) (string, string, string, int, string) { |
| var parts []string |
| var isPred bool |
| if strings.Contains(opName, ".") { |
| parts = strings.Split(opName, ".") |
| } else { |
| parts = strings.Split(opName, "/") |
| isPred = true |
| } |
| if len(parts) == 0 { |
| log.Fatalf("Unrecognized operand: %s", opName) |
| } |
| regName := strings.TrimSuffix(strings.TrimPrefix(parts[0], "<"), ">") |
| var arrName string |
| if len(parts) > 1 { |
| arrName = strings.TrimSuffix(strings.TrimPrefix(parts[1], "<"), ">") |
| } |
| resolvedArr := arrName |
| regNameSanitized := strings.TrimRight(regName, "0123456789") |
| reg := regMap[regNameSanitized] |
| if reg == "" { |
| reg = regMap[regName] |
| } |
| var goAsmOp, gnuAsmOp string |
| var regIdx int |
| if arrMap[arrName] || errCase == nil { |
| // Give error case a random mutation to trigger an error. |
| // Variables, needs mutation |
| switch reg { |
| case "Z": |
| regIdx = cachedOrNew(regCache, regName, 32) |
| arrIdx := cachedOrNew(arrCache, arrName, len(sveArr)) |
| resolvedArr = sveArr[arrIdx] |
| goAsmOp = fmt.Sprintf("Z%d.%s", regIdx, resolvedArr) |
| gnuAsmOp = goAsmOp |
| case "P", "PN": |
| regIdx = cachedOrNew(regCache, regName, 15) |
| if isPred { |
| arrIdx := cachedOrNew(arrCache, arrName, len(svePred)) |
| resolvedArr = svePred[arrIdx] |
| gnuAsmOp = fmt.Sprintf("%s%d/%s", reg, regIdx, resolvedArr) |
| goAsmOp = fmt.Sprintf("%s%d.%s", reg, regIdx, resolvedArr) |
| } else { |
| arrIdx := cachedOrNew(arrCache, arrName, len(sveArr)) |
| resolvedArr = sveArr[arrIdx] |
| goAsmOp = fmt.Sprintf("%s%d.%s", reg, regIdx, resolvedArr) |
| gnuAsmOp = goAsmOp |
| } |
| case "V": |
| regIdx = cachedOrNew(regCache, regName, 32) |
| arrIdx := cachedOrNew(arrCache, arrName, len(neonArrGNU)) |
| resolvedArr = neonArrGNU[arrIdx] |
| gnuAsmOp = fmt.Sprintf("V%d.%s", regIdx, neonArrGNU[arrIdx]) |
| goAsmOp = fmt.Sprintf("V%d.%s", regIdx, neonArrGo[arrIdx]) |
| } |
| } else { |
| // Fixed arrangement or predications |
| var dotArr string |
| if arrName != "" { |
| dotArr = "." + arrName |
| } |
| switch reg { |
| case "Z": |
| regIdx = cachedOrNew(regCache, regName, 32) |
| goAsmOp = fmt.Sprintf("Z%d%s", regIdx, dotArr) |
| gnuAsmOp = goAsmOp |
| case "P", "PN": |
| regIdx = cachedOrNew(regCache, regName, 15) |
| if isPred { |
| var arrNameGNU string |
| if arrName != "" { |
| arrNameGNU = "/" + arrName |
| } |
| gnuAsmOp = fmt.Sprintf("%s%d%s", reg, regIdx, arrNameGNU) |
| goAsmOp = fmt.Sprintf("%s%d%s", reg, regIdx, dotArr) |
| } else { |
| goAsmOp = fmt.Sprintf("%s%d%s", reg, regIdx, dotArr) |
| gnuAsmOp = goAsmOp |
| } |
| case "V": |
| log.Fatalf("Unexpected V with fixed arrangement: %s", opName) |
| } |
| } |
| return goAsmOp, gnuAsmOp, reg, regIdx, resolvedArr |
| } |
| |
| if op.Typ == "AC_ARNG" || op.Typ == "AC_PREG" || op.Typ == "AC_PREGZM" || op.Typ == "AC_ZREG" { |
| goAsmOp, gnuAsmOp, _, _, _ := generateSVERegOp(opName) |
| gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...) |
| goAsmOps = append(goAsmOps, goAsmOp) |
| } else if op.Typ == "AC_ARNGIDX" || op.Typ == "AC_ZREGIDX" || op.Typ == "AC_PREGIDX" { |
| // Operands that takes SVE/SIMD registers and arrangement with index. |
| // reg.T[index] |
| var regPrefix string |
| var regName string |
| var arrangement string |
| index := rng.IntN(4) |
| |
| if op.Typ == "AC_ARNGIDX" { |
| parts := strings.Split(opName, ".") |
| regName = strings.TrimSuffix(strings.TrimPrefix(parts[0], "<"), ">") |
| regPrefix = regMap[regName] |
| if regPrefix == "" { |
| log.Fatalf("Unknown register prefix: %s", regName) |
| } |
| |
| parts2 := strings.Split(parts[1], "[") |
| arrangement = strings.TrimSuffix(strings.TrimPrefix(parts2[0], "<"), ">") |
| } else if op.Typ == "AC_ZREGIDX" || op.Typ == "AC_PREGIDX" { |
| re := regexp.MustCompile(`<([A-Z][A-Za-z0-9]*)>`) |
| matches := re.FindStringSubmatch(opName) |
| if len(matches) > 1 { |
| regName = matches[1] |
| regPrefix = regMap[regName] |
| if regPrefix == "" { |
| log.Fatalf("Unknown register: %s", regName) |
| } |
| } else { |
| log.Fatalf("Unknown AC_[PZ]REGIDX: %s", opName) |
| } |
| } |
| |
| var goAsmOp, gnuAsmOp string |
| limit := 32 |
| if regPrefix == "P" || regPrefix == "PN" { |
| limit = 16 |
| } |
| regIdx := cachedOrNew(regCache, regName, limit) |
| if regPrefix == "V" { |
| log.Fatalf("Unexpected V with index: %s", opName) |
| } |
| |
| if arrangement != "" { |
| if arrMap[arrangement] { |
| arrIdx := cachedOrNew(arrCache, arrangement, len(sveArr)) |
| goAsmOp = fmt.Sprintf("%s%d.%s[%d]", regPrefix, regIdx, sveArr[arrIdx], index) |
| gnuAsmOp = goAsmOp |
| } else { |
| // Fixed arrangement |
| goAsmOp = fmt.Sprintf("%s%d.%s[%d]", regPrefix, regIdx, arrangement, index) |
| gnuAsmOp = goAsmOp |
| } |
| } else { |
| goAsmOp = fmt.Sprintf("%s%d[%d]", regPrefix, regIdx, index) |
| gnuAsmOp = goAsmOp |
| } |
| gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...) |
| goAsmOps = append(goAsmOps, goAsmOp) |
| } else if op.Typ == "AC_SPZGREG" || op.Typ == "AC_VREG" { |
| // Operands that takes scalar or NEON vector registers. |
| // There arn't that much of shapes, just enumerate them... |
| var goPrefix string |
| switch op.Typ { |
| case "AC_SPZGREG": |
| goPrefix = "R" |
| case "AC_VREG": |
| goPrefix = "V" |
| } |
| var gnuPrefix string |
| switch op.Typ { |
| case "AC_SPZGREG": |
| if strings.Contains(op.Name, "W") { |
| gnuPrefix = "W" |
| } else if strings.Contains(op.Name, "<R>") { |
| // We know the parser will append the width to the GoOp as a suffix. |
| switch enc.GoOp[len(enc.GoOp)-1] { |
| case 'W': |
| gnuPrefix = "W" |
| default: |
| // X does not have a suffix, as we know from the parser rules. |
| gnuPrefix = "X" |
| } |
| } else { |
| gnuPrefix = "X" |
| } |
| case "AC_VREG": |
| if strings.Contains(op.Name, "<V>") || strings.Contains(op.Name, "<Dd>") { |
| // We know the parser will append the width to the GoOp as a suffix. |
| // Also <Dd> literally means the width is D. |
| suffix := enc.GoOp[len(enc.GoOp)-1] |
| switch suffix { |
| case 'B', 'H', 'S', 'D': |
| gnuPrefix = string(suffix) |
| default: |
| log.Fatalf("Unrecognized SIMD vector width: %c, enc: %s", suffix, enc.String()) |
| } |
| } else { |
| gnuPrefix = "V" |
| } |
| } |
| var goAsmOp, gnuAsmOp string |
| switch op.Name { |
| case "<R><n|SP>", "<Xd|SP>", "<Xn|SP>", "<R><d>", "<R><dn>", "<R><m>", |
| "<R><n>", "<Wdn>", "<Xd>", "<Xdn>", "<Xm>", "<Xn>", "<Dd>", "<V><d>", |
| "<V><dn>", "<V><m>", "<V><n>": |
| // R32 is RSP |
| regIdx := cachedOrNew(regCache, op.Name, 64) |
| goAsmOp = fmt.Sprintf("%s%d", goPrefix, regIdx) |
| gnuAsmOp = fmt.Sprintf("%s%d", gnuPrefix, regIdx) |
| if regIdx >= 31 && regIdx < 48 { |
| goAsmOp = "RSP" |
| if gnuPrefix == "W" { |
| gnuAsmOp = "WSP" |
| } else { |
| gnuAsmOp = "SP" |
| } |
| } |
| if regIdx >= 56 { |
| goAsmOp = "ZR" |
| if gnuPrefix == "W" { |
| gnuAsmOp = "WZR" |
| } else if gnuPrefix == "X" { |
| gnuAsmOp = "XZR" |
| } |
| } |
| default: |
| log.Fatalf("Unrecognized operand: %s", op.Name) |
| } |
| gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...) |
| goAsmOps = append(goAsmOps, goAsmOp) |
| } else if op.Typ == "AC_IMM" { |
| var imm string |
| var isFloat bool |
| // Check if this is a float imm or an integer imm. |
| if op.Name == "#0.0" { |
| imm = "0.0" |
| isFloat = true |
| } else { |
| for _, e := range op.Elems { |
| if strings.Contains(e.TextExpWithRanges, "float") { |
| isFloat = true |
| break |
| } |
| } |
| if isFloat { |
| // Usually these floats are 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5... |
| imm = fmt.Sprintf("%.1f", float64(cachedOrNew(immCache, op.Name, 20))/5.0) |
| } else { |
| intImm := cachedOrNew(immCache, op.Name, 16) |
| if intImm > 12 { |
| // Some instructions likes 90, 270; let's generate them to make those instructions happy. |
| imm = fmt.Sprintf("%d", (intImm-12)*90) |
| } else { |
| imm = fmt.Sprintf("%d", intImm) |
| } |
| } |
| } |
| if isFloat { |
| goAsmOps = append(goAsmOps, fmt.Sprintf("$(%s)", imm)) |
| } else { |
| goAsmOps = append(goAsmOps, fmt.Sprintf("$%s", imm)) |
| } |
| gnuAsmOps = append([]string{fmt.Sprintf("#%s", imm)}, gnuAsmOps...) |
| } else if op.Typ == "AC_SPECIAL" { |
| var opStr string |
| if op.Name == "<prfop>" { |
| idx := cachedOrNew(immCache, op.Name, len(prefetchOps)) |
| opStr = prefetchOps[idx] |
| } else if op.Name == "<vl>" { |
| idx := cachedOrNew(immCache, op.Name, len(vlOps)) |
| opStr = vlOps[idx] |
| } else { |
| log.Fatalf("Unrecognized AC_SPECIAL operand: %s", op.Name) |
| } |
| goAsmOps = append(goAsmOps, opStr) |
| gnuAsmOps = append([]string{opStr}, gnuAsmOps...) |
| } else if op.Typ == "AC_REGLIST1" || op.Typ == "AC_REGLIST2" || op.Typ == "AC_REGLIST3" || op.Typ == "AC_REGLIST4" { |
| // Register list, they must be contiguous modulo 32 (16 for P registers). |
| // Only AC_REGLIST2 has a P register variant though. |
| // { <Zn>.<T> } |
| // { <Pd1>.<T>, <Pd2>.<T> } |
| // { <Zn1>.B, <Zn2>.B } |
| // { <Zt1>.B, <Zt2>.B, <Zt3>.B } |
| // { <Zt1>.B, <Zt2>.B, <Zt3>.B, <Zt4>.B } |
| // We can probably reuse the logic for these registers in AC_ARNG case. |
| // In Go, the register list is wrapped around [], while in GNU it's wrapped around {}. |
| nRegs := 1 |
| switch op.Typ { |
| case "AC_REGLIST2": |
| nRegs = 2 |
| case "AC_REGLIST3": |
| nRegs = 3 |
| case "AC_REGLIST4": |
| nRegs = 4 |
| } |
| |
| // Extract the first operand inside the list. |
| trimmedName := strings.Trim(op.Name, "{} ") |
| parts := strings.Split(trimmedName, ",") |
| firstOp := strings.TrimSpace(parts[0]) |
| |
| _, _, regPrefix, regIdx, arr := generateSVERegOp(firstOp) |
| |
| limit := 32 |
| if regPrefix == "P" || regPrefix == "PN" { |
| limit = 16 |
| } |
| |
| goRegs := []string{} |
| gnuRegs := []string{} |
| for i := 0; i < nRegs; i++ { |
| currIdx := (regIdx + i) % limit |
| var goReg, gnuReg string |
| if arr != "" { |
| goReg = fmt.Sprintf("%s%d.%s", regPrefix, currIdx, arr) |
| gnuReg = goReg |
| } else { |
| goReg = fmt.Sprintf("%s%d", regPrefix, currIdx) |
| gnuReg = goReg |
| } |
| goRegs = append(goRegs, goReg) |
| gnuRegs = append(gnuRegs, gnuReg) |
| } |
| |
| goAsmOp := "[" + strings.Join(goRegs, ", ") + "]" |
| gnuAsmOp := "{" + strings.Join(gnuRegs, ", ") + "}" |
| |
| goAsmOps = append(goAsmOps, goAsmOp) |
| gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...) |
| } else if op.Typ == "AC_REGLIST_RANGE" { |
| nRegs := 2 |
| if strings.Contains(op.Name, "4") { |
| nRegs = 4 |
| } |
| |
| trimmedName := strings.Trim(op.Name, "{} ") |
| parts := strings.Split(trimmedName, "-") |
| firstOp := strings.TrimSpace(parts[0]) |
| |
| _, _, regPrefix, regIdx, arr := generateSVERegOp(firstOp) |
| limit := 32 |
| if regPrefix == "P" || regPrefix == "PN" { |
| limit = 16 |
| } |
| |
| reg1Idx := regIdx |
| reg2Idx := (regIdx + nRegs - 1) % limit |
| |
| var goReg1, goReg2, gnuReg1, gnuReg2 string |
| if arr != "" { |
| goReg1 = fmt.Sprintf("%s%d.%s", regPrefix, reg1Idx, arr) |
| goReg2 = fmt.Sprintf("%s%d.%s", regPrefix, reg2Idx, arr) |
| gnuReg1 = goReg1 |
| gnuReg2 = goReg2 |
| } else { |
| goReg1 = fmt.Sprintf("%s%d", regPrefix, reg1Idx) |
| goReg2 = fmt.Sprintf("%s%d", regPrefix, reg2Idx) |
| gnuReg1 = goReg1 |
| gnuReg2 = goReg2 |
| } |
| |
| goAsmOp := fmt.Sprintf("[%s-%s]", goReg1, goReg2) |
| gnuAsmOp := fmt.Sprintf("{%s-%s}", gnuReg1, gnuReg2) |
| |
| goAsmOps = append(goAsmOps, goAsmOp) |
| gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...) |
| } else if op.Typ == "AC_PREGSEL" { |
| // GNU: <preg>.<T>[<selreg>, <imm>] |
| // Go: [selreg.T1, $idximm](preg.T2) |
| regIdx := cachedOrNew(regCache, "P_pregsel", 16) |
| arrIdx := rng.IntN(len(sveArr)) |
| resolvedArr := sveArr[arrIdx] |
| selIdx := cachedOrNew(regCache, "sel_pregsel", 32) |
| if selIdx == 18 { |
| selIdx = 19 |
| } |
| immVal := cachedOrNew(immCache, "imm_pregsel", 4) |
| gnuAsmOp := fmt.Sprintf("P%d.%s[W%d, %d]", regIdx, resolvedArr, selIdx, immVal) |
| goAsmOp := fmt.Sprintf("[R%d, $%d](P%d.%s)", selIdx, immVal, regIdx, resolvedArr) |
| gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...) |
| goAsmOps = append(goAsmOps, goAsmOp) |
| } else if op.Typ == "AC_MEMEXT" { |
| var goReg1, gnuReg1 string |
| var goReg2, gnuReg2 string |
| var mod, amount string |
| |
| // 1. Determine reg1 (Base) |
| if strings.Contains(op.Name, "<Xn|SP>") { |
| regIdx := cachedOrNew(regCache, "Xn|SP", 40) |
| if regIdx > 31 { |
| goReg1 = "RSP" |
| gnuReg1 = "SP" |
| } else { |
| if regIdx == 18 { |
| // R18 is reserved as the platform register. |
| regIdx = 19 |
| } |
| goReg1 = fmt.Sprintf("R%d", regIdx) |
| gnuReg1 = fmt.Sprintf("X%d", regIdx) |
| } |
| } else if strings.Contains(op.Name, "<Zn>") { |
| regIdx := cachedOrNew(regCache, "Zn", 32) |
| arr := "D" |
| if strings.Contains(op.Name, ".S") { |
| arr = "S" |
| } else if strings.Contains(op.Name, ".D") { |
| arr = "D" |
| } else if strings.Contains(op.Name, ".<T>") { |
| arr = sveArr[rng.IntN(len(sveArr))] |
| } |
| goReg1 = fmt.Sprintf("Z%d.%s", regIdx, arr) |
| gnuReg1 = goReg1 |
| } |
| |
| // 2. Determine reg2 (Offset) |
| if strings.Contains(op.Name, "<Xm>") { |
| regIdx := cachedOrNew(regCache, "Xm", 32) |
| if regIdx == 18 { |
| regIdx = 19 |
| } |
| goReg2 = fmt.Sprintf("R%d", regIdx) |
| gnuReg2 = fmt.Sprintf("X%d", regIdx) |
| } else if strings.Contains(op.Name, "<Zm>") { |
| regIdx := cachedOrNew(regCache, "Zm", 32) |
| arr2 := "D" |
| if strings.Contains(op.Name, ".S") { |
| arr2 = "S" |
| } else if strings.Contains(op.Name, ".D") { |
| arr2 = "D" |
| } else if strings.Contains(op.Name, ".<T>") { |
| arr2 = sveArr[rng.IntN(len(sveArr))] |
| } |
| goReg2 = fmt.Sprintf("Z%d.%s", regIdx, arr2) |
| gnuReg2 = goReg2 |
| } |
| |
| // 3. Determine mod |
| if strings.Contains(op.Name, "LSL") { |
| mod = "LSL" |
| } else if strings.Contains(op.Name, "UXTW") { |
| mod = "UXTW" |
| } else if strings.Contains(op.Name, "SXTW") { |
| mod = "SXTW" |
| } else if strings.Contains(op.Name, "<mod>") { |
| mods := []string{"", "LSL", "UXTW", "SXTW"} |
| mod = mods[rng.IntN(len(mods))] |
| } |
| |
| // 4. Determine amount |
| if strings.Contains(op.Name, "#1") { |
| amount = "1" |
| } else if strings.Contains(op.Name, "#2") { |
| amount = "2" |
| } else if strings.Contains(op.Name, "#3") { |
| amount = "3" |
| } else if strings.Contains(op.Name, "#4") { |
| amount = "4" |
| } else if strings.Contains(op.Name, "<amount>") || strings.Contains(op.Name, "{<amount>}") { |
| if mod != "" { |
| amount = fmt.Sprintf("%d", rng.IntN(3)+1) // 1, 2, 3 |
| } |
| } |
| |
| // Enforce rule: mod and amount must be nil together, or non nil together |
| if strings.Contains(op.Name, "<mod>") && strings.Contains(op.Name, "<amount>") { |
| if mod == "" { |
| amount = "" |
| } else if amount == "" { |
| amount = fmt.Sprintf("%d", rng.IntN(3)+1) |
| } |
| } |
| |
| // 5. Construct GNU string |
| gnuParts := []string{gnuReg1} |
| if gnuReg2 != "" { |
| gnuParts = append(gnuParts, gnuReg2) |
| } |
| if mod != "" && amount != "" { |
| gnuParts = append(gnuParts, fmt.Sprintf("%s #%s", mod, amount)) |
| } else if mod != "" { |
| gnuParts = append(gnuParts, mod) |
| } |
| gnuAsmOp := "[" + strings.Join(gnuParts, ", ") + "]" |
| |
| // 6. Construct Go string |
| var goOffset string |
| if goReg2 != "" { |
| goOffset = goReg2 |
| if mod == "LSL" { |
| if amount != "" { |
| goOffset = fmt.Sprintf("%s<<%s", goReg2, amount) |
| } |
| } else if mod != "" { |
| if amount != "" { |
| goOffset = fmt.Sprintf("%s.%s<<%s", goReg2, mod, amount) |
| } else { |
| goOffset = fmt.Sprintf("%s.%s", goReg2, mod) |
| } |
| } |
| } |
| |
| var goAsmOp string |
| if goOffset != "" { |
| goAsmOp = fmt.Sprintf("(%s)(%s)", goOffset, goReg1) |
| } else { |
| goAsmOp = fmt.Sprintf("(%s)", goReg1) |
| } |
| |
| goAsmOps = append(goAsmOps, goAsmOp) |
| gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...) |
| } else if op.Typ == "AC_MEMOFF" { |
| var goReg, gnuReg string |
| var imm string |
| |
| if strings.Contains(op.Name, "<Xn|SP>") { |
| regIdx := cachedOrNew(regCache, "Xn|SP", 40) |
| if regIdx > 31 { |
| goReg = "RSP" |
| gnuReg = "SP" |
| } else { |
| if regIdx == 18 { |
| regIdx = 19 |
| } |
| if regIdx == 28 { |
| // R28 is G... |
| regIdx = 29 |
| } |
| goReg = fmt.Sprintf("R%d", regIdx) |
| gnuReg = fmt.Sprintf("X%d", regIdx) |
| } |
| } else if strings.Contains(op.Name, "<Zn>") { |
| regIdx := cachedOrNew(regCache, "Zn", 32) |
| arr := "D" |
| if strings.Contains(op.Name, ".S") { |
| arr = "S" |
| } else if strings.Contains(op.Name, ".D") { |
| arr = "D" |
| } |
| goReg = fmt.Sprintf("Z%d.%s", regIdx, arr) |
| gnuReg = goReg |
| } |
| |
| intImm := rng.IntN(16) |
| if intImm == 0 { |
| imm = "" |
| } else { |
| imm = fmt.Sprintf("%d", intImm) |
| } |
| |
| var goAsmOp, gnuAsmOp string |
| if imm != "" { |
| goAsmOp = fmt.Sprintf("%s(%s)", imm, goReg) |
| gnuAsmOp = fmt.Sprintf("[%s, #%s]", gnuReg, imm) |
| } else { |
| goAsmOp = fmt.Sprintf("(%s)", goReg) |
| gnuAsmOp = fmt.Sprintf("[%s]", gnuReg) |
| } |
| |
| goAsmOps = append(goAsmOps, goAsmOp) |
| gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...) |
| } else if op.Typ == "AC_MEMOFFMULVL" { |
| var goReg, gnuReg string |
| var imm string |
| |
| if strings.Contains(op.Name, "<Xn|SP>") { |
| regIdx := cachedOrNew(regCache, "Xn|SP", 40) |
| if regIdx > 31 { |
| goReg = "RSP" |
| gnuReg = "SP" |
| } else { |
| if regIdx == 18 { |
| regIdx = 19 |
| } |
| if regIdx == 28 { |
| regIdx = 29 |
| } |
| goReg = fmt.Sprintf("R%d", regIdx) |
| gnuReg = fmt.Sprintf("X%d", regIdx) |
| } |
| } |
| |
| intImm := rng.IntN(16) - 8 |
| if intImm == 0 { |
| intImm = 1 |
| } |
| imm = fmt.Sprintf("%d", intImm) |
| |
| var goAsmOp, gnuAsmOp string |
| if intImm < 0 { |
| goAsmOp = fmt.Sprintf("(-VL*%d)(%s)", -intImm, goReg) |
| } else { |
| goAsmOp = fmt.Sprintf("(VL*%d)(%s)", intImm, goReg) |
| } |
| gnuAsmOp = fmt.Sprintf("[%s, #%s, MUL VL]", gnuReg, imm) |
| |
| goAsmOps = append(goAsmOps, goAsmOp) |
| gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...) |
| } |
| } |
| // Try to assemble the GNU version. |
| gnuAsm := fmt.Sprintf(".arch_extension sve2p1\n%s %s", name, strings.Join(gnuAsmOps, ", ")) |
| hex, err := assembleGNU(gnuAsm) |
| goAsmStr := fmt.Sprintf("%s %s", enc.GoOp[1:], strings.Join(goAsmOps, ", ")) |
| if err != nil { |
| gnuErr = err |
| if errCase == nil { |
| errCase = &e2eData{ |
| GoOp: enc.GoOp[1:], |
| Asm: fmt.Sprintf("%-50s", goAsmStr), |
| Binary: "// ERROR \"illegal combination from SVE\"", |
| highFeat: highFeat, |
| } |
| } |
| continue |
| } |
| if validCase == nil { |
| validCase = &e2eData{ |
| GoOp: enc.GoOp[1:], |
| Asm: fmt.Sprintf("%-50s", goAsmStr), |
| Binary: "// " + reverseHex(hex), |
| highFeat: highFeat, |
| } |
| } |
| if name == "CMPLT" || name == "CMPLE" || name == "CMPLO" || name == "CMPLS" { |
| // These instructions has an aliased instruction that's not accepted by the GNU assembler. |
| // However at this moment Go does not support the aliased instruction. |
| // These e2e test cases will be compared to another non-aliased instruction of the same |
| // mnemonic, causing a test failure. |
| // Before we support alias in Go, we skip these test cases. |
| todoCase := e2eData{GoOp: enc.GoOp[1:], Asm: fmt.Sprintf("// TODO: %s", name), highFeat: highFeat} |
| return &todoCase, errCase |
| } |
| if validCase == nil || (errCase == nil && len(gnuAsmOps) != 0) { |
| // We must collect at least one valid and one error case. |
| // if the instruction has 0 operands, we can't generate an error case. |
| continue |
| } |
| |
| if enc.Asm == "INDEX <Zd>.<T>, <R><n>, #<imm>" { |
| // Don't generate error case for this, the GNU assembler has a strict scalar and vector register width |
| // constraint, i.e. <R> must be W if <T> is B, H, S. The Go assembler doesn't have this constraint. |
| // So we can't generate an error case for this. |
| errCase = &e2eData{GoOp: enc.GoOp[1:], Asm: fmt.Sprintf("// TODO: %s", name), highFeat: highFeat} |
| } |
| return validCase, errCase |
| } |
| if name == "ADDQP" || name == "ADDSUBP" || name == "SCVTFLT" || name == "UCVTFLT" || name == "LUTI6" || name == "FCVTZSN" || |
| name == "FCVTZUN" { |
| // Very new instructions |
| // GNU toolchain 2.45 doesn't know about these instruction yet. |
| todoCase := e2eData{GoOp: enc.GoOp[1:], Asm: fmt.Sprintf("// TODO: %s", name), highFeat: highFeat} |
| return &todoCase, &todoCase |
| } |
| if enc.Asm == "BFMMLA <Zda>.H, <Zn>.H, <Zm>.H" || |
| enc.Asm == "FMMLA <Zda>.H, <Zn>.H, <Zm>.H" || |
| enc.Asm == "SABAL <Zda>.<T>, <Zn>.<Tb>, <Zm>.<Tb>" || |
| enc.Asm == "SCVTF <Zd>.<T>, <Zn>.<Tb>" || |
| enc.Asm == "SDOT <Zda>.H, <Zn>.B, <Zm>.B" || |
| enc.Asm == "SUBP <Zdn>.<T>, <Pg>/M, <Zdn>.<T>, <Zm>.<T>" || |
| enc.Asm == "UABAL <Zda>.<T>, <Zn>.<Tb>, <Zm>.<Tb>" || |
| enc.Asm == "UCVTF <Zd>.<T>, <Zn>.<Tb>" || |
| enc.Asm == "UDOT <Zda>.H, <Zn>.B, <Zm>.B" || |
| enc.Asm == "SDOT <Zda>.H, <Zn>.B, <Zm>.B[<imm>]" || |
| enc.Asm == "UDOT <Zda>.H, <Zn>.B, <Zm>.B[<imm>]" || |
| enc.Asm == "SQRSHRN <Zd>.B, { <Zn1>.H-<Zn2>.H }, #<const>" || |
| enc.Asm == "SQRSHRUN <Zd>.B, { <Zn1>.H-<Zn2>.H }, #<const>" || |
| enc.Asm == "SQSHRN <Zd>.<T>, { <Zn1>.<Tb>-<Zn2>.<Tb> }, #<const>" || |
| enc.Asm == "SQSHRUN <Zd>.<T>, { <Zn1>.<Tb>-<Zn2>.<Tb> }, #<const>" || |
| enc.Asm == "UQRSHRN <Zd>.B, { <Zn1>.H-<Zn2>.H }, #<const>" || |
| enc.Asm == "UQSHRN <Zd>.<T>, { <Zn1>.<Tb>-<Zn2>.<Tb> }, #<const>" { |
| // Very new instruction encodings |
| // GNU toolchain 2.45 doesn't know about these specific encodings yet. |
| todoCase := e2eData{GoOp: enc.GoOp[1:], Asm: fmt.Sprintf("// TODO: %s", enc.Asm), highFeat: highFeat} |
| return &todoCase, &todoCase |
| } |
| log.Fatalf("Failed to construct instance for %s: %v", enc.Asm, gnuErr) |
| return &e2eData{GoOp: enc.GoOp[1:]}, &e2eData{GoOp: enc.GoOp[1:]} |
| } |