From a9b54b6868bbd2a27e38615e4c0a7e1637f48183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Wed, 7 Oct 2026 00:43:06 +0200 Subject: [PATCH] fix(asm): carry the arm64 immediate to ZR through MOVZ Assisted-by: GLM 5.3 Flash --- asm/arm64_assemble.go | 7 +++++-- asm/arm64_encode_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/asm/arm64_assemble.go b/asm/arm64_assemble.go index 901c453..cf07e8e 100644 --- a/asm/arm64_assemble.go +++ b/asm/arm64_assemble.go @@ -1820,7 +1820,10 @@ func encodeARM64LoadImm(rd int, v int64, mnem string) ([]byte, error) { // `MOVD $4096, R27` is ORR $4096, not MOVZ $(1<<12) // - outside that band: MOVZ/MOVN first (C_MOVCON before C_BITCON), and // negative values reach MOVN before the bitmask test - tryBitmaskFirst := d > 0 && (d <= 0xFFF || (d&0xFFF == 0 && d <= 0xFFF000)) + // The bitmask path exists only for a real register: omovconst guards it + // with rt != REGZERO, so an immediate to ZR always rides the MOVZ/MOVN + // sequence carrying the value. + tryBitmaskFirst := rd != 31 && d > 0 && (d <= 0xFFF || (d&0xFFF == 0 && d <= 0xFFF000)) if tryBitmaskFirst { // Addcon-band immediate: try bitmask first (Go uses ORR for values @@ -1849,7 +1852,7 @@ func encodeARM64LoadImm(rd int, v int64, mnem string) ([]byte, error) { } // For values outside the bitmask-first range that are not movcon: try bitmask. - if !tryBitmaskFirst { + if !tryBitmaskFirst && rd != 31 { N, immr, imms, ok := arm64Bitmask(uint64(d), int(sf)) if ok { return a64wordLE(sf<<31 | 1<<29 | 0x24<<23 | N<<22 | immr<<16 | imms<<10 | 31<<5 | uint32(rd)), nil diff --git a/asm/arm64_encode_test.go b/asm/arm64_encode_test.go index 767486a..a48dfc7 100644 --- a/asm/arm64_encode_test.go +++ b/asm/arm64_encode_test.go @@ -1979,6 +1979,35 @@ func TestArm64SimdArrangementRejections(t *testing.T) { } } +// TestArm64MovImmZR pins the immediate-to-ZR spellings against `go tool asm` +// words: omovconst takes the bitmask path only for a real register, so an +// immediate to ZR rides the MOVZ/MOVN sequence carrying the value. +func TestArm64MovImmZR(t *testing.T) { + got := arm64Words(t, + "\tMOVW $1, ZR\n"+ + "\tMOVD $1, ZR\n"+ + "\tMOVD $0x123456789, ZR\n"+ + "\tMOVD $-1, ZR\n"+ + "\tMOVD $0, ZR\n") + want := []uint32{ + 0x5280003f, // MOVZ W31, #1 + 0xd280003f, // MOVZ X31, #1 + 0xd28cf13f, // MOVZ X31, #26505 + 0xf2a468bf, // MOVK $(9029<<16), X31 + 0xf2c0003f, // MOVK $(1<<32), X31 + 0x9280001f, // MOVN X31, #0 + 0xaa1f03ff, // ORR X31, XZR, XZR + 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]) + } + } +} // TestArm64ConRn pins the MOVD $con(Rn), Rd lowering against `go tool asm` // words: the single ADD/SUB inside the addcon band, the hi<<12 plus lo pair