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: David Chase <drchase@google.com>
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:
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:
// 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)
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
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 {
}
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 {
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)
}
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)
}
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)
}
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 {
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.
}
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++
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()
// 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
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"
}
// 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()])))
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
(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?
{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
{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
OpNilCheck
OpGetG
OpGetClosurePtr
- OpArrayIndex
OpPtrIndex
OpOffPtr
OpSliceMake
OpStructMake3
OpStructMake4
OpStructSelect
+ OpArrayMake0
+ OpArrayMake1
+ OpArraySelect
OpStoreReg
OpLoadReg
OpFwdRef
argLen: 0,
generic: true,
},
- {
- name: "ArrayIndex",
- auxType: auxInt64,
- argLen: 1,
- generic: true,
- },
{
name: "PtrIndex",
argLen: 2,
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,
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:
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:
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
}
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
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 {
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 {