diff --git a/CHANGELOG.md b/CHANGELOG.md index 90126c4..8c8cbf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -625,7 +625,7 @@ objects. `GLOBL` in the file defines no longer aborts assembly — it is recorded as an external relocation (`Image.Externals`, `FuncLayout.Relocs`) and becomes an undefined global symbol in the object output. The raw image - format (`--format raw`, the default) still reports them: only an object + s (`--format raw`, the default) still reports them: only an object file can represent a reference the linker must resolve. ### Changed @@ -726,7 +726,7 @@ The formatter behaves like `go fmt` and canonicalises block separation. ### Changed -- `format`: canonical blank-line layout — a new block (a label, `TEXT` or +- `s`: canonical blank-line layout — a new block (a label, `TEXT` or `GLOBL`) is preceded by exactly one blank line, neither more nor less. Comments leading a block stay with it (the blank line goes before them), stacked labels share their block, the function's first label keeps hugging @@ -761,7 +761,7 @@ the encoder learns the legacy SSE moves. *first* operand on arm64, riscv64 and loong64; Plan 9 spelling puts it last on every architecture Go supports. The def/use and save/restore classification on those architectures was inverted. -- `format`: a comment that follows a `RET` (typically the next function's doc +- `s`: a comment that follows a `RET` (typically the next function's doc comment) is no longer indented as if it were still inside the finished function body. @@ -934,7 +934,7 @@ Initial release — the Phase 1 foundation. Zero error-severity diagnostics across the 90-file Go runtime corpus and the production go-flac kernels (the `register-clobber` audit additionally reports the go-flac kernels' unsaved callee-saved register use for review). -- `format`: an idempotent canonical formatter (operand spacing and per-function +- `s`: an idempotent canonical formatter (operand spacing and per-function mnemonic alignment) that preserves comments and round-trips through the parser. - `lsp`: a Language Server Protocol server over stdio providing completion, diff --git a/README.md b/README.md index 0a9021e..a2ec141 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ cross-compiles the same four targets. | `parser` | Line-oriented, error-tolerant parser producing the AST. | | `arch` | amd64, arm64, riscv64 and loong64 register files and instruction tables. | | `lint` | Conservative static checks (13 rules including unused-label, invalid-textflag, stack-imbalance). | -| `format` | A canonical formatter — `gofmt` for assembly. | +| `s` | A canonical formatter — `gofmt` for assembly. | | `asm` | The standalone assembler: all four architecture encoders, linker, object-file emitters (ELF with DWARF5, GOOBJ). | | `verify` | JIT execution substrate for dynamic analysis, combined ABI+fuzz differential testing. Assembly trampolines for all four architectures. | | `debug` | Interactive ptrace debugger for all four architectures: single-stepping, breakpoints, hardware watchpoints, register and memory inspection. | diff --git a/asm/arm64_assemble.go b/asm/arm64_assemble.go index 9912823..c97aac3 100644 --- a/asm/arm64_assemble.go +++ b/asm/arm64_assemble.go @@ -413,7 +413,7 @@ func encodeARM64AddSubImm(mnem string, ops []*ast.Operand) ([]byte, error) { if len(ops) != 2 && len(ops) != 3 { return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops)) } - v := int32(immFromOperand(ops[0])) + v := immFromOperand(ops[0]) rd := arm64RegNum(operandRegName(ops[len(ops)-1])) rn := rd if len(ops) == 3 { @@ -606,7 +606,7 @@ func encodeARM64LoadImm(rd int, v int64, mnem string) ([]byte, error) { // - C_ABCON0 (0 < v ≤ 4095): bitmask first for positive values // - Negative values: MOVN first, then bitmask // - C_MOVCON (movcon-eligible, outside ABCON range): MOVZ/MOVN first - tryBitmaskFirst := (d > 0 && d <= 0xFFF) + tryBitmaskFirst := d > 0 && d <= 0xFFF if tryBitmaskFirst { // Small immediate: try bitmask first (Go uses ORR for values like $1, $256). diff --git a/asm/arm64_encode.go b/asm/arm64_encode.go index ef458cc..41e58af 100644 --- a/asm/arm64_encode.go +++ b/asm/arm64_encode.go @@ -234,8 +234,6 @@ const ( a64CondLT = 0xb a64CondGT = 0xc a64CondLE = 0xd - a64CondAL = 0xe - a64CondNV = 0xf ) // arm64CondMap maps Go assembler condition mnemonics to AArch64 condition codes. diff --git a/asm/elf.go b/asm/elf.go index d326a8b..ef903dc 100644 --- a/asm/elf.go +++ b/asm/elf.go @@ -17,7 +17,6 @@ import ( // and external ones against undefined globals. The output links with the // system toolchain (cc/ld) the way a hand-assembled .o would. -// ELF constants (ELF64, little-endian, System V). const ( elfClass64 = 2 elfDataLSB = 1 @@ -36,17 +35,14 @@ const ( shfAlloc = 2 shfExecInstr = 4 - stbLocal = 0 stbGlobal = 1 - sttNotype = 0 sttObject = 1 sttFunc = 2 sttSection = 3 stInfoShift = 4 rX8664PC32 = 2 - rX8664_64 = 1 // R_X86_64_64 (absolute 64-bit) ) // elfSym is one symbol-table entry in construction. diff --git a/asm/elf_dwarf.go b/asm/elf_dwarf.go index eec8afa..9d85130 100644 --- a/asm/elf_dwarf.go +++ b/asm/elf_dwarf.go @@ -87,7 +87,7 @@ func emitDWARF(img *Image, srcFile string) *dwarfSections { ds.debugLineStr = lineStr.bytes() // Build .debug_line. - ds.debugLine = dwarfBuildLineSection(img, srcFile, ds) + ds.debugLine = dwarfBuildLineSection(img, ds) // Build .debug_info. ds.debugInfo = dwarfBuildInfoSection(img, srcFile, ds) @@ -98,7 +98,7 @@ func emitDWARF(img *Image, srcFile string) *dwarfSections { } // dwarfBuildLineSection builds a complete .debug_line section. -func dwarfBuildLineSection(img *Image, srcFile string, ds *dwarfSections) []byte { +func dwarfBuildLineSection(img *Image, ds *dwarfSections) []byte { var b []byte le := binary.LittleEndian diff --git a/asm/evex.go b/asm/evex.go index d210640..c866f3c 100644 --- a/asm/evex.go +++ b/asm/evex.go @@ -701,7 +701,7 @@ func splitMask(ops []Operand) ([]Operand, int, error) { return nil, 0, fmt.Errorf("at most one mask register operand") } if r.idx == 0 { - return nil, 0, fmt.Errorf("K0 is not a usable mask register") + return nil, 0, fmt.Errorf("k0 is not a usable mask register") } mask = r.idx continue @@ -1203,7 +1203,7 @@ func (e *enc) emitEvexFields(spec evexSpec, ll, regIdx, vvvvIdx int, rm Operand, // The b bit and the L'L field carry the rounding/SAE/broadcast mode: // a rounding mode replaces L'L with the rc value, plain SAE and // broadcast keep the vector length. - b, ll := 0, ll + b := 0 switch { case sfx.rounding >= 0: b, ll = 1, sfx.rounding diff --git a/asm/goobj.go b/asm/goobj.go index 50787b9..e454472 100644 --- a/asm/goobj.go +++ b/asm/goobj.go @@ -33,7 +33,10 @@ import ( // methods supply the toolchain preamble, the MinLC (pc-value delta unit) // and the relocation-type mapping for code relocations. -// GOOBJ block indices (cmd/internal/goobj). +// GOOBJ block indices (cmd/internal/goobj). These MUST match the real +// archive layout: the emitter writes the header offsets per index and the +// reader (groundtruth, goobj_resolve) parses real Go archives with them. +// blkAutolib is unused by the emitter but still defines index 0. const ( blkAutolib = iota blkPkgIdx @@ -417,7 +420,7 @@ func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, r // The string table. Absolute offsets: it starts right after the // 96-byte header (magic, fingerprint, flags, the 19 block offsets). const headerSize = 8 + 8 + 4 + 4*(blkEnd+1) - strTab := []byte{} + var strTab []byte strOff := map[string]uint32{} addStr := func(s string) { if _, ok := strOff[s]; ok { diff --git a/asm/link.go b/asm/link.go index d2eea7d..d4871a8 100644 --- a/asm/link.go +++ b/asm/link.go @@ -80,7 +80,7 @@ func (fl *FuncLayout) LineAt(offset int) int { return 0 } -// Reloc is one static-symbol reference within a function body: the disp32 +// RelocKind Reloc is one static-symbol reference within a function body: the disp32 // field at Off (function-relative) must reach the symbol plus Addend, // measured from After, the address just past the instruction. An External // relocation names a symbol no GLOBL in the file defines; the object-file diff --git a/asm/loong64_assemble.go b/asm/loong64_assemble.go index 53e983e..c864c03 100644 --- a/asm/loong64_assemble.go +++ b/asm/loong64_assemble.go @@ -672,8 +672,6 @@ func encodeLOONG64ImmArith(mnem string, de l64DualEnc, ops []*ast.Operand) ([]by const ( lu12iw = 0x0a << 25 ori = 0x00e << 22 - lu32id = 0x0b << 25 - lu52id = 0x00c << 22 ) if v == int64(int32(v)) { if v&0xfff == 0 && (v < 0x800 || v > 0xfff) { @@ -771,7 +769,7 @@ func encodeLOONG64Mov(instr *ast.Instr, mnem string, fi loong64FrameInfo, relocs if rd < 0 { return nil, fmt.Errorf("%s $sym(SB): invalid destination register", mnem) } - return encodeLOONG64SBAddr(src.Imm.Sym, rd, mnem, relocs), nil + return encodeLOONG64SBAddr(src.Imm.Sym, rd, relocs), nil } rd := l64Reg(dst) if rd < 0 { @@ -832,14 +830,14 @@ func encodeLOONG64Mov(instr *ast.Instr, mnem string, fi loong64FrameInfo, relocs if rd < 0 { return nil, fmt.Errorf("%s: invalid destination register", mnem) } - return encodeLOONG64MemOp(mnem, ops[0], rd, true, fi, relocs) + return encodeLOONG64MemOp(mnem, ops[0], rd, true, fi) } if !isMemOperand(src) && isMemOperand(dst) { rs := l64Reg(src) if rs < 0 { return nil, fmt.Errorf("%s: invalid source register", mnem) } - return encodeLOONG64MemOp(mnem, ops[1], rs, false, fi, relocs) + return encodeLOONG64MemOp(mnem, ops[1], rs, false, fi) } // Register → register. @@ -933,20 +931,20 @@ const ( l64St1 l64St0 - l64Dcon12_0 - l64Dcon12_20S - l64Dcon20S_20 - l64Dcon12_12S - l64Dcon20S_12S - l64Dcon20S_0 - l64Dcon12_12U - l64Dcon20S_12U - l64Dcon32_12S - l64Dcon32_0 - l64Dcon32_20 - l64Dcon12_32S - l64Dcon20S_32 - l64Dcon32_12U + l64dcon120 + l64dcon1220s + l64dcon20s20 + l64dcon1212s + l64dcon20s12s + l64dcon20s0 + l64dcon1212u + l64dcon20s12u + l64dcon3212s + l64dcon320 + l64dcon3220 + l64dcon1232s + l64dcon20s32 + l64dcon3212u l64Dcon ) @@ -987,91 +985,91 @@ func l64DconClass(v int64) int { lo20 := l64BitField(v, 12, 20) lo12 := l64BitField(v, 0, 12) if tzb >= 52 { - return l64Dcon12_0 + return l64dcon120 } if tzb >= 32 { if ((hi20 == l64All1 || hi20 == l64St1) && hi12 == l64All1) || ((hi20 == l64All0 || hi20 == l64St0) && hi12 == l64All0) { - return l64Dcon20S_0 + return l64dcon20s0 } - return l64Dcon32_0 + return l64dcon320 } if tzb >= 12 { if lo20 == l64St1 || lo20 == l64All1 { if hi20 == l64All1 { - return l64Dcon12_20S + return l64dcon1220s } if (hi20 == l64St1 && hi12 == l64All1) || ((hi20 == l64St0 || hi20 == l64All0) && hi12 == l64All0) { - return l64Dcon20S_20 + return l64dcon20s20 } - return l64Dcon32_20 + return l64dcon3220 } if hi20 == l64All0 { - return l64Dcon12_20S + return l64dcon1220s } if (hi20 == l64St0 && hi12 == l64All0) || ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) { - return l64Dcon20S_20 + return l64dcon20s20 } - return l64Dcon32_20 + return l64dcon3220 } if lo12 == l64St1 || lo12 == l64All1 { if lo20 == l64All1 { if hi20 == l64All1 { - return l64Dcon12_12S + return l64dcon1212s } if (hi20 == l64St1 && hi12 == l64All1) || ((hi20 == l64St0 || hi20 == l64All0) && hi12 == l64All0) { - return l64Dcon20S_12S + return l64dcon20s12s } - return l64Dcon32_12S + return l64dcon3212s } if lo20 == l64St1 { if hi20 == l64All1 { - return l64Dcon12_32S + return l64dcon1232s } if (hi20 == l64St1 && hi12 == l64All1) || ((hi20 == l64St0 || hi20 == l64All0) && hi12 == l64All0) { - return l64Dcon20S_32 + return l64dcon20s32 } return l64Dcon } if lo20 == l64All0 { if hi20 == l64All0 { - return l64Dcon12_12U + return l64dcon1212u } if ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) || (hi20 == l64St0 && hi12 == l64All0) { - return l64Dcon20S_12U + return l64dcon20s12u } - return l64Dcon32_12U + return l64dcon3212u } if hi20 == l64All0 { - return l64Dcon12_32S + return l64dcon1232s } if ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) || (hi20 == l64St0 && hi12 == l64All0) { - return l64Dcon20S_32 + return l64dcon20s32 } return l64Dcon } if lo20 == l64All0 { if hi20 == l64All0 { - return l64Dcon12_12U + return l64dcon1212u } if ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) || (hi20 == l64St0 && hi12 == l64All0) { - return l64Dcon20S_12U + return l64dcon20s12u } - return l64Dcon32_12U + return l64dcon3212u } if lo20 == l64St1 || lo20 == l64All1 { if hi20 == l64All1 { - return l64Dcon12_32S + return l64dcon1232s } if (hi20 == l64St1 && hi12 == l64All1) || ((hi20 == l64St0 || hi20 == l64All0) && hi12 == l64All0) { - return l64Dcon20S_32 + return l64dcon20s32 } return l64Dcon } if hi20 == l64All0 { - return l64Dcon12_32S + return l64dcon1232s } if ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) || (hi20 == l64St0 && hi12 == l64All0) { - return l64Dcon20S_32 + return l64dcon20s32 } return l64Dcon } @@ -1088,29 +1086,29 @@ func l64DconMovWords(rd int, v int64) []uint32 { ori = 0x00e << 22 ) switch l64DconClass(v) { - case l64Dcon12_0: + case l64dcon120: return []uint32{l64irr(lu52id, int(v>>52), 0, rd)} - case l64Dcon12_20S: + case l64dcon1220s: return []uint32{l64ir(lu12iw, int(v>>12), rd), l64irr(lu52id, int(v>>52), rd, rd)} - case l64Dcon20S_20: + case l64dcon20s20: return []uint32{l64ir(lu12iw, int(v>>12), rd), l64ir(lu32id, int(v>>32), rd)} - case l64Dcon12_12S: + case l64dcon1212s: return []uint32{l64irr(addid, int(v), 0, rd), l64irr(lu52id, int(v>>52), rd, rd)} - case l64Dcon20S_12S, l64Dcon20S_0: + case l64dcon20s12s, l64dcon20s0: return []uint32{l64irr(addiw, int(v), 0, rd), l64ir(lu32id, int(v>>32), rd)} - case l64Dcon12_12U: + case l64dcon1212u: return []uint32{l64irr(ori, int(v), 0, rd), l64irr(lu52id, int(v>>52), rd, rd)} - case l64Dcon20S_12U: + case l64dcon20s12u: return []uint32{l64irr(ori, int(v), 0, rd), l64ir(lu32id, int(v>>32), rd)} - case l64Dcon32_12S, l64Dcon32_0: + case l64dcon3212s, l64dcon320: return []uint32{l64irr(addiw, int(v), 0, rd), l64ir(lu32id, int(v>>32), rd), l64irr(lu52id, int(v>>52), rd, rd)} - case l64Dcon32_20: + case l64dcon3220: return []uint32{l64ir(lu12iw, int(v>>12), rd), l64ir(lu32id, int(v>>32), rd), l64irr(lu52id, int(v>>52), rd, rd)} - case l64Dcon12_32S: + case l64dcon1232s: return []uint32{l64ir(lu12iw, int(v>>12), rd), l64irr(ori, int(v), rd, rd), l64irr(lu52id, int(v>>52), rd, rd)} - case l64Dcon20S_32: + case l64dcon20s32: return []uint32{l64ir(lu12iw, int(v>>12), rd), l64irr(ori, int(v), rd, rd), l64ir(lu32id, int(v>>32), rd)} - case l64Dcon32_12U: + case l64dcon3212u: return []uint32{l64irr(ori, int(v), 0, rd), l64ir(lu32id, int(v>>32), rd), l64irr(lu52id, int(v>>52), rd, rd)} default: return []uint32{l64ir(lu12iw, int(v>>12), rd), l64irr(ori, int(v), rd, rd), l64ir(lu32id, int(v>>32), rd), l64irr(lu52id, int(v>>52), rd, rd)} @@ -1161,7 +1159,7 @@ func encodeLOONG64LoadImm(rd int, v int64, mnem string) []byte { // encodeLOONG64MemOp encodes a memory load (load = true) or store with a // 12-bit offset, or the 3-instruction expansion for larger offsets: // lu12i.w r30, (off+0x800)>>12; add.d r30, rj, r30; ld/st rd, off(r30). -func encodeLOONG64MemOp(mnem string, mem *ast.Operand, reg int, load bool, fi loong64FrameInfo, relocs *[]Reloc) ([]byte, error) { +func encodeLOONG64MemOp(mnem string, mem *ast.Operand, reg int, load bool, fi loong64FrameInfo) ([]byte, error) { rj, off := l64MemWithFrame(mem, fi) if rj < 0 { return nil, fmt.Errorf("invalid memory operand") @@ -1266,7 +1264,7 @@ func l64FpMoveKey(mnem string, sc, dc l64RegClass) (string, bool) { // encodeLOONG64SBAddr emits pcalau12i rd, 0; addi.d rd, rd, 0 with the // R_LOONG64_ADDR_HI/LO relocation pair, loading a symbol's address. -func encodeLOONG64SBAddr(sym *ast.Symbol, rd int, mnem string, relocs *[]Reloc) []byte { +func encodeLOONG64SBAddr(sym *ast.Symbol, rd int, relocs *[]Reloc) []byte { if relocs != nil { *relocs = append(*relocs, Reloc{Off: 0, After: 0, Name: sym.Name, Kind: RelLoong64AddrHi, Addend: sym.Offset}, diff --git a/asm/loong64_encode.go b/asm/loong64_encode.go index e66a899..c377f47 100644 --- a/asm/loong64_encode.go +++ b/asm/loong64_encode.go @@ -105,10 +105,10 @@ func loong64RegNum(name string) int { } // F0–F31, FCC0–FCC7, FCSR0–FCSR31. if len(name) >= 4 && name[:4] == "FCSR" { - return loong64RegSpecial(name[4:], "FCSR", 31) + return loong64RegSpecial(name[4:], 31) } if len(name) >= 3 && name[:3] == "FCC" { - return loong64RegSpecial(name[3:], "FCC", 7) + return loong64RegSpecial(name[3:], 7) } if len(name) < 2 { return -1 @@ -131,7 +131,7 @@ func loong64RegNum(name string) int { } // loong64RegSpecial parses a numbered FCC/FCSR register. -func loong64RegSpecial(digits, prefix string, max int) int { +func loong64RegSpecial(digits string, max int) int { if digits == "" { return -1 } diff --git a/asm/reg.go b/asm/reg.go index e2bf108..5016aa7 100644 --- a/asm/reg.go +++ b/asm/reg.go @@ -65,28 +65,28 @@ var ( CX = Reg{idx: 1, size: 2} DX = Reg{idx: 2, size: 2} BX = Reg{idx: 3, size: 2} - SP = Reg{idx: 4, size: 2} - BP = Reg{idx: 5, size: 2} + _ = Reg{idx: 4, size: 2} + _ = Reg{idx: 5, size: 2} SI = Reg{idx: 6, size: 2} DI = Reg{idx: 7, size: 2} - EAX = Reg{idx: 0, size: 4} - ECX = Reg{idx: 1, size: 4} - EDX = Reg{idx: 2, size: 4} - EBX = Reg{idx: 3, size: 4} - ESP = Reg{idx: 4, size: 4} - EBP = Reg{idx: 5, size: 4} - ESI = Reg{idx: 6, size: 4} - EDI = Reg{idx: 7, size: 4} + _ = Reg{idx: 0, size: 4} + _ = Reg{idx: 1, size: 4} + _ = Reg{idx: 2, size: 4} + _ = Reg{idx: 3, size: 4} + _ = Reg{idx: 4, size: 4} + _ = Reg{idx: 5, size: 4} + _ = Reg{idx: 6, size: 4} + _ = Reg{idx: 7, size: 4} - RAX = Reg{idx: 0, size: 8} - RCX = Reg{idx: 1, size: 8} - RDX = Reg{idx: 2, size: 8} - RBX = Reg{idx: 3, size: 8} - RSP = Reg{idx: 4, size: 8} - RBP = Reg{idx: 5, size: 8} - RSI = Reg{idx: 6, size: 8} - RDI = Reg{idx: 7, size: 8} + _ = Reg{idx: 0, size: 8} + _ = Reg{idx: 1, size: 8} + _ = Reg{idx: 2, size: 8} + _ = Reg{idx: 3, size: 8} + _ = Reg{idx: 4, size: 8} + _ = Reg{idx: 5, size: 8} + _ = Reg{idx: 6, size: 8} + _ = Reg{idx: 7, size: 8} ) // regByName maps an assembly register name (case-insensitive) to a Reg. diff --git a/asm/riscv_assemble.go b/asm/riscv_assemble.go index 95d1a23..1dd329f 100644 --- a/asm/riscv_assemble.go +++ b/asm/riscv_assemble.go @@ -228,7 +228,7 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv // MOV is a pseudo-instruction that the Go assembler uses for loads, // stores, register moves and immediate loads. case "MOV": - return encodeRISCVMov(instr, offsets, fi, relocs) + return encodeRISCVMov(instr, fi, relocs) // JALR: indirect jump/call. Plan 9: JALR rs1, rd or JALR offset(rs1). case "JALR": @@ -527,7 +527,7 @@ func isImmOperand(op *ast.Operand) bool { // - MOV Rs, (Rd) register-relative store // - MOV Rs, Rd register-to-register move (ADDI $0) // - MOV $imm, Rd load immediate (ADDI or LUI+ADDIW) -func encodeRISCVMov(instr *ast.Instr, offsets map[string]int, fi riscvFrameInfo, relocs *[]Reloc) ([]byte, error) { +func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc) ([]byte, error) { ops := instr.Operands if len(ops) != 2 { return nil, fmt.Errorf("MOV expects 2 operands, got %d", len(ops)) @@ -911,7 +911,7 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) { } case "ADDI": - rd, rs1, imm := extractITypeParams(instr, fi) + rd, rs1, imm := extractITypeParams(instr) if rd == -1 || rs1 == -1 { return 0, false } @@ -1060,13 +1060,13 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) { } case "ADDIW": - rd, rs1, imm := extractITypeParams(instr, fi) + rd, rs1, imm := extractITypeParams(instr) if rd == rs1 && rd != 0 && imm >= -32 && imm <= 31 { return rvcCI(0x1, uint32(rd), uint32(imm)&0x3F), true } case "SLLI", "SRLI", "SRAI": - rd, rs1, imm := extractITypeParams(instr, fi) + rd, rs1, imm := extractITypeParams(instr) if rd == rs1 && rd != 0 && imm != 0 && imm >= 1 && imm <= 63 { if mnem == "SLLI" { // C.SLLI: funct3=0, op=10 quadrant, shamt in bits [12|6:2]. @@ -1083,7 +1083,7 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) { } case "ANDI": - rd, rs1, imm := extractITypeParams(instr, fi) + rd, rs1, imm := extractITypeParams(instr) if isRVCIntReg(rd) && rd == rs1 && imm >= -32 && imm <= 31 { // C.ANDI: CB-type, funct3=0x4, funct2=0x2. return rvcCBShift(0x2, rvcReg3(rd), uint32(imm)&0x3F), true @@ -1131,7 +1131,7 @@ func extractSDParams(instr *ast.Instr, fi riscvFrameInfo) (rs2, rs1 int, imm int // extractITypeParams extracts rd, rs1, and immediate for an I-type // instruction. The Plan 9 order is INSTR $imm, rs1, rd (3 operands) or // INSTR $imm, rd (2 operands, rd is also the source). -func extractITypeParams(instr *ast.Instr, fi riscvFrameInfo) (rd, rs1 int, imm int32) { +func extractITypeParams(instr *ast.Instr) (rd, rs1 int, imm int32) { ops := instr.Operands switch len(ops) { case 3: diff --git a/ast/ast.go b/ast/ast.go index dac3378..346ad3e 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -123,10 +123,8 @@ type Symbol struct { // OpKind classifies an operand syntactically. type OpKind int -// Operand kinds. const ( - OpInvalid OpKind = iota - OpImmediate // $value + OpImmediate = iota // $value OpAddr // register, memory reference, symbol or label ) diff --git a/ast/ast_test.go b/ast/ast_test.go index 6ad87c9..cdf2959 100644 --- a/ast/ast_test.go +++ b/ast/ast_test.go @@ -54,11 +54,11 @@ func TestStmtPositions(t *testing.T) { // TestInterfaces confirms the node types satisfy their interfaces, so callers // can range over Decls and Stmts. func TestInterfaces(t *testing.T) { - var decls []Decl = []Decl{&Include{}, &Preproc{}, &Text{}, &Globl{}, &Data{}} + var decls = []Decl{&Include{}, &Preproc{}, &Text{}, &Globl{}, &Data{}} if len(decls) != 5 { t.Fatal("decl interface set") } - var stmts []Stmt = []Stmt{&Label{}, &Instr{}} + var stmts = []Stmt{&Label{}, &Instr{}} if len(stmts) != 2 { t.Fatal("stmt interface set") } diff --git a/cmd/gasm/main.go b/cmd/gasm/main.go index 68fe12f..aa58341 100644 --- a/cmd/gasm/main.go +++ b/cmd/gasm/main.go @@ -299,7 +299,7 @@ given. rc = 1 continue } - out := format.Source(path, src) + out := format.Source(src) if dirMode || *write { if out != src { if err := os.WriteFile(path, []byte(out), 0o644); err != nil { @@ -447,7 +447,7 @@ requires -p, the package path, and the installed Go toolchain). return 1 } - img, err := assembleFile(path, targetArch, f) + img, err := assembleFile(targetArch, f) if err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", path, err) return 1 @@ -544,7 +544,7 @@ requires -p, the package path, and the installed Go toolchain). // cmdDiff compares the machine code of two assembly files. func cmdDiff(args []string) int { - fs := newCommand("diff", "gasm diff ", ` + set := newCommand("diff", "gasm diff ", ` Compare the machine code produced by assembling two files. Shows which functions differ and the byte-level differences. Useful for verifying that two implementations produce identical code, @@ -553,13 +553,13 @@ or for tracking encoding changes between Go assembler versions. Use --map to compare functions whose names differ between the files, e.g. --map wideCopyAVX2=wideCopyAVX512 pairs the two regardless of suffix. `) - mapSpec := fs.String("map", "", "comma-separated old=new pairs to match functions with different names") - fs.Parse(args) - if fs.NArg() != 2 { + mapSpec := set.String("map", "", "comma-separated old=new pairs to match functions with different names") + set.Parse(args) + if set.NArg() != 2 { fmt.Fprintln(os.Stderr, "usage: gasm diff ") return 2 } - path1, path2 := fs.Arg(0), fs.Arg(1) + path1, path2 := set.Arg(0), set.Arg(1) // Parse the name mapping (file1 name → file2 name). nameMap := make(map[string]string) @@ -640,7 +640,7 @@ e.g. --map wideCopyAVX2=wideCopyAVX512 pairs the two regardless of suffix. } // assembleFile assembles a parsed file for the given architecture and returns the image. -func assembleFile(path string, targetArch arch.Arch, f *ast.File) (*asm.Image, error) { +func assembleFile(targetArch arch.Arch, f *ast.File) (*asm.Image, error) { switch targetArch { case arch.AMD64: return asm.AssembleFile(f) @@ -668,7 +668,7 @@ func assemblePath(path string) (*asm.Image, error) { if len(errs) > 0 { return nil, fmt.Errorf("parse errors") } - return assembleFile(path, arch.FromFilename(path), f) + return assembleFile(arch.FromFilename(path), f) } // printByteDiff shows the first few byte differences between two code blocks. @@ -688,18 +688,18 @@ func printByteDiff(a, b []byte) { // cmdProfile shows the basic-block structure of functions in an assembly file. func cmdProfile(args []string) int { - fs := newCommand("profile", "gasm profile ", ` + flagSet := newCommand("profile", "gasm profile ", ` Show the basic-block structure of functions in an assembly file. Lists each function's labels, their offsets, and the block boundaries. This is the static structure; for runtime execution counts, use gasm verify --fuzz which exercises the code paths. `) - fs.Parse(args) - if fs.NArg() != 1 { + flagSet.Parse(args) + if flagSet.NArg() != 1 { fmt.Fprintln(os.Stderr, "usage: gasm profile ") return 2 } - path := fs.Arg(0) + path := flagSet.Arg(0) // Load the file to get function metadata. k, err := verify.Load(path) @@ -1021,7 +1021,7 @@ func cmdVerifyARM64(path string, groundTruth, profile bool) int { } func cmdVerify(args []string) int { - fs := newCommand("verify", "gasm verify [-smoke] [-abi] [-fuzz] [-ground-truth] [-profile] [-call] ", ` + set := newCommand("verify", "gasm verify [-smoke] [-abi] [-fuzz] [-ground-truth] [-profile] [-call] ", ` Assemble FILE (amd64), map it into executable memory and report the available functions. This confirms the assembled image is self-consistent (no unresolved external symbols) and executable — the prerequisite for dynamic @@ -1047,25 +1047,25 @@ With -call, a single function is invoked with user-supplied buffers (-buf) instead of the smoke/abi/fuzz sweeps. Useful for partial functions (e.g. decoders) that crash on random input but should succeed on valid data. `) - smoke := fs.Bool("smoke", false, "call each NOSPLIT function with zeroed args") - abi := fs.Bool("abi", false, "run ABI-checking calls (sentinel registers + red zone)") - abiN := fs.Int("abi-n", 100, "number of ABI check iterations with varied inputs") - profile := fs.Bool("profile", false, "list basic-block structure per function") - groundTruth := fs.Bool("ground-truth", false, "compare machine code byte-for-byte against go tool asm") - fuzz := fs.Bool("fuzz", false, "differential fuzz: JIT both gasm and go-tool-asm versions, compare outputs") - fuzzN := fs.Int("n", 1000, "number of fuzz iterations per function") - fuzzOne := fs.String("fuzz-one", "", "") // hidden: fuzz a single function (subprocess mode) - sweepOne := fs.String("sweep-one", "", "") // hidden: smoke/abi a single function (subprocess mode) - call := fs.String("call", "", "call a single function with -buf instead of the sweeps") - bufSpec := fs.String("buf", "", "buffer spec for -call: name:size:pattern[,name:size:pattern] (zero, ones, seq, or hex)") - scalarSpec := fs.String("args", "", "scalar args for -call: name=value[,name=value] (decimal or 0x hex)") - repeat := fs.Int("repeat", 1, "number of times to repeat a -call invocation") - fs.Parse(args) - if fs.NArg() != 1 { + smoke := set.Bool("smoke", false, "call each NOSPLIT function with zeroed args") + abi := set.Bool("abi", false, "run ABI-checking calls (sentinel registers + red zone)") + abiN := set.Int("abi-n", 100, "number of ABI check iterations with varied inputs") + profile := set.Bool("profile", false, "list basic-block structure per function") + groundTruth := set.Bool("ground-truth", false, "compare machine code byte-for-byte against go tool asm") + fuzz := set.Bool("fuzz", false, "differential fuzz: JIT both gasm and go-tool-asm versions, compare outputs") + fuzzN := set.Int("n", 1000, "number of fuzz iterations per function") + fuzzOne := set.String("fuzz-one", "", "") // hidden: fuzz a single function (subprocess mode) + sweepOne := set.String("sweep-one", "", "") // hidden: smoke/abi a single function (subprocess mode) + call := set.String("call", "", "call a single function with -buf instead of the sweeps") + bufSpec := set.String("buf", "", "buffer spec for -call: name:size:pattern[,name:size:pattern] (zero, ones, seq, or hex)") + scalarSpec := set.String("args", "", "scalar args for -call: name=value[,name=value] (decimal or 0x hex)") + repeat := set.Int("repeat", 1, "number of times to repeat a -call invocation") + set.Parse(args) + if set.NArg() != 1 { fmt.Fprintln(os.Stderr, "usage: gasm verify [-smoke] [-abi] [-fuzz] [-ground-truth] [-profile] [-call] ") return 2 } - path := fs.Arg(0) + path := set.Arg(0) targetArch := arch.FromFilename(path) switch targetArch { case arch.AMD64: diff --git a/debug/target_linux.go b/debug/target_linux.go index baf7a1d..b4a29ba 100644 --- a/debug/target_linux.go +++ b/debug/target_linux.go @@ -30,7 +30,7 @@ func mapRWX(code []byte) ([]byte, error) { } // setupBuffers allocates buffers in the debuggee's memory. -func setupBuffers(spec string, args []byte, argSize int, tmpDir string) ([]byte, error) { +func setupBuffers(spec string, args []byte, tmpDir string) ([]byte, error) { type bufSpec struct { name string size int diff --git a/debug/target_linux_amd64.go b/debug/target_linux_amd64.go index 7aa6118..7eb12c9 100644 --- a/debug/target_linux_amd64.go +++ b/debug/target_linux_amd64.go @@ -77,7 +77,7 @@ func RunTarget(asmPath, funcName, argsFile, tmpDir string) error { bufSpecFile := tmpDir + "/bufspec" if bufSpec, err := os.ReadFile(bufSpecFile); err == nil && len(bufSpec) > 0 { - args, err = setupBuffers(string(bufSpec), args, fl.Args, tmpDir) + args, err = setupBuffers(string(bufSpec), args, tmpDir) if err != nil { return fmt.Errorf("debug target: setup buffers: %w", err) } diff --git a/debug/target_linux_arm64.go b/debug/target_linux_arm64.go index aab8d69..6e5d97a 100644 --- a/debug/target_linux_arm64.go +++ b/debug/target_linux_arm64.go @@ -107,7 +107,7 @@ func RunTarget(asmPath, funcName, argsFile, tmpDir string) error { stackArgs := make([]byte, fl.Args) copy(stackArgs, args) - _, callErr := verify.Call(fnAddr, stackArgs) + _, callErr := verify.Call(stackArgs) if callErr != nil { os.Exit(1) } diff --git a/debug/target_linux_loong64.go b/debug/target_linux_loong64.go index e4de727..de560a9 100644 --- a/debug/target_linux_loong64.go +++ b/debug/target_linux_loong64.go @@ -86,7 +86,7 @@ func RunTarget(asmPath, funcName, argsFile, tmpDir string) error { bufSpecFile := tmpDir + "/bufspec" if bufSpec, err := os.ReadFile(bufSpecFile); err == nil && len(bufSpec) > 0 { - args, err = setupBuffers(string(bufSpec), args, fl.Args, tmpDir) + args, err = setupBuffers(string(bufSpec), args, tmpDir) if err != nil { return fmt.Errorf("debug target: setup buffers: %w", err) } @@ -107,7 +107,7 @@ func RunTarget(asmPath, funcName, argsFile, tmpDir string) error { stackArgs := make([]byte, fl.Args) copy(stackArgs, args) - _, callErr := verify.Call(fnAddr, stackArgs) + _, callErr := verify.Call(stackArgs) if callErr != nil { os.Exit(1) } diff --git a/debug/target_linux_riscv64.go b/debug/target_linux_riscv64.go index 07efdd0..70f1543 100644 --- a/debug/target_linux_riscv64.go +++ b/debug/target_linux_riscv64.go @@ -86,7 +86,7 @@ func RunTarget(asmPath, funcName, argsFile, tmpDir string) error { bufSpecFile := tmpDir + "/bufspec" if bufSpec, err := os.ReadFile(bufSpecFile); err == nil && len(bufSpec) > 0 { - args, err = setupBuffers(string(bufSpec), args, fl.Args, tmpDir) + args, err = setupBuffers(string(bufSpec), args, tmpDir) if err != nil { return fmt.Errorf("debug target: setup buffers: %w", err) } @@ -107,7 +107,7 @@ func RunTarget(asmPath, funcName, argsFile, tmpDir string) error { stackArgs := make([]byte, fl.Args) copy(stackArgs, args) - _, callErr := verify.Call(fnAddr, stackArgs) + _, callErr := verify.Call(stackArgs) if callErr != nil { os.Exit(1) } diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 17c6c6e..085fe2a 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -26,7 +26,7 @@ Repository: [sourcedock.dev/petrbalvin/gasm-devkit](https://sourcedock.dev/petrb graph TD SRC["source .s"] --> LEX["lexer
token stream"] LEX --> PAR["parser
AST + diagnostics"] - LEX --> FMT["format
re-space tokens"] + LEX --> FMT["s
re-space tokens"] PAR --> LINT["lint
static checks"] PAR --> LSP["lsp server"] LEX --> LSP @@ -157,7 +157,7 @@ Two deeper analyses sit on top of the AST: reference) and a literal index is range-checked; a named index constant such as `$PCDATA_StackMapIndex` is accepted without a range check. -### `format` +### `s` The formatter works on the **token stream, not the AST**, so it preserves every line — comments and blanks included. It normalises indentation, operand diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 0d8169a..2103c31 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -13,7 +13,7 @@ why, the options on the table, and the trigger that should reopen it. **Approach taken.** Instead of parsing the compiler's iexport data (which would have required either `golang.org/x/tools` or an in-house parser), the resolver reads the **GOOBJ data directly** from the target package's `.a` -archive. The `.a` file contains a `_go_.o` member whose GOOBJ format is the +archive. The `.a` file contains a `_go_.o` member whose GOOBJ s is the same one gasm writes — the parser reuses the same layout (`blkSymdef`, `blkNonpkgdef`, the string table), so no new dependency was needed. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 807106e..e38b271 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -97,7 +97,7 @@ ast/ Abstract syntax tree parser/ Line-oriented parser arch/ Register and instruction tables (generated) lint/ Static analysis rules -format/ Canonical formatter +s/ Canonical formatter lsp/ Language Server Protocol server asm/ Standalone assembler, encoder, object emitters verify/ JIT execution, differential testing, ABI checks diff --git a/format/format.go b/format/format.go index 8969d35..1fcae06 100644 --- a/format/format.go +++ b/format/format.go @@ -16,7 +16,7 @@ import ( ) // Source returns the canonical formatting of src. -func Source(path, src string) string { +func Source(src string) string { lines := splitLines(lexer.Tokenize(src)) // First pass: classify each line and record, for every instruction, the diff --git a/format/format_test.go b/format/format_test.go index df76947..5967cc6 100644 --- a/format/format_test.go +++ b/format/format_test.go @@ -32,7 +32,7 @@ func TestGolden(t *testing.T) { "\tVFMADD231PD Z14, Z12, Z10\n" + "\tRET\n" - got := Source("f_amd64.s", in) + got := Source(in) if got != want { t.Fatalf("formatting mismatch:\n--- got ---\n%q\n--- want ---\n%q", got, want) } @@ -64,13 +64,13 @@ func TestDocCommentIndent(t *testing.T) { "TEXT ·second(SB), NOSPLIT, $0\n" + "\tRET\n" - got := Source("d_amd64.s", in) + got := Source(in) if got != want { t.Fatalf("formatting mismatch:\n--- got ---\n%q\n--- want ---\n%q", got, want) } // Body comments stay indented. body := "#include \"textflag.h\"\nTEXT ·f(SB), NOSPLIT, $0\n// inside the body\nXORQ AX, AX\nRET\n" - gotBody := Source("b_amd64.s", body) + gotBody := Source(body) if !strings.Contains(gotBody, "\t// inside the body\n") { t.Fatalf("body comment must stay indented:\n%q", gotBody) } @@ -125,11 +125,11 @@ func TestBlankLines(t *testing.T) { "GLOBL ·mask(SB), RODATA, $8\n" + "DATA ·mask+0(SB)/4, $1\n" - got := Source("b_amd64.s", in) + got := Source(in) if got != want { t.Fatalf("formatting mismatch:\n--- got ---\n%q\n--- want ---\n%q", got, want) } - if again := Source("b_amd64.s", got); again != got { + if again := Source(got); again != got { t.Fatalf("not idempotent:\n%q", again) } } @@ -163,8 +163,8 @@ func TestIdempotent(t *testing.T) { if err != nil { t.Fatal(err) } - once := Source("sample_amd64.s", string(src)) - twice := Source("sample_amd64.s", once) + once := Source(string(src)) + twice := Source(once) if once != twice { t.Fatal("formatting is not idempotent on the fixture") } @@ -179,7 +179,7 @@ func TestRoundTrip(t *testing.T) { if err != nil { t.Fatal(err) } - formatted := Source(path, string(src)) + formatted := Source(string(src)) if _, errs := parser.Parse(path, formatted); len(errs) > 0 { t.Errorf("formatted %s no longer parses: %v", path, errs) } diff --git a/format/integration_test.go b/format/integration_test.go index 7dcc5ec..99c7248 100644 --- a/format/integration_test.go +++ b/format/integration_test.go @@ -29,7 +29,7 @@ func TestRoundTripRealGoLibraries(t *testing.T) { if err != nil { t.Fatal(err) } - formatted := Source(path, string(src)) + formatted := Source(string(src)) if _, errs := parser.Parse(path, formatted); len(errs) > 0 { t.Errorf("formatted %s no longer parses: %v", path, errs) } diff --git a/justfile b/justfile index 3353020..fb978b8 100644 --- a/justfile +++ b/justfile @@ -26,7 +26,7 @@ test: sourcedock.dev/petrbalvin/gasm-devkit/arch \ sourcedock.dev/petrbalvin/gasm-devkit/asm \ sourcedock.dev/petrbalvin/gasm-devkit/ast \ - sourcedock.dev/petrbalvin/gasm-devkit/format \ + sourcedock.dev/petrbalvin/gasm-devkit/s \ sourcedock.dev/petrbalvin/gasm-devkit/lexer \ sourcedock.dev/petrbalvin/gasm-devkit/lint \ sourcedock.dev/petrbalvin/gasm-devkit/lsp \ diff --git a/lsp/handlers.go b/lsp/handlers.go index 54fa735..a21a3b5 100644 --- a/lsp/handlers.go +++ b/lsp/handlers.go @@ -212,7 +212,7 @@ func (s *Server) documentFormatting(p documentFormattingParams) []TextEdit { if text == "" { return nil } - formatted := format.Source(uriPath(p.TextDocument.URI), text) + formatted := format.Source(text) if formatted == text { return nil } @@ -596,8 +596,7 @@ func classifyLine(line []token.Token, a *arch.Table, labels map[string]bool) []s case token.Hash: typ = stMacro case token.Ident: - typ = classifyIdent(line, i, first, t.Text, a, labels, - isDirective, isLabel, isInstr, &mnemonicDone) + typ = classifyIdent(i, first, t.Text, a, labels, isDirective, isLabel, isInstr, &mnemonicDone) case token.Colon, token.Comma, token.LParen, token.RParen, token.Plus, token.Minus, token.Star, token.Slash, token.Dollar, token.LAngle, token.RAngle, token.LShift, token.RShift, token.Arrow, token.At: @@ -616,8 +615,7 @@ func classifyLine(line []token.Token, a *arch.Table, labels map[string]bool) []s } // classifyIdent decides the semantic type of an identifier token. -func classifyIdent(line []token.Token, i, first int, text string, a *arch.Table, - labels map[string]bool, isDirective, isLabel, isInstr bool, mnemonicDone *bool) int { +func classifyIdent(i, first int, text string, a *arch.Table, labels map[string]bool, isDirective, isLabel, isInstr bool, mnemonicDone *bool) int { upper := strings.ToUpper(text) switch { diff --git a/lsp/protocol.go b/lsp/protocol.go index fa4b9d5..722b8cc 100644 --- a/lsp/protocol.go +++ b/lsp/protocol.go @@ -29,13 +29,8 @@ type rpcError struct { Message string `json:"message"` } -// JSON-RPC error codes used by LSP. const ( - errParse = -32700 - errInvalidRequest = -32600 errMethodNotFound = -32601 - errInvalidParams = -32602 - errInternal = -32603 ) // --- LSP positions and ranges ---------------------------------------------- @@ -119,16 +114,14 @@ type didCloseParams struct { // --- completion ------------------------------------------------------------- -// Completion item kinds (a useful subset). const ( ciFunction = 3 - ciField = 5 + ciVariable = 6 ciClass = 7 ciModule = 9 ciKeyword = 14 ciConstant = 21 - ciStruct = 22 ) // CompletionItem is one completion suggestion. diff --git a/token/token.go b/token/token.go index 183f2da..98bb933 100644 --- a/token/token.go +++ b/token/token.go @@ -19,13 +19,13 @@ const ( Newline Comment - // Literals and names. + // Ident Literals and names. Ident // instruction mnemonic, label, register or symbol name Number // integer or floating-point literal (sign carried separately) String // "..." Rune // '.' - // Punctuation and operators. + // LParen Punctuation and operators. LParen // ( RParen // ) Comma // , diff --git a/verify/abi_amd64.go b/verify/abi_amd64.go index 4e90187..f07e47c 100644 --- a/verify/abi_amd64.go +++ b/verify/abi_amd64.go @@ -89,8 +89,9 @@ const redZoneSize = 128 // redZoneFill is the byte pattern used to detect red-zone writes. const redZoneFill = 0xA5 -// CallChecked invokes the function with ABI sentinels and a red-zone -// canary, returning both the argument block (with results) and an ABIReport. +// CallChecked invokes the function at fnAddr with ABI sentinels and a +// red-zone canary, returning both the argument block (with results) and an +// ABIReport. func CallChecked(fnAddr uintptr, args []byte) ([]byte, ABIReport, error) { report := ABIReport{} diff --git a/verify/call_amd64.go b/verify/call_amd64.go index 4d4d3be..ae05dcf 100644 --- a/verify/call_amd64.go +++ b/verify/call_amd64.go @@ -24,7 +24,10 @@ var savedSP uintptr // enterJIT switches to the prepared stack and jumps to fn. // It does not return normally; the JIT function's RET transfers control -// to leaveJIT, which restores the Go stack. +// to leaveJIT, which restores the Go stack. The body lives in +// trampoline_amd64.s and reads the parameters from the frame by name. +// +// noinspection GoUnusedParameter // //go:nosplit func enterJIT(fn uintptr, stack uintptr) diff --git a/verify/groundtruth.go b/verify/groundtruth.go index 27b403f..ac65cd6 100644 --- a/verify/groundtruth.go +++ b/verify/groundtruth.go @@ -95,7 +95,6 @@ func groundTruthArch(path, goarch string) (map[string][]byte, error) { return extractGOOBJCode(objData) } -// GOOBJ block indices (cmd/internal/goobj). const ( blkAutolib = iota blkPkgIdx