From d7ee1b78d43177d36d7a3827615eca6e40d5d3bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Fri, 7 Aug 2026 22:20:26 +0200 Subject: [PATCH] feat: drop Mach-O and macOS support, Linux-only --- CHANGELOG.md | 5 + CONTRIBUTING.md | 4 +- README.md | 47 ++++---- asm/macho.go | 258 ------------------------------------------- asm/macho_test.go | 127 --------------------- cmd/gasm/main.go | 17 ++- docs/ARCHITECTURE.md | 4 +- docs/ZED.md | 79 ------------- docs/cli.md | 4 +- 9 files changed, 41 insertions(+), 504 deletions(-) delete mode 100644 asm/macho.go delete mode 100644 asm/macho_test.go delete mode 100644 docs/ZED.md diff --git a/CHANGELOG.md b/CHANGELOG.md index faf6f48..23d46a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ and this project adheres to [Conventional Commits](https://www.conventionalcommi Unreleased changes on the `development` branch. +### Changed + +- **Linux only.** The toolkit, its CI and the released binaries are now + Linux-only; cross-compiled to linux/{amd64,arm64,riscv64,loong64}. + ## [0.29.0] — 2026-08-07 RISC-V GOOBJ emission, YMM vector register display, named buffer allocation diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b0d21f..cef57bb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ - Go 1.26 or later (`toolchain go1.26.5`) - `just` command runner -- A Linux, FreeBSD, or macOS host on amd64 or arm64 +- A Linux host on amd64, arm64, riscv64 or loong64 ## Development Setup @@ -85,7 +85,7 @@ Attribute agent authorship in issues and pull requests on one trailing line: ``` -_Assisted-by: DeepSeek V4 Pro_ +_Assisted-by: Qwen 3.8 Max_ ``` ## Questions diff --git a/README.md b/README.md index f986fc1..72ce3bb 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ gasm profile show basic-block structure of functions > **Status: Phase 4 — done, Phase 5 underway.** Phase 1 (the language > foundation, linter, formatter and language server) shipped in v0.1.0; > Phase 2 (the standalone assembler — the full amd64 instruction set plus -> ELF, Mach-O and GOOBJ object emission) in v0.12.0; Phase 3 (dynamic +> ELF and GOOBJ object emission) in v0.12.0; Phase 3 (dynamic > analysis — JIT execution, differential testing, ABI checks and coverage > profiling) in v0.25.0; Phase 4 (interactive debugger — ptrace-based, > breakpoints, watchpoints, stepping, vector register display, named buffer @@ -56,9 +56,13 @@ carries the traditional conditional-jump spellings (`JZ`, `JNZ`, `JA`, `JC`, command — `just gen` — and requires only a Go installation; the committed output has no runtime dependency on the toolchain. -The target *architectures* above are what the toolkit analyses. The toolkit -itself is portable Go and builds on Linux, FreeBSD and macOS, on amd64 and -arm64 hosts. +## Supported Platforms + +The toolkit runs on Linux. All four Linux architectures are supported as +hosts — amd64, arm64, riscv64 and loong64 — and the release matrix +cross-compiles the same four targets. + +**FreeBSD support is planned for a future release.** ## Roadmap @@ -133,14 +137,14 @@ Done so far: stack deltas, `pcfile`, `pcline`, `pcinline`). Verified end-to-end by swapping a gasm-emitted object into a `go build` in place of the toolchain's, linking and running — bit-identical behaviour. -- **Object-file emission** — `gasm asm --format elf` / `--format macho` - writes a relocatable object (a `.text` and a `.data` section, a symbol - table — file-local `<>` symbols local, the rest global — and one - `R_X86_64_PC32` / `X86_64_RELOC_SIGNED` relocation per static-symbol - reference) that links with the system toolchain: external references - resolve against undefined symbols, file-local ones against the data - section. Verified end-to-end by linking a gasm-emitted object with a C - driver and running it. +- **Object-file emission** — `gasm asm --format elf` writes a relocatable + object (a `.text` and a `.data` section, a symbol table — file-local `<>` + symbols local, the rest global — and one `R_X86_64_PC32` relocation per + static-symbol reference) that links with the system toolchain: external + references resolve against undefined symbols, file-local ones against the + data section. Verified end-to-end by linking a gasm-emitted object with + a C driver and running it. RISC-V uses the equivalent `R_RISCV_PCREL_HI20` + / `R_RISCV_PCREL_LO12_I` pair for AUIPC+JAL/JALR sequences. - **`FP`/`SP` frame mapping** — the pseudo-registers are translated onto the hardware stack pointer (`x+N(FP)` → `(N+8)(SP)` for a zero frame, `(N+frame+ 16)(SP)` with a frame pointer; locals via `x-N(SP)`), and the Go-style @@ -256,8 +260,7 @@ portable Go implementation every kernel is derived from. label resolution, breakpoint management via `/proc/pid/mem`, named buffer allocation with pattern filling (`--buf`), and an interactive REPL. - **Remaining:** disassembly at PC (x86asm decode), memory-write support, - watchpoints, source-line mapping, and multi-platform support - (FreeBSD/macOS ptrace variants). + watchpoints, and source-line mapping. ### Phase 5 — the other architectures · *in progress* @@ -277,8 +280,9 @@ portable Go implementation every kernel is derived from. dependency, `golang.org/x/arch`, is used **only in tests** to validate the instruction encoder by round-trip decoding — it is never linked into the `gasm` binary. -- **Portable.** Builds and runs on Linux, FreeBSD and macOS; amd64 and arm64 - hosts. Latest stable Go only. +- **Linux-only.** Runs natively on amd64, arm64, riscv64 and loong64 Linux + hosts; the release matrix cross-compiles the same four targets. Latest + stable Go only. - **No vendor lock-in.** The integration surface is the Language Server Protocol and a command-line interface — both open standards. No cloud service, no proprietary API, no dependence on any one editor's internals. @@ -297,7 +301,7 @@ portable Go implementation every kernel is derived from. | `arch` | amd64, arm64, riscv64 and loong64 register files and instruction tables. | | `lint` | Conservative static checks. | | `format` | A canonical formatter — `gofmt` for assembly. | -| `asm` | The standalone assembler: amd64 and RISC-V encoders, linker, object-file emitters (ELF, Mach-O, GOOBJ). | +| `asm` | The standalone assembler: amd64 and RISC-V encoders, linker, object-file emitters (ELF, GOOBJ). | | `verify` | JIT execution substrate for dynamic analysis, combined ABI+fuzz differential testing (Phase 3). | | `debug` | Interactive ptrace debugger with GPR/YMM register display and named buffer allocation (Phase 4). | | `lsp` | Language Server Protocol server. | @@ -305,9 +309,8 @@ portable Go implementation every kernel is derived from. | `_gen` | The generator that rebuilds the instruction tables from the Go toolchain. | See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the design rationale and -data flow, [`docs/ZED.md`](docs/ZED.md) for the editor-integration story, and -[`docs/DEFERRED.md`](docs/DEFERRED.md) for design decisions deliberately -postponed (with the analysis needed to pick them up again). +data flow, and [`docs/DEFERRED.md`](docs/DEFERRED.md) for design decisions +deliberately postponed (with the analysis needed to pick them up again). ## Quick start @@ -353,10 +356,6 @@ binary and associate it with `.s` files. Syntax highlighting is delivered as infers the target architecture from the file-name suffix (`_amd64.s` / `_arm64.s` / `_riscv64.s` / `_loong64.s`). -Zed users should read [`docs/ZED.md`](docs/ZED.md): Zed's native highlighting -engine (Tree-sitter, C/WASM) cannot be fed from pure Go, so the pure-Go path -into Zed is the language server and its semantic tokens. - ## Licence BSD-3-Clause — the same licence as Go itself. See [`LICENSE`](LICENSE). diff --git a/asm/macho.go b/asm/macho.go deleted file mode 100644 index 00efbed..0000000 --- a/asm/macho.go +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) -// SPDX-License-Identifier: BSD-3-Clause - -package asm - -import ( - "encoding/binary" - "fmt" -) - -// This file emits Mach-O x86-64 objects (MH_OBJECT) from an assembled -// Image, in the shape the Darwin assembler produces: one unnamed segment -// carrying a __TEXT,__text and a __DATA,__data section laid out back to -// back at addresses zero and len(code), a symbol table (locals first, then -// exported definitions, then undefined externals) and one relocation entry -// per static-symbol reference, of type X86_64_RELOC_SIGNED. -// -// The image's own address space carries straight over — the data section -// starts immediately after the code, and the layout padding already lives -// inside Image.Data — so every symbol keeps its image address as its -// n_value, and a local (non-external) relocation leaves the displacement -// the assembler resolved in place: the linker only adjusts it by the -// section's final movement. - -// Mach-O constants. -const ( - machoMagic64 = 0xfeedfacf - machoCPUamd64 = 0x01000007 // CPU_TYPE_X86_64 - machoCPUSubAll = 3 // CPU_SUBTYPE_X86_64_ALL - machoObj = 1 // MH_OBJECT - - machoSegment64 = 0x19 // LC_SEGMENT_64 - machoSymtab = 0x2 // LC_SYMTAB - - machoSectTextFlags = 0x80000400 // S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS - - nUndf = 0x00 // undefined symbol - nSect = 0x0e // defined in section number n_sect - nExt = 0x01 // external (exported or undefined-global) bit - - x8664RelocSigned = 1 -) - -// MachOObject returns the image as a Mach-O x86-64 relocatable object -// (MH_OBJECT), the shape the Darwin toolchain links. Symbol names follow -// the same rules as the ELF output. Every static-symbol reference becomes -// an X86_64_RELOC_SIGNED relocation: external references against their -// undefined symbol, file-local ones against the __DATA section with the -// resolved displacement carried in the instruction bytes. -func (img *Image) MachOObject() ([]byte, error) { - le := binary.LittleEndian - - // Section ordinals (1-based, as Mach-O numbers them). - const ( - sectText = 1 - sectData = 2 - ) - - // Object address space: code at 0, data immediately after (the layout - // padding is already part of img.Data, so image addresses are object - // addresses). - textAddr := uint64(0) - dataAddr := uint64(len(img.Code)) - vmsize := dataAddr + uint64(len(img.Data)) - - // The code, with external displacements primed to addend − 4: the - // linker adds the symbol's address to the field as it stands. Local - // displacements stay as the assembler resolved them. - code := append([]byte(nil), img.Code...) - for _, fn := range img.Funcs { - for _, r := range fn.Relocs { - if r.External { - // Prime the field to the addend measured from the patch - // site: the assembler records it from the instruction end, - // After − Off bytes past the field. - copy(code[fn.Offset+r.Off:], le32(r.Addend-int64(r.After-r.Off))) - } - } - } - - // Symbols: locals first, then exported definitions, then undefined - // externals — the order the classic link editor expects. - type machoSym struct { - name string - typ byte - sect byte - value uint64 - } - var locals, globals, undefs []machoSym - for _, fn := range img.Funcs { - s := machoSym{name: objectName(fn.Pkg, fn.Name), typ: nSect, sect: sectText, value: textAddr + uint64(fn.Offset)} - if fn.Static { - locals = append(locals, s) - } else { - s.typ |= nExt - globals = append(globals, s) - } - } - for _, d := range img.DataSyms { - s := machoSym{name: objectName(d.Pkg, d.Name), typ: nSect, sect: sectData, value: dataAddr + uint64(d.Offset)} - if d.Static { - locals = append(locals, s) - } else { - s.typ |= nExt - globals = append(globals, s) - } - } - for _, name := range img.Externals { - undefs = append(undefs, machoSym{name: name, typ: nUndf | nExt}) - } - syms := append(append(locals, globals...), undefs...) - symIdx := map[string]int{} - for i, s := range syms { - symIdx[s.name] = i - } - - // Relocations, attached to the __text section. - type machoReloc struct { - addr uint32 - symnum uint32 - extern bool - } - var relocs []machoReloc - for _, fn := range img.Funcs { - for _, r := range fn.Relocs { - rel := machoReloc{addr: uint32(fn.Offset + r.Off)} - if r.External { - idx, ok := symIdx[r.Name] - if !ok { - return nil, fmt.Errorf("relocation references unknown symbol %q", r.Name) - } - rel.symnum = uint32(idx) - rel.extern = true - } else { - // Section-relative: r_symbolnum carries the section number - // and the resolved displacement stays in the bytes. - rel.symnum = sectData - } - relocs = append(relocs, rel) - } - } - - // The string table opens with the conventional " \0". - strtab := []byte{' ', 0} - strOff := map[string]int{} - for _, s := range syms { - if _, ok := strOff[s.name]; ok { - continue - } - strOff[s.name] = len(strtab) - strtab = append(strtab, s.name...) - strtab = append(strtab, 0) - } - - // File layout: header, the two load commands, section data (code, - // data), the relocation table, the symbol table, the string table. - const ( - hdrSize = 32 - segCmdSize = 72 + 2*80 // segment command with two sections - symCmdSize = 24 - ) - sizeofcmds := segCmdSize + symCmdSize - dataOff := hdrSize + sizeofcmds - reloff := dataOff + len(code) + len(img.Data) - symoff := reloff + 8*len(relocs) - stroff := symoff + 16*len(syms) - - out := make([]byte, stroff+len(strtab)) - - // mach_header_64. - le.PutUint32(out[0:], machoMagic64) - le.PutUint32(out[4:], machoCPUamd64) - le.PutUint32(out[8:], machoCPUSubAll) - le.PutUint32(out[12:], machoObj) - le.PutUint32(out[16:], 2) // ncmds - le.PutUint32(out[20:], uint32(sizeofcmds)) - le.PutUint32(out[24:], 0) // flags - le.PutUint32(out[28:], 0) // reserved - - // LC_SEGMENT_64 with the two sections. - p := hdrSize - le.PutUint32(out[p:], machoSegment64) - le.PutUint32(out[p+4:], segCmdSize) - // segname: the empty string, zero-padded to 16 bytes. - le.PutUint64(out[p+8:], 0) - le.PutUint64(out[p+16:], 0) - le.PutUint64(out[p+24:], 0) // vmaddr - le.PutUint64(out[p+32:], vmsize) - le.PutUint64(out[p+40:], uint64(dataOff)) - le.PutUint64(out[p+48:], vmsize) - le.PutUint32(out[p+56:], 7) // maxprot rwx - le.PutUint32(out[p+60:], 7) // initprot rwx - le.PutUint32(out[p+64:], 2) // nsects - le.PutUint32(out[p+68:], 0) // flags - - // __TEXT,__text - s := p + 72 - copy(out[s:], "__text") - copy(out[s+16:], "__TEXT") - le.PutUint64(out[s+32:], textAddr) - le.PutUint64(out[s+40:], uint64(len(code))) - le.PutUint32(out[s+48:], uint32(dataOff)) - le.PutUint32(out[s+52:], 4) // align 2^4 - le.PutUint32(out[s+56:], uint32(reloff)) - le.PutUint32(out[s+60:], uint32(len(relocs))) - le.PutUint32(out[s+64:], machoSectTextFlags) - - // __DATA,__data - s += 80 - copy(out[s:], "__data") - copy(out[s+16:], "__DATA") - le.PutUint64(out[s+32:], dataAddr) - le.PutUint64(out[s+40:], uint64(len(img.Data))) - le.PutUint32(out[s+48:], uint32(dataOff+len(code))) - le.PutUint32(out[s+52:], 4) // align 2^4 - - // LC_SYMTAB. - p = hdrSize + segCmdSize - le.PutUint32(out[p:], machoSymtab) - le.PutUint32(out[p+4:], symCmdSize) - le.PutUint32(out[p+8:], uint32(symoff)) - le.PutUint32(out[p+12:], uint32(len(syms))) - le.PutUint32(out[p+16:], uint32(stroff)) - le.PutUint32(out[p+20:], uint32(len(strtab))) - - // Section data. - copy(out[dataOff:], code) - copy(out[dataOff+len(code):], img.Data) - - // Relocation entries. - for i, r := range relocs { - e := out[reloff+i*8:] - le.PutUint32(e[0:], r.addr) - bits := r.symnum & 0x00ffffff - bits |= 1 << 24 // r_pcrel - bits |= 2 << 25 // r_length = 4 bytes - if r.extern { - bits |= 1 << 27 // r_extern - } - bits |= x8664RelocSigned << 28 - le.PutUint32(e[4:], bits) - } - - // nlist_64 entries. - for i, s := range syms { - e := out[symoff+i*16:] - le.PutUint32(e[0:], uint32(strOff[s.name])) - e[4] = s.typ - e[5] = s.sect - le.PutUint16(e[6:], 0) // n_desc - le.PutUint64(e[8:], s.value) - } - - // String table. - copy(out[stroff:], strtab) - - return out, nil -} diff --git a/asm/macho_test.go b/asm/macho_test.go deleted file mode 100644 index d8602f8..0000000 --- a/asm/macho_test.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) -// SPDX-License-Identifier: BSD-3-Clause - -package asm - -import ( - "bytes" - "debug/macho" - "encoding/binary" - "testing" -) - -// TestMachOObject checks the structure of the emitted MH_OBJECT: the two -// sections and their addresses, the symbol table (types, sections, values) -// and the __text relocation entries, parsed back with debug/macho. No -// Darwin toolchain is available on the test hosts, so the check is -// structural — the ELF output carries the end-to-end link-and-run proof of -// the shared symbol and relocation model. -func TestMachOObject(t *testing.T) { - img := elfTestImage(t) - obj, err := img.MachOObject() - if err != nil { - t.Fatalf("MachOObject: %v", err) - } - f, err := macho.NewFile(bytes.NewReader(obj)) - if err != nil { - t.Fatalf("parse emitted object: %v", err) - } - defer f.Close() - - if f.Type != macho.TypeObj { - t.Errorf("file type = %v, want MH_OBJECT", f.Type) - } - if f.Cpu != macho.CpuAmd64 { - t.Errorf("cpu = %v, want CpuAmd64", f.Cpu) - } - - text := f.Section("__text") - data := f.Section("__data") - if text == nil || data == nil { - t.Fatal("missing __text or __data section") - } - if text.Addr != 0 || text.Size != uint64(len(img.Code)) { - t.Errorf("__text addr/size = %#x/%d, want 0/%d", text.Addr, text.Size, len(img.Code)) - } - if data.Addr != uint64(len(img.Code)) { - t.Errorf("__data addr = %#x, want %#x", data.Addr, len(img.Code)) - } - - // Symbol table: locals, exported definitions, undefined externals. - syms := f.Symtab.Syms - byName := map[string]macho.Symbol{} - for _, s := range syms { - byName[s.Name] = s - } - wantSym := func(name string, typ, sect uint8, value uint64) { - t.Helper() - s, ok := byName[name] - if !ok { - t.Errorf("symbol %q not found", name) - return - } - if s.Type != typ || s.Sect != sect || s.Value != value { - t.Errorf("%s: type/sect/value = %#x/%d/%#x, want %#x/%d/%#x", - name, s.Type, s.Sect, s.Value, typ, sect, value) - } - } - const ( - defined = nSect | nExt - local = nSect - undefined = nUndf | nExt - ) - wantSym("addq", defined, 1, 0) - wantSym("getanswer", defined, 1, 5) - wantSym("useextern", defined, 1, 13) - answer := byName["answer"] - if answer.Type != local || answer.Sect != 2 { - t.Errorf("answer: type/sect = %#x/%d, want %#x/2", answer.Type, answer.Sect, local) - } - wantSym("extvar", undefined, 0, 0) - - // Relocations: both X86_64_RELOC_SIGNED, PC-relative, 4 bytes wide. - // The local one carries its section number in Value, the external one - // its symbol number. - if len(text.Relocs) != 2 { - t.Fatalf("__text relocs = %d, want 2", len(text.Relocs)) - } - var sawLocal, sawExternal bool - for _, r := range text.Relocs { - if !r.Pcrel || r.Len != 2 || r.Type != x8664RelocSigned { - t.Errorf("reloc at %#x: pcrel/len/type = %v/%d/%d", r.Addr, r.Pcrel, r.Len, r.Type) - } - switch { - case r.Extern: - if name := syms[r.Value].Name; name != "extvar" { - t.Errorf("external reloc at %#x names %q, want extvar", r.Addr, name) - } - sawExternal = true - default: - if r.Value != 2 { // __data, the second section - t.Errorf("local reloc at %#x: section %d, want 2 (__data)", r.Addr, r.Value) - } - sawLocal = true - } - } - if !sawLocal || !sawExternal { - t.Errorf("relocs seen: local=%v external=%v, want both", sawLocal, sawExternal) - } - - // The __text bytes are the image code, with the external displacement - // primed to addend − 4 and the local one left resolved. - textData, err := text.Data() - if err != nil { - t.Fatal(err) - } - want := append([]byte(nil), img.Code...) - for _, fn := range img.Funcs { - for _, r := range fn.Relocs { - if r.Name == "extvar" { - binary.LittleEndian.PutUint32(want[fn.Offset+r.Off:], 0xfffffffc) // −4 - } - } - } - if !bytes.Equal(textData, want) { - t.Errorf("__text bytes %x, want %x", textData, want) - } -} diff --git a/cmd/gasm/main.go b/cmd/gasm/main.go index 9f12951..ff14d08 100644 --- a/cmd/gasm/main.go +++ b/cmd/gasm/main.go @@ -395,7 +395,7 @@ hover, document symbols, diagnostics and semantic-token highlighting. } func cmdAsm(args []string) int { - fs := newCommand("asm", "gasm asm [--format raw|elf|macho|goobj] [-p pkg] [-o out] ", ` + fs := newCommand("asm", "gasm asm [--format raw|elf|goobj] [-p pkg] [-o out] ", ` Assemble FILE (amd64 or riscv64) without the Go toolchain: every TEXT function is encoded to machine code — scalar, VEX/AVX2 and EVEX/AVX-512 instructions, FP/SP frame mapping, local labels and file-local static symbols (GLOBL/DATA) @@ -403,18 +403,18 @@ resolved RIP-relative — and printed as a hex dump. With -o the output is written to a file instead. The --format flag selects what is written: raw (the default) concatenates the functions and the data -section into one self-consistent image; elf and macho emit a relocatable -object (.text/.data sections, a symbol table and one PC32 relocation per +section into one self-consistent image; elf emits a relocatable object +(.text/.data sections, a symbol table and one PC32 relocation per static-symbol reference) that links with the system toolchain; goobj emits the Go toolchain's own object format, which cmd/link consumes directly (it requires -p, the package path, and the installed Go toolchain). `) out := fs.String("o", "", "write the output to this file") - format := fs.String("format", "raw", "output format: raw (concatenated image), elf, macho or goobj (Go object)") + format := fs.String("format", "raw", "output format: raw (concatenated image), elf or goobj (Go object)") pkg := fs.String("p", "", "package path for --format goobj (qualifies the exported symbols)") fs.Parse(args) if fs.NArg() != 1 { - fmt.Fprintln(os.Stderr, "usage: gasm asm [--format raw|elf|macho|goobj] [-p pkg] [-o out] ") + fmt.Fprintln(os.Stderr, "usage: gasm asm [--format raw|elf|goobj] [-p pkg] [-o out] ") return 2 } path := fs.Arg(0) @@ -497,7 +497,7 @@ requires -p, the package path, and the installed Go toolchain). switch *format { case "raw": if len(img.Externals) > 0 { - fmt.Fprintf(os.Stderr, "gasm asm: external symbol %q needs an object file (use --format elf or --format macho)\n", img.Externals[0]) + fmt.Fprintf(os.Stderr, "gasm asm: external symbol %q needs an object file (use --format elf)\n", img.Externals[0]) return 1 } obj, kind = img.Bytes(), "raw image" @@ -508,9 +508,6 @@ requires -p, the package path, and the installed Go toolchain). obj, err = img.ELFObject() } kind = "ELF object" - case "macho": - obj, err = img.MachOObject() - kind = "Mach-O object" case "goobj": if targetArch == arch.RISCV { obj, err = img.GOObjectRISCV(*pkg, path) @@ -519,7 +516,7 @@ requires -p, the package path, and the installed Go toolchain). } kind = "Go object" default: - fmt.Fprintf(os.Stderr, "gasm asm: unknown format %q (want raw, elf, macho or goobj)\n", *format) + fmt.Fprintf(os.Stderr, "gasm asm: unknown format %q (want raw, elf or goobj)\n", *format) return 2 } if err != nil { diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 796c542..8546310 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -275,8 +275,8 @@ RIP-relative loads whose displacements point inside the resulting image, so the bytes are self-consistent at any base address. References to symbols no `GLOBL` defines are kept as relocations on the function layout, and the object-file emitters turn the whole image into a linkable object: the ELF -and Mach-O writers (`gasm asm --format elf|macho`) lay the code and data out -as `.text`/`.data` (or `__text`/`__data`) sections, export a symbol per +writer (`gasm asm --format elf`) lays the code and data out as `.text`/`.data` +sections, exports a symbol per `TEXT` and `GLOBL` (the `<>` ones local, the rest global) and emit one PC-relative relocation per static-symbol reference — undefined external symbols included, so the output links with the system toolchain. The GOOBJ diff --git a/docs/ZED.md b/docs/ZED.md deleted file mode 100644 index ba14515..0000000 --- a/docs/ZED.md +++ /dev/null @@ -1,79 +0,0 @@ -# Using gasm-devkit with Zed - -This document is deliberately blunt, because the situation is a genuine -conflict between two of the project's own commitments, and papering over it -would be dishonest. - -## The conflict - -gasm-devkit is **pure Go, no C, no cgo, no JavaScript runtimes, no native -binaries, no vendor lock-in, no platform-specific IDE internals.** - -Zed's extension model, as verified against Zed's own documentation, is: - -- Extensions are written in **Rust** and compiled to **WebAssembly** - (`wasm32-wasip2`). -- Syntax highlighting is provided by **Tree-sitter** grammars, which are - **C** compiled to WebAssembly with the wasi-sdk, from a grammar written in a - **JavaScript** DSL. -- A *new* language cannot be registered through configuration alone. Defining - a language requires an extension, and every language extension must name a - Tree-sitter grammar. (Zed's `lsp` settings section configures - already-registered servers; it does not register an arbitrary external binary - for a brand-new language.) - -There is therefore **no pure-Go path into Zed's extension host.** This is a -property of Zed, not of gasm-devkit: no language tooling author can feed Zed a -pure-Go highlighting grammar, because Zed's highlighting engine is Tree-sitter -and its plugin runtime is Rust/WASM. - -## What gasm-devkit gives Zed regardless - -The toolkit's integration surface is the **Language Server Protocol**, an open -standard. Through `gasm lsp` it provides, with zero editor-specific code: - -- autocomplete (instructions, registers, pseudo-registers, labels), -- hover documentation, -- diagnostics (the linter, pushed as you type), -- document outline (functions and labels), -- **syntax highlighting, delivered as LSP semantic tokens.** - -That last point matters: Zed can render highlighting entirely from LSP semantic -tokens (`"semantic_tokens": "full"` replaces Tree-sitter highlighting for a -language). So the highlighting *capability* exists in pure Go; what Zed needs -is merely to be told that `.s` files are a language served by `gasm lsp`. - -## The honest options - -1. **Use an editor that registers an external LSP by configuration.** - Neovim, Helix, VS Code and Sublime all let you associate `.s` with the - `gasm lsp` binary and use its semantic tokens — no Rust, no C, no lock-in. - This is the option that satisfies every stated constraint with no - exception. - -2. **Treat a Zed adapter as one quarantined exception.** A minimal Zed - extension — a few lines of Rust that register the language and launch - `gasm lsp` — plus either a Tree-sitter grammar or `"full"` semantic tokens - for highlighting. Crucially, this adapter is the *editor's plugin format*; - it is sandboxed inside Zed and never linked into, compiled into, or shipped - with the Go toolkit. gasm-devkit itself stays pure Go. But producing it - uses the Rust/wasi-sdk/Tree-sitter toolchain, which the project constraints - forbid — so it must be a conscious, explicit decision, not a silent one. - -The author's philosophy — digital sovereignty, no dependency on toolchains he -does not control — is the tie-breaker, and it is a value judgement rather than -a technical one. gasm-devkit is built so that **either** choice keeps the -toolkit itself clean: the pure-Go core and the LSP are the product; a Zed -adapter, if ever wanted, is a thin, separable leaf. - -## Wiring the LSP (editor-agnostic) - -Run the server and point an LSP client at it: - -```sh -go run ./cmd/gasm lsp # or: go install ./cmd/gasm && gasm lsp -``` - -Associate the command with `*.s` (and `*_amd64.s` / `*_arm64.s`) in whichever -editor you use. The server infers the target architecture from the file-name -suffix and selects the amd64 or arm64 instruction tables accordingly. diff --git a/docs/cli.md b/docs/cli.md index 1090ebe..dc7418c 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -50,13 +50,13 @@ Rules: `unknown-instruction`, `operand-count`, `undefined-label`, `abi-argsize`, `unreachable-code`, `register-clobber`, `funcdata-pcdata`. -## `gasm asm [--format raw|elf|macho|goobj] [-p pkg] [-o out] ` +## `gasm asm [--format raw|elf|goobj] [-p pkg] [-o out] ` Assemble FILE (amd64) to machine code. | Flag | Description | |------|-------------| -| `--format` | Output format: `raw` (default), `elf`, `macho`, `goobj` | +| `--format` | Output format: `raw` (default), `elf`, `goobj` | | `-p` | Package path (required for `--format goobj`) | | `-o` | Write output to file (default: hex dump to stdout) |