From 459f4a2b6e44ca4908b72f151c10fc61dc5b0657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Thu, 20 Aug 2026 15:44:23 +0200 Subject: [PATCH] fix(test): add arm64 encoding tests for Go 1.26 coverage compatibility Assisted-by: MiMo V2.5 Pro --- asm/arm64_assemble.go | 6 ++ asm/arm64_encode_test.go | 193 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+) diff --git a/asm/arm64_assemble.go b/asm/arm64_assemble.go index c2e2bb6..294f3b9 100644 --- a/asm/arm64_assemble.go +++ b/asm/arm64_assemble.go @@ -214,7 +214,13 @@ func encodeARM64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi arm64 } // Register-register data processing. + // ASR/LSL/LSR/ROR with immediate operands use bitfield encoding (SBFM/UBFM). if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FDPSR { + isShift := mnem == "ASR" || mnem == "ASRW" || mnem == "LSL" || mnem == "LSLW" || + mnem == "LSR" || mnem == "LSRW" || mnem == "ROR" || mnem == "RORW" + if isShift && len(ops) >= 2 && isImmOperand(ops[0]) { + return encodeARM64Bitfield(mnem, enc.op, ops) + } return encodeARM64DPSR(mnem, enc.op, ops) } diff --git a/asm/arm64_encode_test.go b/asm/arm64_encode_test.go index bea4c6f..c463c4c 100644 --- a/asm/arm64_encode_test.go +++ b/asm/arm64_encode_test.go @@ -365,6 +365,199 @@ func TestArm64ResolvePseudo(t *testing.T) { } } +// TestArm64FPSel tests FP conditional select encoding. +func TestArm64FPSel(t *testing.T) { + src := `#include "textflag.h" +TEXT ·f(SB), NOSPLIT, $0-0 + FCSELD GE, F10, F11, F12 + 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) + } + // FCSELD should be 4 bytes + RET 4 bytes = 8 + if img.Funcs[0].Size != 8 { + t.Errorf("size: got %d, want 8", img.Funcs[0].Size) + } +} + +// TestArm64FPCvt tests FP conversion encoding. +func TestArm64FPCvt(t *testing.T) { + src := `#include "textflag.h" +TEXT ·f(SB), NOSPLIT, $0-0 + FCVTZSD F4, R0 + SCVTFD R4, F8 + 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) + } + if img.Funcs[0].Size != 12 { + t.Errorf("size: got %d, want 12", img.Funcs[0].Size) + } +} + +// TestArm64CSEL tests conditional select encoding. +func TestArm64CSEL(t *testing.T) { + src := `#include "textflag.h" +TEXT ·f(SB), NOSPLIT, $0-0 + CSEL EQ, R0, R1, R2 + CSET NE, R3 + CINC GE, R4, R5 + 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) + } + if img.Funcs[0].Size != 16 { + t.Errorf("size: got %d, want 16", img.Funcs[0].Size) + } +} + +// TestArm64CRC32 tests CRC32 encoding. +func TestArm64CRC32(t *testing.T) { + src := `#include "textflag.h" +TEXT ·f(SB), NOSPLIT, $0-0 + CRC32B R0, R2 + CRC32W R6, R8 + 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) + } + if img.Funcs[0].Size != 12 { + t.Errorf("size: got %d, want 12", img.Funcs[0].Size) + } +} + +// TestArm64Bitfield tests bitfield/shift encoding. +func TestArm64Bitfield(t *testing.T) { + src := `#include "textflag.h" +TEXT ·f(SB), NOSPLIT, $0-0 + ASR $4, R0, R1 + LSL $12, R4, R5 + EXTR $8, R0, R1, R2 + 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) + } + if img.Funcs[0].Size != 16 { + t.Errorf("size: got %d, want 16", img.Funcs[0].Size) + } +} + +// TestArm64SIMD tests SIMD encoding (via the instruction table). +func TestArm64SIMD(t *testing.T) { + // Verify SIMD instructions are in the table. + for _, mnem := range []string{"VADD", "VSUB", "VMUL"} { + if _, ok := a64InstrTable[mnem]; !ok { + t.Errorf("%s not in instruction table", mnem) + } + } +} + +// TestArm64LoadImm64 tests 64-bit immediate loading. +func TestArm64LoadImm64(t *testing.T) { + src := `#include "textflag.h" +TEXT ·f(SB), NOSPLIT, $0-0 + MOVD $0x123456789ABCDEF0, R0 + MOVD $0, R1 + MOVD $1, R2 + 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) + } + // $0x123456789ABCDEF0 needs 4 MOVZ/MOVK instructions (16 bytes) + // $0 is 1 instruction (4 bytes) + // $1 is 1 bitmask instruction (4 bytes) + // RET is 1 instruction (4 bytes) + if img.Funcs[0].Size != 28 { + t.Errorf("size: got %d, want 28", img.Funcs[0].Size) + } +} + +// TestArm64BranchCond tests conditional branch encoding. +func TestArm64BranchCond(t *testing.T) { + src := `#include "textflag.h" +TEXT ·f(SB), NOSPLIT, $0-0 + BEQ done + BNE done + BGE done + BLT done + ADD R4, R5 +done: + 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) + } + // 4 branches + 1 ADD + 1 RET = 24 bytes + if img.Funcs[0].Size != 24 { + t.Errorf("size: got %d, want 24", img.Funcs[0].Size) + } +} + +// TestArm64Errors tests error paths. +func TestArm64Errors(t *testing.T) { + tests := []struct { + name string + src string + }{ + {"bad mnemonic", "TEXT ·f(SB), NOSPLIT, $0-0\n\tINVALID\tR4\n\tRET\n"}, + {"bad label", "TEXT ·f(SB), NOSPLIT, $0-0\n\tB\tnosuch\n\tRET\n"}, + {"bad register", "TEXT ·f(SB), NOSPLIT, $0-0\n\tADD\tR99, R0\n\tRET\n"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, errs := parser.Parse("test_arm64.s", tt.src) + if len(errs) > 0 { + return + } + _, err := AssembleFileARM64(f) + if err == nil { + t.Error("expected error, got nil") + } + }) + } +} + // leWord reads a little-endian uint32 from b. func leWord(b []byte) uint32 { return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24