fix(asm): resolve negative numeric PC-relative jumps

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-23 21:02:50 +02:00
parent 8eed54b3da
commit 7b11c62f53
3 changed files with 74 additions and 1 deletions
+5
View File
@@ -28,6 +28,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
to the document that started it, so renaming a symbol used in a second to the document that started it, so renaming a symbol used in a second
file moved that file's text into the first. Each edit now applies to the file moved that file's text into the first. Each edit now applies to the
document it was collected in. document it was collected in.
- **Negative numeric PC-relative jumps.** `JMP -3(PC)`, the shape the
runtime's exit loops write (sys_linux_amd64.s, sys_netbsd_amd64.s),
resolved to nothing: only the forward forms counted. A negative count
now walks the same instruction statements backwards, labels excluded,
byte-identical with the toolchain.
## [0.35.0] - 2026-09-22 ## [0.35.0] - 2026-09-22
+17 -1
View File
@@ -543,11 +543,27 @@ func pcJumpOffset(op *ast.Operand) (int, bool) {
// pcJumpTarget resolves a numeric jump at statement index j: N counts the // pcJumpTarget resolves a numeric jump at statement index j: N counts the
// instruction statements after the jump itself (N = 0 is the jump's own // instruction statements after the jump itself (N = 0 is the jump's own
// address, the classic park loop), and the target is the start of the Nth // address, the classic park loop), and the target is the start of the Nth
// one. It reports false when the count runs past the end of the function. // one. A negative N counts the same way backwards, before the jump: the
// exit loops write JMP -3(PC) to land three instructions earlier. Labels
// count not, in either direction. It reports false when the count runs
// past the end of the function, or before its first instruction.
func pcJumpTarget(t *ast.Text, j, n int, pcs []int) (int, bool) { func pcJumpTarget(t *ast.Text, j, n int, pcs []int) (int, bool) {
if n == 0 { if n == 0 {
return pcs[j], true return pcs[j], true
} }
if n < 0 {
seen := 0
for k := j - 1; k >= 0; k-- {
if _, ok := t.Body[k].(*ast.Instr); !ok {
continue
}
seen--
if seen == n {
return pcs[k], true
}
}
return 0, false
}
seen := 0 seen := 0
for k := j + 1; k < len(t.Body); k++ { for k := j + 1; k < len(t.Body); k++ {
if _, ok := t.Body[k].(*ast.Instr); !ok { if _, ok := t.Body[k].(*ast.Instr); !ok {
+52
View File
@@ -370,6 +370,58 @@ end:
} }
} }
// TestAssembleNumericPCJumps pins the numeric ±N(PC) branch operands: N
// counts instruction statements, skipping labels, in both directions (the
// runtime's exit loops write JMP -3(PC)), N = 0 parks on the jump itself.
func TestAssembleNumericPCJumps(t *testing.T) {
fn := firstText(t, `
#include "textflag.h"
TEXT ·exit(SB), NOSPLIT, $0
MOVB $1, AL
lab:
MOVB $2, AL
MOVB $3, AL
JMP -3(PC)
MOVB $4, AL
park:
JMP 0(PC)
MOVB $5, AL
JMP 2(PC)
MOVB $6, AL
RET
`)
code, _, err := Assemble(fn)
if err != nil {
t.Fatalf("Assemble: %v", err)
}
// From the Go-assembled function:
// MOVB $1, AL b001
// MOVB $2, AL b002
// MOVB $3, AL b003
// JMP -3(PC) ebf8 (three instructions back, past lab:)
// MOVB $4, AL b004
// JMP 0(PC) ebfe (the park loop)
// MOVB $5, AL b005
// JMP 2(PC) eb02 (over MOVB $6 to the RET)
// MOVB $6, AL b006
// RET c3
want := []byte{
0xb0, 0x01,
0xb0, 0x02,
0xb0, 0x03,
0xeb, 0xf8,
0xb0, 0x04,
0xeb, 0xfe,
0xb0, 0x05,
0xeb, 0x02,
0xb0, 0x06,
0xc3,
}
if hexBytes(code) != hexBytes(want) {
t.Errorf("numeric-PC mismatch:\n got: %s\n want: %s", hexBytes(code), hexBytes(want))
}
}
func TestAssemblePrefetch(t *testing.T) { func TestAssemblePrefetch(t *testing.T) {
fn := firstText(t, ` fn := firstText(t, `
#include "textflag.h" #include "textflag.h"