2026-08-29 13:42:47 +02:00
|
|
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
package asm
|
|
|
|
|
|
|
|
|
|
import "strings"
|
|
|
|
|
|
|
|
|
|
// Encodable reports whether the amd64 encoder knows how to encode the
|
|
|
|
|
// mnemonic. It mirrors the dispatch in (*enc).encode: the fixed-name
|
|
|
|
|
// instructions, conditional jumps, the CMOV/SET condition families, the
|
|
|
|
|
// VEX/EVEX/opmask/gather/scatter vector paths, the legacy SSE tables and the
|
|
|
|
|
// explicit scalar cases. A mnemonic that parses (is in the architecture
|
|
|
|
|
// table) but is not encodable would otherwise surface only at assembly time,
|
|
|
|
|
// deep inside a build; the linter uses this predicate to flag it at edit
|
|
|
|
|
// time.
|
|
|
|
|
func Encodable(mnemonic string) bool {
|
|
|
|
|
upper := strings.ToUpper(mnemonic)
|
|
|
|
|
|
|
|
|
|
// Fixed-name instructions (no size suffix).
|
|
|
|
|
switch upper {
|
|
|
|
|
case "RET", "NOP", "CALL", "JMP":
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if _, ok := condCode(upper); ok {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// VEX/EVEX and friends: the trailing B/W/L/Q/D is part of the mnemonic.
|
|
|
|
|
base, _, err := parseEvexSuffix(upper)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if isVex(base) || isEvex(base) || isKOp(base) || isGather(base) || isScatter(base) ||
|
|
|
|
|
base == "KMOVW" || base == "KMOVQ" {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CMOV carries size then condition (CMOVLGT); SET carries the condition
|
2026-09-19 23:49:07 +02:00
|
|
|
// alone (SETNE). The size letter is checked exactly as encodeCmov does,
|
|
|
|
|
// so a spelling like CMOVBGT is not reported encodable when Encode
|
|
|
|
|
// would reject it.
|
2026-08-29 13:42:47 +02:00
|
|
|
if rest, ok := strings.CutPrefix(upper, "CMOV"); ok && len(rest) >= 2 {
|
2026-09-19 23:49:07 +02:00
|
|
|
switch rest[0] {
|
|
|
|
|
case 'W', 'L', 'Q':
|
|
|
|
|
if _, ok := jccMap[rest[1:]]; ok {
|
|
|
|
|
return true
|
|
|
|
|
}
|
2026-08-29 13:42:47 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if rest, ok := strings.CutPrefix(upper, "SET"); ok {
|
|
|
|
|
if _, ok := jccMap[rest]; ok {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Legacy SSE shuffles and packed binaries dispatch on the full name.
|
|
|
|
|
if _, ok := sseShufTable[upper]; ok {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if _, ok := sseBinTable[upper]; ok {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The size-suffix split: retry the tables and the scalar switch on the
|
|
|
|
|
// base.
|
|
|
|
|
base2, size := splitSize(upper)
|
|
|
|
|
if size == 0 {
|
|
|
|
|
size = 8
|
|
|
|
|
}
|
|
|
|
|
_ = size
|
|
|
|
|
if base2 != upper {
|
|
|
|
|
if _, ok := sseBinTable[base2]; ok {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
switch base2 {
|
|
|
|
|
case "MOV",
|
|
|
|
|
"ADD", "SUB", "AND", "OR", "XOR", "CMP",
|
|
|
|
|
"TEST",
|
|
|
|
|
"LEA",
|
|
|
|
|
"INC", "DEC", "NEG", "NOT",
|
|
|
|
|
"SHL", "SHR", "SAR",
|
|
|
|
|
"IMUL", "IMUL3",
|
|
|
|
|
"PUSH", "POP",
|
|
|
|
|
"BSF", "BSR", "LZCNT", "TZCNT", "POPCNT",
|
|
|
|
|
"BSWAP",
|
|
|
|
|
"PREFETCHNTA", "PREFETCHT0", "PREFETCHT1", "PREFETCHT2",
|
|
|
|
|
"MOVBLZX", "MOVBQZX", "MOVWLZX", "MOVWQZX", "MOVWLSX", "MOVLQSX",
|
|
|
|
|
"CVTSL2SD", "CVTSQ2SD",
|
|
|
|
|
"MOVOU", "MOVO", "MOVUPS", "MOVAPS", "MOVUPD", "MOVAPD", "MOVSD", "MOVSS":
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|