diff --git a/src/cmd/asm/internal/asm/testdata/amd64dynlinkerror.s b/src/cmd/asm/internal/asm/testdata/amd64dynlinkerror.s index e110ee8118583..4bf58a39a4326 100644 --- a/src/cmd/asm/internal/asm/testdata/amd64dynlinkerror.s +++ b/src/cmd/asm/internal/asm/testdata/amd64dynlinkerror.s @@ -72,3 +72,100 @@ one: TEXT ·a13(SB), 0, $0-0 MULXQ runtime·writeBarrier(SB), AX, CX RET + +// Various special cases in the use-R15-after-global-access-when-dynlinking check. +// See issue 58632. +TEXT ·a14(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + MULXQ R15, AX, BX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here" + RET +TEXT ·a15(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + MULXQ AX, R15, BX + ADDQ $1, R15 + RET +TEXT ·a16(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + MULXQ AX, BX, R15 + ADDQ $1, R15 + RET +TEXT ·a17(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + MOVQ (R15), AX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here" + RET +TEXT ·a18(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + MOVQ (CX)(R15*1), AX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here" + RET +TEXT ·a19(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + MOVQ AX, (R15) // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here" + RET +TEXT ·a20(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + MOVQ AX, (CX)(R15*1) // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here" + RET +TEXT ·a21(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + MOVBLSX AX, R15 + ADDQ $1, R15 + RET +TEXT ·a22(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + PMOVMSKB X0, R15 + ADDQ $1, R15 + RET +TEXT ·a23(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + LEAQ (AX)(CX*1), R15 + RET +TEXT ·a24(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + LEAQ (R15)(AX*1), AX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here" + RET +TEXT ·a25(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + LEAQ (AX)(R15*1), AX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here" + RET +TEXT ·a26(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + IMUL3Q $33, AX, R15 + ADDQ $1, R15 + RET +TEXT ·a27(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + IMUL3Q $33, R15, AX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here" + RET +TEXT ·a28(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + PEXTRD $0, X0, R15 + ADDQ $1, R15 + RET +TEXT ·a29(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + VPEXTRD $0, X0, R15 + ADDQ $1, R15 + RET +TEXT ·a30(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + BSFQ R15, AX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here" + RET +TEXT ·a31(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + BSFQ AX, R15 + ADDQ $1, R15 + RET +TEXT ·a32(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + POPCNTL R15, AX // ERROR "when dynamic linking, R15 is clobbered by a global variable access and is used here" + RET +TEXT ·a33(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + POPCNTL AX, R15 + ADDQ $1, R15 + RET +TEXT ·a34(SB), 0, $0-0 + CMPL runtime·writeBarrier(SB), $0 + SHLXQ AX, CX, R15 + ADDQ $1, R15 + RET diff --git a/src/cmd/internal/obj/x86/obj6.go b/src/cmd/internal/obj/x86/obj6.go index fc0a9c44848bf..6b6aa8809a6b7 100644 --- a/src/cmd/internal/obj/x86/obj6.go +++ b/src/cmd/internal/obj/x86/obj6.go @@ -1254,29 +1254,6 @@ func progMentionsR15(p *obj.Prog) bool { return addrMentionsR15(&p.From) || addrMentionsR15(&p.To) || isR15(p.Reg) || addrMentionsR15(p.GetFrom3()) } -// progOverwritesR15 reports whether p writes to R15 and does not depend on -// the previous value of R15. -func progOverwritesR15(p *obj.Prog) bool { - if !(p.To.Type == obj.TYPE_REG && isR15(p.To.Reg)) { - // Not writing to R15. - return false - } - if (p.As == AXORL || p.As == AXORQ) && p.From.Type == obj.TYPE_REG && isR15(p.From.Reg) { - // These look like uses of R15, but aren't, so we must detect these - // before the use check below. - return true - } - if addrMentionsR15(&p.From) || isR15(p.Reg) || addrMentionsR15(p.GetFrom3()) { - // use before overwrite - return false - } - if p.As == AMOVL || p.As == AMOVQ || p.As == APOPQ { - return true - // TODO: MOVB might be ok if we only ever use R15B. - } - return false -} - func addrUsesGlobal(a *obj.Addr) bool { if a == nil { return false @@ -1296,6 +1273,114 @@ func progUsesGlobal(p *obj.Prog) bool { return addrUsesGlobal(&p.From) || addrUsesGlobal(&p.To) || addrUsesGlobal(p.GetFrom3()) } +type rwMask int + +const ( + readFrom rwMask = 1 << iota + readTo + readReg + readFrom3 + writeFrom + writeTo + writeReg + writeFrom3 +) + +// progRW returns a mask describing the effects of the instruction p. +// Note: this isn't exhaustively accurate. It is only currently used for detecting +// reads/writes to R15, so SSE register behavior isn't fully correct, and +// other weird cases (e.g. writes to DX by CLD) also aren't captured. +func progRW(p *obj.Prog) rwMask { + var m rwMask + // Default for most instructions + if p.From.Type != obj.TYPE_NONE { + m |= readFrom + } + if p.To.Type != obj.TYPE_NONE { + // Most x86 instructions update the To value + m |= readTo | writeTo + } + if p.Reg != 0 { + m |= readReg + } + if p.GetFrom3() != nil { + m |= readFrom3 + } + + // Lots of exceptions to the above defaults. + name := p.As.String() + if strings.HasPrefix(name, "MOV") || strings.HasPrefix(name, "PMOV") { + // MOV instructions don't read To. + m &^= readTo + } + switch p.As { + case APOPW, APOPL, APOPQ, + ALEAL, ALEAQ, + AIMUL3W, AIMUL3L, AIMUL3Q, + APEXTRB, APEXTRW, APEXTRD, APEXTRQ, AVPEXTRB, AVPEXTRW, AVPEXTRD, AVPEXTRQ, AEXTRACTPS, + ABSFW, ABSFL, ABSFQ, ABSRW, ABSRL, ABSRQ, APOPCNTW, APOPCNTL, APOPCNTQ, ALZCNTW, ALZCNTL, ALZCNTQ, + ASHLXL, ASHLXQ, ASHRXL, ASHRXQ, ASARXL, ASARXQ: + // These instructions are pure writes to To. They don't use its old value. + m &^= readTo + case AXORL, AXORQ: + // Register-clearing idiom doesn't read previous value. + if p.From.Type == obj.TYPE_REG && p.To.Type == obj.TYPE_REG && p.From.Reg == p.To.Reg { + m &^= readFrom | readTo + } + case AMULXL, AMULXQ: + // These are write-only to both To and From3. + m &^= readTo | readFrom3 + m |= writeFrom3 + } + return m +} + +// progReadsR15 reports whether p reads the register R15. +func progReadsR15(p *obj.Prog) bool { + m := progRW(p) + if m&readFrom != 0 && p.From.Type == obj.TYPE_REG && isR15(p.From.Reg) { + return true + } + if m&readTo != 0 && p.To.Type == obj.TYPE_REG && isR15(p.To.Reg) { + return true + } + if m&readReg != 0 && isR15(p.Reg) { + return true + } + if m&readFrom3 != 0 && p.GetFrom3().Type == obj.TYPE_REG && isR15(p.GetFrom3().Reg) { + return true + } + // reads of the index registers + if p.From.Type == obj.TYPE_MEM && (isR15(p.From.Reg) || isR15(p.From.Index)) { + return true + } + if p.To.Type == obj.TYPE_MEM && (isR15(p.To.Reg) || isR15(p.To.Index)) { + return true + } + if f3 := p.GetFrom3(); f3 != nil && f3.Type == obj.TYPE_MEM && (isR15(f3.Reg) || isR15(f3.Index)) { + return true + } + return false +} + +// progWritesR15 reports whether p writes the register R15. +func progWritesR15(p *obj.Prog) bool { + m := progRW(p) + if m&writeFrom != 0 && p.From.Type == obj.TYPE_REG && isR15(p.From.Reg) { + return true + } + if m&writeTo != 0 && p.To.Type == obj.TYPE_REG && isR15(p.To.Reg) { + return true + } + if m&writeReg != 0 && isR15(p.Reg) { + return true + } + if m&writeFrom3 != 0 && p.GetFrom3().Type == obj.TYPE_REG && isR15(p.GetFrom3().Reg) { + return true + } + return false +} + func errorCheck(ctxt *obj.Link, s *obj.LSym) { // When dynamic linking, R15 is used to access globals. Reject code that // uses R15 after a global variable access. @@ -1320,6 +1405,15 @@ func errorCheck(ctxt *obj.Link, s *obj.LSym) { for len(work) > 0 { p := work[len(work)-1] work = work[:len(work)-1] + if progReadsR15(p) { + pos := ctxt.PosTable.Pos(p.Pos) + ctxt.Diag("%s:%s: when dynamic linking, R15 is clobbered by a global variable access and is used here: %v", path.Base(pos.Filename()), pos.LineNumber(), p) + break // only report one error + } + if progWritesR15(p) { + // R15 is overwritten by this instruction. Its value is not junk any more. + continue + } if q := p.To.Target(); q != nil && q.Mark&markBit == 0 { q.Mark |= markBit work = append(work, q) @@ -1327,15 +1421,6 @@ func errorCheck(ctxt *obj.Link, s *obj.LSym) { if p.As == obj.AJMP || p.As == obj.ARET { continue // no fallthrough } - if progMentionsR15(p) { - if progOverwritesR15(p) { - // R15 is overwritten by this instruction. Its value is not junk any more. - continue - } - pos := ctxt.PosTable.Pos(p.Pos) - ctxt.Diag("%s:%s: when dynamic linking, R15 is clobbered by a global variable access and is used here: %v", path.Base(pos.Filename()), pos.LineNumber(), p) - break // only report one error - } if q := p.Link; q != nil && q.Mark&markBit == 0 { q.Mark |= markBit work = append(work, q)