blob: 01bad81250c8c9502c1c5f77310e88f5d7cb323a [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 rewritegeneric
import (
"fmt"
"math"
"math/bits"
"strings"
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/reflectdata"
"cmd/compile/internal/rttype"
"cmd/compile/internal/ssa"
"cmd/compile/internal/ssa/ssaop"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/objabi"
)
func addToSub(op ssaop.Op) ssaop.Op {
switch op {
case ssaop.OpAdd64:
return ssaop.OpSub64
case ssaop.OpAdd32:
return ssaop.OpSub32
case ssaop.OpAdd16:
return ssaop.OpSub16
case ssaop.OpAdd8:
return ssaop.OpSub8
default:
panic(fmt.Sprintf("unexpected op %v", op))
}
}
func bitsAdd64(x, y, carry int64) (r struct{ sum, carry int64 }) {
s, c := bits.Add64(uint64(x), uint64(y), uint64(carry))
r.sum, r.carry = int64(s), int64(c)
return
}
func bitsMulU32(x, y int32) (r struct{ hi, lo int32 }) {
hi, lo := bits.Mul32(uint32(x), uint32(y))
r.hi, r.lo = int32(hi), int32(lo)
return
}
func bitsMulU64(x, y int64) (r struct{ hi, lo int64 }) {
hi, lo := bits.Mul64(uint64(x), uint64(y))
r.hi, r.lo = int64(hi), int64(lo)
return
}
func bitsDiv128u(hi, lo, y int64) (r struct{ quo, rem int64 }) {
q, rem := bits.Div64(uint64(hi), uint64(lo), uint64(y))
r.quo, r.rem = int64(q), int64(rem)
return
}
// bool2int converts bool to int: true to 1, false to 0
func bool2int(x bool) int {
var b int
if x {
b = 1
}
return b
}
// canLoadUnaligned reports if the architecture supports unaligned load operations.
func canLoadUnaligned(c *ssa.Config) bool {
return c.Ctxt.Arch.Alignment == 1
}
// canRotate reports whether the architecture supports
// rotates of integer registers with the given number of bits.
func canRotate(c *ssa.Config, bits int64) bool {
if bits > c.PtrSize*8 {
// Don't rewrite to rotates bigger than the machine word.
return false
}
switch c.Arch {
case "386", "amd64", "arm64", "loong64", "riscv64":
return true
case "arm", "s390x", "ppc64", "ppc64le", "wasm":
return bits >= 32
default:
return false
}
}
func copyCompatibleType(t1, t2 *types.Type) bool {
if t1.Size() != t2.Size() {
return false
}
if t1.IsInteger() {
return t2.IsInteger()
}
if ssa.IsPtr(t1) {
return ssa.IsPtr(t2)
}
return t1.Compare(t2) == types.CMPeq
}
func devirtLECall(v *ssa.Value, sym *obj.LSym) *ssa.Value {
v.Op = ssaop.OpStaticLECall
auxcall := v.Aux.(*ssa.AuxCall)
auxcall.Fn = sym
// Remove first arg
v.Args[0].Uses--
copy(v.Args[0:], v.Args[1:])
v.Args[len(v.Args)-1] = nil // aid GC
v.Args = v.Args[:len(v.Args)-1]
if f := v.Block.Func; f.Pass.Debug > 0 {
f.Warnl(v.Pos, "de-virtualizing call")
}
return v
}
// hasSmallRotate reports whether the architecture has rotate instructions
// for sizes < 32-bit. This is used to decide whether to promote some rotations.
func hasSmallRotate(c *ssa.Config) bool {
switch c.Arch {
case "amd64", "386":
return true
default:
return false
}
}
func invertibleBool(op ssaop.Op) bool {
switch op {
case ssaop.OpLess64, ssaop.OpLess32, ssaop.OpLess16, ssaop.OpLess8,
ssaop.OpLeq64, ssaop.OpLeq32, ssaop.OpLeq16, ssaop.OpLeq8,
ssaop.OpLess64U, ssaop.OpLess32U, ssaop.OpLess16U, ssaop.OpLess8U,
ssaop.OpLeq64U, ssaop.OpLeq32U, ssaop.OpLeq16U, ssaop.OpLeq8U,
ssaop.OpEq64, ssaop.OpEq32, ssaop.OpEq16, ssaop.OpEq8,
ssaop.OpNeq64, ssaop.OpNeq32, ssaop.OpNeq16, ssaop.OpNeq8,
ssaop.OpNot:
return true
default:
return false
}
}
func isDictArgSym(sym ssa.Sym) bool {
return sym.(*ir.Name).Sym().Name == typecheck.LocalDictName
}
// isDirectAndComparableIface reports whether v represents an itab
// (a *runtime._itab) for a type whose value is stored directly
// in an interface (i.e., is pointer or pointer-like) and is comparable.
func isDirectAndComparableIface(v *ssa.Value) bool {
return isDirectAndComparableIface1(v, 9)
}
// v is an itab
func isDirectAndComparableIface1(v *ssa.Value, depth int) bool {
if depth == 0 {
return false
}
switch v.Op {
case ssaop.OpITab:
return isDirectAndComparableIface2(v.Args[0], depth-1)
case ssaop.OpAddr:
lsym := v.Aux.(*obj.LSym)
if ii := lsym.ItabInfo(); ii != nil {
t := ii.Type.(*types.Type)
return types.IsDirectIface(t) && types.IsComparable(t)
}
case ssaop.OpConstNil:
// We can treat this as direct, because if the itab is
// nil, the data field must be nil also.
return true
}
return false
}
// v is an interface
func isDirectAndComparableIface2(v *ssa.Value, depth int) bool {
if depth == 0 {
return false
}
switch v.Op {
case ssaop.OpIMake:
return isDirectAndComparableIface1(v.Args[0], depth-1)
case ssaop.OpPhi:
for _, a := range v.Args {
if !isDirectAndComparableIface2(a, depth-1) {
return false
}
}
return true
}
return false
}
// isDirectAndComparableType reports whether v represents a type
// (a *runtime._type) whose value is stored directly in an
// interface (i.e., is pointer or pointer-like) and is comparable.
func isDirectAndComparableType(v *ssa.Value) bool {
return isDirectAndComparableType1(v)
}
// v is a type
func isDirectAndComparableType1(v *ssa.Value) bool {
switch v.Op {
case ssaop.OpITab:
return isDirectAndComparableType2(v.Args[0])
case ssaop.OpAddr:
lsym := v.Aux.(*obj.LSym)
if ti := lsym.TypeInfo(); ti != nil {
t := ti.Type.(*types.Type)
return types.IsDirectIface(t) && types.IsComparable(t)
}
}
return false
}
// v is an empty interface
func isDirectAndComparableType2(v *ssa.Value) bool {
switch v.Op {
case ssaop.OpIMake:
return isDirectAndComparableType1(v.Args[0])
}
return false
}
// isFixedLoad returns true if the load can be resolved to fixed address or constant,
// and can be rewritten by rewriteFixedLoad.
func isFixedLoad(v *ssa.Value, sym ssa.Sym, off int64) bool {
lsym := sym.(*obj.LSym)
if (v.Type.IsPtrShaped() || v.Type.IsUintptr()) && lsym.Type == objabi.SRODATA {
for _, r := range lsym.R {
if (r.Type == objabi.R_ADDR || r.Type == objabi.R_WEAKADDR) && int64(r.Off) == off && r.Add == 0 {
return true
}
}
return false
}
if ti := lsym.TypeInfo(); ti != nil {
// Type symbols do not contain information about their fields, unlike the cases above.
// Hand-implement field accesses.
// TODO: can this be replaced with reflectdata.writeType and just use the code above?
t := ti.Type.(*types.Type)
for _, f := range rttype.Type.Fields() {
if f.Offset == off && copyCompatibleType(v.Type, f.Type) {
switch f.Sym.Name {
case "Size_", "PtrBytes", "Hash", "Kind_", "GCData", "TFlag":
return true
default:
// fmt.Println("unknown field", f.Sym.Name)
return false
}
}
}
if t.IsPtr() && off == rttype.PtrType.OffsetOf("Elem") {
return true
}
return false
}
return false
}
func isInlinableMemclr(c *ssa.Config, sz int64) bool {
if sz < 0 {
return false
}
// TODO: expand this check to allow other architectures
// see CL 454255 and issue 56997
switch c.Arch {
case "amd64", "arm64":
return true
case "ppc64le", "ppc64", "loong64":
return sz < 512
}
return false
}
func isMalloc(aux ssa.Aux) bool {
return ssa.IsNewObjectCall(aux) || ssa.IsSpecializedMalloc(aux)
}
// isNonNegative reports whether v is known to be greater or equal to zero.
// Note that this is pretty simplistic. The prove pass generates more detailed
// nonnegative information about values.
func isNonNegative(v *ssa.Value) bool {
if !v.Type.IsInteger() {
v.Fatalf("isNonNegative bad type: %v", v.Type)
}
// TODO: return true if !v.Type.IsSigned()
// SSA isn't type-safe enough to do that now (issue 37753).
// The checks below depend only on the pattern of bits.
switch v.Op {
case ssaop.OpConst64:
return v.AuxInt >= 0
case ssaop.OpConst32:
return int32(v.AuxInt) >= 0
case ssaop.OpConst16:
return int16(v.AuxInt) >= 0
case ssaop.OpConst8:
return int8(v.AuxInt) >= 0
case ssaop.OpStringLen, ssaop.OpSliceLen, ssaop.OpSliceCap,
ssaop.OpZeroExt8to64, ssaop.OpZeroExt16to64, ssaop.OpZeroExt32to64,
ssaop.OpZeroExt8to32, ssaop.OpZeroExt16to32, ssaop.OpZeroExt8to16,
ssaop.OpCtz64, ssaop.OpCtz32, ssaop.OpCtz16, ssaop.OpCtz8,
ssaop.OpCtz64NonZero, ssaop.OpCtz32NonZero, ssaop.OpCtz16NonZero, ssaop.OpCtz8NonZero,
ssaop.OpBitLen64, ssaop.OpBitLen32, ssaop.OpBitLen16, ssaop.OpBitLen8:
return true
case ssaop.OpRsh64Ux64, ssaop.OpRsh32Ux64:
by := v.Args[1]
return by.Op == ssaop.OpConst64 && by.AuxInt > 0
case ssaop.OpRsh64x64, ssaop.OpRsh32x64, ssaop.OpRsh8x64, ssaop.OpRsh16x64, ssaop.OpRsh32x32, ssaop.OpRsh64x32,
ssaop.OpSignExt32to64, ssaop.OpSignExt16to64, ssaop.OpSignExt8to64, ssaop.OpSignExt16to32, ssaop.OpSignExt8to32:
return isNonNegative(v.Args[0])
case ssaop.OpAnd64, ssaop.OpAnd32, ssaop.OpAnd16, ssaop.OpAnd8:
return isNonNegative(v.Args[0]) || isNonNegative(v.Args[1])
case ssaop.OpMod64, ssaop.OpMod32, ssaop.OpMod16, ssaop.OpMod8,
ssaop.OpDiv64, ssaop.OpDiv32, ssaop.OpDiv16, ssaop.OpDiv8,
ssaop.OpOr64, ssaop.OpOr32, ssaop.OpOr16, ssaop.OpOr8,
ssaop.OpXor64, ssaop.OpXor32, ssaop.OpXor16, ssaop.OpXor8:
return isNonNegative(v.Args[0]) && isNonNegative(v.Args[1])
// We could handle OpPhi here, but the improvements from doing
// so are very minor, and it is neither simple nor cheap.
}
return false
}
func isStackPtr(v *ssa.Value) bool {
for v.Op == ssaop.OpOffPtr || v.Op == ssaop.OpAddPtr {
v = v.Args[0]
}
return v.Op == ssaop.OpSP || v.Op == ssaop.OpLocalAddr
}
// needRaceCleanup reports whether this call to racefuncenter/exit isn't needed.
func needRaceCleanup(sym *ssa.AuxCall, v *ssa.Value) bool {
f := v.Block.Func
if !f.Config.Race {
return false
}
if !ssa.IsSameCall(sym, "runtime.racefuncenter") && !ssa.IsSameCall(sym, "runtime.racefuncexit") {
return false
}
for _, b := range f.Blocks {
for _, v := range b.Values {
switch v.Op {
case ssaop.OpStaticCall, ssaop.OpStaticLECall:
// Check for racefuncenter will encounter racefuncexit and vice versa.
// Allow calls to panic*
s := v.Aux.(*ssa.AuxCall).Fn.String()
switch s {
case "runtime.racefuncenter", "runtime.racefuncexit",
"runtime.panicdivide", "runtime.panicwrap",
"runtime.panicshift":
continue
}
// If we encountered any call, we need to keep racefunc*,
// for accurate stacktraces.
return false
case ssaop.OpPanicBounds, ssaop.OpPanicExtend:
// Note: these are panic generators that are ok (like the static calls above).
case ssaop.OpClosureCall, ssaop.OpInterCall, ssaop.OpClosureLECall, ssaop.OpInterLECall:
// We must keep the race functions if there are any other call types.
return false
}
}
}
if ssa.IsSameCall(sym, "runtime.racefuncenter") {
// TODO REGISTER ABI this needs to be cleaned up.
// If we're removing racefuncenter, remove its argument as well.
if v.Args[0].Op != ssaop.OpStore {
if v.Op == ssaop.OpStaticLECall {
// there is no store, yet.
return true
}
return false
}
mem := v.Args[0].Args[2]
v.Args[0].Reset(ssaop.OpCopy)
v.Args[0].AddArg(mem)
}
return true
}
func nlz16(x int16) int { return bits.LeadingZeros16(uint16(x)) }
func nlz32(x int32) int { return bits.LeadingZeros32(uint32(x)) }
// nlzX returns the number of leading zeros.
func nlz64(x int64) int { return bits.LeadingZeros64(uint64(x)) }
func nlz8(x int8) int { return bits.LeadingZeros8(uint8(x)) }
func ntz16(x int16) int { return bits.TrailingZeros16(uint16(x)) }
func ntz32(x int32) int { return bits.TrailingZeros32(uint32(x)) }
func ntz8(x int8) int { return bits.TrailingZeros8(uint8(x)) }
// reciprocalExact32 reports whether 1/c is exactly representable.
func reciprocalExact32(c float32) bool {
b := math.Float32bits(c)
man := b & (1<<23 - 1)
if man != 0 {
return false // not a power of 2, denormal, or NaN
}
exp := b >> 23 & (1<<8 - 1)
// exponent bias is 0x7f. So taking the reciprocal of a number
// changes the exponent to 0xfe-exp.
switch exp {
case 0:
return false // ±0
case 0xff:
return false // ±inf
case 0xfe:
return false // exponent is not representable
default:
return true
}
}
// reciprocalExact64 reports whether 1/c is exactly representable.
func reciprocalExact64(c float64) bool {
b := math.Float64bits(c)
man := b & (1<<52 - 1)
if man != 0 {
return false // not a power of 2, denormal, or NaN
}
exp := b >> 52 & (1<<11 - 1)
// exponent bias is 0x3ff. So taking the reciprocal of a number
// changes the exponent to 0x7fe-exp.
switch exp {
case 0:
return false // ±0
case 0x7ff:
return false // ±inf
case 0x7fe:
return false // exponent is not representable
default:
return true
}
}
// registerizable reports whether t is a primitive type that fits in
// a register. It assumes float64 values will always fit into registers
// even if that isn't strictly true.
func registerizable(b *ssa.Block, typ *types.Type) bool {
if typ.IsPtrShaped() || typ.IsFloat() || typ.IsBoolean() {
return true
}
if typ.IsInteger() {
return typ.Size() <= b.Func.Config.RegSize
}
return false
}
// resetCopy resets v to be a copy of arg.
// Always returns true.
func resetCopy(v *ssa.Value, arg *ssa.Value) bool {
v.Reset(ssaop.OpCopy)
v.AddArg(arg)
return true
}
// rewriteCondSelectIntoMath reports whether x OP (y * constant) should be used instead of a CondSelect.
// x arbitrary, y in [0,1]
func rewriteCondSelectIntoMath(config *ssa.Config, op ssaop.Op, constant int64) bool {
// at worst this becomes a left shift by a constant which has asymmetric latency (1:3 vs 2:2)
// but performs better in accumulation chains.
// Various arches do strictly superior for specific cases, but this is a good general default.
// FIXME: optimize more constants in arches where this is possible.
switch config.Arch {
case "arm64":
switch op {
case ssaop.OpAdd64, ssaop.OpAdd32, ssaop.OpAdd16, ssaop.OpAdd8:
if constant == 1 {
return false // better done as CSINC
}
fallthrough
default:
// add sub or xor & and are implemented using inline LSL
// the rest becomes the default LSL
return ssa.IsPowerOfTwo(uint64(constant))
}
default:
return ssa.IsPowerOfTwo(uint64(constant))
}
}
// rewriteFixedLoad rewrites a load to a fixed address or constant, if isFixedLoad returns true.
func rewriteFixedLoad(v *ssa.Value, sym ssa.Sym, sb *ssa.Value, off int64) *ssa.Value {
b := v.Block
f := b.Func
lsym := sym.(*obj.LSym)
if (v.Type.IsPtrShaped() || v.Type.IsUintptr()) && lsym.Type == objabi.SRODATA {
for _, r := range lsym.R {
if (r.Type == objabi.R_ADDR || r.Type == objabi.R_WEAKADDR) && int64(r.Off) == off && r.Add == 0 {
if strings.HasPrefix(r.Sym.Name, "type:") {
// In case we're loading a type out of a dictionary, we need to record
// that the containing function might put that type in an interface.
// That information is currently recorded in relocations in the dictionary,
// but if we perform this load at compile time then the dictionary
// might be dead.
reflectdata.MarkTypeSymUsedInInterface(r.Sym, f.Fe.Func().Linksym())
} else if strings.HasPrefix(r.Sym.Name, "go:itab") {
// Same, but if we're using an itab we need to record that the
// itab._type might be put in an interface.
reflectdata.MarkTypeSymUsedInInterface(r.Sym, f.Fe.Func().Linksym())
}
v.Reset(ssaop.OpAddr)
v.Aux = ssa.SymToAux(r.Sym)
v.AddArg(sb)
return v
}
}
base.Fatalf("fixedLoad data not known for %s:%d", sym, off)
}
if ti := lsym.TypeInfo(); ti != nil {
// Type symbols do not contain information about their fields, unlike the cases above.
// Hand-implement field accesses.
// TODO: can this be replaced with reflectdata.writeType and just use the code above?
t := ti.Type.(*types.Type)
ptrSizedOpConst := ssaop.OpConst64
if f.Config.PtrSize == 4 {
ptrSizedOpConst = ssaop.OpConst32
}
for _, f := range rttype.Type.Fields() {
if f.Offset == off && copyCompatibleType(v.Type, f.Type) {
switch f.Sym.Name {
case "Size_":
v.Reset(ptrSizedOpConst)
v.AuxInt = t.Size()
return v
case "PtrBytes":
v.Reset(ptrSizedOpConst)
v.AuxInt = types.PtrDataSize(t)
return v
case "Hash":
v.Reset(ssaop.OpConst32)
v.AuxInt = int64(int32(types.TypeHash(t)))
return v
case "TFlag":
v.Reset(ssaop.OpConst8)
v.AuxInt = int64(t.TFlag())
return v
case "Kind_":
v.Reset(ssaop.OpConst8)
v.AuxInt = int64(int8(reflectdata.ABIKindOfType(t)))
return v
case "GCData":
gcdata, _ := reflectdata.GCSym(t, true)
v.Reset(ssaop.OpAddr)
v.Aux = ssa.SymToAux(gcdata)
v.AddArg(sb)
return v
default:
base.Fatalf("unknown field %s for fixedLoad of %s at offset %d", f.Sym.Name, lsym.Name, off)
}
}
}
if t.IsPtr() && off == rttype.PtrType.OffsetOf("Elem") {
elemSym := reflectdata.TypeLinksym(t.Elem())
reflectdata.MarkTypeSymUsedInInterface(elemSym, f.Fe.Func().Linksym())
v.Reset(ssaop.OpAddr)
v.Aux = ssa.SymToAux(elemSym)
v.AddArg(sb)
return v
}
base.Fatalf("fixedLoad data not known for %s:%d", sym, off)
}
base.Fatalf("fixedLoad data not known for %s:%d", sym, off)
return nil
}
func rewriteStructLoad(v *ssa.Value) *ssa.Value {
b := v.Block
ptr := v.Args[0]
mem := v.Args[1]
t := v.Type
args := make([]*ssa.Value, t.NumFields())
for i := range args {
ft := t.FieldType(i)
addr := b.NewValue1I(v.Pos, ssaop.OpOffPtr, ft.PtrTo(), t.FieldOff(i), ptr)
args[i] = b.NewValue2(v.Pos, ssaop.OpLoad, ft, addr, mem)
}
v.Reset(ssaop.OpStructMake)
v.AddArgs(args...)
return v
}
// symIsROZero reports whether sym is a read-only global whose data contains all zeros.
func symIsROZero(sym ssa.Sym) bool {
lsym := sym.(*obj.LSym)
if lsym.Type != objabi.SRODATA || len(lsym.R) != 0 {
return false
}
for _, b := range lsym.P {
if b != 0 {
return false
}
}
return true
}
// uaddOvf reports whether unsigned a+b would overflow.
func uaddOvf(a, b int64) bool {
return uint64(a)+uint64(b) < uint64(a)
}
// warnRule generates compiler debug output with string s when
// v is not in autogenerated code, cond is true and the rule has fired.
func warnRule(cond bool, v *ssa.Value, s string) bool {
if pos := v.Pos; pos.Line() > 1 && cond {
v.Block.Func.Warnl(pos, s)
}
return true
}
func bitsSub64(x, y, borrow int64) (r struct{ diff, borrow int64 }) {
d, b := bits.Sub64(uint64(x), uint64(y), uint64(borrow))
r.diff, r.borrow = int64(d), int64(b)
return
}
func modularMultiplicativeInverse(x uint64) (y uint64) {
if x%2 != 1 {
panic("even numbers in a power-of-two modulus do not have a multiplicative inverse")
}
// we start with 3 bits of precision because each odd number is its own multiplicative inverse mod 8
y = x // 3 bits
// now use the Newton-Raphson method to double the number of correct bits in each iteration.
y *= 2 - x*y // 6 bits
y *= 2 - x*y // 12 bits
y *= 2 - x*y // 24 bits
y *= 2 - x*y // 48 bits
y *= 2 - x*y // 96 bits; good enough
return
}