diff --git a/arm64_eval.go b/arm64_eval.go index 7c230f9..61505b6 100644 --- a/arm64_eval.go +++ b/arm64_eval.go @@ -137,6 +137,12 @@ func (c *arm64Ctx) eval64(op Operand, postInc bool) (string, error) { return c.imm64(op.Imm), nil case OpReg: return c.loadReg(op.Reg) + case OpRegExtend: + v, err := c.loadReg(op.Reg) + if err != nil { + return "", err + } + return c.extendReg64(v, op.Ext) case OpRegShift: v, err := c.loadReg(op.Reg) if err != nil { @@ -191,6 +197,38 @@ func (c *arm64Ctx) eval64(op Operand, postInc bool) (string, error) { } } +func (c *arm64Ctx) extendReg64(v string, ext ExtendOp) (string, error) { + switch ext { + case ExtendUXTX, ExtendSXTX: + return v, nil + } + + var fromTy string + var extOp string + switch ext { + case ExtendUXTB: + fromTy, extOp = "i8", "zext" + case ExtendUXTH: + fromTy, extOp = "i16", "zext" + case ExtendUXTW: + fromTy, extOp = "i32", "zext" + case ExtendSXTB: + fromTy, extOp = "i8", "sext" + case ExtendSXTH: + fromTy, extOp = "i16", "sext" + case ExtendSXTW: + fromTy, extOp = "i32", "sext" + default: + return "", fmt.Errorf("arm64: unsupported register extension %q", ext) + } + + tr := c.newTmp() + fmt.Fprintf(c.b, " %%%s = trunc i64 %s to %s\n", tr, v, fromTy) + ex := c.newTmp() + fmt.Fprintf(c.b, " %%%s = %s %s %%%s to i64\n", ex, extOp, fromTy, tr) + return "%" + ex, nil +} + func (c *arm64Ctx) evalFPValue64(op Operand) (string, error) { slot, ok := c.fpParams[op.FPOffset] if !ok { diff --git a/arm64_helper_edge_test.go b/arm64_helper_edge_test.go index fc05ee8..fe2a615 100644 --- a/arm64_helper_edge_test.go +++ b/arm64_helper_edge_test.go @@ -1485,6 +1485,14 @@ func TestARM64EvalCoverage(t *testing.T) { ops := []Operand{ {Kind: OpImm, Imm: 7}, {Kind: OpReg, Reg: "R0"}, + {Kind: OpRegExtend, Reg: "R2", Ext: ExtendUXTB}, + {Kind: OpRegExtend, Reg: "R2", Ext: ExtendUXTH}, + {Kind: OpRegExtend, Reg: "R2", Ext: ExtendUXTW}, + {Kind: OpRegExtend, Reg: "R2", Ext: ExtendUXTX}, + {Kind: OpRegExtend, Reg: "R3", Ext: ExtendSXTB}, + {Kind: OpRegExtend, Reg: "R3", Ext: ExtendSXTH}, + {Kind: OpRegExtend, Reg: "R3", Ext: ExtendSXTW}, + {Kind: OpRegExtend, Reg: "R3", Ext: ExtendSXTX}, {Kind: OpRegShift, Reg: "R1", ShiftOp: ShiftLeft, ShiftAmount: 2}, {Kind: OpRegShift, Reg: "R1", ShiftOp: ShiftRight, ShiftAmount: 1}, {Kind: OpFP, FPOffset: 0}, @@ -1514,6 +1522,9 @@ func TestARM64EvalCoverage(t *testing.T) { if _, err := c.eval64(Operand{Kind: OpRegShift, Reg: "R1", ShiftOp: ShiftRotate, ShiftReg: "R2"}, false); err == nil { t.Fatalf("eval64(register shift) unexpectedly succeeded") } + if _, err := c.eval64(Operand{Kind: OpRegExtend, Reg: "R1", Ext: ExtendOp("BAD")}, false); err == nil { + t.Fatalf("eval64(bad extension) unexpectedly succeeded") + } if _, err := c.eval64(Operand{Kind: OpRegShift, Reg: "R1", ShiftOp: ShiftRotate, ShiftAmount: 1}, false); err == nil { t.Fatalf("eval64(rotate) unexpectedly succeeded") } diff --git a/arm64_new_opfamilies_test.go b/arm64_new_opfamilies_test.go index 5cac3e8..38c143e 100644 --- a/arm64_new_opfamilies_test.go +++ b/arm64_new_opfamilies_test.go @@ -78,3 +78,26 @@ TEXT featureprobe(SB),NOSPLIT,$0-0 t.Fatal(err) } } + +func TestTranslateARM64CompareWithExtendedRegister(t *testing.T) { + src := ` +TEXT countcmp(SB),NOSPLIT,$0-0 + CMP R2.UXTB, R5 + CINC EQ, R11, R11 + RET +` + file, err := Parse(ArchARM64, src) + if err != nil { + t.Fatal(err) + } + _, err = Translate(file, Options{ + TargetTriple: "aarch64-unknown-linux-gnu", + Sigs: map[string]FuncSig{ + "countcmp": {Name: "countcmp", Ret: Void}, + }, + Goarch: "arm64", + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/types.go b/types.go index f4bfeb8..9ecf439 100644 --- a/types.go +++ b/types.go @@ -140,6 +140,7 @@ const ( OpInvalid OperandKind = iota OpImm OpReg + OpRegExtend OpRegShift OpFP OpFPAddr @@ -159,6 +160,22 @@ const ( ShiftRotate ShiftOp = "@>" ) +// ExtendOp is an ARM64 register-extension modifier (for example UXTB, SXTW). +// The prefix U/S selects zero- or sign-extension; the suffix B/H/W/X selects +// the source width (8/16/32/64 bits). +type ExtendOp string + +const ( + ExtendUXTB ExtendOp = "UXTB" + ExtendUXTH ExtendOp = "UXTH" + ExtendUXTW ExtendOp = "UXTW" + ExtendUXTX ExtendOp = "UXTX" + ExtendSXTB ExtendOp = "SXTB" + ExtendSXTH ExtendOp = "SXTH" + ExtendSXTW ExtendOp = "SXTW" + ExtendSXTX ExtendOp = "SXTX" +) + // Operand models a minimal subset of Plan 9 asm operands. // // Supported: @@ -168,9 +185,10 @@ const ( type Operand struct { Kind OperandKind - Imm int64 // OpImm - ImmRaw string // OpImm unresolved symbolic placeholder, including leading '$' - Reg Reg // OpReg + Imm int64 // OpImm + ImmRaw string // OpImm unresolved symbolic placeholder, including leading '$' + Reg Reg // OpReg + Ext ExtendOp // OpRegExtend // OpRegShift ShiftOp ShiftOp ShiftAmount int64 @@ -210,6 +228,8 @@ func (o Operand) String() string { return fmt.Sprintf("$%d", o.Imm) case OpReg: return string(o.Reg) + case OpRegExtend: + return fmt.Sprintf("%s.%s", o.Reg, o.Ext) case OpRegShift: suffix := fmt.Sprintf("%d", o.ShiftAmount) if o.ShiftReg != "" { @@ -516,6 +536,9 @@ func parseOperand(s string) (Operand, error) { if name, off, ok := parseFPAddr(s); ok { return Operand{Kind: OpFPAddr, FPName: name, FPOffset: off}, nil } + if r, ext, ok := parseRegExtend(s); ok { + return Operand{Kind: OpRegExtend, Reg: r, Ext: ext}, nil + } if base, sop, amt, shiftReg, ok := parseRegShift(s); ok { return Operand{Kind: OpRegShift, Reg: base, ShiftOp: sop, ShiftAmount: amt, ShiftReg: shiftReg}, nil } @@ -578,6 +601,29 @@ func parseOperand(s string) (Operand, error) { return Operand{}, fmt.Errorf("unsupported operand: %q", s) } +func parseRegExtend(s string) (Reg, ExtendOp, bool) { + s = strings.TrimSpace(s) + if s == "" { + return "", "", false + } + dot := strings.LastIndexByte(s, '.') + if dot <= 0 || dot == len(s)-1 { + return "", "", false + } + r, ok := parseReg(s[:dot]) + if !ok { + return "", "", false + } + ext := ExtendOp(strings.ToUpper(strings.TrimSpace(s[dot+1:]))) + switch ext { + case ExtendUXTB, ExtendUXTH, ExtendUXTW, ExtendUXTX, + ExtendSXTB, ExtendSXTH, ExtendSXTW, ExtendSXTX: + return r, ext, true + default: + return "", "", false + } +} + func parseRegShift(s string) (base Reg, sop ShiftOp, amt int64, shiftReg Reg, ok bool) { s = strings.TrimSpace(s) if s == "" { diff --git a/types_deep_test.go b/types_deep_test.go index 10e32df..4b9c275 100644 --- a/types_deep_test.go +++ b/types_deep_test.go @@ -10,6 +10,7 @@ func TestTypeHelperCoverage(t *testing.T) { {Operand{Kind: OpImm, Imm: 7}, "$7"}, {Operand{Kind: OpImm, ImmRaw: "$(A+1)"}, "$(A+1)"}, {Operand{Kind: OpReg, Reg: AX}, "AX"}, + {Operand{Kind: OpRegExtend, Reg: "R2", Ext: ExtendUXTB}, "R2.UXTB"}, {Operand{Kind: OpRegShift, Reg: "R1", ShiftOp: ShiftLeft, ShiftAmount: 2}, "R1<<2"}, {Operand{Kind: OpRegShift, Reg: "R2", ShiftOp: ShiftRight, ShiftReg: "R3"}, "R2>>R3"}, {Operand{Kind: OpFP, FPName: "arg", FPOffset: 8}, "arg+8(FP)"}, @@ -203,12 +204,19 @@ func TestTypeParserEdgeCoverage(t *testing.T) { {"MIDR_EL1", OpIdent}, {"helper<>(SB)", OpSym}, {"R1@>2", OpRegShift}, + {"R2.UXTB", OpRegExtend}, } { op, err := parseOperand(tc.in) if err != nil || op.Kind != tc.want { t.Fatalf("parseOperand(%q) = (%v, %v), want kind %v", tc.in, err, op.Kind, tc.want) } } + if reg, ext, ok := parseRegExtend("r3.sxtw"); !ok || reg != "R3" || ext != ExtendSXTW { + t.Fatalf("parseRegExtend(r3.sxtw) = (%q, %q, %v)", reg, ext, ok) + } + if _, _, ok := parseRegExtend("R2.BAD"); ok { + t.Fatalf("parseRegExtend(R2.BAD) unexpectedly succeeded") + } if _, err := parseOperand("[]"); err == nil { t.Fatalf("parseOperand([]) unexpectedly succeeded") }