fix(arm64): encode shifts, divides and multiplies and align sizes with emission
Assisted-by: GLM 5.3
This commit is contained in:
@@ -611,3 +611,312 @@ TEXT ·f(SB), NOSPLIT, $0-0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// arm64Words assembles a single NOSPLIT leaf body and returns its words.
|
||||
func arm64Words(t *testing.T, body string) []uint32 {
|
||||
t.Helper()
|
||||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+body+"\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
img, err := AssembleFileARM64(f)
|
||||
if err != nil {
|
||||
t.Fatalf("AssembleFileARM64: %v", err)
|
||||
}
|
||||
return leWords(img.Code)
|
||||
}
|
||||
|
||||
// TestArm64ShiftEncodings pins the shift words against `go tool asm -S`
|
||||
// output (Go 1.27, arm64): immediate forms alias SBFM/UBFM with ROR as EXTR,
|
||||
// register forms are the two-source LSLV/LSRV/ASRV/RORV.
|
||||
func TestArm64ShiftEncodings(t *testing.T) {
|
||||
got := arm64Words(t, "\tLSL $4, R0, R1\n\tLSR $8, R0, R2\n\tASR $4, R0, R3\n\tROR $12, R0, R4\n"+
|
||||
"\tLSLW $4, R0, R5\n\tLSRW $8, R0, R6\n\tASRW $4, R0, R7\n\tRORW $12, R0, R8\n")
|
||||
want := []uint32{
|
||||
0xd37cec01, // LSL $4 = UBFM X1, X0, #60, #59
|
||||
0xd348fc02, // LSR $8 = UBFM X2, X0, #8, #63
|
||||
0x9344fc03, // ASR $4 = SBFM X3, X0, #4, #63
|
||||
0x93c03004, // ROR $12 = EXTR X4, X0, X0, #12
|
||||
0x531c6c05, // LSLW $4 = UBFM W5, W0, #28, #27
|
||||
0x53087c06, // LSRW $8 = UBFM W6, W0, #8, #31
|
||||
0x13047c07, // ASRW $4 = SBFM W7, W0, #4, #31
|
||||
0x13803008, // RORW $12 = EXTR W8, W0, W0, #12
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("imm shift word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
|
||||
got = arm64Words(t, "\tLSL R9, R0, R10\n\tLSR R9, R0, R11\n\tASR R9, R0, R12\n\tROR R9, R0, R13\n"+
|
||||
"\tLSLW R9, R0, R14\n\tLSRW R9, R0, R15\n\tASRW R9, R0, R16\n\tRORW R9, R0, R17\n")
|
||||
want = []uint32{
|
||||
0x9ac9200a, // LSLV X10, X0, X9
|
||||
0x9ac9240b, // LSRV X11, X0, X9
|
||||
0x9ac9280c, // ASRV X12, X0, X9
|
||||
0x9ac92c0d, // RORV X13, X0, X9
|
||||
0x1ac9200e, // LSLV W14, W0, W9
|
||||
0x1ac9240f, // LSRV W15, W0, W9
|
||||
0x1ac92810, // ASRV W16, W0, W9
|
||||
0x1ac92c11, // RORV W17, W0, W9
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("reg shift word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Two-operand spellings fold to Rn = Rd.
|
||||
got = arm64Words(t, "\tLSL $4, R1\n\tLSR R9, R1\n\tASR $4, R1\n\tROR R9, R1\n\tLSLW $4, R1\n\tRORW R9, R1\n")
|
||||
want = []uint32{
|
||||
0xd37cec21, // LSL $4, R1 = UBFM X1, X1, #60, #59
|
||||
0x9ac92421, // LSRV X1, X1, X9
|
||||
0x9344fc21, // ASR $4, R1 = SBFM X1, X1, #4, #63
|
||||
0x9ac92c21, // RORV X1, X1, X9
|
||||
0x531c6c21, // LSLW $4, R1 = UBFM W1, W1, #28, #27
|
||||
0x1ac92c21, // RORV W1, W1, W9
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("2op shift word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64ShiftRangeErrors: the toolchain reports "illegal bit number" for
|
||||
// shift amounts at or above the operand width.
|
||||
func TestArm64ShiftRangeErrors(t *testing.T) {
|
||||
for _, src := range []string{
|
||||
"\tLSL $64, R0, R1\n",
|
||||
"\tLSRW $32, R0, R1\n",
|
||||
"\tRORW $32, R0, R1\n",
|
||||
"\tASR $-1, R0, R1\n",
|
||||
} {
|
||||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+src+"\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
if _, err := AssembleFileARM64(f); err == nil {
|
||||
t.Errorf("%s: expected an error, got none", src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64DivEncodings pins SDIV/UDIV in both widths: the 2-source opcode
|
||||
// field (bits 15:10 of the 0xd6<<21 fixed field) is UDIV=0b0010, SDIV=0b0011.
|
||||
func TestArm64DivEncodings(t *testing.T) {
|
||||
got := arm64Words(t, "\tSDIV R1, R2, R3\n\tUDIV R1, R2, R3\n\tSDIVW R1, R2, R3\n\tUDIVW R1, R2, R3\n")
|
||||
want := []uint32{
|
||||
0x9ac10c43, // SDIV X3, X2, X1
|
||||
0x9ac10843, // UDIV X3, X2, X1
|
||||
0x1ac10c43, // SDIV W3, W2, W1
|
||||
0x1ac10843, // UDIV W3, W2, W1
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("div word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64MAddSub pins the four-operand MADD/MSUB words (Rm, Ra, Rn, Rd,
|
||||
// with Ra in bits 14:10) and rejects the shorter spellings the toolchain
|
||||
// also rejects.
|
||||
func TestArm64MAddSub(t *testing.T) {
|
||||
got := arm64Words(t, "\tMADD R1, R2, R3, R4\n\tMSUB R1, R2, R3, R4\n\tMADDW R1, R2, R3, R5\n\tMSUBW R1, R2, R3, R5\n")
|
||||
want := []uint32{
|
||||
0x9b010864, // MADD X4, X3, X1, X2 (Rm=1, Ra=2, Rn=3)
|
||||
0x9b018864, // MSUB X4, X3, X1, X2
|
||||
0x1b010865, // MADD W5, W3, W1, W2
|
||||
0x1b018865, // MSUB W5, W3, W1, W2
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("madd word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
|
||||
// The accumulate operand is mandatory: 2- and 3-operand forms error
|
||||
// rather than silently reading R0 or ZR as the accumulator.
|
||||
for _, body := range []string{
|
||||
"\tMADD R1, R2\n",
|
||||
"\tMADD R1, R2, R3\n",
|
||||
"\tMSUBW R1, R2, R3\n",
|
||||
} {
|
||||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+body+"\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
if _, err := AssembleFileARM64(f); err == nil {
|
||||
t.Errorf("%s: expected an error, got none", body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64MovImmWidth pins the immediate classifications whose size pass
|
||||
// once disagreed with the encoder: negative and 0xFFFFFFFF W values go
|
||||
// through MOVN after 32-bit truncation, and 3- to 4-chunk constants expand
|
||||
// to one word per non-zero chunk.
|
||||
func TestArm64MovImmWidth(t *testing.T) {
|
||||
got := arm64Words(t, "\tMOVW $-1, R0\n\tMOVW $0xFFFFFFFF, R3\n")
|
||||
want := []uint32{
|
||||
0x12800000, // MOVN W0, #0
|
||||
0x12800003, // MOVN W3, #0
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("movw word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
|
||||
for _, tt := range []struct {
|
||||
body string
|
||||
words int
|
||||
}{
|
||||
{"\tMOVD $0x0001000200030000, R2\n", 3}, // three chunks
|
||||
{"\tMOVD $0x0001000200030004, R1\n", 4}, // four chunks
|
||||
{"\tMOVW $-1, R0\n", 1}, // MOVN after truncation
|
||||
} {
|
||||
if got := arm64Words(t, tt.body); len(got) != tt.words+1 {
|
||||
t.Errorf("%s: %d words, want %d (including RET)", tt.body, len(got), tt.words+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64ExclOffsetErrors: exclusive and atomic encodings carry no
|
||||
// immediate field, so a non-zero offset is rejected the way the toolchain
|
||||
// reports "illegal combination" for it, never silently dropped.
|
||||
func TestArm64ExclOffsetErrors(t *testing.T) {
|
||||
for _, body := range []string{
|
||||
"\tLDXR 8(R1), R2\n",
|
||||
"\tLDAXR 8(R1), R2\n",
|
||||
"\tSTXR R3, 8(R1), R4\n",
|
||||
"\tSTLXR R3, 8(R1), R4\n",
|
||||
"\tCASD R3, 8(R1), R4\n",
|
||||
"\tLDADDD R3, 8(R1), R4\n",
|
||||
} {
|
||||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+body+"\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
if _, err := AssembleFileARM64(f); err == nil {
|
||||
t.Errorf("%s: expected an error, got none", body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64ExclNoOffset pins the plain (Rn) forms. gasm parses the store
|
||||
// with the status register first (ARM ARM order); go tool asm parses the
|
||||
// same text with the data register first, so the two spellings differ and
|
||||
// the store word below is gasm's own.
|
||||
func TestArm64ExclNoOffset(t *testing.T) {
|
||||
got := arm64Words(t, "\tLDXR (R1), R2\n\tSTXR R3, (R1), R4\n")
|
||||
want := []uint32{
|
||||
0xc85f7c22, // LDXR X2, [X1]
|
||||
0xc8037c24, // STXR W3, X4, [X1] with Rs = R3, Rt = R4
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("excl word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64AddSubImmRange: immediates that cannot ride the imm12 field are
|
||||
// rejected instead of wrapping through int32.
|
||||
func TestArm64AddSubImmRange(t *testing.T) {
|
||||
for _, body := range []string{
|
||||
"\tADD $0x100000000, R0, R1\n",
|
||||
"\tSUB $-0x100000000, R0, R1\n",
|
||||
"\tCMP $0x100000000, R0\n",
|
||||
} {
|
||||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+body+"\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
if _, err := AssembleFileARM64(f); err == nil {
|
||||
t.Errorf("%s: expected an error, got none", body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64LargeRegisterOffset pins the large-offset path for a register
|
||||
// base: the ADD offsets from the operand's own base, not from SP, matching
|
||||
// the toolchain's `ADD $(256<<12), R2, R27; MOVD (R27), R3`.
|
||||
func TestArm64LargeRegisterOffset(t *testing.T) {
|
||||
got := arm64Words(t, "\tMOVD 0x100000(R2), R3\n\tMOVD R3, 0x100000(R2)\n")
|
||||
want := []uint32{
|
||||
0x9144005b, // ADD $(256<<12), R2, R27
|
||||
0xf9400363, // MOVD (R27), R3
|
||||
0x9144005b, // ADD $(256<<12), R2, R27
|
||||
0xf9000363, // MOVD R3, (R27)
|
||||
0xd65f03c0, // RET
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("large offset word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64LargeFrameSpadj checks the stack-adjustment boundaries of a frame
|
||||
// whose autosize must be materialised into REGTMP: $5000 rounds the autosize
|
||||
// to 5024, so the prologue is [MOVD $5024, R27][SUB R27, RSP, R20][STP][ADD
|
||||
// R20, SP][SUB $8] and SP moves only at its fourth word, while the RET's
|
||||
// epilogue is [LDP][MOVD $5024, R27][ADD R27, RSP, RSP] before the final
|
||||
// RET. These PCs feed the DWARF CFA rules and the goobj stack maps.
|
||||
func TestArm64LargeFrameSpadj(t *testing.T) {
|
||||
f, errs := parser.Parse("frame_arm64.s", "#include \"textflag.h\"\n\nTEXT ·framed(SB), $5000-0\n\tCALL ·other(SB)\n\tRET\n\nTEXT ·other(SB), NOSPLIT, $0\n\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
img, err := AssembleFileARM64(f)
|
||||
if err != nil {
|
||||
t.Fatalf("AssembleFileARM64: %v", err)
|
||||
}
|
||||
fn := img.Funcs[0]
|
||||
// autosize 5024: class-2 guard of 6 words (24 bytes), a 5-word prologue
|
||||
// whose ADD R20, SP sits at byte 8 inside it, a one-instruction body,
|
||||
// then a 3-word epilogue before the final RET.
|
||||
wantSpadj := []SpadjStep{{PC: 24 + 12, Value: 5024}, {PC: 24 + 20 + 4 + 12, Value: 0}}
|
||||
if len(fn.Spadj) != len(wantSpadj) {
|
||||
t.Fatalf("spadj = %v, want %v", fn.Spadj, wantSpadj)
|
||||
}
|
||||
for i := range wantSpadj {
|
||||
if fn.Spadj[i] != wantSpadj[i] {
|
||||
t.Errorf("spadj[%d] = %v, want %v", i, fn.Spadj[i], wantSpadj[i])
|
||||
}
|
||||
}
|
||||
// The words those PCs point between: the prologue's ADD R20, SP at byte
|
||||
// 36, and the epilogue's materialised ADD R27, RSP, RSP right before the
|
||||
// final RET at byte 60.
|
||||
words := leWords(img.Code[fn.Offset : fn.Offset+fn.Size])
|
||||
if got := words[(24+12)/4]; got != 0x9100029f {
|
||||
t.Errorf("prologue word at byte 36 = %08x, want 9100029f (ADD R20, SP)", got)
|
||||
}
|
||||
if got := words[(24+20+4+8)/4]; got != 0x8b3b63ff {
|
||||
t.Errorf("epilogue word at byte 56 = %08x, want 8b3b63ff (ADD R27, RSP, RSP)", got)
|
||||
}
|
||||
if got := words[(24+20+4+12)/4]; got != 0xd65f03c0 {
|
||||
t.Errorf("final RET word at byte 60 = %08x, want d65f03c0", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user