arm64/instgen: add support for register lists This CL generates CL 763820. Change-Id: I7d17aebcd80f85796955b02748249cb54468ada4 Reviewed-on: https://go-review.googlesource.com/c/arch/+/763780 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: David Chase <drchase@google.com>
diff --git a/arm64/instgen/encodings.go b/arm64/instgen/encodings.go index 08ca665..c6750ae 100644 --- a/arm64/instgen/encodings.go +++ b/arm64/instgen/encodings.go
@@ -205,7 +205,7 @@ `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"}, +`: {"encodeZm1621V2", `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) @@ -1490,4 +1490,97 @@ return 1 << 23, true } return 0, false`, "enc_tszh_tszl"}, + `For the "Byte" variant: is the vector segment index, in the range 0 to 3, encoded in the "i2" field. +bit range mappings: +i2: [22:24) +`: {"encodeI22224", `if v > 3 { + return 0, false + } + return v << 22, true`, "enc_i2"}, + `For the "Byte, single register table" variant: is the vector segment index, in the range 0 to 1, encoded in the "i1" field. +bit range mappings: +i1: [23:24) +`: {"encodeI12324B", `if v > 1 { + return 0, false + } + return v << 23, true`, "enc_i1"}, + `For the "Halfword" variant: is the vector segment index, in the range 0 to 7, encoded in the "i3h:i3l" fields. +bit range mappings: +i3h: [22:24) +i3l: [12:13) +`: {"encodeI3224I31213", `if v > 7 { + return 0, false + } + return (v&1)<<12 | (v>>1)<<22, true`, "enc_i3h_i3l"}, + `For the "Halfword, single register table" and "Halfword, two register table" variants: is the vector segment index, in the range 0 to 3, encoded in the "i2" field. +bit range mappings: +i2: [22:24) +`: {"encodeI22224HW", `if v > 3 { + return 0, false + } + return v << 22, true`, "enc_i2"}, + `Is the name of the first destination scalable predicate register, encoded as "Pd" times 2. +bit range mappings: +Pd: [1:4) +`: {"encodePd14", `if v > 14 { + return 0, false + } + if v&1 != 0 { + return 0, false + } + return v, true`, "enc_Pd"}, + `Is the name of the first destination scalable predicate register, encoded in the "Pd" field. +bit range mappings: +Pd: [0:4) +`: {"encodePd04", `return v, true`, "enc_Pd"}, + `Is the name of the first scalable vector register of the source multi-vector group, encoded in the "Zn" field. +bit range mappings: +Zn: [5:10) +`: {"encodeZn510MultiSrc1", `return v << 5, true`, "enc_Zn"}, + `Is the name of the first table vector register, encoded as "Zn". +bit range mappings: +Zn: [5:10) +`: {"encodeZn510Table1", `return v << 5, true`, "enc_Zn"}, + `Is the name of the second destination scalable predicate register, encoded as "Pd" times 2 plus 1. +bit range mappings: +Pd: [1:4) +`: {"encodePd14Plus1", `if v&1 == 0 { + return 0, false + } + return v - 1, true`, "enc_Pd"}, + `Is the name of the second destination scalable predicate register, encoded in the "Pd" field. +bit range mappings: +Pd: [0:4) +`: {"encodePd04Plus1", `// This "second destination" incurs Pd + 1 == v + return v - 1, true`, "enc_Pd"}, + `Is the name of the second scalable vector register of the source multi-vector group, encoded in the "Zn" field. +bit range mappings: +Zn: [5:10) +`: {"encodeZn510MultiSrc2", `return (v - 1) << 5, true`, "enc_Zn"}, + `Is the name of the second table vector register, encoded as "Zn" plus 1 modulo 32. +bit range mappings: +Zn: [5:10) +`: {"encodeZn510Table2", `return ((v - 1) & 0x1f) << 5, true`, "enc_Zn"}, + `Is the name of the source scalable vector register, encoded in the "Zm" field. +bit range mappings: +Zm: [16:21) +`: {"encodeZm1621V1", `return v << 16, true`, "enc_Zm"}, + `Is the name of the table vector register, encoded in the "Zn" field. +bit range mappings: +Zn: [5:10) +`: {"encodeZn510Table3", `return v << 5, true`, "enc_Zn"}, + `Is the portion index, in the range 0 to 1, encoded in the "i1" field. +bit range mappings: +i1: [8:9) +`: {"encodeI189", `if v > 1 { + return 0, false + } + return v << 8, true`, "enc_i1"}, + `Is the vector segment index, in the range 0 to 1, encoded in the "i1" field. +bit range mappings: +i1: [23:24) +`: {"encodeI12324", `if v > 1 { + return 0, false + } + return v << 23, true`, "enc_i1"}, }
diff --git a/arm64/instgen/generator.go b/arm64/instgen/generator.go index 9f734be..5dbda31 100644 --- a/arm64/instgen/generator.go +++ b/arm64/instgen/generator.go
@@ -196,17 +196,21 @@ ` var operandTypeOrders = map[string]int{ - "AC_ARNG": 0, - "AC_PREG": 0, - "AC_PREGZ": 0, - "AC_PREGZM": 0, - "AC_ZREG": 0, - "AC_SPZGREG": 1, - "AC_VREG": 1, - "AC_ARNGIDX": 2, - "AC_ZREGIDX": 2, - "AC_PREGIDX": 2, - "AC_IMM": 3, + "AC_ARNG": 0, + "AC_PREG": 0, + "AC_PREGZ": 0, + "AC_PREGZM": 0, + "AC_ZREG": 0, + "AC_SPZGREG": 1, + "AC_VREG": 1, + "AC_ARNGIDX": 2, + "AC_ZREGIDX": 2, + "AC_PREGIDX": 2, + "AC_IMM": 3, + "AC_REGLIST1": 4, + "AC_REGLIST2": 4, + "AC_REGLIST3": 4, + "AC_REGLIST4": 4, } func readExistingGoOps(aoutPath string) map[string]bool { @@ -866,9 +870,7 @@ log.Fatalf("Unsupported operand type in constructInstance: %s, enc: %s", op.Typ, enc.String()) } highFeat = max(highFeat, operandTypeOrders[op.Typ]) - if op.Typ == "AC_ARNG" || op.Typ == "AC_PREG" || op.Typ == "AC_PREGZM" || op.Typ == "AC_ZREG" { - // Operands that takes SVE registers and arrangement. - + generateSVERegOp := func(opName string) (string, string, string, int, string) { var parts []string var isPred bool if strings.Contains(opName, ".") { @@ -885,66 +887,79 @@ if len(parts) > 1 { arrName = strings.TrimSuffix(strings.TrimPrefix(parts[1], "<"), ">") } - reg := regMap[regName] + resolvedArr := arrName + regNameSanitized := strings.TrimRight(regName, "0123456789") + reg := regMap[regNameSanitized] + if reg == "" { + reg = regMap[regName] + } + var goAsmOp, gnuAsmOp string + var regIdx int if arrMap[arrName] || errCase == nil { // Give error case a random mutation to trigger an error. // Variables, needs mutation switch reg { case "Z": - regIdx := cachedOrNew(regCache, regName, 32) + 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) + resolvedArr = sveArr[arrIdx] + goAsmOp = fmt.Sprintf("Z%d.%s", regIdx, resolvedArr) + gnuAsmOp = goAsmOp case "P", "PN": - regIdx := cachedOrNew(regCache, regName, 15) + 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])) + resolvedArr = svePred[arrIdx] + gnuAsmOp = fmt.Sprintf("%s%d/%s", reg, regIdx, resolvedArr) + goAsmOp = fmt.Sprintf("%s%d.%s", reg, regIdx, resolvedArr) } else { arrIdx := cachedOrNew(arrCache, arrName, len(sveArr)) - asmOp := fmt.Sprintf("%s%d.%s", reg, regIdx, sveArr[arrIdx]) - gnuAsmOps = append([]string{asmOp}, gnuAsmOps...) - goAsmOps = append(goAsmOps, asmOp) + resolvedArr = sveArr[arrIdx] + goAsmOp = fmt.Sprintf("%s%d.%s", reg, regIdx, resolvedArr) + gnuAsmOp = goAsmOp } case "V": - regIdx := cachedOrNew(regCache, regName, 32) + 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])) + resolvedArr = neonArrGNU[arrIdx] + gnuAsmOp = fmt.Sprintf("V%d.%s", regIdx, neonArrGNU[arrIdx]) + goAsmOp = fmt.Sprintf("V%d.%s", regIdx, neonArrGo[arrIdx]) } } else { // Fixed arrangement or predications + var dotArr string if arrName != "" { - arrName = "." + arrName + dotArr = "." + 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) + regIdx = cachedOrNew(regCache, regName, 32) + goAsmOp = fmt.Sprintf("Z%d%s", regIdx, dotArr) + gnuAsmOp = goAsmOp case "P", "PN": - regIdx := cachedOrNew(regCache, regName, 15) + regIdx = cachedOrNew(regCache, regName, 15) if isPred { var arrNameGNU string if arrName != "" { - arrNameGNU = "/" + arrName[1:] + arrNameGNU = "/" + arrName } - gnuAsmOps = append([]string{fmt.Sprintf("%s%d%s", reg, regIdx, arrNameGNU)}, gnuAsmOps...) - goAsmOps = append(goAsmOps, fmt.Sprintf("%s%d%s", reg, regIdx, arrName)) + gnuAsmOp = fmt.Sprintf("%s%d%s", reg, regIdx, arrNameGNU) + goAsmOp = fmt.Sprintf("%s%d%s", reg, regIdx, dotArr) } else { - asmOp := fmt.Sprintf("%s%d%s", reg, regIdx, arrName) - gnuAsmOps = append([]string{asmOp}, gnuAsmOps...) - goAsmOps = append(goAsmOps, asmOp) + goAsmOp = fmt.Sprintf("%s%d%s", reg, regIdx, dotArr) + gnuAsmOp = goAsmOp } case "V": log.Fatalf("Unexpected V with fixed arrangement: %s", opName) } } + return goAsmOp, gnuAsmOp, reg, regIdx, resolvedArr + } + + if op.Typ == "AC_ARNG" || op.Typ == "AC_PREG" || op.Typ == "AC_PREGZM" || op.Typ == "AC_ZREG" { + goAsmOp, gnuAsmOp, _, _, _ := generateSVERegOp(opName) + gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...) + goAsmOps = append(goAsmOps, goAsmOp) } else if op.Typ == "AC_ARNGIDX" || op.Typ == "AC_ZREGIDX" || op.Typ == "AC_PREGIDX" { // Operands that takes SVE/SIMD registers and arrangement with index. // reg.T[index] @@ -1108,6 +1123,59 @@ goAsmOps = append(goAsmOps, fmt.Sprintf("$%s", imm)) } gnuAsmOps = append([]string{fmt.Sprintf("#%s", imm)}, gnuAsmOps...) + } else if op.Typ == "AC_REGLIST1" || op.Typ == "AC_REGLIST2" || op.Typ == "AC_REGLIST3" || op.Typ == "AC_REGLIST4" { + // Register list, they must be contiguous modulo 32 (16 for P registers). + // Only AC_REGLIST2 has a P register variant though. + // { <Zn>.<T> } + // { <Pd1>.<T>, <Pd2>.<T> } + // { <Zn1>.B, <Zn2>.B } + // { <Zt1>.B, <Zt2>.B, <Zt3>.B } + // { <Zt1>.B, <Zt2>.B, <Zt3>.B, <Zt4>.B } + // We can probably reuse the logic for these registers in AC_ARNG case. + // In Go, the register list is wrapped around [], while in GNU it's wrapped around {}. + nRegs := 1 + switch op.Typ { + case "AC_REGLIST2": + nRegs = 2 + case "AC_REGLIST3": + nRegs = 3 + case "AC_REGLIST4": + nRegs = 4 + } + + // Extract the first operand inside the list. + trimmedName := strings.Trim(op.Name, "{} ") + parts := strings.Split(trimmedName, ",") + firstOp := strings.TrimSpace(parts[0]) + + _, _, regPrefix, regIdx, arr := generateSVERegOp(firstOp) + + limit := 32 + if regPrefix == "P" || regPrefix == "PN" { + limit = 16 + } + + goRegs := []string{} + gnuRegs := []string{} + for i := 0; i < nRegs; i++ { + currIdx := (regIdx + i) % limit + var goReg, gnuReg string + if arr != "" { + goReg = fmt.Sprintf("%s%d.%s", regPrefix, currIdx, arr) + gnuReg = goReg + } else { + goReg = fmt.Sprintf("%s%d", regPrefix, currIdx) + gnuReg = goReg + } + goRegs = append(goRegs, goReg) + gnuRegs = append(gnuRegs, gnuReg) + } + + goAsmOp := "[" + strings.Join(goRegs, ", ") + "]" + gnuAsmOp := "{" + strings.Join(gnuRegs, ", ") + "}" + + goAsmOps = append(goAsmOps, goAsmOp) + gnuAsmOps = append([]string{gnuAsmOp}, gnuAsmOps...) } } // Try to assemble the GNU version. @@ -1157,7 +1225,7 @@ } return validCase, errCase } - if name == "ADDQP" || name == "ADDSUBP" || name == "SCVTFLT" || name == "UCVTFLT" { + if name == "ADDQP" || name == "ADDSUBP" || name == "SCVTFLT" || name == "UCVTFLT" || name == "LUTI6" { // Very new instructions // GNU toolchain 2.45 doesn't know about these instruction yet. todoCase := e2eData{GoOp: enc.GoOp[1:], Asm: fmt.Sprintf("// TODO: %s", name), highFeat: highFeat}
diff --git a/arm64/instgen/xmlspec/parser.go b/arm64/instgen/xmlspec/parser.go index 0c8194e..2deb524 100644 --- a/arm64/instgen/xmlspec/parser.go +++ b/arm64/instgen/xmlspec/parser.go
@@ -1060,7 +1060,18 @@ // This function also checks that the operand has the expected number of elements // after resolving the constraints. func (op *Operand) resolveConstraints() { - insertElmAt := func(idx int, symbol, textExpWithRanges string) { + // insertElmAt takes idx as the index in the old op.Elems slice. + // we need to keep track of the index shifts brought by prior insertions. + insertionHistory := make([]int, expectedElemCount[op.Typ]) + insertElmAt := func(idx int, symbol, textExpWithRanges string, needOffset bool) { + if needOffset { + offset := 0 + for i := range idx { + offset += insertionHistory[i] + } + insertionHistory[idx]++ + idx += offset + } op.Elems = append(op.Elems[:idx], append([]Element{ { encodedIn: "nil", @@ -1072,7 +1083,7 @@ } // Constraint format: COP_<AClass>__<index>_(_<constraintTypes>)* // <AClass> is the operand class, e.g. AC_SPZGREG, AC_IMM, etc. - // <index> is the index of the operand in the instruction, e.g. 0, 1, 2, etc. + // <index> is the index of the constrained element in the operand, e.g. 0, 1, 2, etc. // <constraintTypes> is the type of the constraint, e.g. ARNG, MODAMT, etc. for _, constraint := range op.constraints { constraint = strings.TrimPrefix(constraint, "COP_") @@ -1092,53 +1103,39 @@ for _, constraintType := range constraintTypes { switch constraintType { case "ARNGB": - insertElmAt(index+1, "B", "Check this is a B arrangement") - index++ + insertElmAt(index+1, "B", "Check this is a B arrangement", true) case "ARNGD": - insertElmAt(index+1, "D", "Check this is a D arrangement") - index++ + insertElmAt(index+1, "D", "Check this is a D arrangement", true) case "ARNGH": - insertElmAt(index+1, "H", "Check this is a H arrangement") - index++ + insertElmAt(index+1, "H", "Check this is a H arrangement", true) case "ARNGQ": - insertElmAt(index+1, "Q", "Check this is a Q arrangement") - index++ + insertElmAt(index+1, "Q", "Check this is a Q arrangement", true) case "ARNGS": - insertElmAt(index+1, "S", "Check this is a S arrangement") - index++ + insertElmAt(index+1, "S", "Check this is a S arrangement", true) case "R64": // Width constraints are preceeding the element. - insertElmAt(index, "X", "Check this is a 64-bit scalar register") - index++ + insertElmAt(index, "X", "Check this is a 64-bit scalar register", true) case "R32": - insertElmAt(index, "W", "Check this is a 32-bit scalar register") - index++ + insertElmAt(index, "W", "Check this is a 32-bit scalar register", true) case "LSL1", "LSL2", "LSL3", "LSL4", "SXTW", "UXTW", "MODAMT1", "MODAMT2", "MODAMT3": if acl == "AC_MEMEXT" { switch constraintType { case "LSL1", "LSL2", "LSL3", "LSL4": - insertElmAt(index+1, "LSL", "Check this is mod and is LSL") - index++ + insertElmAt(index+1, "LSL", "Check this is mod and is LSL", true) case "UXTW": - insertElmAt(index+1, "UXTW", "Check this is mod and is UXTW") - index++ + insertElmAt(index+1, "UXTW", "Check this is mod and is UXTW", true) case "SXTW": - insertElmAt(index+1, "SXTW", "Check this is mod and is SXTW") - index++ + insertElmAt(index+1, "SXTW", "Check this is mod and is SXTW", true) } switch constraintType { case "LSL1", "MODAMT1": - insertElmAt(index+1, "#1", "Check this is mod amount and is 1") - index++ + insertElmAt(index+1, "#1", "Check this is mod amount and is 1", true) case "LSL2", "MODAMT2": - insertElmAt(index+1, "#2", "Check this is mod amount and is 2") - index++ + insertElmAt(index+1, "#2", "Check this is mod amount and is 2", true) case "LSL3", "MODAMT3": - insertElmAt(index+1, "#3", "Check this is mod amount and is 3") - index++ + insertElmAt(index+1, "#3", "Check this is mod amount and is 3", true) case "LSL4": - insertElmAt(index+1, "#4", "Check this is mod amount and is 4") - index++ + insertElmAt(index+1, "#4", "Check this is mod amount and is 4", true) } } else { log.Printf("Unknown constraint: %s", constraint) @@ -1155,60 +1152,60 @@ case "#0.0": if el == 1 && len(op.Elems) == 0 { op.Elems = make([]Element, 0, 1) - insertElmAt(0, "#0.0", "Check this is immediate 0.0") + insertElmAt(0, "#0.0", "Check this is immediate 0.0", false) resolved = true } case "#<imm>{, <shift>}": if el == 1 && len(op.Elems) == 2 { // The 2 elements explanation need to be merged - insertElmAt(0, "#<imm>{, <shift>}", op.Elems[0].TextExpWithRanges+"\n"+op.Elems[1].TextExpWithRanges) + insertElmAt(0, "#<imm>{, <shift>}", op.Elems[0].TextExpWithRanges+"\n"+op.Elems[1].TextExpWithRanges, false) op.Elems = op.Elems[:1] resolved = true } case "<Pd>", "<Pg>", "<Pn>", "<PNg>", "<Pt>", "<Pv>", "<Zd>", "<Zm>", "<Zn>", "<Zt>": if el == 2 && len(op.Elems) == 1 { - insertElmAt(1, "nil", noOpCheck) + insertElmAt(1, "nil", noOpCheck, false) resolved = true } case "<Dd>": if el == 2 && len(op.Elems) == 1 { - insertElmAt(0, "nil", "Check this SIMD vector register is of width 64-bit.") + insertElmAt(0, "nil", "Check this SIMD vector register is of width 64-bit.", false) resolved = true } case "<PNg>/Z", "<Pg>/Z": if el == 2 && len(op.Elems) == 1 { - insertElmAt(1, "Z", "Check this is a zeroing predication") + insertElmAt(1, "Z", "Check this is a zeroing predication", false) resolved = true } case "<Pg>/M", "<Pv>/M": if el == 2 && len(op.Elems) == 1 { - insertElmAt(1, "M", "Check this is a merging predication") + insertElmAt(1, "M", "Check this is a merging predication", false) resolved = true } case "<PNn>[<imm>]": if el == 3 && len(op.Elems) == 2 { - insertElmAt(1, "nil", noOpCheck) + insertElmAt(1, "nil", noOpCheck, false) resolved = true } case "<Pd>.<T>{, <pattern>}": if el == 4 && len(op.Elems) == 3 { - insertElmAt(3, "nil", noOpCheck) + insertElmAt(3, "nil", noOpCheck, false) resolved = true } case "<Zd>{[<imm>]}", "<Zm>[<index>]", "<Zn>{[<imm>]}": if el == 3 && len(op.Elems) == 2 { - insertElmAt(1, "nil", noOpCheck) + insertElmAt(1, "nil", noOpCheck, false) 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 { - insertElmAt(4, "nil", noOpCheck) - insertElmAt(5, "nil", noOpCheck) + insertElmAt(4, "nil", noOpCheck, false) + insertElmAt(5, "nil", noOpCheck, false) resolved = true } case "[<Xn|SP>, <Zm>.S, <mod>]", "[<Xn|SP>, <Zm>.D, <mod>]": if el == 6 && len(op.Elems) == 5 { - insertElmAt(5, "nil", noOpCheck) + insertElmAt(5, "nil", noOpCheck, false) resolved = true } }