fix(asm): read the arm64 move-wide immediate as an unsigned pattern

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-23 21:03:03 +02:00
parent 7b11c62f53
commit 74d6b90d69
3 changed files with 54 additions and 14 deletions
+5
View File
@@ -33,6 +33,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
resolved to nothing: only the forward forms counted. A negative count
now walks the same instruction statements backwards, labels excluded,
byte-identical with the toolchain.
- **The arm64 move-wide family reads its immediate as an unsigned
pattern.** `MOVK $(40000<<48)` folds to a negative int64 and was
rejected; the toolchain picks the 16-bit lane from the 64-bit bit
pattern, so the encoder now does the same, and a zero immediate is
rejected where the toolchain rejects it.
## [0.35.0] - 2026-09-22
+14 -14
View File
@@ -3069,29 +3069,29 @@ func encodeARM64MoveWide(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte
// base, so MOVZ and MOVN come along for free.
opc := baseOp >> 29 & 3
sf := baseOp >> 31 & 1
v := arm64Imm64(ops[0])
if v < 0 {
return nil, fmt.Errorf("%s: negative immediate %d", mnem, v)
// The toolchain's optab case 33, shared by the whole family in both
// widths: the immediate is one unsigned 64-bit pattern (a high-lane
// constant such as $(40000<<48) arrives negative through int64
// folding), it must occupy exactly one 16-bit lane, zero is rejected,
// and the W forms cannot reach the top half.
u := uint64(arm64Imm64(ops[0]))
if u == 0 {
return nil, fmt.Errorf("%s: zero immediate cannot be handled", mnem)
}
hw := -1
for i := range 4 {
if v>>(uint(i)*16)&0xFFFF != 0 {
hw = i
for lane := range 4 {
if u&^(uint64(0xFFFF)<<(lane*16)) == 0 {
hw = lane
break
}
}
if hw < 0 {
hw = 0 // zero: every chunk is zero, hw = 0 carries it
}
for i := hw + 1; i < 4; i++ {
if v>>(uint(i)*16)&0xFFFF != 0 {
return nil, fmt.Errorf("%s: immediate %d does not fit one 16-bit chunk", mnem, v)
}
return nil, fmt.Errorf("%s: immediate %#x does not fit one 16-bit chunk", mnem, u)
}
if sf == 0 && hw > 1 {
return nil, fmt.Errorf("%s: immediate %d out of range for the 32-bit form", mnem, v)
return nil, fmt.Errorf("%s: immediate %#x out of range for the 32-bit form", mnem, u)
}
return a64wordLE(a64MoveWide(sf, opc, uint32(hw), uint32(v>>uint(hw*16)&0xFFFF), uint32(rd))), nil
return a64wordLE(a64MoveWide(sf, opc, uint32(hw), uint32(u>>uint(hw*16)&0xFFFF), uint32(rd))), nil
}
// ---- Bitfield/EXTR encoding ----
+35
View File
@@ -1051,6 +1051,41 @@ func TestArm64MOVK(t *testing.T) {
}
}
// TestArm64MOVKHighLane pins the shifted high-lane immediate the arm64 test
// kernels write: $(40000<<48) folds to a negative int64, and the toolchain
// reads the value as an unsigned 64-bit pattern when it picks the lane.
func TestArm64MOVKHighLane(t *testing.T) {
got := arm64Words(t, "\tMOVK $(40000<<48), R0\n\tMOVK $0x9c40000000000000, R1\n")
want := []uint32{
0xf2f38800, // MOVK $(40000<<48), R0 (go tool asm: f2f38800)
0xf2f38801, // MOVK hw=3
0xd65f03c0,
}
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])
}
}
}
// TestArm64MoveWideZeroImmediate pins the toolchain's rejection of a zero
// immediate in the move-wide family (optab case 33: "zero shifts cannot be
// handled"): every lane is zero, so no hw field can carry it.
func TestArm64MoveWideZeroImmediate(t *testing.T) {
for _, mnem := range []string{"MOVK", "MOVZ", "MOVN"} {
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\t"+mnem+" $0, R0\n\tRET\n")
if len(errs) > 0 {
t.Fatalf("%s: parse: %v", mnem, errs)
}
if _, err := AssembleFileARM64(f); err == nil {
t.Errorf("%s $0: expected error, got nil", mnem)
}
}
}
// TestArm64LoadImm64 tests 64-bit immediate loading.
func TestArm64LoadImm64(t *testing.T) {
src := `#include "textflag.h"