cl/blocks
This commit is contained in:
20
cl/_testdefer/loop/in.go
Normal file
20
cl/_testdefer/loop/in.go
Normal file
@@ -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")
|
||||||
|
}
|
||||||
7
cl/_testdefer/loop/out.txt
Normal file
7
cl/_testdefer/loop/out.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
0: always
|
||||||
|
2: loop
|
||||||
|
4: loop
|
||||||
|
5: loop
|
||||||
|
3: cond
|
||||||
|
6: cond
|
||||||
|
1: cond
|
||||||
18
cl/_testdefer/multiret/in.go
Normal file
18
cl/_testdefer/multiret/in.go
Normal file
@@ -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")
|
||||||
|
}
|
||||||
4
cl/_testdefer/multiret/out.txt
Normal file
4
cl/_testdefer/multiret/out.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
0: always
|
||||||
|
1: cond
|
||||||
|
2: cond
|
||||||
|
3: cond
|
||||||
52
cl/_testdefer/print/in.go
Normal file
52
cl/_testdefer/print/in.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
0
cl/_testdefer/print/out.txt
Normal file
0
cl/_testdefer/print/out.txt
Normal file
17
cl/_testdefer/singleret/in.go
Normal file
17
cl/_testdefer/singleret/in.go
Normal file
@@ -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")
|
||||||
|
}
|
||||||
5
cl/_testdefer/singleret/out.txt
Normal file
5
cl/_testdefer/singleret/out.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
0: always
|
||||||
|
1: cond
|
||||||
|
2: cond
|
||||||
|
4: cond
|
||||||
|
3: always
|
||||||
156
cl/blocks/block.go
Normal file
156
cl/blocks/block.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
129
cl/blocks/block_test.go
Normal file
129
cl/blocks/block_test.go
Normal file
@@ -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",
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user