From 9d22c101f58dd8f65410fb352562b91de5fbcb7a Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Fri, 11 Sep 2015 11:02:57 -0700 Subject: [PATCH] [dev.ssa] cmd/compile/internal/gc: implement OAPPEND Change-Id: I1fbce8c421c48074a964b4d9481c92fbc3524f80 Reviewed-on: https://go-review.googlesource.com/14525 Reviewed-by: Todd Neal --- src/cmd/compile/internal/gc/ssa.go | 182 ++++++++++++++---- src/cmd/compile/internal/gc/ssa_test.go | 2 + .../internal/gc/testdata/append_ssa.go | 71 +++++++ 3 files changed, 221 insertions(+), 34 deletions(-) create mode 100644 src/cmd/compile/internal/gc/testdata/append_ssa.go diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index e6a5627abf..5cd074b0c6 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -181,7 +181,7 @@ func buildssa(fn *Node) (ssafn *ssa.Func, usessa bool) { hstr += fmt.Sprintf("%08b", b) } if strings.HasSuffix(hstr, os.Getenv("GOSSAHASH")) { - fmt.Println("GOSSAHASH triggered %s\n", name) + fmt.Printf("GOSSAHASH triggered %s\n", name) return s.f, true } return s.f, false @@ -264,6 +264,7 @@ var memvar = Node{Op: ONAME, Sym: &Sym{Name: "mem"}} // dummy nodes for temporary variables var ptrvar = Node{Op: ONAME, Sym: &Sym{Name: "ptr"}} +var capvar = Node{Op: ONAME, Sym: &Sym{Name: "cap"}} // startBlock sets the current block we're generating code in to b. func (s *state) startBlock(b *ssa.Block) { @@ -560,6 +561,16 @@ func (s *state) stmt(n *Node) { if n.Right != nil { r = s.expr(n.Right) } + if n.Right != nil && n.Right.Op == OAPPEND { + // Yuck! The frontend gets rid of the write barrier, but we need it! + // At least, we need it in the case where growslice is called. + // TODO: Do the write barrier on just the growslice branch. + // TODO: just add a ptr graying to the end of growslice? + // TODO: check whether we need to do this for ODOTTYPE and ORECV also. + // They get similar wb-removal treatment in walk.go:OAS. + s.assign(n.Left, r, true) + return + } s.assign(n.Left, r, n.Op == OASWB) case OIF: @@ -1865,6 +1876,103 @@ func (s *state) expr(n *Node) *ssa.Value { case OGETG: return s.newValue0(ssa.OpGetG, n.Type) + case OAPPEND: + // append(s, e1, e2, e3). Compile like: + // ptr,len,cap := s + // newlen := len + 3 + // if newlen > s.cap { + // ptr,_,cap = growslice(s, newlen) + // } + // *(ptr+len) = e1 + // *(ptr+len+1) = e2 + // *(ptr+len+2) = e3 + // makeslice(ptr,newlen,cap) + + et := n.Type.Type + pt := Ptrto(et) + + // Evaluate slice + slice := s.expr(n.List.N) + + // Evaluate args + nargs := int64(count(n.List) - 1) + args := make([]*ssa.Value, 0, nargs) + for l := n.List.Next; l != nil; l = l.Next { + args = append(args, s.expr(l.N)) + } + + // Allocate new blocks + grow := s.f.NewBlock(ssa.BlockPlain) + growresult := s.f.NewBlock(ssa.BlockPlain) + assign := s.f.NewBlock(ssa.BlockPlain) + + // Decide if we need to grow + p := s.newValue1(ssa.OpSlicePtr, pt, slice) + l := s.newValue1(ssa.OpSliceLen, Types[TINT], slice) + c := s.newValue1(ssa.OpSliceCap, Types[TINT], slice) + nl := s.newValue2(s.ssaOp(OADD, Types[TINT]), Types[TINT], l, s.constInt(Types[TINT], nargs)) + cmp := s.newValue2(s.ssaOp(OGT, Types[TINT]), Types[TBOOL], nl, c) + s.vars[&ptrvar] = p + s.vars[&capvar] = c + b := s.endBlock() + b.Kind = ssa.BlockIf + b.Likely = ssa.BranchUnlikely + b.Control = cmp + b.AddEdgeTo(grow) + b.AddEdgeTo(assign) + + // Call growslice + s.startBlock(grow) + taddr := s.newValue1A(ssa.OpAddr, Types[TUINTPTR], &ssa.ExternSymbol{Types[TUINTPTR], typenamesym(n.Type)}, s.sb) + + spplus1 := s.newValue1I(ssa.OpOffPtr, Types[TUINTPTR], int64(Widthptr), s.sp) + spplus2 := s.newValue1I(ssa.OpOffPtr, Types[TUINTPTR], int64(2*Widthptr), s.sp) + spplus3 := s.newValue1I(ssa.OpOffPtr, Types[TUINTPTR], int64(3*Widthptr), s.sp) + spplus4 := s.newValue1I(ssa.OpOffPtr, Types[TUINTPTR], int64(4*Widthptr), s.sp) + s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, int64(Widthptr), s.sp, taddr, s.mem()) + s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, int64(Widthptr), spplus1, p, s.mem()) + s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, int64(Widthptr), spplus2, l, s.mem()) + s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, int64(Widthptr), spplus3, c, s.mem()) + s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, int64(Widthptr), spplus4, nl, s.mem()) + call := s.newValue1A(ssa.OpStaticCall, ssa.TypeMem, syslook("growslice", 0).Sym, s.mem()) + call.AuxInt = int64(8 * Widthptr) + s.vars[&memvar] = call + b = s.endBlock() + b.Kind = ssa.BlockCall + b.Control = call + b.AddEdgeTo(growresult) + + // Read result of growslice + s.startBlock(growresult) + spplus5 := s.newValue1I(ssa.OpOffPtr, Types[TUINTPTR], int64(5*Widthptr), s.sp) + // Note: we don't need to read the result's length. + spplus7 := s.newValue1I(ssa.OpOffPtr, Types[TUINTPTR], int64(7*Widthptr), s.sp) + s.vars[&ptrvar] = s.newValue2(ssa.OpLoad, pt, spplus5, s.mem()) + s.vars[&capvar] = s.newValue2(ssa.OpLoad, Types[TINT], spplus7, s.mem()) + b = s.endBlock() + b.AddEdgeTo(assign) + + // assign new elements to slots + s.startBlock(assign) + p = s.variable(&ptrvar, pt) // generates phi for ptr + c = s.variable(&capvar, Types[TINT]) // generates phi for cap + p2 := s.newValue2(ssa.OpPtrIndex, pt, p, l) + for i, arg := range args { + addr := s.newValue2(ssa.OpPtrIndex, pt, p2, s.constInt(Types[TUINTPTR], int64(i))) + s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, et.Size(), addr, arg, s.mem()) + if haspointers(et) { + // TODO: just one write barrier call for all of these writes? + // TODO: maybe just one writeBarrierEnabled check? + s.insertWB(et, addr) + } + } + + // make result + r := s.newValue3(ssa.OpSliceMake, n.Type, p, nl, c) + delete(s.vars, &ptrvar) + delete(s.vars, &capvar) + return r + default: s.Unimplementedf("unhandled expr %s", opnames[n.Op]) return nil @@ -1902,39 +2010,7 @@ func (s *state) assign(left *Node, right *ssa.Value, wb bool) { } s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, t.Size(), addr, right, s.mem()) if wb { - // if writeBarrierEnabled { - // typedmemmove_nostore(t, &l) - // } - bThen := s.f.NewBlock(ssa.BlockPlain) - bNext := s.f.NewBlock(ssa.BlockPlain) - - aux := &ssa.ExternSymbol{Types[TBOOL], syslook("writeBarrierEnabled", 0).Sym} - flagaddr := s.newValue1A(ssa.OpAddr, Ptrto(Types[TBOOL]), aux, s.sb) - flag := s.newValue2(ssa.OpLoad, Types[TBOOL], flagaddr, s.mem()) - b := s.endBlock() - b.Kind = ssa.BlockIf - b.Likely = ssa.BranchUnlikely - b.Control = flag - b.AddEdgeTo(bThen) - b.AddEdgeTo(bNext) - - s.startBlock(bThen) - // NOTE: there must be no GC suspension points between the write above - // (the OpStore) and this call to typedmemmove_nostore. - // TODO: writebarrierptr_nostore if just one pointer word (or a few?) - taddr := s.newValue1A(ssa.OpAddr, Types[TUINTPTR], &ssa.ExternSymbol{Types[TUINTPTR], typenamesym(left.Type)}, s.sb) - s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, int64(Widthptr), s.sp, taddr, s.mem()) - spplus8 := s.newValue1I(ssa.OpOffPtr, Types[TUINTPTR], int64(Widthptr), s.sp) - s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, int64(Widthptr), spplus8, addr, s.mem()) - call := s.newValue1A(ssa.OpStaticCall, ssa.TypeMem, syslook("typedmemmove_nostore", 0).Sym, s.mem()) - call.AuxInt = int64(2 * Widthptr) - s.vars[&memvar] = call - c := s.endBlock() - c.Kind = ssa.BlockCall - c.Control = call - c.AddEdgeTo(bNext) - - s.startBlock(bNext) + s.insertWB(left.Type, addr) } } @@ -2228,6 +2304,44 @@ func (s *state) check(cmp *ssa.Value, panicOp ssa.Op) { s.startBlock(bNext) } +// insertWB inserts a write barrier. A value of type t has already +// been stored at location p. Tell the runtime about this write. +// Note: there must be no GC suspension points between the write and +// the call that this function inserts. +func (s *state) insertWB(t *Type, p *ssa.Value) { + // if writeBarrierEnabled { + // typedmemmove_nostore(&t, p) + // } + bThen := s.f.NewBlock(ssa.BlockPlain) + bNext := s.f.NewBlock(ssa.BlockPlain) + + aux := &ssa.ExternSymbol{Types[TBOOL], syslook("writeBarrierEnabled", 0).Sym} + flagaddr := s.newValue1A(ssa.OpAddr, Ptrto(Types[TBOOL]), aux, s.sb) + flag := s.newValue2(ssa.OpLoad, Types[TBOOL], flagaddr, s.mem()) + b := s.endBlock() + b.Kind = ssa.BlockIf + b.Likely = ssa.BranchUnlikely + b.Control = flag + b.AddEdgeTo(bThen) + b.AddEdgeTo(bNext) + + s.startBlock(bThen) + // TODO: writebarrierptr_nostore if just one pointer word (or a few?) + taddr := s.newValue1A(ssa.OpAddr, Types[TUINTPTR], &ssa.ExternSymbol{Types[TUINTPTR], typenamesym(t)}, s.sb) + s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, int64(Widthptr), s.sp, taddr, s.mem()) + spplus8 := s.newValue1I(ssa.OpOffPtr, Types[TUINTPTR], int64(Widthptr), s.sp) + s.vars[&memvar] = s.newValue3I(ssa.OpStore, ssa.TypeMem, int64(Widthptr), spplus8, p, s.mem()) + call := s.newValue1A(ssa.OpStaticCall, ssa.TypeMem, syslook("typedmemmove_nostore", 0).Sym, s.mem()) + call.AuxInt = int64(2 * Widthptr) + s.vars[&memvar] = call + c := s.endBlock() + c.Kind = ssa.BlockCall + c.Control = call + c.AddEdgeTo(bNext) + + s.startBlock(bNext) +} + // slice computes the slice v[i:j:k] and returns ptr, len, and cap of result. // i,j,k may be nil, in which case they are set to their default value. // t is a slice, ptr to array, or string type. diff --git a/src/cmd/compile/internal/gc/ssa_test.go b/src/cmd/compile/internal/gc/ssa_test.go index b3ab09d914..bbd06748b1 100644 --- a/src/cmd/compile/internal/gc/ssa_test.go +++ b/src/cmd/compile/internal/gc/ssa_test.go @@ -82,3 +82,5 @@ func TestDeferNoReturn(t *testing.T) { buildTest(t, "deferNoReturn_ssa.go") } func TestClosure(t *testing.T) { runTest(t, "closure_ssa.go") } func TestArray(t *testing.T) { runTest(t, "array_ssa.go") } + +func TestAppend(t *testing.T) { runTest(t, "append_ssa.go") } diff --git a/src/cmd/compile/internal/gc/testdata/append_ssa.go b/src/cmd/compile/internal/gc/testdata/append_ssa.go new file mode 100644 index 0000000000..dba81736c8 --- /dev/null +++ b/src/cmd/compile/internal/gc/testdata/append_ssa.go @@ -0,0 +1,71 @@ +// 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. + +// append_ssa.go tests append operations. +package main + +import "fmt" + +var failed = false + +func appendOne_ssa(a []int, x int) []int { + switch { // prevent inlining + } + return append(a, x) +} +func appendThree_ssa(a []int, x, y, z int) []int { + switch { // prevent inlining + } + return append(a, x, y, z) +} + +func eq(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +func expect(got, want []int) { + if eq(got, want) { + return + } + fmt.Printf("expected %v, got %v\n", want, got) + failed = true +} + +func testAppend() { + var store [7]int + a := store[:0] + + a = appendOne_ssa(a, 1) + expect(a, []int{1}) + a = appendThree_ssa(a, 2, 3, 4) + expect(a, []int{1, 2, 3, 4}) + a = appendThree_ssa(a, 5, 6, 7) + expect(a, []int{1, 2, 3, 4, 5, 6, 7}) + if &a[0] != &store[0] { + fmt.Println("unnecessary grow") + failed = true + } + a = appendOne_ssa(a, 8) + expect(a, []int{1, 2, 3, 4, 5, 6, 7, 8}) + if &a[0] == &store[0] { + fmt.Println("didn't grow") + failed = true + } +} + +func main() { + testAppend() + + if failed { + panic("failed") + } +}