blob: 746f9c9e82a1c4964c70fe0edd8c1990eba5d946 [file]
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
import (
"encoding/binary"
"fmt"
"internal/buildcfg"
"io"
"math"
"math/bits"
"os"
"path/filepath"
"strings"
"cmd/compile/internal/base"
"cmd/compile/internal/logopt"
"cmd/compile/internal/ssa/ssaop"
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/obj/s390x"
"cmd/internal/objabi"
"cmd/internal/src"
)
// Aux is an interface to hold miscellaneous data in Blocks and Values.
type Aux interface {
CanBeAnSSAAux()
}
func BoolToAuxInt(b bool) int64 {
if b {
return 1
}
return 0
}
// FlagConstant represents the result of a compile-time comparison.
// The sense of these flags does not necessarily represent the hardware's notion
// of a flags register - these are just a compile-time construct.
// We happen to match the semantics to those of arm/arm64.
// Note that these semantics differ from x86: the carry flag has the opposite
// sense on a subtraction!
//
// On amd64, C=1 represents a borrow, e.g. SBB on amd64 does x - y - C.
// On arm64, C=0 represents a borrow, e.g. SBC on arm64 does x - y - ^C.
// (because it does x + ^y + C).
//
// See https://en.wikipedia.org/wiki/Carry_flag#Vs._borrow_flag
type FlagConstant uint8
func IsNewObjectCall(aux Aux) bool {
fn := aux.(*AuxCall).Fn
return fn != nil && fn.String() == "runtime.newobject"
}
func IsSpecializedMalloc(aux Aux) bool {
fn := aux.(*AuxCall).Fn
if fn == nil {
return false
}
name := fn.String()
return strings.HasPrefix(name, "runtime.mallocgcSmallNoScanSC") ||
strings.HasPrefix(name, "runtime.mallocgcSmallScanNoHeaderSC") ||
strings.HasPrefix(name, "runtime.mallocgcTinySC")
}
// StringAux wraps string values for use in Aux.
type StringAux string
func StringToAux(s string) Aux {
return StringAux(s)
}
func AuxIntToArm64ConditionalParams(i int64) Arm64ConditionalParams {
var params Arm64ConditionalParams
params.Cond = ssaop.Op(i & 0xffff)
i >>= 16
params.NzcvVal = uint8(i & 0x0f)
i >>= 4
params.ConstVal = uint8(i & 0x1f)
i >>= 5
params.Ind = i == 1
return params
}
func LogLargeCopy(funcName string, pos src.XPos, s int64) {
if s < 128 {
return
}
if logopt.Enabled() {
logopt.LogOpt(pos, "copy", "lower", funcName, fmt.Sprintf("%d bytes", s))
}
}
// for now only used to mark moves that need to avoid clobbering flags
type auxMark bool
var AuxMark auxMark
// PanicBoundsC contains a constant for a bounds failure.
type PanicBoundsC struct {
C int64
}
// PanicBoundsCC contains 2 constants for a bounds failure.
type PanicBoundsCC struct {
Cx int64
Cy int64
}
func GetPPC64Shiftsh(auxint int64) int64 {
return int64(int8(auxint >> 16))
}
func GetPPC64Shiftmb(auxint int64) int64 {
return int64(int8(auxint >> 8))
}
// DecodePPC64RotateMask is the inverse operation of encodePPC64RotateMask. The values returned as
// mb and me satisfy the POWER ISA definition of MASK(x,y) where MASK(mb,me) = mask.
func DecodePPC64RotateMask(sauxint int64) (rotate, mb, me int64, mask uint64) {
auxint := uint64(sauxint)
rotate = int64((auxint >> 16) & 0xFF)
mb = int64((auxint >> 8) & 0xFF)
me = int64((auxint >> 0) & 0xFF)
nbits := int64((auxint >> 24) & 0xFF)
mask = ((1 << uint(nbits-mb)) - 1) ^ ((1 << uint(nbits-me)) - 1)
if mb > me {
mask = ^mask
}
if nbits == 32 {
mask = uint64(uint32(mask))
}
// Fixup ME to match ISA definition. The second argument to MASK(..,me)
// is inclusive.
me = (me - 1) & (nbits - 1)
return
}
// DivisionNeedsFixUp reports whether the division needs fix-up code.
func DivisionNeedsFixUp(v *Value) bool {
return v.AuxInt == 0
}
// ZeroUpper32Bits checks if value zeroes out upper 32-bit of 64-bit register.
// depth limits recursion depth. In AMD64.rules 3 is used as limit,
// because it catches same amount of cases as 4.
func ZeroUpper32Bits(x *Value) bool { return zeroUpperBits(x, 32, 3) }
// zeroUpperBits reports whether the 64-bit register holding x provably has
// its upper `bits` bits zero, i.e. the value is below 2^(64-bits).
//
// Which ops guarantee this is declared per op in the _gen op definitions
// (the zeroUpperBits attribute); only the value-dependent cases live here.
func zeroUpperBits(x *Value, bits int64, depth int) bool {
if x.Type.IsSigned() && 8*x.Type.Size() <= 64-bits {
// A spill/restore sign-extends from the type's width (issue 68227).
// A signed type no wider than the claimed value width may have its
// sign bit set, so a restore can write ones into the upper bits.
// Wider signed types are safe: their value is below the type's
// sign bit, so a restore zero-extends.
return false
}
if int64(ssaop.OpcodeTable[x.Op].ZeroUpperBits) >= bits {
return true
}
switch x.Op {
case ssaop.OpAMD64MOVQconst, ssaop.OpAMD64MOVLconst:
// A constant qualifies whenever its value fits the claimed width.
// (MOVLconst always zeroes the upper 32 bits, so for bits==32 it
// is already handled by its zeroUpperBits attribute.)
return uint64(x.AuxInt)>>(64-bits) == 0
case ssaop.OpArg: // note: but not ArgIntReg
// amd64 always loads args from the stack unsigned.
// most other architectures load them sign/zero extended based on the type.
return 8*x.Type.Size() == 64-bits && x.Block.Func.Config.Arch == "amd64"
case ssaop.OpSelect0, ssaop.OpSelect1:
// A Select names one register result of a tuple-producing op, so
// the question is what that op's write does. The op's attribute
// covers every integer result; a Select of a non-covered result
// (flags, memory) never appears as an operand of the rules that
// ask about upper bits.
return int64(ssaop.OpcodeTable[x.Args[0].Op].ZeroUpperBits) >= bits
case ssaop.OpPhi:
// Phis can use each-other as an arguments, instead of tracking visited values,
// just limit recursion depth.
if depth <= 0 {
return false
}
for i := range x.Args {
if !zeroUpperBits(x.Args[i], bits, depth-1) {
return false
}
}
return true
}
return false
}
// ZeroUpper48Bits is similar to ZeroUpper32Bits, but for upper 48 bits.
func ZeroUpper48Bits(x *Value) bool { return zeroUpperBits(x, 48, 3) }
// ZeroUpper56Bits is similar to ZeroUpper32Bits, but for upper 56 bits.
func ZeroUpper56Bits(x *Value) bool { return zeroUpperBits(x, 56, 3) }
// IsSamePtr reports whether p1 and p2 point to the same address.
func IsSamePtr(p1, p2 *Value) bool {
if p1 == p2 {
return true
}
if p1.Op != p2.Op {
for p1.Op == ssaop.OpOffPtr && p1.AuxInt == 0 {
p1 = p1.Args[0]
}
for p2.Op == ssaop.OpOffPtr && p2.AuxInt == 0 {
p2 = p2.Args[0]
}
if p1 == p2 {
return true
}
if p1.Op != p2.Op {
return false
}
}
switch p1.Op {
case ssaop.OpOffPtr:
return p1.AuxInt == p2.AuxInt && IsSamePtr(p1.Args[0], p2.Args[0])
case ssaop.OpAddr, ssaop.OpLocalAddr:
return p1.Aux == p2.Aux
case ssaop.OpAddPtr:
return p1.Args[1] == p2.Args[1] && IsSamePtr(p1.Args[0], p2.Args[0])
}
return false
}
// Disjoint reports whether the memory region specified by [p1:p1+t1.Size())
// does not overlap with [p2:p2+t2.Size()).
// A return value of false does not imply the regions overlap.
func Disjoint(p1 *Value, t1 *types.Type, p2 *Value, t2 *types.Type) bool {
return Disjoint1(p1, t1.Size(), p2, t2.Size())
}
// Disjoint1 reports whether the memory region specified by [p1:p1+n1)
// does not overlap with [p2:p2+n2).
// A return value of false does not imply the regions overlap.
func Disjoint1(p1 *Value, n1 int64, p2 *Value, n2 int64) bool {
if n1 == 0 || n2 == 0 {
return true
}
if p1 == p2 {
return false
}
baseAndOffset := func(ptr *Value) (base *Value, offset int64) {
base, offset = ptr, 0
for base.Op == ssaop.OpOffPtr {
offset += base.AuxInt
base = base.Args[0]
}
if ssaop.OpcodeTable[base.Op].NilCheck {
base = base.Args[0]
}
return base, offset
}
// Run types-based analysis
if DisjointTypes(p1.Type, p2.Type) {
return true
}
p1, off1 := baseAndOffset(p1)
p2, off2 := baseAndOffset(p2)
if IsSamePtr(p1, p2) {
return !Overlap(off1, n1, off2, n2)
}
// p1 and p2 are not the same, so if they are both OpAddrs then
// they point to different variables.
// If one pointer is on the stack and the other is an argument
// then they can't overlap.
switch p1.Op {
case ssaop.OpAddr, ssaop.OpLocalAddr:
if p2.Op == ssaop.OpAddr || p2.Op == ssaop.OpLocalAddr || p2.Op == ssaop.OpSP {
return true
}
return (p2.Op == ssaop.OpArg || p2.Op == ssaop.OpArgIntReg) && p1.Args[0].Op == ssaop.OpSP
case ssaop.OpArg, ssaop.OpArgIntReg:
if p2.Op == ssaop.OpSP || p2.Op == ssaop.OpLocalAddr {
return true
}
case ssaop.OpSP:
return p2.Op == ssaop.OpAddr || p2.Op == ssaop.OpLocalAddr || p2.Op == ssaop.OpArg || p2.Op == ssaop.OpArgIntReg || p2.Op == ssaop.OpSP
}
return false
}
// DisjointTypes reports whether a memory region pointed to by a pointer of type
// t1 does not overlap with a memory region pointed to by a pointer of type t2 --
// based on type aliasing rules.
func DisjointTypes(t1 *types.Type, t2 *types.Type) bool {
// Unsafe pointer can alias with anything.
if t1.IsUnsafePtr() || t2.IsUnsafePtr() {
return false
}
if !t1.IsPtr() || !t2.IsPtr() {
// Treat non-pointer types (such as TFUNC, TMAP, uintptr) conservatively.
return false
}
t1 = t1.Elem()
t2 = t2.Elem()
// Not-in-heap types are not supported -- they are rare and non-important; also,
// type.HasPointers check doesn't work for them correctly.
if t1.NotInHeap() || t2.NotInHeap() {
return false
}
isPtrShaped := func(t *types.Type) bool { return int(t.Size()) == types.PtrSize && t.HasPointers() }
// Pointers and non-pointers are disjoint (https://pkg.go.dev/unsafe#Pointer).
if (isPtrShaped(t1) && !t2.HasPointers()) ||
(isPtrShaped(t2) && !t1.HasPointers()) {
return true
}
return false
}
// Overlap reports whether the ranges given by the given offset and
// size pairs Overlap.
func Overlap(offset1, size1, offset2, size2 int64) bool {
if offset1 >= offset2 && offset2+size2 > offset1 {
return true
}
if offset2 >= offset1 && offset1+size1 > offset2 {
return true
}
return false
}
// isInlinableMemmove reports whether the given arch performs a Move of the given size
// faster than memmove. It will only return true if replacing the memmove with a Move is
// safe, either because Move will do all of its loads before any of its stores, or
// because the arguments are known to be disjoint.
// This is used as a check for replacing memmove with Move ops.
func isInlinableMemmove(dst, src *Value, sz int64, c *Config) bool {
// It is always safe to convert memmove into Move when its arguments are disjoint.
// Move ops may or may not be faster for large sizes depending on how the platform
// lowers them, so we only perform this optimization on platforms that we know to
// have fast Move ops.
switch c.Arch {
case "amd64":
return sz <= 16 || (sz < 1024 && Disjoint1(dst, sz, src, sz))
case "arm64":
return sz <= 64 || (sz <= 1024 && Disjoint1(dst, sz, src, sz))
case "loong64":
return sz <= 16 || (sz <= 64 && Disjoint1(dst, sz, src, sz))
case "386":
return sz <= 8
case "s390x", "ppc64", "ppc64le":
return sz <= 8 || Disjoint1(dst, sz, src, sz)
case "arm", "mips", "mips64", "mipsle", "mips64le":
return sz <= 4
}
return false
}
func IsInlinableMemmove(dst, src *Value, sz int64, c *Config) bool {
return isInlinableMemmove(dst, src, sz, c)
}
func (auxMark) CanBeAnSSAAux() {}
func (StringAux) CanBeAnSSAAux() {}
// returns the Lsb part of the auxInt field of arm64 bitfield ops.
func (bfc Arm64BitField) Lsb() int64 {
return int64(uint64(bfc) >> 8)
}
// returns the Width part of the auxInt field of arm64 bitfield ops.
func (bfc Arm64BitField) Width() int64 {
return int64(bfc) & 0xff
}
// extracts NZCV flags from auxint.
func (condParams Arm64ConditionalParams) Nzcv() int64 {
return int64(condParams.NzcvVal)
}
// extracts constant value from auxint if present.
func (condParams Arm64ConditionalParams) ConstValue() (int64, bool) {
return int64(condParams.ConstVal), condParams.Ind
}
// N reports whether the result of an operation is negative (high bit set).
func (fc FlagConstant) N() bool {
return fc&1 != 0
}
// Z reports whether the result of an operation is 0.
func (fc FlagConstant) Z() bool {
return fc&2 != 0
}
// C reports whether an unsigned add overflowed (carry), or an
// unsigned subtract did not underflow (borrow).
func (fc FlagConstant) C() bool {
return fc&4 != 0
}
// V reports whether a signed operation overflowed or underflowed.
func (fc FlagConstant) V() bool {
return fc&8 != 0
}
func (fc FlagConstant) Eq() bool {
return fc.Z()
}
func (fc FlagConstant) Ne() bool {
return !fc.Z()
}
func (fc FlagConstant) Lt() bool {
return fc.N() != fc.V()
}
func (fc FlagConstant) Le() bool {
return fc.Z() || fc.Lt()
}
func (fc FlagConstant) Gt() bool {
return !fc.Z() && fc.Ge()
}
func (fc FlagConstant) Ge() bool {
return fc.N() == fc.V()
}
func (fc FlagConstant) Ult() bool {
return !fc.C()
}
func (fc FlagConstant) Ule() bool {
return fc.Z() || fc.Ult()
}
func (fc FlagConstant) Ugt() bool {
return !fc.Z() && fc.Uge()
}
func (fc FlagConstant) Uge() bool {
return fc.C()
}
func (fc FlagConstant) LtNoov() bool {
return fc.Lt() && !fc.V()
}
func (fc FlagConstant) LeNoov() bool {
return fc.Le() && !fc.V()
}
func (fc FlagConstant) GtNoov() bool {
return fc.Gt() && !fc.V()
}
func (fc FlagConstant) GeNoov() bool {
return fc.Ge() && !fc.V()
}
func (fc FlagConstant) String() string {
return fmt.Sprintf("N=%v,Z=%v,C=%v,V=%v", fc.N(), fc.Z(), fc.C(), fc.V())
}
func (p PanicBoundsC) CanBeAnSSAAux() {
}
func (p PanicBoundsCC) CanBeAnSSAAux() {
}
// AddFlags32 returns the flags that would be set from computing x+y.
func AddFlags32(x, y int32) FlagConstant {
var fcb FlagConstantBuilder
fcb.Z = x+y == 0
fcb.N = x+y < 0
fcb.C = uint32(x+y) < uint32(x)
fcb.V = x >= 0 && y >= 0 && x+y < 0 || x < 0 && y < 0 && x+y >= 0
return fcb.Encode()
}
// Note: addFlags(x,y) != subFlags(x,-y) in some situations:
// - the results of the C flag are different
// - the results of the V flag when y==minint are different
// AddFlags64 returns the flags that would be set from computing x+y.
func AddFlags64(x, y int64) FlagConstant {
var fcb FlagConstantBuilder
fcb.Z = x+y == 0
fcb.N = x+y < 0
fcb.C = uint64(x+y) < uint64(x)
fcb.V = x >= 0 && y >= 0 && x+y < 0 || x < 0 && y < 0 && x+y >= 0
return fcb.Encode()
}
func Arm64BitFieldToAuxInt(v Arm64BitField) int64 {
return int64(v)
}
func Arm64ConditionalParamsToAuxInt(v Arm64ConditionalParams) int64 {
if v.Cond&^0xffff != 0 {
panic("condition value exceeds 16 bits")
}
var i int64
if v.Ind {
i = 1 << 25
}
i |= int64(v.ConstVal) << 20
i |= int64(v.NzcvVal) << 16
i |= int64(v.Cond)
return i
}
type Int64Aux int64
func (Int64Aux) CanBeAnSSAAux() {}
func Int64ToAux(v int64) Aux {
return Int64Aux(v)
}
// encodes the lsb and width for arm(64) bitfield ops into the expected auxInt format.
func ArmBFAuxInt(lsb, width int64) Arm64BitField {
if lsb < 0 || lsb > 63 {
panic("ARM(64) bit field lsb constant out of range")
}
if width < 1 || lsb+width > 64 {
panic("ARM(64) bit field width constant out of range")
}
return Arm64BitField(width | lsb<<8)
}
func AuxIntToArm64BitField(i int64) Arm64BitField {
return Arm64BitField(i)
}
func AuxIntToBool(i int64) bool {
if i == 0 {
return false
}
return true
}
func AuxIntToFlagConstant(x int64) FlagConstant {
return FlagConstant(x)
}
func AuxIntToFloat32(i int64) float32 {
return float32(math.Float64frombits(uint64(i)))
}
func AuxIntToFloat64(i int64) float64 {
return math.Float64frombits(uint64(i))
}
func AuxIntToInt16(i int64) int16 {
return int16(i)
}
func AuxIntToInt32(i int64) int32 {
return int32(i)
}
func AuxIntToInt64(i int64) int64 {
return i
}
func AuxIntToInt8(i int64) int8 {
return int8(i)
}
func AuxIntToOp(cc int64) ssaop.Op {
return ssaop.Op(cc)
}
func AuxIntToUint64(i int64) uint64 {
return uint64(i)
}
func AuxIntToUint8(i int64) uint8 {
return uint8(i)
}
func AuxIntToValAndOff(i int64) ValAndOff {
return ValAndOff(i)
}
func AuxToCall(i Aux) *AuxCall {
return i.(*AuxCall)
}
func AuxToPanicBoundsC(i Aux) PanicBoundsC {
return i.(PanicBoundsC)
}
func AuxToPanicBoundsCC(i Aux) PanicBoundsCC {
return i.(PanicBoundsCC)
}
func AuxToS390xCCMask(i Aux) s390x.CCMask {
return i.(s390x.CCMask)
}
func AuxToS390xRotateParams(i Aux) s390x.RotateParams {
return i.(s390x.RotateParams)
}
func AuxToString(i Aux) string {
return string(i.(StringAux))
}
func AuxToSym(i Aux) Sym {
// TODO: kind of a hack - allows nil interface through
s, _ := i.(Sym)
return s
}
func AuxToType(i Aux) *types.Type {
return i.(*types.Type)
}
// B2i translates a boolean value to 0 or 1 for assigning to auxInt.
func B2i(b bool) int64 {
if b {
return 1
}
return 0
}
// B2i32 translates a boolean value to 0 or 1.
func B2i32(b bool) int32 {
if b {
return 1
}
return 0
}
func CallToAux(s *AuxCall) Aux {
return s
}
// CanMergeLoad reports whether the load can be merged into target without
// invalidating the schedule.
func CanMergeLoad(target, load *Value) bool {
if target.Block.ID != load.Block.ID {
// If the load is in a different block do not merge it.
return false
}
// We can't merge the load into the target if the load
// has more than one use.
if load.Uses != 1 {
return false
}
mem := load.MemoryArg()
// We need the load's memory arg to still be alive at target. That
// can't be the case if one of target's args depends on a memory
// state that is a successor of load's memory arg.
//
// For example, it would be invalid to merge load into target in
// the following situation because newmem has killed oldmem
// before target is reached:
// load = read ... oldmem
// newmem = write ... oldmem
// arg0 = read ... newmem
// target = add arg0 load
//
// If the argument comes from a different block then we can exclude
// it immediately because it must dominate load (which is in the
// same block as target).
var args []*Value
for _, a := range target.Args {
if a != load && a.Block.ID == target.Block.ID {
args = append(args, a)
}
}
f := target.Block.Func
visited := f.NewSparseSet(f.NumValues())
defer f.RetSparseSet(visited)
// memPreds contains memory states known to be predecessors of load's
// memory state. It is lazily initialized.
var memPreds map[*Value]bool
for len(args) > 0 {
const limit = 2048 // enough to comfortably cover unrolled crypto blocks
if visited.Size() >= limit {
// Give up if we have visited a lot of values.
return false
}
v := args[len(args)-1]
args = args[:len(args)-1]
if visited.Contains(v.ID) {
continue
}
visited.Add(v.ID)
if target.Block.ID != v.Block.ID {
// Since target and load are in the same block
// we can stop searching when we leave the block.
continue
}
if v.Op == ssaop.OpPhi {
// A Phi implies we have reached the top of the block.
// The memory phi, if it exists, is always
// the first logical store in the block.
continue
}
if v.Type.IsTuple() && v.Type.FieldType(1).IsMemory() {
// We could handle this situation however it is likely
// to be very rare.
return false
}
if v.Op.SymEffect()&ssaop.SymAddr != 0 {
// This case prevents an operation that calculates the
// address of a local variable from being forced to schedule
// before its corresponding VarDef.
// See issue 28445.
// v1 = LOAD ...
// v2 = VARDEF
// v3 = LEAQ
// v4 = CMPQ v1 v3
// We don't want to combine the CMPQ with the load, because
// that would force the CMPQ to schedule before the VARDEF, which
// in turn requires the LEAQ to schedule before the VARDEF.
return false
}
if v.Type.IsMemory() {
if memPreds == nil {
// Initialise a map containing memory states
// known to be predecessors of load's memory
// state.
memPreds = make(map[*Value]bool)
m := mem
const limit = 50
for i := 0; i < limit; i++ {
if m.Op == ssaop.OpPhi {
// The memory phi, if it exists, is always
// the first logical store in the block.
break
}
if m.Block.ID != target.Block.ID {
break
}
if !m.Type.IsMemory() {
break
}
memPreds[m] = true
if len(m.Args) == 0 {
break
}
m = m.MemoryArg()
}
}
// We can merge if v is a predecessor of mem.
//
// For example, we can merge load into target in the
// following scenario:
// x = read ... v
// mem = write ... v
// load = read ... mem
// target = add x load
if memPreds[v] {
continue
}
return false
}
if len(v.Args) > 0 && v.Args[len(v.Args)-1] == mem {
// If v takes mem as an input then we know mem
// is valid at this point.
continue
}
for _, a := range v.Args {
if target.Block.ID == a.Block.ID {
args = append(args, a)
}
}
}
return true
}
// CanMergeLoadClobber reports whether the load can be merged into target without
// invalidating the schedule.
// It also checks that the other non-load argument x is something we
// are ok with clobbering.
func CanMergeLoadClobber(target, load, x *Value) bool {
// The register containing x is going to get clobbered.
// Don't merge if we still need the value of x.
// We don't have liveness information here, but we can
// approximate x dying with:
// 1) target is x's only use.
// 2) target is not in a deeper loop than x.
switch {
case x.Uses == 2 && x.Op == ssaop.OpPhi && len(x.Args) == 2 && (x.Args[0] == target || x.Args[1] == target) && target.Uses == 1:
// This is a simple detector to determine that x is probably
// not live after target. (It does not need to be perfect,
// regalloc will issue a reg-reg move to save it if we are wrong.)
// We have:
// x = Phi(?, target)
// target = Op(load, x)
// Because target has only one use as a Phi argument, we can schedule it
// very late. Hopefully, later than the other use of x. (The other use died
// between x and target, or exists on another branch entirely).
case x.Uses > 1:
return false
}
loopnest := x.Block.Func.Loopnest()
if loopnest.Depth(target.Block.ID) > loopnest.Depth(x.Block.ID) {
return false
}
return CanMergeLoad(target, load)
}
func CanMergeSym(x, y Sym) bool {
return x == nil || y == nil
}
func CanMulStrengthReduce(config *Config, x int64) bool {
_, ok := config.MulRecipes[x]
return ok
}
func CanMulStrengthReduce32(config *Config, x int32) bool {
_, ok := config.MulRecipes[int64(x)]
return ok
}
// CanonLessThan returns whether x is "ordered" less than y, for purposes of normalizing
// generated code as much as possible.
func CanonLessThan(x, y *Value) bool {
if x.Op != y.Op {
return x.Op < y.Op
}
if !x.Pos.SameFileAndLine(y.Pos) {
return x.Pos.Before(y.Pos)
}
return x.ID < y.ID
}
// Clobber invalidates values. Returns true.
// Clobber is used by rewrite rules to:
//
// A) make sure the values are really dead and never used again.
// B) decrement use counts of the values' args.
func Clobber(vv ...*Value) bool {
for _, v := range vv {
v.Reset(ssaop.OpInvalid)
// Note: leave v.Block intact. The Block field is used after clobber.
}
return true
}
// ClobberIfDead resets v when use count is 1. Returns true.
// ClobberIfDead is used by rewrite rules to decrement
// use counts of v's args when v is dead and never used.
func ClobberIfDead(v *Value) bool {
if v.Uses == 1 {
v.Reset(ssaop.OpInvalid)
}
// Note: leave v.Block intact. The Block field is used after clobberIfDead.
return true
}
// CountRule increments Func.ruleMatches[key].
// If Func.ruleMatches is non-nil at the end
// of compilation, it will be printed to stdout.
// This is intended to make it easier to find which functions
// which contain lots of rules matches when developing new rules.
func CountRule(v *Value, key string) bool {
f := v.Block.Func
if f.RuleMatches == nil {
f.RuleMatches = make(map[string]int)
}
f.RuleMatches[key]++
return true
}
type DeadValueChoice bool
// Compress mask and shift into single value of the form
// me | mb<<8 | rotate<<16 | nbits<<24 where me and mb can
// be used to regenerate the input mask.
func EncodePPC64RotateMask(rotate, mask, nbits int64) int64 {
var mb, me, mbn, men int
// Determine boundaries and then decode them
if mask == 0 || ^mask == 0 || rotate >= nbits {
panic(fmt.Sprintf("invalid PPC64 rotate mask: %x %d %d", uint64(mask), rotate, nbits))
} else if nbits == 32 {
mb = bits.LeadingZeros32(uint32(mask))
me = 32 - bits.TrailingZeros32(uint32(mask))
mbn = bits.LeadingZeros32(^uint32(mask))
men = 32 - bits.TrailingZeros32(^uint32(mask))
} else {
mb = bits.LeadingZeros64(uint64(mask))
me = 64 - bits.TrailingZeros64(uint64(mask))
mbn = bits.LeadingZeros64(^uint64(mask))
men = 64 - bits.TrailingZeros64(^uint64(mask))
}
// Check for a wrapping mask (e.g bits at 0 and 63)
if mb == 0 && me == int(nbits) {
// swap the inverted values
mb, me = men, mbn
}
return int64(me) | int64(mb<<8) | rotate<<16 | nbits<<24
}
// for a pseudo-op like (LessThan x), extract x.
func FlagArg(v *Value) *Value {
if len(v.Args) != 1 || !v.Args[0].Type.IsFlags() {
return nil
}
return v.Args[0]
}
type FlagConstantBuilder struct {
N bool
Z bool
C bool
V bool
}
func FlagConstantToAuxInt(x FlagConstant) int64 {
return int64(x)
}
func Float32ToAuxInt(f float32) int64 {
return int64(math.Float64bits(float64(f)))
}
func Float64ToAuxInt(f float64) int64 {
return int64(math.Float64bits(f))
}
// When v is (IMake typ (StructMake ...)), convert to
// (IMake typ arg) where arg is the pointer-y argument to
// the StructMake (there must be exactly one).
func ImakeOfStructMake(v *Value) *Value {
var arg *Value
for _, a := range v.Args[1].Args {
if a.Type.Size() > 0 {
arg = a
break
}
}
return v.Block.NewValue2(v.Pos, ssaop.OpIMake, v.Type, v.Args[0], arg)
}
func Int16ToAuxInt(i int16) int64 {
return int64(i)
}
func Int32ToAuxInt(i int32) int64 {
return int64(i)
}
func Int64ToAuxInt(i int64) int64 {
return i
}
func Int8ToAuxInt(i int8) int64 {
return int64(i)
}
// Is12Bit reports whether n can be represented as a signed 12 bit integer.
func Is12Bit(n int64) bool {
return -(1<<11) <= n && n < (1<<11)
}
// Is16Bit reports whether n can be represented as a signed 16 bit integer.
func Is16Bit(n int64) bool {
return n == int64(int16(n))
}
func Is16BitInt(t *types.Type) bool {
return t.Size() == 2 && t.IsInteger()
}
// Is20Bit reports whether n can be represented as a signed 20 bit integer.
func Is20Bit(n int64) bool {
return -(1<<19) <= n && n < (1<<19)
}
// Is32Bit reports whether n can be represented as a signed 32 bit integer.
func Is32Bit(n int64) bool {
return n == int64(int32(n))
}
func Is32BitFloat(t *types.Type) bool {
return t.Size() == 4 && t.IsFloat()
}
func Is32BitInt(t *types.Type) bool {
return t.Size() == 4 && t.IsInteger()
}
// Common functions called from rewriting rules
func Is64BitFloat(t *types.Type) bool {
return t.Size() == 8 && t.IsFloat()
}
func Is64BitInt(t *types.Type) bool {
return t.Size() == 8 && t.IsInteger()
}
func Is8BitInt(t *types.Type) bool {
return t.Size() == 1 && t.IsInteger()
}
// isPowerOfTwoX functions report whether n is a power of 2.
func IsPowerOfTwo[T int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64](n T) bool {
return n > 0 && n&(n-1) == 0
}
// This verifies that the mask is a set of
// consecutive bits including the least
// significant bit.
func IsPPC64ValidShiftMask(v int64) bool {
if (v != 0) && ((v+1)&v) == 0 {
return true
}
return false
}
// Test if this value can encoded as a mask for a rlwinm like
// operation. Masks can also extend from the msb and wrap to
// the lsb too. That is, the valid masks are 32 bit strings
// of the form: 0..01..10..0 or 1..10..01..1 or 1...1
//
// Note: This ignores the upper 32 bits of the input. When a
// zero extended result is desired (e.g a 64 bit result), the
// user must verify the upper 32 bits are 0 and the mask is
// contiguous (that is, non-wrapping).
func IsPPC64WordRotateMask(v64 int64) bool {
// Isolate rightmost 1 (if none 0) and add.
v := uint32(v64)
vp := (v & -v) + v
// Likewise, for the wrapping case.
vn := ^v
vpn := (vn & -vn) + vn
return (v&vp == 0 || vn&vpn == 0) && v != 0
}
func IsPtr(t *types.Type) bool {
return t.IsPtrShaped()
}
// IsSameCall reports whether aux is the same as the given named symbol.
func IsSameCall(aux Aux, name string) bool {
fn := aux.(*AuxCall).Fn
return fn != nil && fn.String() == name
}
// IsU32Bit reports whether n can be represented as an unsigned 32 bit integer.
func IsU32Bit(n int64) bool {
return n == int64(uint32(n))
}
// IsVolatile reports whether v is a pointer to argument region on stack which
// will be clobbered by a function call.
func IsVolatile(v *Value) bool {
for v.Op == ssaop.OpOffPtr || v.Op == ssaop.OpAddPtr || v.Op == ssaop.OpPtrIndex || v.Op == ssaop.OpCopy || v.Op == ssaop.OpSelectNAddr {
v = v.Args[0]
}
return v.Op == ssaop.OpSP
}
const (
LeaveDeadValues DeadValueChoice = false
RemoveDeadValues = true
RepZeroThreshold = 1408 // size beyond which we use REP STOS for zeroing
RepMoveThreshold = 1408 // size beyond which we use REP MOVS for copying
)
func Log16(n int16) int64 { return Log16u(uint16(n)) }
func Log16u(n uint16) int64 { return int64(bits.Len16(n)) - 1 }
func Log32(n int32) int64 { return Log32u(uint32(n)) }
func Log32u(n uint32) int64 { return int64(bits.Len32(n)) - 1 }
func Log64(n int64) int64 { return Log64u(uint64(n)) }
func Log64u(n uint64) int64 { return int64(bits.Len64(n)) - 1 }
// logXu returns the logarithm of n base 2.
// n must be a power of 2 (isPowerOfTwo returns true)
func Log8u(n uint8) int64 { return int64(bits.Len8(n)) - 1 }
// LogicFlags32 returns flags set to the sign/zeroness of x.
// C and V are set to false.
func LogicFlags32(x int32) FlagConstant {
var fcb FlagConstantBuilder
fcb.Z = x == 0
fcb.N = x < 0
return fcb.Encode()
}
// LogicFlags64 returns flags set to the sign/zeroness of x.
// C and V are set to false.
func LogicFlags64(x int64) FlagConstant {
var fcb FlagConstantBuilder
fcb.Z = x == 0
fcb.N = x < 0
return fcb.Encode()
}
// LogLargeCopyValue logs the occurrence of a large copy.
// The best place to do this is in the rewrite rules where the size of the move is easy to find.
// "Large" is arbitrarily chosen to be 128 bytes; this may change.
func LogLargeCopyValue(v *Value, s int64) bool {
if s < 128 {
return true
}
if logopt.Enabled() {
logopt.LogOpt(v.Pos, "copy", "lower", v.Block.Func.Name, fmt.Sprintf("%d bytes", s))
}
return true
}
// LogRule logs the use of the rule s. This will only be enabled if
// rewrite rules were generated with the -log option, see _gen/rulegen.go.
func LogRule(s string) {
if ruleFile == nil {
// Open a log file to write log to. We open in append
// mode because all.bash runs the compiler lots of times,
// and we want the concatenation of all of those logs.
// This means, of course, that users need to rm the old log
// to get fresh data.
// TODO: all.bash runs compilers in parallel. Need to synchronize logging somehow?
w, err := os.OpenFile(filepath.Join(os.Getenv("GOROOT"), "src", "rulelog"),
os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
panic(err)
}
ruleFile = w
}
// Ignore errors in case of multiple processes fighting over the file.
fmt.Fprintln(ruleFile, s)
}
func MakeJumpTableSym(b *Block) *obj.LSym {
s := base.Ctxt.Lookup(fmt.Sprintf("%s.jump%d", b.Func.Fe.Func().LSym.Name, b.ID))
// The jump table symbol is accessed only from the function symbol.
s.Set(obj.AttrStatic, true)
return s
}
// Combine (ANDconst [m] (SRWconst [s])) into (RLWINM [y]) or return 0
func MergePPC64AndSrwi(m, s int64) int64 {
mask := MergePPC64RShiftMask(m, s, 32)
if !IsPPC64WordRotateMask(mask) {
return 0
}
return EncodePPC64RotateMask((32-s)&31, mask, 32)
}
// Test if a RLWINM feeding into a CLRLSLDI can be merged into RLWINM. Return
// the encoded RLWINM constant, or 0 if they cannot be merged.
func MergePPC64ClrlsldiRlwinm(sld int32, rlw int64) int64 {
r_1, _, _, mask_1 := DecodePPC64RotateMask(rlw)
// for CLRLSLDI, it's more convenient to think of it as a mask left bits then rotate left.
mask_2 := uint64(0xFFFFFFFFFFFFFFFF) >> uint(GetPPC64Shiftmb(int64(sld)))
// combine the masks, and adjust for the final left shift.
mask_3 := (mask_1 & mask_2) << uint(GetPPC64Shiftsh(int64(sld)))
r_2 := GetPPC64Shiftsh(int64(sld))
r_3 := (r_1 + r_2) & 31 // This can wrap.
// Verify the result is still a valid bitmask of <= 32 bits.
if !IsPPC64WordRotateMask(int64(mask_3)) || uint64(uint32(mask_3)) != mask_3 {
return 0
}
return EncodePPC64RotateMask(r_3, int64(mask_3), 32)
}
// Test if a word shift right feeding into a CLRLSLDI can be merged into RLWINM.
// Return the encoded RLWINM constant, or 0 if they cannot be merged.
func MergePPC64ClrlsldiSrw(sld, srw int64) int64 {
mask_1 := uint64(0xFFFFFFFF >> uint(srw))
// for CLRLSLDI, it's more convenient to think of it as a mask left bits then rotate left.
mask_2 := uint64(0xFFFFFFFFFFFFFFFF) >> uint(GetPPC64Shiftmb(sld))
// Rewrite mask to apply after the final left shift.
mask_3 := (mask_1 & mask_2) << uint(GetPPC64Shiftsh(sld))
r_1 := 32 - srw
r_2 := GetPPC64Shiftsh(sld)
r_3 := (r_1 + r_2) & 31 // This can wrap.
if uint64(uint32(mask_3)) != mask_3 || mask_3 == 0 {
return 0
}
return EncodePPC64RotateMask(r_3, int64(mask_3), 32)
}
// Decompose a shift right into an equivalent rotate/mask,
// and return mask & m.
func MergePPC64RShiftMask(m, s, nbits int64) int64 {
smask := uint64((1<<uint(nbits))-1) >> uint(s)
return m & int64(smask)
}
// Compute the encoded RLWINM constant from combining (SLDconst [sld] (SRWconst [srw] x)),
// or return 0 if they cannot be combined.
func MergePPC64SldiSrw(sld, srw int64) int64 {
if sld > srw || srw >= 32 {
return 0
}
mask_r := uint32(0xFFFFFFFF) >> uint(srw)
mask_l := uint32(0xFFFFFFFF) >> uint(sld)
mask := (mask_r & mask_l) << uint(sld)
return EncodePPC64RotateMask((32-srw+sld)&31, int64(mask), 32)
}
// MergeSym merges two symbolic offsets. There is no real merging of
// offsets, we just pick the non-nil one.
func MergeSym(x, y Sym) Sym {
if x == nil {
return y
}
if y == nil {
return x
}
panic(fmt.Sprintf("mergeSym with two non-nil syms %v %v", x, y))
}
// MoveSize returns the number of bytes an aligned MOV instruction moves.
func MoveSize(align int64, c *Config) int64 {
switch {
case align%8 == 0 && c.PtrSize == 8:
return 8
case align%4 == 0:
return 4
case align%2 == 0:
return 2
}
return 1
}
// MulStrengthReduce returns v*x evaluated at the location
// (block and source position) of m.
// canMulStrengthReduce must have returned true.
func MulStrengthReduce(m *Value, v *Value, x int64) *Value {
return v.Block.Func.Config.MulRecipes[x].Build(m, v)
}
// MulStrengthReduce32 returns v*x evaluated at the location
// (block and source position) of m.
// canMulStrengthReduce32 must have returned true.
// The upper 32 bits of m might be set to junk.
func MulStrengthReduce32(m *Value, v *Value, x int32) *Value {
return v.Block.Func.Config.MulRecipes[int64(x)].Build(m, v)
}
func NewPPC64ShiftAuxInt(sh, mb, me, sz int64) int32 {
if sh < 0 || sh >= sz {
panic("PPC64 shift arg sh out of range")
}
if mb < 0 || mb >= sz {
panic("PPC64 shift arg mb out of range")
}
if me < 0 || me >= sz {
panic("PPC64 shift arg me out of range")
}
return int32(sh<<16 | mb<<8 | me)
}
// NoteRule is an easy way to track if a rule is matched when writing
// new ones. Make the rule of interest also conditional on
//
// NoteRule("note to self: rule of interest matched")
//
// and that message will print when the rule matches.
func NoteRule(s string) bool {
fmt.Println(s)
return true
}
// ntzX returns the number of trailing zeros.
func Ntz64(x int64) int { return bits.TrailingZeros64(uint64(x)) }
// OneBit reports whether x contains exactly one set bit.
func OneBit[T int8 | int16 | int32 | int64](x T) bool {
return x&(x-1) == 0 && x != 0
}
func OpToAuxInt(o ssaop.Op) int64 {
return int64(o)
}
func PanicBoundsCCToAux(p PanicBoundsCC) Aux {
return p
}
func PanicBoundsCToAux(p PanicBoundsC) Aux {
return p
}
// Read16 reads two bytes from the read-only global sym at offset off.
func Read16(sym Sym, off int64, byteorder binary.ByteOrder) uint16 {
lsym := sym.(*obj.LSym)
// lsym.P is written lazily.
// Bytes requested after the end of lsym.P are 0.
var src []byte
if 0 <= off && off < int64(len(lsym.P)) {
src = lsym.P[off:]
}
buf := make([]byte, 2)
copy(buf, src)
return byteorder.Uint16(buf)
}
// Read32 reads four bytes from the read-only global sym at offset off.
func Read32(sym Sym, off int64, byteorder binary.ByteOrder) uint32 {
lsym := sym.(*obj.LSym)
var src []byte
if 0 <= off && off < int64(len(lsym.P)) {
src = lsym.P[off:]
}
buf := make([]byte, 4)
copy(buf, src)
return byteorder.Uint32(buf)
}
// Read64 reads eight bytes from the read-only global sym at offset off.
func Read64(sym Sym, off int64, byteorder binary.ByteOrder) uint64 {
lsym := sym.(*obj.LSym)
var src []byte
if 0 <= off && off < int64(len(lsym.P)) {
src = lsym.P[off:]
}
buf := make([]byte, 8)
copy(buf, src)
return byteorder.Uint64(buf)
}
// Read8 reads one byte from the read-only global sym at offset off.
func Read8(sym Sym, off int64) uint8 {
lsym := sym.(*obj.LSym)
if off >= int64(len(lsym.P)) || off < 0 {
// Invalid index into the global sym.
// This can happen in dead code, so we don't want to panic.
// Just return any value, it will eventually get ignored.
// See issue 29215.
return 0
}
return lsym.P[off]
}
func RewriteStructStore(v *Value) *Value {
b := v.Block
dst := v.Args[0]
x := v.Args[1]
if x.Op != ssaop.OpStructMake {
base.Fatalf("invalid struct store: %v", x)
}
mem := v.Args[2]
t := x.Type
for i, arg := range x.Args {
ft := t.FieldType(i)
addr := b.NewValue1I(v.Pos, ssaop.OpOffPtr, ft.PtrTo(), t.FieldOff(i), dst)
mem = b.NewValue3A(v.Pos, ssaop.OpStore, types.TypeMem, TypeToAux(ft), addr, arg, mem)
}
return mem
}
func S390xCCMaskToAux(c s390x.CCMask) Aux {
return c
}
func S390xRotateParamsToAux(r s390x.RotateParams) Aux {
return r
}
// SetPos sets the position of v to pos, then returns true.
// Useful for setting the result of a rewrite's position to
// something other than the default.
func SetPos(v *Value, pos src.XPos) bool {
v.Pos = pos
return true
}
// ShiftIsBounded reports whether (left/right) shift Value v is known to be bounded.
// A shift is bounded if it is shifting by less than the width of the shifted value.
func ShiftIsBounded(v *Value) bool {
return v.AuxInt != 0
}
// SubFlags32 returns the flags that would be set from computing x-y.
func SubFlags32(x, y int32) FlagConstant {
var fcb FlagConstantBuilder
fcb.Z = x-y == 0
fcb.N = x-y < 0
fcb.C = uint32(y) <= uint32(x) // This code follows the arm carry flag model.
fcb.V = x >= 0 && y < 0 && x-y < 0 || x < 0 && y >= 0 && x-y >= 0
return fcb.Encode()
}
// SubFlags64 returns the flags that would be set from computing x-y.
func SubFlags64(x, y int64) FlagConstant {
var fcb FlagConstantBuilder
fcb.Z = x-y == 0
fcb.N = x-y < 0
fcb.C = uint64(y) <= uint64(x) // This code follows the arm carry flag model.
fcb.V = x >= 0 && y < 0 && x-y < 0 || x < 0 && y >= 0 && x-y >= 0
return fcb.Encode()
}
func SupportsPPC64PCRel() bool {
// PCRel is currently supported for >= power10, linux only
// Internal and external linking supports this on ppc64le; internal linking on ppc64.
return buildcfg.GOPPC64 >= 10 && buildcfg.GOOS == "linux"
}
// SymIsRO reports whether sym is a read-only global.
func SymIsRO(sym Sym) bool {
lsym := sym.(*obj.LSym)
return lsym.Type == objabi.SRODATA && len(lsym.R) == 0
}
func SymToAux(s Sym) Aux {
return s
}
func TypeToAux(t *types.Type) Aux {
return t
}
func Uint64ToAuxInt(i uint64) int64 {
return int64(i)
}
func Uint8ToAuxInt(i uint8) int64 {
return int64(int8(i))
}
func ValAndOffToAuxInt(v ValAndOff) int64 {
return int64(v)
}
var ruleFile io.Writer
func (fcs FlagConstantBuilder) Encode() FlagConstant {
var fc FlagConstant
if fcs.N {
fc |= 1
}
if fcs.Z {
fc |= 2
}
if fcs.C {
fc |= 4
}
if fcs.V {
fc |= 8
}
return fc
}
func IsConstZero(v *Value) bool {
switch v.Op {
case ssaop.OpConstNil:
return true
case ssaop.OpConst64, ssaop.OpConst32, ssaop.OpConst16, ssaop.OpConst8, ssaop.OpConstBool, ssaop.OpConst32F, ssaop.OpConst64F:
return v.AuxInt == 0
case ssaop.OpStringMake, ssaop.OpIMake, ssaop.OpComplexMake:
return IsConstZero(v.Args[0]) && IsConstZero(v.Args[1])
case ssaop.OpSliceMake:
return IsConstZero(v.Args[0]) && IsConstZero(v.Args[1]) && IsConstZero(v.Args[2])
case ssaop.OpStringPtr, ssaop.OpStringLen, ssaop.OpSlicePtr, ssaop.OpSliceLen, ssaop.OpSliceCap, ssaop.OpITab, ssaop.OpIData, ssaop.OpComplexReal, ssaop.OpComplexImag:
return IsConstZero(v.Args[0])
}
return false
}