// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package lint import ( "testing" "sourcedock.dev/petrbalvin/gasm-sdk/arch" "sourcedock.dev/petrbalvin/gasm-sdk/parser" ) // TestVEXSSEMixing checks the encoding-mixing advisory: VEX and legacy SSE // in one kernel pay a transition penalty on every switch, so the finding is // informational, not a defect. func TestVEXSSEMixing(t *testing.T) { // GOROOT's memmove shape: VMOVDQU on Y registers beside MOVOU on X. diags := lintSrc(t, ` #include "textflag.h" TEXT ·copy(SB), NOSPLIT, $0 VMOVDQU (SI), Y4 MOVOU (SI), X0 RET `) if codes(diags)[CodeVEXSSEMixing] != 1 { t.Fatalf("want one vex-sse-mixing, got %+v", diags) } // One encoding throughout is the recommendation. diags = lintSrc(t, ` #include "textflag.h" TEXT ·copy(SB), NOSPLIT, $0 VMOVDQU (SI), Y4 VPSUBD Y1, Y2, Y3 RET `) if codes(diags)[CodeVEXSSEMixing] != 0 { t.Fatalf("pure VEX kernel must not be flagged: %+v", diags) } diags = lintSrc(t, ` #include "textflag.h" TEXT ·copy(SB), NOSPLIT, $0 MOVOU (SI), X0 PADDB X1, X2 RET `) if codes(diags)[CodeVEXSSEMixing] != 0 { t.Fatalf("pure legacy kernel must not be flagged: %+v", diags) } // VZEROUPPER is the boundary management, not a side of the mixing: the // idiomatic epilogue after legacy SSE stays clean. diags = lintSrc(t, ` #include "textflag.h" TEXT ·copy(SB), NOSPLIT, $0 MOVOU (SI), X0 PADDB X1, X2 VZEROUPPER RET `) if codes(diags)[CodeVEXSSEMixing] != 0 { t.Fatalf("VZEROUPPER epilogue must not be flagged: %+v", diags) } // A symbol whose name collides with the register spelling (the runtime's // internal∕cpu·X86 feature flags) is not a register. diags = lintSrc(t, ` #include "textflag.h" TEXT ·probe(SB), NOSPLIT, $0 CMPB internal∕cpu·X86+const_offsetX86HasERMS(SB), $1 VMOVDQU (SI), Y4 RET `) if codes(diags)[CodeVEXSSEMixing] != 0 { t.Fatalf("a symbol named X86 must not count as legacy SSE: %+v", diags) } // The rule is amd64-only: arm64 V registers are not SSE at all. diags = lintSrcArch(t, "f_arm64.s", ` #include "textflag.h" TEXT ·f(SB), NOSPLIT, $0 VLD1 (R0), [V1.B16] RET `) if codes(diags)[CodeVEXSSEMixing] != 0 { t.Fatalf("arm64 must not be flagged: %+v", diags) } // -disable silences the rule. f, _ := parser.Parse("t_amd64.s", "#include \"textflag.h\"\nTEXT ·copy(SB), NOSPLIT, $0\n\tVMOVDQU (SI), Y4\n\tMOVOU (SI), X0\n\tRET\n") diags = File(f, Config{Arch: arch.AMD64, Disable: map[string]bool{CodeVEXSSEMixing: true}}) if codes(diags)[CodeVEXSSEMixing] != 0 { t.Fatalf("disabled rule must stay silent: %+v", diags) } }