Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion testdata/golden/calc_O2.golden
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
global _start

_start:
mov rbx, 0
xor ebx, ebx
mov rcx, 1
mov rdx, 10

Expand Down
2 changes: 1 addition & 1 deletion testdata/golden/fib_O2.golden
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
global _start

_start:
mov rax, 0
xor eax, eax
mov rbx, 1
mov rcx, 20
mov rdx, 1
Expand Down
2 changes: 1 addition & 1 deletion testdata/golden/hello_O2.golden
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ _start:
syscall

; sys_exit(0)
mov rdi, 0
xor edi, edi
mov rax, 60
syscall
4 changes: 2 additions & 2 deletions testdata/golden/opt_showcase_O2.golden
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ data:

_start:
mov rdx, 10
mov rbx, 0
mov rcx, 0
xor ebx, ebx
xor ecx, ecx

; LICM test: LEA v0, [data] is loop-invariant
; -O2 hoists it before the loop
Expand Down
16 changes: 6 additions & 10 deletions testdata/golden/sort_O2.golden
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ data:
_start:
mov rax, 8
push rax
mov rbx, 0
xor ebx, ebx

outer:
pop rax
Expand All @@ -22,8 +22,8 @@ outer:
mov rax, 1
sub rsi, rax
sub rsi, rbx
mov rcx, 0
mov rdx, 0
xor ecx, ecx
xor edx, edx

inner:
cmp rdx, rsi
Expand All @@ -38,9 +38,7 @@ inner:

; addr of a[j+1] = data + (j+1)*8
mov r8, 1
mov rdi, rdx
add rdi, r8
mov rdi, rdi
lea rdi, [rdx+r8]
shl rdi, 3
lea rax, [data]
add rdi, rax
Expand All @@ -59,9 +57,7 @@ swap:
mov [rdi], r8

mov rax, 1
mov rdi, rdx
add rdi, rax
mov rdi, rdi
lea rdi, [rdx+rax]
shl rdi, 3
lea rax, [data]
add rdi, rax
Expand All @@ -75,7 +71,7 @@ no_swap:
jmp inner

check_swap:
cmp rcx, 0
test ecx, ecx
je done
mov rax, 1
add rbx, rax
Expand Down
13 changes: 11 additions & 2 deletions vas/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,17 @@ func AssembleWithOptWithHook(input string, cfg OptConfig, preHook func(string) (

output := strings.Join(outLines, "\n")

// PeepholeOnly is called inside Optimize already (twice).
// No need to run it again here.
// The peephole passes inside Optimize run on VAS source lines and
// use regexes that require tab-prefixed lowercase opcodes — they are
// effectively no-ops on VAS source. Run PeepholeOnly here on the fully
// expanded nasm output so passes like pushModPopElim can eliminate
// patterns such as "push rbx; add rbx, r8; pop rbx".
// These aggressive peephole optimizations are gated at O2 and above
// to match the opt_showcase behavior where O2 output is strictly
// shorter than O1.
if cfg.Level >= 2 {
output = opt.PeepholeOnly(output)
}

if postHook != nil {
var err error
Expand Down
84 changes: 84 additions & 0 deletions vas/opt/opt.go
Original file line number Diff line number Diff line change
Expand Up @@ -1680,6 +1680,15 @@ func pushPopBlock(lines []string) []string {
}
reg := fields[1]

// Fast 3-instruction pattern: PUSH reg; OP reg, src; POP reg
// The middle instruction modifies reg; POP restores it — net result: no-op on reg.
if removed, mid, pop := scanPushModPop(i, reg, lines, remove); removed {
remove[i] = true
remove[mid] = true
remove[pop] = true
continue
}

for j := i + 1; j < len(lines); j++ {
if remove[j] {
continue
Expand Down Expand Up @@ -1724,6 +1733,81 @@ func pushPopBlock(lines []string) []string {
return result
}

// scanPushModPop checks whether lines[i:i+N] is a PUSH reg; instr-writes-reg; POP reg
// pattern. If yes, returns (true, midIdx, popIdx). Otherwise (false, 0, 0).
func scanPushModPop(i int, reg string, lines []string, remove []bool) (bool, int, int) {
// Find next non-empty, non-comment, non-removed line after PUSH
midIdx := -1
for j := i + 1; j < len(lines); j++ {
if remove[j] {
continue
}
if stripComment(lines[j]) == "" {
continue
}
midIdx = j
break
}
if midIdx < 0 {
return false, 0, 0
}
// Check that the middle line writes to reg, and that the opcode
// is one that does not set FLAGS. This is conservative: peephole
// passes on the final nasm output will catch flag-setting variants.
midFields := strings.Fields(stripComment(lines[midIdx]))
if len(midFields) < 2 {
return false, 0, 0
}
midOp := strings.ToUpper(midFields[0])
midReg := strings.TrimRight(midFields[1], ",")
if midReg != reg {
return false, 0, 0
}
if dstReg(midOp, midFields[1:]) < 0 {
return false, 0, 0
}
switch midOp {
case "MOV", "MOVI", "LOAD", "LEA", "STORE":
// These do not set FLAGS — safe to drop.
default:
return false, 0, 0
}
// Then find next non-empty/non-comment line after midIdx — must be POP reg
popIdx := -1
for j := midIdx + 1; j < len(lines); j++ {
if remove[j] {
continue
}
if stripComment(lines[j]) == "" {
continue
}
popIdx = j
break
}
if popIdx < 0 {
return false, 0, 0
}
popFields := strings.Fields(stripComment(lines[popIdx]))
if len(popFields) < 2 {
return false, 0, 0
}
popOp := strings.ToUpper(popFields[0])
popReg := strings.TrimRight(popFields[1], ",")
if popOp != "POP" || popReg != reg {
return false, 0, 0
}
return true, midIdx, popIdx
}

// stripComment trims leading/trailing whitespace; returns "" for lines that are empty or pure comments only.
func stripComment(line string) string {
trimmed := strings.TrimSpace(line)
if idx := strings.IndexAny(trimmed, ";#"); idx >= 0 {
trimmed = strings.TrimSpace(trimmed[:idx])
}
return trimmed
}

func tailCallOpt(lines []string) []string {
result := make([]string, 0, len(lines))
i := 0
Expand Down
15 changes: 13 additions & 2 deletions vas/opt/opt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -973,11 +973,22 @@ func TestPushPopElimWithCode(t *testing.T) {
}

func TestPushPopElimModifiedReg(t *testing.T) {
// PUSH v0; MOVI v0, 1; POP v0 → reg modified between, no elimination
// PUSH v0; MOVI v0, 1; POP v0 → MOVI writes a value that POP overwrites.
// The entire triple is a dead write and should be eliminated.
lines := []string{"\tPUSH\tv0", "\tMOVI\tv0, 1", "\tPOP\tv0"}
result := pushPopElim(lines)
if len(result) != 0 {
t.Fatalf("expected 0 lines (dead write eliminated), got %d: %v", len(result), result)
}
}

func TestPushPopElimFlagSettingMiddle(t *testing.T) {
// PUSH v0; ADD v0, v0, v1; POP v0 → middle instr sets FLAGS.
// Conservative: keep, let the nasm-level peephole pass deal with it.
lines := []string{"\tPUSH\tv0", "\tADD\tv0, v0, v1", "\tPOP\tv0"}
result := pushPopElim(lines)
if len(result) != 3 {
t.Fatalf("expected 3 lines (reg modified), got %d: %v", len(result), result)
t.Fatalf("expected 3 lines (flag-setting middle kept), got %d: %v", len(result), result)
}
}

Expand Down
Loading