From 9beff4ae8593e079be090cb4091f7f82aa9d5e11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Sat, 29 Aug 2026 15:40:31 +0200 Subject: [PATCH] style: modernize to splitseq, cut, min, maps.copy and range-over-int --- AGENTS.md | 119 ------------------------------- asm/aarch64_goobj_test.go | 2 +- asm/arm64_assemble.go | 4 +- asm/elf_test.go | 2 +- asm/elfloong64_test.go | 2 +- asm/encode.go | 2 +- asm/evex.go | 12 ++-- asm/goobj.go | 2 +- asm/goobj_dwarf.go | 5 +- asm/goobj_resolve.go | 4 +- asm/goobj_test.go | 4 +- asm/l64_goobj_test.go | 4 +- asm/link.go | 2 +- asm/loong64_encode.go | 8 +-- asm/reg.go | 8 +-- asm/riscv_goobj_test.go | 6 +- cmd/gasm/audit.go | 2 +- cmd/gasm/debug_linux.go | 6 +- cmd/gasm/main.go | 34 +++------ cmd/gasm/scaffold.go | 2 +- debug/breakpoint.go | 8 ++- debug/debug_test.go | 4 +- debug/disasm_linux_amd64.go | 9 +-- debug/ptrace_linux.go | 13 ++-- debug/ptrace_linux_amd64.go | 4 +- debug/repl.go | 7 +- debug/stopinfo_linux.go | 2 +- debug/target_linux.go | 2 +- debug/tracer.go | 4 +- debug/watchpoint_linux_amd64.go | 4 +- go.mod | 2 +- go.sum | 2 + lint/abi.go | 2 +- lsp/protocol.go | 2 +- verify/arm64_groundtruth_test.go | 5 +- verify/buffers.go | 4 +- verify/fuzz.go | 9 +-- verify/groundtruth.go | 2 +- verify/l64_groundtruth_test.go | 5 +- verify/sigparse.go | 4 +- 40 files changed, 90 insertions(+), 234 deletions(-) delete mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 816bc26..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,119 +0,0 @@ -# AGENTS.md — gasm-devkit - -Repository rules for AI agents and contributors. Read before modifying any -code in this repository. - -## AI Contribution Policy - -AI agents may assist with code, documentation, tests, and review in this -repository. All AI-assisted changes must: - -- Follow the code style and conventions in this file. -- Include the trailer `Assisted-by: ` in every commit message. -- Not commit directly to `main` — work on `development`. -- Pass the full Definition of Done before any commit. - -## Workflow - -- **Branching.** `development` is the working branch. `main` is - release-only: merge from `development`, then tag. Never commit directly - to `main`. -- **Release procedure.** - 1. Bump `version` in `justfile` and `cmd/gasm/main.go`. - 2. Update `CHANGELOG.md` with a new `## [X.Y.Z] — YYYY-MM-DD` section. - 3. Update `README.md` and `docs/ARCHITECTURE.md` if user-visible - behaviour changed. - 4. Run the Definition of Done (below). - 5. Commit on `development`. - 6. `git checkout main && git merge --ff-only development`. - 7. `git tag vX.Y.Z`. - 8. `git checkout development`. - 9. `GOBIN=~/.local/bin just install-bin`. - -## Commit Messages - -Conventional Commits, subject line only, imperative mood, lowercase after -the colon: - -``` -feat(asm): add EVEX gather and scatter with VSIB addressing -``` - -Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, -`chore`, `ci`, `build`, `revert`. - -Every commit ends with exactly one trailer, using the model that -assisted with the change: - -``` -Assisted-by: -``` - -Replace `` with the actual model (e.g. `DeepSeek V4 Pro`). - -No body, no footers, no trailing period on the subject. - -## Code Style - -Language: Go 1.27 (`toolchain go1.27.0`). - -### Formatter - -`gofmt` — zero diff. Run `just fmt` before committing. - -### Linter - -`go vet` — zero warnings. Run `just build` before committing. - -### Tests - -`go test -race -count=1 ./...` — all green, coverage ≥ 80 % (hard gate, -enforced by `just test`). - -### Dependencies - -- **Production code:** standard library only. No third-party imports in - shipped code. -- **Test code:** `golang.org/x/arch` is the sole test dependency (decode - oracle for round-trip validation). It is never linked into the binary. -- **No cgo, no C, no external toolchains, no JavaScript.** - -### Error Handling - -Explicit `if err != nil`. Wrap with `fmt.Errorf("context: %w", err)`. -No panics outside `main`. The one exception: the JIT trampoline's -`recover`-guarded decoder hot path, which converts bounds panics to -sentinel errors. - -### Assembly - -Plan 9 syntax (Go's assembler dialect). Hand-written — no code generators -except `_gen/gen.go` for instruction tables (which parses the Go -toolchain source). Every instruction table is committed; no runtime -dependency on the Go toolchain. - -### File Naming - -- `_amd64.s`, `_arm64.s`, `_riscv64.s`, `_loong64.s` for - architecture-specific assembly. -- `_linux_amd64.go` for platform-specific Go files. -- `_test.go` suffix for test files. - -## Definition of Done - -A task is not complete until all of these pass: - -1. `just build` — `go vet` + `gofmt` check, zero errors, zero warnings. -2. `just test` — full suite with `-race`, coverage ≥ 80 %. -3. `just fmt` — produces no diff. -4. Diagnostics — zero warnings across the project. -5. Non-trivial changes reviewed. - -## Licence - -BSD-3-Clause. Every source file carries the SPDX header: - -``` -// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) -// SPDX-License-Identifier: BSD-3-Clause -``` diff --git a/asm/aarch64_goobj_test.go b/asm/aarch64_goobj_test.go index 9a7bebd..e8e11d6 100644 --- a/asm/aarch64_goobj_test.go +++ b/asm/aarch64_goobj_test.go @@ -110,7 +110,7 @@ func main() { t.Fatalf("baseline build: %v\n%s", err, buildLog) } var work, linkLine, asmObj string - for _, line := range strings.Split(string(buildLog), "\n") { + for line := range strings.SplitSeq(string(buildLog), "\n") { switch { case strings.HasPrefix(line, "WORK="): work = strings.TrimPrefix(line, "WORK=") diff --git a/asm/arm64_assemble.go b/asm/arm64_assemble.go index 918c9f9..9912823 100644 --- a/asm/arm64_assemble.go +++ b/asm/arm64_assemble.go @@ -638,7 +638,7 @@ func encodeARM64LoadImm(rd int, v int64, mnem string) ([]byte, error) { // Multi-instruction: MOVZ + MOVK for each non-zero16-bit chunk. var ws []uint32 first := true - for i := 0; i < 4; i++ { + for i := range 4 { chunk := (d >> uint(i*16)) & 0xFFFF if chunk == 0 { continue @@ -679,7 +679,7 @@ func arm64Bitmask(v uint64, sf int) (N, immr, imms uint32, ok bool) { } // Check each rotation: is the rotated pattern a contiguous block of 1s at the LSB? - for r := uint(0); r < esize; r++ { + for r := range esize { rotated := (pattern >> r) | ((pattern << (esize - r)) & emask) if rotated == 0 { continue diff --git a/asm/elf_test.go b/asm/elf_test.go index 7316294..544a8de 100644 --- a/asm/elf_test.go +++ b/asm/elf_test.go @@ -187,7 +187,7 @@ func TestELFObject(t *testing.T) { end := bytes.IndexByte(strtabRaw[stName:], 0) return string(strtabRaw[stName : int(stName)+end]) } - for i := 0; i < 2; i++ { + for i := range 2 { e := raw[i*24 : (i+1)*24] off := binary.LittleEndian.Uint64(e[0:]) info := binary.LittleEndian.Uint64(e[8:]) diff --git a/asm/elfloong64_test.go b/asm/elfloong64_test.go index 2a2bd0f..a53d9aa 100644 --- a/asm/elfloong64_test.go +++ b/asm/elfloong64_test.go @@ -139,7 +139,7 @@ DATA answer<>+0(SB)/8, $42 t.Fatalf(".rela.text has %d bytes, want two 24-byte entries", len(raw)) } le := binary.LittleEndian - for i := 0; i < 2; i++ { + for i := range 2 { e := raw[i*24 : (i+1)*24] off := le.Uint64(e[0:]) info := le.Uint64(e[8:]) diff --git a/asm/encode.go b/asm/encode.go index e5dba0d..24af92d 100644 --- a/asm/encode.go +++ b/asm/encode.go @@ -419,7 +419,7 @@ func le16(v int64) []byte { func le64(v int64) []byte { u := uint64(v) b := make([]byte, 8) - for i := 0; i < 8; i++ { + for i := range 8 { b[i] = byte(u >> (8 * i)) } return b diff --git a/asm/evex.go b/asm/evex.go index 555f82d..d210640 100644 --- a/asm/evex.go +++ b/asm/evex.go @@ -603,12 +603,12 @@ func (s evexSuffix) evexOnly() bool { // broadcast together with rounding/SAE. func parseEvexSuffix(mnem string) (string, evexSuffix, error) { sfx := evexSuffix{rounding: -1} - i := strings.IndexByte(mnem, '.') - if i < 0 { + before, after, ok := strings.Cut(mnem, ".") + if !ok { return mnem, sfx, nil } - base := mnem[:i] - parts := strings.Split(mnem[i+1:], ".") + base := before + parts := strings.Split(after, ".") seen := map[string]bool{} for j, p := range parts { if seen[p] { @@ -618,7 +618,7 @@ func parseEvexSuffix(mnem string) (string, evexSuffix, error) { switch p { case "Z": if j != len(parts)-1 { - return "", sfx, fmt.Errorf("the .Z suffix must come last in %q", mnem[i+1:]) + return "", sfx, fmt.Errorf("the .Z suffix must come last in %q", after) } sfx.zeroing = true case "SAE": @@ -638,7 +638,7 @@ func parseEvexSuffix(mnem string) (string, evexSuffix, error) { } } if sfx.bcst && (sfx.sae || sfx.rounding >= 0) { - return "", sfx, fmt.Errorf("cannot combine .BCST with rounding or SAE in %q", mnem[i+1:]) + return "", sfx, fmt.Errorf("cannot combine .BCST with rounding or SAE in %q", after) } return base, sfx, nil } diff --git a/asm/goobj.go b/asm/goobj.go index 00c2895..50787b9 100644 --- a/asm/goobj.go +++ b/asm/goobj.go @@ -464,7 +464,7 @@ func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, r auxIdxBlk := make([]byte, 0, 4*(nsyms+1)) dataIdxBlk := make([]byte, 0, 4*(nsyms+1)) var nr, na, nd uint32 - for si := 0; si < nsyms; si++ { + for si := range nsyms { relocIdxBlk = binary.LittleEndian.AppendUint32(relocIdxBlk, nr) auxIdxBlk = binary.LittleEndian.AppendUint32(auxIdxBlk, na) dataIdxBlk = binary.LittleEndian.AppendUint32(dataIdxBlk, nd) diff --git a/asm/goobj_dwarf.go b/asm/goobj_dwarf.go index f26c517..7a16fda 100644 --- a/asm/goobj_dwarf.go +++ b/asm/goobj_dwarf.go @@ -139,10 +139,7 @@ func dwSelectOpcode(deltaPC uint64, deltaLC int64) int64 { return int64(dwOpcodeBase) + (deltaLC - dwLineBase) + dwLineRange*int64(deltaPC) default: if deltaPC <= uint64(dwPCRange) { - op := int64(dwOpcodeBase) + (dwLineRange - 1) + dwLineRange*int64(deltaPC) - if op > 255 { - op = 255 - } + op := min(int64(dwOpcodeBase)+(dwLineRange-1)+dwLineRange*int64(deltaPC), 255) return op } switch deltaPC - uint64(dwPCRange) { diff --git a/asm/goobj_resolve.go b/asm/goobj_resolve.go index 6212adc..9059b83 100644 --- a/asm/goobj_resolve.go +++ b/asm/goobj_resolve.go @@ -323,8 +323,8 @@ func splitQualified(full string) (pkg, name string) { if idx := strings.IndexByte(full, '\u00b7'); idx >= 0 { return full[:idx], full[idx+len("\u00b7"):] } - if idx := strings.IndexByte(full, '.'); idx >= 0 { - return full[:idx], full[idx+1:] + if before, after, ok := strings.Cut(full, "."); ok { + return before, after } return "", full } diff --git a/asm/goobj_test.go b/asm/goobj_test.go index 93425e3..66fc0df 100644 --- a/asm/goobj_test.go +++ b/asm/goobj_test.go @@ -393,7 +393,7 @@ func main() { } var work string var asmObj, pkgArch, linkLine string - for _, line := range strings.Split(string(buildLog), "\n") { + for line := range strings.SplitSeq(string(buildLog), "\n") { switch { case strings.HasPrefix(line, "WORK="): work = strings.TrimPrefix(line, "WORK=") @@ -461,7 +461,7 @@ func main() { newArch := filepath.Join(dir, "pkg.a") args := []string{"tool", "pack", "c", newArch} seen := map[string]bool{} - for _, m := range strings.Fields(string(listOut)) { + for m := range strings.FieldsSeq(string(listOut)) { if seen[m] { continue } diff --git a/asm/l64_goobj_test.go b/asm/l64_goobj_test.go index 75b7852..3e1fc1a 100644 --- a/asm/l64_goobj_test.go +++ b/asm/l64_goobj_test.go @@ -214,7 +214,7 @@ func main() { t.Fatalf("baseline build: %v\n%s", err, buildLog) } var pkgArch, work, linkLine, asmObj string - for _, line := range strings.Split(string(buildLog), "\n") { + for line := range strings.SplitSeq(string(buildLog), "\n") { switch { case strings.HasPrefix(line, "WORK="): work = strings.TrimPrefix(line, "WORK=") @@ -278,7 +278,7 @@ func main() { newArch := filepath.Join(dir, "pkg.a") args := []string{"tool", "pack", "c", newArch} seen := map[string]bool{} - for _, m := range strings.Fields(string(listOut)) { + for m := range strings.FieldsSeq(string(listOut)) { if seen[m] { continue } diff --git a/asm/link.go b/asm/link.go index a9f51ad..d2eea7d 100644 --- a/asm/link.go +++ b/asm/link.go @@ -488,7 +488,7 @@ func collectData(f *ast.File) ([]dataSym, error) { if dd.Value.Imm.Neg { v = -v } - for j := 0; j < w; j++ { + for j := range w { buf[off+int64(j)] = byte(v >> (8 * j)) } } diff --git a/asm/loong64_encode.go b/asm/loong64_encode.go index 66a7961..e66a899 100644 --- a/asm/loong64_encode.go +++ b/asm/loong64_encode.go @@ -30,6 +30,8 @@ package asm // of the immediate and register fields), mirroring the toolchain's OP_* // helpers, so each l64* function only ORs its fields in. +import "maps" + // loong64RegNum returns the 5-bit register number for a LoongArch register // name: R0–R31 (integer), F0–F31 (floating point), FCC0–FCC7 (condition // flags), FCSR0–FCSR31 (control/status) and the ABI aliases the runtime's @@ -368,7 +370,7 @@ func init() { // The dual-form arithmetic mnemonics (register 3R + immediate 2RI12), // selected by the operand kind; the shift mnemonics pair the 3R form // with a 5/6-bit shift immediate. - for m, e := range map[string]l64DualEnc{ + maps.Copy(l64DualTable, map[string]l64DualEnc{ "ADD": {rrr: 0x20 << 15, imm: 0x00a << 22}, "ADDW": {rrr: 0x20 << 15, imm: 0x00a << 22}, "ADDV": {rrr: 0x21 << 15, imm: 0x00b << 22}, @@ -386,9 +388,7 @@ func init() { "SRLV": {rrr: 0x32 << 15, imm: 0x0045 << 16, shift: true}, "SRAV": {rrr: 0x33 << 15, imm: 0x0049 << 16, shift: true}, "ROTRV": {rrr: 0x37 << 15, imm: 0x004d << 16, shift: true}, - } { - l64DualTable[m] = e - } + }) // 2RI12 — pure immediate arithmetic (LU52ID has no register form). l64InstrTable["LU52ID"] = l64Enc{format: l64Firr, op: 0x00c << 22} diff --git a/asm/reg.go b/asm/reg.go index 2760a45..e2bf108 100644 --- a/asm/reg.go +++ b/asm/reg.go @@ -7,6 +7,8 @@ // by round-tripping through golang.org/x/arch's decoder in the tests. package asm +import "maps" + import "strings" // Reg is an x86-64 register. In Plan 9 assembly the classic names (AX, BX, …) @@ -121,13 +123,11 @@ func buildRegByName() map[string]Reg { } // 8-bit: AL..BH, SPL..DIL, R8B..R15B. - for n, r := range map[string]Reg{ + maps.Copy(m, map[string]Reg{ "AL": AL, "CL": CL, "DL": DL, "BL": BL, "AH": AH, "CH": CH, "DH": DH, "BH": BH, "SPL": SPL, "BPL": BPL, "SIL": SIL, "DIL": DIL, - } { - m[n] = r - } + }) for i := 8; i <= 15; i++ { m["R"+itoa(i)+"B"] = Reg{idx: i, size: 1} } diff --git a/asm/riscv_goobj_test.go b/asm/riscv_goobj_test.go index 3a2e6b2..3ba768d 100644 --- a/asm/riscv_goobj_test.go +++ b/asm/riscv_goobj_test.go @@ -141,7 +141,7 @@ DATA answer<>+0(SB)/8, $42 first := int(le.Uint32(relocIdx[4*(4+4):])) wantType := []uint16{relocRISCVPcrelItype, relocRISCVPcrelItype, relocRISCVPcrelStype} wantOffAbs := []int{0, 8, 16} - for i := 0; i < 3; i++ { + for i := range 3 { e := relocs[(first+i)*23:] if int32(le.Uint32(e[0:])) != int32(wantOffAbs[i]) || e[4] != 8 || le.Uint16(e[5:]) != wantType[i] || le.Uint32(e[15:]) != pkgIdxSelf || le.Uint32(e[19:]) != 0 { @@ -221,7 +221,7 @@ func main() { t.Fatalf("baseline build: %v\n%s", err, buildLog) } var pkgArch, work, linkLine, asmObj string - for _, line := range strings.Split(string(buildLog), "\n") { + for line := range strings.SplitSeq(string(buildLog), "\n") { switch { case strings.HasPrefix(line, "WORK="): work = strings.TrimPrefix(line, "WORK=") @@ -279,7 +279,7 @@ func main() { newArch := filepath.Join(dir, "pkg.a") args := []string{"tool", "pack", "c", newArch} seen := map[string]bool{} - for _, m := range strings.Fields(string(listOut)) { + for m := range strings.FieldsSeq(string(listOut)) { if seen[m] { continue } diff --git a/cmd/gasm/audit.go b/cmd/gasm/audit.go index 47b2116..08c99d2 100644 --- a/cmd/gasm/audit.go +++ b/cmd/gasm/audit.go @@ -171,7 +171,7 @@ func probeGoAsm(names []string) (map[string]bool, error) { result[name] = true // no news = the name parsed fine } reParse := regexp.MustCompile(`probe\.s:(\d+):`) - for _, l := range strings.Split(string(out), "\n") { + for l := range strings.SplitSeq(string(out), "\n") { m := reParse.FindStringSubmatch(l) if m == nil { continue diff --git a/cmd/gasm/debug_linux.go b/cmd/gasm/debug_linux.go index b73c4f6..fbc2251 100644 --- a/cmd/gasm/debug_linux.go +++ b/cmd/gasm/debug_linux.go @@ -150,7 +150,7 @@ REPL commands: // Find the buffer size from the spec. size := parseBufSize(*bufSpec, name) // Length at offset+8, capacity at offset+16. - for j := 0; j < 8; j++ { + for j := range 8 { argBlock[off+8+j] = byte(size >> (j * 8)) argBlock[off+16+j] = byte(size >> (j * 8)) } @@ -260,7 +260,7 @@ REPL commands: // Format: name:size:pattern[,name:size:pattern...] func parseBufNames(spec string) []string { var names []string - for _, part := range strings.Split(spec, ",") { + for part := range strings.SplitSeq(spec, ",") { fields := strings.SplitN(part, ":", 3) if len(fields) >= 1 && fields[0] != "" { names = append(names, fields[0]) @@ -271,7 +271,7 @@ func parseBufNames(spec string) []string { // parseBufSize extracts the size of a named buffer from a buffer specification. func parseBufSize(spec, name string) int { - for _, part := range strings.Split(spec, ",") { + for part := range strings.SplitSeq(spec, ",") { fields := strings.SplitN(part, ":", 3) if len(fields) >= 2 && fields[0] == name { var size int diff --git a/cmd/gasm/main.go b/cmd/gasm/main.go index ef753d5..68fe12f 100644 --- a/cmd/gasm/main.go +++ b/cmd/gasm/main.go @@ -359,7 +359,7 @@ unreachable-code, register-clobber and funcdata-pcdata. return 2 } disabled := map[string]bool{} - for _, code := range strings.Split(*disable, ",") { + for code := range strings.SplitSeq(*disable, ",") { if code = strings.TrimSpace(code); code != "" { disabled[code] = true } @@ -460,10 +460,7 @@ requires -p, the package path, and the installed Go toolchain). code := img.Code[fn.Offset : fn.Offset+fn.Size] fmt.Printf("%s: %d bytes\n", fn.Name, fn.Size) for i := 0; i < len(code); i += 16 { - end := i + 16 - if end > len(code) { - end = len(code) - } + end := min(i+16, len(code)) fmt.Printf(" %04x:", i) for _, b := range code[i:end] { fmt.Printf(" %02x", b) @@ -485,10 +482,7 @@ requires -p, the package path, and the installed Go toolchain). fmt.Printf(" %s: %d bytes at 0x%x\n", g.Name.Name, size, img.Symbols[g.Name.Name]) } for i := 0; i < len(img.Data); i += 16 { - end := i + 16 - if end > len(img.Data) { - end = len(img.Data) - } + end := min(i+16, len(img.Data)) fmt.Printf(" %04x:", len(img.Code)+i) for _, b := range img.Data[i:end] { fmt.Printf(" %02x", b) @@ -570,7 +564,7 @@ e.g. --map wideCopyAVX2=wideCopyAVX512 pairs the two regardless of suffix. // Parse the name mapping (file1 name → file2 name). nameMap := make(map[string]string) if *mapSpec != "" { - for _, pair := range strings.Split(*mapSpec, ",") { + for pair := range strings.SplitSeq(*mapSpec, ",") { old, new, ok := strings.Cut(pair, "=") if !ok || old == "" || new == "" { fmt.Fprintf(os.Stderr, "gasm diff: invalid --map pair %q (expected old=new)\n", pair) @@ -679,10 +673,7 @@ func assemblePath(path string) (*asm.Image, error) { // printByteDiff shows the first few byte differences between two code blocks. func printByteDiff(a, b []byte) { - maxLen := len(a) - if len(b) < maxLen { - maxLen = len(b) - } + maxLen := min(len(b), len(a)) shown := 0 for i := 0; i < maxLen && shown < 8; i++ { if a[i] != b[i] { @@ -1344,8 +1335,8 @@ func fuzzInSubprocess(path, funcName string, n int) string { } } // Non-zero exit without a signal: the fuzz reported mismatches. - lines := strings.Split(strings.TrimSpace(string(out)), "\n") - for _, l := range lines { + lines := strings.SplitSeq(strings.TrimSpace(string(out)), "\n") + for l := range lines { if strings.Contains(l, funcName) { return strings.TrimSpace(l) } @@ -1353,8 +1344,8 @@ func fuzzInSubprocess(path, funcName string, n int) string { return fmt.Sprintf("%s: FAIL (exit %v)", funcName, err) } // Success: extract the result line. - lines := strings.Split(strings.TrimSpace(string(out)), "\n") - for _, l := range lines { + lines := strings.SplitSeq(strings.TrimSpace(string(out)), "\n") + for l := range lines { if strings.Contains(l, funcName) { return strings.TrimSpace(l) } @@ -1400,7 +1391,7 @@ func sweepInSubprocess(path, funcName string, smoke, abi bool, abiN int) (string // dropping the child's own file/function banners (the parent prints those). func sweepCheckLines(out []byte) string { var lines []string - for _, l := range strings.Split(string(out), "\n") { + for l := range strings.SplitSeq(string(out), "\n") { t := strings.TrimSpace(l) if strings.HasPrefix(t, "smoke:") || strings.HasPrefix(t, "abi:") { lines = append(lines, " "+t) @@ -1570,10 +1561,7 @@ func formatResults(rs []verify.Param) string { // hexDump returns a one-line hex dump of buf, truncated to 64 bytes. func hexDump(buf []byte) string { const max = 64 - n := len(buf) - if n > max { - n = max - } + n := min(len(buf), max) var sb strings.Builder for i := range n { if i > 0 { diff --git a/cmd/gasm/scaffold.go b/cmd/gasm/scaffold.go index edab81d..da3dd67 100644 --- a/cmd/gasm/scaffold.go +++ b/cmd/gasm/scaffold.go @@ -136,7 +136,7 @@ type sigResult struct { // parseSig parses the // func signature of a doc comment. func parseSig(doc string) ([]sigParam, []sigResult, bool) { var line string - for _, l := range strings.Split(doc, "\n") { + for l := range strings.SplitSeq(doc, "\n") { if t := strings.TrimSpace(l); strings.HasPrefix(t, "func ") { line = t break diff --git a/debug/breakpoint.go b/debug/breakpoint.go index 53ee4b1..62aa831 100644 --- a/debug/breakpoint.go +++ b/debug/breakpoint.go @@ -5,6 +5,8 @@ package debug +import "strings" + import "fmt" // Breakpoint is one INT3 breakpoint in the debuggee. @@ -119,7 +121,7 @@ func (bm *Breakpoints) Info() string { if len(bm.bps) == 0 { return "no breakpoints set\n" } - result := "" + var result strings.Builder i := 0 for _, bp := range bm.bps { i++ @@ -135,9 +137,9 @@ func (bm *Breakpoints) Info() string { if bp.Cond != nil { cond = fmt.Sprintf(" if %s %s %#x", bp.Cond.Reg, bp.Cond.Op, bp.Cond.Value) } - result += fmt.Sprintf(" %d: %s at %#x [%s, %d hits]%s\n", i, label, bp.Addr, status, bp.hits, cond) + result.WriteString(fmt.Sprintf(" %d: %s at %#x [%s, %d hits]%s\n", i, label, bp.Addr, status, bp.hits, cond)) } - return result + return result.String() } // Clear removes the breakpoint at addr, restoring the original byte. diff --git a/debug/debug_test.go b/debug/debug_test.go index ce4225d..63a7a8a 100644 --- a/debug/debug_test.go +++ b/debug/debug_test.go @@ -279,7 +279,7 @@ func TestWatchpointSlotTracking(t *testing.T) { s := &Session{} // All four slots are free initially. - for i := 0; i < 4; i++ { + for i := range 4 { if s.IsWatchpointSlotUsed(i) { t.Errorf("slot %d should be free initially", i) } @@ -317,7 +317,7 @@ func TestWatchpointSlotTracking(t *testing.T) { } // Mark all slots used: FindFreeWatchpointSlot returns -1. - for i := 0; i < 4; i++ { + for i := range 4 { wpSlots[i] = true } if got := s.FindFreeWatchpointSlot(); got != -1 { diff --git a/debug/disasm_linux_amd64.go b/debug/disasm_linux_amd64.go index 24ac13f..8cada40 100644 --- a/debug/disasm_linux_amd64.go +++ b/debug/disasm_linux_amd64.go @@ -7,6 +7,7 @@ package debug import ( "fmt" + "strings" "golang.org/x/arch/x86/x86asm" ) @@ -34,19 +35,19 @@ func (s *Session) Disassemble(addr uint64) (string, int, error) { // DisassembleN decodes up to n instructions starting at addr and returns // them as a formatted string with addresses and byte offsets. func (s *Session) DisassembleN(addr uint64, n int) string { - var result string + var result strings.Builder pc := addr for range n { text, length, err := s.Disassemble(pc) if err != nil { - result += fmt.Sprintf(" %#08x: \n", pc, err) + result.WriteString(fmt.Sprintf(" %#08x: \n", pc, err)) break } - result += fmt.Sprintf(" %#08x: %s\n", pc, text) + result.WriteString(fmt.Sprintf(" %#08x: %s\n", pc, text)) if length == 0 { length = 1 } pc += uint64(length) } - return result + return result.String() } diff --git a/debug/ptrace_linux.go b/debug/ptrace_linux.go index 81a6d3e..e9cb95d 100644 --- a/debug/ptrace_linux.go +++ b/debug/ptrace_linux.go @@ -72,7 +72,7 @@ func LaunchWithBuffers(gasmBin, asmPath, funcName string, args []byte, bufSpec s s := &Session{pid: cmd.Process.Pid, cmd: cmd} readyFile := filepath.Join(tmpDir, "ready") - for i := 0; i < 500; i++ { + for range 500 { if _, err := os.Stat(readyFile); err == nil { break } @@ -86,7 +86,7 @@ func LaunchWithBuffers(gasmBin, asmPath, funcName string, args []byte, bufSpec s } entryFile := filepath.Join(tmpDir, "entry") - for i := 0; i < 500; i++ { + for range 500 { if _, err := os.Stat(entryFile); err == nil { break } @@ -115,7 +115,7 @@ func LaunchWithBuffers(gasmBin, asmPath, funcName string, args []byte, bufSpec s if bufSpec != "" { addrFile := filepath.Join(tmpDir, "bufaddrs") if data, err := os.ReadFile(addrFile); err == nil { - for _, line := range strings.Split(strings.TrimSpace(string(data)), "\n") { + for line := range strings.SplitSeq(strings.TrimSpace(string(data)), "\n") { var addr uint64 if _, err := fmt.Sscanf(line, "%d", &addr); err == nil { bufAddrs = append(bufAddrs, addr) @@ -190,10 +190,7 @@ func (s *Session) ReadMemory(addr uint64, length int) ([]byte, error) { // WriteMemory writes bytes to the debuggee's memory at addr. func (s *Session) WriteMemory(addr uint64, data []byte) error { for i := 0; i < len(data); i += 8 { - end := i + 8 - if end > len(data) { - end = len(data) - } + end := min(i+8, len(data)) var word uint64 for j := 0; j < end-i; j++ { word |= uint64(data[i+j]) << (8 * j) @@ -275,7 +272,7 @@ func findRWXMapping(pid int) uint64 { if err != nil { return 0 } - for _, line := range strings.Split(string(data), "\n") { + for line := range strings.SplitSeq(string(data), "\n") { fields := strings.Fields(line) if len(fields) < 2 { continue diff --git a/debug/ptrace_linux_amd64.go b/debug/ptrace_linux_amd64.go index 3f0dbf9..f6277f2 100644 --- a/debug/ptrace_linux_amd64.go +++ b/debug/ptrace_linux_amd64.go @@ -89,8 +89,8 @@ func (s *Session) GetVectorRegs() (VectorRegs, error) { if err != nil { return v, err } - for i := 0; i < 16; i++ { - for j := 0; j < 16; j++ { + for i := range 16 { + for j := range 16 { v.YMM[i][j] = fp.XMM[i][j] } } diff --git a/debug/repl.go b/debug/repl.go index b4d3d74..1c9baae 100644 --- a/debug/repl.go +++ b/debug/repl.go @@ -297,7 +297,7 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar continue } if v > 255 { - for j := 0; j < 8; j++ { + for j := range 8 { bytes = append(bytes, byte(v>>(8*j))) } } else { @@ -482,10 +482,7 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar func hexDump(addr uint64, data []byte) { for i := 0; i < len(data); i += 16 { - end := i + 16 - if end > len(data) { - end = len(data) - } + end := min(i+16, len(data)) fmt.Printf(" %#08x:", addr+uint64(i)) for j := i; j < i+16; j++ { if j < end { diff --git a/debug/stopinfo_linux.go b/debug/stopinfo_linux.go index 05804a5..fe19e95 100644 --- a/debug/stopinfo_linux.go +++ b/debug/stopinfo_linux.go @@ -61,7 +61,7 @@ func (s *Session) StopInfo() (StopReason, uint64) { case trapBRKPT: return StopBreakpoint, 0 case trapHWBRKPT: - addr := *(*uint64)(unsafe.Pointer(uintptr(unsafe.Pointer(&info)) + 16)) + addr := *(*uint64)(unsafe.Add(unsafe.Pointer(&info), 16)) return StopWatchpoint, addr default: return StopSingleStep, 0 diff --git a/debug/target_linux.go b/debug/target_linux.go index 5300ee1..baf7a1d 100644 --- a/debug/target_linux.go +++ b/debug/target_linux.go @@ -37,7 +37,7 @@ func setupBuffers(spec string, args []byte, argSize int, tmpDir string) ([]byte, pattern string } var specs []bufSpec - for _, part := range strings.Split(spec, ",") { + for part := range strings.SplitSeq(spec, ",") { fields := strings.SplitN(part, ":", 3) if len(fields) != 3 { continue diff --git a/debug/tracer.go b/debug/tracer.go index 796106e..27c1a04 100644 --- a/debug/tracer.go +++ b/debug/tracer.go @@ -36,7 +36,7 @@ func newMockTracer() *mockTracer { func (m *mockTracer) Peek(addr uint64) (uint64, error) { m.peeks = append(m.peeks, addr) var val uint64 - for i := uint64(0); i < 8; i++ { + for i := range uint64(8) { val |= uint64(m.mem[addr+i]) << (i * 8) } return val, nil @@ -47,7 +47,7 @@ func (m *mockTracer) Poke(addr uint64, val uint64) error { addr uint64 val uint64 }{addr, val}) - for i := uint64(0); i < 8; i++ { + for i := range uint64(8) { m.mem[addr+i] = byte(val >> (i * 8)) } return nil diff --git a/debug/watchpoint_linux_amd64.go b/debug/watchpoint_linux_amd64.go index 98fb172..7c0affb 100644 --- a/debug/watchpoint_linux_amd64.go +++ b/debug/watchpoint_linux_amd64.go @@ -26,7 +26,7 @@ var wpSlots [4]bool // FindFreeWatchpointSlot returns the index of the first free watchpoint slot // (0-3), or -1 if all four hardware watchpoints are in use. func (s *Session) FindFreeWatchpointSlot() int { - for i := 0; i < 4; i++ { + for i := range 4 { if !wpSlots[i] { return i } @@ -122,7 +122,7 @@ func (s *Session) ClearWatchpoint(slot int) error { // ClearAllWatchpoints removes all hardware watchpoints. func (s *Session) ClearAllWatchpoints() error { - for slot := 0; slot < 4; slot++ { + for slot := range 4 { if wpSlots[slot] { if err := s.ClearWatchpoint(slot); err != nil { return err diff --git a/go.mod b/go.mod index 2ad9bdf..5a91796 100644 --- a/go.mod +++ b/go.mod @@ -4,4 +4,4 @@ go 1.27 toolchain go1.27.0 -require golang.org/x/arch v0.29.0 +require golang.org/x/arch v0.30.0 diff --git a/go.sum b/go.sum index 8d13548..d9adce7 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ golang.org/x/arch v0.29.0 h1:8sSET5wB0+exBm0FGmOtdHMqjlRdV2DRD3/IV6OZgho= golang.org/x/arch v0.29.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= +golang.org/x/arch v0.30.0 h1:sB9h+1gRGa2+LauFSV0tm8bK1J2yo1bx6/Uyi/P6DTU= +golang.org/x/arch v0.30.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= diff --git a/lint/abi.go b/lint/abi.go index be29aa6..b2e7de8 100644 --- a/lint/abi.go +++ b/lint/abi.go @@ -74,7 +74,7 @@ func parseSignature(sig string) (*ast.FuncDecl, bool) { // signatureLine returns the first `func …` line from a doc comment, trimmed. func signatureLine(doc string) string { - for _, line := range strings.Split(doc, "\n") { + for line := range strings.SplitSeq(doc, "\n") { if t := strings.TrimSpace(line); strings.HasPrefix(t, "func ") { return t } diff --git a/lsp/protocol.go b/lsp/protocol.go index efb855d..fa4b9d5 100644 --- a/lsp/protocol.go +++ b/lsp/protocol.go @@ -215,7 +215,7 @@ const ( // Hover is the hover response. type Hover struct { Contents markupContent `json:"contents"` - Range Range `json:"range,omitempty"` + Range Range `json:"range"` } type markupContent struct { diff --git a/verify/arm64_groundtruth_test.go b/verify/arm64_groundtruth_test.go index 1b3386f..b4b391f 100644 --- a/verify/arm64_groundtruth_test.go +++ b/verify/arm64_groundtruth_test.go @@ -54,10 +54,7 @@ func TestGroundTruthARM64(t *testing.T) { // The Go toolchain may add zero padding at the end of // functions. Compare up to the shorter length, then // verify any trailing bytes are zero. - cmpLen := len(gasmCode) - if len(goCode) < cmpLen { - cmpLen = len(goCode) - } + cmpLen := min(len(goCode), len(gasmCode)) if !bytes.Equal(gasmCode[:cmpLen], goCode[:cmpLen]) { t.Errorf("%s: MISMATCH gasm=%d go=%d bytes\n%s", fn.Name, len(gasmCode), len(goCode), diffHex(gasmCode, goCode)) continue diff --git a/verify/buffers.go b/verify/buffers.go index 1489087..1b094f0 100644 --- a/verify/buffers.go +++ b/verify/buffers.go @@ -25,7 +25,7 @@ func ParseBufSpec(spec string) ([]BufSpec, error) { return nil, nil } var out []BufSpec - for _, part := range strings.Split(spec, ",") { + for part := range strings.SplitSeq(spec, ",") { fields := strings.SplitN(part, ":", 3) if len(fields) != 3 { return nil, fmt.Errorf("verify: invalid buffer spec %q (expected name:size:pattern)", part) @@ -185,7 +185,7 @@ func ParseScalarArgs(spec string) (map[string]uint64, error) { if strings.TrimSpace(spec) == "" { return out, nil } - for _, part := range strings.Split(spec, ",") { + for part := range strings.SplitSeq(spec, ",") { part = strings.TrimSpace(part) if part == "" { continue diff --git a/verify/fuzz.go b/verify/fuzz.go index 3646f3a..8894057 100644 --- a/verify/fuzz.go +++ b/verify/fuzz.go @@ -61,8 +61,8 @@ func parseFuncSig(comment string) (funcSig, bool) { if m == nil { return funcSig{}, false } - sig := funcSig{name: m[1]} - sig.params = parseParams(m[2]) + sig := funcSig{name: m[1], + params: parseParams(m[2])} // Results may be "(a int, b int)" or "int" or "(int, error)". res := strings.TrimSpace(m[3]) res = strings.TrimPrefix(res, "(") @@ -271,10 +271,7 @@ func genDualArgs(rng *rand.Rand, sig funcSig, argSize int) (gasmArgs, goArgs []b case strings.HasPrefix(p.typ, "*["): nElem := arrayLen(p.typ) elem := elemSizeFor("[]" + p.typ[strings.Index(p.typ, "]")+1:]) - size := nElem * elem - if size < 8 { - size = 8 - } + size := max(nElem*elem, 8) buf1 := make([]byte, size) buf2 := make([]byte, size) rng.Read(buf1) diff --git a/verify/groundtruth.go b/verify/groundtruth.go index f911097..27b403f 100644 --- a/verify/groundtruth.go +++ b/verify/groundtruth.go @@ -169,7 +169,7 @@ func extractGOOBJCode(data []byte) (map[string][]byte, error) { result := make(map[string][]byte) const kindSTEXT = 1 - for s := 0; s < nSyms; s++ { + for s := range nSyms { x := nonpkg[s*symSize:] nameLen := le.Uint32(x[0:]) nameOff := le.Uint32(x[4:]) diff --git a/verify/l64_groundtruth_test.go b/verify/l64_groundtruth_test.go index 192f02f..aeffe22 100644 --- a/verify/l64_groundtruth_test.go +++ b/verify/l64_groundtruth_test.go @@ -76,10 +76,7 @@ func maskRelocs(code []byte, relocs []asm.Reloc) []byte { func diffHex(a, b []byte) string { var out bytes.Buffer - n := len(a) - if len(b) > n { - n = len(b) - } + n := max(len(b), len(a)) for i := 0; i < n; i += 4 { ab, bb := "??", "??" if i+4 <= len(a) { diff --git a/verify/sigparse.go b/verify/sigparse.go index c7d0c1d..bdc4adc 100644 --- a/verify/sigparse.go +++ b/verify/sigparse.go @@ -32,8 +32,8 @@ func ParseFuncSig(comment string) (FuncSig, bool) { if m == nil { return FuncSig{}, false } - sig := FuncSig{Name: m[1]} - sig.Params = parseParamsExported(m[2]) + sig := FuncSig{Name: m[1], + Params: parseParamsExported(m[2])} // Results may be "(a int, b int)" or "int" or "(int, error)". res := strings.TrimSpace(m[3]) res = strings.TrimPrefix(res, "(")