diff --git a/cl/_testdefer/loop/in.go b/cl/_testdefer/loop/in.go new file mode 100644 index 00000000..ebd8938e --- /dev/null +++ b/cl/_testdefer/loop/in.go @@ -0,0 +1,20 @@ +package main + +func f(s string) bool { + return len(s) > 2 +} + +func main() { + defer func() { + println("hi") + }() + for i := 0; i < 3; i++ { + if s := "hello"; f(s) { + defer println(s) + } else { + defer println("world") + return + } + } + defer println("bye") +} diff --git a/cl/_testdefer/loop/out.txt b/cl/_testdefer/loop/out.txt new file mode 100644 index 00000000..f8981ee0 --- /dev/null +++ b/cl/_testdefer/loop/out.txt @@ -0,0 +1,7 @@ +0: always +2: loop +4: loop +5: loop +3: cond +6: cond +1: cond diff --git a/cl/_testdefer/multiret/in.go b/cl/_testdefer/multiret/in.go new file mode 100644 index 00000000..82d34e31 --- /dev/null +++ b/cl/_testdefer/multiret/in.go @@ -0,0 +1,18 @@ +package main + +func f(s string) bool { + return len(s) > 2 +} + +func main() { + defer func() { + println("hi") + }() + if s := "hello"; f(s) { + defer println(s) + } else { + defer println("world") + return + } + defer println("bye") +} diff --git a/cl/_testdefer/multiret/out.txt b/cl/_testdefer/multiret/out.txt new file mode 100644 index 00000000..c47bbe4c --- /dev/null +++ b/cl/_testdefer/multiret/out.txt @@ -0,0 +1,4 @@ + 0: always + 1: cond + 2: cond + 3: cond diff --git a/cl/_testdefer/print/in.go b/cl/_testdefer/print/in.go new file mode 100644 index 00000000..c9726adc --- /dev/null +++ b/cl/_testdefer/print/in.go @@ -0,0 +1,52 @@ +package main + +func f() float64 { + return 1.0 +} + +func main() { + var v = f() + const n = 7 // digits printed + var buf [n + 7]byte + buf[0] = '+' + e := 0 // exp + if v == 0 { + if 1/v < 0 { + buf[0] = '-' + } + } else { + if v < 0 { + v = -v + buf[0] = '-' + } + + // normalize + for v >= 10 { + e++ + v /= 10 + } + for v < 1 { + e-- + v *= 10 + } + + // round + h := 5.0 + for i := 0; i < n; i++ { + h /= 10 + } + v += h + if v >= 10 { + e++ + v /= 10 + } + } + + // format +d.dddd+edd + for i := 0; i < n; i++ { + s := int(v) + buf[i+2] = byte(s + '0') + v -= float64(s) + v *= 10 + } +} diff --git a/cl/_testdefer/print/out.txt b/cl/_testdefer/print/out.txt new file mode 100644 index 00000000..e69de29b diff --git a/cl/_testdefer/singleret/in.go b/cl/_testdefer/singleret/in.go new file mode 100644 index 00000000..cd95da67 --- /dev/null +++ b/cl/_testdefer/singleret/in.go @@ -0,0 +1,17 @@ +package main + +func f(s string) bool { + return len(s) > 2 +} + +func main() { + defer func() { + println("hi") + }() + if s := "hello"; f(s) { + defer println(s) + } else { + defer println("world") + } + defer println("bye") +} diff --git a/cl/_testdefer/singleret/out.txt b/cl/_testdefer/singleret/out.txt new file mode 100644 index 00000000..530763ee --- /dev/null +++ b/cl/_testdefer/singleret/out.txt @@ -0,0 +1,5 @@ + 0: always + 1: cond + 2: cond + 4: cond + 3: always diff --git a/cl/blocks/block.go b/cl/blocks/block.go new file mode 100644 index 00000000..a617c612 --- /dev/null +++ b/cl/blocks/block.go @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package blocks + +import ( + llssa "github.com/goplus/llgo/ssa" + "golang.org/x/tools/go/ssa" +) + +type Info struct { + Kind llssa.DoAction + Next int +} + +// ----------------------------------------------------------------------------- + +type blockState struct { + self *ssa.BasicBlock + preds int + succs []int + loop bool + always bool + fdel bool +} + +func (p *blockState) kind() llssa.DoAction { + if p.loop { + return llssa.DeferInLoop + } + if p.always { + return llssa.DeferAlways + } + return llssa.DeferInCond +} + +func newSuccs(succs []*ssa.BasicBlock) []int { + ret := make([]int, len(succs)) + for i, blk := range succs { + ret[i] = blk.Index + } + return ret +} + +func findLoop(states []*blockState, path []int, from, iblk int) []int { + path = append(path, iblk) + self := states[iblk] + for _, succ := range self.succs { + if states[succ].fdel { + continue + } + if succ == from { + for _, i := range path { + states[i].loop = true + } + return path + } + if ret := findLoop(states, path, from, succ); ret != nil { + return ret + } + } + return nil +} + +// https://en.wikipedia.org/wiki/Topological_sorting +func Infos(blks []*ssa.BasicBlock) []Info { + n := len(blks) + order := make([]int, 1, n+1) + order[0] = 0 + end, iend := 0, 0 + states := make([]*blockState, n) + for i, blk := range blks { + preds := len(blk.Preds) + if preds == 0 && i != 0 { + order = append(order, i) + } + if isEnd(blk) { + end++ + iend = i + } + states[i] = &blockState{ + self: blk, + preds: preds, + succs: newSuccs(blk.Succs), + } + } + + path := make([]int, 0, n) + if states[0].preds != 0 { + if loop := findLoop(states, path, 0, 0); len(loop) > 0 { + order = append(order, loop[1:]...) + } + } else { + states[0].always = true + } + if end == 1 { + states[iend].always = true + } + pos := 0 + +retry: + for pos < len(order) { + iblk := order[pos] + pos++ + state := states[iblk] + state.fdel = true + for _, succ := range state.succs { + s := states[succ] + if s.fdel { + continue + } + if s.preds--; s.preds == 0 { + order = append(order, succ) + } + } + } + if pos < n { + for iblk, state := range states { + if state.fdel { + continue + } + if loop := findLoop(states, path, iblk, iblk); len(loop) > 0 { + order = append(order, loop...) + goto retry + } + } + panic("unreachable") + } + order = append(order, -1) + ret := make([]Info, n) + for i := 0; i < n; i++ { + iblk := order[i] + ret[iblk] = Info{states[iblk].kind(), order[i+1]} + } + return ret +} + +func isEnd(blk *ssa.BasicBlock) bool { + // Note: skip recover block + return len(blk.Succs) == 0 && (len(blk.Preds) > 0 || blk.Index == 0) +} + +// ----------------------------------------------------------------------------- diff --git a/cl/blocks/block_test.go b/cl/blocks/block_test.go new file mode 100644 index 00000000..ee171acf --- /dev/null +++ b/cl/blocks/block_test.go @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package blocks + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/token" + "go/types" + "log" + "os" + "path" + "strings" + "testing" + + "github.com/goplus/gogen/packages" + "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" + + llssa "github.com/goplus/llgo/ssa" +) + +func TestTestdefer(t *testing.T) { + // debug = true + fromDir(t, "loop", "../_testdefer") +} + +func fromDir(t *testing.T, sel, relDir string) { + dir, err := os.Getwd() + if err != nil { + t.Fatal("Getwd failed:", err) + } + dir = path.Join(dir, relDir) + fis, err := os.ReadDir(dir) + if err != nil { + t.Fatal("ReadDir failed:", err) + } + for _, fi := range fis { + name := fi.Name() + if !fi.IsDir() || strings.HasPrefix(name, "_") { + continue + } + t.Run(name, func(t *testing.T) { + testFrom(t, dir+"/"+name, sel) + }) + } +} + +func testFrom(t *testing.T, pkgDir, sel string) { + if sel != "" && !strings.Contains(pkgDir, sel) { + return + } + log.Println("Parsing", pkgDir) + in := pkgDir + "/in.go" + out := pkgDir + "/out.txt" + b, err := os.ReadFile(out) + if err != nil { + t.Fatal("ReadFile failed:", err) + } + expected := string(b) + testBlockInfo(t, nil, in, expected) +} + +func testBlockInfo(t *testing.T, src any, fname, expected string) { + t.Helper() + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, fname, src, parser.ParseComments) + if err != nil { + t.Fatal("ParseFile failed:", err) + } + files := []*ast.File{f} + name := f.Name.Name + pkg := types.NewPackage(name, name) + imp := packages.NewImporter(fset) + foo, _, err := ssautil.BuildPackage( + &types.Config{Importer: imp}, fset, pkg, files, ssa.SanityCheckFunctions) + if err != nil { + t.Fatal("BuildPackage failed:", err) + } + foo.WriteTo(os.Stderr) + + for _, member := range foo.Members { + switch f := member.(type) { + case *ssa.Function: + if f.Name() == "main" { + f.WriteTo(os.Stderr) + infos := Infos(f.Blocks) + if v := resultOf(infos); v != expected { + t.Fatalf("\n==> got:\n%s\n==> expected:\n%s\n", v, expected) + } + return + } + } + } +} + +func resultOf(infos []Info) string { + var b bytes.Buffer + i := 0 + for { + fmt.Fprintf(&b, "%2d: %s\n", i, kinds[infos[i].Kind]) + if i = infos[i].Next; i < 0 { + break + } + } + return b.String() +} + +var kinds = [...]string{ + llssa.DeferAlways: "always", + llssa.DeferInCond: "cond", + llssa.DeferInLoop: "loop", +}