mirror of
https://github.com/golang/go.git
synced 2025-05-24 00:41:24 +00:00
Now that the write barrier insertion is moved to SSA, the SSA building code can be simplified. Updates #17583. Change-Id: I5cacc034b11aa90b0abe6f8dd97e4e3994e2bc25 Reviewed-on: https://go-review.googlesource.com/36840 Run-TryBot: Cherry Zhang <cherryyz@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org>
443 lines
14 KiB
Go
443 lines
14 KiB
Go
// 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 (
|
|
"cmd/internal/obj"
|
|
"cmd/internal/src"
|
|
"crypto/sha1"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// A Config holds readonly compilation information.
|
|
// It is created once, early during compilation,
|
|
// and shared across all compilations.
|
|
type Config struct {
|
|
arch string // "amd64", etc.
|
|
IntSize int64 // 4 or 8
|
|
PtrSize int64 // 4 or 8
|
|
RegSize int64 // 4 or 8
|
|
lowerBlock func(*Block, *Config) bool // lowering function
|
|
lowerValue func(*Value, *Config) bool // lowering function
|
|
registers []Register // machine registers
|
|
gpRegMask regMask // general purpose integer register mask
|
|
fpRegMask regMask // floating point register mask
|
|
specialRegMask regMask // special register mask
|
|
FPReg int8 // register number of frame pointer, -1 if not used
|
|
LinkReg int8 // register number of link register if it is a general purpose register, -1 if not used
|
|
hasGReg bool // has hardware g register
|
|
fe Frontend // callbacks into compiler frontend
|
|
HTML *HTMLWriter // html writer, for debugging
|
|
ctxt *obj.Link // Generic arch information
|
|
optimize bool // Do optimization
|
|
noDuffDevice bool // Don't use Duff's device
|
|
nacl bool // GOOS=nacl
|
|
use387 bool // GO386=387
|
|
OldArch bool // True for older versions of architecture, e.g. true for PPC64BE, false for PPC64LE
|
|
NeedsFpScratch bool // No direct move between GP and FP register sets
|
|
BigEndian bool //
|
|
DebugTest bool // default true unless $GOSSAHASH != ""; as a debugging aid, make new code conditional on this and use GOSSAHASH to binary search for failing cases
|
|
sparsePhiCutoff uint64 // Sparse phi location algorithm used above this #blocks*#variables score
|
|
curFunc *Func
|
|
|
|
// TODO: more stuff. Compiler flags of interest, ...
|
|
|
|
// Given an environment variable used for debug hash match,
|
|
// what file (if any) receives the yes/no logging?
|
|
logfiles map[string]*os.File
|
|
|
|
// Storage for low-numbered values and blocks.
|
|
values [2000]Value
|
|
blocks [200]Block
|
|
locs [2000]Location
|
|
|
|
// Reusable stackAllocState.
|
|
// See stackalloc.go's {new,put}StackAllocState.
|
|
stackAllocState *stackAllocState
|
|
|
|
domblockstore []ID // scratch space for computing dominators
|
|
scrSparse []*sparseSet // scratch sparse sets to be re-used.
|
|
}
|
|
|
|
type TypeSource interface {
|
|
TypeBool() Type
|
|
TypeInt8() Type
|
|
TypeInt16() Type
|
|
TypeInt32() Type
|
|
TypeInt64() Type
|
|
TypeUInt8() Type
|
|
TypeUInt16() Type
|
|
TypeUInt32() Type
|
|
TypeUInt64() Type
|
|
TypeInt() Type
|
|
TypeFloat32() Type
|
|
TypeFloat64() Type
|
|
TypeUintptr() Type
|
|
TypeString() Type
|
|
TypeBytePtr() Type // TODO: use unsafe.Pointer instead?
|
|
|
|
CanSSA(t Type) bool
|
|
}
|
|
|
|
type Logger interface {
|
|
// Logf logs a message from the compiler.
|
|
Logf(string, ...interface{})
|
|
|
|
// Log returns true if logging is not a no-op
|
|
// some logging calls account for more than a few heap allocations.
|
|
Log() bool
|
|
|
|
// Fatal reports a compiler error and exits.
|
|
Fatalf(pos src.XPos, msg string, args ...interface{})
|
|
|
|
// Error reports a compiler error but keep going.
|
|
Error(pos src.XPos, msg string, args ...interface{})
|
|
|
|
// Warnl writes compiler messages in the form expected by "errorcheck" tests
|
|
Warnl(pos src.XPos, fmt_ string, args ...interface{})
|
|
|
|
// Forwards the Debug flags from gc
|
|
Debug_checknil() bool
|
|
Debug_wb() bool
|
|
}
|
|
|
|
type Frontend interface {
|
|
TypeSource
|
|
Logger
|
|
|
|
// StringData returns a symbol pointing to the given string's contents.
|
|
StringData(string) interface{} // returns *gc.Sym
|
|
|
|
// Auto returns a Node for an auto variable of the given type.
|
|
// The SSA compiler uses this function to allocate space for spills.
|
|
Auto(Type) GCNode
|
|
|
|
// Given the name for a compound type, returns the name we should use
|
|
// for the parts of that compound type.
|
|
SplitString(LocalSlot) (LocalSlot, LocalSlot)
|
|
SplitInterface(LocalSlot) (LocalSlot, LocalSlot)
|
|
SplitSlice(LocalSlot) (LocalSlot, LocalSlot, LocalSlot)
|
|
SplitComplex(LocalSlot) (LocalSlot, LocalSlot)
|
|
SplitStruct(LocalSlot, int) LocalSlot
|
|
SplitArray(LocalSlot) LocalSlot // array must be length 1
|
|
SplitInt64(LocalSlot) (LocalSlot, LocalSlot) // returns (hi, lo)
|
|
|
|
// DerefItab dereferences an itab function
|
|
// entry, given the symbol of the itab and
|
|
// the byte offset of the function pointer.
|
|
// It may return nil.
|
|
DerefItab(sym *obj.LSym, offset int64) *obj.LSym
|
|
|
|
// Line returns a string describing the given position.
|
|
Line(src.XPos) string
|
|
|
|
// AllocFrame assigns frame offsets to all live auto variables.
|
|
AllocFrame(f *Func)
|
|
|
|
// Syslook returns a symbol of the runtime function/variable with the
|
|
// given name.
|
|
Syslook(string) *obj.LSym
|
|
|
|
// UseWriteBarrier returns whether write barrier is enabled
|
|
UseWriteBarrier() bool
|
|
}
|
|
|
|
// interface used to hold *gc.Node. We'd use *gc.Node directly but
|
|
// that would lead to an import cycle.
|
|
type GCNode interface {
|
|
Typ() Type
|
|
String() string
|
|
}
|
|
|
|
// NewConfig returns a new configuration object for the given architecture.
|
|
func NewConfig(arch string, fe Frontend, ctxt *obj.Link, optimize bool) *Config {
|
|
c := &Config{arch: arch, fe: fe}
|
|
switch arch {
|
|
case "amd64":
|
|
c.IntSize = 8
|
|
c.PtrSize = 8
|
|
c.RegSize = 8
|
|
c.lowerBlock = rewriteBlockAMD64
|
|
c.lowerValue = rewriteValueAMD64
|
|
c.registers = registersAMD64[:]
|
|
c.gpRegMask = gpRegMaskAMD64
|
|
c.fpRegMask = fpRegMaskAMD64
|
|
c.FPReg = framepointerRegAMD64
|
|
c.LinkReg = linkRegAMD64
|
|
c.hasGReg = false
|
|
case "amd64p32":
|
|
c.IntSize = 4
|
|
c.PtrSize = 4
|
|
c.RegSize = 8
|
|
c.lowerBlock = rewriteBlockAMD64
|
|
c.lowerValue = rewriteValueAMD64
|
|
c.registers = registersAMD64[:]
|
|
c.gpRegMask = gpRegMaskAMD64
|
|
c.fpRegMask = fpRegMaskAMD64
|
|
c.FPReg = framepointerRegAMD64
|
|
c.LinkReg = linkRegAMD64
|
|
c.hasGReg = false
|
|
c.noDuffDevice = true
|
|
case "386":
|
|
c.IntSize = 4
|
|
c.PtrSize = 4
|
|
c.RegSize = 4
|
|
c.lowerBlock = rewriteBlock386
|
|
c.lowerValue = rewriteValue386
|
|
c.registers = registers386[:]
|
|
c.gpRegMask = gpRegMask386
|
|
c.fpRegMask = fpRegMask386
|
|
c.FPReg = framepointerReg386
|
|
c.LinkReg = linkReg386
|
|
c.hasGReg = false
|
|
case "arm":
|
|
c.IntSize = 4
|
|
c.PtrSize = 4
|
|
c.RegSize = 4
|
|
c.lowerBlock = rewriteBlockARM
|
|
c.lowerValue = rewriteValueARM
|
|
c.registers = registersARM[:]
|
|
c.gpRegMask = gpRegMaskARM
|
|
c.fpRegMask = fpRegMaskARM
|
|
c.FPReg = framepointerRegARM
|
|
c.LinkReg = linkRegARM
|
|
c.hasGReg = true
|
|
case "arm64":
|
|
c.IntSize = 8
|
|
c.PtrSize = 8
|
|
c.RegSize = 8
|
|
c.lowerBlock = rewriteBlockARM64
|
|
c.lowerValue = rewriteValueARM64
|
|
c.registers = registersARM64[:]
|
|
c.gpRegMask = gpRegMaskARM64
|
|
c.fpRegMask = fpRegMaskARM64
|
|
c.FPReg = framepointerRegARM64
|
|
c.LinkReg = linkRegARM64
|
|
c.hasGReg = true
|
|
c.noDuffDevice = obj.GOOS == "darwin" // darwin linker cannot handle BR26 reloc with non-zero addend
|
|
case "ppc64":
|
|
c.OldArch = true
|
|
c.BigEndian = true
|
|
fallthrough
|
|
case "ppc64le":
|
|
c.IntSize = 8
|
|
c.PtrSize = 8
|
|
c.RegSize = 8
|
|
c.lowerBlock = rewriteBlockPPC64
|
|
c.lowerValue = rewriteValuePPC64
|
|
c.registers = registersPPC64[:]
|
|
c.gpRegMask = gpRegMaskPPC64
|
|
c.fpRegMask = fpRegMaskPPC64
|
|
c.FPReg = framepointerRegPPC64
|
|
c.LinkReg = linkRegPPC64
|
|
c.noDuffDevice = true // TODO: Resolve PPC64 DuffDevice (has zero, but not copy)
|
|
c.hasGReg = true
|
|
case "mips64":
|
|
c.BigEndian = true
|
|
fallthrough
|
|
case "mips64le":
|
|
c.IntSize = 8
|
|
c.PtrSize = 8
|
|
c.RegSize = 8
|
|
c.lowerBlock = rewriteBlockMIPS64
|
|
c.lowerValue = rewriteValueMIPS64
|
|
c.registers = registersMIPS64[:]
|
|
c.gpRegMask = gpRegMaskMIPS64
|
|
c.fpRegMask = fpRegMaskMIPS64
|
|
c.specialRegMask = specialRegMaskMIPS64
|
|
c.FPReg = framepointerRegMIPS64
|
|
c.LinkReg = linkRegMIPS64
|
|
c.hasGReg = true
|
|
case "s390x":
|
|
c.IntSize = 8
|
|
c.PtrSize = 8
|
|
c.RegSize = 8
|
|
c.lowerBlock = rewriteBlockS390X
|
|
c.lowerValue = rewriteValueS390X
|
|
c.registers = registersS390X[:]
|
|
c.gpRegMask = gpRegMaskS390X
|
|
c.fpRegMask = fpRegMaskS390X
|
|
c.FPReg = framepointerRegS390X
|
|
c.LinkReg = linkRegS390X
|
|
c.hasGReg = true
|
|
c.noDuffDevice = true
|
|
c.BigEndian = true
|
|
case "mips":
|
|
c.BigEndian = true
|
|
fallthrough
|
|
case "mipsle":
|
|
c.IntSize = 4
|
|
c.PtrSize = 4
|
|
c.RegSize = 4
|
|
c.lowerBlock = rewriteBlockMIPS
|
|
c.lowerValue = rewriteValueMIPS
|
|
c.registers = registersMIPS[:]
|
|
c.gpRegMask = gpRegMaskMIPS
|
|
c.fpRegMask = fpRegMaskMIPS
|
|
c.specialRegMask = specialRegMaskMIPS
|
|
c.FPReg = framepointerRegMIPS
|
|
c.LinkReg = linkRegMIPS
|
|
c.hasGReg = true
|
|
c.noDuffDevice = true
|
|
default:
|
|
fe.Fatalf(src.NoXPos, "arch %s not implemented", arch)
|
|
}
|
|
c.ctxt = ctxt
|
|
c.optimize = optimize
|
|
c.nacl = obj.GOOS == "nacl"
|
|
|
|
// Don't use Duff's device on Plan 9 AMD64, because floating
|
|
// point operations are not allowed in note handler.
|
|
if obj.GOOS == "plan9" && arch == "amd64" {
|
|
c.noDuffDevice = true
|
|
}
|
|
|
|
if c.nacl {
|
|
c.noDuffDevice = true // Don't use Duff's device on NaCl
|
|
|
|
// runtime call clobber R12 on nacl
|
|
opcodeTable[OpARMCALLudiv].reg.clobbers |= 1 << 12 // R12
|
|
}
|
|
|
|
// Assign IDs to preallocated values/blocks.
|
|
for i := range c.values {
|
|
c.values[i].ID = ID(i)
|
|
}
|
|
for i := range c.blocks {
|
|
c.blocks[i].ID = ID(i)
|
|
}
|
|
|
|
c.logfiles = make(map[string]*os.File)
|
|
|
|
// cutoff is compared with product of numblocks and numvalues,
|
|
// if product is smaller than cutoff, use old non-sparse method.
|
|
// cutoff == 0 implies all sparse.
|
|
// cutoff == -1 implies none sparse.
|
|
// Good cutoff values seem to be O(million) depending on constant factor cost of sparse.
|
|
// TODO: get this from a flag, not an environment variable
|
|
c.sparsePhiCutoff = 2500000 // 0 for testing. // 2500000 determined with crude experiments w/ make.bash
|
|
ev := os.Getenv("GO_SSA_PHI_LOC_CUTOFF")
|
|
if ev != "" {
|
|
v, err := strconv.ParseInt(ev, 10, 64)
|
|
if err != nil {
|
|
fe.Fatalf(src.NoXPos, "Environment variable GO_SSA_PHI_LOC_CUTOFF (value '%s') did not parse as a number", ev)
|
|
}
|
|
c.sparsePhiCutoff = uint64(v) // convert -1 to maxint, for never use sparse
|
|
}
|
|
|
|
return c
|
|
}
|
|
|
|
func (c *Config) Set387(b bool) {
|
|
c.NeedsFpScratch = b
|
|
c.use387 = b
|
|
}
|
|
|
|
func (c *Config) Frontend() Frontend { return c.fe }
|
|
func (c *Config) SparsePhiCutoff() uint64 { return c.sparsePhiCutoff }
|
|
func (c *Config) Ctxt() *obj.Link { return c.ctxt }
|
|
|
|
// NewFunc returns a new, empty function object.
|
|
// Caller must call f.Free() before calling NewFunc again.
|
|
func (c *Config) NewFunc() *Func {
|
|
// TODO(khr): should this function take name, type, etc. as arguments?
|
|
if c.curFunc != nil {
|
|
c.Fatalf(src.NoXPos, "NewFunc called without previous Free")
|
|
}
|
|
f := &Func{Config: c, NamedValues: map[LocalSlot][]*Value{}}
|
|
c.curFunc = f
|
|
return f
|
|
}
|
|
|
|
func (c *Config) Logf(msg string, args ...interface{}) { c.fe.Logf(msg, args...) }
|
|
func (c *Config) Log() bool { return c.fe.Log() }
|
|
func (c *Config) Fatalf(pos src.XPos, msg string, args ...interface{}) { c.fe.Fatalf(pos, msg, args...) }
|
|
func (c *Config) Error(pos src.XPos, msg string, args ...interface{}) { c.fe.Error(pos, msg, args...) }
|
|
func (c *Config) Warnl(pos src.XPos, msg string, args ...interface{}) { c.fe.Warnl(pos, msg, args...) }
|
|
func (c *Config) Debug_checknil() bool { return c.fe.Debug_checknil() }
|
|
func (c *Config) Debug_wb() bool { return c.fe.Debug_wb() }
|
|
|
|
func (c *Config) logDebugHashMatch(evname, name string) {
|
|
file := c.logfiles[evname]
|
|
if file == nil {
|
|
file = os.Stdout
|
|
tmpfile := os.Getenv("GSHS_LOGFILE")
|
|
if tmpfile != "" {
|
|
var ok error
|
|
file, ok = os.Create(tmpfile)
|
|
if ok != nil {
|
|
c.Fatalf(src.NoXPos, "Could not open hash-testing logfile %s", tmpfile)
|
|
}
|
|
}
|
|
c.logfiles[evname] = file
|
|
}
|
|
s := fmt.Sprintf("%s triggered %s\n", evname, name)
|
|
file.WriteString(s)
|
|
file.Sync()
|
|
}
|
|
|
|
// DebugHashMatch returns true if environment variable evname
|
|
// 1) is empty (this is a special more-quickly implemented case of 3)
|
|
// 2) is "y" or "Y"
|
|
// 3) is a suffix of the sha1 hash of name
|
|
// 4) is a suffix of the environment variable
|
|
// fmt.Sprintf("%s%d", evname, n)
|
|
// provided that all such variables are nonempty for 0 <= i <= n
|
|
// Otherwise it returns false.
|
|
// When true is returned the message
|
|
// "%s triggered %s\n", evname, name
|
|
// is printed on the file named in environment variable
|
|
// GSHS_LOGFILE
|
|
// or standard out if that is empty or there is an error
|
|
// opening the file.
|
|
|
|
func (c *Config) DebugHashMatch(evname, name string) bool {
|
|
evhash := os.Getenv(evname)
|
|
if evhash == "" {
|
|
return true // default behavior with no EV is "on"
|
|
}
|
|
if evhash == "y" || evhash == "Y" {
|
|
c.logDebugHashMatch(evname, name)
|
|
return true
|
|
}
|
|
if evhash == "n" || evhash == "N" {
|
|
return false
|
|
}
|
|
// Check the hash of the name against a partial input hash.
|
|
// We use this feature to do a binary search to
|
|
// find a function that is incorrectly compiled.
|
|
hstr := ""
|
|
for _, b := range sha1.Sum([]byte(name)) {
|
|
hstr += fmt.Sprintf("%08b", b)
|
|
}
|
|
|
|
if strings.HasSuffix(hstr, evhash) {
|
|
c.logDebugHashMatch(evname, name)
|
|
return true
|
|
}
|
|
|
|
// Iteratively try additional hashes to allow tests for multi-point
|
|
// failure.
|
|
for i := 0; true; i++ {
|
|
ev := fmt.Sprintf("%s%d", evname, i)
|
|
evv := os.Getenv(ev)
|
|
if evv == "" {
|
|
break
|
|
}
|
|
if strings.HasSuffix(hstr, evv) {
|
|
c.logDebugHashMatch(ev, name)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *Config) DebugNameMatch(evname, name string) bool {
|
|
return os.Getenv(evname) == name
|
|
}
|