fix(lint): guard unnamed TEXT and refresh the textflag table
This commit is contained in:
+3
-3
@@ -12,8 +12,8 @@ import (
|
|||||||
|
|
||||||
// checkFuncdata validates the structure of FUNCDATA and PCDATA directives,
|
// checkFuncdata validates the structure of FUNCDATA and PCDATA directives,
|
||||||
// which carry the GC stack-map information. The checks are deliberately
|
// which carry the GC stack-map information. The checks are deliberately
|
||||||
// shallow — they confirm the operands are well formed and that a literal index
|
// shallow, they confirm the operands are well formed and that a literal index
|
||||||
// is within the small range the runtime uses — and never try to interpret a
|
// is within the small range the runtime uses, and never try to interpret a
|
||||||
// named index constant such as $PCDATA_StackMapIndex.
|
// named index constant such as $PCDATA_StackMapIndex.
|
||||||
func checkFuncdata(t *ast.Text, cfg Config) []Diagnostic {
|
func checkFuncdata(t *ast.Text, cfg Config) []Diagnostic {
|
||||||
var out []Diagnostic
|
var out []Diagnostic
|
||||||
@@ -89,7 +89,7 @@ func checkIndex(op *ast.Operand, directive string) []Diagnostic {
|
|||||||
if op.Imm.HasVal && (op.Imm.Val < 0 || op.Imm.Val > 10) {
|
if op.Imm.HasVal && (op.Imm.Val < 0 || op.Imm.Val > 10) {
|
||||||
return []Diagnostic{{
|
return []Diagnostic{{
|
||||||
Pos: op.Pos, Severity: Warning, Code: CodeFuncdata,
|
Pos: op.Pos, Severity: Warning, Code: CodeFuncdata,
|
||||||
Message: fmt.Sprintf("%s index %d is outside the valid range 0–10", directive, op.Imm.Val),
|
Message: fmt.Sprintf("%s index %d is outside the valid range 0-10", directive, op.Imm.Val),
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+22
-21
@@ -10,6 +10,7 @@ package lint
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/arch"
|
"sourcedock.dev/petrbalvin/gasm-devkit/arch"
|
||||||
@@ -84,10 +85,12 @@ const (
|
|||||||
CodeReservedRegister = "reserved-register-write"
|
CodeReservedRegister = "reserved-register-write"
|
||||||
)
|
)
|
||||||
|
|
||||||
// knownTextFlags are the flags recognised by the Go assembler's textflag.h.
|
// knownTextFlags are the flags recognised by runtime/textflag.h, plus the
|
||||||
|
// older REFLECTED spelling of REFLECTMETHOD.
|
||||||
var knownTextFlags = map[string]bool{
|
var knownTextFlags = map[string]bool{
|
||||||
"NOSPLIT": true, "DUPOK": true, "RODATA": true, "NOPROF": true,
|
"NOSPLIT": true, "DUPOK": true, "RODATA": true, "NOPROF": true,
|
||||||
"WRIT": true, "TLSBSS": true, "NOFRAME": true, "REFLECTED": true,
|
"NOPTR": true, "WRAPPER": true, "NEEDCTXT": true, "TLSBSS": true,
|
||||||
|
"NOFRAME": true, "REFLECTED": true, "REFLECTMETHOD": true,
|
||||||
"TOPFRAME": true, "ABIWRAPPER": true,
|
"TOPFRAME": true, "ABIWRAPPER": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,8 +175,9 @@ func File(f *ast.File, cfg Config) []Diagnostic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, fl := range flags {
|
for _, fl := range flags {
|
||||||
// Numeric flags (1, 8, 9) are legacy Go toolchain constants.
|
// Numeric flags are legacy textflag.h constants (1, 2, 8,
|
||||||
if fl >= "0" && fl <= "9" {
|
// 9, 10, …); their meaning is decided at assembly time.
|
||||||
|
if _, err := strconv.Atoi(fl); err == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !knownTextFlags[fl] {
|
if !knownTextFlags[fl] {
|
||||||
@@ -242,7 +246,7 @@ func lintText(t *ast.Text, tab *arch.Table, archKnown bool, cfg Config, macros m
|
|||||||
|
|
||||||
// Unreachable code: a real instruction following a RET/UNDEF and
|
// Unreachable code: a real instruction following a RET/UNDEF and
|
||||||
// before any label, in a function whose control flow is fully
|
// before any label, in a function whose control flow is fully
|
||||||
// resolvable. Only RET/UNDEF are treated as terminators here — an
|
// resolvable. Only RET/UNDEF are treated as terminators here, an
|
||||||
// unconditional jump may be one entry of a hand-arranged branch
|
// unconditional jump may be one entry of a hand-arranged branch
|
||||||
// table (e.g. the generated callback tables), so it is not assumed
|
// table (e.g. the generated callback tables), so it is not assumed
|
||||||
// to make the following code dead. Pseudo-ops and macro invocations
|
// to make the following code dead. Pseudo-ops and macro invocations
|
||||||
@@ -373,8 +377,10 @@ func lintText(t *ast.Text, tab *arch.Table, archKnown bool, cfg Config, macros m
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Missing RET heuristic. Functions that invoke a macro are skipped: the
|
// Missing RET heuristic. Functions that invoke a macro are skipped: the
|
||||||
// macro body (opaque to us) may supply the RET.
|
// macro body (opaque to us) may supply the RET. A TEXT whose symbol is
|
||||||
if doLabelChecks && !cfg.Disable[CodeMissingRet] && instrCount > 0 && !hasRet && !lastTerminal && !hasMacro {
|
// missing (already reported by the parser) is skipped too.
|
||||||
|
if doLabelChecks && !cfg.Disable[CodeMissingRet] && t.Name != nil &&
|
||||||
|
instrCount > 0 && !hasRet && !lastTerminal && !hasMacro {
|
||||||
out = append(out, Diagnostic{
|
out = append(out, Diagnostic{
|
||||||
Pos: t.Keyword.Pos,
|
Pos: t.Keyword.Pos,
|
||||||
Severity: Warning,
|
Severity: Warning,
|
||||||
@@ -506,7 +512,7 @@ func reachesRuntime(t *ast.Text) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// usesFPArgs reports whether a function references its arguments through the FP
|
// usesFPArgs reports whether a function references its arguments through the FP
|
||||||
// pseudo-register — i.e. it uses the stack-based ABI0 layout, where the
|
// pseudo-register, i.e. it uses the stack-based ABI0 layout, where the
|
||||||
// declared argument size must match the signature.
|
// declared argument size must match the signature.
|
||||||
func usesFPArgs(t *ast.Text) bool {
|
func usesFPArgs(t *ast.Text) bool {
|
||||||
for _, s := range t.Body {
|
for _, s := range t.Body {
|
||||||
@@ -572,7 +578,7 @@ func isMacroInvocation(mnem string, macros map[string]bool) bool {
|
|||||||
|
|
||||||
// maskedEvex reports whether the instruction is a masked EVEX form: the
|
// maskedEvex reports whether the instruction is a masked EVEX form: the
|
||||||
// mnemonic carries a .Z suffix, or the operand list contains an opmask
|
// mnemonic carries a .Z suffix, or the operand list contains an opmask
|
||||||
// register (K1–K7). Either way the operand count differs from the unmasked
|
// register (K1-K7). Either way the operand count differs from the unmasked
|
||||||
// form, so count checks are skipped.
|
// form, so count checks are skipped.
|
||||||
func maskedEvex(mnem string, ops []*ast.Operand) bool {
|
func maskedEvex(mnem string, ops []*ast.Operand) bool {
|
||||||
if strings.Contains(mnem, ".") {
|
if strings.Contains(mnem, ".") {
|
||||||
@@ -587,7 +593,7 @@ func maskedEvex(mnem string, ops []*ast.Operand) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// isMaskReg reports whether name is an opmask register K0–K7.
|
// isMaskReg reports whether name is an opmask register K0-K7.
|
||||||
func isMaskReg(name string) bool {
|
func isMaskReg(name string) bool {
|
||||||
return len(name) == 2 && name[0] == 'K' && name[1] >= '0' && name[1] <= '7'
|
return len(name) == 2 && name[0] == 'K' && name[1] >= '0' && name[1] <= '7'
|
||||||
}
|
}
|
||||||
@@ -702,11 +708,6 @@ func stackDelta(t *ast.Text, a arch.Arch) int64 {
|
|||||||
}
|
}
|
||||||
case arch.ARM64:
|
case arch.ARM64:
|
||||||
switch upper {
|
switch upper {
|
||||||
case "STP":
|
|
||||||
// STP with pre-index: STP Xt1, Xt2, [SP, #imm]!
|
|
||||||
if len(in.Operands) >= 3 && isSPReg(in.Operands[2], a) {
|
|
||||||
// Could be pre-index decrement; skip for simplicity.
|
|
||||||
}
|
|
||||||
case "SUB":
|
case "SUB":
|
||||||
if len(in.Operands) >= 3 && isSPReg(in.Operands[2], a) {
|
if len(in.Operands) >= 3 && isSPReg(in.Operands[2], a) {
|
||||||
if in.Operands[1].Imm.HasVal {
|
if in.Operands[1].Imm.HasVal {
|
||||||
@@ -770,10 +771,10 @@ func isSPReg(op *ast.Operand, a arch.Arch) bool {
|
|||||||
|
|
||||||
// checkRegisterWidth detects amd64 register-width mismatches. The naming
|
// checkRegisterWidth detects amd64 register-width mismatches. The naming
|
||||||
// truth of the Go assembler governs: AX, BX, CX, DX, SI, DI, BP, SP and
|
// truth of the Go assembler governs: AX, BX, CX, DX, SI, DI, BP, SP and
|
||||||
// R8–R15 ARE the 64-bit register names (there are no separate EAX/RAX
|
// R8-R15 ARE the 64-bit register names (there are no separate EAX/RAX
|
||||||
// spellings in go tool asm), and AL–DH are the byte forms. The width comes
|
// spellings in go tool asm), and AL-DH are the byte forms. The width comes
|
||||||
// from the opcode suffix, so an L/W operation over a canonical 64-bit name is
|
// from the opcode suffix, so an L/W operation over a canonical 64-bit name is
|
||||||
// the normal, correct spelling — flagging it is pure noise on real kernels.
|
// the normal, correct spelling, flagging it is pure noise on real kernels.
|
||||||
// What remains worth flagging: a Q (64-bit) operation over a narrower spelled
|
// What remains worth flagging: a Q (64-bit) operation over a narrower spelled
|
||||||
// register (EAX under the gasm alias extension, or a byte form), and byte
|
// register (EAX under the gasm alias extension, or a byte form), and byte
|
||||||
// registers in L/W operations.
|
// registers in L/W operations.
|
||||||
@@ -812,8 +813,8 @@ func checkRegisterWidth(mnem string, ops []*ast.Operand) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// amd64RegWidth returns the width in bytes of an amd64 register name under
|
// amd64RegWidth returns the width in bytes of an amd64 register name under
|
||||||
// the Go assembler's naming model: the canonical word names (AX…SP, R8–R15)
|
// the Go assembler's naming model: the canonical word names (AX…SP, R8-R15)
|
||||||
// are 64-bit, AL–DH are the 8-bit forms, and the R/E-prefixed spellings are
|
// are 64-bit, AL-DH are the 8-bit forms, and the R/E-prefixed spellings are
|
||||||
// the gasm alias extension with their intuitive widths.
|
// the gasm alias extension with their intuitive widths.
|
||||||
func amd64RegWidth(name string) int {
|
func amd64RegWidth(name string) int {
|
||||||
switch name {
|
switch name {
|
||||||
@@ -833,7 +834,7 @@ func countRange(min, max int) string {
|
|||||||
if min == max {
|
if min == max {
|
||||||
return fmt.Sprintf("%d operand(s)", min)
|
return fmt.Sprintf("%d operand(s)", min)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%d–%d operands", min, max)
|
return fmt.Sprintf("%d-%d operands", min, max)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sortDiagnostics orders diagnostics by line, then column, then code.
|
// sortDiagnostics orders diagnostics by line, then column, then code.
|
||||||
|
|||||||
+2
-2
@@ -370,7 +370,7 @@ func sameSet(a, b map[string]bool) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// goFixedGPRs returns the general-purpose registers the Go ABI designates as
|
// goFixedGPRs returns the general-purpose registers the Go ABI designates as
|
||||||
// fixed across calls — the ones hand-written assembly must not permanently
|
// fixed across calls, the ones hand-written assembly must not permanently
|
||||||
// clobber. This follows cmd/compile/abi-internal.md, not the platform ABI:
|
// clobber. This follows cmd/compile/abi-internal.md, not the platform ABI:
|
||||||
// Go's stack-based ABI0 (which hand-written assembly uses) has no System V
|
// Go's stack-based ABI0 (which hand-written assembly uses) has no System V
|
||||||
// style callee-saved registers, so clobbering the argument and scratch
|
// style callee-saved registers, so clobbering the argument and scratch
|
||||||
@@ -414,7 +414,7 @@ func gprSet(names ...string) map[string]bool {
|
|||||||
// without also saving and restoring them. The first result lists registers
|
// without also saving and restoring them. The first result lists registers
|
||||||
// whose loss is never safe; the second lists the goroutine-pointer class,
|
// whose loss is never safe; the second lists the goroutine-pointer class,
|
||||||
// whose loss is reported only when reachesRuntime is true (a non-NOSPLIT
|
// whose loss is reported only when reachesRuntime is true (a non-NOSPLIT
|
||||||
// function, or one that makes calls — the ABI0 transition machinery restores
|
// function, or one that makes calls, the ABI0 transition machinery restores
|
||||||
// the g pointer only on such paths).
|
// the g pointer only on such paths).
|
||||||
func clobberedGoFixed(l *liveness, a arch.Arch, reachesRuntime bool) (always, runtime []string) {
|
func clobberedGoFixed(l *liveness, a arch.Arch, reachesRuntime bool) (always, runtime []string) {
|
||||||
alwaysSet, runtimeSet := goFixedGPRs(a)
|
alwaysSet, runtimeSet := goFixedGPRs(a)
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ var amd64WordNames = map[string]string{
|
|||||||
|
|
||||||
// nonportableRegister maps a gasm-only register alias to the canonical go
|
// nonportableRegister maps a gasm-only register alias to the canonical go
|
||||||
// tool asm spelling. R or E followed by a canonical word name is the only
|
// tool asm spelling. R or E followed by a canonical word name is the only
|
||||||
// alias family; R8–R15 are already canonical.
|
// alias family; R8-R15 are already canonical.
|
||||||
func nonportableRegister(name string) (string, bool) {
|
func nonportableRegister(name string) (string, bool) {
|
||||||
up := strings.ToUpper(name)
|
up := strings.ToUpper(name)
|
||||||
if len(up) != 3 {
|
if len(up) != 3 {
|
||||||
|
|||||||
Reference in New Issue
Block a user