diff --git a/CHANGELOG.md b/CHANGELOG.md index 6626f2d..a2d278e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,160 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 lines entered the alignment width computation, and rendered `/ *`, `> >` sequences re-lexed as comments and shifts. The label, width and spacing rules now agree between passes. +- **`gasm fmt` deleted the `|` separators from TEXT and GLOBL flag + lists.** The lexer had no token for `|`, the formatter dropped the + resulting illegal token, and an in-place format silently rewrote + `NOSPLIT|DUPOK` as `NOSPLIT DUPOK`, which the Go assembler rejects. + The bars now round-trip byte-identically, and `·foo(SB)` + parses its ABI marker instead of swallowing `ABIInternal` and `SB` + into the flags, which produced false lint warnings on the standard + runtime spelling. +- **A malformed TEXT declaration crashed `gasm lint` and the language + server.** A TEXT line without a symbol left a nil name that lint and + the LSP dereferenced; both now carry on with a diagnostic. A branch + to a label at the end of a function body panicked the liveness + analysis the same way. A real NUL byte truncated the token stream + (everything after it was dropped); it is an illegal token now, invalid + UTF-8 no longer inflates byte offsets, and CRLF files format to + uniform LF. +- **A frameless amd64 function containing a CALL read its arguments from + the wrong stack slot.** The forced base-pointer frame shifted + FP references by eight bytes (`x+0(FP)` resolved to SP+0x18 where the + toolchain emits SP+0x10), so such functions loaded garbage. The + class-2 stack guard had the sibling defect: whenever the underflow + branch relaxed to its 32-bit form, its displacement ran four bytes + past the target and into the morestack CALL. +- **Immediate operands wrapped silently on amd64.** Shift counts, + immediates beyond the operand's width and displacements beyond int32 + truncated without a diagnostic (`SHLQ $300` assembled as `$44`); they + are range-checked now, matching `go tool asm`. EVEX scalar moves + (`VMOVSS Z1, Z2`) accepted forms the toolchain rejects and emitted + invalid encodings; `PUSHW`/`POPW` emit the 0x66-prefixed forms the + toolchain emits; `PUSHL` is rejected as illegal in 64-bit mode; a bare + zero-operand `JE` reports a diagnostic instead of panicking. +- **The arm64 shift and divide instructions encoded entirely different + operations.** `LSL`, `LSR`, `ASR` and `ROR`, immediate and register + forms, all encoded as `ORR`; `SDIV`/`UDIV` sat in the wrong opcode + space; `MADD`/`MSUB` never encoded the accumulate operand and silently + read X0 for it. All now match the toolchain byte for byte (new + differential kernels cover shifts, divides and multiplies), MADD + takes its four operands in the toolchain's order, and shift amounts at + or above the operand width are rejected. +- **arm64 multi-chunk immediates corrupted every branch that followed + them.** The size pass and the emitter disagreed on the expansion of + constants with three or more non-zero 16-bit chunks and of `MOVW $-1`, + so later label displacements were computed against the wrong offsets. + The size now comes from the encoder itself. Large-frame stack guards + (frames from roughly 64 KiB) branched to the wrong morestack entry, + and the pcsp and DWARF CFA boundaries for materialised large frames + are computed from the real prologue word counts. +- **arm64 immediates and addressing wrapped instead of erroring.** + Constants beyond the encodable range (`ADD $0x100000000`) wrapped to + zero, memory offsets wrapped at 2^31, exclusive and atomic accesses + silently ignored their offsets (`LDXR 8(R1)` read `[R1]`), and large + register-based offsets were routed through SP instead of the operand's + base. All four now either encode correctly or produce diagnostics. +- **riscv64 compressed stores with certain offsets wrote to the wrong + address.** The C.SD/C.SW/C.FSD immediate pattern dropped one bit, so + any register-relative store with offset bit 4 or 5 set targeted a + different address than the same-index load beside it. `FENCE` + assembled as `fence 0,0` instead of `fence iorw, iorw`. Branch and + jump displacements beyond ±4 KiB / ±1 MiB wrapped silently; they are + diagnostics now. The GOROOT width spellings (`MOVW 4(SP), X9`) + compress to their C.LW/C.SW forms exactly as the toolchain lowers + them, restoring byte parity for those shapes. +- **loong64 `MOVW $c, Fd` wrote a general register.** The immediate was + routed to the GPR of the F register's number (`MOVW $2, F4` clobbered + argument register R4), and the correct R30 + `movgr2fr.w` sequence was + unreachable. Two-operand `BLTU R4, label` encoded as `beqz` + (sometimes-taken where the toolchain's form is never-taken), and + out-of-range FP constants and BSTRINS/BSTRPICK bit numbers wrapped + silently; all are corrected or diagnosed. +- **ELF objects carried wrong relocation records.** The amd64 + stack-guard TLS load relocated as R_X86_64_PC32 against the null + symbol (every non-NOSPLIT object mislinked); arm64 SB references + applied HI21 twice instead of the HI21/LO12 pair; riscv64 PCREL_LO12 + referenced the target instead of its AUIPC site, which the system + linker rejects; riscv64 and loong64 e_flags declared the soft-float + ABI, so standard linkers refused the merge. +- **ELF DWARF was unparseable.** Eight abbrev-table constants were + wrong, the version-5 line header carried DWARF2-shaped tables, the + section count omitted `.debug_frame` (it sat past the section table, + invisible to every tool), the CIE hardcoded one architecture's + CFA and return-address registers for all four, and no DWARF address + was ever relocated: the `.rela.debug_info` and `.rela.debug_line` + records were computed and then discarded, so every address stayed + zero after linking. The tables parse in readelf, the registers are + per-architecture, `.rela.debug_info`, `.rela.debug_line` and + `.rela.debug_frame` are emitted, and addresses resolve after the + link; a data-only file emits a valid object instead of panicking, and + the DWARF records the real source path. +- **GOOBJ cross-package references resolved against the wrong object.** + External package indices were zero-based against a table that + reserves zero for the dummy invalid package, and symbol indices + ignored the hashed definition blocks between the sections, so a + reference into the first external package could bind to whatever + object the loader saw first. An end-to-end cross-package link pins + the chain. arm64 ADRP pairs now emit the toolchain's single 8-byte + relocation (the previous twin 4-byte records were a hard link error), + and symbols no longer claim the linkname flag the toolchain reserves + for `//go:linkname` declarations. +- **The arm64 JIT trampolines saved a scratch register as the stack + pointer.** `enterJIT` and its checked twin stored R3, a plain + caller-saved register on arm64, and restored RSP from it, so the + first JIT call would have returned to a garbage stack. The loong64 + trampoline hands its leave address through the raw-symbol pattern the + arm64 one uses, avoiding the ABI wrapper's prologue. +- **The checked-ABI report flagged legal frames.** The red-zone canary + sat 64 bytes below the entry stack, so any kernel with a larger + declared frame reported "stack below SP written"; the guard now sizes + itself from the kernel's frame. The amd64 JIT tests are gated to + amd64 hosts (the suite previously SIGILL-crashed on the other three + architectures), fuzz signatures wider than the TEXT frame report + instead of panicking, `--buf` specifications are validated strictly + (a typo no longer verifies against a zeroed buffer), and ABI0 + parameter sizes cover `string` and `complex` correctly. +- **amd64 hardware watchpoints never armed.** The debug registers were + poked at offsets inside `user_regs_struct`, corrupting five general + registers while the REPL reported success; they now use the real + u_debugreg window and stop on the watched address. loong64 watch + goes through the kernel's HW_WATCH regset (riscv64 reports the + kernel's interface as unsupported instead of failing obscurely). +- **`gasm debug` hung on the first faulting kernel.** Genuine + SIGSEGV/SIGBUS/SIGFPE/SIGILL stops were discarded as runtime noise + and the faulting instruction restarted forever; faults now surface as + reported stops. Conditional breakpoints with a false condition + resumed mid-instruction, `next` and `finish` evaluated traps with + stale registers and landed off instruction boundaries, and the + breakpoint restore covered one byte of the four-byte traps (arm64 + silently skipped the instruction under it); the trap PCs follow the + kernel's reporting on every architecture. `regs` reports YMM from + the xstate (a struct-size overrun crashed FP register reads before), + V register halves print correctly on arm64, `unwatch` accepts the + architecture's slot range, `x -8` no longer crashes, break + conditions accept memory operands, and session scratch directories + are cleaned up. +- **The language server died on one malformed frame and corrupted + sources on rename.** A bad `Content-Length` or an unparsable JSON + body terminated the process instead of answering `-32700` and + continuing; rename and references covered the stripped name instead + of the full `·name` token, so renaming produced `·helper` minus its + last letter; documentHighlight never matched middle-dot symbols. + Positions are UTF-16 code units in both directions (astral characters + no longer shift columns) and responses always carry an explicit + `result` member. +- **The linter now recognises the `g` spelling of the goroutine + register.** `MOVD R0, g` clobbered R28 on arm64 (and the equivalents + on the other architectures) unflagged, and the numeric spellings the + linter did track are rejected by the toolchain there, so `g` was the + one spelling that escaped the audit. FUNCDATA and PCDATA literal + indices are validated against the ranges the runtime defines. +- **Usage errors exit 2 uniformly.** `audit-instructions` and + `scaffold` argument errors and an unknown `asm --format` exited 1 (or, + for `--format` without `-o`, exited 0 silently); the documented + exit-2 contract now holds, `asm -o` no longer prints the hex dump it + claimed to replace, and `verify --ground-truth` works for amd64 + kernels on non-amd64 hosts instead of refusing with JIT advice. ## [0.33.0] - 2026-09-14