fix: restore iota blocks, asm --format flag and prose after the syntax pass
Test / vet (push) Successful in 48s
Test / test (push) Successful in 2m35s
Test / build (push) Successful in 40s

This commit is contained in:
2026-08-29 17:12:53 +02:00
parent 15e8b88d32
commit 56ad158772
34 changed files with 170 additions and 183 deletions
+4 -4
View File
@@ -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,
+1 -1
View File
@@ -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. |
+2 -2
View File
@@ -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).
-2
View File
@@ -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.
-4
View File
@@ -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.
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+5 -2
View File
@@ -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 {
+1 -1
View File
@@ -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
+57 -59
View File
@@ -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},
+3 -3
View File
@@ -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
}
+18 -18
View File
@@ -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.
+7 -7
View File
@@ -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:
+1 -3
View File
@@ -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
)
+2 -2
View File
@@ -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")
}
+30 -30
View File
@@ -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 <file1.s> <file2.s>", `
set := newCommand("diff", "gasm diff <file1.s> <file2.s>", `
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 <file1.s> <file2.s>")
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 <file.s>", `
flagSet := newCommand("profile", "gasm profile <file.s>", `
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 <file.s>")
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] <file.s>", `
set := newCommand("verify", "gasm verify [-smoke] [-abi] [-fuzz] [-ground-truth] [-profile] [-call] <file.s>", `
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] <file.s>")
return 2
}
path := fs.Arg(0)
path := set.Arg(0)
targetArch := arch.FromFilename(path)
switch targetArch {
case arch.AMD64:
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
}
+1 -1
View File
@@ -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)
}
+2 -2
View File
@@ -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)
}
+2 -2
View File
@@ -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)
}
+2 -2
View File
@@ -26,7 +26,7 @@ Repository: [sourcedock.dev/petrbalvin/gasm-devkit](https://sourcedock.dev/petrb
graph TD
SRC["source .s"] --> LEX["lexer<br/>token stream"]
LEX --> PAR["parser<br/>AST + diagnostics"]
LEX --> FMT["format<br/>re-space tokens"]
LEX --> FMT["s<br/>re-space tokens"]
PAR --> LINT["lint<br/>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
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+8 -8
View File
@@ -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)
}
+1 -1
View File
@@ -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)
}
+1 -1
View File
@@ -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 \
+3 -5
View File
@@ -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 {
+1 -8
View File
@@ -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.
+2 -2
View File
@@ -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 // ,
+3 -2
View File
@@ -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{}
+4 -1
View File
@@ -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)
-1
View File
@@ -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