Commit 74144506 authored by Keith Randall's avatar Keith Randall

cmd/compile: make [0]T and [1]T SSAable types

We used to have to keep on-stack copies of these types.
Now they can be registerized.

[0]T is kind of trivial but might as well handle it.

This change enables another change I'm working on to improve how x.(T)
expressions are handled (#17405).  This CL helps because now all
types that are direct interface types are registerizeable (e.g. [1]*byte).

No higher-degree arrays for now because non-constant indexes are hard.

Update #17405

Change-Id: I2399940965d17b3969ae66f6fe447a8cefdd6edd
Reviewed-on: https://go-review.googlesource.com/32416
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarDavid Chase <drchase@google.com>
parent 9c066bab
......@@ -1974,7 +1974,22 @@ func (s *state) expr(n *Node) *ssa.Value {
p, _ := s.addr(n, false)
return s.newValue2(ssa.OpLoad, n.Left.Type.Elem(), p, s.mem())
case n.Left.Type.IsArray():
// TODO: fix when we can SSA arrays of length 1.
if bound := n.Left.Type.NumElem(); bound <= 1 {
// SSA can handle arrays of length at most 1.
a := s.expr(n.Left)
i := s.expr(n.Right)
if bound == 0 {
// Bounds check will never succeed. Might as well
// use constants for the bounds check.
z := s.constInt(Types[TINT], 0)
s.boundsCheck(z, z)
// The return value won't be live, return junk.
return s.newValue0(ssa.OpUnknown, n.Type)
}
i = s.extendIndex(i, panicindex)
s.boundsCheck(i, s.constInt(Types[TINT], bound))
return s.newValue1I(ssa.OpArraySelect, n.Type, 0, a)
}
p, _ := s.addr(n, false)
return s.newValue2(ssa.OpLoad, n.Left.Type.Elem(), p, s.mem())
default:
......@@ -2017,32 +2032,6 @@ func (s *state) expr(n *Node) *ssa.Value {
case OEFACE:
tab := s.expr(n.Left)
data := s.expr(n.Right)
// The frontend allows putting things like struct{*byte} in
// the data portion of an eface. But we don't want struct{*byte}
// as a register type because (among other reasons) the liveness
// analysis is confused by the "fat" variables that result from
// such types being spilled.
// So here we ensure that we are selecting the underlying pointer
// when we build an eface.
// TODO: get rid of this now that structs can be SSA'd?
for !data.Type.IsPtrShaped() {
switch {
case data.Type.IsArray():
data = s.newValue1I(ssa.OpArrayIndex, data.Type.ElemType(), 0, data)
case data.Type.IsStruct():
for i := data.Type.NumFields() - 1; i >= 0; i-- {
f := data.Type.FieldType(i)
if f.Size() == 0 {
// eface type could also be struct{p *byte; q [0]int}
continue
}
data = s.newValue1I(ssa.OpStructSelect, f, int64(i), data)
break
}
default:
s.Fatalf("type being put into an eface isn't a pointer")
}
}
return s.newValue2(ssa.OpIMake, n.Type, tab, data)
case OSLICE, OSLICEARR, OSLICE3, OSLICE3ARR:
......@@ -2377,6 +2366,30 @@ func (s *state) assign(left *Node, right *ssa.Value, wb, deref bool, line int32,
// TODO: do we need to update named values here?
return
}
if left.Op == OINDEX && left.Left.Type.IsArray() {
// We're assigning to an element of an ssa-able array.
// a[i] = v
t := left.Left.Type
n := t.NumElem()
i := s.expr(left.Right) // index
if n == 0 {
// The bounds check must fail. Might as well
// ignore the actual index and just use zeros.
z := s.constInt(Types[TINT], 0)
s.boundsCheck(z, z)
return
}
if n != 1 {
s.Fatalf("assigning to non-1-length array")
}
// Rewrite to a = [1]{v}
i = s.extendIndex(i, panicindex)
s.boundsCheck(i, s.constInt(Types[TINT], 1))
v := s.newValue1(ssa.OpArrayMake1, t, right)
s.assign(left.Left, v, false, false, line, 0, rightIsVolatile)
return
}
// Update variable assignment.
s.vars[left] = right
s.addNamedValue(left, right)
......@@ -2475,6 +2488,13 @@ func (s *state) zeroVal(t *Type) *ssa.Value {
v.AddArg(s.zeroVal(t.FieldType(i).(*Type)))
}
return v
case t.IsArray():
switch t.NumElem() {
case 0:
return s.entryNewValue0(ssa.OpArrayMake0, t)
case 1:
return s.entryNewValue1(ssa.OpArrayMake1, t, s.zeroVal(t.Elem()))
}
}
s.Fatalf("zero for type %v not implemented", t)
return nil
......@@ -3071,7 +3091,7 @@ func (s *state) canSSA(n *Node) bool {
if Debug['N'] != 0 {
return false
}
for n.Op == ODOT {
for n.Op == ODOT || (n.Op == OINDEX && n.Left.Type.IsArray()) {
n = n.Left
}
if n.Op != ONAME {
......@@ -3123,11 +3143,15 @@ func canSSAType(t *Type) bool {
}
switch t.Etype {
case TARRAY:
// We can't do arrays because dynamic indexing is
// We can't do larger arrays because dynamic indexing is
// not supported on SSA variables.
// TODO: maybe allow if length is <=1? All indexes
// are constant? Might be good for the arrays
// introduced by the compiler for variadic functions.
// TODO: allow if all indexes are constant.
if t.NumElem() == 0 {
return true
}
if t.NumElem() == 1 {
return canSSAType(t.Elem())
}
return false
case TSTRUCT:
if t.NumFields() > ssa.MaxStruct {
......@@ -3406,6 +3430,10 @@ func (s *state) storeTypeScalars(t *Type, left, right *ssa.Value, skip skipMask)
val := s.newValue1I(ssa.OpStructSelect, ft, int64(i), right)
s.storeTypeScalars(ft.(*Type), addr, val, 0)
}
case t.IsArray() && t.NumElem() == 0:
// nothing
case t.IsArray() && t.NumElem() == 1:
s.storeTypeScalars(t.Elem(), left, s.newValue1I(ssa.OpArraySelect, t.Elem(), 0, right), 0)
default:
s.Fatalf("bad write barrier type %v", t)
}
......@@ -3438,6 +3466,10 @@ func (s *state) storeTypePtrs(t *Type, left, right *ssa.Value) {
val := s.newValue1I(ssa.OpStructSelect, ft, int64(i), right)
s.storeTypePtrs(ft.(*Type), addr, val)
}
case t.IsArray() && t.NumElem() == 0:
// nothing
case t.IsArray() && t.NumElem() == 1:
s.storeTypePtrs(t.Elem(), left, s.newValue1I(ssa.OpArraySelect, t.Elem(), 0, right))
default:
s.Fatalf("bad write barrier type %v", t)
}
......@@ -3470,6 +3502,10 @@ func (s *state) storeTypePtrsWB(t *Type, left, right *ssa.Value) {
val := s.newValue1I(ssa.OpStructSelect, ft, int64(i), right)
s.storeTypePtrsWB(ft.(*Type), addr, val)
}
case t.IsArray() && t.NumElem() == 0:
// nothing
case t.IsArray() && t.NumElem() == 1:
s.storeTypePtrsWB(t.Elem(), left, s.newValue1I(ssa.OpArraySelect, t.Elem(), 0, right))
default:
s.Fatalf("bad write barrier type %v", t)
}
......@@ -4567,6 +4603,20 @@ func (e *ssaExport) SplitStruct(name ssa.LocalSlot, i int) ssa.LocalSlot {
return ssa.LocalSlot{N: n, Type: ft, Off: name.Off + st.FieldOff(i)}
}
func (e *ssaExport) SplitArray(name ssa.LocalSlot) ssa.LocalSlot {
n := name.N.(*Node)
at := name.Type
if at.NumElem() != 1 {
Fatalf("bad array size")
}
et := at.ElemType()
if n.Class == PAUTO && !n.Addrtaken {
x := e.namedAuto(n.Sym.Name+"[0]", et)
return ssa.LocalSlot{N: x, Type: et, Off: 0}
}
return ssa.LocalSlot{N: n, Type: et, Off: name.Off}
}
// namedAuto returns a new AUTO variable with the given name and type.
// These are exposed to the debugger.
func (e *ssaExport) namedAuto(name string, typ ssa.Type) ssa.GCNode {
......
......@@ -115,6 +115,7 @@ type Frontend interface {
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)
// Line returns a string describing the given line number.
......
......@@ -253,6 +253,21 @@ func decomposeUser(f *Func) {
}
delete(f.NamedValues, name)
newNames = append(newNames, fnames...)
case t.IsArray():
if t.NumElem() == 0 {
// TODO(khr): Not sure what to do here. Probably nothing.
// Names for empty arrays aren't important.
break
}
if t.NumElem() != 1 {
f.Fatalf("array not of size 1")
}
elemName := f.Config.fe.SplitArray(name)
for _, v := range f.NamedValues[name] {
e := v.Block.NewValue1I(v.Line, OpArraySelect, t.ElemType(), 0, v)
f.NamedValues[elemName] = append(f.NamedValues[elemName], e)
}
default:
f.Names[i] = name
i++
......@@ -266,10 +281,13 @@ func decomposeUserPhi(v *Value) {
switch {
case v.Type.IsStruct():
decomposeStructPhi(v)
case v.Type.IsArray():
decomposeArrayPhi(v)
}
// TODO: Arrays of length 1?
}
// decomposeStructPhi replaces phi-of-struct with structmake(phi-for-each-field),
// and then recursively decomposes the phis for each field.
func decomposeStructPhi(v *Value) {
t := v.Type
n := t.NumFields()
......@@ -287,10 +305,30 @@ func decomposeStructPhi(v *Value) {
// Recursively decompose phis for each field.
for _, f := range fields[:n] {
if f.Type.IsStruct() {
decomposeStructPhi(f)
}
decomposeUserPhi(f)
}
}
// decomposeArrayPhi replaces phi-of-array with arraymake(phi-of-array-element),
// and then recursively decomposes the element phi.
func decomposeArrayPhi(v *Value) {
t := v.Type
if t.NumElem() == 0 {
v.reset(OpArrayMake0)
return
}
if t.NumElem() != 1 {
v.Fatalf("SSAable array must have no more than 1 element")
}
elem := v.Block.NewValue0(v.Line, OpPhi, t.ElemType())
for _, a := range v.Args {
elem.AddArg(a.Block.NewValue1I(v.Line, OpArraySelect, t.ElemType(), 0, a))
}
v.reset(OpArrayMake1)
v.AddArg(elem)
// Recursively decompose elem phi.
decomposeUserPhi(elem)
}
// MaxStruct is the maximum number of fields a struct
......
......@@ -58,6 +58,9 @@ func (d DummyFrontend) SplitInt64(s LocalSlot) (LocalSlot, LocalSlot) {
func (d DummyFrontend) SplitStruct(s LocalSlot, i int) LocalSlot {
return LocalSlot{s.N, s.Type.FieldType(i), s.Off + s.Type.FieldOff(i)}
}
func (d DummyFrontend) SplitArray(s LocalSlot) LocalSlot {
return LocalSlot{s.N, s.Type.ElemType(), s.Off}
}
func (DummyFrontend) Line(line int32) string {
return "unknown.go:0"
}
......
......@@ -668,7 +668,6 @@
// indexing operations
// Note: bounds check has already been done
(ArrayIndex <t> [0] x:(Load ptr mem)) -> @x.Block (Load <t> ptr mem)
(PtrIndex <t> ptr idx) && config.PtrSize == 4 -> (AddPtr ptr (Mul32 <config.fe.TypeInt()> idx (Const32 <config.fe.TypeInt()> [t.ElemType().Size()])))
(PtrIndex <t> ptr idx) && config.PtrSize == 8 -> (AddPtr ptr (Mul64 <config.fe.TypeInt()> idx (Const64 <config.fe.TypeInt()> [t.ElemType().Size()])))
......@@ -736,12 +735,30 @@
f1
(Store [t.FieldType(0).Size()] dst f0 mem))))
(IMake typ (StructMake1 val)) -> (IMake typ val)
// un-SSAable values use mem->mem copies
(Store [size] dst (Load <t> src mem) mem) && !config.fe.CanSSA(t) ->
(Move [MakeSizeAndAlign(size, t.Alignment()).Int64()] dst src mem)
(Store [size] dst (Load <t> src mem) (VarDef {x} mem)) && !config.fe.CanSSA(t) ->
(Move [MakeSizeAndAlign(size, t.Alignment()).Int64()] dst src (VarDef {x} mem))
// array ops
(ArraySelect (ArrayMake1 x)) -> x
(Load <t> _ _) && t.IsArray() && t.NumElem() == 0 ->
(ArrayMake0)
(Load <t> ptr mem) && t.IsArray() && t.NumElem() == 1 && config.fe.CanSSA(t) ->
(ArrayMake1 (Load <t.ElemType()> ptr mem))
(Store _ (ArrayMake0) mem) -> mem
(Store [size] dst (ArrayMake1 e) mem) -> (Store [size] dst e mem)
(ArraySelect [0] (Load ptr mem)) -> (Load ptr mem)
(IMake typ (ArrayMake1 val)) -> (IMake typ val)
// string ops
// Decomposing StringMake and lowering of StringPtr and StringLen
// happens in a later pass, dec, so that these operations are available
......@@ -850,6 +867,11 @@
(Arg <t.FieldType(2)> {n} [off+t.FieldOff(2)])
(Arg <t.FieldType(3)> {n} [off+t.FieldOff(3)]))
(Arg <t>) && t.IsArray() && t.NumElem() == 0 ->
(ArrayMake0)
(Arg <t> {n} [off]) && t.IsArray() && t.NumElem() == 1 && config.fe.CanSSA(t) ->
(ArrayMake1 (Arg <t.ElemType()> {n} [off]))
// strength reduction of divide by a constant.
// Note: frontend does <=32 bits. We only need to do 64 bits here.
// TODO: Do them all here?
......
......@@ -373,9 +373,8 @@ var genericOps = []opData{
{name: "GetClosurePtr"}, // get closure pointer from dedicated register
// Indexing operations
{name: "ArrayIndex", aux: "Int64", argLength: 1}, // arg0=array, auxint=index. Returns a[i]
{name: "PtrIndex", argLength: 2}, // arg0=ptr, arg1=index. Computes ptr+sizeof(*v.type)*index, where index is extended to ptrwidth type
{name: "OffPtr", argLength: 1, aux: "Int64"}, // arg0 + auxint (arg0 and result are pointers)
{name: "PtrIndex", argLength: 2}, // arg0=ptr, arg1=index. Computes ptr+sizeof(*v.type)*index, where index is extended to ptrwidth type
{name: "OffPtr", argLength: 1, aux: "Int64"}, // arg0 + auxint (arg0 and result are pointers)
// Slices
{name: "SliceMake", argLength: 3}, // arg0=ptr, arg1=len, arg2=cap
......@@ -406,6 +405,11 @@ var genericOps = []opData{
{name: "StructMake4", argLength: 4}, // arg0..3=field0..3. Returns struct.
{name: "StructSelect", argLength: 1, aux: "Int64"}, // arg0=struct, auxint=field index. Returns the auxint'th field.
// Arrays
{name: "ArrayMake0"}, // Returns array with 0 elements
{name: "ArrayMake1", argLength: 1}, // Returns array with 1 element
{name: "ArraySelect", argLength: 1, aux: "Int64"}, // arg0=array, auxint=index. Returns a[i].
// Spill&restore ops for the register allocator. These are
// semantically identical to OpCopy; they do not take/return
// stores like regular memory ops do. We can get away without memory
......
......@@ -1698,7 +1698,6 @@ const (
OpNilCheck
OpGetG
OpGetClosurePtr
OpArrayIndex
OpPtrIndex
OpOffPtr
OpSliceMake
......@@ -1720,6 +1719,9 @@ const (
OpStructMake3
OpStructMake4
OpStructSelect
OpArrayMake0
OpArrayMake1
OpArraySelect
OpStoreReg
OpLoadReg
OpFwdRef
......@@ -19616,12 +19618,6 @@ var opcodeTable = [...]opInfo{
argLen: 0,
generic: true,
},
{
name: "ArrayIndex",
auxType: auxInt64,
argLen: 1,
generic: true,
},
{
name: "PtrIndex",
argLen: 2,
......@@ -19729,6 +19725,22 @@ var opcodeTable = [...]opInfo{
argLen: 1,
generic: true,
},
{
name: "ArrayMake0",
argLen: 0,
generic: true,
},
{
name: "ArrayMake1",
argLen: 1,
generic: true,
},
{
name: "ArraySelect",
auxType: auxInt64,
argLen: 1,
generic: true,
},
{
name: "StoreReg",
argLen: 1,
......
......@@ -32,8 +32,8 @@ func rewriteValuegeneric(v *Value, config *Config) bool {
return rewriteValuegeneric_OpAnd8(v, config)
case OpArg:
return rewriteValuegeneric_OpArg(v, config)
case OpArrayIndex:
return rewriteValuegeneric_OpArrayIndex(v, config)
case OpArraySelect:
return rewriteValuegeneric_OpArraySelect(v, config)
case OpCom16:
return rewriteValuegeneric_OpCom16(v, config)
case OpCom32:
......@@ -110,6 +110,8 @@ func rewriteValuegeneric(v *Value, config *Config) bool {
return rewriteValuegeneric_OpGreater8(v, config)
case OpGreater8U:
return rewriteValuegeneric_OpGreater8U(v, config)
case OpIMake:
return rewriteValuegeneric_OpIMake(v, config)
case OpIsInBounds:
return rewriteValuegeneric_OpIsInBounds(v, config)
case OpIsSliceInBounds:
......@@ -1607,31 +1609,69 @@ func rewriteValuegeneric_OpArg(v *Value, config *Config) bool {
v.AddArg(v3)
return true
}
// match: (Arg <t>)
// cond: t.IsArray() && t.NumElem() == 0
// result: (ArrayMake0)
for {
t := v.Type
if !(t.IsArray() && t.NumElem() == 0) {
break
}
v.reset(OpArrayMake0)
return true
}
// match: (Arg <t> {n} [off])
// cond: t.IsArray() && t.NumElem() == 1 && config.fe.CanSSA(t)
// result: (ArrayMake1 (Arg <t.ElemType()> {n} [off]))
for {
t := v.Type
off := v.AuxInt
n := v.Aux
if !(t.IsArray() && t.NumElem() == 1 && config.fe.CanSSA(t)) {
break
}
v.reset(OpArrayMake1)
v0 := b.NewValue0(v.Line, OpArg, t.ElemType())
v0.AuxInt = off
v0.Aux = n
v.AddArg(v0)
return true
}
return false
}
func rewriteValuegeneric_OpArrayIndex(v *Value, config *Config) bool {
func rewriteValuegeneric_OpArraySelect(v *Value, config *Config) bool {
b := v.Block
_ = b
// match: (ArrayIndex <t> [0] x:(Load ptr mem))
// match: (ArraySelect (ArrayMake1 x))
// cond:
// result: @x.Block (Load <t> ptr mem)
// result: x
for {
v_0 := v.Args[0]
if v_0.Op != OpArrayMake1 {
break
}
x := v_0.Args[0]
v.reset(OpCopy)
v.Type = x.Type
v.AddArg(x)
return true
}
// match: (ArraySelect [0] (Load ptr mem))
// cond:
// result: (Load ptr mem)
for {
t := v.Type
if v.AuxInt != 0 {
break
}
x := v.Args[0]
if x.Op != OpLoad {
v_0 := v.Args[0]
if v_0.Op != OpLoad {
break
}
ptr := x.Args[0]
mem := x.Args[1]
b = x.Block
v0 := b.NewValue0(v.Line, OpLoad, t)
v.reset(OpCopy)
v.AddArg(v0)
v0.AddArg(ptr)
v0.AddArg(mem)
ptr := v_0.Args[0]
mem := v_0.Args[1]
v.reset(OpLoad)
v.AddArg(ptr)
v.AddArg(mem)
return true
}
return false
......@@ -3101,6 +3141,41 @@ func rewriteValuegeneric_OpGreater8U(v *Value, config *Config) bool {
}
return false
}
func rewriteValuegeneric_OpIMake(v *Value, config *Config) bool {
b := v.Block
_ = b
// match: (IMake typ (StructMake1 val))
// cond:
// result: (IMake typ val)
for {
typ := v.Args[0]
v_1 := v.Args[1]
if v_1.Op != OpStructMake1 {
break
}
val := v_1.Args[0]
v.reset(OpIMake)
v.AddArg(typ)
v.AddArg(val)
return true
}
// match: (IMake typ (ArrayMake1 val))
// cond:
// result: (IMake typ val)
for {
typ := v.Args[0]
v_1 := v.Args[1]
if v_1.Op != OpArrayMake1 {
break
}
val := v_1.Args[0]
v.reset(OpIMake)
v.AddArg(typ)
v.AddArg(val)
return true
}
return false
}
func rewriteValuegeneric_OpIsInBounds(v *Value, config *Config) bool {
b := v.Block
_ = b
......@@ -3982,6 +4057,34 @@ func rewriteValuegeneric_OpLoad(v *Value, config *Config) bool {
v.AddArg(v5)
return true
}
// match: (Load <t> _ _)
// cond: t.IsArray() && t.NumElem() == 0
// result: (ArrayMake0)
for {
t := v.Type
if !(t.IsArray() && t.NumElem() == 0) {
break
}
v.reset(OpArrayMake0)
return true
}
// match: (Load <t> ptr mem)
// cond: t.IsArray() && t.NumElem() == 1 && config.fe.CanSSA(t)
// result: (ArrayMake1 (Load <t.ElemType()> ptr mem))
for {
t := v.Type
ptr := v.Args[0]
mem := v.Args[1]
if !(t.IsArray() && t.NumElem() == 1 && config.fe.CanSSA(t)) {
break
}
v.reset(OpArrayMake1)
v0 := b.NewValue0(v.Line, OpLoad, t.ElemType())
v0.AddArg(ptr)
v0.AddArg(mem)
v.AddArg(v0)
return true
}
return false
}
func rewriteValuegeneric_OpLsh16x16(v *Value, config *Config) bool {
......@@ -10291,6 +10394,39 @@ func rewriteValuegeneric_OpStore(v *Value, config *Config) bool {
v.AddArg(v0)
return true
}
// match: (Store _ (ArrayMake0) mem)
// cond:
// result: mem
for {
v_1 := v.Args[1]
if v_1.Op != OpArrayMake0 {
break
}
mem := v.Args[2]
v.reset(OpCopy)
v.Type = mem.Type
v.AddArg(mem)
return true
}
// match: (Store [size] dst (ArrayMake1 e) mem)
// cond:
// result: (Store [size] dst e mem)
for {
size := v.AuxInt
dst := v.Args[0]
v_1 := v.Args[1]
if v_1.Op != OpArrayMake1 {
break
}
e := v_1.Args[0]
mem := v.Args[2]
v.reset(OpStore)
v.AuxInt = size
v.AddArg(dst)
v.AddArg(e)
v.AddArg(mem)
return true
}
return false
}
func rewriteValuegeneric_OpStringLen(v *Value, config *Config) bool {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment