blob: c2645e369ff34aabd5bf1db5e34079421e2f1092 [file]
// Copyright 2016 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 (
"fmt"
"cmd/compile/internal/ssa/ssaop"
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/objabi"
)
// IsNewObject reports whether v is a pointer to a freshly allocated & zeroed object,
// if so, also returns the memory state mem at which v is zero.
func IsNewObject(v *Value, select1 []*Value) (mem *Value, ok bool) {
f := v.Block.Func
c := f.Config
if f.ABIDefault == f.ABI1 && len(c.IntParamRegs) >= 1 {
if v.Op != ssaop.OpSelectN || v.AuxInt != 0 {
return nil, false
}
mem = select1[v.Args[0].ID]
if mem == nil {
return nil, false
}
} else {
if v.Op != ssaop.OpLoad {
return nil, false
}
mem = v.MemoryArg()
if mem.Op != ssaop.OpSelectN {
return nil, false
}
if mem.Type != types.TypeMem {
return nil, false
} // assume it is the right selection if true
}
call := mem.Args[0]
if call.Op != ssaop.OpStaticCall {
return nil, false
}
// Check for new object, or for new object calls that have been transformed into size-specialized malloc calls.
// Calls that have return type unsafe pointer may have originally been produced by flushPendingHeapAllocations
// in the ssa generator, so may have not originally been newObject calls.
var numParameters int64
switch {
case IsNewObjectCall(call.Aux):
numParameters = 1
case IsSpecializedMalloc(call.Aux) && !v.Type.IsUnsafePtr():
numParameters = 3
default:
return nil, false
}
if f.ABIDefault == f.ABI1 && len(c.IntParamRegs) >= 1 {
if v.Args[0] == call {
return mem, true
}
return nil, false
}
if v.Args[0].Op != ssaop.OpOffPtr {
return nil, false
}
if v.Args[0].Args[0].Op != ssaop.OpSP {
return nil, false
}
if v.Args[0].AuxInt != c.Ctxt.Arch.FixedFrameSize+numParameters*c.RegSize { // offset of return value
return nil, false
}
return mem, true
}
// A ZeroRegion records parts of an object which are known to be zero.
// A ZeroRegion only applies to a single memory state.
// Each bit in mask is set if the corresponding pointer-sized word of
// the base object is known to be zero.
// In other words, if mask & (1<<i) != 0, then [base+i*ptrSize, base+(i+1)*ptrSize)
// is known to be zero.
type ZeroRegion struct {
Base *Value
Mask uint64
}
// IsStackAddr reports whether v is known to be an address of a stack slot.
func IsStackAddr(v *Value) bool {
for v.Op == ssaop.OpOffPtr || v.Op == ssaop.OpAddPtr || v.Op == ssaop.OpPtrIndex || v.Op == ssaop.OpCopy {
v = v.Args[0]
}
switch v.Op {
case ssaop.OpSP, ssaop.OpLocalAddr, ssaop.OpSelectNAddr, ssaop.OpGetCallerSP:
return true
}
return false
}
// IsSanitizerSafeAddr reports whether v is known to be an address
// that doesn't need instrumentation.
func IsSanitizerSafeAddr(v *Value) bool {
for v.Op == ssaop.OpOffPtr || v.Op == ssaop.OpAddPtr || v.Op == ssaop.OpPtrIndex || v.Op == ssaop.OpCopy {
v = v.Args[0]
}
switch v.Op {
case ssaop.OpSP, ssaop.OpLocalAddr, ssaop.OpSelectNAddr:
// Stack addresses are always safe.
return true
case ssaop.OpITab, ssaop.OpStringPtr, ssaop.OpGetClosurePtr:
// Itabs, string data, and closure fields are
// read-only once initialized.
return true
case ssaop.OpAddr:
vt := v.Aux.(*obj.LSym).Type
return vt == objabi.SRODATA || vt == objabi.SLIBFUZZER_8BIT_COUNTER || vt == objabi.SCOVERAGE_COUNTER || vt == objabi.SCOVERAGE_AUXVAR
}
return false
}
// ComputeZeroMap returns a map from an ID of a memory value to
// a set of locations that are known to be zeroed at that memory value.
func (f *Func) ComputeZeroMap(select1 []*Value) map[ID]ZeroRegion {
ptrSize := f.Config.PtrSize
// Keep track of which parts of memory are known to be zero.
// This helps with removing write barriers for various initialization patterns.
// This analysis is conservative. We only keep track, for each memory state, of
// which of the first 64 words of a single object are known to be zero.
zeroes := map[ID]ZeroRegion{}
// Find new objects.
for _, b := range f.Blocks {
for _, v := range b.Values {
if mem, ok := IsNewObject(v, select1); ok {
// While compiling package runtime itself, we might see user
// calls to newobject, which will have result type
// unsafe.Pointer instead. We can't easily infer how large the
// allocated memory is, so just skip it.
if types.LocalPkg.Path == "runtime" && v.Type.IsUnsafePtr() {
continue
}
nptr := min(64, v.Type.Elem().Size()/ptrSize)
zeroes[mem.ID] = ZeroRegion{Base: v, Mask: 1<<uint(nptr) - 1}
}
}
}
// Find stores to those new objects.
for {
changed := false
for _, b := range f.Blocks {
// Note: iterating forwards helps convergence, as values are
// typically (but not always!) in store order.
for _, v := range b.Values {
if v.Op != ssaop.OpStore {
continue
}
z, ok := zeroes[v.MemoryArg().ID]
if !ok {
continue
}
ptr := v.Args[0]
var off int64
size := v.Aux.(*types.Type).Size()
for ptr.Op == ssaop.OpOffPtr {
off += ptr.AuxInt
ptr = ptr.Args[0]
}
if ptr != z.Base {
// Different base object - we don't know anything.
// We could even be writing to the base object we know
// about, but through an aliased but offset pointer.
// So we have to throw all the zero information we have away.
continue
}
// Round to cover any partially written pointer slots.
// Pointer writes should never be unaligned like this, but non-pointer
// writes to pointer-containing types will do this.
if d := off % ptrSize; d != 0 {
off -= d
size += d
}
if d := size % ptrSize; d != 0 {
size += ptrSize - d
}
// Clip to the 64 words that we track.
minimum := max(off, 0)
maximum := min(off+size, 64*ptrSize)
// Clear bits for parts that we are writing (and hence
// will no longer necessarily be zero).
for i := minimum; i < maximum; i += ptrSize {
bit := i / ptrSize
z.Mask &^= 1 << uint(bit)
}
if z.Mask == 0 {
// No more known zeros - don't bother keeping.
continue
}
// Save updated known zero contents for new store.
if zeroes[v.ID] != z {
zeroes[v.ID] = z
changed = true
}
}
}
if !changed {
break
}
}
if f.Pass.Debug > 0 {
fmt.Printf("func %s\n", f.Name)
for mem, z := range zeroes {
fmt.Printf(" memory=v%d ptr=%v zeromask=%b\n", mem, z.Base, z.Mask)
}
}
return zeroes
}