Skip to content

cmd/compile: signed division on ppc64x clobbers CR0 without clobberFlags, so a later branch tests the wrong condition #81842

Closed

Description

@smm-h

Go version

go version go1.27.1 linux/amd64 (cross-compiling for GOARCH=ppc64le and ppc64). Also reproduced with go1.26.8, go1.21.13, and tip (2ff5743).

Output of go env in your module/workspace:

GOARCH='ppc64le'   # also ppc64; any GOPPC64 (power8, power9, power10)
GOOS='linux'
GOAMD64='v1'
CGO_ENABLED='0'

What did you do?

Built and ran this program for GOARCH=ppc64le (and ppc64), under qemu-user 10.2.2 and 11.1.1:

package main

import "fmt"

var sinkA, sinkQ, sinkB int64

//go:noinline
func f(c bool, a, b int64) {
	if b == 0 { // b != 0 below, so the division has no zero check of its own
		return
	}
	if c {
		sinkA = 1
	}
	sinkQ = a / b
	if !c {
		sinkB = 1
	}
}

func main() {
	f(false, 7, 2)
	fmt.Println(sinkA, sinkQ, sinkB) // want 0 3 1
}

What did you see happen?

On ppc64le and ppc64 it prints 0 3 0. On amd64, 386, arm, arm64, loong64, mips64, mips64le, mipsle, riscv64, and s390x it prints 0 3 1.

The if !c branch tests the condition register left by the compare inside the expansion of a / b, not c. go1.26.8, ppc64le:

CMP R5,$0            ; b == 0 ?
BEQ ret
ANDCC R3,$1,R3       ; CR0 := c&1, the flag for both `if c` and `if !c`
BEQ L1
MOVD $1,R3 ; MOVD R3,sinkA
BR L2
L1: MOVD $1,R3
L2: CMP R5,$-1       ; the DIVD expansion writes CR0 := (b ? -1)
BEQ L3
DIVD R5,R4,R4
BR L4
L3: NEG R4,R4
L4: MOVD R4,sinkQ
BNE ret              ; meant c&1 != 0, tests b != -1
MOVD R3,sinkB

ssaGenValue expands OpPPC64DIVD (and OpPPC64DIVW, with CMPW) into CMP arg1,$-1; BEQ; DIVD; BR; NEG to avoid divd's undefined result for MinInt/-1. That compare writes CR0, but DIVD and DIVW in _gen/PPC64Ops.go are not marked clobberFlags, so flagalloc keeps a flags value live across them. -d=ssa/flagalloc/dump shows the flag defined before the division and used unchanged as the control of the block that holds the division.

It needs a signed division by a non-constant divisor already known to be nonzero (an earlier check, an earlier division by it, or a loop bound), with a flag live across it. The typical shape is the same comparison on both sides of the division. With an ordered compare, the branch instead follows the sign of b+1. % is affected the same way at GOPPC64=power8 (power9+ uses MODSD/MODSW). An ISEL consumer in a loop is affected too. aix/ppc64 emits the same code.

s390x and amd64/386 mark their division ops that do the same fixup as clobberFlags: true. arm64, riscv64, mips64, and loong64 emit no compare for division.

What did you expect to see?

0 3 1 on every architecture. Marking DIVD and DIVW clobberFlags: true fixes it: flagalloc regenerates the flag after the division. With that change, the program above and nine variants (int/int32/int16/int8 division, % on power8, ordered compares, an ISEL in a loop) print the amd64 results on ppc64le and ppc64, and MinInt/-1 still gives MinInt. I'll send a CL with that change and a regression test.

Activity

  1. gopherbot commented on Sep 29, 2026

    @gopherbot
    Contributor

    Change https://go.dev/cl/841125 mentions this issue: cmd/compile: mark ppc64 signed division ops as clobbering flags

  2. prattmic commented on Sep 29, 2026

    @prattmic
    Member

    cc @golang/ppc64

  3. added
    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.
    and removed on Sep 29, 2026
  4. pmur commented on Sep 29, 2026

    @pmur
    Contributor

    divd and divw shouldn't modify cr0. This shouldn't be confused with the dotted divd. form (aka DIVDCC) which does write to cr0.

  5. pmur commented on Sep 29, 2026

    @pmur
    Contributor

    Ugh, I skimmed over the issue report... sorry. The analysis is right, and the fix is OK.

    I wonder if DIVD and DIVW should just lower to the expected single ppc64 opcode. This seems like an optimization which should be made in the generic pass.

    Likewise, I think most ppc64 processors optimize for certain divisors. I wonder if we could just get rid of the optimization?

    Nevermind, that is go-ism for dividing by -1.

  6. added this to the Go1.28 milestone on Oct 5, 2026
  7. added
    FixPendingIssues that have a fix which has not yet been reviewed or submitted.
    and removed
    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.
    on Oct 5, 2026
  8. added a commit that references this issue on Oct 5, 2026
    6200ca7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    FixPendingIssues that have a fix which has not yet been reviewed or submitted.compiler/runtimeIssues related to the Go compiler and/or runtime.

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions