diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cmd/compile/internal/base/flag.go | 4 | ||||
-rw-r--r-- | src/cmd/compile/internal/ssagen/phi.go | 2 | ||||
-rw-r--r-- | src/cmd/compile/internal/staticinit/sched.go | 368 |
3 files changed, 292 insertions, 82 deletions
diff --git a/src/cmd/compile/internal/base/flag.go b/src/cmd/compile/internal/base/flag.go index eb346e29fc..f1685104b1 100644 --- a/src/cmd/compile/internal/base/flag.go +++ b/src/cmd/compile/internal/base/flag.go @@ -80,8 +80,8 @@ type CmdFlags struct { LowerV *bool "help:\"increase debug verbosity\"" // Special characters - Percent int "flag:\"%\" help:\"debug non-static initializers\"" - CompilingRuntime bool "flag:\"+\" help:\"compiling runtime\"" + Percent CountFlag "flag:\"%\" help:\"debug non-static initializers\"" + CompilingRuntime bool "flag:\"+\" help:\"compiling runtime\"" // Longer names AsmHdr string "help:\"write assembly header to `file`\"" diff --git a/src/cmd/compile/internal/ssagen/phi.go b/src/cmd/compile/internal/ssagen/phi.go index 01ad211282..3e31ac7fd6 100644 --- a/src/cmd/compile/internal/ssagen/phi.go +++ b/src/cmd/compile/internal/ssagen/phi.go @@ -483,7 +483,7 @@ loop: var_ := v.Aux.(fwdRefAux).N if b == s.f.Entry { // No variable should be live at entry. - s.s.Fatalf("Value live at entry. It shouldn't be. func %s, node %v, value %v", s.f.Name, var_, v) + s.s.Fatalf("value %v (%v) incorrectly live at entry", var_, v) } if !s.reachable[b.ID] { // This block is dead. diff --git a/src/cmd/compile/internal/staticinit/sched.go b/src/cmd/compile/internal/staticinit/sched.go index e9b97e6c87..8ad340c046 100644 --- a/src/cmd/compile/internal/staticinit/sched.go +++ b/src/cmd/compile/internal/staticinit/sched.go @@ -48,7 +48,7 @@ func (s *Schedule) append(n ir.Node) { func (s *Schedule) StaticInit(n ir.Node) { if !s.tryStaticInit(n) { if base.Flag.Percent != 0 { - ir.Dump("nonstatic", n) + ir.Dump("StaticInit failed", n) } s.append(n) } @@ -364,9 +364,15 @@ func (s *Schedule) StaticAssign(l *ir.Name, loff int64, r ir.Node, typ *types.Ty } return true + + case ir.OINLCALL: + r := r.(*ir.InlinedCallExpr) + return s.staticAssignInlinedCall(l, loff, r, typ) } - //dump("not static", r); + if base.Flag.Percent != 0 { + ir.Dump("not static", r) + } return false } @@ -443,6 +449,163 @@ func (s *Schedule) addvalue(p *Plan, xoffset int64, n ir.Node) { p.E = append(p.E, Entry{Xoffset: xoffset, Expr: n}) } +func (s *Schedule) staticAssignInlinedCall(l *ir.Name, loff int64, call *ir.InlinedCallExpr, typ *types.Type) bool { + // Handle the special case of an inlined call of + // a function body with a single return statement, + // which turns into a single assignment plus a goto. + // + // For example code like this: + // + // type T struct{ x int } + // func F(x int) *T { return &T{x} } + // var Global = F(400) + // + // turns into IR like this: + // + // INLCALL-init + // . AS2-init + // . . DCL # x.go:18:13 + // . . . NAME-p.x Class:PAUTO Offset:0 InlFormal OnStack Used int tc(1) # x.go:14:9,x.go:18:13 + // . AS2 Def tc(1) # x.go:18:13 + // . AS2-Lhs + // . . NAME-p.x Class:PAUTO Offset:0 InlFormal OnStack Used int tc(1) # x.go:14:9,x.go:18:13 + // . AS2-Rhs + // . . LITERAL-400 int tc(1) # x.go:18:14 + // . INLMARK Index:1 # +x.go:18:13 + // INLCALL PTR-*T tc(1) # x.go:18:13 + // INLCALL-Body + // . BLOCK tc(1) # x.go:18:13 + // . BLOCK-List + // . . DCL tc(1) # x.go:18:13 + // . . . NAME-p.~R0 Class:PAUTO Offset:0 OnStack Used PTR-*T tc(1) # x.go:18:13 + // . . AS2 tc(1) # x.go:18:13 + // . . AS2-Lhs + // . . . NAME-p.~R0 Class:PAUTO Offset:0 OnStack Used PTR-*T tc(1) # x.go:18:13 + // . . AS2-Rhs + // . . . INLINED RETURN ARGUMENT HERE + // . . GOTO p..i1 tc(1) # x.go:18:13 + // . LABEL p..i1 # x.go:18:13 + // INLCALL-ReturnVars + // . NAME-p.~R0 Class:PAUTO Offset:0 OnStack Used PTR-*T tc(1) # x.go:18:13 + // + // In non-unified IR, the tree is slightly different: + // - if there are no arguments to the inlined function, + // the INLCALL-init omits the AS2. + // - the DCL inside BLOCK is on the AS2's init list, + // not its own statement in the top level of the BLOCK. + // + // If the init values are side-effect-free and each either only + // appears once in the function body or is safely repeatable, + // then we inline the value expressions into the return argument + // and then call StaticAssign to handle that copy. + // + // This handles simple cases like + // + // var myError = errors.New("mine") + // + // where errors.New is + // + // func New(text string) error { + // return &errorString{text} + // } + // + // We could make things more sophisticated but this kind of initializer + // is the most important case for us to get right. + + init := call.Init() + var as2init *ir.AssignListStmt + if len(init) == 2 && init[0].Op() == ir.OAS2 && init[1].Op() == ir.OINLMARK { + as2init = init[0].(*ir.AssignListStmt) + } else if len(init) == 1 && init[0].Op() == ir.OINLMARK { + as2init = new(ir.AssignListStmt) + } else { + return false + } + if len(call.Body) != 2 || call.Body[0].Op() != ir.OBLOCK || call.Body[1].Op() != ir.OLABEL { + return false + } + label := call.Body[1].(*ir.LabelStmt).Label + block := call.Body[0].(*ir.BlockStmt) + list := block.List + var dcl *ir.Decl + if len(list) == 3 && list[0].Op() == ir.ODCL { + dcl = list[0].(*ir.Decl) + list = list[1:] + } + if len(list) != 2 || + list[0].Op() != ir.OAS2 || + list[1].Op() != ir.OGOTO || + list[1].(*ir.BranchStmt).Label != label { + return false + } + as2body := list[0].(*ir.AssignListStmt) + if dcl == nil { + ainit := as2body.Init() + if len(ainit) != 1 || ainit[0].Op() != ir.ODCL { + return false + } + dcl = ainit[0].(*ir.Decl) + } + if len(as2body.Lhs) != 1 || as2body.Lhs[0] != dcl.X { + return false + } + + // Can't remove the parameter variables if an address is taken. + for _, v := range as2init.Lhs { + if v.(*ir.Name).Addrtaken() { + return false + } + } + // Can't move the computation of the args if they have side effects. + for _, r := range as2init.Rhs { + if AnySideEffects(r) { + return false + } + } + + // Can only substitute arg for param if param is used + // at most once or is repeatable. + count := make(map[*ir.Name]int) + for _, x := range as2init.Lhs { + count[x.(*ir.Name)] = 0 + } + ir.Visit(as2body.Rhs[0], func(n ir.Node) { + if name, ok := n.(*ir.Name); ok { + if c, ok := count[name]; ok { + count[name] = c + 1 + } + } + }) + for name, c := range count { + if c > 1 { + // Check whether corresponding initializer can be repeated. + // Something like 1 can be; make(chan int) or &T{} cannot, + // because they need to evaluate to the same result in each use. + for i, n := range as2init.Lhs { + if n == name && !canRepeat(as2init.Rhs[i]) { + return false + } + } + } + } + + // Possible static init. + // Build tree with args substituted for params and try it. + args := make(map[*ir.Name]ir.Node) + for i, v := range as2init.Lhs { + args[v.(*ir.Name)] = as2init.Rhs[i] + } + r := subst(as2body.Rhs[0], args) + ok := s.StaticAssign(l, loff, r, typ) + + if ok && base.Flag.Percent != 0 { + ir.Dump("static inlined-LEFT", l) + ir.Dump("static inlined-ORIG", call) + ir.Dump("static inlined-RIGHT", r) + } + return ok +} + // from here down is the walk analysis // of composite literals. // most of the work is to generate @@ -510,91 +673,118 @@ func StaticLoc(n ir.Node) (name *ir.Name, offset int64, ok bool) { return nil, 0, false } -// AnySideEffects reports whether n contains any operations that could have observable side effects. -func AnySideEffects(n ir.Node) bool { - return ir.Any(n, func(n ir.Node) bool { - switch n.Op() { - // Assume side effects unless we know otherwise. - default: +func isSideEffect(n ir.Node) bool { + switch n.Op() { + // Assume side effects unless we know otherwise. + default: + return true + + // No side effects here (arguments are checked separately). + case ir.ONAME, + ir.ONONAME, + ir.OTYPE, + ir.OLITERAL, + ir.ONIL, + ir.OADD, + ir.OSUB, + ir.OOR, + ir.OXOR, + ir.OADDSTR, + ir.OADDR, + ir.OANDAND, + ir.OBYTES2STR, + ir.ORUNES2STR, + ir.OSTR2BYTES, + ir.OSTR2RUNES, + ir.OCAP, + ir.OCOMPLIT, + ir.OMAPLIT, + ir.OSTRUCTLIT, + ir.OARRAYLIT, + ir.OSLICELIT, + ir.OPTRLIT, + ir.OCONV, + ir.OCONVIFACE, + ir.OCONVNOP, + ir.ODOT, + ir.OEQ, + ir.ONE, + ir.OLT, + ir.OLE, + ir.OGT, + ir.OGE, + ir.OKEY, + ir.OSTRUCTKEY, + ir.OLEN, + ir.OMUL, + ir.OLSH, + ir.ORSH, + ir.OAND, + ir.OANDNOT, + ir.ONEW, + ir.ONOT, + ir.OBITNOT, + ir.OPLUS, + ir.ONEG, + ir.OOROR, + ir.OPAREN, + ir.ORUNESTR, + ir.OREAL, + ir.OIMAG, + ir.OCOMPLEX: + return false + + // Only possible side effect is division by zero. + case ir.ODIV, ir.OMOD: + n := n.(*ir.BinaryExpr) + if n.Y.Op() != ir.OLITERAL || constant.Sign(n.Y.Val()) == 0 { return true + } - // No side effects here (arguments are checked separately). - case ir.ONAME, - ir.ONONAME, - ir.OTYPE, - ir.OLITERAL, - ir.ONIL, - ir.OADD, - ir.OSUB, - ir.OOR, - ir.OXOR, - ir.OADDSTR, - ir.OADDR, - ir.OANDAND, - ir.OBYTES2STR, - ir.ORUNES2STR, - ir.OSTR2BYTES, - ir.OSTR2RUNES, - ir.OCAP, - ir.OCOMPLIT, - ir.OMAPLIT, - ir.OSTRUCTLIT, - ir.OARRAYLIT, - ir.OSLICELIT, - ir.OPTRLIT, - ir.OCONV, - ir.OCONVIFACE, - ir.OCONVNOP, - ir.ODOT, - ir.OEQ, - ir.ONE, - ir.OLT, - ir.OLE, - ir.OGT, - ir.OGE, - ir.OKEY, - ir.OSTRUCTKEY, - ir.OLEN, - ir.OMUL, - ir.OLSH, - ir.ORSH, - ir.OAND, - ir.OANDNOT, - ir.ONEW, - ir.ONOT, - ir.OBITNOT, - ir.OPLUS, - ir.ONEG, - ir.OOROR, - ir.OPAREN, - ir.ORUNESTR, - ir.OREAL, - ir.OIMAG, - ir.OCOMPLEX: - return false + // Only possible side effect is panic on invalid size, + // but many makechan and makemap use size zero, which is definitely OK. + case ir.OMAKECHAN, ir.OMAKEMAP: + n := n.(*ir.MakeExpr) + if !ir.IsConst(n.Len, constant.Int) || constant.Sign(n.Len.Val()) != 0 { + return true + } - // Only possible side effect is division by zero. - case ir.ODIV, ir.OMOD: - n := n.(*ir.BinaryExpr) - if n.Y.Op() != ir.OLITERAL || constant.Sign(n.Y.Val()) == 0 { - return true - } + // Only possible side effect is panic on invalid size. + // TODO(rsc): Merge with previous case (probably breaks toolstash -cmp). + case ir.OMAKESLICE, ir.OMAKESLICECOPY: + return true + } + return false +} - // Only possible side effect is panic on invalid size, - // but many makechan and makemap use size zero, which is definitely OK. - case ir.OMAKECHAN, ir.OMAKEMAP: - n := n.(*ir.MakeExpr) - if !ir.IsConst(n.Len, constant.Int) || constant.Sign(n.Len.Val()) != 0 { - return true - } +// AnySideEffects reports whether n contains any operations that could have observable side effects. +func AnySideEffects(n ir.Node) bool { + return ir.Any(n, isSideEffect) +} - // Only possible side effect is panic on invalid size. - // TODO(rsc): Merge with previous case (probably breaks toolstash -cmp). - case ir.OMAKESLICE, ir.OMAKESLICECOPY: +// canRepeat reports whether executing n multiple times has the same effect as +// assigning n to a single variable and using that variable multiple times. +func canRepeat(n ir.Node) bool { + bad := func(n ir.Node) bool { + if isSideEffect(n) { + return true + } + switch n.Op() { + case ir.OMAKECHAN, + ir.OMAKEMAP, + ir.OMAKESLICE, + ir.OMAKESLICECOPY, + ir.OMAPLIT, + ir.ONEW, + ir.OPTRLIT, + ir.OSLICELIT, + ir.OSTR2BYTES, + ir.OSTR2RUNES: return true } return false - }) + } + return !ir.Any(n, bad) } func getlit(lit ir.Node) int { @@ -607,3 +797,23 @@ func getlit(lit ir.Node) int { func isvaluelit(n ir.Node) bool { return n.Op() == ir.OARRAYLIT || n.Op() == ir.OSTRUCTLIT } + +func subst(n ir.Node, m map[*ir.Name]ir.Node) ir.Node { + var edit func(ir.Node) ir.Node + edit = func(x ir.Node) ir.Node { + switch x.Op() { + case ir.ONAME: + x := x.(*ir.Name) + if v, ok := m[x]; ok { + return ir.DeepCopy(v.Pos(), v) + } + return x + case ir.ONONAME, ir.OLITERAL, ir.ONIL, ir.OTYPE: + return x + } + x = ir.Copy(x) + ir.EditChildren(x, edit) + return typecheck.EvalConst(x) + } + return edit(n) +} |