feat(arm64): wide immediates, SIMD compare and system operand forms
Assisted-by: GLM 5.3 Flash
This commit is contained in:
+164
-13
@@ -4,6 +4,7 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
||||
@@ -1295,20 +1296,170 @@ func TestArm64ExclNoOffset(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
// TestArm64AddSubImmWide pins the wide-immediate classification the toolchain
|
||||
// applies to the ADD/SUB family (asm7.go cases 48, 62, 13): the ADDCON2 split
|
||||
// into two imm12 instructions for plain ADD/SUB, the bitmask ORR into REGTMP,
|
||||
// and the MOVZ/MOVN/MOVK materialisations followed by the register form.
|
||||
// Comparisons never split, and the W forms classify the 32-bit value. Every
|
||||
// word is go tool asm's own for the same source.
|
||||
func TestArm64AddSubImmWide(t *testing.T) {
|
||||
got := arm64Words(t, strings.Join([]string{
|
||||
"\tADD $0xaaaaaa, R2, R3",
|
||||
"\tSUB $0xaaaaaa, R2",
|
||||
"\tADD $0x186a0, R2, R5",
|
||||
"\tADD $0x1ffe00, R2, R3",
|
||||
"\tADD $0x3fffffffc000, R5",
|
||||
"\tADD $-100000, R2, R3",
|
||||
"\tADD $-2048, R2, R3",
|
||||
"\tCMP $0xaaaaaa, R2",
|
||||
"\tCMP $0xffffffffffa0, R3",
|
||||
"\tCMPW $27745, R2",
|
||||
"\tCMPW $0x60060, R2",
|
||||
"\tADDS $0xaaaaaa, R2, R3",
|
||||
"\tADD $0x12345678, R2, R3",
|
||||
"\tADDW $0x60060, R2",
|
||||
"\tSUB $0xe7791f700, R3, R1",
|
||||
"\tADDW $0x12345678, R2, R3",
|
||||
"\tCMN $0x1000000, R2",
|
||||
}, "\n")+"\n")
|
||||
want := []uint32{
|
||||
0x912aa843, 0x916aa863, // ADD $0xaaaaaa, R2, R3: ADDCON2 split
|
||||
0xd12aa842, 0xd16aa842, // SUB $0xaaaaaa, R2: split with Rd = Rn
|
||||
0x911a8045, 0x914060a5, // ADD $0x186a0, R2, R5: split
|
||||
0xb2772ffb, 0x8b1b0043, // ADD $0x1ffe00: bitmask beats the split
|
||||
0xb2727ffb, 0x8b1b00a5, // ADD $0x3fffffffc000: bitmask into REGTMP
|
||||
0x9290d3fb, 0xf2bfffdb, 0x8b1b0043, // ADD $-100000: MOVN + MOVK
|
||||
0x9280fffb, 0x8b1b0043, // ADD $-2048: single MOVN + ADD
|
||||
0xd295555b, 0xf2a0155b, 0xeb1b005f, // CMP: never split, MOVZ + MOVK
|
||||
0x92800bfb, 0xf2e0001b, 0xeb1b007f, // CMP $0xffffffffffa0: MOVN + fixup
|
||||
0x528d8c3b, 0x6b1b005f, // CMPW $27745: W movcon, single MOVZW
|
||||
0x52800c1b, 0x72a000db, 0x6b1b005f, // CMPW $0x60060: S form skips the split
|
||||
0xd295555b, 0xf2a0155b, 0xab1b0043, // ADDS $0xaaaaaa: MOVZ + MOVK + ADDS
|
||||
0xd28acf1b, 0xf2a2469b, 0x8b1b0043, // ADD $0x12345678: MOVZ + MOVK
|
||||
0x11018042, 0x11418042, // ADDW $0x60060: W split
|
||||
0xd29ee01b, 0xf2aef23b, 0xf2c001db, 0xcb1b0061, // SUB $0xe7791f700
|
||||
0x528acf1b, 0x72a2469b, 0x0b1b0043, // ADDW $0x12345678: MOVZW + MOVKW
|
||||
0xd2a0201b, 0xab1b005f, // CMN $0x1000000: single MOVZ + CMN
|
||||
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("wide word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
if _, err := AssembleFileARM64(f); err == nil {
|
||||
t.Errorf("%s: expected an error, got none", body)
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64CarryImmWide pins the carry family's $0 spellings in two and
|
||||
// three operands, the ROR shift on the logical group (and its rejection for
|
||||
// the arithmetic forms), the NGC/MNEG zero-register aliases and the vector
|
||||
// alias with an element selector. Words are go tool asm's own.
|
||||
func TestArm64CarryShiftAlias(t *testing.T) {
|
||||
got := arm64Words(t, "\tADC $0, R20\n\tADC $0, R20, R4\n\tSBCS $0, R4, R12\n"+
|
||||
"\tSBCS R15, R4, R12\n\tANDW R9@>7, R19, R26\n\tAND R1@>33, R2, R3\n"+
|
||||
"\tNEGSW R23<<1, R30\n\tNGC R2, R7\n\tMNEG R14, R27, R23\n")
|
||||
want := []uint32{
|
||||
0x9a1f0294, // ADC ZR, R20, R20
|
||||
0x9a1f0284, // ADC ZR, R20, R4
|
||||
0xfa1f008c, // SBCS ZR, R4, R12
|
||||
0xfa0f008c, // SBCS R15, R4, R12
|
||||
0x0ac91e7a, // ANDW R9 ROR 7, R19, R26
|
||||
0x8ac18443, // AND R1 ROR 33, R2, R3
|
||||
0x6b1707fe, // SUBSW ZR, R30, R23 LSL 1
|
||||
0xda0203e7, // SBC ZR, R7, R2
|
||||
0x9b0eff77, // MSUB ZR, R27, R14, R23
|
||||
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("carry word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
|
||||
// ROR on an arithmetic form is unallocated: the toolchain reports an
|
||||
// unsupported shift operator.
|
||||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\tADD R1@>33, R2, R3\n\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
if _, err := AssembleFileARM64(f); err == nil {
|
||||
t.Error("ADD R1@>33: expected an error, got none")
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64VecAliasElement pins the register-alias rewrite inside a vector
|
||||
// operand with an element selector and inside a split register list: the
|
||||
// aliases resolve textually where the parser carries the selector apart from
|
||||
// the name. Words are go tool asm's own.
|
||||
func TestArm64VecAliasElement(t *testing.T) {
|
||||
src := `#include "textflag.h"
|
||||
|
||||
#define POLY V15
|
||||
#define ACC0 V8
|
||||
#define ACC1 V9
|
||||
|
||||
TEXT ·f(SB), NOSPLIT, $0-0
|
||||
VMOV R1, POLY.D[0]
|
||||
VEOR POLY.B16, POLY.B16, POLY.B16
|
||||
VLD1 (R0), [ACC0.B16]
|
||||
VLD1.P (R0), [ACC0.B16, ACC1.B16]
|
||||
VST1.P [ACC0.B16, ACC1.B16], 32(R1)
|
||||
RET
|
||||
`
|
||||
f, errs := parser.Parse("test_arm64.s", src)
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
img, err := AssembleFileARM64(f)
|
||||
if err != nil {
|
||||
t.Fatalf("AssembleFileARM64: %v", err)
|
||||
}
|
||||
got := leWords(img.Code)
|
||||
want := []uint32{
|
||||
0x4e081c2f, // INS V15.D[0], R1
|
||||
0x6e2f1def, // VEOR V15.B16, V15.B16, V15.B16
|
||||
0x4c407008, // VLD1 (R0), [V8.B16]
|
||||
0x4cdfa008, // VLD1.P (R0), [V8.B16, V9.B16]
|
||||
0x4c9fa028, // VST1.P [V8.B16, V9.B16], 32(R1)
|
||||
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("vecalias word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64AddSubImmBeyond32 pins the materialisation the toolchain applies
|
||||
// once the value leaves every imm12 form: a constant sequence into REGTMP
|
||||
// (R27) followed by the register form. SUB $-0x100000000 is a bitmask
|
||||
// immediate, so it rides the ORR form; the others take MOVZ. Words are go
|
||||
// tool asm's own.
|
||||
func TestArm64AddSubImmBeyond32(t *testing.T) {
|
||||
got := arm64Words(t, "\tADD $0x100000000, R0, R1\n\tSUB $-0x100000000, R0, R1\n\tCMP $0x100000000, R0\n")
|
||||
want := []uint32{
|
||||
0xd2c0003b, // MOVZ $(1<<32>>16), R27 (hw=2)
|
||||
0x8b1b0001, // ADD R27, R0, R1
|
||||
0xb2607ffb, // ORR $-4294967296, ZR, R27 (bitmask)
|
||||
0xcb1b0001, // SUB R27, R0, R1
|
||||
0xd2c0003b, // MOVZ $(1<<32>>16), R27 (hw=2)
|
||||
0xeb1b001f, // CMP R27, R0
|
||||
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("word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user