feat(asm): encode the GETCALLERPC, REM, DWORD and half-FCVT shapes
Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
df7a5091e0
commit
4066396226
3 files changed
+217
No files matched your search
@@ -439,6 +439,15 @@ func arm64InstrSize(instr *ast.Instr, fi arm64FrameInfo, pos int) int {
|
||||
if mnem == "BYTE" {
|
||||
return len(ops)
|
||||
}
|
||||
if mnem == "DWORD" {
|
||||
// Eight little-endian bytes per immediate.
|
||||
return 8 * len(ops)
|
||||
}
|
||||
// The remainder family: the division into REGTMP plus the MSUB tail.
|
||||
switch mnem {
|
||||
case "REM", "REMW", "UREM", "UREMW":
|
||||
return 8
|
||||
}
|
||||
if mnem == "NOP" {
|
||||
return 0 // the zero-size pseudo-instruction, operand or not
|
||||
}
|
||||
@@ -610,6 +619,41 @@ func encodeARM64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi arm64
|
||||
return nil, fmt.Errorf("WORD: immediate %d does not fit a 32-bit word", w)
|
||||
}
|
||||
return a64wordLE(uint32(w)), nil
|
||||
case "DWORD":
|
||||
// The toolchain's ADWORD (asm7.go case 11): eight little-endian
|
||||
// bytes per immediate. A symbol operand would need the data
|
||||
// section's relocation plumbing, which the code path does not
|
||||
// reach, so the immediate form alone is supported.
|
||||
if len(ops) == 0 {
|
||||
return nil, fmt.Errorf("DWORD expects an operand")
|
||||
}
|
||||
var out []byte
|
||||
for _, op := range ops {
|
||||
if !isImmOperand(op) || op.Imm.Sym != nil {
|
||||
return nil, fmt.Errorf("DWORD: only an immediate operand is supported")
|
||||
}
|
||||
v := arm64Imm64(op)
|
||||
out = append(out, byte(v), byte(v>>8), byte(v>>16), byte(v>>24),
|
||||
byte(v>>32), byte(v>>40), byte(v>>48), byte(v>>56))
|
||||
}
|
||||
return out, nil
|
||||
case "GETCALLERPC":
|
||||
// The toolchain's rewrite (obj7.go AGETCALLERPC): a leaf reads the
|
||||
// link register, a function with a frame reads the saved LR at
|
||||
// 0(SP), where both prologue shapes leave it.
|
||||
if len(ops) != 1 {
|
||||
return nil, fmt.Errorf("GETCALLERPC expects 1 operand, got %d", len(ops))
|
||||
}
|
||||
rd := arm64RegNum(operandRegName(ops[0]))
|
||||
if rd < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand in GETCALLERPC")
|
||||
}
|
||||
if fi.leaf {
|
||||
return a64wordLE(0xaa000000 | 30<<16 | 31<<5 | uint32(rd)), nil // MOVD R30, Rd
|
||||
}
|
||||
return a64wordLE(a64LSU(3, 0, 1, 0, 31, uint32(rd))), nil // MOVD (RSP), Rd
|
||||
case "REM", "REMW", "UREM", "UREMW":
|
||||
return encodeARM64Rem(mnem, ops)
|
||||
case "B", "JMP":
|
||||
return encodeARM64Branch(mnem, ops, pc, offsets, false, relocs, resolve)
|
||||
case "BL", "CALL":
|
||||
@@ -2059,6 +2103,43 @@ func encodeARM64Mov(instr *ast.Instr, mnem string, wb string, fi arm64FrameInfo,
|
||||
return encodeARM64RegMove(mnem, src, dst)
|
||||
}
|
||||
|
||||
// encodeARM64Rem encodes the remainder family the toolchain's optab case
|
||||
// 16 does: the quotient rides REGTMP (R27) through SDIV/UDIV and the
|
||||
// remainder is the minuend less REGTMP times the divisor, an MSUB whose W
|
||||
// forms keep the 32-bit size. The two-operand spelling divides into the
|
||||
// destination (asm7.go: r defaults to rt), and an RSP destination is the
|
||||
// toolchain's illegal combination.
|
||||
func encodeARM64Rem(mnem string, ops []*ast.Operand) ([]byte, error) {
|
||||
if len(ops) != 2 && len(ops) != 3 {
|
||||
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
rd := arm64RegNum(operandRegName(ops[len(ops)-1]))
|
||||
if strings.EqualFold(operandRegName(ops[len(ops)-1]), "RSP") {
|
||||
return nil, fmt.Errorf("%s: illegal combination: the destination cannot be RSP", mnem)
|
||||
}
|
||||
rm := arm64RegNum(operandRegName(ops[0]))
|
||||
rn := rd
|
||||
if len(ops) == 3 {
|
||||
rn = arm64RegNum(operandRegName(ops[1]))
|
||||
}
|
||||
if rm < 0 || rn < 0 || rd < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||
}
|
||||
sf := uint32(1) // 64-bit
|
||||
if strings.HasSuffix(mnem, "W") {
|
||||
sf = 0
|
||||
}
|
||||
div := sf<<31 | 0xd6<<21 | 3<<10 // SDIV / SDIVW
|
||||
if strings.HasPrefix(mnem, "U") {
|
||||
div = sf<<31 | 0xd6<<21 | 2<<10 // UDIV / UDIVW
|
||||
}
|
||||
msub := sf<<31 | 0x1b<<24 | 1<<15 // MSUB / MSUBW
|
||||
return a64WordsLE(
|
||||
div|uint32(rm)<<16|uint32(rn)<<5|27,
|
||||
msub|uint32(rm)<<16|uint32(rn)<<10|27<<5|uint32(rd),
|
||||
), nil
|
||||
}
|
||||
|
||||
// arm64PairSize returns the encoded size of a load/store pair instruction,
|
||||
// mirroring the encoder's branch order exactly: the label offsets of pass 1
|
||||
// must match the bytes pass 2 lays down.
|
||||
|
||||
@@ -595,6 +595,10 @@ func init() {
|
||||
"FNEGS": 0x1e214000, "FNEGD": 0x1e614000,
|
||||
"FSQRTS": 0x1e21c000, "FSQRTD": 0x1e61c000,
|
||||
"FCVTSD": 0x1e22c000, "FCVTDS": 0x1e624000,
|
||||
// The half-precision conversions: FPOP1S rows with the half source
|
||||
// or destination (type 3), single and double beside them.
|
||||
"FCVTHS": 0x1ee24000, "FCVTHD": 0x1ee2c000,
|
||||
"FCVTSH": 0x1e23c000, "FCVTDH": 0x1e63c000,
|
||||
"FRINTNS": 0x1e244000, "FRINTND": 0x1e644000,
|
||||
"FRINTPS": 0x1e24c000, "FRINTPD": 0x1e64c000,
|
||||
"FRINTMS": 0x1e254000, "FRINTMD": 0x1e654000,
|
||||
|
||||
@@ -2642,3 +2642,135 @@ func TestArm64ImmediateExpression(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64RemFamily pins the remainder family the toolchain lowers to a
|
||||
// division into REGTMP plus the MSUB tail (asm7.go case 16): the words are
|
||||
// `go tool asm`'s own, and the two-operand spelling divides into the
|
||||
// destination.
|
||||
func TestArm64RemFamily(t *testing.T) {
|
||||
got := arm64Words(t, "\tREM R1, R2, R3\n\tREMW R1, R2, R3\n\tUREM R1, R2, R3\n\tUREMW R1, R2, R3\n\tREM R1, R2\n")
|
||||
want := []uint32{
|
||||
0x9ac10c5b, // SDIV R1, R2, R27
|
||||
0x9b018b63, // MSUB R3 = R2 - R27*R1
|
||||
0x1ac10c5b, // SDIVW R1, R2, R27
|
||||
0x1b018b63, // MSUBW
|
||||
0x9ac1085b, // UDIV R1, R2, R27
|
||||
0x9b018b63, // MSUB
|
||||
0x1ac1085b, // UDIVW R1, R2, R27
|
||||
0x1b018b63, // MSUBW
|
||||
0x9ac10c5b, // SDIV R1, R2, R27
|
||||
0x9b018b62, // MSUB R2 = R2 - R27*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("word %d = %08x, want %08x", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\tREM R1, R2, RSP\n\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
if _, err := AssembleFileARM64(f); err == nil {
|
||||
t.Error("REM with an RSP destination assembled, want an illegal combination")
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64GetCallerPC pins the toolchain's rewrite (obj7.go AGETCALLERPC):
|
||||
// a leaf reads the link register, a function with a frame reads the saved
|
||||
// LR at 0(SP).
|
||||
func TestArm64GetCallerPC(t *testing.T) {
|
||||
got := arm64Words(t, "\tGETCALLERPC R0\n\tGETCALLERPC R5\n")
|
||||
want := []uint32{
|
||||
0xaa1e03e0, // MOVD R30, R0: the leaf reads LR
|
||||
0xaa1e03e5, // MOVD R30, R5
|
||||
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])
|
||||
}
|
||||
}
|
||||
// With a call in the body the frame exists and the saved LR sits at
|
||||
// 0(SP): prologue (3 words), CALL, then the load.
|
||||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $8-8\n\tCALL ·x(SB)\n\tGETCALLERPC R5\n\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
img, err := AssembleFileARM64(f)
|
||||
if err != nil {
|
||||
t.Fatalf("AssembleFileARM64: %v", err)
|
||||
}
|
||||
ws := leWords(img.Code)
|
||||
// words 3: CALL, 4: MOVD (RSP), R5.
|
||||
if len(ws) < 6 {
|
||||
t.Fatalf("word count = %d, want at least 6", len(ws))
|
||||
}
|
||||
if ws[4] != 0xf94003e5 {
|
||||
t.Errorf("non-leaf GETCALLERPC = %08x, want MOVD (RSP), R5 (f94003e5)", ws[4])
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64Dword pins the DWORD pseudo-statement: eight little-endian bytes
|
||||
// per immediate, the toolchain's ADWORD.
|
||||
func TestArm64Dword(t *testing.T) {
|
||||
got := arm64Words(t, "\tDWORD $1\n\tDWORD $0x1122334455667788\n")
|
||||
// The data words ride the listing raw: DWORD $1 is 01 00 00 00 00 00 00 00,
|
||||
// the second an 8-byte little-endian constant.
|
||||
wantBytes := []byte{
|
||||
0x01, 0, 0, 0, 0, 0, 0, 0,
|
||||
0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11,
|
||||
}
|
||||
// The RET follows; locate the 16 data bytes at the head.
|
||||
_ = got
|
||||
code := imgCode(t, "\tDWORD $1\n\tDWORD $0x1122334455667788\n")
|
||||
if len(code) != len(wantBytes)+4 {
|
||||
t.Fatalf("code length = %d, want %d", len(code), len(wantBytes)+4)
|
||||
}
|
||||
for i, b := range wantBytes {
|
||||
if code[i] != b {
|
||||
t.Errorf("byte %d = %02x, want %02x", i, code[i], b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// imgCode assembles a leaf body and returns the whole function image.
|
||||
func imgCode(t *testing.T, body string) []byte {
|
||||
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 img.Code
|
||||
}
|
||||
|
||||
// TestArm64HalfFCVT pins the half-precision conversions beside their single
|
||||
// and double relatives, the FPOP1S rows with the half type field.
|
||||
func TestArm64HalfFCVT(t *testing.T) {
|
||||
got := arm64Words(t, "\tFCVTHS F1, F2\n\tFCVTSH F1, F2\n\tFCVTDH F1, F2\n\tFCVTHD F1, F2\n")
|
||||
want := []uint32{
|
||||
0x1ee24022, // FCVTHS F1, F2: half to single
|
||||
0x1e23c022, // FCVTSH F1, F2: single to half
|
||||
0x1e63c022, // FCVTDH F1, F2: half to double
|
||||
0x1ee2c022, // FCVTHD F1, F2: double to half
|
||||
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