From ca9e450bed8e0884e88731526adfb2b4080b548e Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 8 Sep 2015 08:59:57 -0700 Subject: [PATCH] [dev.ssa] cmd/compile/internal/ssa: fix defer in functions with no return The after-defer test jumps to a deferreturn site. Some functions (those with infinite loops) have no deferreturn site. Add one so we have one to jump to. Change-Id: I505e7f3f888f5e7d03ca49a3477b41cf1f78eb8a Reviewed-on: https://go-review.googlesource.com/14349 Reviewed-by: Josh Bleecher Snyder --- src/cmd/compile/internal/gc/ssa.go | 38 ++++++++++++------- src/cmd/compile/internal/gc/ssa_test.go | 10 ++++- .../internal/gc/testdata/deferNoReturn_ssa.go | 21 ++++++++++ 3 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 src/cmd/compile/internal/gc/testdata/deferNoReturn_ssa.go diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 098a1e15f6..70990bbd18 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -2621,6 +2621,12 @@ func genssa(f *ssa.Func, ptxt *obj.Prog, gcargs, gclocals *Sym) { for _, br := range s.branches { br.p.To.Val = s.bstart[br.b.ID] } + if s.deferBranches != nil && s.deferTarget == nil { + // This can happen when the function has a defer but + // no return (because it has an infinite loop). + s.deferReturn() + Prog(obj.ARET) + } for _, p := range s.deferBranches { p.To.Val = s.deferTarget } @@ -3463,20 +3469,7 @@ func (s *genState) genBlock(b, next *ssa.Block) { case ssa.BlockExit: case ssa.BlockRet: if Hasdefer != 0 { - // Deferred calls will appear to be returning to - // the CALL deferreturn(SB) that we are about to emit. - // However, the stack trace code will show the line - // of the instruction byte before the return PC. - // To avoid that being an unrelated instruction, - // insert an actual hardware NOP that will have the right line number. - // This is different from obj.ANOP, which is a virtual no-op - // that doesn't make it into the instruction stream. - s.deferTarget = Pc - Thearch.Ginsnop() - p := Prog(obj.ACALL) - p.To.Type = obj.TYPE_MEM - p.To.Name = obj.NAME_EXTERN - p.To.Sym = Linksym(Deferreturn.Sym) + s.deferReturn() } Prog(obj.ARET) case ssa.BlockCall: @@ -3537,6 +3530,23 @@ func (s *genState) genBlock(b, next *ssa.Block) { } } +func (s *genState) deferReturn() { + // Deferred calls will appear to be returning to + // the CALL deferreturn(SB) that we are about to emit. + // However, the stack trace code will show the line + // of the instruction byte before the return PC. + // To avoid that being an unrelated instruction, + // insert an actual hardware NOP that will have the right line number. + // This is different from obj.ANOP, which is a virtual no-op + // that doesn't make it into the instruction stream. + s.deferTarget = Pc + Thearch.Ginsnop() + p := Prog(obj.ACALL) + p.To.Type = obj.TYPE_MEM + p.To.Name = obj.NAME_EXTERN + p.To.Sym = Linksym(Deferreturn.Sym) +} + // addAux adds the offset in the aux fields (AuxInt and Aux) of v to a. func addAux(a *obj.Addr, v *ssa.Value) { if a.Type != obj.TYPE_MEM { diff --git a/src/cmd/compile/internal/gc/ssa_test.go b/src/cmd/compile/internal/gc/ssa_test.go index e4f5bcd1fd..0bce902982 100644 --- a/src/cmd/compile/internal/gc/ssa_test.go +++ b/src/cmd/compile/internal/gc/ssa_test.go @@ -17,12 +17,18 @@ import ( // TODO: move all these tests elsewhere? // Perhaps teach test/run.go how to run them with a new action verb. func runTest(t *testing.T, filename string) { + doTest(t, filename, "run") +} +func buildTest(t *testing.T, filename string) { + doTest(t, filename, "build") +} +func doTest(t *testing.T, filename string, kind string) { if runtime.GOARCH != "amd64" { t.Skipf("skipping SSA tests on %s for now", runtime.GOARCH) } testenv.MustHaveGoBuild(t) var stdout, stderr bytes.Buffer - cmd := exec.Command("go", "run", filepath.Join("testdata", filename)) + cmd := exec.Command("go", kind, filepath.Join("testdata", filename)) cmd.Stdout = &stdout cmd.Stderr = &stderr // TODO: set GOGC=off until we have stackmaps @@ -70,3 +76,5 @@ func TestMap(t *testing.T) { runTest(t, "map_ssa.go") } func TestRegalloc(t *testing.T) { runTest(t, "regalloc_ssa.go") } func TestString(t *testing.T) { runTest(t, "string_ssa.go") } + +func TestDeferNoReturn(t *testing.T) { buildTest(t, "deferNoReturn_ssa.go") } diff --git a/src/cmd/compile/internal/gc/testdata/deferNoReturn_ssa.go b/src/cmd/compile/internal/gc/testdata/deferNoReturn_ssa.go new file mode 100644 index 0000000000..171f5837bc --- /dev/null +++ b/src/cmd/compile/internal/gc/testdata/deferNoReturn_ssa.go @@ -0,0 +1,21 @@ +// compile + +// 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. + +// Test that a defer in a function with no return +// statement will compile correctly. + +package main + +func deferNoReturn_ssa() { + defer func() { println("returned") }() + for { + println("loop") + } +} + +func main() { + deferNoReturn_ssa() +}