arm64/instgen: add assembler code generator for SVE This CL implements the code generation logic. It generates 4 files to the assembler: - inst_gen.go: this file contains the instruction table for these new SVE instructions. - goops_gen.go: this file contains the go op constants for these new SVE instructions. - anames_gen.go: this file contains the anames (debugger symbols) for these new SVE instructions. - encoding_gen.go: this file contains the encoding functions for parts (elements) of these new SVE instructions. They are emitted with their natural language specification and we need to fill up their logic, which will be in the next CL. This CL generated files into CL 747180 This CL adds logic to allow only certain AClasses to be generated. This CL also merged PREGZ and PREGM, and defer its check to encoding phase. This is required to distinguish <P>/<ZM> cases. This CL also filters out the generation of aliased encodings, as they are not fully specified. This CL also added encoding specifications for another weird encoding defined in the decoding section, please see the added code in `extractBinary` for details. An example instruction is "Unsigned divide (predicated)". It is useful for partial code gen for assembler without all AClasses support. This CL added a generation target that generates e2e test data, it uses the GNU toolchain as an oracle. This CL assumes the toolchain version 2.45. There currently exists a bleeding edge new toolchain 2.46, some special cases in `constructInstance` might be removed if we upgrade the GNU toolchain. This CL also rearranged the types. XML types are parsed data types are in different files now. Change-Id: Ia7e30e1d1da17ad6aff5d963cf90fe76bc1a76fd Reviewed-on: https://go-review.googlesource.com/c/arch/+/755180 Reviewed-by: David Chase <drchase@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/arm64/instgen/encodings.go b/arm64/instgen/encodings.go new file mode 100644 index 0000000..7ceebe9 --- /dev/null +++ b/arm64/instgen/encodings.go
@@ -0,0 +1,608 @@ +// 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 + +var defaultEncodingTmpl string = ` + // TODO: implement this. + // Note: the raw value v is from obj.Prog, starting at bit 0. + // e.g. If it's a register, then its value is following the ones defined in a.out.go + // If it's a constant, then its value is the constant value. + return 0, false` // The %s will be replaced by the symbol name in the operand initialiser. + +// encodingImpls are the known implementations of encoding functions, key is their description, value is their implementation. +type encodingImpl struct { + name string + body string + encodedIn string +} + +// encodingImpls are the known implementations of encoding functions, key is their description, value is their implementation. +var encodingImpls = map[string]encodingImpl{ + "Check this is a B arrangement": {"encodeArngBCheck", `if v == ARNG_B { + return 0, true + } + return 0, false`, "enc_NIL"}, + "Check this is a D arrangement": {"encodeArngDCheck", `if v == ARNG_D { + return 0, true + } + return 0, false`, "enc_NIL"}, + "Check this is a H arrangement": {"encodeArngHCheck", `if v == ARNG_H { + return 0, true + } + return 0, false`, "enc_NIL"}, + "Check this is a Q arrangement": {"encodeArngQCheck", `if v == ARNG_Q { + return 0, true + } + return 0, false`, "enc_NIL"}, + "Check this is a S arrangement": {"encodeArngSCheck", `if v == ARNG_S { + return 0, true + } + return 0, false`, "enc_NIL"}, + "Check this is a merging predication": {"encodeMergePredCheck", `if v == PRED_M { + return 0, true + } + return 0, false`, "enc_NIL"}, + "Check this is a zeroing predication": {"encodeZeroPredCheck", `if v == PRED_Z { + return 0, true + } + return 0, false`, "enc_NIL"}, + `For the "Byte and halfword" variant: is the size specifier, +sz <T> +0 B +1 H +bit range mappings: +sz: [22:23) +`: {"encodeSzByteHalfword", `switch v { + case ARNG_B: + return 0, true + case ARNG_H: + return 1 << 22, true + } + return 0, false`, "enc_sz"}, + `For the "Byte, merging" and "Byte, zeroing" variants: is the size specifier, +size <T> +00 RESERVED +01 H +10 S +11 D +bit range mappings: +size: [22:24) +`: {"encodeSizeByteMergeZero", `switch v { + case ARNG_H: + return 1 << 22, true + case ARNG_S: + return 2 << 22, true + case ARNG_D: + return 3 << 22, true + } + return 0, false`, "enc_size"}, + `For the "Halfword, merging" and "Halfword, zeroing" variants: is the size specifier, +size[0] <T> +0 S +1 D +bit range mappings: +size: [22:23) +`: {"encodeSize0HalfwordMergeZero", `switch v { + case ARNG_S: + return 0, true + case ARNG_D: + return 1 << 22, true + } + return 0, false`, "enc_size0"}, + `For the "Word and doubleword" variant: is the size specifier, +sz <T> +0 S +1 D +bit range mappings: +sz: [22:23) +`: {"encodeSzWordDoubleword", `switch v { + case ARNG_S: + return 0, true + case ARNG_D: + return 1 << 22, true + } + return 0, false`, "enc_sz"}, + `Is an arrangement specifier, +size <T> +00 16B +01 8H +10 4S +11 2D +bit range mappings: +size: [22:24) +`: {"encodeSize16B8H4S2D", `switch v { + case ARNG_16B: + return 0, true + case ARNG_8H: + return 1 << 22, true + case ARNG_4S: + return 2 << 22, true + case ARNG_2D: + return 3 << 22, true + } + return 0, false`, "enc_size"}, + `Is an arrangement specifier, +size <T> +00 RESERVED +01 8H +10 4S +11 2D +bit range mappings: +size: [22:24) +`: {"encodeSize8H4S2D", `switch v { + case ARNG_8H: + return 1 << 22, true + case ARNG_4S: + return 2 << 22, true + case ARNG_2D: + return 3 << 22, true + } + return 0, false`, "enc_size"}, + `Is the name of the destination SIMD&FP register, encoded in the "Vd" field. +bit range mappings: +Vd: [0:5) +`: {"encodeVd", `return v, true`, "enc_Vd"}, + `Is the name of the destination scalable predicate register PN8-PN15, with predicate-as-counter encoding, encoded in the "PNd" field. +bit range mappings: +PNd: [0:3) +`: {"encodePNd", `if v >= 24 && v <= 31 { + // PN registers starts from 16. + return v - 24, true + } + return 0, false`, "enc_PNd"}, + `Is the name of the destination scalable predicate register, encoded in the "Pd" field. +bit range mappings: +Pd: [0:4) +`: {"encodePd", `return v, true`, "enc_Pd"}, + `Is the name of the destination scalable vector register, encoded in the "Zd" field. +bit range mappings: +Zd: [0:5) +`: {"encodeZd", `return v, true`, "enc_Zd"}, + `Is the name of the first source and destination scalable predicate register, encoded in the "Pdn" field. +bit range mappings: +Pdn: [0:4) +`: {"encodePdnDest", `return v, true`, "enc_Pdn"}, + `Is the name of the first source and destination scalable vector register, encoded in the "Zdn" field. +bit range mappings: +Zdn: [0:5) +`: {"encodeZdnDest", `return v, true`, "enc_Zdn"}, + `Is the name of the first source scalable predicate register, encoded in the "Pn" field. +bit range mappings: +Pn: [5:9) +`: {"encodePn59", `return v << 5, true`, "enc_Pn"}, + `Is the name of the first source scalable vector register, encoded in the "Zn" field. +bit range mappings: +Zn: [5:10) +`: {"encodeZn510", `return v << 5, true`, "enc_Zn"}, + `Is the name of the governing scalable predicate register P0-P7, encoded in the "Pg" field. +bit range mappings: +Pg: [10:13) +`: {"encodePg1013", `if v <= 7 { + return v << 10, true + } + return 0, false`, "enc_Pg"}, + `Is the name of the governing scalable predicate register, encoded in the "Pg" field. +bit range mappings: +Pg: [10:14) +`: {"encodePg1014", `return v << 10, true`, "enc_Pg"}, + `Is the name of the governing scalable predicate register, encoded in the "Pg" field. +bit range mappings: +Pg: [5:9) +`: {"encodePg59", `return v << 5, true`, "enc_Pg"}, + `Is the name of the second source and destination scalable predicate register, encoded in the "Pdm" field. +bit range mappings: +Pdm: [0:4) +`: {"encodePdmDest", `return v, true`, "enc_Pdm"}, + `Is the name of the second source and destination scalable vector register, encoded in the "Zda" field. +bit range mappings: +Zda: [0:5) +`: {"encodeZdaDest", `return v, true`, "enc_Zda"}, + `Is the name of the second source scalable predicate register, encoded in the "Pm" field. +bit range mappings: +Pm: [16:20) +`: {"encodePm1620", `return v << 16, true`, "enc_Pm"}, + `Is the name of the second source scalable vector register, encoded in the "Zm" field. +bit range mappings: +Zm: [16:21) +`: {"encodeZm1621", `return v << 16, true`, "enc_Zm"}, + `Is the name of the second source scalable vector register, encoded in the "Zm" field. +bit range mappings: +Zm: [5:10) +`: {"encodeZm510", `return v << 5, true`, "enc_Zm"}, + `Is the name of the source and destination scalable predicate register, encoded in the "Pdn" field. +bit range mappings: +Pdn: [0:4) +`: {"encodePdnSrcDst", `return v, true`, "enc_Pdn"}, + `Is the name of the source and destination scalable vector register, encoded in the "Zdn" field. +bit range mappings: +Zdn: [0:5) +`: {"encodeZdnSrcDst", `return v, true`, "enc_Zdn"}, + `Is the name of the source scalable predicate register, encoded in the "Pm" field. +bit range mappings: +Pm: [5:9) +`: {"encodePm59v1", `return v << 5, true`, "enc_Pm"}, + `Is the name of the source scalable predicate register, encoded in the "Pn" field. +bit range mappings: +Pn: [5:9) +`: {"encodePn59v2", `return v << 5, true`, "enc_Pn"}, + `Is the name of the source scalable vector register, encoded in the "Zn" field. +bit range mappings: +Zn: [5:10) +`: {"encodeZn510Src", `return v << 5, true`, "enc_Zn"}, + `Is the name of the third source and destination scalable vector register, encoded in the "Zda" field. +bit range mappings: +Zda: [0:5) +`: {"encodeZda3RdSrcDst", `return v, true`, "enc_Zda"}, + `Is the name of the third source scalable vector register, encoded in the "Za" field. +bit range mappings: +Za: [16:21) +`: {"encodeZa16213Rd", `return v << 16, true`, "enc_Za"}, + `Is the name of the third source scalable vector register, encoded in the "Za" field. +bit range mappings: +Za: [5:10) +`: {"encodeZa5103Rd", `return v << 5, true`, "enc_Za"}, + `Is the name of the third source scalable vector register, encoded in the "Zk" field. +bit range mappings: +Zk: [5:10) +`: {"encodeZk5103Rd", `return v << 5, true`, "enc_Zk"}, + `Is the name of the vector select predicate register P0-P7, encoded in the "Pv" field. +bit range mappings: +Pv: [10:13) +`: {"encodePv1013", `return v << 10, true`, "enc_Pv"}, + `Is the name of the vector select predicate register, encoded in the "Pv" field. +bit range mappings: +Pv: [10:14) +`: {"encodePv1014", `return v << 10, true`, "enc_Pv"}, + `Is the name of the vector select predicate register, encoded in the "Pv" field. +bit range mappings: +Pv: [5:9) +`: {"encodePv59", `return v << 5, true`, "enc_Pv"}, + `Is the predication qualifier, +M <ZM> +0 Z +1 M +bit range mappings: +M: [16:17) +`: {"encodePredQualM1617", `switch v { + case PRED_Z: + return 0, true + case PRED_M: + return 1 << 16, true + } + return 0, false`, "enc_M"}, + `Is the predication qualifier, +M <ZM> +0 Z +1 M +bit range mappings: +M: [4:5) +`: {"encodePredQualM45", `switch v { + case PRED_Z: + return 0, true + case PRED_M: + return 1 << 4, true + } + return 0, false`, "enc_M"}, + `Is the size specifier, +size <T> +00 B +01 H +10 S +11 D +bit range mappings: +size: [22:24) +`: {"encodeSizeBHSD2224", `switch v { + case ARNG_B: + return 0 << 22, true + case ARNG_H: + return 1 << 22, true + case ARNG_S: + return 2 << 22, true + case ARNG_D: + return 3 << 22, true + } + return 0, false`, "enc_size"}, + `Is the size specifier, +size <T> +00 B +01 H +10 S +11 RESERVED +bit range mappings: +size: [22:24) +`: {"encodeSizeBHS2224", `switch v { + case ARNG_B: + return 0 << 22, true + case ARNG_H: + return 1 << 22, true + case ARNG_S: + return 2 << 22, true + } + return 0, false`, "enc_size"}, + `Is the size specifier, +size <T> +00 RESERVED +01 B +10 H +11 S +bit range mappings: +size: [22:24) +`: {"encodeSizeBHS2224Offset1", `switch v { + case ARNG_B: + return 1 << 22, true + case ARNG_H: + return 2 << 22, true + case ARNG_S: + return 3 << 22, true + } + return 0, false`, "enc_size"}, + `Is the size specifier, +size <T> +00 RESERVED +01 H +10 S +11 D +bit range mappings: +size: [13:15) +`: {"encodeSizeHSD1315", `switch v { + case ARNG_H: + return 1 << 13, true + case ARNG_S: + return 2 << 13, true + case ARNG_D: + return 3 << 13, true + } + return 0, false`, "enc_size"}, + `Is the size specifier, +size <T> +00 RESERVED +01 H +10 S +11 D +bit range mappings: +size: [17:19) +`: {"encodeSizeHSD1719", `switch v { + case ARNG_H: + return 1 << 17, true + case ARNG_S: + return 2 << 17, true + case ARNG_D: + return 3 << 17, true + } + return 0, false`, "enc_size"}, + `Is the size specifier, +size <T> +00 RESERVED +01 H +10 S +11 D +bit range mappings: +size: [22:24) +`: {"encodeSizeHSD2224", `switch v { + case ARNG_H: + return 1 << 22, true + case ARNG_S: + return 2 << 22, true + case ARNG_D: + return 3 << 22, true + } + return 0, false`, "enc_size"}, + `Is the size specifier, +size <T> +01 H +10 S +11 D +bit range mappings: +size: [22:24) +`: {"encodeSizeHSD2224No00", `switch v { + case ARNG_H: + return 1 << 22, true + case ARNG_S: + return 2 << 22, true + case ARNG_D: + return 3 << 22, true + } + return 0, false`, "enc_size"}, + `Is the size specifier, +size <T> +01 H +1x D +bit range mappings: +size: [22:24) +`: {"encodeSizeHD2224", `switch v { + case ARNG_H: + return 1 << 22, true + case ARNG_D: + return 3 << 22, true + } + return 0, false`, "enc_size"}, + `Is the size specifier, +size <Tb> +00 B +01 H +10 S +11 D +bit range mappings: +size: [22:24) +`: {"encodeSizeTbBHSD2224", `switch v { + case ARNG_B: + return 0 << 22, true + case ARNG_H: + return 1 << 22, true + case ARNG_S: + return 2 << 22, true + case ARNG_D: + return 3 << 22, true + } + return 0, false`, "enc_size"}, + `Is the size specifier, +size <Tb> +00 RESERVED +01 B +10 H +11 S +bit range mappings: +size: [22:24) +`: {"encodeSizeTbBHS2224", `switch v { + case ARNG_B: + return 1 << 22, true + case ARNG_H: + return 2 << 22, true + case ARNG_S: + return 3 << 22, true + } + return 0, false`, "enc_size"}, + `Is the size specifier, +size <Tb> +00 RESERVED +01 H +10 S +11 D +bit range mappings: +size: [22:24) +`: {"encodeSizeTbHSD2224Offset1", `switch v { + case ARNG_H: + return 1 << 22, true + case ARNG_S: + return 2 << 22, true + case ARNG_D: + return 3 << 22, true + } + return 0, false`, "enc_size"}, + `Is the size specifier, +size <Tb> +01 B +1x S +bit range mappings: +size: [22:24) +`: {"encodeSizeTbBS2224", `switch v { + case ARNG_B: + return 1 << 22, true + case ARNG_S: + return 3 << 22, true + } + return 0, false`, "enc_size"}, + `Is the size specifier, +size[0] <T> +0 B +1 H +bit range mappings: +size: [22:23) +`: {"encodeSize0BH2223", `switch v { + case ARNG_B: + return 0 << 22, true + case ARNG_H: + return 1 << 22, true + } + return 0, false`, "enc_size0"}, + `Is the size specifier, +size[0] <T> +0 S +1 D +bit range mappings: +size: [22:23) +`: {"encodeSize0SD2223", `switch v { + case ARNG_S: + return 0 << 22, true + case ARNG_D: + return 1 << 22, true + } + return 0, false`, "enc_size0"}, + `Is the size specifier, +size[0] <Tb> +0 B +1 H +bit range mappings: +size: [22:23) +`: {"encodeSize0TbBH2223", `switch v { + case ARNG_B: + return 0 << 22, true + case ARNG_H: + return 1 << 22, true + } + return 0, false`, "enc_size0"}, + `Is the size specifier, +sz <T> +0 S +1 D +bit range mappings: +sz: [14:15) +`: {"encodeSzSD1415", `switch v { + case ARNG_S: + return 0 << 14, true + case ARNG_D: + return 1 << 14, true + } + return 0, false`, "enc_sz"}, + `Is the size specifier, +sz <T> +0 S +1 D +bit range mappings: +sz: [17:18) +`: {"encodeSzSD1718", `switch v { + case ARNG_S: + return 0 << 17, true + case ARNG_D: + return 1 << 17, true + } + return 0, false`, "enc_sz"}, + `Is the size specifier, +sz <T> +0 S +1 D +bit range mappings: +sz: [22:23) +`: {"encodeSzSD2223", `switch v { + case ARNG_S: + return 0 << 22, true + case ARNG_D: + return 1 << 22, true + } + return 0, false`, "enc_sz"}, + `Is the size specifier, +tszh tszl <T> +0 00 RESERVED +0 01 B +0 10 H +0 11 RESERVED +1 00 S +1 01 RESERVED +1 1x RESERVED +bit range mappings: +tszh: [22:23) +tszl: [19:21) +`: {"encodeTszhTszlBHS", `switch v { + case ARNG_B: + return 0<<22 | 1<<19, true + case ARNG_H: + return 0<<22 | 2<<19, true + case ARNG_S: + return 1 << 22, true + } + return 0, false`, "enc_tszh_tszl"}, + `Is the size specifier, +tszh tszl <Tb> +0 00 RESERVED +0 01 H +0 10 S +0 11 RESERVED +1 00 D +1 01 RESERVED +1 1x RESERVED +bit range mappings: +tszh: [22:23) +tszl: [19:21) +`: {"encodeTszhTszlTbHSD", `switch v { + case ARNG_H: + return 0<<22 | 1<<19, true + case ARNG_S: + return 0<<22 | 2<<19, true + case ARNG_D: + return 1 << 22, true + } + return 0, false`, "enc_tszh_tszl"}, + `No-op check, returns true`: {"encodeNoop", `return 0, true`, "enc_NIL"}, +}
diff --git a/arm64/instgen/generator.go b/arm64/instgen/generator.go new file mode 100644 index 0000000..a39ff58 --- /dev/null +++ b/arm64/instgen/generator.go
@@ -0,0 +1,959 @@ +// 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" + "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 +} + +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 +} + +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 + +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 allowedOperandTypes = map[string]bool{ + "AC_ARNG": true, + "AC_PREG": true, + "AC_PREGZ": true, + "AC_PREGZM": true, + "AC_ZREG": true, +} + +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 +} + +// 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 _, encoding := range iclass.Encodings { + if !encoding.Parsed { + continue + } + + if encoding.Alias { + // Alias encodings are not fully specified in their own section, + // skip them. + continue + } + + // Filtering logic: + // 1. If 0 operands (len <= 1), keep with warning. + // 2. If > 0 operands, ALL must be in allowedOperandTypes. + 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 !allowedOperandTypes[op.Typ] { + 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) + } + instData := instData{ + Asm: encoding.GoOp[1:], + GoOp: encoding.GoOp, + FixedBits: fmt.Sprintf("%#x", encoding.Binary), + } + + 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 { + v := sym + v = strings.ReplaceAll(v, " :: ", "_") + v = strings.ReplaceAll(v, "(", "") + v = strings.ReplaceAll(v, ")", "") + v = strings.ReplaceAll(v, "[", "") + v = strings.ReplaceAll(v, "]", "") + enc = fmt.Sprintf("enc_%s", v) + } + } + 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 { + v0 := v + v = strings.ReplaceAll(v, " :: ", "_") + v = strings.ReplaceAll(v, "(", "") + v = strings.ReplaceAll(v, ")", "") + v = strings.ReplaceAll(v, "[", "") + v = strings.ReplaceAll(v, "]", "") + name := fmt.Sprintf("enc_%s", v) + encodedInMap[v0] = name + // Only add to output if used + if neededEncodedIn[v0] { + constants = append(constants, constantData{ + ConstName: name, + BinName: v0, + }) + } + } + 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 = fmt.Sprintf(defaultEncodingTmpl, sym) + 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) + 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) + + 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) + } + + 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 { + ugly2better[u] = fmt.Sprintf("%s__%d", operandsToName(x), j+1) + } + } else { + // if only one, do not add a __N suffix. + ugly2better[uglies[0]] = operandsToName(x) + } + } + + // 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. + sort.Slice(data.Insts, func(i, j int) bool { + return data.Insts[i].GoOp < data.Insts[j].GoOp + }) + 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 { + sort.Slice(e2eTestData, func(i, j int) bool { + return e2eTestData[i].GoOp < e2eTestData[j].GoOp + }) + sort.Slice(e2eErrorData, func(i, j int) bool { + return e2eErrorData[i].GoOp < e2eErrorData[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", + + "Pd": "P", + "Pdn": "P", + "Pdm": "P", + "Pn": "P", + "Pm": "P", + "Pg": "P", + "PNd": "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] +} + +// 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 + var gnuErr error + var errCase *e2eData + var validCase *e2eData + for range 100 { + goAsmOps := make([]string, 0) + gnuAsmOps := make([]string, 0) + // Useful for pruning, e.g. + // 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{} + 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 !allowedOperandTypes[op.Typ] { + log.Fatalf("Unsupported operand type in constructInstance: %s, enc: %s", op.Typ, enc.String()) + } + // Right now allowed types are all pure regs. + // TODO: when this function need to handle more operand types, + // guard these logic with the correct conditions. + 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], "<"), ">") + } + reg := regMap[regName] + 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)) + asmOp := fmt.Sprintf("Z%d.%s", regIdx, sveArr[arrIdx]) + gnuAsmOps = append([]string{asmOp}, gnuAsmOps...) + // go operand order is gnu reversed + goAsmOps = append(goAsmOps, asmOp) + case "P", "PN": + regIdx := cachedOrNew(regCache, regName, 15) + if isPred { + arrIdx := cachedOrNew(arrCache, arrName, len(svePred)) + // Go use "." for predication, GNU use "/" + gnuAsmOps = append([]string{fmt.Sprintf("%s%d/%s", reg, regIdx, svePred[arrIdx])}, gnuAsmOps...) + goAsmOps = append(goAsmOps, fmt.Sprintf("%s%d.%s", reg, regIdx, svePred[arrIdx])) + } else { + arrIdx := cachedOrNew(arrCache, arrName, len(sveArr)) + asmOp := fmt.Sprintf("%s%d.%s", reg, regIdx, sveArr[arrIdx]) + gnuAsmOps = append([]string{asmOp}, gnuAsmOps...) + goAsmOps = append(goAsmOps, asmOp) + } + case "V": + regIdx := cachedOrNew(regCache, regName, 32) + arrIdx := cachedOrNew(arrCache, arrName, len(neonArrGNU)) + gnuAsmOps = append([]string{fmt.Sprintf("V%d.%s", regIdx, neonArrGNU[arrIdx])}, gnuAsmOps...) + goAsmOps = append(goAsmOps, fmt.Sprintf("V%d.%s", regIdx, neonArrGo[arrIdx])) + } + } else { + // Fixed arrangement or predications + if arrName != "" { + arrName = "." + arrName + } + switch reg { + case "Z": + regIdx := cachedOrNew(regCache, regName, 32) + asmOp := fmt.Sprintf("Z%d%s", regIdx, arrName) + gnuAsmOps = append([]string{asmOp}, gnuAsmOps...) + goAsmOps = append(goAsmOps, asmOp) + case "P", "PN": + regIdx := cachedOrNew(regCache, regName, 15) + if isPred { + var arrNameGNU string + if arrName != "" { + arrNameGNU = "/" + arrName[1:] + } + gnuAsmOps = append([]string{fmt.Sprintf("%s%d%s", reg, regIdx, arrNameGNU)}, gnuAsmOps...) + goAsmOps = append(goAsmOps, fmt.Sprintf("%s%d%s", reg, regIdx, arrName)) + } else { + asmOp := fmt.Sprintf("%s%d%s", reg, regIdx, arrName) + gnuAsmOps = append([]string{asmOp}, gnuAsmOps...) + goAsmOps = append(goAsmOps, asmOp) + } + case "V": + log.Fatalf("Unexpected V with fixed arrangement: %s", opName) + } + } + } + // 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\"", + } + } + continue + } + if validCase == nil { + validCase = &e2eData{ + GoOp: enc.GoOp[1:], + Asm: fmt.Sprintf("%-50s", goAsmStr), + Binary: "// " + reverseHex(hex), + } + } + 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)} + 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 + } + + return validCase, errCase + } + if name == "ADDQP" || name == "ADDSUBP" || name == "SCVTFLT" || name == "UCVTFLT" { + // 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)} + 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" { + // 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)} + 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:]} +}
diff --git a/arm64/instgen/main.go b/arm64/instgen/main.go index fbdc14a..2db2126 100644 --- a/arm64/instgen/main.go +++ b/arm64/instgen/main.go
@@ -22,7 +22,10 @@ // // The program parses and processes all the XML files, and generates four .go files: // inst_gen.go, elem_gen.go, goops_gen.go and arm64ops_gen.go to the output directory. -// If -o option is not specified, the files will be written to the current directory. +// The output directory is assumed to be GOROOT, these four files will be generated to +// <outputDir>/src/cmd/internal/obj/arm64/. +// +// If -o option is not specified, no output files will be generated. // // Since the format of the ARM64 instruction specification document may update, // this parser may not work for some versions of the XML document. @@ -47,6 +50,7 @@ var input = flag.String("i", "", "the input directory of the xml files, this is an optional argument") var output = flag.String("o", "", "the output directory of the generated files, this is an optional argument") +var genE2E = flag.Bool("e2e", false, "generate end-to-end test data") var url = flag.String("url", xmlspec.ExpectedURL, "the url of the xml files") var version = flag.String("version", xmlspec.ExpectedVersion, "the version of the xml files") @@ -76,8 +80,14 @@ // Parse each xml file to insts. insts := xmlspec.ParseXMLFiles(xmlDir) xmlspec.ProcessXMLFiles(insts) + if *output != "" { + Generate(insts, *output, *genE2E) + } errCnt := 0 for _, inst := range insts { + if inst == nil { + continue + } if inst.ParseError != "" { errCnt++ log.Printf("error: %s", inst.ParseError)
diff --git a/arm64/instgen/xmlspec/inst.go b/arm64/instgen/xmlspec/inst.go deleted file mode 100644 index b80532c..0000000 --- a/arm64/instgen/xmlspec/inst.go +++ /dev/null
@@ -1,429 +0,0 @@ -// 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 xmlspec - -import ( - "archive/tar" - "compress/gzip" - "encoding/xml" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "strings" -) - -// The unexported fields are filled by the parser. Example: -// For instruction ADD predicated -// https://developer.arm.com/documentation/111108/2025-12/SVE-Instructions/ADD--vectors--predicated---Add--predicated--?lang=en) -// - parsed data in RegDiagram -// --- Parsed Data --- -// fixedBin: 0x4000000 -// mask: 0xff3fe000 -// varBin: -// size: [22, 24] -// Pg: [10, 13] -// Zm: [5, 10] -// Zdn: [0, 5] -// parsed: true -// --- Parsed Data --- -// -// - parsed data in Encoding -// --- Parsed Data --- -// binary: 0x4000000 -// mask: 0xff3fe000 -// asm: ADD <Zdn>.<T>, <Pg>/M, <Zdn>.<T>, <Zm>.<T> -// goOp: AZADD -// arm64Op: A64ADD -// class: C_SVE -// invalid: false -// alias: false -// prefix: AZ -// operands: -// operand{ -// name: ADD -// typ: -// elems: -// } -// operand{ -// name: <Zm>.<T> -// typ: AC_ARNG -// elems: -// element{ -// encodedIn: Zm -// textExp: Is the name of the second source scalable vector register, encoded in the "Zm" field. -// symbol: <Zm> -// } -// element{ -// encodedIn: size -// textExp: size <T> -// 00 B -// 01 H -// 10 S -// 11 D -// symbol: <T> -// } -// } -// operand{ -// name: <Zdn>.<T> -// typ: AC_ARNG -// elems: -// element{ -// encodedIn: Zdn -// textExp: Is the name of the first source and destination scalable vector register, encoded in the "Zdn" field. -// symbol: <Zdn> -// } -// element{ -// encodedIn: size -// textExp: size <T> -// 00 B -// 01 H -// 10 S -// 11 D -// symbol: <T> -// } -// } -// operand{ -// name: <Pg>/M -// typ: AC_PREGM -// elems: -// element{ -// encodedIn: Pg -// textExp: Is the name of the governing scalable predicate register P0-P7, encoded in the "Pg" field. -// symbol: <Pg> -// } -// } -// operand{ -// name: <Zdn>.<T> -// typ: AC_ARNG -// elems: -// element{ -// encodedIn: Zdn -// textExp: Is the name of the first source and destination scalable vector register, encoded in the "Zdn" field. -// symbol: <Zdn> -// } -// element{ -// encodedIn: size -// textExp: size <T> -// 00 B -// 01 H -// 10 S -// 11 D -// symbol: <T> -// } -// } -// parsed: true -// --- Parsed Data --- - -// DocVar represents a <docvar> element, storing document metadata like "isa" or "mnemonic". -type DocVar struct { - Key string `xml:"key,attr"` - Value string `xml:"value,attr"` -} - -// ArchVariant represents an <arch_variant> element, specifying the required architectural features. -type ArchVariant struct { - Name string `xml:"name,attr"` - Feature string `xml:"feature,attr"` -} - -// C represents a <c> element within a register diagram box, specifying bit content or a named field. -type C struct { - Value string `xml:",chardata"` - ColSpan string `xml:"colspan,attr"` -} - -// Box represents a <box> element in a register diagram, describing a specific bitfield. -type Box struct { - HiBit string `xml:"hibit,attr"` - Width string `xml:"width,attr"` - Name string `xml:"name,attr"` - UseName string `xml:"usename,attr"` - Settings string `xml:"settings,attr"` - PsBits string `xml:"psbits,attr"` - Cs []C `xml:"c"` -} - -// bitRange represents a range of bits from lo (inclusive) to hi (exclusive) -type bitRange struct { - lo, hi int -} - -// RegDiagram represents a <regdiagram> element, detailing the bit layout of an encoding. -type RegDiagram struct { - Boxes []Box `xml:"box"` - // The fields below are the parsed results of the XML files. - fixedBin uint32 // instruction encoding binary - mask uint32 // instruction decoding mask, it specifies the fixed bit positions of the instruction encoding - varBin map[string]bitRange // named bit ranges, key is the name - parsed bool // whether this regdiagram has been parsed -} - -// TextA represents a <text> or <a> element within an assembly template. -type TextA struct { - Value string `xml:",chardata"` - Link string `xml:"link,attr"` - Hover string `xml:"hover,attr"` // contains possible values -} - -// AsmTemplate represents an <asmtemplate> element, defining the syntax of the instruction. -type AsmTemplate struct { - // <Asmtemplate> contains two kinds of sub-elements, <text> and <a>. - // <text> contains string literals, <a> contains a symbol and - // two attributes: link and hover. The order of <text> and <a> matters, - // so we save both into the following structure to preserve their order. - TextA []TextA `xml:",any"` -} - -type element struct { - encodedIn string // the name of the binary box this element is encoded in. - textExp string // text explanation extracted - textExpWithRanges string // text explanation with named bit ranges mapping attached. - symbol string // asm template - // Fields below are all parsed metadata for the symbol. - // Useful for deduplication at instruction matching. - // When they are default value, they should have no effect on the instruction matching. - fixedArng string // if non empty, this element has a fixed arrangement - fixedLSL string // if non empty, this element has a fixed LSL - fixedSXTW bool // if true, this element has a fixed SXTW - fixedUXTW bool // if true, this element has a fixed UXTW - fixedModAmt string // if non empty, <mod> comes with a fixed <amount> - fixedScalarWidth int // if non zero, this element has a fixed scalar width - hasMod bool // if true, this element is a <mod> - isP bool // if true, this element is a scalable predicate register - isZ bool // if true, this element is a scalable vector register -} - -type operand struct { - name string // asm template - typ string - elems []element - constraints []string -} - -type class int - -const ( - C_NONE class = iota - C_SVE - C_SVE2 -) - -// Encoding represents an <encoding> element for a specific instruction variant. -type Encoding struct { - Name string `xml:"name,attr"` - Label string `xml:"label,attr"` - DocVars []DocVar `xml:"docvars>docvar"` - Boxes []Box `xml:"box"` - AsmTemplate AsmTemplate `xml:"asmtemplate"` - // The fields below are the parsed results of the XML files. - binary uint32 // more specific instruction encoding than regdiagram.binary - mask uint32 - asm string // asm template - goOp string // opcode in Go - arm64Op string // arm64 opcode - operands []operand - class class // instruction class - invalid bool // indicate if this is a valid encoding that need to print - alias bool // whether it is an alias - prefix string // prefix to GoOp - parsed bool // whether this encoding has been parsed -} - -// Iclass represents an <iclass> element, grouping instruction encodings that share a register diagram. -type Iclass struct { - Name string `xml:"name,attr"` - OneOf string `xml:"oneof,attr"` - ID string `xml:"id,attr"` - NoEncodings string `xml:"no_encodings,attr"` - ISA string `xml:"isa,attr"` - DocVars []DocVar `xml:"docvars>docvar"` - ArchVariant ArchVariant `xml:"arch_variants>arch_variant"` - RegDiagram RegDiagram `xml:"regdiagram"` - Encodings []Encoding `xml:"encoding"` -} - -// Classes represents a <classes> element, grouping instruction classes. -type Classes struct { - Iclass []Iclass `xml:"iclass"` -} - -// Symbol represents a <symbol> element, linking an explanation to an assembly sequence symbol. -type Symbol struct { - Value string `xml:",chardata"` - Link string `xml:"link,attr"` -} - -// Account represents an <account> element, providing a textual explanation for a symbol. -type Account struct { - Encodedin string `xml:"encodedin,attr"` - DocVars []DocVar `xml:"docvars>docvar"` - Intro string `xml:"intro>para"` -} - -// Entry represents an <entry> element, defining a single cell in a definition table. -type Entry struct { - Value string `xml:",chardata"` - Class string `xml:"class,attr"` -} - -// Row represents a <row> element within a table header or body. -type Row struct { - Entries []Entry `xml:"entry"` -} - -// THead represents a <thead> element, containing the table header. -type THead struct { - Row Row `xml:"row"` -} - -// TBody represents a <tbody> element, containing the table body. -type TBody struct { - Row []Row `xml:"row"` -} - -// TGroup represents a <tgroup> element, defining the column and row groups of a table. -type TGroup struct { - Cols string `xml:"cols,attr"` - THead THead `xml:"thead"` - TBody TBody `xml:"tbody"` -} - -// Table represents a <table> element used to explain symbol encodings. -type Table struct { - Class string `xml:"class,attr"` - TGroup TGroup `xml:"tgroup"` -} - -// Definition represents a <definition> element, usually containing a table to define symbol values. -type Definition struct { - Encodedin string `xml:"encodedin,attr"` - Intro string `xml:"intro"` - Table Table `xml:"table"` -} - -// Explanation represents an <explanation> element for a symbol used in the assembly template. -type Explanation struct { - Symbol Symbol `xml:"symbol"` - Account Account `xml:"account"` - Definition Definition `xml:"definition"` -} - -// Explanations represents an <explanations> element, grouping symbol explanations. -type Explanations struct { - Scope string `xml:"scope,attr"` - Explanations []Explanation `xml:"explanation"` -} - -// Instruction represents the root <instructionsection> element of an instruction XML specification. -type Instruction struct { - XMLName xml.Name `xml:"instructionsection"` - Title string `xml:"title,attr"` - Type string `xml:"type,attr"` - DocVars []DocVar `xml:"docvars>docvar"` - Classes Classes `xml:"classes"` - Explanations Explanations `xml:"explanations"` - ParseError string - // The file that this instruction is from, used for error reporting. - file string -} - -// GetArm64XMLSpec downloads the ARM64 XML spec from the given URL to a temporary directory. -// It returns the path to directory containing all instruction XML files. -// If anything goes wrong, it will return an error. -func GetArm64XMLSpec(tmpDir string, url string, version string) (string, error) { - if err := downloadArm64XMLSpec(tmpDir, url); err != nil { - return "", fmt.Errorf("downloadArm64XMLSpec failed: %v", err) - } - - // The tarball extracts to a directory like "ISA_A64_xml_A_profile-2025-12". - // We need to find it. - entries, err := os.ReadDir(tmpDir) - if err != nil { - return "", fmt.Errorf("os.ReadDir failed: %v", err) - } - - var xmlDir string - for _, e := range entries { - if e.IsDir() && strings.HasPrefix(e.Name(), version) { - xmlDir = filepath.Join(tmpDir, e.Name()) - break - } - } - - if xmlDir == "" { - return "", fmt.Errorf("could not find extracted XML directory in %s", tmpDir) - } - return xmlDir, nil -} - -// downloadArm64XMLSpec downloads the ARM64 XML spec from the given URL to the given directory. -func downloadArm64XMLSpec(dir string, url string) error { - resp, err := http.Get(url) - if err != nil { - return fmt.Errorf("fetching ARM64 XML spec from %s failed: %v", url, err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("fetching ARM64 XML spec from %s returned status: %s", url, resp.Status) - } - - if err := extractTarGz(resp.Body, dir); err != nil { - return err - } - return nil -} - -// extractTarGz extracts the tar.gz file to the given directory. -func extractTarGz(r io.Reader, dir string) error { - gzr, err := gzip.NewReader(r) - if err != nil { - return err - } - defer gzr.Close() - - tr := tar.NewReader(gzr) - - // Iterate over the entries in the tarball. - for { - header, err := tr.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - - target := filepath.Join(dir, header.Name) - - switch header.Typeflag { - // directories - case tar.TypeDir: - if err := os.MkdirAll(target, 0755); err != nil { - return err - } - // regular files - case tar.TypeReg: - if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { - return err - } - f, err := os.Create(target) - if err != nil { - return err - } - if _, err := io.Copy(f, tr); err != nil { - f.Close() - return err - } - f.Close() - } - } - return nil -} - -const ExpectedURL = "https://developer.arm.com/-/cdn-downloads/permalink/Exploration-Tools-A64-ISA/ISA_A64/ISA_A64_xml_A_profile-2025-12.tar.gz" -const ExpectedVersion = "ISA_A64_xml_A_profile-2025-12"
diff --git a/arm64/instgen/xmlspec/parsed.go b/arm64/instgen/xmlspec/parsed.go new file mode 100644 index 0000000..4a10ba6 --- /dev/null +++ b/arm64/instgen/xmlspec/parsed.go
@@ -0,0 +1,182 @@ +// 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 xmlspec + +// This file contains the parsed data type. + +// The unexported fields are filled by the parser. Example: +// For instruction ADD predicated +// https://developer.arm.com/documentation/111108/2025-12/SVE-Instructions/ADD--vectors--predicated---Add--predicated--?lang=en) +// - parsed data in RegDiagram +// --- Parsed Data --- +// fixedBin: 0x4000000 +// mask: 0xff3fe000 +// varBin: +// size: [22, 24] +// Pg: [10, 13] +// Zm: [5, 10] +// Zdn: [0, 5] +// parsed: true +// --- Parsed Data --- +// +// - parsed data in Encoding +// --- Parsed Data --- +// binary: 0x4000000 +// mask: 0xff3fe000 +// asm: ADD <Zdn>.<T>, <Pg>/M, <Zdn>.<T>, <Zm>.<T> +// goOp: AZADD +// arm64Op: A64ADD +// class: C_SVE +// invalid: false +// alias: false +// prefix: AZ +// operands: +// operand{ +// name: ADD +// typ: +// elems: +// } +// operand{ +// name: <Zm>.<T> +// typ: AC_ARNG +// elems: +// element{ +// encodedIn: Zm +// textExp: Is the name of the second source scalable vector register, encoded in the "Zm" field. +// symbol: <Zm> +// } +// element{ +// encodedIn: size +// textExp: size <T> +// 00 B +// 01 H +// 10 S +// 11 D +// symbol: <T> +// } +// } +// operand{ +// name: <Zdn>.<T> +// typ: AC_ARNG +// elems: +// element{ +// encodedIn: Zdn +// textExp: Is the name of the first source and destination scalable vector register, encoded in the "Zdn" field. +// symbol: <Zdn> +// } +// element{ +// encodedIn: size +// textExp: size <T> +// 00 B +// 01 H +// 10 S +// 11 D +// symbol: <T> +// } +// } +// operand{ +// name: <Pg>/M +// typ: AC_PREGM +// elems: +// element{ +// encodedIn: Pg +// textExp: Is the name of the governing scalable predicate register P0-P7, encoded in the "Pg" field. +// symbol: <Pg> +// } +// } +// operand{ +// name: <Zdn>.<T> +// typ: AC_ARNG +// elems: +// element{ +// encodedIn: Zdn +// textExp: Is the name of the first source and destination scalable vector register, encoded in the "Zdn" field. +// symbol: <Zdn> +// } +// element{ +// encodedIn: size +// textExp: size <T> +// 00 B +// 01 H +// 10 S +// 11 D +// symbol: <T> +// } +// } +// parsed: true +// --- Parsed Data --- + +// InstructionParsed is the parsed Instruction, with additional fields for parsing status. +type InstructionParsed struct { + Instruction + ParseError string + // The file that this instruction is from, used for error reporting. + file string +} + +// bitRange represents a range of bits from lo (inclusive) to hi (exclusive) +type bitRange struct { + lo, hi int +} + +// EncodingParsed is the parsed Encoding. +type EncodingParsed struct { + Encoding + Binary uint32 // more specific instruction encoding than regdiagram.fixedBin + GoOp string // opcode in Go + Operands []Operand // The operands of the instruction + Asm string // asm template + Alias bool // whether it is an alias + Parsed bool // whether this encoding has been parsed + mask uint32 + arm64Op string // arm64 opcode + class class // instruction class + invalid bool // indicate if this is a valid encoding that need to print + prefix string // prefix to GoOp +} + +// RegDiagramParsed is the parsed RegDiagram. +type RegDiagramParsed struct { + RegDiagram + Parsed bool // whether this regdiagram has been parsed + fixedBin uint32 // instruction encoding binary + mask uint32 // instruction decoding mask, it specifies the fixed bit positions of the instruction encoding + varBin map[string]bitRange // named bit ranges, key is the name +} + +// Operand is the parsed operand of the instruction. +type Operand struct { + Name string // asm template + Typ string // The operand type + Elems []Element + constraints []string +} + +// Element represents a parsed element of an operand. +type Element struct { + TextExpWithRanges string // text explanation with named bit ranges mapping attached. + encodedIn string // the name of the binary box this element is encoded in. + textExp string // text explanation extracted + symbol string // asm template + // Fields below are useful for deduplication at instruction matching. + // When they are default value, they should have no effect on the instruction matching. + fixedArng string // if non empty, this element has a fixed arrangement + fixedLSL string // if non empty, this element has a fixed LSL + fixedSXTW bool // if true, this element has a fixed SXTW + fixedUXTW bool // if true, this element has a fixed UXTW + fixedModAmt string // if non empty, <mod> comes with a fixed <amount> + fixedScalarWidth int // if non zero, this element has a fixed scalar width + hasMod bool // if true, this element is a <mod> + isP bool // if true, this element is a scalable predicate register + isZ bool // if true, this element is a scalable vector register +} + +type class int + +const ( + C_NONE class = iota + C_SVE + C_SVE2 +)
diff --git a/arm64/instgen/xmlspec/parser.go b/arm64/instgen/xmlspec/parser.go index ffb5d7a..7bdae99 100644 --- a/arm64/instgen/xmlspec/parser.go +++ b/arm64/instgen/xmlspec/parser.go
@@ -88,10 +88,10 @@ {regexp.MustCompile(`^<P[a-z]{1}>$`), "AC_PREG"}, // AC_PREG: Predicate-as-counter registers (PN). {regexp.MustCompile(`^<PN[a-z]{1}>$`), "AC_PREG"}, - // AC_PREGM: Predicate registers with merging predication (/M). - {regexp.MustCompile(`^<P[N]?[a-z]{1}>\/M$`), "AC_PREGM"}, - // AC_PREGZ: Predicate registers with zeroing predication (/Z). - {regexp.MustCompile(`^<P[N]?[a-z]{1}>\/(Z|<ZM>)$`), "AC_PREGZ"}, + // AC_PREGZM: Predicate registers with merging predication (/M). + {regexp.MustCompile(`^<P[N]?[a-z]{1}>\/M$`), "AC_PREGZM"}, + // AC_PREGZM: Predicate registers with zeroing predication (/Z). + {regexp.MustCompile(`^<P[N]?[a-z]{1}>\/(Z|<ZM>)$`), "AC_PREGZM"}, // AC_REGIDX: Registers with immediate index. {regexp.MustCompile(`^(<[PZ][N]?[a-z]{1}>|ZT0)\[<[a-z]+>\]$`), "AC_REGIDX"}, // AC_ZREG: Scalable vector registers (Z). @@ -148,7 +148,7 @@ // struct fields, and this process is not thread-safe if multiple goroutines // attempt to unmarshal into the same type for the first time concurrently. func warmUpCache() { - var inst Instruction + var inst InstructionParsed // Unmarshal a more complete XML to warm up the cache for nested types. // This ensures that reflection data for all referenced types is initialized // sequentially before parallel workers start. @@ -206,7 +206,7 @@ warmUpCache() } -func ParseXMLFiles(dir string) []*Instruction { +func ParseXMLFiles(dir string) []*InstructionParsed { log.Println("Start parsing the xml files") files, err := os.ReadDir(dir) if err != nil { @@ -214,7 +214,7 @@ } var wg sync.WaitGroup - insts := make([]*Instruction, len(files)) + insts := make([]*InstructionParsed, len(files)) for i, file := range files { fileName := file.Name() @@ -237,7 +237,7 @@ } // parse parses an xml file and returns the instruction. -func parse(f string) *Instruction { +func parse(f string) *InstructionParsed { xmlFile, err := os.Open(f) if err != nil { log.Fatalf("Open file %s failed: %v\n", f, err) @@ -248,7 +248,7 @@ log.Fatalf("io.ReadAll %s failed: %v\n", f, err) } - var inst = new(Instruction) + var inst = new(InstructionParsed) inst.file = f if err = xml.Unmarshal(byteValue, inst); err != nil { // Ignore non-instruction files. @@ -264,7 +264,7 @@ return inst } -func (inst *Instruction) setBinary(code, bitVal uint32, value string) uint32 { +func (inst *InstructionParsed) setBinary(code, bitVal uint32, value string) uint32 { switch value { case "0", "(0)": code &^= bitVal @@ -278,7 +278,7 @@ return code } -func (inst *Instruction) setMask(code, bitVal uint32, value string) uint32 { +func (inst *InstructionParsed) setMask(code, bitVal uint32, value string) uint32 { switch value { // See the comment of [Regdiagram.mask] case "0", "1", "(0)", "(1)": @@ -291,7 +291,7 @@ return code } -func (inst *Instruction) boxEncoding(b Box, callBack func(uint32, uint32, string) uint32) uint32 { +func (inst *InstructionParsed) boxEncoding(b Box, callBack func(uint32, uint32, string) uint32) uint32 { code := uint32(0) hi, err := strconv.Atoi(b.HiBit) if err != nil { @@ -307,7 +307,7 @@ return code } -func (inst *Instruction) supported() bool { +func (inst *InstructionParsed) supported() bool { foundSVE := false for _, doc := range inst.DocVars { if doc.Key == "instr-class" { @@ -321,7 +321,7 @@ // extractBinary extracts the known bits of instruction encoding in regdiagram, // and assign the binary to inst.regdiagram.binary. -func (inst *Instruction) extractBinary() { +func (inst *InstructionParsed) extractBinary() { if !inst.supported() { return } @@ -363,17 +363,33 @@ } regDiagram.fixedBin = bin regDiagram.mask = mask - regDiagram.parsed = true + regDiagram.Parsed = true if inst.Title == "URSQRTE -- A64" || inst.Title == "URECPE -- A64" { // Special case, its "size" box is actually not specified in the assembler symbol section. // By reading the decoding ASL we know that this "size" box should be 0b10... regDiagram.fixedBin |= uint32(1 << 23) } + if len(inst.Classes.Iclass[i].PsSection) == 1 { + squashedPs := inst.Classes.Iclass[i].PsSection[0].Ps[0].PSText + if strings.Contains(strings.Join(squashedPs, "\n"), "if size IN {'0x'} then EndOfDecode") { + // Very ugly encoding specification in the decoding ASL. We have to set + // the high bit of the size box to 1. + // Example instruction is "Unsigned divide (predicated)": + // UDIV <Zdn>.<T>, <Pg>/M, <Zdn>.<T>, <Zm>.<T> + if _, ok := regDiagram.varBin["size"]; !ok { + log.Fatalf("size box not found in %s", inst.file) + } + if regDiagram.varBin["size"].hi != 24 || regDiagram.varBin["size"].lo != 22 { + log.Fatalf("unexpecetd size box in %s", inst.file) + } + regDiagram.fixedBin |= uint32(1 << 23) + } + } } } // processEncoding handles each encoding element of a inst. -func (inst *Instruction) processEncodings() { +func (inst *InstructionParsed) processEncodings() { if !inst.supported() { return } @@ -387,14 +403,14 @@ continue } // Set alias - enc.alias = inst.Type == "alias" + enc.Alias = inst.Type == "alias" // Refine the known bits and mask of the binary. bin, mask := iclass.RegDiagram.fixedBin, iclass.RegDiagram.mask for _, box := range enc.Boxes { bin |= inst.boxEncoding(box, inst.setBinary) mask |= inst.boxEncoding(box, inst.setMask) } - enc.binary = bin + enc.Binary = bin enc.mask = mask inst.parseOperands(enc) @@ -409,17 +425,17 @@ // Special case, its "size" box is not specified in the assembler symbol // section for the [B] and [W] variants, which for [B] it's 0b00 (no-op) // and for [W] it's 0b11. - imnemonic := enc.operands[0].name + imnemonic := enc.Operands[0].Name if imnemonic[len(imnemonic)-1] == 'W' { - enc.binary |= uint32(0b11 << 22) + enc.Binary |= uint32(0b11 << 22) } } - enc.parsed = true + enc.Parsed = true } } } -func (inst *Instruction) findExplanation(link string) *Explanation { +func (inst *InstructionParsed) findExplanation(link string) *Explanation { for _, exp := range inst.Explanations.Explanations { if exp.Symbol.Link == link { return &exp @@ -436,9 +452,9 @@ // and the instruction that contains this fixed symbol. // This data is just used for debugging. var allFixedSymbolsLock sync.Mutex -var allFixedSymbols = map[string]map[string]*Instruction{} +var allFixedSymbols = map[string]map[string]*InstructionParsed{} -func (inst *Instruction) parseOperands(enc *Encoding) { +func (inst *InstructionParsed) parseOperands(enc *EncodingParsed) { // This is the most vulnerable part. // // The mnemonic and operands of an instruction are sequentially recorded @@ -454,11 +470,11 @@ // operand interval symbol ", " will be discarded. asm, oprAsm := "", "" leftCurly, leftSquare := 0, 0 - elems := []element{} + elems := []Element{} recordFixedSymbol := func(symbol, val string) { allFixedSymbolsLock.Lock() if _, ok := allFixedSymbols[symbol]; !ok { - allFixedSymbols[symbol] = make(map[string]*Instruction) + allFixedSymbols[symbol] = make(map[string]*InstructionParsed) } if _, ok := allFixedSymbols[symbol][val]; ok { allFixedSymbolsLock.Unlock() @@ -517,7 +533,7 @@ } } val = trimXMLEscape(val) - elem := element{encodedIn: encodedin, textExp: explanation, symbol: val} + elem := Element{encodedIn: encodedin, textExp: explanation, symbol: val} // Some hardcoded logic to populate register type and the presence // of <mod> for deduplication purposes if strings.HasPrefix(val, "<X") { @@ -579,10 +595,10 @@ asm += val appendOperand := func() { - elemsCopy := make([]element, len(elems)) + elemsCopy := make([]Element, len(elems)) copy(elemsCopy, elems) - opr := operand{name: oprAsm, elems: elemsCopy} - enc.operands = append(enc.operands, opr) + opr := Operand{Name: oprAsm, Elems: elemsCopy} + enc.Operands = append(enc.Operands, opr) oprAsm = "" elems = elems[:0] } @@ -625,33 +641,33 @@ if oprAsm != "" || len(elems) != 0 { log.Fatalf("malformed Asmtemplate, oprAsm: %v, elems: %v in %s\n", oprAsm, elems, inst.file) } - enc.asm = asm + enc.Asm = asm } // template resets the arm64 assembly template of an encoding, to make it cleaner. -func (inst *Instruction) template(enc *Encoding) { - asm := enc.operands[0].name - if len(enc.operands) > 1 { // Has operands +func (inst *InstructionParsed) template(enc *EncodingParsed) { + asm := enc.Operands[0].Name + if len(enc.Operands) > 1 { // Has operands asm += " " i := 1 - for ; i < len(enc.operands)-1; i++ { - asm += enc.operands[i].name + ", " + for ; i < len(enc.Operands)-1; i++ { + asm += enc.Operands[i].Name + ", " } - asm += enc.operands[i].name + asm += enc.Operands[i].Name } - enc.asm = asm + enc.Asm = asm } // arm64Opcode sets the arm64 opcode of an encoding. -func (inst *Instruction) arm64Opcode(enc *Encoding) { - if len(enc.operands) == 0 { +func (inst *InstructionParsed) arm64Opcode(enc *EncodingParsed) { + if len(enc.Operands) == 0 { log.Fatalf("Miss mnemonic: %v in %s\n", enc, inst.file) } // Add a prefix "A64", to differ with the "A" prefix of Go opcode. - enc.arm64Op = "A64" + enc.operands[0].name + enc.arm64Op = "A64" + enc.Operands[0].Name } -func (enc *Encoding) classString() string { +func (enc *EncodingParsed) classString() string { val := "" for _, d := range enc.DocVars { if d.Key == "instr-class" { @@ -662,7 +678,7 @@ return val } -func (enc *Encoding) instClass() bool { +func (enc *EncodingParsed) instClass() bool { val := enc.classString() switch val { case "sve": @@ -675,16 +691,16 @@ return true } -func (enc *Encoding) hasZREG() bool { +func (enc *EncodingParsed) hasZREG() bool { // Special case: <Pg>/<ZM>, <ZM> is not Z register. - return reZREG.MatchString(enc.asm) + return reZREG.MatchString(enc.Asm) } -func (enc *Encoding) hasPREG() bool { - return rePREG.MatchString(enc.asm) +func (enc *EncodingParsed) hasPREG() bool { + return rePREG.MatchString(enc.Asm) } -func (enc *Encoding) goOpcodePrefix(inst *Instruction) string { +func (enc *EncodingParsed) goOpcodePrefix(inst *InstructionParsed) string { if enc.prefix != "" { return enc.prefix } @@ -703,33 +719,33 @@ } // goOpcode determines the Go opcode representation of an encoding. -func (inst *Instruction) goOpcode(enc *Encoding) { - if len(enc.operands) == 0 { +func (inst *InstructionParsed) goOpcode(enc *EncodingParsed) { + if len(enc.Operands) == 0 { log.Fatalf("Missing mnemonic: %v in %s\n", enc, inst.file) } - if enc.goOp != "" { + if enc.GoOp != "" { return } prefix, opcode := "A", "" prefix += enc.goOpcodePrefix(inst) - opcode = enc.operands[0].name - enc.goOp = prefix + opcode + opcode = enc.Operands[0].Name + enc.GoOp = prefix + opcode enc.prefix = prefix } // sortOperands reorders the operands of an encoding according to Go assembly syntax. -func (enc *Encoding) sortOperands() { +func (enc *EncodingParsed) sortOperands() { // Reverse args, placing dest last. - for i, j := 1, len(enc.operands)-1; i < j; i, j = i+1, j-1 { - enc.operands[i], enc.operands[j] = enc.operands[j], enc.operands[i] + for i, j := 1, len(enc.Operands)-1; i < j; i, j = i+1, j-1 { + enc.Operands[i], enc.Operands[j] = enc.Operands[j], enc.Operands[i] } } -func (inst *Instruction) operandType(opr operand) string { - if opr.typ != "" { - return opr.typ +func (inst *InstructionParsed) operandType(opr Operand) string { + if opr.Typ != "" { + return opr.Typ } - name := opr.name + name := opr.Name for i := 0; i < len(operandRules); i++ { if operandRules[i].re.MatchString(name) { return operandRules[i].class @@ -740,13 +756,13 @@ } // operandsType classifies all operands of an encoding. -func (inst *Instruction) operandsType(enc *Encoding) { - for i := 1; i < len(enc.operands); i++ { - enc.operands[i].typ = inst.operandType(enc.operands[i]) +func (inst *InstructionParsed) operandsType(enc *EncodingParsed) { + for i := 1; i < len(enc.Operands); i++ { + enc.Operands[i].Typ = inst.operandType(enc.Operands[i]) } } -func ProcessXMLFiles(insts []*Instruction) { +func ProcessXMLFiles(insts []*InstructionParsed) { var wg sync.WaitGroup sort.Slice(insts, func(i, j int) bool { if insts[i] == nil { @@ -765,7 +781,7 @@ } for _, inst := range insts { wg.Add(1) - go func(inst *Instruction) { + go func(inst *InstructionParsed) { defer wg.Done() inst.extractBinary() inst.processEncodings() @@ -777,18 +793,18 @@ } // The operand constraints, the value is an example instruction. -var allOpConstraints = map[string]*Instruction{} +var allOpConstraints = map[string]*InstructionParsed{} // The encoding function descriptions with their references to named bit ranges expanded. // The value is an example instruction. -var allEncodingDescs = map[string]*Instruction{} +var AllEncodingDescs = map[string]*InstructionParsed{} // The mapping from encoding function description to encoded-in. -var encodingDescsToEncodedIn = map[string]string{} +var EncodingDescsToEncodedIn = map[string]string{} var concatedRangeRe = regexp.MustCompile(`\((.*?) :: (.*?)(?: :: (.*?))?\)`) var rangeIndexRe = regexp.MustCompile(`(.*?)\[(\d+)\]`) -func (inst *Instruction) expandNamedBitRanges(elm *element, varBin map[string]bitRange) string { +func (inst *InstructionParsed) expandNamedBitRanges(elm *Element, varBin map[string]bitRange) string { ranges := map[string]string{} textExp := elm.textExp br, ok := varBin[elm.encodedIn] @@ -806,7 +822,7 @@ idx := matches[2] idxI, err := strconv.Atoi(idx) if err != nil { - panic(fmt.Sprintf("invalid index: %s in %s, available: %v in %s\n", idx, elm.encodedIn, varBin, inst.file)) + log.Fatalf("invalid index: %s in %s, available: %v in %s\n", idx, elm.encodedIn, varBin, inst.file) } br, ok2 := varBin[key] if ok2 { @@ -858,19 +874,19 @@ // // 2. populates the constraints field of each operand. // 3. bookkeep the encoding function descriptions and operand constraints. -func validate(insts []*Instruction) { +func validate(insts []*InstructionParsed) { allEncodings := map[string][]string{} for _, inst := range insts { for i, iclass := range inst.Classes.Iclass { for j, encoding := range iclass.Encodings { - if encoding.parsed == false { + if encoding.Parsed == false { continue } key := encoding.arm64Op - for k, operand := range encoding.operands { - key += " " + operand.typ - for l, elem := range operand.elems { - constraints := []string{fmt.Sprintf("COP_%s__%d_", operand.typ, l)} + for k, operand := range encoding.Operands { + key += " " + operand.Typ + for l, elem := range operand.Elems { + constraints := []string{fmt.Sprintf("COP_%s__%d_", operand.Typ, l)} if elem.fixedArng != "" { key += "_(Arng:" + elem.fixedArng + ")" constraints = append(constraints, "ARNG"+elem.fixedArng) @@ -909,20 +925,20 @@ cStr = strings.Join(constraints, "_") allOpConstraints[cStr] = inst } - inst.Classes.Iclass[i].Encodings[j].operands[k].constraints = append( - inst.Classes.Iclass[i].Encodings[j].operands[k].constraints, cStr) + inst.Classes.Iclass[i].Encodings[j].Operands[k].constraints = append( + inst.Classes.Iclass[i].Encodings[j].Operands[k].constraints, cStr) textExpWithRanges := inst.expandNamedBitRanges(&elem, iclass.RegDiagram.varBin) - allEncodingDescs[textExpWithRanges] = inst - if existing, ok := encodingDescsToEncodedIn[textExpWithRanges]; ok && existing != elem.encodedIn { + AllEncodingDescs[textExpWithRanges] = inst + if existing, ok := EncodingDescsToEncodedIn[textExpWithRanges]; ok && existing != elem.encodedIn { log.Fatalf("duplicate encoding description for two different encoded-ins: %s for %s and %s in %s\n", textExpWithRanges, existing, elem.encodedIn, inst.file) } - encodingDescsToEncodedIn[textExpWithRanges] = elem.encodedIn - inst.Classes.Iclass[i].Encodings[j].operands[k].elems[l].textExpWithRanges = textExpWithRanges + EncodingDescsToEncodedIn[textExpWithRanges] = elem.encodedIn + inst.Classes.Iclass[i].Encodings[j].Operands[k].Elems[l].TextExpWithRanges = textExpWithRanges } - inst.Classes.Iclass[i].Encodings[j].operands[k].resolveConstraints() + inst.Classes.Iclass[i].Encodings[j].Operands[k].resolveConstraints() } - allEncodings[key] = append(allEncodings[key], encoding.asm) + allEncodings[key] = append(allEncodings[key], encoding.Asm) } } } @@ -964,6 +980,21 @@ continue } } + // If the diff is only by Pg/M or Pg/Z, it's ok to ignore, they are handled by the assembler. + if len(v) == 2 { + var hasPgM, hasPgZ bool + for _, s := range v { + if strings.Contains(s, "/M") { + hasPgM = true + } + if strings.Contains(s, "/Z") { + hasPgZ = true + } + } + if hasPgM && hasPgZ { + continue + } + } sort.Strings(v) log.Printf("%s:\n\t%v\n", k, strings.Join(v, "\n\t")) } @@ -976,11 +1007,10 @@ // the arrow-bracket enclosed parts are elements. var expectedElemCount = map[string]int{ // <reg>.<T> - "AC_ARNG": 2, - "AC_PREG": 2, - "AC_PREGZ": 2, - "AC_PREGM": 2, - "AC_ZREG": 2, + "AC_ARNG": 2, + "AC_PREG": 2, + "AC_PREGZM": 2, + "AC_ZREG": 2, // <reg>.<T>[<index>] "AC_ARNGIDX": 3, "AC_ZREGIDX": 3, @@ -1030,16 +1060,16 @@ // to the new element's encoding function. // This function also checks that the operand has the expected number of elements // after resolving the constraints. -func (op *operand) resolveConstraints() { +func (op *Operand) resolveConstraints() { insertElmAt := func(idx int, symbol, textExpWithRanges string) { - op.elems = append(op.elems[:idx], append([]element{ + op.Elems = append(op.Elems[:idx], append([]Element{ { encodedIn: "nil", - textExpWithRanges: textExpWithRanges, + TextExpWithRanges: textExpWithRanges, symbol: symbol, }, - }, op.elems[idx:]...)...) - allEncodingDescs[textExpWithRanges] = nil + }, op.Elems[idx:]...)...) + AllEncodingDescs[textExpWithRanges] = nil } // Constraint format: COP_<AClass>__<index>_(_<constraintTypes>)* // <AClass> is the operand class, e.g. AC_REG, AC_IMM, etc. @@ -1120,65 +1150,65 @@ } noOpCheck := "No-op check, returns true" // Check the number of elements - if el := expectedElemCount[op.typ]; len(op.elems) != el { + if el := expectedElemCount[op.Typ]; len(op.Elems) != el { resolved := false - switch op.name { + switch op.Name { case "#0.0": - if el == 2 && len(op.elems) == 0 { - op.elems = make([]element, 0, 2) + if el == 2 && len(op.Elems) == 0 { + op.Elems = make([]Element, 0, 2) insertElmAt(0, "#0.0", "Check this is immediate 0.0") insertElmAt(1, "nil", noOpCheck) resolved = true } case "#<const>", "#<imm1>", "#<imm2>", "#<imm>", "<const>": - if el == 2 && len(op.elems) == 1 { + if el == 2 && len(op.Elems) == 1 { insertElmAt(1, "nil", noOpCheck) resolved = true } case "<Dd>", "<Pd>", "<Pg>", "<Pn>", "<PNg>", "<Pt>", "<Pv>", "<Zd>", "<Zm>", "<Zn>", "<Zt>": - if el == 2 && len(op.elems) == 1 { + if el == 2 && len(op.Elems) == 1 { insertElmAt(1, "nil", noOpCheck) resolved = true } case "<PNg>/Z", "<Pg>/Z": - if el == 2 && len(op.elems) == 1 { + if el == 2 && len(op.Elems) == 1 { insertElmAt(1, "Z", "Check this is a zeroing predication") resolved = true } case "<Pg>/M", "<Pv>/M": - if el == 2 && len(op.elems) == 1 { + if el == 2 && len(op.Elems) == 1 { insertElmAt(1, "M", "Check this is a merging predication") resolved = true } case "<PNn>[<imm>]": - if el == 3 && len(op.elems) == 2 { + if el == 3 && len(op.Elems) == 2 { insertElmAt(1, "nil", noOpCheck) resolved = true } case "<Pd>.<T>{, <pattern>}": - if el == 4 && len(op.elems) == 3 { + if el == 4 && len(op.Elems) == 3 { insertElmAt(3, "nil", noOpCheck) resolved = true } case "<Zd>{[<imm>]}", "<Zm>[<index>]", "<Zn>{[<imm>]}": - if el == 3 && len(op.elems) == 2 { + if el == 3 && len(op.Elems) == 2 { insertElmAt(1, "nil", noOpCheck) resolved = true } case "[<Xn|SP>, <Xm>]", "[<Xn|SP>, <Zm>.D]", "[<Xn|SP>{, <Xm>}]", "[<Zn>.D{, <Xm>}]", "[<Zn>.S{, <Xm>}]": - if el == 6 && len(op.elems) == 4 { + if el == 6 && len(op.Elems) == 4 { insertElmAt(4, "nil", noOpCheck) insertElmAt(5, "nil", noOpCheck) resolved = true } case "[<Xn|SP>, <Zm>.S, <mod>]", "[<Xn|SP>, <Zm>.D, <mod>]": - if el == 6 && len(op.elems) == 5 { + if el == 6 && len(op.Elems) == 5 { insertElmAt(5, "nil", noOpCheck) resolved = true } } if !resolved { - unresolvedConstraints[fmt.Sprintf("Operand %s has %d elements, expected %d", op.name, len(op.elems), expectedElemCount[op.typ])] = struct{}{} + unresolvedConstraints[fmt.Sprintf("Operand %s has %d elements, expected %d", op.Name, len(op.Elems), expectedElemCount[op.Typ])] = struct{}{} } } } @@ -1217,17 +1247,17 @@ } } } - log.Printf("len(allEncodingDescs) = %v\n", len(allEncodingDescs)) + log.Printf("len(AllEncodingDescs) = %v\n", len(AllEncodingDescs)) if debug > 0 { - keys := make([]string, 0, len(allEncodingDescs)) - for k := range allEncodingDescs { + keys := make([]string, 0, len(AllEncodingDescs)) + for k := range AllEncodingDescs { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { fmt.Printf("%s\n", k) if debug > 1 { - fmt.Printf("Example Inst at %s\n", allEncodingDescs[k].file) + fmt.Printf("Example Inst at %s\n", AllEncodingDescs[k].file) } } }
diff --git a/arm64/instgen/xmlspec/parser_test.go b/arm64/instgen/xmlspec/parser_test.go index 9cbae57..08d17a2 100644 --- a/arm64/instgen/xmlspec/parser_test.go +++ b/arm64/instgen/xmlspec/parser_test.go
@@ -13,7 +13,7 @@ var remoteData = flag.Bool("remote", false, "use remote data") -func getData(t *testing.T) []*Instruction { +func getData(t *testing.T) []*InstructionParsed { if *remoteData { xmlDir, err := GetArm64XMLSpec(t.TempDir(), ExpectedURL, ExpectedVersion) if err != nil { @@ -66,11 +66,11 @@ } // Check RegDiagram and Encodings all parsed for _, iclass := range inst.Classes.Iclass { - if !iclass.RegDiagram.parsed { + if !iclass.RegDiagram.Parsed { continue } for _, encoding := range iclass.Encodings { - if !encoding.parsed { + if !encoding.Parsed { continue outer } } @@ -78,13 +78,13 @@ // by some encoding elements encodedBoxes := make(map[string]bool) for _, encoding := range iclass.Encodings { - if encoding.alias { + if encoding.Alias { // Alias encodings are not fully specified in their own section, // skip them. continue } - for _, operand := range encoding.operands { - for _, elem := range operand.elems { + for _, operand := range encoding.Operands { + for _, elem := range operand.Elems { encodedIn := elem.encodedIn encodedBoxes[encodedIn] = true if strings.Contains(encodedIn, ":") {
diff --git a/arm64/instgen/xmlspec/printer.go b/arm64/instgen/xmlspec/printer.go index 17c84d1..640abc9 100644 --- a/arm64/instgen/xmlspec/printer.go +++ b/arm64/instgen/xmlspec/printer.go
@@ -11,14 +11,23 @@ // String methods for Instruction and its types to provide a comprehensive recursive print. -func (i *Instruction) String() string { +func (i *InstructionParsed) String() string { if i == nil { return "Instruction{ <nil> }" } var sb strings.Builder sb.WriteString("Instruction{") sb.WriteString(indent("Title: "+i.Title, 1)) + if i.ParseError != "" { + sb.WriteString(indent("ParseError: "+i.ParseError, 1)) + } sb.WriteString(indent("Type: "+i.Type, 1)) + sb.WriteString(indent("Desc:", 1)) + sb.WriteString(indent(i.Desc.String(), 2)) + sb.WriteString(indent("PsSections:", 1)) + for _, ps := range i.PsSections { + sb.WriteString(indent(ps.String(), 2)) + } sb.WriteString(indent("DocVars:", 1)) for _, dv := range i.DocVars { sb.WriteString(indent(dv.String(), 2)) @@ -57,17 +66,15 @@ var sb strings.Builder sb.WriteString("Iclass{") sb.WriteString(indent("Name: "+ic.Name, 1)) - sb.WriteString(indent("OneOf: "+ic.OneOf, 1)) - sb.WriteString(indent("ID: "+ic.ID, 1)) - sb.WriteString(indent("NoEncodings: "+ic.NoEncodings, 1)) - sb.WriteString(indent("ISA: "+ic.ISA, 1)) sb.WriteString(indent("DocVars:", 1)) for _, dv := range ic.DocVars { sb.WriteString(indent(dv.String(), 2)) } - sb.WriteString(indent("ArchVariant:", 1)) - sb.WriteString(indent(ic.ArchVariant.String(), 2)) + sb.WriteString(indent("PsSection:", 1)) + for _, ps := range ic.PsSection { + sb.WriteString(indent(ps.String(), 2)) + } sb.WriteString(indent("Regdiagram:", 1)) sb.WriteString(indent(ic.RegDiagram.String(), 2)) @@ -80,18 +87,10 @@ return sb.String() } -func (av ArchVariant) String() string { - var sb strings.Builder - sb.WriteString("ArchVariant{") - sb.WriteString(indent("Name: "+av.Name, 1)) - sb.WriteString(indent("Feature: "+av.Feature, 1)) - sb.WriteString("\n}") - return sb.String() -} - -func (rd RegDiagram) String() string { +func (rd RegDiagramParsed) String() string { var sb strings.Builder sb.WriteString("Regdiagram{") + sb.WriteString(indent("PsName: "+rd.PsName, 1)) sb.WriteString(indent("Boxes:", 1)) for _, b := range rd.Boxes { sb.WriteString(indent(b.String(), 2)) @@ -103,7 +102,7 @@ for k, v := range rd.varBin { sb.WriteString(indent(fmt.Sprintf("%s: [%d, %d]", k, v.lo, v.hi), 3)) } - sb.WriteString(indent(fmt.Sprintf("parsed: %t", rd.parsed), 2)) + sb.WriteString(indent(fmt.Sprintf("Parsed: %t", rd.Parsed), 2)) sb.WriteString(indent("--- Parsed Data ---", 1)) sb.WriteString("\n}") return sb.String() @@ -113,11 +112,7 @@ var sb strings.Builder sb.WriteString("Box{") sb.WriteString(indent("HiBit: "+b.HiBit, 1)) - sb.WriteString(indent("Width: "+b.Width, 1)) sb.WriteString(indent("Name: "+b.Name, 1)) - sb.WriteString(indent("UseName: "+b.UseName, 1)) - sb.WriteString(indent("Settings: "+b.Settings, 1)) - sb.WriteString(indent("PsBits: "+b.PsBits, 1)) sb.WriteString(indent("Cs:", 1)) for _, c := range b.Cs { sb.WriteString(indent(c.String(), 2)) @@ -135,11 +130,10 @@ return sb.String() } -func (e Encoding) String() string { +func (e EncodingParsed) String() string { var sb strings.Builder sb.WriteString("Encoding{") sb.WriteString(indent("Name: "+e.Name, 1)) - sb.WriteString(indent("Label: "+e.Label, 1)) sb.WriteString(indent("DocVars:", 1)) for _, dv := range e.DocVars { sb.WriteString(indent(dv.String(), 2)) @@ -151,20 +145,20 @@ sb.WriteString(indent("Asmtemplate: "+e.AsmTemplate.String(), 1)) sb.WriteString(indent("--- Parsed Data ---", 1)) - sb.WriteString(indent(fmt.Sprintf("binary: 0x%x", e.binary), 2)) + sb.WriteString(indent(fmt.Sprintf("Binary: 0x%x", e.Binary), 2)) sb.WriteString(indent(fmt.Sprintf("mask: 0x%x", e.mask), 2)) - sb.WriteString(indent("asm: "+e.asm, 2)) - sb.WriteString(indent("goOp: "+e.goOp, 2)) + sb.WriteString(indent("Asm: "+e.Asm, 2)) + sb.WriteString(indent("GoOp: "+e.GoOp, 2)) sb.WriteString(indent("arm64Op: "+e.arm64Op, 2)) sb.WriteString(indent("class: "+e.class.String(), 2)) sb.WriteString(indent(fmt.Sprintf("invalid: %t", e.invalid), 2)) - sb.WriteString(indent(fmt.Sprintf("alias: %t", e.alias), 2)) + sb.WriteString(indent(fmt.Sprintf("Alias: %t", e.Alias), 2)) sb.WriteString(indent("prefix: "+e.prefix, 2)) - sb.WriteString(indent("operands:", 2)) - for _, op := range e.operands { + sb.WriteString(indent("Operands:", 2)) + for _, op := range e.Operands { sb.WriteString(indent(op.String(), 3)) } - sb.WriteString(indent("parsed: "+fmt.Sprintf("%t", e.parsed), 2)) + sb.WriteString(indent("Parsed: "+fmt.Sprintf("%t", e.Parsed), 2)) sb.WriteString(indent("--- Parsed Data ---", 1)) sb.WriteString("\n}") return sb.String() @@ -173,22 +167,21 @@ func (at AsmTemplate) String() string { var sb strings.Builder sb.WriteString("Asmtemplate{") - content := "" + sb.WriteString(indent("TextA:", 1)) for _, ta := range at.TextA { - content += ta.Value + sb.WriteString(indent(ta.String(), 2)) } - sb.WriteString(indent("Content: "+content, 1)) sb.WriteString("\n}") return sb.String() } -func (op operand) String() string { +func (op Operand) String() string { var sb strings.Builder - sb.WriteString("operand{") - sb.WriteString(indent("name: "+op.name, 1)) - sb.WriteString(indent("typ: "+op.typ, 1)) + sb.WriteString("Operand{") + sb.WriteString(indent("name: "+op.Name, 1)) + sb.WriteString(indent("typ: "+op.Typ, 1)) sb.WriteString(indent("elems:", 1)) - for _, elem := range op.elems { + for _, elem := range op.Elems { sb.WriteString(indent(elem.String(), 2)) } for _, v := range op.constraints { @@ -198,12 +191,12 @@ return sb.String() } -func (e element) String() string { +func (e Element) String() string { var sb strings.Builder - sb.WriteString("element{") + sb.WriteString("Element{") sb.WriteString(indent("encodedIn: "+e.encodedIn, 1)) sb.WriteString(indent("textExp: "+e.textExp, 1)) - sb.WriteString(indent("textExpWithRanges: "+e.textExpWithRanges, 1)) + sb.WriteString(indent("textExpWithRanges: "+e.TextExpWithRanges, 1)) sb.WriteString(indent("symbol: "+e.symbol, 1)) sb.WriteString(indent(fmt.Sprintf("fixedArng: %s", e.fixedArng), 1)) sb.WriteString(indent(fmt.Sprintf("fixedLSL: %s", e.fixedLSL), 1)) @@ -286,7 +279,6 @@ func (tg TGroup) String() string { var sb strings.Builder sb.WriteString("TGroup{") - sb.WriteString(indent("Cols: "+tg.Cols, 1)) sb.WriteString(indent("THead:", 1)) sb.WriteString(indent(tg.THead.String(), 2)) sb.WriteString(indent("TBody:", 1)) @@ -350,3 +342,76 @@ prefix := strings.Repeat(" ", level) return "\n" + prefix + strings.ReplaceAll(s, "\n", "\n"+prefix) } + +func (d Desc) String() string { + var sb strings.Builder + sb.WriteString("Desc{") + sb.WriteString(indent("Brief:", 1)) + sb.WriteString(indent(d.Brief.String(), 2)) + sb.WriteString(indent("Authored:", 1)) + sb.WriteString(indent(d.Authored.String(), 2)) + sb.WriteString("\n}") + return sb.String() +} + +func (b Brief) String() string { + var sb strings.Builder + sb.WriteString("Brief{") + sb.WriteString(indent("Para:", 1)) + for _, p := range b.Para { + sb.WriteString(indent(p.String(), 2)) + } + sb.WriteString("\n}") + return sb.String() +} + +func (a Authored) String() string { + var sb strings.Builder + sb.WriteString("Authored{") + sb.WriteString(indent("Paragraphs:", 1)) + for _, p := range a.Paragraphs { + sb.WriteString(indent(p.String(), 2)) + } + sb.WriteString("\n}") + return sb.String() +} + +func (p Para) String() string { + var sb strings.Builder + sb.WriteString("Para{") + sb.WriteString(indent("Text: "+p.Text, 1)) + sb.WriteString("\n}") + return sb.String() +} + +func (ps PsSection) String() string { + var sb strings.Builder + sb.WriteString("PsSection{") + sb.WriteString(indent("Ps:", 1)) + for _, p := range ps.Ps { + sb.WriteString(indent(p.String(), 2)) + } + sb.WriteString("\n}") + return sb.String() +} + +func (p Ps) String() string { + var sb strings.Builder + sb.WriteString("Ps{") + for _, t := range p.PSText { + sb.WriteString(indent("PSText: "+t, 1)) + } + sb.WriteString("\n}") + return sb.String() +} + +func (ta TextA) String() string { + var sb strings.Builder + sb.WriteString("TextA{") + sb.WriteString(indent("Value: "+ta.Value, 1)) + if ta.Link != "" { + sb.WriteString(indent("Link: "+ta.Link, 1)) + } + sb.WriteString("\n}") + return sb.String() +}
diff --git a/arm64/instgen/xmlspec/util.go b/arm64/instgen/xmlspec/util.go new file mode 100644 index 0000000..4572b31 --- /dev/null +++ b/arm64/instgen/xmlspec/util.go
@@ -0,0 +1,115 @@ +// 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 xmlspec + +// This file contains utility functions. + +import ( + "archive/tar" + "compress/gzip" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" +) + +// GetArm64XMLSpec downloads the ARM64 XML spec from the given URL to a temporary directory. +// It returns the path to directory containing all instruction XML files. +// If anything goes wrong, it will return an error. +func GetArm64XMLSpec(tmpDir string, url string, version string) (string, error) { + if err := downloadArm64XMLSpec(tmpDir, url); err != nil { + return "", fmt.Errorf("downloadArm64XMLSpec failed: %v", err) + } + + // The tarball extracts to a directory like "ISA_A64_xml_A_profile-2025-12". + // We need to find it. + entries, err := os.ReadDir(tmpDir) + if err != nil { + return "", fmt.Errorf("os.ReadDir failed: %v", err) + } + + var xmlDir string + for _, e := range entries { + if e.IsDir() && strings.HasPrefix(e.Name(), version) { + xmlDir = filepath.Join(tmpDir, e.Name()) + break + } + } + + if xmlDir == "" { + return "", fmt.Errorf("could not find extracted XML directory in %s", tmpDir) + } + return xmlDir, nil +} + +// downloadArm64XMLSpec downloads the ARM64 XML spec from the given URL to the given directory. +func downloadArm64XMLSpec(dir string, url string) error { + resp, err := http.Get(url) + if err != nil { + return fmt.Errorf("fetching ARM64 XML spec from %s failed: %v", url, err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("fetching ARM64 XML spec from %s returned status: %s", url, resp.Status) + } + + if err := extractTarGz(resp.Body, dir); err != nil { + return err + } + return nil +} + +// extractTarGz extracts the tar.gz file to the given directory. +func extractTarGz(r io.Reader, dir string) error { + gzr, err := gzip.NewReader(r) + if err != nil { + return err + } + defer gzr.Close() + + tr := tar.NewReader(gzr) + + // Iterate over the entries in the tarball. + for { + header, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + + target := filepath.Join(dir, header.Name) + + switch header.Typeflag { + // directories + case tar.TypeDir: + if err := os.MkdirAll(target, 0755); err != nil { + return err + } + // regular files + case tar.TypeReg: + if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { + return err + } + f, err := os.Create(target) + if err != nil { + return err + } + if _, err := io.Copy(f, tr); err != nil { + f.Close() + return err + } + f.Close() + } + } + return nil +} + +const ExpectedURL = "https://developer.arm.com/-/cdn-downloads/permalink/Exploration-Tools-A64-ISA/ISA_A64/ISA_A64_xml_A_profile-2025-12.tar.gz" +const ExpectedVersion = "ISA_A64_xml_A_profile-2025-12"
diff --git a/arm64/instgen/xmlspec/xml.go b/arm64/instgen/xmlspec/xml.go new file mode 100644 index 0000000..5e8cee6 --- /dev/null +++ b/arm64/instgen/xmlspec/xml.go
@@ -0,0 +1,180 @@ +// 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 xmlspec + +import "encoding/xml" + +// This file contains the data types for the XML specification of the ARM64 ISA. + +// DocVar represents a <docvar> element, storing document metadata like "isa" or "mnemonic". +type DocVar struct { + Key string `xml:"key,attr"` + Value string `xml:"value,attr"` +} + +// C represents a <c> element within a register diagram box, specifying bit content or a named field. +type C struct { + Value string `xml:",chardata"` + ColSpan string `xml:"colspan,attr"` +} + +// Box represents a <box> element in a register diagram, describing a specific bitfield. +type Box struct { + HiBit string `xml:"hibit,attr"` + Name string `xml:"name,attr"` + Cs []C `xml:"c"` +} + +// TextA represents a <text> or <a> element within an assembly template. +type TextA struct { + Value string `xml:",chardata"` + Link string `xml:"link,attr"` +} + +// AsmTemplate represents an <asmtemplate> element, defining the syntax of the instruction. +type AsmTemplate struct { + // <Asmtemplate> contains two kinds of sub-elements, <text> and <a>. + // <text> contains string literals, <a> contains a symbol and + // two attributes: link and hover. The order of <text> and <a> matters, + // so we save both into the following structure to preserve their order. + TextA []TextA `xml:",any"` +} + +// RegDiagram represents a <regdiagram> element, detailing the bit layout of an encoding. +type RegDiagram struct { + Boxes []Box `xml:"box"` + PsName string `xml:"psname,attr"` +} + +// Encoding represents an <encoding> element for a specific instruction variant. +type Encoding struct { + Name string `xml:"name,attr"` + DocVars []DocVar `xml:"docvars>docvar"` + Boxes []Box `xml:"box"` + AsmTemplate AsmTemplate `xml:"asmtemplate"` +} + +// Iclass represents an <iclass> element, grouping instruction encodings that share a register diagram. +type Iclass struct { + Name string `xml:"name,attr"` + DocVars []DocVar `xml:"docvars>docvar"` + RegDiagram RegDiagramParsed `xml:"regdiagram"` + Encodings []EncodingParsed `xml:"encoding"` + PsSection []PsSection `xml:"ps_section"` +} + +// Classes represents a <classes> element, grouping instruction classes. +type Classes struct { + Iclass []Iclass `xml:"iclass"` +} + +// Symbol represents a <symbol> element, linking an explanation to an assembly sequence symbol. +type Symbol struct { + Value string `xml:",chardata"` + Link string `xml:"link,attr"` +} + +// Account represents an <account> element, providing a textual explanation for a symbol. +type Account struct { + Encodedin string `xml:"encodedin,attr"` + DocVars []DocVar `xml:"docvars>docvar"` + Intro string `xml:"intro>para"` +} + +// Entry represents an <entry> element, defining a single cell in a definition table. +type Entry struct { + Value string `xml:",chardata"` + Class string `xml:"class,attr"` +} + +// Row represents a <row> element within a table header or body. +type Row struct { + Entries []Entry `xml:"entry"` +} + +// THead represents a <thead> element, containing the table header. +type THead struct { + Row Row `xml:"row"` +} + +// TBody represents a <tbody> element, containing the table body. +type TBody struct { + Row []Row `xml:"row"` +} + +// TGroup represents a <tgroup> element, defining the column and row groups of a table. +type TGroup struct { + THead THead `xml:"thead"` + TBody TBody `xml:"tbody"` +} + +// Table represents a <table> element used to explain symbol encodings. +type Table struct { + Class string `xml:"class,attr"` + TGroup TGroup `xml:"tgroup"` +} + +// Definition represents a <definition> element, usually containing a table to define symbol values. +type Definition struct { + Encodedin string `xml:"encodedin,attr"` + Intro string `xml:"intro"` + Table Table `xml:"table"` +} + +// Explanation represents an <explanation> element for a symbol used in the assembly template. +type Explanation struct { + Symbol Symbol `xml:"symbol"` + Account Account `xml:"account"` + Definition Definition `xml:"definition"` +} + +// Explanations represents an <explanations> element, grouping symbol explanations. +type Explanations struct { + Scope string `xml:"scope,attr"` + Explanations []Explanation `xml:"explanation"` +} + +// Desc represents a <desc> element, containing the description of an instruction. +type Desc struct { + Brief Brief `xml:"brief"` + Authored Authored `xml:"authored"` +} + +// Brief represents a <brief> element, containing a brief description. +type Brief struct { + Para []Para `xml:"para"` +} + +// Authored represents an <authored> element, containing authored paragraphs. +type Authored struct { + Paragraphs []Para `xml:"para"` +} + +// Para represents a <para> element, containing paragraph text. +type Para struct { + Text string `xml:",innerxml"` +} + +// PsSection represents a <ps_section> element, containing pseudocode sections. +type PsSection struct { + Ps []Ps `xml:"ps"` +} + +// Ps represents a <ps> element, containing pseudocode text. +type Ps struct { + PSText []string `xml:"pstext"` // pseudocode text +} + +// Instruction represents the root <instructionsection> element of an instruction XML specification. +type Instruction struct { + XMLName xml.Name `xml:"instructionsection"` + Title string `xml:"title,attr"` + Desc Desc `xml:"desc"` + Type string `xml:"type,attr"` + DocVars []DocVar `xml:"docvars>docvar"` + Classes Classes `xml:"classes"` + Explanations Explanations `xml:"explanations"` + PsSections []PsSection `xml:"ps_section"` +}