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 `GLOBL` in the file defines no longer aborts assembly — it is recorded
as an external relocation (`Image.Externals`, `FuncLayout.Relocs`) and as an external relocation (`Image.Externals`, `FuncLayout.Relocs`) and
becomes an undefined global symbol in the object output. The raw image 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. file can represent a reference the linker must resolve.
### Changed ### Changed
@@ -726,7 +726,7 @@ The formatter behaves like `go fmt` and canonicalises block separation.
### Changed ### 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. `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), 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 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 *first* operand on arm64, riscv64 and loong64; Plan 9 spelling puts it last
on every architecture Go supports. The def/use and save/restore on every architecture Go supports. The def/use and save/restore
classification on those architectures was inverted. 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 comment) is no longer indented as if it were still inside the finished
function body. 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 Zero error-severity diagnostics across the 90-file Go runtime corpus and the
production go-flac kernels (the `register-clobber` audit additionally reports production go-flac kernels (the `register-clobber` audit additionally reports
the go-flac kernels' unsaved callee-saved register use for review). 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 mnemonic alignment) that preserves comments and round-trips through the
parser. parser.
- `lsp`: a Language Server Protocol server over stdio providing completion, - `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. | | `parser` | Line-oriented, error-tolerant parser producing the AST. |
| `arch` | amd64, arm64, riscv64 and loong64 register files and instruction tables. | | `arch` | amd64, arm64, riscv64 and loong64 register files and instruction tables. |
| `lint` | Conservative static checks (13 rules including unused-label, invalid-textflag, stack-imbalance). | | `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). | | `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. | | `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. | | `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 { if len(ops) != 2 && len(ops) != 3 {
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops)) 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])) rd := arm64RegNum(operandRegName(ops[len(ops)-1]))
rn := rd rn := rd
if len(ops) == 3 { 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 // - C_ABCON0 (0 < v ≤ 4095): bitmask first for positive values
// - Negative values: MOVN first, then bitmask // - Negative values: MOVN first, then bitmask
// - C_MOVCON (movcon-eligible, outside ABCON range): MOVZ/MOVN first // - C_MOVCON (movcon-eligible, outside ABCON range): MOVZ/MOVN first
tryBitmaskFirst := (d > 0 && d <= 0xFFF) tryBitmaskFirst := d > 0 && d <= 0xFFF
if tryBitmaskFirst { if tryBitmaskFirst {
// Small immediate: try bitmask first (Go uses ORR for values like $1, $256). // Small immediate: try bitmask first (Go uses ORR for values like $1, $256).
-2
View File
@@ -234,8 +234,6 @@ const (
a64CondLT = 0xb a64CondLT = 0xb
a64CondGT = 0xc a64CondGT = 0xc
a64CondLE = 0xd a64CondLE = 0xd
a64CondAL = 0xe
a64CondNV = 0xf
) )
// arm64CondMap maps Go assembler condition mnemonics to AArch64 condition codes. // 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 // and external ones against undefined globals. The output links with the
// system toolchain (cc/ld) the way a hand-assembled .o would. // system toolchain (cc/ld) the way a hand-assembled .o would.
// ELF constants (ELF64, little-endian, System V).
const ( const (
elfClass64 = 2 elfClass64 = 2
elfDataLSB = 1 elfDataLSB = 1
@@ -36,17 +35,14 @@ const (
shfAlloc = 2 shfAlloc = 2
shfExecInstr = 4 shfExecInstr = 4
stbLocal = 0
stbGlobal = 1 stbGlobal = 1
sttNotype = 0
sttObject = 1 sttObject = 1
sttFunc = 2 sttFunc = 2
sttSection = 3 sttSection = 3
stInfoShift = 4 stInfoShift = 4
rX8664PC32 = 2 rX8664PC32 = 2
rX8664_64 = 1 // R_X86_64_64 (absolute 64-bit)
) )
// elfSym is one symbol-table entry in construction. // 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() ds.debugLineStr = lineStr.bytes()
// Build .debug_line. // Build .debug_line.
ds.debugLine = dwarfBuildLineSection(img, srcFile, ds) ds.debugLine = dwarfBuildLineSection(img, ds)
// Build .debug_info. // Build .debug_info.
ds.debugInfo = dwarfBuildInfoSection(img, srcFile, ds) ds.debugInfo = dwarfBuildInfoSection(img, srcFile, ds)
@@ -98,7 +98,7 @@ func emitDWARF(img *Image, srcFile string) *dwarfSections {
} }
// dwarfBuildLineSection builds a complete .debug_line section. // 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 var b []byte
le := binary.LittleEndian 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") return nil, 0, fmt.Errorf("at most one mask register operand")
} }
if r.idx == 0 { 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 mask = r.idx
continue 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: // 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 // a rounding mode replaces L'L with the rc value, plain SAE and
// broadcast keep the vector length. // broadcast keep the vector length.
b, ll := 0, ll b := 0
switch { switch {
case sfx.rounding >= 0: case sfx.rounding >= 0:
b, ll = 1, sfx.rounding 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) // methods supply the toolchain preamble, the MinLC (pc-value delta unit)
// and the relocation-type mapping for code relocations. // 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 ( const (
blkAutolib = iota blkAutolib = iota
blkPkgIdx 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 // The string table. Absolute offsets: it starts right after the
// 96-byte header (magic, fingerprint, flags, the 19 block offsets). // 96-byte header (magic, fingerprint, flags, the 19 block offsets).
const headerSize = 8 + 8 + 4 + 4*(blkEnd+1) const headerSize = 8 + 8 + 4 + 4*(blkEnd+1)
strTab := []byte{} var strTab []byte
strOff := map[string]uint32{} strOff := map[string]uint32{}
addStr := func(s string) { addStr := func(s string) {
if _, ok := strOff[s]; ok { if _, ok := strOff[s]; ok {
+1 -1
View File
@@ -80,7 +80,7 @@ func (fl *FuncLayout) LineAt(offset int) int {
return 0 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, // field at Off (function-relative) must reach the symbol plus Addend,
// measured from After, the address just past the instruction. An External // measured from After, the address just past the instruction. An External
// relocation names a symbol no GLOBL in the file defines; the object-file // 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 ( const (
lu12iw = 0x0a << 25 lu12iw = 0x0a << 25
ori = 0x00e << 22 ori = 0x00e << 22
lu32id = 0x0b << 25
lu52id = 0x00c << 22
) )
if v == int64(int32(v)) { if v == int64(int32(v)) {
if v&0xfff == 0 && (v < 0x800 || v > 0xfff) { 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 { if rd < 0 {
return nil, fmt.Errorf("%s $sym(SB): invalid destination register", mnem) 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) rd := l64Reg(dst)
if rd < 0 { if rd < 0 {
@@ -832,14 +830,14 @@ func encodeLOONG64Mov(instr *ast.Instr, mnem string, fi loong64FrameInfo, relocs
if rd < 0 { if rd < 0 {
return nil, fmt.Errorf("%s: invalid destination register", mnem) 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) { if !isMemOperand(src) && isMemOperand(dst) {
rs := l64Reg(src) rs := l64Reg(src)
if rs < 0 { if rs < 0 {
return nil, fmt.Errorf("%s: invalid source register", mnem) 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. // Register → register.
@@ -933,20 +931,20 @@ const (
l64St1 l64St1
l64St0 l64St0
l64Dcon12_0 l64dcon120
l64Dcon12_20S l64dcon1220s
l64Dcon20S_20 l64dcon20s20
l64Dcon12_12S l64dcon1212s
l64Dcon20S_12S l64dcon20s12s
l64Dcon20S_0 l64dcon20s0
l64Dcon12_12U l64dcon1212u
l64Dcon20S_12U l64dcon20s12u
l64Dcon32_12S l64dcon3212s
l64Dcon32_0 l64dcon320
l64Dcon32_20 l64dcon3220
l64Dcon12_32S l64dcon1232s
l64Dcon20S_32 l64dcon20s32
l64Dcon32_12U l64dcon3212u
l64Dcon l64Dcon
) )
@@ -987,91 +985,91 @@ func l64DconClass(v int64) int {
lo20 := l64BitField(v, 12, 20) lo20 := l64BitField(v, 12, 20)
lo12 := l64BitField(v, 0, 12) lo12 := l64BitField(v, 0, 12)
if tzb >= 52 { if tzb >= 52 {
return l64Dcon12_0 return l64dcon120
} }
if tzb >= 32 { if tzb >= 32 {
if ((hi20 == l64All1 || hi20 == l64St1) && hi12 == l64All1) || ((hi20 == l64All0 || hi20 == l64St0) && hi12 == l64All0) { 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 tzb >= 12 {
if lo20 == l64St1 || lo20 == l64All1 { if lo20 == l64St1 || lo20 == l64All1 {
if hi20 == l64All1 { if hi20 == l64All1 {
return l64Dcon12_20S return l64dcon1220s
} }
if (hi20 == l64St1 && hi12 == l64All1) || ((hi20 == l64St0 || hi20 == l64All0) && hi12 == l64All0) { if (hi20 == l64St1 && hi12 == l64All1) || ((hi20 == l64St0 || hi20 == l64All0) && hi12 == l64All0) {
return l64Dcon20S_20 return l64dcon20s20
} }
return l64Dcon32_20 return l64dcon3220
} }
if hi20 == l64All0 { if hi20 == l64All0 {
return l64Dcon12_20S return l64dcon1220s
} }
if (hi20 == l64St0 && hi12 == l64All0) || ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) { 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 lo12 == l64St1 || lo12 == l64All1 {
if lo20 == l64All1 { if lo20 == l64All1 {
if hi20 == l64All1 { if hi20 == l64All1 {
return l64Dcon12_12S return l64dcon1212s
} }
if (hi20 == l64St1 && hi12 == l64All1) || ((hi20 == l64St0 || hi20 == l64All0) && hi12 == l64All0) { 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 lo20 == l64St1 {
if hi20 == l64All1 { if hi20 == l64All1 {
return l64Dcon12_32S return l64dcon1232s
} }
if (hi20 == l64St1 && hi12 == l64All1) || ((hi20 == l64St0 || hi20 == l64All0) && hi12 == l64All0) { if (hi20 == l64St1 && hi12 == l64All1) || ((hi20 == l64St0 || hi20 == l64All0) && hi12 == l64All0) {
return l64Dcon20S_32 return l64dcon20s32
} }
return l64Dcon return l64Dcon
} }
if lo20 == l64All0 { if lo20 == l64All0 {
if hi20 == l64All0 { if hi20 == l64All0 {
return l64Dcon12_12U return l64dcon1212u
} }
if ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) || (hi20 == l64St0 && hi12 == l64All0) { if ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) || (hi20 == l64St0 && hi12 == l64All0) {
return l64Dcon20S_12U return l64dcon20s12u
} }
return l64Dcon32_12U return l64dcon3212u
} }
if hi20 == l64All0 { if hi20 == l64All0 {
return l64Dcon12_32S return l64dcon1232s
} }
if ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) || (hi20 == l64St0 && hi12 == l64All0) { if ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) || (hi20 == l64St0 && hi12 == l64All0) {
return l64Dcon20S_32 return l64dcon20s32
} }
return l64Dcon return l64Dcon
} }
if lo20 == l64All0 { if lo20 == l64All0 {
if hi20 == l64All0 { if hi20 == l64All0 {
return l64Dcon12_12U return l64dcon1212u
} }
if ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) || (hi20 == l64St0 && hi12 == l64All0) { 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 lo20 == l64St1 || lo20 == l64All1 {
if hi20 == l64All1 { if hi20 == l64All1 {
return l64Dcon12_32S return l64dcon1232s
} }
if (hi20 == l64St1 && hi12 == l64All1) || ((hi20 == l64St0 || hi20 == l64All0) && hi12 == l64All0) { if (hi20 == l64St1 && hi12 == l64All1) || ((hi20 == l64St0 || hi20 == l64All0) && hi12 == l64All0) {
return l64Dcon20S_32 return l64dcon20s32
} }
return l64Dcon return l64Dcon
} }
if hi20 == l64All0 { if hi20 == l64All0 {
return l64Dcon12_32S return l64dcon1232s
} }
if ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) || (hi20 == l64St0 && hi12 == l64All0) { if ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) || (hi20 == l64St0 && hi12 == l64All0) {
return l64Dcon20S_32 return l64dcon20s32
} }
return l64Dcon return l64Dcon
} }
@@ -1088,29 +1086,29 @@ func l64DconMovWords(rd int, v int64) []uint32 {
ori = 0x00e << 22 ori = 0x00e << 22
) )
switch l64DconClass(v) { switch l64DconClass(v) {
case l64Dcon12_0: case l64dcon120:
return []uint32{l64irr(lu52id, int(v>>52), 0, rd)} 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)} 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)} 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)} 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)} 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)} 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)} 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)} 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)} 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)} 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)} 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)} return []uint32{l64irr(ori, int(v), 0, rd), l64ir(lu32id, int(v>>32), rd), l64irr(lu52id, int(v>>52), rd, rd)}
default: 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)} 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 // encodeLOONG64MemOp encodes a memory load (load = true) or store with a
// 12-bit offset, or the 3-instruction expansion for larger offsets: // 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). // 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) rj, off := l64MemWithFrame(mem, fi)
if rj < 0 { if rj < 0 {
return nil, fmt.Errorf("invalid memory operand") 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 // encodeLOONG64SBAddr emits pcalau12i rd, 0; addi.d rd, rd, 0 with the
// R_LOONG64_ADDR_HI/LO relocation pair, loading a symbol's address. // 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 { if relocs != nil {
*relocs = append(*relocs, *relocs = append(*relocs,
Reloc{Off: 0, After: 0, Name: sym.Name, Kind: RelLoong64AddrHi, Addend: sym.Offset}, 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. // F0–F31, FCC0–FCC7, FCSR0–FCSR31.
if len(name) >= 4 && name[:4] == "FCSR" { 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" { if len(name) >= 3 && name[:3] == "FCC" {
return loong64RegSpecial(name[3:], "FCC", 7) return loong64RegSpecial(name[3:], 7)
} }
if len(name) < 2 { if len(name) < 2 {
return -1 return -1
@@ -131,7 +131,7 @@ func loong64RegNum(name string) int {
} }
// loong64RegSpecial parses a numbered FCC/FCSR register. // loong64RegSpecial parses a numbered FCC/FCSR register.
func loong64RegSpecial(digits, prefix string, max int) int { func loong64RegSpecial(digits string, max int) int {
if digits == "" { if digits == "" {
return -1 return -1
} }
+18 -18
View File
@@ -65,28 +65,28 @@ var (
CX = Reg{idx: 1, size: 2} CX = Reg{idx: 1, size: 2}
DX = Reg{idx: 2, size: 2} DX = Reg{idx: 2, size: 2}
BX = Reg{idx: 3, size: 2} BX = Reg{idx: 3, size: 2}
SP = Reg{idx: 4, size: 2} _ = Reg{idx: 4, size: 2}
BP = Reg{idx: 5, size: 2} _ = Reg{idx: 5, size: 2}
SI = Reg{idx: 6, size: 2} SI = Reg{idx: 6, size: 2}
DI = Reg{idx: 7, size: 2} DI = Reg{idx: 7, size: 2}
EAX = Reg{idx: 0, size: 4} _ = Reg{idx: 0, size: 4}
ECX = Reg{idx: 1, size: 4} _ = Reg{idx: 1, size: 4}
EDX = Reg{idx: 2, size: 4} _ = Reg{idx: 2, size: 4}
EBX = Reg{idx: 3, size: 4} _ = Reg{idx: 3, size: 4}
ESP = Reg{idx: 4, size: 4} _ = Reg{idx: 4, size: 4}
EBP = Reg{idx: 5, size: 4} _ = Reg{idx: 5, size: 4}
ESI = Reg{idx: 6, size: 4} _ = Reg{idx: 6, size: 4}
EDI = Reg{idx: 7, size: 4} _ = Reg{idx: 7, size: 4}
RAX = Reg{idx: 0, size: 8} _ = Reg{idx: 0, size: 8}
RCX = Reg{idx: 1, size: 8} _ = Reg{idx: 1, size: 8}
RDX = Reg{idx: 2, size: 8} _ = Reg{idx: 2, size: 8}
RBX = Reg{idx: 3, size: 8} _ = Reg{idx: 3, size: 8}
RSP = Reg{idx: 4, size: 8} _ = Reg{idx: 4, size: 8}
RBP = Reg{idx: 5, size: 8} _ = Reg{idx: 5, size: 8}
RSI = Reg{idx: 6, size: 8} _ = Reg{idx: 6, size: 8}
RDI = Reg{idx: 7, size: 8} _ = Reg{idx: 7, size: 8}
) )
// regByName maps an assembly register name (case-insensitive) to a Reg. // 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, // MOV is a pseudo-instruction that the Go assembler uses for loads,
// stores, register moves and immediate loads. // stores, register moves and immediate loads.
case "MOV": 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). // JALR: indirect jump/call. Plan 9: JALR rs1, rd or JALR offset(rs1).
case "JALR": case "JALR":
@@ -527,7 +527,7 @@ func isImmOperand(op *ast.Operand) bool {
// - MOV Rs, (Rd) register-relative store // - MOV Rs, (Rd) register-relative store
// - MOV Rs, Rd register-to-register move (ADDI $0) // - MOV Rs, Rd register-to-register move (ADDI $0)
// - MOV $imm, Rd load immediate (ADDI or LUI+ADDIW) // - 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 ops := instr.Operands
if len(ops) != 2 { if len(ops) != 2 {
return nil, fmt.Errorf("MOV expects 2 operands, got %d", len(ops)) 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": case "ADDI":
rd, rs1, imm := extractITypeParams(instr, fi) rd, rs1, imm := extractITypeParams(instr)
if rd == -1 || rs1 == -1 { if rd == -1 || rs1 == -1 {
return 0, false return 0, false
} }
@@ -1060,13 +1060,13 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) {
} }
case "ADDIW": case "ADDIW":
rd, rs1, imm := extractITypeParams(instr, fi) rd, rs1, imm := extractITypeParams(instr)
if rd == rs1 && rd != 0 && imm >= -32 && imm <= 31 { if rd == rs1 && rd != 0 && imm >= -32 && imm <= 31 {
return rvcCI(0x1, uint32(rd), uint32(imm)&0x3F), true return rvcCI(0x1, uint32(rd), uint32(imm)&0x3F), true
} }
case "SLLI", "SRLI", "SRAI": 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 rd == rs1 && rd != 0 && imm != 0 && imm >= 1 && imm <= 63 {
if mnem == "SLLI" { if mnem == "SLLI" {
// C.SLLI: funct3=0, op=10 quadrant, shamt in bits [12|6:2]. // 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": case "ANDI":
rd, rs1, imm := extractITypeParams(instr, fi) rd, rs1, imm := extractITypeParams(instr)
if isRVCIntReg(rd) && rd == rs1 && imm >= -32 && imm <= 31 { if isRVCIntReg(rd) && rd == rs1 && imm >= -32 && imm <= 31 {
// C.ANDI: CB-type, funct3=0x4, funct2=0x2. // C.ANDI: CB-type, funct3=0x4, funct2=0x2.
return rvcCBShift(0x2, rvcReg3(rd), uint32(imm)&0x3F), true 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 // extractITypeParams extracts rd, rs1, and immediate for an I-type
// instruction. The Plan 9 order is INSTR $imm, rs1, rd (3 operands) or // instruction. The Plan 9 order is INSTR $imm, rs1, rd (3 operands) or
// INSTR $imm, rd (2 operands, rd is also the source). // 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 ops := instr.Operands
switch len(ops) { switch len(ops) {
case 3: case 3:
+1 -3
View File
@@ -123,10 +123,8 @@ type Symbol struct {
// OpKind classifies an operand syntactically. // OpKind classifies an operand syntactically.
type OpKind int type OpKind int
// Operand kinds.
const ( const (
OpInvalid OpKind = iota OpImmediate = iota // $value
OpImmediate // $value
OpAddr // register, memory reference, symbol or label 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 // TestInterfaces confirms the node types satisfy their interfaces, so callers
// can range over Decls and Stmts. // can range over Decls and Stmts.
func TestInterfaces(t *testing.T) { 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 { if len(decls) != 5 {
t.Fatal("decl interface set") t.Fatal("decl interface set")
} }
var stmts []Stmt = []Stmt{&Label{}, &Instr{}} var stmts = []Stmt{&Label{}, &Instr{}}
if len(stmts) != 2 { if len(stmts) != 2 {
t.Fatal("stmt interface set") t.Fatal("stmt interface set")
} }
+30 -30
View File
@@ -299,7 +299,7 @@ given.
rc = 1 rc = 1
continue continue
} }
out := format.Source(path, src) out := format.Source(src)
if dirMode || *write { if dirMode || *write {
if out != src { if out != src {
if err := os.WriteFile(path, []byte(out), 0o644); err != nil { 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 return 1
} }
img, err := assembleFile(path, targetArch, f) img, err := assembleFile(targetArch, f)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", path, err) fmt.Fprintf(os.Stderr, "%s: %v\n", path, err)
return 1 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. // cmdDiff compares the machine code of two assembly files.
func cmdDiff(args []string) int { 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. Compare the machine code produced by assembling two files.
Shows which functions differ and the byte-level differences. Shows which functions differ and the byte-level differences.
Useful for verifying that two implementations produce identical code, 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, Use --map to compare functions whose names differ between the files,
e.g. --map wideCopyAVX2=wideCopyAVX512 pairs the two regardless of suffix. 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") mapSpec := set.String("map", "", "comma-separated old=new pairs to match functions with different names")
fs.Parse(args) set.Parse(args)
if fs.NArg() != 2 { if set.NArg() != 2 {
fmt.Fprintln(os.Stderr, "usage: gasm diff <file1.s> <file2.s>") fmt.Fprintln(os.Stderr, "usage: gasm diff <file1.s> <file2.s>")
return 2 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). // Parse the name mapping (file1 name → file2 name).
nameMap := make(map[string]string) 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. // 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 { switch targetArch {
case arch.AMD64: case arch.AMD64:
return asm.AssembleFile(f) return asm.AssembleFile(f)
@@ -668,7 +668,7 @@ func assemblePath(path string) (*asm.Image, error) {
if len(errs) > 0 { if len(errs) > 0 {
return nil, fmt.Errorf("parse errors") 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. // 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. // cmdProfile shows the basic-block structure of functions in an assembly file.
func cmdProfile(args []string) int { 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. Show the basic-block structure of functions in an assembly file.
Lists each function's labels, their offsets, and the block boundaries. Lists each function's labels, their offsets, and the block boundaries.
This is the static structure; for runtime execution counts, use This is the static structure; for runtime execution counts, use
gasm verify --fuzz which exercises the code paths. gasm verify --fuzz which exercises the code paths.
`) `)
fs.Parse(args) flagSet.Parse(args)
if fs.NArg() != 1 { if flagSet.NArg() != 1 {
fmt.Fprintln(os.Stderr, "usage: gasm profile <file.s>") fmt.Fprintln(os.Stderr, "usage: gasm profile <file.s>")
return 2 return 2
} }
path := fs.Arg(0) path := flagSet.Arg(0)
// Load the file to get function metadata. // Load the file to get function metadata.
k, err := verify.Load(path) k, err := verify.Load(path)
@@ -1021,7 +1021,7 @@ func cmdVerifyARM64(path string, groundTruth, profile bool) int {
} }
func cmdVerify(args []string) 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 Assemble FILE (amd64), map it into executable memory and report the available
functions. This confirms the assembled image is self-consistent (no functions. This confirms the assembled image is self-consistent (no
unresolved external symbols) and executable — the prerequisite for dynamic 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. 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. decoders) that crash on random input but should succeed on valid data.
`) `)
smoke := fs.Bool("smoke", false, "call each NOSPLIT function with zeroed args") smoke := set.Bool("smoke", false, "call each NOSPLIT function with zeroed args")
abi := fs.Bool("abi", false, "run ABI-checking calls (sentinel registers + red zone)") abi := set.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") abiN := set.Int("abi-n", 100, "number of ABI check iterations with varied inputs")
profile := fs.Bool("profile", false, "list basic-block structure per function") profile := set.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") groundTruth := set.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") fuzz := set.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") fuzzN := set.Int("n", 1000, "number of fuzz iterations per function")
fuzzOne := fs.String("fuzz-one", "", "") // hidden: fuzz a single function (subprocess mode) fuzzOne := set.String("fuzz-one", "", "") // hidden: fuzz a single function (subprocess mode)
sweepOne := fs.String("sweep-one", "", "") // hidden: smoke/abi a single function (subprocess mode) sweepOne := set.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") call := set.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)") bufSpec := set.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)") scalarSpec := set.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") repeat := set.Int("repeat", 1, "number of times to repeat a -call invocation")
fs.Parse(args) set.Parse(args)
if fs.NArg() != 1 { if set.NArg() != 1 {
fmt.Fprintln(os.Stderr, "usage: gasm verify [-smoke] [-abi] [-fuzz] [-ground-truth] [-profile] [-call] <file.s>") fmt.Fprintln(os.Stderr, "usage: gasm verify [-smoke] [-abi] [-fuzz] [-ground-truth] [-profile] [-call] <file.s>")
return 2 return 2
} }
path := fs.Arg(0) path := set.Arg(0)
targetArch := arch.FromFilename(path) targetArch := arch.FromFilename(path)
switch targetArch { switch targetArch {
case arch.AMD64: 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. // 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 { type bufSpec struct {
name string name string
size int size int
+1 -1
View File
@@ -77,7 +77,7 @@ func RunTarget(asmPath, funcName, argsFile, tmpDir string) error {
bufSpecFile := tmpDir + "/bufspec" bufSpecFile := tmpDir + "/bufspec"
if bufSpec, err := os.ReadFile(bufSpecFile); err == nil && len(bufSpec) > 0 { 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 { if err != nil {
return fmt.Errorf("debug target: setup buffers: %w", err) 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) stackArgs := make([]byte, fl.Args)
copy(stackArgs, args) copy(stackArgs, args)
_, callErr := verify.Call(fnAddr, stackArgs) _, callErr := verify.Call(stackArgs)
if callErr != nil { if callErr != nil {
os.Exit(1) os.Exit(1)
} }
+2 -2
View File
@@ -86,7 +86,7 @@ func RunTarget(asmPath, funcName, argsFile, tmpDir string) error {
bufSpecFile := tmpDir + "/bufspec" bufSpecFile := tmpDir + "/bufspec"
if bufSpec, err := os.ReadFile(bufSpecFile); err == nil && len(bufSpec) > 0 { 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 { if err != nil {
return fmt.Errorf("debug target: setup buffers: %w", err) 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) stackArgs := make([]byte, fl.Args)
copy(stackArgs, args) copy(stackArgs, args)
_, callErr := verify.Call(fnAddr, stackArgs) _, callErr := verify.Call(stackArgs)
if callErr != nil { if callErr != nil {
os.Exit(1) os.Exit(1)
} }
+2 -2
View File
@@ -86,7 +86,7 @@ func RunTarget(asmPath, funcName, argsFile, tmpDir string) error {
bufSpecFile := tmpDir + "/bufspec" bufSpecFile := tmpDir + "/bufspec"
if bufSpec, err := os.ReadFile(bufSpecFile); err == nil && len(bufSpec) > 0 { 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 { if err != nil {
return fmt.Errorf("debug target: setup buffers: %w", err) 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) stackArgs := make([]byte, fl.Args)
copy(stackArgs, args) copy(stackArgs, args)
_, callErr := verify.Call(fnAddr, stackArgs) _, callErr := verify.Call(stackArgs)
if callErr != nil { if callErr != nil {
os.Exit(1) os.Exit(1)
} }
+2 -2
View File
@@ -26,7 +26,7 @@ Repository: [sourcedock.dev/petrbalvin/gasm-devkit](https://sourcedock.dev/petrb
graph TD graph TD
SRC["source .s"] --> LEX["lexer<br/>token stream"] SRC["source .s"] --> LEX["lexer<br/>token stream"]
LEX --> PAR["parser<br/>AST + diagnostics"] 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 --> LINT["lint<br/>static checks"]
PAR --> LSP["lsp server"] PAR --> LSP["lsp server"]
LEX --> LSP 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 reference) and a literal index is range-checked; a named index constant such
as `$PCDATA_StackMapIndex` is accepted without a range check. as `$PCDATA_StackMapIndex` is accepted without a range check.
### `format` ### `s`
The formatter works on the **token stream, not the AST**, so it preserves The formatter works on the **token stream, not the AST**, so it preserves
every line — comments and blanks included. It normalises indentation, operand 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 **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 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` 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`, same one gasm writes — the parser reuses the same layout (`blkSymdef`,
`blkNonpkgdef`, the string table), so no new dependency was needed. `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 parser/ Line-oriented parser
arch/ Register and instruction tables (generated) arch/ Register and instruction tables (generated)
lint/ Static analysis rules lint/ Static analysis rules
format/ Canonical formatter s/ Canonical formatter
lsp/ Language Server Protocol server lsp/ Language Server Protocol server
asm/ Standalone assembler, encoder, object emitters asm/ Standalone assembler, encoder, object emitters
verify/ JIT execution, differential testing, ABI checks verify/ JIT execution, differential testing, ABI checks
+1 -1
View File
@@ -16,7 +16,7 @@ import (
) )
// Source returns the canonical formatting of src. // Source returns the canonical formatting of src.
func Source(path, src string) string { func Source(src string) string {
lines := splitLines(lexer.Tokenize(src)) lines := splitLines(lexer.Tokenize(src))
// First pass: classify each line and record, for every instruction, the // 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" + "\tVFMADD231PD Z14, Z12, Z10\n" +
"\tRET\n" "\tRET\n"
got := Source("f_amd64.s", in) got := Source(in)
if got != want { if got != want {
t.Fatalf("formatting mismatch:\n--- got ---\n%q\n--- want ---\n%q", 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" + "TEXT ·second(SB), NOSPLIT, $0\n" +
"\tRET\n" "\tRET\n"
got := Source("d_amd64.s", in) got := Source(in)
if got != want { if got != want {
t.Fatalf("formatting mismatch:\n--- got ---\n%q\n--- want ---\n%q", got, want) t.Fatalf("formatting mismatch:\n--- got ---\n%q\n--- want ---\n%q", got, want)
} }
// Body comments stay indented. // Body comments stay indented.
body := "#include \"textflag.h\"\nTEXT ·f(SB), NOSPLIT, $0\n// inside the body\nXORQ AX, AX\nRET\n" 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") { if !strings.Contains(gotBody, "\t// inside the body\n") {
t.Fatalf("body comment must stay indented:\n%q", gotBody) 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" + "GLOBL ·mask(SB), RODATA, $8\n" +
"DATA ·mask+0(SB)/4, $1\n" "DATA ·mask+0(SB)/4, $1\n"
got := Source("b_amd64.s", in) got := Source(in)
if got != want { if got != want {
t.Fatalf("formatting mismatch:\n--- got ---\n%q\n--- want ---\n%q", 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) t.Fatalf("not idempotent:\n%q", again)
} }
} }
@@ -163,8 +163,8 @@ func TestIdempotent(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
once := Source("sample_amd64.s", string(src)) once := Source(string(src))
twice := Source("sample_amd64.s", once) twice := Source(once)
if once != twice { if once != twice {
t.Fatal("formatting is not idempotent on the fixture") t.Fatal("formatting is not idempotent on the fixture")
} }
@@ -179,7 +179,7 @@ func TestRoundTrip(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
formatted := Source(path, string(src)) formatted := Source(string(src))
if _, errs := parser.Parse(path, formatted); len(errs) > 0 { if _, errs := parser.Parse(path, formatted); len(errs) > 0 {
t.Errorf("formatted %s no longer parses: %v", path, errs) 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
formatted := Source(path, string(src)) formatted := Source(string(src))
if _, errs := parser.Parse(path, formatted); len(errs) > 0 { if _, errs := parser.Parse(path, formatted); len(errs) > 0 {
t.Errorf("formatted %s no longer parses: %v", path, errs) 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/arch \
sourcedock.dev/petrbalvin/gasm-devkit/asm \ sourcedock.dev/petrbalvin/gasm-devkit/asm \
sourcedock.dev/petrbalvin/gasm-devkit/ast \ 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/lexer \
sourcedock.dev/petrbalvin/gasm-devkit/lint \ sourcedock.dev/petrbalvin/gasm-devkit/lint \
sourcedock.dev/petrbalvin/gasm-devkit/lsp \ sourcedock.dev/petrbalvin/gasm-devkit/lsp \
+3 -5
View File
@@ -212,7 +212,7 @@ func (s *Server) documentFormatting(p documentFormattingParams) []TextEdit {
if text == "" { if text == "" {
return nil return nil
} }
formatted := format.Source(uriPath(p.TextDocument.URI), text) formatted := format.Source(text)
if formatted == text { if formatted == text {
return nil return nil
} }
@@ -596,8 +596,7 @@ func classifyLine(line []token.Token, a *arch.Table, labels map[string]bool) []s
case token.Hash: case token.Hash:
typ = stMacro typ = stMacro
case token.Ident: case token.Ident:
typ = classifyIdent(line, i, first, t.Text, a, labels, typ = classifyIdent(i, first, t.Text, a, labels, isDirective, isLabel, isInstr, &mnemonicDone)
isDirective, isLabel, isInstr, &mnemonicDone)
case token.Colon, token.Comma, token.LParen, token.RParen, case token.Colon, token.Comma, token.LParen, token.RParen,
token.Plus, token.Minus, token.Star, token.Slash, token.Dollar, token.Plus, token.Minus, token.Star, token.Slash, token.Dollar,
token.LAngle, token.RAngle, token.LShift, token.RShift, token.Arrow, token.At: 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. // classifyIdent decides the semantic type of an identifier token.
func classifyIdent(line []token.Token, i, first int, text string, a *arch.Table, func classifyIdent(i, first int, text string, a *arch.Table, labels map[string]bool, isDirective, isLabel, isInstr bool, mnemonicDone *bool) int {
labels map[string]bool, isDirective, isLabel, isInstr bool, mnemonicDone *bool) int {
upper := strings.ToUpper(text) upper := strings.ToUpper(text)
switch { switch {
+1 -8
View File
@@ -29,13 +29,8 @@ type rpcError struct {
Message string `json:"message"` Message string `json:"message"`
} }
// JSON-RPC error codes used by LSP.
const ( const (
errParse = -32700
errInvalidRequest = -32600
errMethodNotFound = -32601 errMethodNotFound = -32601
errInvalidParams = -32602
errInternal = -32603
) )
// --- LSP positions and ranges ---------------------------------------------- // --- LSP positions and ranges ----------------------------------------------
@@ -119,16 +114,14 @@ type didCloseParams struct {
// --- completion ------------------------------------------------------------- // --- completion -------------------------------------------------------------
// Completion item kinds (a useful subset).
const ( const (
ciFunction = 3 ciFunction = 3
ciField = 5
ciVariable = 6 ciVariable = 6
ciClass = 7 ciClass = 7
ciModule = 9 ciModule = 9
ciKeyword = 14 ciKeyword = 14
ciConstant = 21 ciConstant = 21
ciStruct = 22
) )
// CompletionItem is one completion suggestion. // CompletionItem is one completion suggestion.
+2 -2
View File
@@ -19,13 +19,13 @@ const (
Newline Newline
Comment Comment
// Literals and names. // Ident Literals and names.
Ident // instruction mnemonic, label, register or symbol name Ident // instruction mnemonic, label, register or symbol name
Number // integer or floating-point literal (sign carried separately) Number // integer or floating-point literal (sign carried separately)
String // "..." String // "..."
Rune // '.' Rune // '.'
// Punctuation and operators. // LParen Punctuation and operators.
LParen // ( LParen // (
RParen // ) RParen // )
Comma // , Comma // ,
+3 -2
View File
@@ -89,8 +89,9 @@ const redZoneSize = 128
// redZoneFill is the byte pattern used to detect red-zone writes. // redZoneFill is the byte pattern used to detect red-zone writes.
const redZoneFill = 0xA5 const redZoneFill = 0xA5
// CallChecked invokes the function with ABI sentinels and a red-zone // CallChecked invokes the function at fnAddr with ABI sentinels and a
// canary, returning both the argument block (with results) and an ABIReport. // red-zone canary, returning both the argument block (with results) and an
// ABIReport.
func CallChecked(fnAddr uintptr, args []byte) ([]byte, ABIReport, error) { func CallChecked(fnAddr uintptr, args []byte) ([]byte, ABIReport, error) {
report := ABIReport{} report := ABIReport{}
+4 -1
View File
@@ -24,7 +24,10 @@ var savedSP uintptr
// enterJIT switches to the prepared stack and jumps to fn. // enterJIT switches to the prepared stack and jumps to fn.
// It does not return normally; the JIT function's RET transfers control // 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 //go:nosplit
func enterJIT(fn uintptr, stack uintptr) 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) return extractGOOBJCode(objData)
} }
// GOOBJ block indices (cmd/internal/goobj).
const ( const (
blkAutolib = iota blkAutolib = iota
blkPkgIdx blkPkgIdx