cl/blocks

This commit is contained in:
xushiwei
2024-06-05 13:48:11 +08:00
parent f1a4af013a
commit 519e69a7f8
10 changed files with 408 additions and 0 deletions

20
cl/_testdefer/loop/in.go Normal file
View 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")
}

View File

@@ -0,0 +1,7 @@
0: always
2: loop
4: loop
5: loop
3: cond
6: cond
1: cond

View 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")
}

View File

@@ -0,0 +1,4 @@
0: always
1: cond
2: cond
3: cond

52
cl/_testdefer/print/in.go Normal file
View 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
}
}

View File

View 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")
}

View File

@@ -0,0 +1,5 @@
0: always
1: cond
2: cond
4: cond
3: always

156
cl/blocks/block.go Normal file
View 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
View 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",
}