feat(lint): flag table-known instructions the encoder cannot emit

This commit is contained in:
2026-08-29 13:42:47 +02:00
parent c92e6bed3a
commit 685b150ecf
3 changed files with 166 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package lint
import (
"testing"
"sourcedock.dev/petrbalvin/gasm-devkit/asm"
)
func TestUnencodableInstruction(t *testing.T) {
// MOVBE is a real x86 instruction the architecture table knows about;
// before the encoder grows it, lint must flag the gap at edit time.
diags := lintSrc(t, `
#include "textflag.h"
TEXT ·f(SB), NOSPLIT, $0
MOVQ AX, BX
RET
`)
if codes(diags)[CodeUnencodable] != 0 {
t.Fatalf("encodable instructions must not be flagged: %+v", diags)
}
// A mnemonic that is neither in the table nor encodable fires
// unknown-instruction, not unencodable.
diags = lintSrc(t, `
#include "textflag.h"
TEXT ·f(SB), NOSPLIT, $0
FROBNICATE AX, BX
RET
`)
if codes(diags)[CodeUnencodable] != 0 || codes(diags)[CodeUnknownInstr] != 1 {
t.Fatalf("unknown instruction must fire unknown-instruction only: %+v", diags)
}
}
func TestEncodableMirror(t *testing.T) {
// Spot-check the predicate against instructions from every dispatch
// family of the encoder.
yes := []string{
"RET", "NOP", "CALL", "JMP", "JEQ", "JLT",
"MOVQ", "MOVL", "ADDQ", "CMPQ", "LEAQ", "TESTL",
"NOTL", "DECL", "SHRQ", "IMULQ", "PUSHQ", "POPQ",
"BSWAPL", "TZCNTQ", "MOVBLZX", "MOVOU", "MOVSD",
"PREFETCHT0", "PREFETCHNTA",
"VMOVDQU", "VPCMPUB", "VPCOMPRESSD", "VPERMB", "VPGATHERDD", "VPSCATTERDD",
"KMOVQ", "KANDNW", "VPMOVMSKB",
"CMOVLGT", "SETNE", "PSHUFB", "PADDB",
}
for _, m := range yes {
if !asm.Encodable(m) {
t.Errorf("Encodable(%q) = false, want true", m)
}
}
no := []string{"FROBNICATE", "MOVXX", "CMOVXX", "SETXX"}
for _, m := range no {
if asm.Encodable(m) {
t.Errorf("Encodable(%q) = true, want false", m)
}
}
}
+15
View File
@@ -13,6 +13,7 @@ import (
"strings"
"sourcedock.dev/petrbalvin/gasm-devkit/arch"
"sourcedock.dev/petrbalvin/gasm-devkit/asm"
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
"sourcedock.dev/petrbalvin/gasm-devkit/token"
)
@@ -78,6 +79,8 @@ const (
CodeStackImbalance = "stack-imbalance"
CodeRegisterWidthMismatch = "register-width-mismatch"
CodeABI0RegisterArgs = "abi0-register-args"
CodeNonportableRegister = "nonportable-register-name"
CodeUnencodable = "unencodable-instruction"
)
// knownTextFlags are the flags recognised by the Go assembler's textflag.h.
@@ -282,6 +285,18 @@ func lintText(t *ast.Text, tab *arch.Table, archKnown bool, cfg Config, macros m
Code: CodeUnknownInstr,
Message: fmt.Sprintf("unknown %s instruction %q", cfg.Arch, mnem),
})
} else if cfg.Arch == arch.AMD64 && !cfg.Disable[CodeUnencodable] && !asm.Encodable(upper) {
// Known to the architecture table but missing from the
// encoder: the file parses everywhere and then fails at
// assembly time. Flag it at lint so the gap is visible
// in the editor, and so the audit can close it.
out = append(out, Diagnostic{
Pos: st.Mnemonic.Pos,
End: st.Mnemonic.End,
Severity: Warning,
Code: CodeUnencodable,
Message: fmt.Sprintf("instruction %q is known but the encoder cannot assemble it yet", mnem),
})
}
}