cmd/compile: use zero register instead of specialized *zero instructions

This lets us get rid of lots of specialized opcodes for storing zero.
Instead, use regular store opcodes that just happen to use the zero
register as one of their inputs.

Change-Id: I2902a6f9b0831cb598df45189ca6bb57221bef72
Reviewed-on: https://go-review.googlesource.com/c/go/+/633075
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@google.com>
This commit is contained in:
khr@golang.org 2024-11-24 15:29:56 -08:00 committed by Keith Randall
parent 2d050e91a3
commit e373771490
12 changed files with 795 additions and 2348 deletions

View file

@ -181,6 +181,10 @@ func (m regMask) String() string {
return s
}
func (m regMask) contains(r register) bool {
return m>>r&1 != 0
}
func (s *regAllocState) RegMaskString(m regMask) string {
str := ""
for r := register(0); m != 0; r++ {
@ -247,6 +251,7 @@ type regAllocState struct {
SPReg register
SBReg register
GReg register
ZeroIntReg register
allocatable regMask
// live values at the end of each block. live[b.ID] is a list of value IDs
@ -269,7 +274,8 @@ type regAllocState struct {
// preregalloc Value it was derived from.
orig []*Value
// current state of each register
// current state of each register.
// Includes only registers in allocatable.
regs []regState
// registers that contain values which can't be kicked out
@ -348,6 +354,9 @@ type startReg struct {
// freeReg frees up register r. Any current user of r is kicked out.
func (s *regAllocState) freeReg(r register) {
if !s.allocatable.contains(r) && !s.isGReg(r) {
return
}
v := s.regs[r].v
if v == nil {
s.f.Fatalf("tried to free an already free register %d\n", r)
@ -404,15 +413,19 @@ func (s *regAllocState) assignReg(r register, v *Value, c *Value) {
if s.f.pass.debug > regDebug {
fmt.Printf("assignReg %s %s/%s\n", &s.registers[r], v, c)
}
// Allocate v to r.
s.values[v.ID].regs |= regMask(1) << r
s.f.setHome(c, &s.registers[r])
// Allocate r to v.
if !s.allocatable.contains(r) && !s.isGReg(r) {
return
}
if s.regs[r].v != nil {
s.f.Fatalf("tried to assign register %d to %s/%s but it is already used by %s", r, v, c, s.regs[r].v)
}
// Update state.
s.regs[r] = regState{v, c}
s.values[v.ID].regs |= regMask(1) << r
s.used |= regMask(1) << r
s.f.setHome(c, &s.registers[r])
}
// allocReg chooses a register from the set of registers in mask.
@ -549,6 +562,9 @@ func (s *regAllocState) allocValToReg(v *Value, mask regMask, nospill bool, pos
// Check if v is already in a requested register.
if mask&vi.regs != 0 {
r := pickReg(mask & vi.regs)
if !s.allocatable.contains(r) {
return v // v is in a fixed register
}
if s.regs[r].v != v || s.regs[r].c == nil {
panic("bad register state")
}
@ -572,11 +588,17 @@ func (s *regAllocState) allocValToReg(v *Value, mask regMask, nospill bool, pos
if vi.regs != 0 {
// Copy from a register that v is already in.
r2 := pickReg(vi.regs)
if s.regs[r2].v != v {
panic("bad register state")
var current *Value
if !s.allocatable.contains(r2) {
current = v // v is in a fixed register
} else {
if s.regs[r2].v != v {
panic("bad register state")
}
current = s.regs[r2].c
}
s.usedSinceBlockStart |= regMask(1) << r2
c = s.curBlock.NewValue1(pos, OpCopy, v.Type, s.regs[r2].c)
c = s.curBlock.NewValue1(pos, OpCopy, v.Type, current)
} else if v.rematerializeable() {
// Rematerialize instead of loading from the spill location.
c = v.copyIntoWithXPos(s.curBlock, pos)
@ -637,6 +659,7 @@ func (s *regAllocState) init(f *Func) {
s.SPReg = noRegister
s.SBReg = noRegister
s.GReg = noRegister
s.ZeroIntReg = noRegister
for r := register(0); r < s.numRegs; r++ {
switch s.registers[r].String() {
case "SP":
@ -645,6 +668,8 @@ func (s *regAllocState) init(f *Func) {
s.SBReg = r
case "g":
s.GReg = r
case "ZERO": // TODO: arch-specific?
s.ZeroIntReg = r
}
}
// Make sure we found all required registers.
@ -666,6 +691,9 @@ func (s *regAllocState) init(f *Func) {
if s.f.Config.hasGReg {
s.allocatable &^= 1 << s.GReg
}
if s.ZeroIntReg != noRegister {
s.allocatable &^= 1 << s.ZeroIntReg
}
if buildcfg.FramePointerEnabled && s.f.Config.FPReg >= 0 {
s.allocatable &^= 1 << uint(s.f.Config.FPReg)
}
@ -818,7 +846,7 @@ func (s *regAllocState) advanceUses(v *Value) {
ai := &s.values[a.ID]
r := ai.uses
ai.uses = r.next
if r.next == nil || (a.Op != OpSP && a.Op != OpSB && r.next.dist > s.nextCall[s.curIdx]) {
if r.next == nil || (!opcodeTable[a.Op].fixedReg && r.next.dist > s.nextCall[s.curIdx]) {
// Value is dead (or is not used again until after a call), free all registers that hold it.
s.freeRegs(ai.regs)
}
@ -836,7 +864,7 @@ func (s *regAllocState) dropIfUnused(v *Value) {
}
vi := &s.values[v.ID]
r := vi.uses
if r == nil || (v.Op != OpSP && v.Op != OpSB && r.dist > s.nextCall[s.curIdx]) {
if r == nil || (!opcodeTable[v.Op].fixedReg && r.dist > s.nextCall[s.curIdx]) {
s.freeRegs(vi.regs)
}
}
@ -1366,18 +1394,21 @@ func (s *regAllocState) regalloc(f *Func) {
if v.Op == OpPhi {
f.Fatalf("phi %s not at start of block", v)
}
if v.Op == OpSP {
s.assignReg(s.SPReg, v, v)
if opcodeTable[v.Op].fixedReg {
switch v.Op {
case OpSP:
s.assignReg(s.SPReg, v, v)
s.sp = v.ID
case OpSB:
s.assignReg(s.SBReg, v, v)
s.sb = v.ID
case OpARM64ZERO:
s.assignReg(s.ZeroIntReg, v, v)
default:
f.Fatalf("unknown fixed-register op %s", v)
}
b.Values = append(b.Values, v)
s.advanceUses(v)
s.sp = v.ID
continue
}
if v.Op == OpSB {
s.assignReg(s.SBReg, v, v)
b.Values = append(b.Values, v)
s.advanceUses(v)
s.sb = v.ID
continue
}
if v.Op == OpSelect0 || v.Op == OpSelect1 || v.Op == OpSelectN {
@ -2414,15 +2445,20 @@ func (e *edgeState) processDest(loc Location, vid ID, splice **Value, pos src.XP
fmt.Printf("moving v%d to %s\n", vid, loc)
fmt.Printf("sources of v%d:", vid)
}
for _, w := range e.cache[vid] {
h := e.s.f.getHome(w.ID)
if e.s.f.pass.debug > regDebug {
fmt.Printf(" %s:%s", h, w)
}
_, isreg := h.(*Register)
if src == nil || isreg {
c = w
src = h
if opcodeTable[v.Op].fixedReg {
c = v
src = e.s.f.getHome(v.ID)
} else {
for _, w := range e.cache[vid] {
h := e.s.f.getHome(w.ID)
if e.s.f.pass.debug > regDebug {
fmt.Printf(" %s:%s", h, w)
}
_, isreg := h.(*Register)
if src == nil || isreg {
c = w
src = h
}
}
}
if e.s.f.pass.debug > regDebug {
@ -2649,8 +2685,9 @@ func (v *Value) rematerializeable() bool {
return false
}
for _, a := range v.Args {
// SP and SB (generated by OpSP and OpSB) are always available.
if a.Op != OpSP && a.Op != OpSB {
// Fixed-register allocations (SP, SB, etc.) are always available.
// Any other argument of an opcode makes it not rematerializeable.
if !opcodeTable[a.Op].fixedReg {
return false
}
}