From 27ef71859b8c6fd83b0c4b177636e9a3dd5784c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Wed, 7 Oct 2026 19:34:02 +0200 Subject: [PATCH] feat(arch): add VMINMAXSH to the extension layer Assisted-by: GLM 5.3 Flash --- arch/amd64_ext.go | 9 +++++++++ arch/amd64_ext_test.go | 22 ++++++++++++++++++++-- asm/extension_amd64_test.go | 8 ++++++-- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/arch/amd64_ext.go b/arch/amd64_ext.go index f29f94e..9cf1c7c 100644 --- a/arch/amd64_ext.go +++ b/arch/amd64_ext.go @@ -1627,4 +1627,13 @@ var amd64Extensions = []ExtInstr{ {Name: "VMINMAXPH", Summary: "Return the per-lane minimum or maximum of packed FP16 values under an imm8 control", Bytes: []byte{0x62, 0x03, 0x04, 0x00, 0x52, 0xC0}, Form: ExtFormAmdVec3Imm, Mem: 3, Mask: true, Feature: ExtFeatureFP16, Ref: "Intel SDM Vol. 2C, VMINMAXPH (EVEX.NDS.128.0F3A.W0 52 /r /ib)"}, + + // The scalar mirror: VMINMAXSH picks between one pair of FP16 values + // under the same imm8 control, EVEX.NDS.LIG.0F3A.W0 53 /r /ib, the + // control byte riding last as the packed form's does. A scalar + // destination takes no write mask and the m16 memory source no + // broadcast, like the rest of the scalar core. + {Name: "VMINMAXSH", Summary: "Return the minimum or maximum of scalar FP16 values under an imm8 control", + Bytes: []byte{0x62, 0x03, 0x04, 0x00, 0x53, 0xC0}, Form: ExtFormAmdVec3Imm, Mem: 3, Feature: ExtFeatureFP16, + Ref: "Intel SDM Vol. 2C, VMINMAXSH (EVEX.NDS.LIG.0F3A.W0 53 /r /ib)"}, } diff --git a/arch/amd64_ext_test.go b/arch/amd64_ext_test.go index d2ac54a..bd18cda 100644 --- a/arch/amd64_ext_test.go +++ b/arch/amd64_ext_test.go @@ -1460,6 +1460,18 @@ var amd64GoldenRows = []amd64GoldenRow{ {"vminmaxph k7 zeroing", "VMINMAXPH", []ExtOperand{ExtImmediate(0x88), ExtZmm(29), ExtZmm(28), ExtWriteMasked(ExtZmm(30), 7, true)}, "620314c752f488", "62 03 14 c7 52 f4 88 vminmaxph $0x88,%zmm28,%zmm29,%zmm30{%k7}{z}"}, + + // The scalar mirror, EVEX.NDS.LIG.0F3A.W0 53 /r /ib: one control byte, + // one pair of half-precision values, the m16 memory source plain. + {"vminmaxsh", "VMINMAXSH", + []ExtOperand{ExtImmediate(0x88), ExtXmm(29), ExtXmm(28), ExtXmm(30)}, + "6203140053f488", "62 03 14 00 53 f4 88 vminmaxsh $0x88,%xmm28,%xmm29,%xmm30"}, + {"vminmaxsh memory source", "VMINMAXSH", + []ExtOperand{ExtImmediate(0x88), ExtXmm(28), ExtMemory(9, 0), ExtXmm(29)}, + "62431c00532988", "62 43 1c 00 53 29 88 vminmaxsh $0x88,(%r9),%xmm28,%xmm29"}, + {"vminmaxsh memory source disp8", "VMINMAXSH", + []ExtOperand{ExtImmediate(0x88), ExtXmm(28), ExtMemory(1, 127), ExtXmm(29)}, + "62631c0053697f88", "62 63 1c 00 53 69 7f 88 vminmaxsh $0x88,0xfe(%rcx),%xmm28,%xmm29 (Disp8(7f))"}, } // amd64ResolveEntry finds the table entry a golden row exercises: the entry @@ -1811,6 +1823,12 @@ func TestAmd64ExtRejects(t *testing.T) { {"rounding on the minimum-maximum", "VMINMAXPH", []ExtOperand{ExtImmediate(0x88), ExtZmm(5), ExtZmm(4), ExtRounded(ExtZmm(6), ExtRoundNearest)}, "the entry's destination takes none"}, + {"write mask on the scalar minimum-maximum", "VMINMAXSH", + []ExtOperand{ExtImmediate(0x88), ExtXmm(29), ExtXmm(28), ExtWriteMasked(ExtXmm(30), 7, true)}, + "the entry's destination takes none"}, + {"broadcast on the scalar minimum-maximum's memory source", "VMINMAXSH", + []ExtOperand{ExtImmediate(0x88), ExtXmm(29), ExtBroadcast(9, 0), ExtXmm(30)}, + "the entry's memory operand takes none"}, {"a register where the multiply-add reads memory", "VFMSUB231PH", []ExtOperand{ExtZmm(29), ExtYmm(4), ExtZmm(30)}, "wants a ZMM register"}, @@ -1961,7 +1979,7 @@ func TestAmd64ExtArchBinding(t *testing.T) { t.Errorf("Extensions(%s) carries %d instructions, want none", a, len(got)) } } - if got := Extensions(AMD64); len(got) != 182 { - t.Errorf("the amd64 layer registers %d instructions, want 182", len(got)) + if got := Extensions(AMD64); len(got) != 183 { + t.Errorf("the amd64 layer registers %d instructions, want 183", len(got)) } } diff --git a/asm/extension_amd64_test.go b/asm/extension_amd64_test.go index 81a8861..d6b914d 100644 --- a/asm/extension_amd64_test.go +++ b/asm/extension_amd64_test.go @@ -85,6 +85,7 @@ func TestAmd64ExtensionRegistry(t *testing.T) { {"VFMADDCSH", 1}, {"VFCMADDCSH", 1}, {"VMINMAXPH", 3}, + {"VMINMAXSH", 1}, } { cands, ok := LookupExtension(arch.AMD64, tt.mnem) if !ok { @@ -98,8 +99,8 @@ func TestAmd64ExtensionRegistry(t *testing.T) { t.Errorf("the %s lookup is not case-insensitive", tt.mnem) } } - if got := arch.Extensions(arch.AMD64); len(got) != 182 { - t.Errorf("the amd64 layer registers %d instructions, want 182", len(got)) + if got := arch.Extensions(arch.AMD64); len(got) != 183 { + t.Errorf("the amd64 layer registers %d instructions, want 183", len(got)) } if _, ok := LookupExtension(arch.AMD64, "NOSUCHINSTR"); ok { t.Error("a non-extended mnemonic resolved") @@ -217,6 +218,9 @@ func TestEncodeExtensionAmd64(t *testing.T) { {"minimum or maximum under a control byte", "VMINMAXPH", []arch.ExtOperand{arch.ExtImmediate(0x88), arch.ExtZmm(29), arch.ExtMemory(9, 0), arch.ExtZmm(30)}, "62431440523188"}, + {"scalar minimum or maximum out of memory", "VMINMAXSH", + []arch.ExtOperand{arch.ExtImmediate(0x88), arch.ExtXmm(28), arch.ExtMemory(9, 0), arch.ExtXmm(29)}, + "62431c00532988"}, } { got, err := EncodeExtension(arch.AMD64, tt.mnem, tt.ops...) if err != nil {