From eacf33d0f70209d999a14856afd16d9f74766c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Sat, 29 Aug 2026 15:25:15 +0200 Subject: [PATCH] fix: staticcheck and deadcode findings repo-wide, modernize counting loops --- asm/aarch64_goobj_test.go | 5 +-- asm/arm64_assemble.go | 5 --- asm/arm64_encode.go | 75 ------------------------------- asm/elf.go | 2 - asm/elf_dwarf_sections.go | 24 ---------- asm/goobj.go | 2 +- asm/goobj_resolve.go | 20 +-------- asm/loong64_assemble.go | 5 --- asm/operand.go | 5 --- asm/riscv_assemble.go | 5 --- cmd/gasm/audit.go | 14 ++---- cmd/gasm/main.go | 4 +- cmd/gasm/scaffold.go | 21 ++++++--- debug/breakpoint.go | 5 --- debug/disasm_linux_amd64.go | 2 +- debug/disasm_linux_arm64.go | 2 +- debug/disasm_linux_loong64.go | 2 +- debug/disasm_linux_riscv64.go | 2 +- debug/repl.go | 2 +- debug/target_linux_amd64.go | 6 +-- debug/watchpoint_linux_arm64.go | 2 +- debug/watchpoint_linux_loong64.go | 2 +- debug/watchpoint_linux_riscv64.go | 2 +- lint/abi.go | 2 +- lint/corpus_test.go | 9 +++- lsp/protocol.go | 4 -- parser/parser.go | 15 +++---- verify/abi_amd64.go | 21 ++++++--- verify/call_amd64.go | 7 ++- verify/coverage.go | 46 ------------------- verify/fuzz.go | 2 +- verify/groundtruth.go | 16 ++++++- verify/verify.go | 2 +- 33 files changed, 88 insertions(+), 250 deletions(-) diff --git a/asm/aarch64_goobj_test.go b/asm/aarch64_goobj_test.go index 5bdf2d6..9a7bebd 100644 --- a/asm/aarch64_goobj_test.go +++ b/asm/aarch64_goobj_test.go @@ -109,16 +109,13 @@ func main() { if err != nil { t.Fatalf("baseline build: %v\n%s", err, buildLog) } - var pkgArch, work, linkLine, asmObj string + var work, linkLine, asmObj string for _, line := range strings.Split(string(buildLog), "\n") { switch { case strings.HasPrefix(line, "WORK="): work = strings.TrimPrefix(line, "WORK=") case strings.Contains(line, "/asm ") && strings.Contains(line, "main_arm64.s") && !strings.Contains(line, "-gensymabis"): asmObj = fieldAfter(line, "-o") - case strings.Contains(line, "pack r") && strings.Contains(line, "_pkg_.a"): - pkgArch = strings.TrimSpace(strings.SplitN(line, "pack r", 2)[1]) - pkgArch = strings.Fields(strings.SplitN(pkgArch, "#", 2)[0])[0] case strings.Contains(line, "/link ") && strings.Contains(line, "-importcfg"): linkLine = line } diff --git a/asm/arm64_assemble.go b/asm/arm64_assemble.go index a338f9f..918c9f9 100644 --- a/asm/arm64_assemble.go +++ b/asm/arm64_assemble.go @@ -878,11 +878,6 @@ func encodeARM64SBStore(sym *ast.Symbol, rs int, mnem string, relocs *[]Reloc) ( // ---- operand helpers ---- -// arm64Reg returns the register number of an operand, or -1. -func arm64Reg(op *ast.Operand) int { - return arm64RegNum(operandRegName(op)) -} - // arm64Imm64 returns the full 64-bit immediate value of an operand. func arm64Imm64(op *ast.Operand) int64 { if op.Imm.HasVal { diff --git a/asm/arm64_encode.go b/asm/arm64_encode.go index 0f10e56..ef458cc 100644 --- a/asm/arm64_encode.go +++ b/asm/arm64_encode.go @@ -115,12 +115,6 @@ func arm64RegNum(name string) int { return -1 } -// arm64IsSP reports whether a register operand is the stack pointer (R31/SP), -// which uses a different encoding path for some instructions. -func arm64IsSP(name string) bool { - return name == "SP" -} - // ---- format helpers ---- // a64wordLE encodes a uint32 as 4 little-endian bytes. @@ -137,14 +131,6 @@ func a64WordsLE(ws ...uint32) []byte { return out } -// ---- data-processing (shifted register) ---- - -// a64DPSR encodes a data-processing (shifted register) instruction: -// sf<<31 | op<<30 | S<<29 | 0x0b<<24 | shift<<22 | 0<<21 | Rm<<16 | imm6<<10 | Rn<<5 | Rd. -func a64DPSR(sf, op, S, shift, rm, imm6, rn, rd uint32) uint32 { - return sf<<31 | op<<30 | S<<29 | 0x0b<<24 | shift<<22 | rm<<16 | imm6<<10 | rn<<5 | rd -} - // ---- data-processing (immediate) ---- // a64AddSub encodes an ADD/SUB (immediate) instruction: @@ -153,14 +139,6 @@ func a64AddSub(sf, op, S, sh, imm12, rn, rd uint32) uint32 { return sf<<31 | op<<30 | S<<29 | 0x11<<24 | sh<<22 | imm12<<10 | rn<<5 | rd } -// ---- logical (immediate) ---- - -// a64LogicalImm encodes a logical (immediate) instruction: -// sf<<31 | opc<<29 | 0x24<<23 | N<<22 | immr<<16 | imms<<10 | Rn<<5 | Rd. -func a64LogicalImm(sf, opc, N, immr, imms, rn, rd uint32) uint32 { - return sf<<31 | opc<<29 | 0x24<<23 | N<<22 | immr<<16 | imms<<10 | rn<<5 | rd -} - // ---- move wide ---- // a64MoveWide encodes a MOVZ/MOVK/MOVN instruction: @@ -198,38 +176,6 @@ func a64LSP(opc, V, L uint32, imm7 int32, rt2, rn, rt uint32) uint32 { return opc<<30 | 5<<27 | V<<26 | 2<<23 | L<<22 | (uint32(imm7)&0x7F)<<15 | rt2<<10 | rn<<5 | rt } -// ---- load/store pair (pre-index) ---- - -// a64LSPPre encodes a load/store pair (pre-index): -// opc<<30 | 0x5<<27 | V<<26 | 0b11<<23 | L<<22 | imm7<<15 | Rt2<<10 | Rn<<5 | Rt. -func a64LSPPre(opc, V, L uint32, imm7 int32, rt2, rn, rt uint32) uint32 { - return opc<<30 | 5<<27 | V<<26 | 3<<23 | L<<22 | (uint32(imm7)&0x7F)<<15 | rt2<<10 | rn<<5 | rt -} - -// ---- load/store pair (post-index) ---- - -// a64LSPPost encodes a load/store pair (post-index): -// opc<<30 | 0x5<<27 | V<<26 | 0b01<<23 | L<<22 | imm7<<15 | Rt2<<10 | Rn<<5 | Rt. -func a64LSPPost(opc, V, L uint32, imm7 int32, rt2, rn, rt uint32) uint32 { - return opc<<30 | 5<<27 | V<<26 | 1<<23 | L<<22 | (uint32(imm7)&0x7F)<<15 | rt2<<10 | rn<<5 | rt -} - -// ---- pre-index load/store ---- - -// a64LSPreIndex encodes a load/store register (pre-index): -// size<<30 | 0x7<<27 | V<<26 | opc<<22 | 1<<11 | 1<<10 | imm9<<12 | Rn<<5 | Rt. -func a64LSPreIndex(size, V, opc uint32, imm9 int32, rn, rt uint32) uint32 { - return size<<30 | 7<<27 | V<<26 | opc<<22 | 3<<10 | (uint32(imm9)&0x1FF)<<12 | rn<<5 | rt -} - -// ---- post-index load/store ---- - -// a64LSPostIndex encodes a load/store register (post-index): -// size<<30 | 0x7<<27 | V<<26 | opc<<22 | 0<<11 | 1<<10 | imm9<<12 | Rn<<5 | Rt. -func a64LSPostIndex(size, V, opc uint32, imm9 int32, rn, rt uint32) uint32 { - return size<<30 | 7<<27 | V<<26 | opc<<22 | 1<<10 | (uint32(imm9)&0x1FF)<<12 | rn<<5 | rt -} - // ---- branches ---- // a64Branch encodes an unconditional branch (B/BL): @@ -259,14 +205,6 @@ func a64ADR(p uint32, immhi int32, immlo uint32, rd uint32) uint32 { return p<<31 | immlo<<29 | 0x10<<24 | (uint32(immhi)&0x7FFFF)<<5 | rd } -// ---- EXTR ---- - -// a64EXTR encodes an EXTR instruction: -// sf<<31 | 0<<29 | 0x27<<23 | N<<22 | 0<<21 | Rm<<16 | imms<<10 | Rn<<5 | Rd. -func a64EXTR(sf, N, rm, imms, rn, rd uint32) uint32 { - return sf<<31 | 0x27<<23 | N<<22 | rm<<16 | imms<<10 | rn<<5 | rd -} - // ---- system ---- // a64NOP encodes a NOP: 0xd503201f. @@ -359,7 +297,6 @@ const ( type a64Enc struct { format a64Format op uint32 // the pre-positioned opcode bits - size int // 4 for most, 8 for DP-imm with shift, etc. } // a64InstrTable maps AArch64 mnemonics (as the Go assembler spells them) to @@ -717,18 +654,6 @@ func a64StoreOpc(t a64LSType) int { return 0 // integer store } -// a64MovRegTable maps register-to-register MOV mnemonic expansions. -// The Go toolchain encodes MOV Rn, Rd as ORR Rn, ZR, Rd. -var a64MovRegTable = map[string]uint32{ - "MOVD": 1<<31 | 1<<29 | 0x0a<<24, // ORR 64-bit - "MOVW": 0<<31 | 1<<29 | 0x0a<<24, // ORR 32-bit - "MOVB": 0<<31 | 1<<29 | 0x0a<<24, // ORR 32-bit (byte move) - "MOVBU": 0<<31 | 1<<29 | 0x0a<<24, // ORR 32-bit - "MOVH": 0<<31 | 1<<29 | 0x0a<<24, // ORR 32-bit - "MOVHU": 0<<31 | 1<<29 | 0x0a<<24, // ORR 32-bit - "MOVWU": 0<<31 | 1<<29 | 0x0a<<24, // ORR 32-bit -} - // arm64RegClass discriminates integer (R), floating-point (F) registers for // the MOV pseudo-instruction. type arm64RegClass int diff --git a/asm/elf.go b/asm/elf.go index 70345d6..d326a8b 100644 --- a/asm/elf.go +++ b/asm/elf.go @@ -45,8 +45,6 @@ const ( sttSection = 3 stInfoShift = 4 - shnUndef = 0 - rX8664PC32 = 2 rX8664_64 = 1 // R_X86_64_64 (absolute 64-bit) ) diff --git a/asm/elf_dwarf_sections.go b/asm/elf_dwarf_sections.go index d08f85a..79e4507 100644 --- a/asm/elf_dwarf_sections.go +++ b/asm/elf_dwarf_sections.go @@ -3,8 +3,6 @@ package asm -import "encoding/binary" - // dwarfELFSections holds the laid-out DWARF sections ready for inclusion // in an ELF file. type dwarfELFSections struct { @@ -114,28 +112,6 @@ func appendDWARFSections(out *[]byte, img *Image, srcFile string, symIdx map[str return result } -// dwarfRelaRecords returns the ELF RELA records for DWARF address fixups. -// Each record uses R_X86_64_64 (absolute 64-bit) relocation type. -func dwarfRelaRecords(dw *dwarfELFSections, relType uint32) []byte { - le := binary.LittleEndian - var out []byte - for _, r := range dw.infoRelocs { - var b [24]byte - le.PutUint64(b[0:], r.off) - le.PutUint64(b[8:], uint64(r.sym)<<32|uint64(relType)) - le.PutUint64(b[16:], uint64(r.addend)) - out = append(out, b[:]...) - } - for _, r := range dw.lineRelocs { - var b [24]byte - le.PutUint64(b[0:], r.off) - le.PutUint64(b[8:], uint64(r.sym)<<32|uint64(relType)) - le.PutUint64(b[16:], uint64(r.addend)) - out = append(out, b[:]...) - } - return out -} - // dwarfSectionNames returns the DWARF section names for the string table. var dwarfSectionNames = []string{ ".debug_abbrev", ".debug_info", ".debug_line", ".debug_line_str", diff --git a/asm/goobj.go b/asm/goobj.go index 3f1087b..00c2895 100644 --- a/asm/goobj.go +++ b/asm/goobj.go @@ -505,7 +505,7 @@ func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, r // The fingerprint stays zero, as cmd/asm leaves it. binary.LittleEndian.PutUint32(payload[16:], 4) // ObjFlagFromAssembly off := uint32(headerSize + len(strTab)) - for i := 0; i < blkEnd; i++ { + for i := range blkEnd { binary.LittleEndian.PutUint32(payload[20+4*i:], off) off += uint32(len(blocks[i])) } diff --git a/asm/goobj_resolve.go b/asm/goobj_resolve.go index 953ad93..6212adc 100644 --- a/asm/goobj_resolve.go +++ b/asm/goobj_resolve.go @@ -12,24 +12,6 @@ import ( "strings" ) -// readGOOBJSymbols reads the GOOBJ symbol definitions from a compiled Go -// package's export file. The file is an ar archive containing a __.PKGDEF -// member whose payload is the "go object ...\n!\n" preamble followed by the -// GOOBJ data. The function returns the symbol names in definition order -// (the order they appear in blkSymdef), which matches the SymIdx the linker -// expects for cross-package references. -func readGOOBJSymbols(exportPath string) ([]string, error) { - data, err := os.ReadFile(exportPath) - if err != nil { - return nil, err - } - goobj, err := extractGOOBJ(data) - if err != nil { - return nil, fmt.Errorf("%s: %w", exportPath, err) - } - return goobj.symbols(), nil -} - // exportPath returns the export file path for a given import path by running // "go list -export". The result is cached so repeated calls for the same // package are fast. @@ -221,7 +203,7 @@ func (f *goobjFile) readSymNames(block []byte) []string { } n := len(block) / recSize names := make([]string, 0, n) - for i := 0; i < n; i++ { + for i := range n { rec := block[i*recSize : (i+1)*recSize] nameLen := binary.LittleEndian.Uint32(rec[0:4]) nameOff := binary.LittleEndian.Uint32(rec[4:8]) diff --git a/asm/loong64_assemble.go b/asm/loong64_assemble.go index 1132bb9..53e983e 100644 --- a/asm/loong64_assemble.go +++ b/asm/loong64_assemble.go @@ -1342,11 +1342,6 @@ func l64Reg(op *ast.Operand) int { return loong64RegNum(operandRegName(op)) } -// l64Imm returns the immediate value of an operand. -func l64Imm(op *ast.Operand) int32 { - return immFromOperand(op) -} - // l64Imm64 returns the full 64-bit immediate value of an operand. func l64Imm64(op *ast.Operand) int64 { if op.Imm.HasVal { diff --git a/asm/operand.go b/asm/operand.go index 743327b..dd79d93 100644 --- a/asm/operand.go +++ b/asm/operand.go @@ -37,11 +37,6 @@ func Idx(base, index Reg, scale int, disp int64, size int) Mem { return Mem{Base: base, Index: index, Scale: scale, Disp: disp, Size: size, HasBase: true, HasIndex: true} } -// Rip builds a RIP-relative memory operand (RIP)+disp. -func Rip(disp int64, size int) Mem { - return Mem{Disp: disp, Size: size} -} - // sbMem is a memory operand that references a static (SB) symbol. It encodes // as a RIP-relative reference with a placeholder displacement; the encoder // records a patch site so the file-level layout can fill in the true rel32 diff --git a/asm/riscv_assemble.go b/asm/riscv_assemble.go index 1e738e9..95d1a23 100644 --- a/asm/riscv_assemble.go +++ b/asm/riscv_assemble.go @@ -1250,11 +1250,6 @@ func isFPCmpInstr(m string) bool { return false } -func isFPCvtInstr(m string) bool { - _, ok := riscvCvtTable[m] - return ok -} - // Operand helpers. func regFromOperand(op *ast.Operand) int { // Register is in Addr.Base (from (base) syntax) or Addr.Sym.Name (bare ident). diff --git a/cmd/gasm/audit.go b/cmd/gasm/audit.go index 102da0f..47b2116 100644 --- a/cmd/gasm/audit.go +++ b/cmd/gasm/audit.go @@ -66,7 +66,7 @@ mnemonic, so the audit tracks whatever toolchain `+"`go env GOROOT`"+` names. return err } - var superset, backlog, shared, goOnly []string + var superset, backlog, shared []string for _, name := range names { switch { case !asm.Encodable(name): @@ -77,16 +77,8 @@ mnemonic, so the audit tracks whatever toolchain `+"`go env GOROOT`"+` names. shared = append(shared, name) } } - for _, name := range names { - if goKnown[name] && !seen[name] { - goOnly = append(goOnly, name) - } - } - // GO-ONLY is computed from the complement of the gasm table, which this - // audit cannot enumerate (Go's table is only visible through probes of - // names we already know). Report it as informational zero instead of a - // misleading list. - goOnly = nil + // GO-ONLY is not enumerable by probing: Go's table is only visible + // through names we already know, so nothing can be reported there. sort.Strings(superset) sort.Strings(backlog) diff --git a/cmd/gasm/main.go b/cmd/gasm/main.go index 89aa4c1..ef753d5 100644 --- a/cmd/gasm/main.go +++ b/cmd/gasm/main.go @@ -1526,7 +1526,7 @@ func cmdVerifyCall(k *verify.Kernel, path, funcName, bufSpec, scalarSpec string, fmt.Printf(" args before: %s\n", hexDump(args)) rc := 0 - for i := 0; i < repeat; i++ { + for i := range repeat { out, err := k.CallFunc(funcName, args) if err != nil { fmt.Printf(" call %d: FAIL — %v\n", i+1, err) @@ -1575,7 +1575,7 @@ func hexDump(buf []byte) string { n = max } var sb strings.Builder - for i := 0; i < n; i++ { + for i := range n { if i > 0 { sb.WriteByte(' ') } diff --git a/cmd/gasm/scaffold.go b/cmd/gasm/scaffold.go index 232d1c3..edab81d 100644 --- a/cmd/gasm/scaffold.go +++ b/cmd/gasm/scaffold.go @@ -60,6 +60,7 @@ bodies, place the file in the kernel's package, and run it in CI. out.WriteString(headerComment) out.WriteString("package " + packageName + "\n\n") out.WriteString("import (\n\t\"bytes\"\n\t\"math/rand\"\n\t\"testing\"\n)\n\n") + out.WriteString(generatedHelpers) kernels := 0 for _, d := range f.Decls { @@ -305,9 +306,12 @@ func goCast(elem string) string { } } -// outputBytes narrows a returned slice to bytes for the comparison; scalar -// results are compared through the same helper via a reflect-free trick the -// author may need to adjust for non-slice returns. +// generatedHelpers is emitted into every generated test file: outputBytes +// narrows returned slices and scalars to a byte form for the comparison. +// It lives in the template, not in this binary, because only the generated +// file ever calls it. +const generatedHelpers = `// outputBytes narrows a returned slice or scalar to bytes for the +// comparison; extend the switch when a kernel returns a wider type. func outputBytes(v any) []byte { switch t := v.(type) { case []byte: @@ -324,11 +328,18 @@ func outputBytes(v any) []byte { case []uint16: b := make([]byte, 2*len(t)) for i, x := range t { - b[i*4/2] = byte(x) - b[i*4/2+1] = byte(x >> 8) + b[i*2] = byte(x) + b[i*2+1] = byte(x >> 8) + } + return b + case int: + b := make([]byte, 8) + for i := 0; i < 8; i++ { + b[i] = byte(uint64(t) >> (8 * i)) } return b default: return nil } } +` diff --git a/debug/breakpoint.go b/debug/breakpoint.go index cdf0ab6..53ee4b1 100644 --- a/debug/breakpoint.go +++ b/debug/breakpoint.go @@ -114,11 +114,6 @@ func (bm *Breakpoints) SetWithCond(addr uint64, label string, cond *Condition) ( return bp, nil } -// Hits returns the number of times the breakpoint has been hit. -func (bp *Breakpoint) Hits() int { - return bp.hits -} - // Info returns a formatted list of all breakpoints. func (bm *Breakpoints) Info() string { if len(bm.bps) == 0 { diff --git a/debug/disasm_linux_amd64.go b/debug/disasm_linux_amd64.go index 42aaaf2..24ac13f 100644 --- a/debug/disasm_linux_amd64.go +++ b/debug/disasm_linux_amd64.go @@ -36,7 +36,7 @@ func (s *Session) Disassemble(addr uint64) (string, int, error) { func (s *Session) DisassembleN(addr uint64, n int) string { var result string pc := addr - for i := 0; i < n; i++ { + for range n { text, length, err := s.Disassemble(pc) if err != nil { result += fmt.Sprintf(" %#08x: \n", pc, err) diff --git a/debug/disasm_linux_arm64.go b/debug/disasm_linux_arm64.go index 91a9fde..166bccf 100644 --- a/debug/disasm_linux_arm64.go +++ b/debug/disasm_linux_arm64.go @@ -30,7 +30,7 @@ func (s *Session) Disassemble(addr uint64) (string, int, error) { func (s *Session) DisassembleN(addr uint64, n int) string { var result string pc := addr - for i := 0; i < n; i++ { + for i := range n { text, length, err := s.Disassemble(pc) if err != nil { result += fmt.Sprintf(" %#08x: \n", pc, err) diff --git a/debug/disasm_linux_loong64.go b/debug/disasm_linux_loong64.go index 942aa71..3bf0f85 100644 --- a/debug/disasm_linux_loong64.go +++ b/debug/disasm_linux_loong64.go @@ -30,7 +30,7 @@ func (s *Session) Disassemble(addr uint64) (string, int, error) { func (s *Session) DisassembleN(addr uint64, n int) string { var result string pc := addr - for i := 0; i < n; i++ { + for i := range n { text, length, err := s.Disassemble(pc) if err != nil { result += fmt.Sprintf(" %#08x: \n", pc, err) diff --git a/debug/disasm_linux_riscv64.go b/debug/disasm_linux_riscv64.go index 4f0cc91..e9dfab9 100644 --- a/debug/disasm_linux_riscv64.go +++ b/debug/disasm_linux_riscv64.go @@ -30,7 +30,7 @@ func (s *Session) Disassemble(addr uint64) (string, int, error) { func (s *Session) DisassembleN(addr uint64, n int) string { var result string pc := addr - for i := 0; i < n; i++ { + for i := range n { text, length, err := s.Disassemble(pc) if err != nil { result += fmt.Sprintf(" %#08x: \n", pc, err) diff --git a/debug/repl.go b/debug/repl.go index c1d870b..b4d3d74 100644 --- a/debug/repl.go +++ b/debug/repl.go @@ -72,7 +72,7 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar if len(parts) > 1 { n, _ = strconv.Atoi(parts[1]) } - for i := 0; i < n; i++ { + for range n { if s.Exited() { fmt.Println("debuggee exited") break diff --git a/debug/target_linux_amd64.go b/debug/target_linux_amd64.go index fbba65e..7aa6118 100644 --- a/debug/target_linux_amd64.go +++ b/debug/target_linux_amd64.go @@ -98,10 +98,10 @@ func RunTarget(asmPath, funcName, argsFile, tmpDir string) error { stackArgs := make([]byte, fl.Args) copy(stackArgs, args) - _, callErr := verify.Call(fnAddr, stackArgs) - if callErr != nil { + if _, callErr := verify.Call(fnAddr, stackArgs); callErr != nil { os.Exit(1) } - os.Exit(0) + // Success returns to the caller, which exits with status 0; the JIT + // code has already run to its own trampoline by the time Call returns. return nil } diff --git a/debug/watchpoint_linux_arm64.go b/debug/watchpoint_linux_arm64.go index c5507b4..8bdebb0 100644 --- a/debug/watchpoint_linux_arm64.go +++ b/debug/watchpoint_linux_arm64.go @@ -44,7 +44,7 @@ const ( ) func (s *Session) FindFreeWatchpointSlot() int { - for i := 0; i < maxWatchpoints; i++ { + for i := range maxWatchpoints { if !wpSlots[i] { return i } diff --git a/debug/watchpoint_linux_loong64.go b/debug/watchpoint_linux_loong64.go index 9b9c854..ef7f4ad 100644 --- a/debug/watchpoint_linux_loong64.go +++ b/debug/watchpoint_linux_loong64.go @@ -27,7 +27,7 @@ var wpSlots [4]bool const maxWatchpoints = 4 func (s *Session) FindFreeWatchpointSlot() int { - for i := 0; i < maxWatchpoints; i++ { + for i := range maxWatchpoints { if !wpSlots[i] { return i } diff --git a/debug/watchpoint_linux_riscv64.go b/debug/watchpoint_linux_riscv64.go index 06ebe60..00dd802 100644 --- a/debug/watchpoint_linux_riscv64.go +++ b/debug/watchpoint_linux_riscv64.go @@ -27,7 +27,7 @@ var wpSlots [4]bool const maxWatchpoints = 4 func (s *Session) FindFreeWatchpointSlot() int { - for i := 0; i < maxWatchpoints; i++ { + for i := range maxWatchpoints { if !wpSlots[i] { return i } diff --git a/lint/abi.go b/lint/abi.go index 6e65a56..be29aa6 100644 --- a/lint/abi.go +++ b/lint/abi.go @@ -119,7 +119,7 @@ func fieldsSizeAlign(list *ast.FieldList) (size, align int, ok bool) { if n == 0 { n = 1 } - for i := 0; i < n; i++ { + for range n { offset = alignUp(offset, ea) offset += es } diff --git a/lint/corpus_test.go b/lint/corpus_test.go index f952960..05ac742 100644 --- a/lint/corpus_test.go +++ b/lint/corpus_test.go @@ -5,8 +5,9 @@ package lint import ( "os" + "os/exec" "path/filepath" - "runtime" + "strings" "testing" "sourcedock.dev/petrbalvin/gasm-devkit/arch" @@ -24,7 +25,11 @@ import ( // assembler accepts. Advisory warnings are reported but not fatal, since they // are heuristics that may legitimately differ across Go versions. func TestGoRuntimeCorpus(t *testing.T) { - dir := filepath.Join(runtime.GOROOT(), "src", "runtime") + out, err := exec.Command("go", "env", "GOROOT").Output() + if err != nil { + t.Skipf("cannot locate GOROOT: %v", err) + } + dir := filepath.Join(strings.TrimSpace(string(out)), "src", "runtime") var files []string for _, suffix := range []string{"_amd64.s", "_arm64.s", "_riscv64.s", "_loong64.s"} { matches, _ := filepath.Glob(filepath.Join(dir, "*"+suffix)) diff --git a/lsp/protocol.go b/lsp/protocol.go index 279a4c7..efb855d 100644 --- a/lsp/protocol.go +++ b/lsp/protocol.go @@ -295,10 +295,6 @@ type semanticTokensParams struct { // --- initialize ------------------------------------------------------------- -type initializeParams struct { - RootURI string `json:"rootUri"` -} - // ServerCapabilities advertises what this server provides. type ServerCapabilities struct { TextDocumentSync int `json:"textDocumentSync"` diff --git a/parser/parser.go b/parser/parser.go index 5f7a124..3d32be9 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -210,18 +210,17 @@ func (p *state) parseText(line []token.Token) { rest = rest[1:] } - // Frame: $number ; optional args: -number. + // Frame: $number ; optional args: -number. Whatever remains after the + // header is the body and is parsed by the caller. if len(rest) > 0 && rest[0].Kind == token.Dollar { text.Frame = parseOperand(rest[:2]) // "$" "number" - rest = rest[2:] - if len(rest) >= 2 && rest[0].Kind == token.Minus && rest[1].Kind == token.Number { + if len(rest) >= 4 && rest[2].Kind == token.Minus && rest[3].Kind == token.Number { text.Args = &ast.Operand{ Kind: ast.OpImmediate, - Imm: ast.Immediate{Val: parseInt(rest[1].Text), HasVal: true}, - Raw: "-" + rest[1].Text, - Pos: rest[0].Pos, + Imm: ast.Immediate{Val: parseInt(rest[3].Text), HasVal: true}, + Raw: "-" + rest[3].Text, + Pos: rest[2].Pos, } - rest = rest[2:] } } @@ -387,10 +386,8 @@ func parseImmediate(g []token.Token) ast.Immediate { } else { imm.Float = text } - i++ } else if i < len(g) && (g[i].Kind == token.String || g[i].Kind == token.Rune) { imm.Str = g[i].Text - i++ } return imm } diff --git a/verify/abi_amd64.go b/verify/abi_amd64.go index e29fd1f..4e90187 100644 --- a/verify/abi_amd64.go +++ b/verify/abi_amd64.go @@ -17,7 +17,12 @@ import ( var abiResult uint64 // savedBP holds the caller's frame pointer across the ABI-checked JIT call. -// Referenced by enterJITChecked to satisfy go vet's save-before-clobber rule. +// Written and read by enterJITChecked/leaveJITChecked (abi_amd64.s); no Go +// code references it, which GoLand cannot see inside assembly. +// +// noinspection GoUnusedGlobalVariable +// +//lint:ignore U1000 written and read by the assembly var savedBP uintptr // leaveCheckedPtr is initialised by the linker from the GLOBL/DATA in @@ -26,7 +31,10 @@ var savedBP uintptr var leaveCheckedPtr uintptr // enterJITChecked sets sentinels in BP and R14, switches to the prepared -// stack and jumps to fn. +// stack and jumps to fn. The body lives in abi_amd64.s and reads the +// parameters from the frame by name, which GoLand cannot see. +// +// noinspection GoUnusedParameter // //go:nosplit func enterJITChecked(fn uintptr, stack uintptr) @@ -37,6 +45,9 @@ func enterJITChecked(fn uintptr, stack uintptr) // declaration would generate. The declaration exists solely to satisfy // go vet's "missing Go declaration" check. // +// noinspection GoUnusedFunction +// +//lint:ignore U1000 the assembly obtains this address through leaveCheckedPtr //go:nosplit func leaveJITCheckedRaw() @@ -95,10 +106,10 @@ func CallChecked(fnAddr uintptr, args []byte) ([]byte, ABIReport, error) { if err != nil { return nil, report, fmt.Errorf("verify: stack mmap: %w", err) } - defer syscall.Munmap(stackMem) + defer func() { _ = syscall.Munmap(stackMem) }() // Fill the red zone with the canary pattern. - for i := 0; i < redZoneSize; i++ { + for i := range redZoneSize { stackMem[i] = redZoneFill } @@ -116,7 +127,7 @@ func CallChecked(fnAddr uintptr, args []byte) ([]byte, ABIReport, error) { report.R14Clobbered = res&2 != 0 // Check the red zone. - for i := 0; i < redZoneSize; i++ { + for i := range redZoneSize { if stackMem[i] != redZoneFill { report.RedZoneHit = true break diff --git a/verify/call_amd64.go b/verify/call_amd64.go index 5dbb398..4d4d3be 100644 --- a/verify/call_amd64.go +++ b/verify/call_amd64.go @@ -14,7 +14,12 @@ import ( ) // savedSP holds the Go stack pointer while a JIT call is in flight. -// Referenced by the assembly trampoline (trampoline_amd64.s). +// Written and read by the assembly trampoline (trampoline_amd64.s); no Go +// code references it, which staticcheck and GoLand cannot see. +// +// noinspection GoUnusedGlobalVariable +// +//lint:ignore U1000 written and read by the assembly var savedSP uintptr // enterJIT switches to the prepared stack and jumps to fn. diff --git a/verify/coverage.go b/verify/coverage.go index 539f526..c456838 100644 --- a/verify/coverage.go +++ b/verify/coverage.go @@ -55,49 +55,3 @@ func (k *Kernel) BlockCount(name string) (int, error) { } return len(blocks), nil } - -// PathFingerprint is the observable output of one function execution: the -// values written back into the result slots of the argument block. Two -// executions that produce the same fingerprint took observationally -// equivalent paths (though they may differ internally). -type PathFingerprint struct { - Results []uint64 // the result words from the arg block -} - -// ProfilePaths runs the function with each of the given argument blocks and -// collects the distinct output fingerprints. This measures path diversity: -// how many observationally different execution paths the input corpus -// exercises. Combined with Blocks (the static block count), it gives a -// lower bound on code coverage. -func (k *Kernel) ProfilePaths(name string, argSets [][]byte, resultOffsets []int) ([]PathFingerprint, error) { - idx, ok := k.funcs[name] - if !ok { - return nil, fmt.Errorf("verify: function %q not found", name) - } - fl := k.img.Funcs[idx] - - seen := map[string]bool{} - var paths []PathFingerprint - - for _, args := range argSets { - if len(args) < fl.Args { - return nil, fmt.Errorf("verify: %s: arg block too small", name) - } - out, err := k.CallFunc(name, args) - if err != nil { - return nil, err - } - fp := PathFingerprint{} - key := "" - for _, off := range resultOffsets { - v := GetUint64(out, off) - fp.Results = append(fp.Results, v) - key += fmt.Sprintf("%016x", v) - } - if !seen[key] { - seen[key] = true - paths = append(paths, fp) - } - } - return paths, nil -} diff --git a/verify/fuzz.go b/verify/fuzz.go index edf6d94..3646f3a 100644 --- a/verify/fuzz.go +++ b/verify/fuzz.go @@ -177,7 +177,7 @@ func (k *Kernel) FuzzFunc(name string, sig funcSig, goCode []byte, iterations in return result } - for i := 0; i < iterations; i++ { + for i := range iterations { // Generate inputs and build TWO independent arg blocks (one per // version) so that functions which write to their arguments // (e.g. histogram increments) don't corrupt the other's input. diff --git a/verify/groundtruth.go b/verify/groundtruth.go index 6a5757f..f911097 100644 --- a/verify/groundtruth.go +++ b/verify/groundtruth.go @@ -38,6 +38,17 @@ func GroundTruthLOONG64(path string) (map[string][]byte, error) { return groundTruthArch(path, "loong64") } +// goRoot locates the GOROOT of the `go` binary on PATH: the build-time +// runtime.GOROOT is meaningless for a relocated binary, so the toolchain is +// asked directly. +func goRoot() (string, error) { + out, err := exec.Command("go", "env", "GOROOT").Output() + if err != nil { + return "", fmt.Errorf("verify: go env GOROOT: %w", err) + } + return strings.TrimSpace(string(out)), nil +} + // GroundTruthARM64 assembles the given .s file with the Go toolchain in // AArch64 cross-assembly mode (GOARCH=arm64). func GroundTruthARM64(path string) (map[string][]byte, error) { @@ -45,7 +56,10 @@ func GroundTruthARM64(path string) (map[string][]byte, error) { } func groundTruthArch(path, goarch string) (map[string][]byte, error) { - goroot := runtime.GOROOT() + goroot, err := goRoot() + if err != nil { + return nil, err + } asmBin := filepath.Join(goroot, "pkg", "tool", runtime.GOOS+"_"+runtime.GOARCH, "asm") if _, err := os.Stat(asmBin); err != nil { return nil, fmt.Errorf("verify: go tool asm not found at %s: %w", asmBin, err) diff --git a/verify/verify.go b/verify/verify.go index 5906dac..1a9a44e 100644 --- a/verify/verify.go +++ b/verify/verify.go @@ -144,7 +144,7 @@ func (k *Kernel) FuzzFuncChecked(name string, sig funcSig, iterations int, seed } violations := 0 - for i := 0; i < iterations; i++ { + for i := range iterations { gasmArgs, _, bufs := genDualArgs(rng, sig, fl.Args) result.CrashInput = gasmArgs